@s2script/sdk 0.22.0 → 0.24.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/README.md +12 -4
- package/commands.d.ts +52 -5
- package/config.d.ts +5 -0
- package/damage.d.ts +12 -7
- package/dist/cli.js +88 -47
- package/entity.d.ts +1 -1
- package/index.d.ts +47 -0
- package/menu.d.ts +9 -5
- package/package.json +8 -2
- package/phrases.d.ts +5 -4
- package/plugin.d.ts +103 -43
- package/sdkhooks.d.ts +543 -0
- package/topmenu.d.ts +5 -1
- package/translations.d.ts +6 -4
- package/votes.d.ts +4 -2
package/README.md
CHANGED
|
@@ -19,13 +19,21 @@ Copy the `.s2sp` into `addons/s2script/plugins/` on a running server and it load
|
|
|
19
19
|
re-drop to hot-reload, delete to unload. No restart.
|
|
20
20
|
|
|
21
21
|
```ts
|
|
22
|
-
import {
|
|
22
|
+
import { command, hook } from "@s2script/sdk";
|
|
23
|
+
import type { Client } from "@s2script/sdk";
|
|
23
24
|
|
|
24
|
-
export
|
|
25
|
-
|
|
25
|
+
export function OnPluginStart(): void {
|
|
26
|
+
command("hello", (cmd) => {
|
|
26
27
|
cmd.reply("hello from s2script");
|
|
27
28
|
});
|
|
28
|
-
|
|
29
|
+
hook.on("player_spawn", (ev) => {
|
|
30
|
+
console.log("spawned", ev.getPlayerSlot("userid"));
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function OnClientPostAdminCheck(client: Client): void {
|
|
35
|
+
client.chat("welcome");
|
|
36
|
+
}
|
|
29
37
|
```
|
|
30
38
|
|
|
31
39
|
This package is **types-only** — the engine injects the implementation at load time, and
|
package/commands.d.ts
CHANGED
|
@@ -57,7 +57,7 @@ export interface CommandInvocation {
|
|
|
57
57
|
*
|
|
58
58
|
* @example
|
|
59
59
|
* // `!help` answers in chat; `sm_help` typed at a console answers in that console.
|
|
60
|
-
*
|
|
60
|
+
* command("sm_help", (cmd) => { cmd.reply("[SM] Commands: …"); return HookResult.Handled; });
|
|
61
61
|
*/
|
|
62
62
|
reply(message: string): void;
|
|
63
63
|
/**
|
|
@@ -81,6 +81,53 @@ export interface CommandInvocation {
|
|
|
81
81
|
* never in the way. */
|
|
82
82
|
replyT(key: PhraseKey, ...args: (string | number)[]): void;
|
|
83
83
|
}
|
|
84
|
+
|
|
85
|
+
/** Alias for {@link CommandInvocation} — the SourceMod-shaped name for a parsed invocation. */
|
|
86
|
+
export type Command = CommandInvocation;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* A command callback: the parsed invocation, plus an optional {@link HookResultValue}.
|
|
90
|
+
* SourceMod `Plugin_Handled` is {@link HookResult}.Handled — return it from a command you own
|
|
91
|
+
* (usage errors included; the command was still consumed). Omit the return (`void`) to continue —
|
|
92
|
+
* the same as returning {@link HookResult}.Continue. Engine SUPERCEDE-on-Continue is not this API;
|
|
93
|
+
* chat `!` vs `/` suppress is unchanged.
|
|
94
|
+
*/
|
|
95
|
+
export type CommandHandler = (
|
|
96
|
+
cmd: CommandInvocation,
|
|
97
|
+
) => HookResultValue | void | Promise<HookResultValue | void>;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Register a public command in the load window (SourceMod `RegConsoleCmd`).
|
|
101
|
+
* Callable only while `OnPluginStart` is running — throws after settle.
|
|
102
|
+
*
|
|
103
|
+
* `.admin` / `.server` are the `RegAdminCmd` / `RegServerCmd` shapes: name the handler anything.
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* import { command, ADMFLAG, HookResult } from "@s2script/sdk";
|
|
107
|
+
* export function OnPluginStart(): void {
|
|
108
|
+
* command.admin("sm_kick", ADMFLAG.KICK, kick);
|
|
109
|
+
* }
|
|
110
|
+
* function kick(cmd: CommandInvocation): HookResultValue {
|
|
111
|
+
* cmd.reply("kicked");
|
|
112
|
+
* return HookResult.Handled;
|
|
113
|
+
* }
|
|
114
|
+
*/
|
|
115
|
+
export declare const command: {
|
|
116
|
+
(name: string, handler: CommandHandler): void;
|
|
117
|
+
/** Register an admin command gated by `flags` (an `ADMFLAG` bitmask). */
|
|
118
|
+
admin(name: string, flags: number, handler: CommandHandler): void;
|
|
119
|
+
/** Register a server-only command (console/rcon, not client-runnable). */
|
|
120
|
+
server(name: string, handler: CommandHandler): void;
|
|
121
|
+
/**
|
|
122
|
+
* Observe an existing CLIENT command by name — SourceMod's `AddCommandListener`.
|
|
123
|
+
* Load-window only. Same contract as {@link CtxCommands.onClientCommand} (on PluginContext).
|
|
124
|
+
*/
|
|
125
|
+
onClientCommand(
|
|
126
|
+
name: string,
|
|
127
|
+
handler: (slot: number, argString: string) => HookResultValue | void,
|
|
128
|
+
): void;
|
|
129
|
+
};
|
|
130
|
+
|
|
84
131
|
/** A parsed chat trigger: which command + args, and whether it was the silent (`/`) trigger. */
|
|
85
132
|
export interface ChatTrigger {
|
|
86
133
|
/** `true` = the silent trigger (`/`, hidden); `false` = the public trigger (`!`). */
|
|
@@ -93,9 +140,9 @@ export interface ChatTrigger {
|
|
|
93
140
|
|
|
94
141
|
/**
|
|
95
142
|
* Command-registry utilities: dispatch by name, parse/route chat triggers, and enumerate the global
|
|
96
|
-
* registry. Commands themselves are registered
|
|
143
|
+
* registry. Commands themselves are registered in the load window via {@link command}.
|
|
97
144
|
* @example
|
|
98
|
-
* import { Commands } from "@s2script/sdk
|
|
145
|
+
* import { Commands } from "@s2script/sdk";
|
|
99
146
|
* // sm_help backend: every registered command + its required admin flag mask.
|
|
100
147
|
* const cmds = Commands.list().slice().sort((a, b) => (a.name < b.name ? -1 : 1));
|
|
101
148
|
*/
|
|
@@ -122,7 +169,7 @@ export declare const Commands: {
|
|
|
122
169
|
/**
|
|
123
170
|
* Observe a CLIENT command by name — SourceMod's `AddCommandListener`.
|
|
124
171
|
*
|
|
125
|
-
* Unlike {@link
|
|
172
|
+
* Unlike {@link command}, which CREATES a command, this hooks one that already exists — including
|
|
126
173
|
* commands the engine itself owns (`player_ping`, `jointeam`, `drop`, `buy`). Registering a
|
|
127
174
|
* ConCommand of an engine-owned name fails outright ("unable to link multiple ConCommands named
|
|
128
175
|
* X"), so this is the only way to see those.
|
|
@@ -133,7 +180,7 @@ export declare const Commands: {
|
|
|
133
180
|
*
|
|
134
181
|
* @example
|
|
135
182
|
* // Middle-mouse ping opens the shop, and still places the ping.
|
|
136
|
-
*
|
|
183
|
+
* command.onClientCommand("player_ping", (slot) => { openShop(slot); });
|
|
137
184
|
*/
|
|
138
185
|
onClientCommand(name: string, handler: (slot: number, argString: string) => HookResultValue | void): void;
|
|
139
186
|
/** Every globally-registered command with its required admin `flags`: `0` = anyone, `-1` = console/server-only,
|
package/config.d.ts
CHANGED
|
@@ -23,4 +23,9 @@ export declare const config: {
|
|
|
23
23
|
readFile(name: string): string | null;
|
|
24
24
|
/** Write a raw file to the configs dir (creates/overwrites). */
|
|
25
25
|
writeFile(name: string, content: string): void;
|
|
26
|
+
/**
|
|
27
|
+
* Fires when this plugin's config file is re-materialized on disk. Ledgered per plugin.
|
|
28
|
+
* Not load-window-gated (safe from `OnPluginStart` or from `ctx.config.onChange`).
|
|
29
|
+
*/
|
|
30
|
+
onChange(handler: (cfg: Config) => void): void;
|
|
26
31
|
};
|
package/damage.d.ts
CHANGED
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @s2script/damage —
|
|
3
|
-
*
|
|
2
|
+
* @s2script/damage — the block-scoped {@link DamageInfo} view of a live `CTakeDamageInfo`.
|
|
3
|
+
* NO runtime code (injected at load). Engine-generic: `CTakeDamageInfo` is a Source 2 type.
|
|
4
|
+
*
|
|
5
|
+
* Subscribe with `SDKHook` (`SDKHookType.OnTakeDamage` / `OnTakeDamagePost`), not a global mux.
|
|
4
6
|
*/
|
|
5
7
|
import type { EntityRef } from "./entity";
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
|
-
* A block-scoped view of the current damage event (valid only inside
|
|
10
|
+
* A block-scoped view of the current damage event (valid only inside an `OnTakeDamage` or
|
|
11
|
+
* `OnTakeDamagePost` SDKHook).
|
|
9
12
|
* @example
|
|
10
|
-
* import type { DamageInfo } from "@s2script/sdk
|
|
11
|
-
*
|
|
12
|
-
* ctx.entities.onDamage((info: DamageInfo) => { info.damage = info.damage / 2; });
|
|
13
|
+
* import type { DamageInfo } from "@s2script/sdk";
|
|
14
|
+
* function onTakeDamage(info: DamageInfo) { info.damage = info.damage / 2; }
|
|
13
15
|
*/
|
|
14
16
|
export interface DamageInfo {
|
|
15
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* The damage amount (`m_flDamage`). On the pre-hook, assigning MODIFIES the live damage; set to
|
|
19
|
+
* 0 to block. On `OnTakeDamagePost`, assignment is ignored (the original already ran).
|
|
20
|
+
*/
|
|
16
21
|
damage: number;
|
|
17
22
|
/** The damage-type bit flags (m_bitsDamageType). */
|
|
18
23
|
readonly damageType: number;
|
package/dist/cli.js
CHANGED
|
@@ -3126,8 +3126,8 @@ var sharedCompilerOptionsJson = {
|
|
|
3126
3126
|
noEmit: true,
|
|
3127
3127
|
moduleResolution: "bundler",
|
|
3128
3128
|
module: "ESNext",
|
|
3129
|
-
target: "
|
|
3130
|
-
lib: ["
|
|
3129
|
+
target: "ES2024",
|
|
3130
|
+
lib: ["ES2024"],
|
|
3131
3131
|
types: [],
|
|
3132
3132
|
skipLibCheck: true,
|
|
3133
3133
|
allowImportingTsExtensions: true
|
|
@@ -3139,7 +3139,7 @@ function sharedProgramOptions(tsMod) {
|
|
|
3139
3139
|
allowImportingTsExtensions: sharedCompilerOptionsJson.allowImportingTsExtensions,
|
|
3140
3140
|
moduleResolution: tsMod.ModuleResolutionKind.Bundler,
|
|
3141
3141
|
module: tsMod.ModuleKind.ESNext,
|
|
3142
|
-
target: tsMod.ScriptTarget.
|
|
3142
|
+
target: tsMod.ScriptTarget.ES2024,
|
|
3143
3143
|
lib: sharedCompilerOptionsJson.lib.map((l2) => `lib.${l2.toLowerCase()}.d.ts`),
|
|
3144
3144
|
types: [...sharedCompilerOptionsJson.types],
|
|
3145
3145
|
skipLibCheck: sharedCompilerOptionsJson.skipLibCheck
|
|
@@ -3795,7 +3795,7 @@ function typecheckPlugin(pluginDir, opts) {
|
|
|
3795
3795
|
const entryRel = s2.main ?? pkg.main;
|
|
3796
3796
|
if (!entryRel) throw new Error(`typecheckPlugin: no entry point in ${join7(absDir, "package.json")}`);
|
|
3797
3797
|
const entry = resolve6(absDir, entryRel);
|
|
3798
|
-
const isAlwaysResolved = (d) => d.startsWith("@s2script/sdk/") || d === "@s2script/cs2" || d.startsWith("@s2script/cs2/");
|
|
3798
|
+
const isAlwaysResolved = (d) => d === "@s2script/sdk" || d.startsWith("@s2script/sdk/") || d === "@s2script/cs2" || d.startsWith("@s2script/cs2/");
|
|
3799
3799
|
const localDts = localDeclarationFiles(absDir);
|
|
3800
3800
|
const locallyDeclared = declaredModules(localDts);
|
|
3801
3801
|
const allDeclaredDeps = [
|
|
@@ -3829,10 +3829,12 @@ function typecheckPlugin(pluginDir, opts) {
|
|
|
3829
3829
|
...sharedProgramOptions(ts),
|
|
3830
3830
|
baseUrl: packagesDir,
|
|
3831
3831
|
paths: {
|
|
3832
|
-
// Builtins
|
|
3833
|
-
//
|
|
3834
|
-
//
|
|
3835
|
-
// sdk
|
|
3832
|
+
// Builtins: the root barrel `@s2script/sdk` → packages/sdk/index.d.ts; subpaths
|
|
3833
|
+
// `@s2script/sdk/<cap>` → packages/sdk/<cap>.d.ts. The `@s2script/*` fallback serves
|
|
3834
|
+
// @s2script/cs2 → packages/cs2/index.d.ts (and would also hit sdk/index.d.ts; the exact
|
|
3835
|
+
// `@s2script/sdk` entry makes the barrel obvious). tsc picks the longest matching prefix,
|
|
3836
|
+
// so `@s2script/sdk/*` wins for capability imports.
|
|
3837
|
+
"@s2script/sdk": ["sdk/index.d.ts"],
|
|
3836
3838
|
"@s2script/sdk/*": ["sdk/*.d.ts"],
|
|
3837
3839
|
// Game-package SUBPATHS (e.g. @s2script/cs2/econ -> packages/cs2/econ.d.ts). Needed because
|
|
3838
3840
|
// the `@s2script/*` fallback below maps to `<pkg>/index.d.ts`, which cannot express a
|
|
@@ -4171,31 +4173,60 @@ function isPluginContext(type) {
|
|
|
4171
4173
|
const sym = type.getSymbol() ?? type.aliasSymbol;
|
|
4172
4174
|
return sym?.getName() === "PluginContext";
|
|
4173
4175
|
}
|
|
4176
|
+
function isFromPluginDts(checker, node) {
|
|
4177
|
+
let sym = checker.getSymbolAtLocation(node);
|
|
4178
|
+
if (sym === void 0) return false;
|
|
4179
|
+
if (sym.flags & ts2.SymbolFlags.Alias) {
|
|
4180
|
+
const aliased = checker.getAliasedSymbol(sym);
|
|
4181
|
+
if (aliased !== void 0) sym = aliased;
|
|
4182
|
+
}
|
|
4183
|
+
for (const d of sym.declarations ?? []) {
|
|
4184
|
+
const f = d.getSourceFile().fileName.replace(/\\/g, "/");
|
|
4185
|
+
if (f.endsWith("/plugin.d.ts")) return true;
|
|
4186
|
+
}
|
|
4187
|
+
return false;
|
|
4188
|
+
}
|
|
4189
|
+
function recordPublishOrUse(out, method, arg0, sf, node) {
|
|
4190
|
+
if (method === "publish") {
|
|
4191
|
+
if (arg0 !== void 0 && ts2.isStringLiteralLike(arg0)) {
|
|
4192
|
+
out.publishNames.push(arg0.text);
|
|
4193
|
+
} else {
|
|
4194
|
+
const { line } = sf.getLineAndCharacterOfPosition(node.getStart());
|
|
4195
|
+
out.dynamicPublishSites.push(`${sf.fileName}:${line + 1}`);
|
|
4196
|
+
}
|
|
4197
|
+
} else if (arg0 !== void 0 && ts2.isStringLiteralLike(arg0)) {
|
|
4198
|
+
out.useNames.push(arg0.text);
|
|
4199
|
+
}
|
|
4200
|
+
}
|
|
4201
|
+
function recordImportSpecifier(out, spec) {
|
|
4202
|
+
if (spec.startsWith(".") || spec.startsWith("@s2script/")) return;
|
|
4203
|
+
out.importNames.push(spec);
|
|
4204
|
+
}
|
|
4174
4205
|
function scanPluginProgram(program, pluginDir) {
|
|
4175
4206
|
const checker = program.getTypeChecker();
|
|
4176
|
-
const out = { publishNames: [], dynamicPublishSites: [], useNames: [] };
|
|
4207
|
+
const out = { publishNames: [], dynamicPublishSites: [], useNames: [], importNames: [] };
|
|
4177
4208
|
const dirPrefix = pluginDir.replace(/\\/g, "/").replace(/\/+$/, "") + "/";
|
|
4178
4209
|
for (const sf of program.getSourceFiles()) {
|
|
4179
4210
|
if (sf.isDeclarationFile) continue;
|
|
4180
4211
|
if (!sf.fileName.replace(/\\/g, "/").startsWith(dirPrefix)) continue;
|
|
4181
4212
|
const visit = (node) => {
|
|
4182
|
-
if (ts2.
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
const { line } = sf.getLineAndCharacterOfPosition(node.getStart());
|
|
4193
|
-
out.dynamicPublishSites.push(`${sf.fileName}:${line + 1}`);
|
|
4194
|
-
}
|
|
4195
|
-
} else if (arg0 !== void 0 && ts2.isStringLiteralLike(arg0)) {
|
|
4196
|
-
out.useNames.push(arg0.text);
|
|
4213
|
+
if ((ts2.isImportDeclaration(node) || ts2.isExportDeclaration(node)) && node.moduleSpecifier !== void 0 && ts2.isStringLiteralLike(node.moduleSpecifier)) {
|
|
4214
|
+
recordImportSpecifier(out, node.moduleSpecifier.text);
|
|
4215
|
+
}
|
|
4216
|
+
if (ts2.isCallExpression(node)) {
|
|
4217
|
+
if (ts2.isPropertyAccessExpression(node.expression)) {
|
|
4218
|
+
const method = node.expression.name.text;
|
|
4219
|
+
if (method === "publish" || method === "use" || method === "tryUse") {
|
|
4220
|
+
const recv = checker.getTypeAtLocation(node.expression.expression);
|
|
4221
|
+
if (isPluginContext(recv)) {
|
|
4222
|
+
recordPublishOrUse(out, method, node.arguments[0], sf, node);
|
|
4197
4223
|
}
|
|
4198
4224
|
}
|
|
4225
|
+
} else if (ts2.isIdentifier(node.expression)) {
|
|
4226
|
+
const name = node.expression.text;
|
|
4227
|
+
if ((name === "publish" || name === "use" || name === "tryUse") && isFromPluginDts(checker, node.expression)) {
|
|
4228
|
+
recordPublishOrUse(out, name, node.arguments[0], sf, node);
|
|
4229
|
+
}
|
|
4199
4230
|
}
|
|
4200
4231
|
}
|
|
4201
4232
|
ts2.forEachChild(node, visit);
|
|
@@ -4204,6 +4235,7 @@ function scanPluginProgram(program, pluginDir) {
|
|
|
4204
4235
|
}
|
|
4205
4236
|
out.publishNames = [...new Set(out.publishNames)];
|
|
4206
4237
|
out.useNames = [...new Set(out.useNames)];
|
|
4238
|
+
out.importNames = [...new Set(out.importNames)];
|
|
4207
4239
|
return out;
|
|
4208
4240
|
}
|
|
4209
4241
|
|
|
@@ -4569,12 +4601,14 @@ function emitHookDts(model) {
|
|
|
4569
4601
|
out.push(...emitViewIface(h2));
|
|
4570
4602
|
}
|
|
4571
4603
|
}
|
|
4572
|
-
for (const { ctxIface, hooks } of model) {
|
|
4604
|
+
for (const { ctxIface, hooks, ns } of model) {
|
|
4573
4605
|
out.push(`export interface ${ctxIface} {`);
|
|
4574
4606
|
for (const h2 of hooks) {
|
|
4575
4607
|
out.push(` ${h2.name}(handler: (view: ${h2.viewIface}) => HookResultValue | void): void;`);
|
|
4576
4608
|
}
|
|
4577
4609
|
out.push("}", "");
|
|
4610
|
+
out.push(`export declare const ${ns}: ${ctxIface};`);
|
|
4611
|
+
out.push("");
|
|
4578
4612
|
}
|
|
4579
4613
|
out.push('declare module "@s2script/sdk/plugin" {');
|
|
4580
4614
|
out.push(" interface PluginContext {");
|
|
@@ -4837,13 +4871,16 @@ ${lint.output}`);
|
|
|
4837
4871
|
for (const used of scan.useNames) {
|
|
4838
4872
|
if (!declaredDeps.has(used)) {
|
|
4839
4873
|
console.warn(
|
|
4840
|
-
`WARN:
|
|
4874
|
+
`WARN: use()/tryUse(${JSON.stringify(used)}) is not declared under s2script.pluginDependencies/optionalPluginDependencies \u2014 it will throw at runtime`
|
|
4841
4875
|
);
|
|
4842
4876
|
}
|
|
4843
4877
|
}
|
|
4878
|
+
const referenced = /* @__PURE__ */ new Set([...scan.useNames, ...scan.importNames]);
|
|
4844
4879
|
for (const dep of declaredDeps) {
|
|
4845
|
-
if (!
|
|
4846
|
-
console.warn(
|
|
4880
|
+
if (!referenced.has(dep)) {
|
|
4881
|
+
console.warn(
|
|
4882
|
+
`WARN: dependency ${JSON.stringify(dep)} is declared but never ctx.use()d or imported`
|
|
4883
|
+
);
|
|
4847
4884
|
}
|
|
4848
4885
|
}
|
|
4849
4886
|
const { name, version } = pkg;
|
|
@@ -4871,7 +4908,7 @@ ${lint.output}`);
|
|
|
4871
4908
|
platform: "neutral",
|
|
4872
4909
|
format: "cjs",
|
|
4873
4910
|
external,
|
|
4874
|
-
target: "
|
|
4911
|
+
target: "es2024",
|
|
4875
4912
|
// platform:"neutral" defaults mainFields to EMPTY, so a workspace-sibling package that
|
|
4876
4913
|
// declares `main` (rather than `exports`) fails to resolve with an undiagnosable error.
|
|
4877
4914
|
// Set it explicitly so a monorepo plugin bundles whichever field the author wrote.
|
|
@@ -8071,29 +8108,33 @@ function fileDevDeps(packagesDir, game) {
|
|
|
8071
8108
|
}
|
|
8072
8109
|
function pluginSource(game) {
|
|
8073
8110
|
if (game === "cs2") {
|
|
8074
|
-
return `import {
|
|
8075
|
-
import {
|
|
8111
|
+
return `import { command, Chat, HookResult } from "@s2script/sdk";
|
|
8112
|
+
import type { Command, HookResultValue } from "@s2script/sdk";
|
|
8076
8113
|
|
|
8077
|
-
export
|
|
8078
|
-
|
|
8079
|
-
|
|
8080
|
-
|
|
8081
|
-
|
|
8082
|
-
|
|
8083
|
-
|
|
8084
|
-
|
|
8114
|
+
export function OnPluginStart(): void {
|
|
8115
|
+
command("hello", hello);
|
|
8116
|
+
}
|
|
8117
|
+
|
|
8118
|
+
function hello(cmd: Command): HookResultValue {
|
|
8119
|
+
cmd.reply("hello from s2script");
|
|
8120
|
+
if (cmd.callerSlot >= 0) {
|
|
8121
|
+
Chat.toSlot(cmd.callerSlot, "hello from s2script");
|
|
8122
|
+
}
|
|
8123
|
+
return HookResult.Handled;
|
|
8124
|
+
}
|
|
8085
8125
|
`;
|
|
8086
8126
|
}
|
|
8087
|
-
return `import {
|
|
8088
|
-
import { delay } from "@s2script/sdk/timers";
|
|
8127
|
+
return `import { delay } from "@s2script/sdk";
|
|
8089
8128
|
|
|
8090
|
-
|
|
8091
|
-
|
|
8092
|
-
|
|
8093
|
-
n += 1;
|
|
8094
|
-
});
|
|
8129
|
+
let n = 0;
|
|
8130
|
+
|
|
8131
|
+
export function OnPluginStart(): void {
|
|
8095
8132
|
void delay(1000).then(() => console.log("s2script plugin alive; frames so far:", n));
|
|
8096
|
-
}
|
|
8133
|
+
}
|
|
8134
|
+
|
|
8135
|
+
export function OnGameFrame(): void {
|
|
8136
|
+
n += 1;
|
|
8137
|
+
}
|
|
8097
8138
|
`;
|
|
8098
8139
|
}
|
|
8099
8140
|
var LIBRARY_ENTRY = `/** Encode a string. Replace this with the library's real surface. */
|
package/entity.d.ts
CHANGED
|
@@ -220,7 +220,7 @@ export type EntityKeyValueMap = { [key: string]: string | number | boolean };
|
|
|
220
220
|
*/
|
|
221
221
|
export declare function createEntity(className: string, keyvalues?: EntityKeyValueMap): EntityRef | null;
|
|
222
222
|
|
|
223
|
-
/** The payload delivered to an `
|
|
223
|
+
/** The payload delivered to an `onOutput` handler. */
|
|
224
224
|
export interface OutputEvent {
|
|
225
225
|
/** The output's name (e.g. "OnTrigger", "OnPressed", "OnStartTouch"). */
|
|
226
226
|
output: string;
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@s2script/sdk` — the engine-generic authoring barrel.
|
|
3
|
+
*
|
|
4
|
+
* Subpath imports (`@s2script/sdk/commands`, `@s2script/sdk/plugin`, …) stay valid.
|
|
5
|
+
* This root re-exports the same names so a plugin can write:
|
|
6
|
+
*
|
|
7
|
+
* import { command, hook, HookResult, ADMFLAG } from "@s2script/sdk";
|
|
8
|
+
*
|
|
9
|
+
* `Player` / `Pawn` / CS2 schema types are NOT here — they live on `@s2script/cs2`.
|
|
10
|
+
* `@s2script/sdk/unsafe` stays a deliberate subpath (not re-exported).
|
|
11
|
+
*
|
|
12
|
+
* NO runtime code in this file: the engine injects `globalThis.__s2pkg_sdk` at load
|
|
13
|
+
* (`__s2require("@s2script/sdk")`).
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export * from "./admin";
|
|
17
|
+
export * from "./bans";
|
|
18
|
+
export * from "./chat";
|
|
19
|
+
export * from "./clients";
|
|
20
|
+
export * from "./commands";
|
|
21
|
+
export * from "./config";
|
|
22
|
+
export * from "./cookies";
|
|
23
|
+
export * from "./damage";
|
|
24
|
+
export * from "./db";
|
|
25
|
+
export * from "./entity";
|
|
26
|
+
export * from "./events";
|
|
27
|
+
export * from "./http";
|
|
28
|
+
export * from "./interfaces";
|
|
29
|
+
export * from "./math";
|
|
30
|
+
export * from "./menu";
|
|
31
|
+
export * from "./net";
|
|
32
|
+
export * from "./phrases";
|
|
33
|
+
export * from "./plugin";
|
|
34
|
+
export * from "./plugins";
|
|
35
|
+
export * from "./sdkhooks";
|
|
36
|
+
export * from "./server";
|
|
37
|
+
export * from "./sound";
|
|
38
|
+
export * from "./timers";
|
|
39
|
+
export * from "./topmenu";
|
|
40
|
+
export * from "./trace";
|
|
41
|
+
export * from "./translations";
|
|
42
|
+
export * from "./transmit";
|
|
43
|
+
export * from "./usercmd";
|
|
44
|
+
export * from "./usermessages";
|
|
45
|
+
export * from "./voice";
|
|
46
|
+
export * from "./votes";
|
|
47
|
+
export * from "./ws";
|
package/menu.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
/** @s2script/menu — interactive player menus (chat + center backends). NO runtime code (injected at load). */
|
|
2
2
|
|
|
3
|
+
/** How a menu becomes interactive. Unknown values coerce to `"immediate"`. */
|
|
4
|
+
export type MenuActivation = "immediate" | "tab";
|
|
5
|
+
|
|
3
6
|
/** The display backend a {@link Menu} renders through — the value of {@link Menu.style}. */
|
|
4
7
|
export declare const enum MenuStyle {
|
|
5
8
|
/** Chat-log backend: numbered lines printed to chat, picked by typing the number. */
|
|
6
9
|
Chat = "chat",
|
|
7
|
-
/** CS2
|
|
10
|
+
/** CS2 HUD sheet (both styles paint the same center sheet on CS2). */
|
|
8
11
|
Center = "center",
|
|
9
12
|
}
|
|
10
13
|
/** Why a menu closed without a selection — the {@link MenuCancelEvent.reason}. */
|
|
@@ -48,7 +51,7 @@ export interface MenuCancelEvent {
|
|
|
48
51
|
* import { Menu, MenuStyle } from "@s2script/sdk/menu";
|
|
49
52
|
* const m = new Menu("s2script Menu Demo");
|
|
50
53
|
* m.style = MenuStyle.Center;
|
|
51
|
-
* m.freezePlayer = true; // freeze movement while the
|
|
54
|
+
* m.freezePlayer = true; // freeze movement while the HUD menu is open
|
|
52
55
|
* m.addItem("hp", "Heal to 100");
|
|
53
56
|
* m.addItem("noclip", "Toggle Noclip");
|
|
54
57
|
* m.onSelect(e => { console.log(`picked ${e.info} (slot ${e.slot})`); });
|
|
@@ -62,10 +65,11 @@ export declare class Menu {
|
|
|
62
65
|
style: MenuStyle;
|
|
63
66
|
/** Append an auto Exit control (default true). */
|
|
64
67
|
exitButton: boolean;
|
|
65
|
-
/** When true, a renderer that supports it (the CS2
|
|
66
|
-
* while the menu is open and restores it on close
|
|
67
|
-
* The chat renderer ignores it. Default false (movement allowed, the normal behavior). */
|
|
68
|
+
/** When true, a renderer that supports it (the CS2 HUD renderer) freezes the player's movement
|
|
69
|
+
* while the menu is open and restores it on close. Default false. */
|
|
68
70
|
freezePlayer: boolean;
|
|
71
|
+
/** `"immediate"` (default) arms cursor on open. `"tab"` paints first and waits for Tab. */
|
|
72
|
+
activation: MenuActivation;
|
|
69
73
|
/** (info, display) — `info` is returned to onSelect; `display` is shown. */
|
|
70
74
|
addItem(info: string, display: string, opts?: { disabled?: boolean }): void;
|
|
71
75
|
/** Register the callback fired when a player picks an item. Replaces any previous handler. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@s2script/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.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",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"test": "node --experimental-strip-types --no-warnings --test test/*.test.mjs"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@s2script/eslint-plugin": "0.2.
|
|
16
|
+
"@s2script/eslint-plugin": "0.2.1",
|
|
17
17
|
"esbuild": "^0.25.0",
|
|
18
18
|
"eslint": "^10.7.0",
|
|
19
19
|
"typescript": "^5.6.0"
|
|
@@ -35,6 +35,9 @@
|
|
|
35
35
|
"contracts"
|
|
36
36
|
],
|
|
37
37
|
"exports": {
|
|
38
|
+
".": {
|
|
39
|
+
"types": "./index.d.ts"
|
|
40
|
+
},
|
|
38
41
|
"./admin": {
|
|
39
42
|
"types": "./admin.d.ts"
|
|
40
43
|
},
|
|
@@ -92,6 +95,9 @@
|
|
|
92
95
|
"./plugins": {
|
|
93
96
|
"types": "./plugins.d.ts"
|
|
94
97
|
},
|
|
98
|
+
"./sdkhooks": {
|
|
99
|
+
"types": "./sdkhooks.d.ts"
|
|
100
|
+
},
|
|
95
101
|
"./server": {
|
|
96
102
|
"types": "./server.d.ts"
|
|
97
103
|
},
|
package/phrases.d.ts
CHANGED
|
@@ -6,13 +6,14 @@
|
|
|
6
6
|
* from the phrase files you load.
|
|
7
7
|
*
|
|
8
8
|
* @example
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* import { translations } from "@s2script/sdk";
|
|
10
|
+
* export function OnPluginStart(): void {
|
|
11
|
+
* translations.load("basecomm", "common");
|
|
11
12
|
*
|
|
12
13
|
* cmd.replyT("Usage Gag"); // ok — in translations/basecomm.phrases.json
|
|
13
14
|
* cmd.replyT("No matching players"); // ok — in translations/common.phrases.json
|
|
14
15
|
* cmd.replyT("Usage Gagg"); // compile error — no such key in either
|
|
15
|
-
* }
|
|
16
|
+
* }
|
|
16
17
|
*
|
|
17
18
|
* ## Writing phrases
|
|
18
19
|
*
|
|
@@ -29,7 +30,7 @@
|
|
|
29
30
|
*
|
|
30
31
|
* ## How the checking works
|
|
31
32
|
*
|
|
32
|
-
* The build reads your `
|
|
33
|
+
* The build reads your `translations.load(...)` call and writes `src/phrases.generated.d.ts`,
|
|
33
34
|
* filling in {@link PhraseKeys} with the keys of exactly those files. Load a file and its keys
|
|
34
35
|
* become valid; don't, and they don't — so a forgotten `load` is a compile error rather than raw key
|
|
35
36
|
* text printed to a player at runtime. SourceMod enforces the same rule, but only when it happens.
|