@s2script/sdk 0.0.1 → 0.3.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 ADDED
@@ -0,0 +1,176 @@
1
+ /**
2
+ * @s2script/entity — author-time type stubs for the injected entity API.
3
+ * NO runtime code: the engine injects the implementation at load time.
4
+ */
5
+ import type { HookResultValue } from "./events";
6
+
7
+ /**
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.
11
+ */
12
+ export declare class EntityRef {
13
+ readonly index: number;
14
+ readonly serial: number;
15
+ /** This entity's targetname (`CEntityIdentity::m_name`) — e.g. a map trigger's `"map_start"`. `""` if
16
+ * the entity has no targetname; `null` if the ref is stale/invalid. */
17
+ readonly name: string | null;
18
+ constructor(index: number, serial: number);
19
+ /** True iff the slot's current serial still matches the captured serial. */
20
+ isValid(): boolean;
21
+ /** Read an i32 at `offset` bytes into the entity, or null if the ref is stale. */
22
+ readInt32(offset: number): number | null;
23
+ /** Write an i32 at `offset` bytes into the entity. Returns true on success, false if stale. */
24
+ writeInt32(offset: number, value: number): boolean;
25
+ /** Read an f32 at `offset` bytes into the entity, or null if the ref is stale. */
26
+ readFloat32(offset: number): number | null;
27
+ /** Write an f32 at `offset`. Returns true on success, false if stale. */
28
+ writeFloat32(offset: number, value: number): boolean;
29
+ /** Read a bool at `offset`, or null if the ref is stale. */
30
+ readBool(offset: number): boolean | null;
31
+ /** Write a bool at `offset`. Returns true on success, false if stale. */
32
+ writeBool(offset: number, value: boolean): boolean;
33
+ /** Read an i8 (sign-extended to number) at `offset`, or null if the ref is stale. */
34
+ readInt8(offset: number): number | null;
35
+ /** Read an i16 (sign-extended to number) at `offset`, or null if the ref is stale. */
36
+ readInt16(offset: number): number | null;
37
+ /** Read a u8 at `offset`, or null if the ref is stale. */
38
+ readUInt8(offset: number): number | null;
39
+ /** Read a u16 at `offset`, or null if the ref is stale. */
40
+ readUInt16(offset: number): number | null;
41
+ /** Read a u32 at `offset`, or null if the ref is stale. */
42
+ readUInt32(offset: number): number | null;
43
+ /** Write an i8 (truncated) at `offset`. Returns true on success, false if stale. */
44
+ writeInt8(offset: number, value: number): boolean;
45
+ /** Write an i16 (truncated) at `offset`. Returns true on success, false if stale. */
46
+ writeInt16(offset: number, value: number): boolean;
47
+ /** Write a u8 (truncated) at `offset`. Returns true on success, false if stale. */
48
+ writeUInt8(offset: number, value: number): boolean;
49
+ /** Write a u16 (truncated) at `offset`. Returns true on success, false if stale. */
50
+ writeUInt16(offset: number, value: number): boolean;
51
+ /** Write a u32 at `offset`. Returns true on success, false if stale. */
52
+ writeUInt32(offset: number, value: number): boolean;
53
+ /** Read a u64 at `offset` as a BigInt, or null if the ref is stale. */
54
+ readUInt64(offset: number): bigint | null;
55
+ /** Read an i64 at `offset` as a BigInt, or null if the ref is stale. */
56
+ readInt64(offset: number): bigint | null;
57
+ /** Read an f64 at `offset`, or null if the ref is stale. */
58
+ readFloat64(offset: number): number | null;
59
+ /** Read a NUL-terminated string (up to `maxLen` bytes) at `offset`, or null if the ref is stale. */
60
+ readString(offset: number, maxLen: number): string | null;
61
+ /** Write a bounded, NUL-terminated string into an inline `char[maxLen]` field at `offset` (truncated to
62
+ * `maxLen-1` bytes + always NUL-terminated). Returns true on success, false if the ref is stale. */
63
+ writeString(offset: number, maxLen: number, value: string): boolean;
64
+ /** Read `count` (1..4) contiguous float32s at `offset` into a number[], or null if the ref is stale. */
65
+ readFloats(offset: number, count: number): number[] | null;
66
+ /** Follow a chain of pointer derefs (each an offset into the current target), then read `count` (1..4) floats
67
+ * at `finalOff` into a number[]. All in-core (raw pointers never cross); null if the root is stale or any hop
68
+ * is null. */
69
+ readFloatsChain(ptrOffs: number[], finalOff: number, count: number): number[] | null;
70
+ /** 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. */
72
+ 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
74
+ * false on a stale ref or a null hop. Used to clear a flag on a pointer-referenced sub-object. */
75
+ writeInt32Via(pathOffs: number[], finalOff: number, value: number): boolean;
76
+ readInt8Via(pathOffs: number[], finalOff: number): number | null;
77
+ readInt16Via(pathOffs: number[], finalOff: number): number | null;
78
+ readUInt8Via(pathOffs: number[], finalOff: number): number | null;
79
+ readUInt16Via(pathOffs: number[], finalOff: number): number | null;
80
+ readUInt32Via(pathOffs: number[], finalOff: number): number | null;
81
+ readFloat32Via(pathOffs: number[], finalOff: number): number | null;
82
+ readBoolVia(pathOffs: number[], finalOff: number): boolean | null;
83
+ readUInt64Via(pathOffs: number[], finalOff: number): bigint | null;
84
+ readInt64Via(pathOffs: number[], finalOff: number): bigint | null;
85
+ 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;
87
+ * returns false on a stale ref, an unresolved hop, or a bad offset/kind. Does NOT notifyStateChanged —
88
+ * the caller decides (many sub-object fields, e.g. the fire gate, are server-authoritative).
89
+ * (`writeInt32Via` is declared above alongside `readInt32Via`.) */
90
+ writeFloat32Via(pathOffs: number[], finalOff: number, value: number): boolean;
91
+ writeBoolVia(pathOffs: number[], finalOff: number, value: boolean): boolean;
92
+ /** Read a `CEntityHandle` at `offset`, decode it, and return a live `EntityRef` — or null if stale/invalid. */
93
+ readHandle(offset: number): EntityRef | null;
94
+ /** Notify the engine that the field at `offset` changed (triggers network replication). No-op if stale. */
95
+ notifyStateChanged(offset: number): void;
96
+ /** DispatchSpawn this created entity. With keyvalues, the entity's own Spawn() parses them (the
97
+ * SourceMod DispatchKeyValue / CSSharp DispatchSpawn(kv) mechanism). Returns false if stale,
98
+ * unresolved, or the keyvalue map is rejected (non-finite number, unsupported value type, empty
99
+ * key) — a rejection spawns NOTHING (never a partially-configured entity). */
100
+ spawn(keyvalues?: EntityKeyValueMap): boolean;
101
+ /** Teleport this entity. origin/angles/velocity are [x,y,z] triples; any may be null. False if stale. */
102
+ teleport(origin: number[] | null, angles?: number[] | null, velocity?: number[] | null): boolean;
103
+ /** Remove (UTIL_Remove) this entity from the world. Returns false if stale/unresolved. */
104
+ remove(): boolean;
105
+ /** Register this entity's collision bounds in the spatial partition (zones real-trigger backend).
106
+ * A runtime-created trigger_multiple needs this to fire touch; false if the op is unavailable. */
107
+ activateCollision(): boolean;
108
+ /** Give this entity a model (and its collision) via `CBaseEntity::SetModel`. A runtime
109
+ * `trigger_multiple` needs a model to build the physics volume that fires touch. Returns false
110
+ * if the op is unavailable or the ref is stale. */
111
+ setModel(name: string): boolean;
112
+ /** Read a CUtlVector<CHandle> at (ptrOffs chain -> vectorOff) as live serial-gated EntityRefs.
113
+ * Follows the pointer chain, reads count@+0 / elements@+8, caps at maxCount. [] if stale/unresolved. */
114
+ readHandleVector(ptrOffs: number[], vectorOff: number, maxCount?: number): EntityRef[];
115
+ /** Fire an entity input (e.g. "Kill"/"Ignite"/"SetHealth"/"Enable"/"Open"/"FireUser1"/"AddOutput")
116
+ * via `AddEntityIOEvent` — the game's own input-firing path (map I/O and `FireOutputInternal` route
117
+ * through it). `value` is the input's string argument (Source parses it per the input's field type;
118
+ * omit for a value-less input). `activator`/`caller` are optional entities threaded through to any
119
+ * output the input triggers. `delay` queues the event on the engine's same-tick I/O pump (0 = fires
120
+ * this same tick — NOT synchronous-within-the-call). Returns false with no op / a stale ref. */
121
+ acceptInput(input: string, value?: string, activator?: EntityRef, caller?: EntityRef, delay?: number): boolean;
122
+ }
123
+
124
+ /** Keyvalues for a CEntityKeyValues-configured spawn. Inference: string -> SetString,
125
+ * boolean -> SetBool, integer (int32) -> SetInt, other finite number -> SetFloat.
126
+ * Keys are case-insensitive (hashed via MurmurHash2LowerCase, the engine's own keying). */
127
+ export type EntityKeyValueMap = { [key: string]: string | number | boolean };
128
+
129
+ /** Create a new entity by class name. WITHOUT keyvalues: create only — set fields, then call
130
+ * `.spawn()`. WITH keyvalues: create + DispatchSpawn(keyvalues) in one call — a non-null result is
131
+ * 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. */
133
+ export declare function createEntity(className: string, keyvalues?: EntityKeyValueMap): EntityRef | null;
134
+
135
+ /** The payload delivered to an `Entity.onOutput` handler. */
136
+ export interface OutputEvent {
137
+ /** The output's name (e.g. "OnTrigger", "OnPressed", "OnStartTouch"). */
138
+ output: string;
139
+ /** The entity that activated the chain leading to this output firing, or null. */
140
+ activator: EntityRef | null;
141
+ /** The entity that owns/fired this output (the `this` of `FireOutputInternal`), or null. */
142
+ caller: EntityRef | null;
143
+ /** The output's value, formatted as a string (MVP — typed `CVariant` marshalling is deferred). */
144
+ value: string;
145
+ /** The output's fire delay in seconds (0 = same-tick). */
146
+ delay: number;
147
+ }
148
+
149
+ /**
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.
154
+ */
155
+ 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. */
175
+ findByClass(className: string): EntityRef[];
176
+ };
package/events.d.ts ADDED
@@ -0,0 +1,46 @@
1
+ /** @s2script/events — author-time stubs for the injected game-event API. NO runtime code. */
2
+
3
+ /** A live game-event accessor. Valid ONLY during the synchronous handler — read fields before any `await`;
4
+ * a stashed GameEvent used later reads defaults. The raw engine event never crosses to JS. */
5
+ export declare class GameEvent {
6
+ readonly name: string;
7
+ getInt(key: string): number;
8
+ getFloat(key: string): number;
9
+ getBool(key: string): boolean;
10
+ getString(key: string): string;
11
+ /** A 64-bit field as a decimal string (SM-parity, wire-safe). */
12
+ getUint64(key: string): string;
13
+ /** A player field (e.g. "userid"/"attacker") as a 0-based slot, or -1 if absent. Resolve with Player.fromSlot. */
14
+ getPlayerSlot(key: string): number;
15
+ setInt(key: string, value: number): void;
16
+ setFloat(key: string, value: number): void;
17
+ setBool(key: string, value: boolean): void;
18
+ setString(key: string, value: string): void;
19
+ /** Set a 64-bit field from a decimal string (SM-parity, wire-safe). */
20
+ setUint64(key: string, value: string): void;
21
+ }
22
+
23
+ /** Collapsed pre-hook result. Return from an `onPre` handler; `Handled`/`Stop` suppress the client
24
+ * broadcast (server still processes). Returning nothing = `Continue`. */
25
+ export declare const HookResult: { readonly Continue: 0; readonly Changed: 1; readonly Handled: 2; readonly Stop: 3 };
26
+ export type HookResultValue = 0 | 1 | 2 | 3;
27
+
28
+ 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
+ /** Fire a game event. Returns the engine FireEvent result. */
43
+ fire(name: string, fields?: Record<string, number | string | boolean | bigint>, dontBroadcast?: boolean): boolean;
44
+ /** Fire a game event to ONE client (SourceMod FireToClient). Same field type-inference as `fire`. Returns false on miss. */
45
+ fireToClient(slot: number, name: string, fields?: Record<string, string | number | boolean | bigint>): boolean;
46
+ };
package/frame.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @s2script/frame — author-time type stubs for the per-frame subscription API.
3
+ * NO runtime code: the engine injects the implementation at load time.
4
+ */
5
+
6
+ export interface SubscribeOptions {
7
+ priority?: number;
8
+ }
9
+
10
+ export declare const OnGameFrame: {
11
+ /** Register a callback that fires every game frame. */
12
+ subscribe(fn: () => void, opts?: SubscribeOptions): void;
13
+ };
package/globals.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ // @s2script/globals — ambient declarations for the globals the engine injects into EVERY plugin
2
+ // context. NO runtime code, NO import/export (this is a global/script .d.ts). Included by the
3
+ // typecheck gate as a root file so plugins that use these globals WITHOUT importing type-check against
4
+ // the real sandbox — NOT lib.dom (the sandbox has no window/document/etc.).
5
+
6
+ /** The engine-injected console (a subset of the browser/node console). */
7
+ declare const console: {
8
+ log(...data: any[]): void;
9
+ error(...data: any[]): void;
10
+ warn(...data: any[]): void;
11
+ info(...data: any[]): void;
12
+ };
package/http.d.ts ADDED
@@ -0,0 +1,11 @@
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; }
3
+ export interface Response {
4
+ readonly status: number; readonly ok: boolean; readonly statusText: string;
5
+ readonly headers: Record<string, string>;
6
+ text(): string;
7
+ json<T = unknown>(): T;
8
+ }
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. */
11
+ export declare function fetch(url: string, options?: FetchOptions): Promise<Response>;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * @s2script/interfaces — author-time type stubs for typed inter-plugin interfaces.
3
+ * NO runtime code: the engine injects the implementation at load time.
4
+ */
5
+
6
+ /**
7
+ * Handle returned by {@link publishInterface}: lets the producer emit forwarded
8
+ * events to every plugin subscribed to this interface via its `on(event, …)`.
9
+ */
10
+ export interface PublishHandle {
11
+ /** Emit a forwarded event to all consumers subscribed via `interface.on(event, …)`. */
12
+ emit(event: string, payload: unknown): void;
13
+ }
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 ADDED
@@ -0,0 +1,27 @@
1
+ /**
2
+ * @s2script/math — author-time type stubs for the injected math value types.
3
+ * NO runtime code: the engine injects the implementation (core prelude) at load time.
4
+ */
5
+
6
+ /** A 3-component vector value (a copied snapshot; never a live pointer). */
7
+ export declare class Vector {
8
+ x: number;
9
+ y: number;
10
+ z: number;
11
+ constructor(x: number, y: number, z: number);
12
+ /** Euclidean magnitude — e.g. speed from a velocity vector. */
13
+ length(): number;
14
+ toString(): string;
15
+ }
16
+
17
+ /** A Source 2 Euler angle value (x=pitch, y=yaw, z=roll), a copied snapshot. */
18
+ export declare class QAngle {
19
+ x: number;
20
+ y: number;
21
+ z: number;
22
+ constructor(x: number, y: number, z: number);
23
+ toString(): string;
24
+ }
25
+
26
+ /** The unit forward-direction vector for a Euler angle (pitch=`a.x`, yaw=`a.y`; ignores roll). */
27
+ export declare function forwardVector(a: QAngle): Vector;
package/menu.d.ts ADDED
@@ -0,0 +1,38 @@
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
+
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; }
7
+
8
+ export declare class Menu {
9
+ constructor(title?: string);
10
+ title: string;
11
+ /** Which registered renderer to use. Falls back to Chat if the style's renderer is unregistered. */
12
+ style: MenuStyle;
13
+ /** Append an auto Exit control (default true). */
14
+ exitButton: boolean;
15
+ /** When true, a renderer that supports it (the CS2 center renderer) freezes the player's movement
16
+ * while the menu is open and restores it on close — buttons still register, so WASD nav still works.
17
+ * The chat renderer ignores it. Default false (movement allowed, the normal behavior). */
18
+ freezePlayer: boolean;
19
+ /** (info, display) — `info` is returned to onSelect; `display` is shown. */
20
+ addItem(info: string, display: string, opts?: { disabled?: boolean }): void;
21
+ onSelect(handler: (e: MenuSelectEvent) => void): void;
22
+ onCancel(handler: (e: MenuCancelEvent) => void): void;
23
+ /** Show to a 0-based slot for `seconds` (0 = until selection/cancel/disconnect). */
24
+ display(slot: number, seconds?: number): void;
25
+ /** Close an open menu for `slot` early. */
26
+ close(slot: number): void;
27
+ /** Register a display backend under a MenuStyle value (used by the CS2 center renderer). */
28
+ static registerRenderer(name: string, renderer: MenuRenderer): void;
29
+ }
30
+
31
+ export interface MenuSession {
32
+ readonly slot: number;
33
+ view(): { title: string; lines: MenuLine[]; page: number; pageCount: number; exit: boolean };
34
+ pickNumber(n: number): void;
35
+ moveUp(): void; moveDown(): void; confirm(): void; cancel(): void;
36
+ }
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 ADDED
@@ -0,0 +1,19 @@
1
+ /** @s2script/net — engine-generic raw TCP + UDP sockets (binary), off the game thread. */
2
+ export interface TcpSocket {
3
+ send(data: Uint8Array | string): void;
4
+ onData(handler: (bytes: Uint8Array) => void): void;
5
+ onClose(handler: () => void): void;
6
+ onError(handler: (err: string) => void): void;
7
+ close(): void;
8
+ }
9
+ export interface UdpSocket {
10
+ sendTo(host: string, port: number, data: Uint8Array | string): void;
11
+ onMessage(handler: (from: { host: string; port: number }, bytes: Uint8Array) => void): void;
12
+ close(): void;
13
+ }
14
+ export declare const Net: {
15
+ /** Connect a TCP client. Rejects on connect failure. */
16
+ connectTcp(host: string, port: number): Promise<TcpSocket>;
17
+ /** Bind a UDP socket on an ephemeral local port. */
18
+ udp(): Promise<UdpSocket>;
19
+ };
package/package.json CHANGED
@@ -1,13 +1,125 @@
1
1
  {
2
2
  "name": "@s2script/sdk",
3
- "version": "0.0.1",
4
- "description": "s2script SDK — types + CLI for building Source 2 / CS2 plugins. Placeholder release that bootstraps the package name + OIDC trusted publishing; the real per-capability type subpaths (@s2script/sdk/entity, …) and the `s2s` CLI land in 0.1.0 via the consolidation (Part C).",
3
+ "version": "0.3.0",
4
+ "description": "s2script SDK — the builtin capability types (imported as @s2script/sdk/entity, …) plus the `s2s` CLI for building Source 2 / CS2 plugins.",
5
+ "type": "module",
6
+ "bin": {
7
+ "s2s": "dist/cli.js"
8
+ },
9
+ "scripts": {
10
+ "prepare": "node build.mjs",
11
+ "build": "node build.mjs",
12
+ "test": "node --experimental-strip-types --no-warnings --test test/*.test.mjs"
13
+ },
14
+ "dependencies": {
15
+ "esbuild": "^0.25.0",
16
+ "adm-zip": "^0.5.16",
17
+ "typescript": "^5.6.0"
18
+ },
19
+ "devDependencies": {
20
+ "@types/adm-zip": "^0.5.7"
21
+ },
5
22
  "publishConfig": {
6
23
  "access": "public"
7
24
  },
8
25
  "files": [
26
+ "dist",
27
+ "*.d.ts",
9
28
  "README.md"
10
29
  ],
30
+ "exports": {
31
+ "./admin": {
32
+ "types": "./admin.d.ts"
33
+ },
34
+ "./bans": {
35
+ "types": "./bans.d.ts"
36
+ },
37
+ "./chat": {
38
+ "types": "./chat.d.ts"
39
+ },
40
+ "./clients": {
41
+ "types": "./clients.d.ts"
42
+ },
43
+ "./commands": {
44
+ "types": "./commands.d.ts"
45
+ },
46
+ "./config": {
47
+ "types": "./config.d.ts"
48
+ },
49
+ "./console": {
50
+ "types": "./console.d.ts"
51
+ },
52
+ "./cookies": {
53
+ "types": "./cookies.d.ts"
54
+ },
55
+ "./damage": {
56
+ "types": "./damage.d.ts"
57
+ },
58
+ "./db": {
59
+ "types": "./db.d.ts"
60
+ },
61
+ "./entity": {
62
+ "types": "./entity.d.ts"
63
+ },
64
+ "./events": {
65
+ "types": "./events.d.ts"
66
+ },
67
+ "./frame": {
68
+ "types": "./frame.d.ts"
69
+ },
70
+ "./http": {
71
+ "types": "./http.d.ts"
72
+ },
73
+ "./interfaces": {
74
+ "types": "./interfaces.d.ts"
75
+ },
76
+ "./math": {
77
+ "types": "./math.d.ts"
78
+ },
79
+ "./menu": {
80
+ "types": "./menu.d.ts"
81
+ },
82
+ "./net": {
83
+ "types": "./net.d.ts"
84
+ },
85
+ "./plugins": {
86
+ "types": "./plugins.d.ts"
87
+ },
88
+ "./server": {
89
+ "types": "./server.d.ts"
90
+ },
91
+ "./sound": {
92
+ "types": "./sound.d.ts"
93
+ },
94
+ "./timers": {
95
+ "types": "./timers.d.ts"
96
+ },
97
+ "./topmenu": {
98
+ "types": "./topmenu.d.ts"
99
+ },
100
+ "./trace": {
101
+ "types": "./trace.d.ts"
102
+ },
103
+ "./translations": {
104
+ "types": "./translations.d.ts"
105
+ },
106
+ "./transmit": {
107
+ "types": "./transmit.d.ts"
108
+ },
109
+ "./usercmd": {
110
+ "types": "./usercmd.d.ts"
111
+ },
112
+ "./usermessages": {
113
+ "types": "./usermessages.d.ts"
114
+ },
115
+ "./votes": {
116
+ "types": "./votes.d.ts"
117
+ },
118
+ "./ws": {
119
+ "types": "./ws.d.ts"
120
+ },
121
+ "./package.json": "./package.json"
122
+ },
11
123
  "repository": {
12
124
  "type": "git",
13
125
  "url": "https://github.com/GabeHirakawa/s2script.git"
package/plugins.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ /** @s2script/plugins — runtime plugin management (the SM `sm plugins` backend). NO runtime code (injected). */
2
+ export interface PluginInfo {
3
+ /** the plugin id (from its manifest). */
4
+ readonly id: string;
5
+ /** true = running; false = manually unloaded (on disk but not running, via `unload`). */
6
+ readonly loaded: boolean;
7
+ }
8
+ export declare const Plugins: {
9
+ /** Every loaded/unloaded plugin. */
10
+ list(): PluginInfo[];
11
+ /** Unload a running plugin (deferred to the next frame drain). false if it isn't loaded. It stays
12
+ * unloaded (not auto-reloaded by the file-watcher) until `load`/`reload`. */
13
+ unload(id: string): boolean;
14
+ /** Reload a plugin (unload + load from disk, deferred). false if the id is unknown. */
15
+ reload(id: string): boolean;
16
+ /** Load a previously-unloaded plugin (deferred). false if it isn't currently unloaded. */
17
+ load(id: string): boolean;
18
+ };
package/server.d.ts ADDED
@@ -0,0 +1,43 @@
1
+ /** @s2script/server — server control (run console commands, query map validity). NO runtime code (injected at load). */
2
+ export declare const Server: {
3
+ /** Run `cmd` at the server console (queued; executes next frame). */
4
+ command(cmd: string): void;
5
+ /** Whether `map` is an installed, valid map file. */
6
+ isMapValid(map: string): boolean;
7
+ /** A cvar's current value as a string. `""` if the cvar doesn't exist (or an unsupported type → `"<type>"`). */
8
+ getCvar(name: string): string;
9
+ /**
10
+ * Set a cvar via the server console (so any type is accepted). SECURITY: this builds and runs a console
11
+ * command (`<name> <value>`), which the console splits on `;` — treat `value` like `command()` input and
12
+ * sanitize/quote any untrusted value to avoid command injection. Queued: applies next frame.
13
+ */
14
+ setCvar(name: string, value: string): void;
15
+ /**
16
+ * Register a plugin-owned ConVar (CSSharp FakeConVar / SM CreateConVar parity). Idempotent —
17
+ * re-registering an existing name is a no-op success, and the cvar + its value persist across
18
+ * plugin reloads (SourceMod parity). The shim adds FCVAR_RELEASE (customer-visible); `flags`
19
+ * are additive raw FCVAR bits. Read the value with `getCvar`; set it with `setCvar`/the console.
20
+ * `min`/`max` apply to numeric types only.
21
+ */
22
+ registerCvar(name: string, opts: {
23
+ type: "bool" | "int" | "float" | "string";
24
+ default: boolean | number | string;
25
+ help?: string;
26
+ flags?: number;
27
+ min?: number;
28
+ max?: number;
29
+ }): boolean;
30
+ /**
31
+ * Subscribe to map start (fires on every server map startup — the framework event that replaces
32
+ * polling `Server.mapName` on OnGameFrame). Boot-loaded plugins receive the first map's fire;
33
+ * a plugin hot-loaded mid-map does NOT get a synthetic fire for the current map — read
34
+ * `Server.mapName` at load. Torn down automatically on plugin unload.
35
+ */
36
+ onMapStart(handler: (mapName: string) => void): void;
37
+ /** The server's configured max client count (`GetMaxClients()`). `0` if unavailable. */
38
+ readonly maxPlayers: number;
39
+ /** The current map name (`GetMapName()`, the BSP). `""` if unavailable. */
40
+ readonly mapName: string;
41
+ /** The current map time in seconds (`GetGlobals()->curtime`). `0` if unavailable. */
42
+ readonly gameTime: number;
43
+ };
package/sound.d.ts ADDED
@@ -0,0 +1,35 @@
1
+ /** @s2script/sound — engine-generic sound: emit a named SoundEvent + register custom precache paths. */
2
+ import type { EntityRef } from "./entity";
3
+
4
+ export interface SoundEmitOptions {
5
+ /** Source entity (serial-gated; a stale ref emits nothing and returns 0). Omitted -> worldspawn
6
+ * (a global/2D sound). */
7
+ entity?: EntityRef;
8
+ /** Recipient player slots. Omitted -> every valid client. Bot slots are always skipped
9
+ * (no netchannel); an all-bot-skipped set still emits to nobody (a real, safe engine call),
10
+ * whereas requesting NO recipients (an empty array) returns 0 without emitting. */
11
+ recipients?: number[];
12
+ /** Volume in [0, 1]. Default 1.0 (out-of-range/NaN clamps to 1.0). */
13
+ volume?: number;
14
+ }
15
+
16
+ /** Block-scoped precache context — valid ONLY during the onPrecache dispatch. Synchronous use
17
+ * only: a stashed context used after the handler returns (or past an await) is a no-op `false`
18
+ * (the engine manifest is gone). */
19
+ export interface PrecacheContext {
20
+ /** Add a resource path (e.g. "soundevents/mypack.vsndevts") to the session resource manifest.
21
+ * True iff the engine accepted the add. */
22
+ add(path: string): boolean;
23
+ }
24
+
25
+ export declare const Sound: {
26
+ /** Play a named SoundEvent (the engine resolves name->hash; built-in soundevents need no
27
+ * precache). Returns the engine sound GUID (nonzero) or 0 on failure (unresolved engine
28
+ * function / stale source entity / an empty `recipients` array). An all-bot-skipped recipient
29
+ * set still emits to nobody (a real engine call, may return a nonzero GUID). */
30
+ emit(name: string, opts?: SoundEmitOptions): number;
31
+ /** Subscribe to the session resource-manifest build (fires at map load / mapchange). Register
32
+ * custom .vsndevts/.vsnd content here so a later emit can play it. A plugin hot-loaded mid-map
33
+ * gets its first fire at the NEXT map change. */
34
+ onPrecache(handler: (ctx: PrecacheContext) => void): void;
35
+ };
package/timers.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * @s2script/timers — author-time type stubs for the async timing API.
3
+ * NO runtime code: the engine injects the implementation at load time.
4
+ */
5
+
6
+ /** Await a delay of `ms` milliseconds before continuing. */
7
+ export declare function delay(ms: number): Promise<void>;
8
+ /** Yield to the next microtick. */
9
+ export declare function nextTick(): Promise<void>;
10
+ /** Yield until the next game frame. */
11
+ export declare function nextFrame(): Promise<void>;
12
+ /**
13
+ * Block the current thread (fiber) for `ms` milliseconds.
14
+ * Only valid inside a threadSleep-capable fiber context.
15
+ */
16
+ export declare function threadSleep(ms: number): void;