@s2script/sdk 0.8.0 → 0.10.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@s2script/sdk",
3
- "version": "0.8.0",
3
+ "version": "0.10.0",
4
4
  "license": "MIT OR Apache-2.0",
5
5
  "description": "s2script SDK — the builtin capability types (imported as @s2script/sdk/entity, …) plus the `s2s` CLI for building Source 2 / CS2 plugins.",
6
6
  "type": "module",
@@ -30,7 +30,8 @@
30
30
  "*.d.ts",
31
31
  "README.md",
32
32
  "LICENSE-MIT",
33
- "LICENSE-APACHE"
33
+ "LICENSE-APACHE",
34
+ "contracts"
34
35
  ],
35
36
  "exports": {
36
37
  "./admin": {
@@ -111,19 +112,28 @@
111
112
  "./transmit": {
112
113
  "types": "./transmit.d.ts"
113
114
  },
115
+ "./unsafe": {
116
+ "types": "./unsafe.d.ts"
117
+ },
114
118
  "./usercmd": {
115
119
  "types": "./usercmd.d.ts"
116
120
  },
117
121
  "./usermessages": {
118
122
  "types": "./usermessages.d.ts"
119
123
  },
124
+ "./voice": {
125
+ "types": "./voice.d.ts"
126
+ },
120
127
  "./votes": {
121
128
  "types": "./votes.d.ts"
122
129
  },
123
130
  "./ws": {
124
131
  "types": "./ws.d.ts"
125
132
  },
126
- "./package.json": "./package.json"
133
+ "./package.json": "./package.json",
134
+ "./contracts/workshop": {
135
+ "types": "./contracts/workshop.d.ts"
136
+ }
127
137
  },
128
138
  "repository": {
129
139
  "type": "git",
package/server.d.ts CHANGED
@@ -35,6 +35,28 @@ export declare const Server: {
35
35
  min?: number;
36
36
  max?: number;
37
37
  }): boolean;
38
+ /**
39
+ * Watch a cvar for changes (SourceMod `HookConVarChange`, ModSharp's cvar change hook).
40
+ *
41
+ * `name` is a cvar name, or `"*"` for every cvar. The handler receives the cvar's name and its
42
+ * new and old values as strings — for `"*"` the name tells you which one moved.
43
+ *
44
+ * NOTIFY-only: the engine's global change callback runs **after** the value has been applied, so
45
+ * a handler cannot veto a change; returning anything is ignored. A handler that throws is logged
46
+ * and contained, and the remaining handlers still run.
47
+ *
48
+ * Subscriptions are ledgered — unload removes them whether or not `dispose()` is called.
49
+ *
50
+ * @example
51
+ * import { Server } from "@s2script/sdk/server";
52
+ * Server.onCvarChange("mp_friendlyfire", (name, next, prev) => {
53
+ * console.log(`${name}: ${prev} -> ${next}`);
54
+ * });
55
+ */
56
+ onCvarChange(
57
+ name: string,
58
+ handler: (name: string, newValue: string, oldValue: string) => void,
59
+ ): { dispose(): void };
38
60
  /** The server's configured max client count (`GetMaxClients()`). `0` if unavailable. */
39
61
  readonly maxPlayers: number;
40
62
  /** The current map name (`GetMapName()`, the BSP). `""` if unavailable. */
package/timers.d.ts CHANGED
@@ -20,3 +20,42 @@ export declare function nextFrame(): Promise<void>;
20
20
  * Only valid inside a threadSleep-capable fiber context.
21
21
  */
22
22
  export declare function threadSleep(ms: number): void;
23
+
24
+ /**
25
+ * A live callback timer, returned by {@link after} and {@link every}. Ledgered against the creating
26
+ * plugin: unload kills it whether or not `kill()` was called, so a repeating timer can never outlive
27
+ * its plugin and fire into a dead context.
28
+ */
29
+ export interface Timer {
30
+ /** False once the timer has fired (one-shot), been killed, or had its plugin unloaded. */
31
+ readonly alive: boolean;
32
+ /** Cancel it. Idempotent — returns false if it was already dead. Safe to call from inside the callback. */
33
+ kill(): boolean;
34
+ }
35
+
36
+ /**
37
+ * Run `fn` once after `ms` milliseconds (SourceMod `CreateTimer` without `TIMER_REPEAT`).
38
+ *
39
+ * Prefer {@link delay} when you can `await`; use this when you need something cancellable.
40
+ * A throwing callback is reported and contained — it never kills the frame or the timer system.
41
+ *
42
+ * @example
43
+ * import { after } from "@s2script/sdk/timers";
44
+ * const t = after(5000, () => console.log("5s later"));
45
+ * t.kill(); // ...unless cancelled first
46
+ */
47
+ export declare function after(ms: number, fn: () => void): Timer;
48
+
49
+ /**
50
+ * Run `fn` every `ms` milliseconds until killed (SourceMod `CreateTimer` with `TIMER_REPEAT`).
51
+ *
52
+ * The next firing is scheduled *after* the callback returns, so a slow callback cannot pile up.
53
+ * `ms` must be greater than 0 — a zero-interval repeat would re-arm every drain and starve the
54
+ * frame, so it throws rather than degrading.
55
+ *
56
+ * @example
57
+ * import { every } from "@s2script/sdk/timers";
58
+ * const tick = every(1000, () => console.log("tick"));
59
+ * // later: tick.kill();
60
+ */
61
+ export declare function every(ms: number, fn: () => void): Timer;
package/unsafe.d.ts ADDED
@@ -0,0 +1,35 @@
1
+ /**
2
+ * @s2script/sdk/unsafe — plugin-declared engine calls. NO runtime code: the engine injects the
3
+ * implementation at load (`__s2pkg_unsafe`).
4
+ *
5
+ * UNSAFE by design. A declared call reaches a real engine function. The framework validates every
6
+ * descriptor at load and degrades a failure to `null`, but it cannot prove your signature or vtable
7
+ * index names the function you meant. Requires manifest `permissions: ["engine:calls"]` AND an
8
+ * operator allow-list entry (see docs/superpowers/specs/2026-07-24-plugin-gamedata-design.md §6).
9
+ */
10
+
11
+ /**
12
+ * The calls this plugin declares. The base interface is EMPTY — `s2s build` generates
13
+ * `.s2script/gamedata.d.ts`, which augments this from your gamedata's `calls` section.
14
+ */
15
+ export interface EngineCalls {}
16
+
17
+ /**
18
+ * Plugin-declared engine calls, resolved from this plugin's own gamedata.
19
+ *
20
+ * @example
21
+ * import { Engine } from "@s2script/sdk/unsafe";
22
+ * // Resolve ONCE at load — null means the descriptor failed a load-time gate.
23
+ * const ignite = Engine.call("ignite");
24
+ * if (!ignite) console.log(`unavailable: ${Engine.status("ignite")}`);
25
+ */
26
+ export declare const Engine: {
27
+ /**
28
+ * The declared call, or `null` when its descriptor failed a load-time gate (signature miss,
29
+ * validator rejection, slot outside `.text`, missing platform entry, or the plugin is not
30
+ * operator-allow-listed). Guard once at load; the returned function is a plain callable.
31
+ */
32
+ call<K extends keyof EngineCalls>(name: K): EngineCalls[K] | null;
33
+ /** Why a descriptor is unavailable; `"available"` when it resolved. For diagnostics/operator reports. */
34
+ status(name: string): string;
35
+ };
package/voice.d.ts ADDED
@@ -0,0 +1,59 @@
1
+ /**
2
+ * @s2script/sdk/voice — who can hear whom. NO runtime code: the engine injects the implementation
3
+ * at load (`__s2pkg_voice`).
4
+ *
5
+ * Layered UNDER `Client.voiceMuted`: a muted sender is inaudible to everyone regardless of any rule
6
+ * here, so admin moderation always beats a gameplay rule.
7
+ *
8
+ * Rules are declarative state, not a callback. The engine's listen matrix is re-asserted
9
+ * continuously and the underlying hook fires per (receiver, sender) pair, so a per-pair JS callback
10
+ * would run up to 64x64 times per refresh. Set a rule; the shim enforces it.
11
+ */
12
+
13
+ /** Hot-path measurement counters returned by {@link Voice.stats}. */
14
+ export interface VoiceStats {
15
+ /** Listen-matrix decisions the engine has asked about. */
16
+ calls: number;
17
+ /** Senders currently carrying a merged rule. */
18
+ entries: number;
19
+ /** Times a rule actually silenced a pair. THE effect counter — a rule that never rewrites is not working. */
20
+ rewrites: number;
21
+ }
22
+
23
+ /**
24
+ * Per-(receiver, sender) voice hearability.
25
+ *
26
+ * @example
27
+ * import { Voice } from "@s2script/sdk/voice";
28
+ * // A dead player is heard only by other dead players.
29
+ * Voice.setAudibleTo(deadSlot, otherDeadSlots);
30
+ * // ...and on respawn, hand the slot back to the engine.
31
+ * Voice.reset(deadSlot);
32
+ */
33
+ export declare const Voice: {
34
+ /**
35
+ * This sender is audible ONLY to `receivers` (0-based slots). An empty array means audible to
36
+ * nobody — distinct from {@link Voice.reset}, which removes the rule and lets the engine decide.
37
+ * Rules from multiple plugins AND-merge, so another plugin can only ever narrow yours, never widen it.
38
+ *
39
+ * A rule is dropped automatically when the sender disconnects: slots are recycled, and a rule is
40
+ * about the player who occupied one, not the seat.
41
+ *
42
+ * Returns `false` — never throws — when the rule was NOT applied: voice control is degraded or its
43
+ * engine hook never installed on this build, `sender` is outside `[0, 64)`, or any element of
44
+ * `receivers` is. Note this differs from {@link import("./transmit").Transmit.setVisibleTo}, which
45
+ * throws `RangeError` on a bad slot; check the return value here rather than relying on a throw.
46
+ *
47
+ * @throws `TypeError` if `receivers` is not an array.
48
+ */
49
+ setAudibleTo(sender: number, receivers: readonly number[]): boolean;
50
+ /**
51
+ * Drop THIS plugin's rule for `sender`; other plugins' rules survive and are re-merged.
52
+ * `false` when you had no rule for that sender.
53
+ */
54
+ reset(sender: number): boolean;
55
+ /** Drop every rule this plugin owns. Unload does this automatically. */
56
+ resetAll(): void;
57
+ /** Hot-path counters, or null when the running shim predates this capability. */
58
+ stats(): VoiceStats | null;
59
+ };