@lingfan-engine/editor 0.0.0-stage → 0.1.2
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/LICENSE +201 -0
- package/package.json +39 -3
- package/src/chapters/build.ts +180 -0
- package/src/chapters/index.ts +34 -0
- package/src/chapters/types.ts +71 -0
- package/src/contracts/diagnostics.ts +62 -0
- package/src/contracts/editing.ts +26 -0
- package/src/contracts/forms.ts +69 -0
- package/src/contracts/index.ts +22 -0
- package/src/contracts/texts.ts +204 -0
- package/src/diagnostics/analyze.ts +161 -0
- package/src/diagnostics/grouping.ts +164 -0
- package/src/diagnostics/index.ts +35 -0
- package/src/diagnostics/message.ts +60 -0
- package/src/diagnostics/refs.ts +123 -0
- package/src/diagnostics/symbols.ts +221 -0
- package/src/editing/columnGrouping.ts +331 -0
- package/src/editing/graphConnect.ts +88 -0
- package/src/editing/index.ts +72 -0
- package/src/editing/pointers.ts +291 -0
- package/src/editing/saveNormalization.ts +58 -0
- package/src/editing/session.ts +134 -0
- package/src/editing/storyOps.ts +268 -0
- package/src/editing/tsSourceGuard.ts +40 -0
- package/src/element/drag.ts +22 -0
- package/src/element/dragCreate.ts +60 -0
- package/src/element/index.ts +11 -0
- package/src/i18n/coverage.ts +191 -0
- package/src/i18n/extract.ts +38 -0
- package/src/i18n/index.ts +47 -0
- package/src/i18n/reconcile.ts +71 -0
- package/src/i18n/skeleton.ts +112 -0
- package/src/i18n/storyKeys.ts +27 -0
- package/src/i18n/surfaces.ts +54 -0
- package/src/i18n/table.ts +235 -0
- package/src/index.ts +342 -0
- package/src/layout/index.ts +14 -0
- package/src/layout/steps.ts +378 -0
- package/src/schema/catalog.ts +300 -0
- package/src/schema/elementForms.ts +307 -0
- package/src/schema/elementPalette.ts +100 -0
- package/src/schema/forms.ts +196 -0
- package/src/schema/index.ts +55 -0
- package/src/schema/opSchemas/commands.ts +374 -0
- package/src/schema/opSchemas/fragments.ts +24 -0
- package/src/schema/opSchemas/index.ts +16 -0
- package/src/schema/opSchemas/map.ts +134 -0
- package/src/schema/opSchemas/types.ts +42 -0
- package/src/schema/opSchemas/validate.ts +184 -0
- package/src/schema/surface.ts +101 -0
- package/src/schema/validation.ts +92 -0
- package/src/schema/walk.ts +167 -0
- package/src/script/expr/audit/index.ts +8 -0
- package/src/script/expr/audit/parser.ts +370 -0
- package/src/script/expr/audit/token.ts +23 -0
- package/src/script/expr/build.ts +203 -0
- package/src/script/expr/index.ts +17 -0
- package/src/script/expr/registry.ts +130 -0
- package/src/script/index.ts +168 -0
- package/src/script/vocabulary-gen.ts +202 -0
- package/src/script/vocabulary.g.ts +175 -0
- package/src/script/words/audio.ts +114 -0
- package/src/script/words/cells.ts +78 -0
- package/src/script/words/compile-time.ts +27 -0
- package/src/script/words/composite.ts +125 -0
- package/src/script/words/elements.ts +202 -0
- package/src/script/words/extension.ts +25 -0
- package/src/script/words/flow.ts +391 -0
- package/src/script/words/index.ts +23 -0
- package/src/script/words/interaction.ts +44 -0
- package/src/script/words/minigame.ts +66 -0
- package/src/script/words/narrative.ts +261 -0
- package/src/script/words/save.ts +54 -0
- package/src/script/words/vars.ts +155 -0
- package/src/script/words/video.ts +80 -0
- package/src/shared/guards.ts +19 -0
- package/src/shared/index.ts +6 -0
- package/src/shared/pointer.ts +23 -0
- package/README.md +0 -3
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Script 词汇层 · **流程域**(wait / pause / random / 跳转导航 / func·call / 控制流 /
|
|
3
|
+
* assert / guard)。
|
|
4
|
+
*
|
|
5
|
+
* **控制流是结构化构造,不是 TS 原生语句**:`when(...).then([...])` 产出 if-op
|
|
6
|
+
* **节点**(数据);TS 原生 `if` 在构建期求值(分支展开成静态数据)——两者语义不同,
|
|
7
|
+
* 按「这一分支是运行期还是构建期」选择。运行期分支的运行期循环同理(whileDo/forIn)。
|
|
8
|
+
*
|
|
9
|
+
* `Stmt` 刻意保持 StoryCommand envelope(**不**收窄为 ScriptCommand):
|
|
10
|
+
* 块体内建 op 与扩展 op(extOp)混排是合法创作形态,闭合联合会把扩展挡在块体外。
|
|
11
|
+
*/
|
|
12
|
+
import type { StoryCommand } from "@lingfan-engine/core";
|
|
13
|
+
import type { CommandOf, ScriptValue } from "../../schema/index.ts";
|
|
14
|
+
import type { CellHandle, KnownColumnName, KnownGuardName } from "./cells.ts";
|
|
15
|
+
|
|
16
|
+
export type Stmt = StoryCommand;
|
|
17
|
+
|
|
18
|
+
// —— 等待与随机 ——
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 等待 `seconds` 秒后继续。等待建立时就是回溯检查点;开启 autoSave 时
|
|
22
|
+
* 此处自动存档。
|
|
23
|
+
* @param seconds 等待秒数。
|
|
24
|
+
* @param opts 可选项:skipable 为 true 时玩家可点击/按键提前跳过。
|
|
25
|
+
* @returns `wait` 故事命令。
|
|
26
|
+
* @example
|
|
27
|
+
* wait(2, { skipable: true });
|
|
28
|
+
*/
|
|
29
|
+
export function wait(
|
|
30
|
+
seconds: number,
|
|
31
|
+
opts?: { skipable?: boolean },
|
|
32
|
+
): CommandOf<"wait"> {
|
|
33
|
+
return { op: "wait", seconds, ...opts };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 定长暂停(不可跳过——可跳过的等待用 {@link wait} 传
|
|
38
|
+
* `{ skipable: true }`)。等待建立时建立回溯检查点。
|
|
39
|
+
* @param seconds 暂停秒数。
|
|
40
|
+
* @returns `pause` 故事命令。
|
|
41
|
+
*/
|
|
42
|
+
export function pause(seconds: number): CommandOf<"pause"> {
|
|
43
|
+
return { op: "pause", seconds };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 用显式种子掷一次随机数:把 `[min, max]` **闭区间**内的一个整数写进
|
|
48
|
+
* 变量 `into`。种子同时进入随机状态——回溯重放时序列完全一致。
|
|
49
|
+
* `seed` 必须是整数;`min > max` 报错停机。
|
|
50
|
+
* @param seed 随机种子(整数,决定具体取值)。
|
|
51
|
+
* @param min 下界(含)。
|
|
52
|
+
* @param max 上界(含)。
|
|
53
|
+
* @param into 收纳结果的变量名。
|
|
54
|
+
* @returns `random` 故事命令。
|
|
55
|
+
* @example
|
|
56
|
+
* random(20260101, 1, 6, "dice");
|
|
57
|
+
* say("掷出了 {dice} 点!");
|
|
58
|
+
*/
|
|
59
|
+
export function random(
|
|
60
|
+
seed: number,
|
|
61
|
+
min: number,
|
|
62
|
+
max: number,
|
|
63
|
+
into: string,
|
|
64
|
+
): CommandOf<"random"> {
|
|
65
|
+
return { op: "random", seed, range: [min, max], var: into };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// —— 跳转与导航 ——
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 立即跳进 `target` 列继续(列名即标签,同一故事文件内跳转)。
|
|
72
|
+
* @param target 目标列名(`stories.gen.d.ts` 存在时收窄为工程内列名字面量联合)。
|
|
73
|
+
* @returns `jump` 故事命令。
|
|
74
|
+
* @example
|
|
75
|
+
* when(cond`gold >= 10`, [jump("shop")]);
|
|
76
|
+
*/
|
|
77
|
+
export function jump(target: KnownColumnName): CommandOf<"jump"> {
|
|
78
|
+
return { op: "jump", target };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* 导航到目标列继续:`scene` 给出时优先(跨章节/场景跳转),缺省用
|
|
83
|
+
* `path`。跳转前会清掉旧列的对话残留画面;不建立检查点。
|
|
84
|
+
* @param path 目标列名(缺省目标)。
|
|
85
|
+
* @param scene 目标列名(显式给出时优先于 path)。
|
|
86
|
+
* @returns `navigate` 故事命令。
|
|
87
|
+
* @example
|
|
88
|
+
* navigate("ep2_gate", "ep2");
|
|
89
|
+
*/
|
|
90
|
+
export function navigate(
|
|
91
|
+
path: KnownColumnName,
|
|
92
|
+
scene?: KnownColumnName,
|
|
93
|
+
): CommandOf<"navigate"> {
|
|
94
|
+
return { op: "navigate", path, ...(scene === undefined ? {} : { scene }) };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// —— 子过程(func / call / ret)——
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* 注册一个具名子过程:执行到这里时把 `body` 挂进函数表,之后用
|
|
101
|
+
* {@link call} 调用。对同名重复注册报错(除非两次定义完全相同)。
|
|
102
|
+
* @param name 子过程名(call 时引用)。
|
|
103
|
+
* @param params 形参名列表(call 的实参按位绑定进来)。
|
|
104
|
+
* @param 子过程体(一组故事命令)。
|
|
105
|
+
* @returns `func` 故事命令。
|
|
106
|
+
* @example
|
|
107
|
+
* func("greet", ["who"], [say("你好,{who}!")]);
|
|
108
|
+
* call("greet", ["阿岚"]);
|
|
109
|
+
*/
|
|
110
|
+
export function func(
|
|
111
|
+
name: string,
|
|
112
|
+
params: readonly string[],
|
|
113
|
+
body: readonly Stmt[],
|
|
114
|
+
): CommandOf<"func"> {
|
|
115
|
+
return { op: "func", name, params: [...params], body: [...body] };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* 调用 {@link func} 注册的子过程(或跳到普通流程列):实参逐项求值后
|
|
120
|
+
* 按位绑定给形参,过程体跑完(或 {@link ret})后回到调用点继续。
|
|
121
|
+
* 参数个数对不上报错停机。
|
|
122
|
+
* @param target 子过程名或目标列名。
|
|
123
|
+
* @param args 实参列表(字面量或 `"{expr}"` 表达式串,按位绑定)。
|
|
124
|
+
* @param into 结果绑定名:被调过程 {@link ret} 带值返回时,把返回值
|
|
125
|
+
* 声明为调用方作用域里的这个局部变量;过程正常跑完没有 ret 带值时绑定为
|
|
126
|
+
* `null`(与表达式内建的缺失语义同族)。不带 `into` 时返回值被静默丢弃。
|
|
127
|
+
* @returns `call` 故事命令。
|
|
128
|
+
* @example
|
|
129
|
+
* call("greet", ["阿岚"]);
|
|
130
|
+
* call("greet", ["阿岚"], "greeting");
|
|
131
|
+
*/
|
|
132
|
+
export function call(
|
|
133
|
+
target: string,
|
|
134
|
+
args?: readonly ScriptValue[],
|
|
135
|
+
into?: string,
|
|
136
|
+
): CommandOf<"call"> {
|
|
137
|
+
return {
|
|
138
|
+
op: "call",
|
|
139
|
+
target,
|
|
140
|
+
...(args === undefined ? {} : { args: [...args] }),
|
|
141
|
+
...(into === undefined ? {} : { into }),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* 从子过程返回到调用点。在子过程(func 体)之外使用报错停机。
|
|
147
|
+
* @param value 可选返回值(字面量或 `"{expr}"` 表达式串):带 `into`
|
|
148
|
+
* 的调用点会把它绑定成局部变量,其余调用点丢弃。
|
|
149
|
+
* @returns `return` 故事命令。
|
|
150
|
+
* @example
|
|
151
|
+
* func("early", [], [when(cond`hp <= 0`, [ret()]), say("继续")]);
|
|
152
|
+
* func("level", ["exp"], [ret(`Math.floor(exp / 100)`)]);
|
|
153
|
+
*/
|
|
154
|
+
export function ret(value?: ScriptValue): CommandOf<"return"> {
|
|
155
|
+
return { op: "return", ...(value === undefined ? {} : { value }) };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// —— 条件分支(双入口)——
|
|
159
|
+
// 为什么不做一个可链式的 when:链式方法名(elif/else)与 if-op 的**数据字段同名**
|
|
160
|
+
// ——方法挂上命令对象后,读字段读到的是方法(elifs.push is not a function),
|
|
161
|
+
// 未收尾的链放进命令数组还会把函数属性带进 Story ⇒ build 期响亮报错。
|
|
162
|
+
// 拆两个入口:`when` 覆盖最常用(then-only,干净数据直出);`whenChain` 才有链式。
|
|
163
|
+
|
|
164
|
+
export interface IfChain {
|
|
165
|
+
elif(cond: string, body: readonly Stmt[]): IfChain;
|
|
166
|
+
else(body: readonly Stmt[]): Stmt;
|
|
167
|
+
/** elif 链收尾(无 else 分支时)——产物 = 最终 if 数据 */
|
|
168
|
+
end(): Stmt;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* 运行期条件分支(then-only 最常用形态):执行到这里时求值 `cond`,
|
|
173
|
+
* 为真才执行 `thenBody`(分支体在新块作用域里执行)。
|
|
174
|
+
*
|
|
175
|
+
* `cond` 是条件表达式:用 {@link cond} 标签串接变量(如
|
|
176
|
+
* `cond\`gold >= 10\``),或直接写 `"{gold >= 10}"`;表达式必须产出
|
|
177
|
+
* 布尔值,否则报错停机。
|
|
178
|
+
*
|
|
179
|
+
* @param cond 条件表达式(运行期求值,须为布尔)。
|
|
180
|
+
* @param thenBody 条件成立时执行的一组命令。
|
|
181
|
+
* @returns `if` 故事命令。
|
|
182
|
+
* @example
|
|
183
|
+
* when(cond`gold >= 10`, [
|
|
184
|
+
* say("买下了药水。"),
|
|
185
|
+
* set("gold", "-= 10"),
|
|
186
|
+
* ]);
|
|
187
|
+
*/
|
|
188
|
+
export function when(cond: string, thenBody: readonly Stmt[]): CommandOf<"if"> {
|
|
189
|
+
return { op: "if", cond, then: [...thenBody] };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* 需要 elif / else 的条件分支:先给条件与 then 体,再链上
|
|
194
|
+
* `.elif(cond, body)`、以 `.else(body)` 或 `.end()` 收尾取产物。
|
|
195
|
+
* 各分支条件都在运行期依次求值,命中第一个为真的分支。
|
|
196
|
+
* @param cond 首个条件表达式(运行期求值,须为布尔)。
|
|
197
|
+
* @param thenBody 首个条件成立时执行的一组命令。
|
|
198
|
+
* @returns 分支链(以 `.else(...)` 或 `.end()` 收尾产出 `if` 故事命令)。
|
|
199
|
+
* @example
|
|
200
|
+
* whenChain(cond`mood > 5`, [say("今天心情不错。")])
|
|
201
|
+
* .elif(cond`mood > 0`, [say("还过得去。")])
|
|
202
|
+
* .else([say("唉……")]);
|
|
203
|
+
*/
|
|
204
|
+
export function whenChain(cond: string, thenBody: readonly Stmt[]): IfChain {
|
|
205
|
+
const then = [...thenBody];
|
|
206
|
+
const elifs: Array<{ cond: string; then: Stmt[] }> = [];
|
|
207
|
+
let elseBody: Stmt[] | undefined;
|
|
208
|
+
const build = (): Stmt => {
|
|
209
|
+
const out: Record<string, unknown> = { op: "if", cond, then };
|
|
210
|
+
if (elifs.length > 0) out.elif = elifs;
|
|
211
|
+
if (elseBody !== undefined) out.else = elseBody;
|
|
212
|
+
return out as unknown as Stmt;
|
|
213
|
+
};
|
|
214
|
+
const chain: IfChain = {
|
|
215
|
+
elif(c, body) {
|
|
216
|
+
elifs.push({ cond: c, then: [...body] });
|
|
217
|
+
return chain;
|
|
218
|
+
},
|
|
219
|
+
else(body) {
|
|
220
|
+
elseBody = [...body];
|
|
221
|
+
return build();
|
|
222
|
+
},
|
|
223
|
+
end() {
|
|
224
|
+
return build();
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
return chain;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// —— 循环(while / for / foreach)——
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* 运行期 while 循环:每轮重新求值 `cond`,为真就执行一轮 `body`
|
|
234
|
+
* (体在新块作用域里执行)。条件必须产出布尔值;永真条件会让循环无法退出。
|
|
235
|
+
* @param cond 每轮重判的条件表达式(须为布尔)。
|
|
236
|
+
* @param body 每轮执行的一组命令。
|
|
237
|
+
* @returns `while` 故事命令。
|
|
238
|
+
* @example
|
|
239
|
+
* whileDo(cond`hunger < 100`, [eat()]);
|
|
240
|
+
*/
|
|
241
|
+
export function whileDo(
|
|
242
|
+
cond: string,
|
|
243
|
+
body: readonly Stmt[],
|
|
244
|
+
): CommandOf<"while"> {
|
|
245
|
+
return { op: "while", cond, body: [...body] };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* 运行期 for 循环:把 `iterable` 求值(必须得到数组),逐元素赋给
|
|
250
|
+
* `varName`(块级临时变量,出循环即销毁)并执行一轮 `body`。
|
|
251
|
+
* @param varName 循环变量名(每轮接收当前元素)。
|
|
252
|
+
* @param iterable 数组表达式(求值结果必须是数组)。
|
|
253
|
+
* @param body 每个元素执行的一组命令。
|
|
254
|
+
* @returns `for` 故事命令。
|
|
255
|
+
* @example
|
|
256
|
+
* forIn("item", "backpack", [say("背包里有:{item}")]);
|
|
257
|
+
*/
|
|
258
|
+
export function forIn(
|
|
259
|
+
varName: string,
|
|
260
|
+
iterable: string,
|
|
261
|
+
body: readonly Stmt[],
|
|
262
|
+
): CommandOf<"for"> {
|
|
263
|
+
return { op: "for", var: varName, in: iterable, body: [...body] };
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* foreach:`collection` = 集合**变量名**(运行期按名 resolve,须为数组)。
|
|
268
|
+
* 形状 = 规范四事实(`data/format/` 校验 / foreachSchema / parseTextStory 产物 /
|
|
269
|
+
* generateText 投影):`{var, key, body}`——`in` 是 DSL 行文法(`foreach "v" in "k"`)
|
|
270
|
+
* 的分隔符,**不属于 op 数据**(`in` 字段属于 `for` op);多产 `in` 会被编辑器
|
|
271
|
+
* 判 unknown-field。漏 `key` parseStory 即拒(执行层以 key resolve,互锁测试锚定)。
|
|
272
|
+
*
|
|
273
|
+
* 逐元素迭代:每轮把当前元素赋给 `varName`(块级临时变量)再执行 `body`。
|
|
274
|
+
*
|
|
275
|
+
* @param varName 循环变量名(每轮接收当前元素)。
|
|
276
|
+
* @param collection 集合**变量名**(运行期按名取数组)。
|
|
277
|
+
* @param body 每个元素执行的一组命令。
|
|
278
|
+
* @returns `foreach` 故事命令。
|
|
279
|
+
* @example
|
|
280
|
+
* forEach("item", "party", [say("{item} 入队了。")]);
|
|
281
|
+
*/
|
|
282
|
+
export function forEach(
|
|
283
|
+
varName: string,
|
|
284
|
+
collection: string,
|
|
285
|
+
body: readonly Stmt[],
|
|
286
|
+
): CommandOf<"foreach"> {
|
|
287
|
+
return {
|
|
288
|
+
op: "foreach",
|
|
289
|
+
var: varName,
|
|
290
|
+
key: collection,
|
|
291
|
+
body: [...body],
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// —— 多路分支与循环控制 ——
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* 运行期多路分支:把 `on` 求值后与各 `cases` 的取值做相等比较,命中
|
|
299
|
+
* 哪个就执行哪个分支体(不穿透、不fall-through);都不命中走
|
|
300
|
+
* `defaultBody`(未给则直接继续)。
|
|
301
|
+
* @param on 被比较的表达式(运行期求值)。
|
|
302
|
+
* @param cases 分支表:`[取值, 分支体]` 对。
|
|
303
|
+
* @param defaultBody 都不命中时执行的命令(可省略)。
|
|
304
|
+
* @returns `switch` 故事命令。
|
|
305
|
+
* @example
|
|
306
|
+
* switchOn("{choice}", [
|
|
307
|
+
* ["inn", [navigate("to_inn")]],
|
|
308
|
+
* ["shop", [navigate("to_shop")]],
|
|
309
|
+
* ], [say("原地待命。")]);
|
|
310
|
+
*/
|
|
311
|
+
export function switchOn(
|
|
312
|
+
on: string,
|
|
313
|
+
cases: ReadonlyArray<readonly [ScriptValue, readonly Stmt[]]>,
|
|
314
|
+
defaultBody?: readonly Stmt[],
|
|
315
|
+
): CommandOf<"switch"> {
|
|
316
|
+
return {
|
|
317
|
+
op: "switch",
|
|
318
|
+
on,
|
|
319
|
+
cases: cases.map(([value, body]) => ({ value, body: [...body] })),
|
|
320
|
+
...(defaultBody === undefined ? {} : { default: [...defaultBody] }),
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* 跳出当前所在循环(while / for / foreach)。在循环体之外使用报错停机。
|
|
326
|
+
* @returns `break` 故事命令。
|
|
327
|
+
* @example
|
|
328
|
+
* whileDo(cond`true`, [when(cond`found`, [breakLoop()]), search()]);
|
|
329
|
+
*/
|
|
330
|
+
export function breakLoop(): CommandOf<"break"> {
|
|
331
|
+
return { op: "break" };
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* 结束当前这一轮循环,直接进入下一轮条件判断。在循环体之外使用报错停机。
|
|
336
|
+
* @returns `continue` 故事命令。
|
|
337
|
+
* @example
|
|
338
|
+
* forIn("x", "list", [when(cond`x == 0`, [continueLoop()]), use(x)]);
|
|
339
|
+
*/
|
|
340
|
+
export function continueLoop(): CommandOf<"continue"> {
|
|
341
|
+
return { op: "continue" };
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// —— 运行期守卫与断言 ——
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* 运行期断言:`cond` 求值为假时故事报错停机(状态原样、停在当前命令),
|
|
348
|
+
* 为真则直接继续。用于给作者捕获「不该发生」的状态。
|
|
349
|
+
* @param cond 断言条件表达式(须为布尔)。
|
|
350
|
+
* @param message 报错附带的说明(可省略)。
|
|
351
|
+
* @returns `assert` 故事命令。
|
|
352
|
+
* @example
|
|
353
|
+
* assert(cond`gold >= 0`, "金币不应为负");
|
|
354
|
+
*/
|
|
355
|
+
export function assert(cond: string, message?: string): CommandOf<"assert"> {
|
|
356
|
+
return { op: "assert", cond, ...(message === undefined ? {} : { message }) };
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
export interface GuardOptions {
|
|
360
|
+
/** 纯数据参数(JSON 安全——嵌套对象/数组合法,运行期 findJsonValueError 兜底) */
|
|
361
|
+
args?: Record<string, unknown>;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* 运行期守卫:调用组合根注册的 `(ctx, args)` 纯函数(未注册名 ⇒ fail-closed)。
|
|
366
|
+
* 名字参数接受**字面量**(注册表有声明 ⇒ 字面量联合,补全 + 写错即红;空表 = 普通
|
|
367
|
+
* string)或 **cell 句柄**(名字只写一次——声明点即引用点)。
|
|
368
|
+
*
|
|
369
|
+
* 值口径原则:**被求值的走 `Value`**(call args / reward /
|
|
370
|
+
* set——运行期表达式求值,标量语义);**被运输的走 JSON**(guard args /
|
|
371
|
+
* minigame config——原样透传宿主,JSON 安全校验兜底)。guardSchema.args 已对齐。
|
|
372
|
+
*
|
|
373
|
+
* 运行期行为:守卫通过 = 故事直接继续(不改任何状态);守卫返回失败、
|
|
374
|
+
* 抛错、或名字未在组合根注册时,故事报错停机(fail-closed)。
|
|
375
|
+
*
|
|
376
|
+
* @param fn 守卫名(字面量联合,补全友好)或 {@link cell} 声明的句柄。
|
|
377
|
+
* @param opts 可选项:args 传给守卫的纯数据参数(JSON 安全)。
|
|
378
|
+
* @returns `guard` 故事命令。
|
|
379
|
+
* @example
|
|
380
|
+
* guard("checkMoney", { args: { cost: 50 } });
|
|
381
|
+
*/
|
|
382
|
+
export function guard(
|
|
383
|
+
fn: KnownGuardName | CellHandle,
|
|
384
|
+
opts?: GuardOptions,
|
|
385
|
+
): CommandOf<"guard"> {
|
|
386
|
+
return {
|
|
387
|
+
op: "guard",
|
|
388
|
+
fn: typeof fn === "string" ? fn : fn.name,
|
|
389
|
+
...(opts?.args === undefined ? {} : { args: { ...opts.args } }),
|
|
390
|
+
};
|
|
391
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Script 词汇层 · 词条实现(作者可用的 builder 与选项类型)。
|
|
3
|
+
* 每个文件对应一族词条:叙事 / 流程 / 变量 / 存档 / 音频 / 视频 / 小游戏 /
|
|
4
|
+
* 元素 / 复合词 / 扩展 / 具名实现槽位 / 交互。
|
|
5
|
+
* 各文件导出名互不重复,故此处整体转发;对外仍由 `script` 出口按族列出名单。
|
|
6
|
+
*
|
|
7
|
+
* 每个词条函数都是**纯数据构造**:只返回一条 `StoryCommand`(与 JSON 同族),
|
|
8
|
+
* 不执行任何副作用——真正的画面/状态效果发生在引擎运行这条命令时。
|
|
9
|
+
* 条目级 JSDoc 以「作者视角」描述调用后玩家看到什么、状态怎么变。
|
|
10
|
+
*/
|
|
11
|
+
export * from "./audio.ts";
|
|
12
|
+
export * from "./cells.ts";
|
|
13
|
+
export * from "./compile-time.ts";
|
|
14
|
+
export * from "./composite.ts";
|
|
15
|
+
export * from "./elements.ts";
|
|
16
|
+
export * from "./extension.ts";
|
|
17
|
+
export * from "./flow.ts";
|
|
18
|
+
export * from "./interaction.ts";
|
|
19
|
+
export * from "./minigame.ts";
|
|
20
|
+
export * from "./narrative.ts";
|
|
21
|
+
export * from "./save.ts";
|
|
22
|
+
export * from "./vars.ts";
|
|
23
|
+
export * from "./video.ts";
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Script 词汇层 · **玩法系统域**(interaction op)。
|
|
3
|
+
*
|
|
4
|
+
* 外部玩法系统(行走 / 战斗 / QTE…)由宿主注册制挂载(未知 system 运行期 fail-closed)——
|
|
5
|
+
* builder 只产数据。onSuccess/onFail 是作者侧驼峰词,builder 负责映射到 op 的 snake_case 字段。
|
|
6
|
+
*/
|
|
7
|
+
import type { CommandOf } from "../../schema/index.ts";
|
|
8
|
+
|
|
9
|
+
export interface InteractionOptions {
|
|
10
|
+
config?: Record<string, unknown>;
|
|
11
|
+
/** 成功/失败分流目标列 */
|
|
12
|
+
onSuccess?: string;
|
|
13
|
+
onFail?: string;
|
|
14
|
+
z?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 让外部玩法系统接管整屏(行走 / 战斗 / QTE…):系统名由宿主注册制提供
|
|
19
|
+
* (未注册的 system 报错停机)。接管期间建立回溯检查点;开启 autoSave 时
|
|
20
|
+
* 此处自动存档。
|
|
21
|
+
*
|
|
22
|
+
* 系统回报结果后:成功走 `onSuccess` 列、失败走 `onFail` 列;
|
|
23
|
+
* 两者都省略则在原列继续。系统附带的状态由引擎归档,回溯/存档一致。
|
|
24
|
+
*
|
|
25
|
+
* @param system 外部玩法系统标识(宿主已注册的)。
|
|
26
|
+
* @param opts 可选项:config 透传给系统的配置(任意 JSON)、
|
|
27
|
+
* onSuccess/onFail 分流目标列、z 实例层级。
|
|
28
|
+
* @returns `interaction` 故事命令。
|
|
29
|
+
* @example
|
|
30
|
+
* interaction("walk", { onSuccess: "arrived", onFail: "cancelled" });
|
|
31
|
+
*/
|
|
32
|
+
export function interaction(
|
|
33
|
+
system: string,
|
|
34
|
+
opts?: InteractionOptions,
|
|
35
|
+
): CommandOf<"interaction"> {
|
|
36
|
+
return {
|
|
37
|
+
op: "interaction",
|
|
38
|
+
system,
|
|
39
|
+
...(opts?.config === undefined ? {} : { config: { ...opts.config } }),
|
|
40
|
+
...(opts?.onSuccess === undefined ? {} : { on_success: opts.onSuccess }),
|
|
41
|
+
...(opts?.onFail === undefined ? {} : { on_fail: opts.onFail }),
|
|
42
|
+
...(opts?.z === undefined ? {} : { z: opts.z }),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Script 词汇层 · **小游戏域**(minigame op + reward 句柄)。
|
|
3
|
+
* UI 组件由注册制挂载(未知 gameId 运行期 fail-closed)——builder 只产数据。
|
|
4
|
+
* onSuccess/onFail 是作者侧驼峰词,builder 负责映射到 op 的 snake_case 字段。
|
|
5
|
+
*/
|
|
6
|
+
import type { CommandOf, ScriptValue } from "../../schema/index.ts";
|
|
7
|
+
|
|
8
|
+
export interface MinigameOptions {
|
|
9
|
+
config?: Record<string, unknown>;
|
|
10
|
+
/** 成功/失败分流目标列 */
|
|
11
|
+
onSuccess?: string;
|
|
12
|
+
onFail?: string;
|
|
13
|
+
reward?: ReadonlyArray<{ readonly key: string; readonly value: ScriptValue }>;
|
|
14
|
+
z?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 进入一个小游戏(UI 组件由注册制挂载,未知 game 报错停机):
|
|
19
|
+
* 玩家玩完这局后故事按胜负分流——成功走 `onSuccess` 列并发放奖励,
|
|
20
|
+
* 失败走 `onFail` 列;两者都省略则在原列继续。
|
|
21
|
+
*
|
|
22
|
+
* 建立等待时就是回溯检查点;开启 autoSave 时此处自动存档。
|
|
23
|
+
* 奖励的 `value` 在运行期求值(支持 `"{expr}"`),回溯重放保持确定。
|
|
24
|
+
*
|
|
25
|
+
* @param game 小游戏标识(宿主已注册的)。
|
|
26
|
+
* @param opts 可选项:config 透传给组件的配置(任意 JSON)、
|
|
27
|
+
* onSuccess/onFail 分流目标列、reward 奖励键值表(用 {@link reward} 构造)、
|
|
28
|
+
* z 实例层级。
|
|
29
|
+
* @returns `minigame` 故事命令。
|
|
30
|
+
* @example
|
|
31
|
+
* minigame("cook", {
|
|
32
|
+
* reward: [reward("coin", "+= 20")],
|
|
33
|
+
* onSuccess: "dish_done",
|
|
34
|
+
* });
|
|
35
|
+
*/
|
|
36
|
+
export function minigame(
|
|
37
|
+
game: string,
|
|
38
|
+
opts?: MinigameOptions,
|
|
39
|
+
): CommandOf<"minigame"> {
|
|
40
|
+
return {
|
|
41
|
+
op: "minigame",
|
|
42
|
+
game,
|
|
43
|
+
...(opts?.config === undefined ? {} : { config: { ...opts.config } }),
|
|
44
|
+
...(opts?.onSuccess === undefined ? {} : { on_success: opts.onSuccess }),
|
|
45
|
+
...(opts?.onFail === undefined ? {} : { on_fail: opts.onFail }),
|
|
46
|
+
...(opts?.reward === undefined
|
|
47
|
+
? {}
|
|
48
|
+
: { reward: opts.reward.map((r) => ({ key: r.key, value: r.value })) }),
|
|
49
|
+
...(opts?.z === undefined ? {} : { z: opts.z }),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 构造一条奖励条目,配合 {@link minigame} 的 `reward` 使用:
|
|
55
|
+
* 小游戏成功后把 `value` 写进状态键 `key`(`value` 支持 `"{expr}"`
|
|
56
|
+
* 运行期求值,如 `"+= 20"` 复合赋值)。
|
|
57
|
+
* @param key 收纳奖励的状态变量名。
|
|
58
|
+
* @param value 奖励值(字面量或 `"{expr}"` 表达式串)。
|
|
59
|
+
* @returns 奖励键值对。
|
|
60
|
+
*/
|
|
61
|
+
export function reward(
|
|
62
|
+
key: string,
|
|
63
|
+
value: ScriptValue,
|
|
64
|
+
): { key: string; value: ScriptValue } {
|
|
65
|
+
return { key, value };
|
|
66
|
+
}
|