@s2script/sdk 0.20.0 → 0.21.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/commands.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  /** @s2script/commands — register server commands. NO runtime code (injected at load). */
2
+ import type { PhraseKey } from "./phrases";
2
3
 
3
4
  import type { HookResultValue } from "./events";
4
5
 
@@ -72,9 +73,13 @@ export interface CommandInvocation {
72
73
  * is printed immediately. The server console (`callerSlot` `-1`) prints to the server console.
73
74
  */
74
75
  replyToConsole(message: string): void;
75
- /** reply to the caller, translated for THEIR language (SM's `%t` on the reply path). Soft-deps
76
- * `@s2script/translations` — degrades to the raw `key` if it isn't loaded. */
77
- replyT(key: string, ...args: (string | number)[]): void;
76
+ /** Reply to the caller, translated for THEIR language (SM's `%t` on the reply path). Soft-deps
77
+ * `@s2script/translations` — degrades to the raw `key` if it isn't loaded.
78
+ *
79
+ * `key` is checked against this plugin's phrase file plus the shared one (see
80
+ * `@s2script/sdk/phrases`); it widens to `string` in a plugin that has no phrase file, so this is
81
+ * never in the way. */
82
+ replyT(key: PhraseKey, ...args: (string | number)[]): void;
78
83
  }
79
84
  /** A parsed chat trigger: which command + args, and whether it was the silent (`/`) trigger. */
80
85
  export interface ChatTrigger {
package/entity.d.ts CHANGED
@@ -158,6 +158,40 @@ export declare class EntityRef {
158
158
  * `trigger_multiple` needs a model to build the physics volume that fires touch. Returns false
159
159
  * if the op is unavailable or the ref is stale. */
160
160
  setModel(name: string): boolean;
161
+ /** Set this entity's gravity multiplier (`CBaseEntity::SetGravityScale`). 1 is normal, 0 is
162
+ * weightless.
163
+ *
164
+ * Use this rather than writing `m_flGravityScale`: the engine setter early-returns when the value
165
+ * is unchanged and maintains a second field (`m_flActualGravityScale`), so a raw field write
166
+ * appears to succeed and does nothing. Returns false if the op is unavailable, the ref is stale,
167
+ * or `scale` is not finite. */
168
+ setGravityScale(scale: number): boolean;
169
+ /** Add to this entity's velocity, physics-aware (`CBaseEntity::ApplyAbsVelocityImpulse`) — for
170
+ * knockback, boosts, explosions.
171
+ *
172
+ * Additive, unlike `teleport(null, null, velocity)` which sets velocity absolutely; and unlike a
173
+ * direct `m_vecAbsVelocity` write, which skips the partition/physics update. `impulse` is an
174
+ * [x,y,z] triple. A zero impulse is a legal no-op. Returns false if the op is unavailable, the ref
175
+ * is stale, or any component is not finite. */
176
+ applyAbsVelocityImpulse(impulse: number[]): boolean;
177
+ /** Stop a sound playing on this entity (`CBaseEntity::StopSound`). `Sound.stop(name, { entity })`
178
+ * in `@s2script/sdk/sound` is the same call spelled next to `Sound.emit`, and
179
+ * `pawn.stopSound(name)` in `@s2script/cs2` is the pawn-shaped one. Returns false if the op is
180
+ * unavailable or the ref is stale. */
181
+ stopSound(name: string): boolean;
182
+ /** Set a model body group by name (`CBaseModelEntity::SetBodyGroupByName`).
183
+ *
184
+ * There is no schema equivalent — `m_bodyGroupChoices` is a `CUtlOrderedMap`, not a writable
185
+ * scalar. `group` is 32-bit engine-side. Returns false if the op is unavailable, the ref is stale,
186
+ * or `group` is out of 32-bit range. */
187
+ setBodyGroupByName(name: string, group: number): boolean;
188
+ /** Set this entity's model scale (`CBaseModelEntity::SetModelScale`).
189
+ *
190
+ * The argument shape is confirmed against the pinned build; the function's NAME is a catalogue
191
+ * attribution its body does not itself prove, so verify the effect before relying on this in a
192
+ * shipped plugin — see the gamedata comment. Calling it is safe either way. Returns false if the
193
+ * op is unavailable, the ref is stale, or `scale` is not finite. */
194
+ setModelScale(scale: number): boolean;
161
195
  /** Read a CUtlVector<CHandle> at (ptrOffs chain -> vectorOff) as live liveness-gated EntityRefs.
162
196
  * Follows the pointer chain, reads count@+0 / elements@+8, caps at maxCount. [] if stale/unresolved. */
163
197
  readHandleVector(ptrOffs: number[], vectorOff: number, maxCount?: number): EntityRef[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@s2script/sdk",
3
- "version": "0.20.0",
3
+ "version": "0.21.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",
package/phrases.d.ts ADDED
@@ -0,0 +1,57 @@
1
+ /**
2
+ * @s2script/phrases — the phrase keys your plugin is allowed to use.
3
+ *
4
+ * **You do not import this module.** It exists so that `cmd.replyT(...)` and
5
+ * `Translations.translate(...)` know which keys are valid in your plugin, and the build fills it in
6
+ * from the phrase files you load.
7
+ *
8
+ * @example
9
+ * export default plugin((ctx) => {
10
+ * ctx.translations.load("basecomm", "common");
11
+ *
12
+ * cmd.replyT("Usage Gag"); // ok — in translations/basecomm.phrases.json
13
+ * cmd.replyT("No matching players"); // ok — in translations/common.phrases.json
14
+ * cmd.replyT("Usage Gagg"); // compile error — no such key in either
15
+ * });
16
+ *
17
+ * ## Writing phrases
18
+ *
19
+ * Create `translations/<your-plugin>.phrases.json` — a flat key to English map. `{1}`, `{2}` … are
20
+ * positional slots filled by the trailing arguments; `{green}`, `{default}` … are colour tags
21
+ * expanded on output (see `@s2script/sdk/chat`). Keys are human-readable English on purpose: an
22
+ * unresolved key renders as the key itself, so it should still read as a sentence.
23
+ *
24
+ * ```json
25
+ * { "Gagged Player": "{green}[SM]{default} Gagged {1} player." }
26
+ * ```
27
+ *
28
+ * A translator adds `translations/<code>/<your-plugin>.phrases.json` — no code change, no rebuild.
29
+ *
30
+ * ## How the checking works
31
+ *
32
+ * The build reads your `ctx.translations.load(...)` call and writes `src/phrases.generated.d.ts`,
33
+ * filling in {@link PhraseKeys} with the keys of exactly those files. Load a file and its keys
34
+ * become valid; don't, and they don't — so a forgotten `load` is a compile error rather than raw key
35
+ * text printed to a player at runtime. SourceMod enforces the same rule, but only when it happens.
36
+ *
37
+ * That file is derived and gitignored. Until it has been written — a brand-new plugin, say — keys
38
+ * fall back to plain `string` and go unchecked, so nothing is blocked on the build having run.
39
+ */
40
+
41
+ /**
42
+ * The set of valid phrase keys, filled in per plugin by its generated declaration.
43
+ *
44
+ * Empty here deliberately. Each plugin's `src/phrases.generated.d.ts` augments this interface, and
45
+ * interface merging turns that into the exact union {@link PhraseKey} resolves to. Never add keys
46
+ * here by hand — this is the shared module, and a key added here would apply to every plugin.
47
+ */
48
+ export interface PhraseKeys {}
49
+
50
+ /**
51
+ * Every phrase key valid in this plugin — the union of the keys in every file it loads.
52
+ *
53
+ * Falls back to `string` when nothing has augmented {@link PhraseKeys}, so a plugin that loads no
54
+ * phrases (or whose generated declaration has not been written yet) compiles unchecked rather than
55
+ * failing to compile at all.
56
+ */
57
+ export type PhraseKey = [keyof PhraseKeys] extends [never] ? string : Extract<keyof PhraseKeys, string>;
package/plugin.d.ts CHANGED
@@ -114,6 +114,26 @@ export interface CtxCommands {
114
114
  handler: (slot: number, argString: string) => HookResultValue | void,
115
115
  ): void;
116
116
  }
117
+ /**
118
+ * The phrase files this plugin uses ({@link PluginContext.translations}).
119
+ *
120
+ * Nothing is loaded automatically — a plugin declares what it needs, the same rule SourceMod's
121
+ * `LoadTranslations` enforces. The build reads this call to work out which keys `cmd.replyT` and
122
+ * `Translations.translate` will accept, so a key from a file you did not load is a compile error.
123
+ */
124
+ export interface CtxTranslations {
125
+ /**
126
+ * Load `translations/<name>.phrases.json` for each name, in the order given.
127
+ *
128
+ * Order is significant: `translate` takes the first hit within each of its two passes (the
129
+ * client's language, then English), so list your own set before any shared one if you want to be
130
+ * able to override a shared phrase.
131
+ *
132
+ * @example ctx.translations.load("basecomm", "common");
133
+ */
134
+ load(...names: string[]): void;
135
+ }
136
+
117
137
  /** Config live-reload subscription on this plugin's load-scope ({@link PluginContext.config}). */
118
138
  export interface CtxConfig {
119
139
  /** Fires when the plugin's config file is re-materialized on disk; re-read values inside. */
@@ -175,6 +195,8 @@ export interface PluginContext {
175
195
  readonly commands: CtxCommands;
176
196
  /** Config live-reload subscription ({@link CtxConfig}). */
177
197
  readonly config: CtxConfig;
198
+ /** Phrase files this plugin uses ({@link CtxTranslations}). */
199
+ readonly translations: CtxTranslations;
178
200
  /** TopMenu (adminmenu) contribution ({@link CtxTopMenu}). */
179
201
  readonly topmenu: CtxTopMenu;
180
202
  /** Publish this plugin's manifest-declared interface. Buffered; goes live at Active. */
package/sound.d.ts CHANGED
@@ -52,4 +52,18 @@ export declare const Sound: {
52
52
  * function / stale source entity / an empty `recipients` array). An all-bot-skipped recipient
53
53
  * set still emits to nobody (a real engine call, may return a nonzero GUID). */
54
54
  emit(name: string, opts?: SoundEmitOptions): number;
55
+ /**
56
+ * Stop a named SoundEvent playing on an entity — the counterpart to {@link Sound.emit}.
57
+ *
58
+ * **`opts.entity` is required**, unlike `emit`. The engine call behind this is an instance method
59
+ * on the entity, so there is no global/2D form to default to the way `emit` falls back to
60
+ * worldspawn. `recipients` and `volume` do not apply: the engine stops the sound for everyone
61
+ * hearing it.
62
+ *
63
+ * Equivalent to `entity.stopSound(name)`; this is the discoverable spelling next to `emit`.
64
+ * `pawn.stopSound(name)` in `@s2script/cs2` is the pawn-shaped one.
65
+ *
66
+ * Returns false when `opts.entity` is absent or stale, or the engine op is unavailable.
67
+ */
68
+ stop(name: string, opts: { entity: EntityRef }): boolean;
55
69
  };
package/translations.d.ts CHANGED
@@ -1,20 +1,50 @@
1
1
  /** @s2script/translations — SourceMod-style i18n (per-client language, phrase files, {1} formatting). */
2
+ import type { PhraseKey } from "./phrases";
2
3
 
3
4
  /** A phrase set: key → template string, where `{1}`/`{2}`/… are positional substitution slots. */
4
5
  export type Phrases = Record<string, string>;
6
+
5
7
  /**
6
- * SourceMod-style translation registry: seed a default phrase set, override it per language from
7
- * `translations/<code>/<name>.phrases.json`, then translate a key for a given client's language.
8
+ * SourceMod-style translation registry. Phrases live in `translations/<name>.phrases.json`, shipped
9
+ * with the addon and edited by operators; `translations/<code>/<name>.phrases.json` overrides per
10
+ * language, added by a translator with no code change.
11
+ *
12
+ * A plugin declares the files it uses with {@link PluginContext.translations} — nothing is loaded
13
+ * for it automatically, the same rule SourceMod's `LoadTranslations` enforces:
14
+ *
8
15
  * @example
9
- * import { Translations } from "@s2script/sdk/translations";
10
- * Translations.load("trdemo", { Greeting: "Hello {1}", Bye: "Goodbye {1}" });
11
- * console.log(Translations.translate(-1, "Greeting", "world")); // "Hello world"
16
+ * export default plugin((ctx) => {
17
+ * ctx.translations.load("basecomm", "common");
18
+ * ctx.commands.registerAdmin("sm_gag", ADMFLAG.CHAT, (cmd) => {
19
+ * cmd.replyT("Usage Gag"); // key-checked
20
+ * Chat.toSlot(p.slot, Translations.translate(p.slot, "Gagged Player", n));
21
+ * });
22
+ * });
23
+ *
24
+ * Keys are checked against the files that plugin loads — see `@s2script/sdk/phrases`.
12
25
  */
13
26
  export declare const Translations: {
14
- /** Register a phrase set: `seed` is the built-in English default; translations/<code>/<name>.phrases.json overrides per language. */
15
- load(name: string, seed: Phrases): void;
16
- /** Translate `key` for `slot`'s language (slot < 0 = the server default), substituting positional {1}/{2} args. */
17
- translate(slot: number, key: string, ...args: (string | number)[]): string;
27
+ /**
28
+ * Register a phrase set by name, populating it from `translations/<name>.phrases.json`.
29
+ *
30
+ * Prefer {@link PluginContext.translations}`.load(...)`, which is what the build reads to work out
31
+ * which keys your plugin may use. This is the lower-level form, for a name computed at runtime or
32
+ * a set with an in-code default — a `seed` here is the starting content, which the file overrides.
33
+ *
34
+ * Registration order is significant: `translate` takes the first hit within each of its two passes
35
+ * (the client's language, then English), so load your own set before any shared one if you want to
36
+ * override a shared phrase.
37
+ */
38
+ load(name: string, seed?: Phrases): void;
39
+ /**
40
+ * Translate `key` for `slot`'s language (slot < 0 = the server default), substituting positional
41
+ * {1}/{2} args.
42
+ *
43
+ * `key` is checked against the phrase files this plugin loads; it widens to `string` in a plugin
44
+ * that loads none, so this is never in the way. A key found in no loaded set returns the key
45
+ * itself — which is what the checking exists to prevent reaching a player.
46
+ */
47
+ translate(slot: number, key: PhraseKey, ...args: (string | number)[]): string;
18
48
  /** Set the server/console default language code (default "" = root/English). */
19
49
  setDefaultLanguage(code: string): void;
20
50
  };