@doodle-engine/cli 0.0.17 â 0.0.19
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/CHANGELOG.md +18 -0
- package/dist/__tests__/manifest.test.d.ts +5 -0
- package/dist/__tests__/manifest.test.d.ts.map +1 -0
- package/dist/cli.js +601 -384
- package/dist/commands/build.d.ts.map +1 -1
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/manifest.d.ts +21 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/service-worker.d.ts +14 -0
- package/dist/service-worker.d.ts.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -1,84 +1,84 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { Command as
|
|
3
|
-
import { crayon as
|
|
4
|
-
import { createServer as
|
|
5
|
-
import
|
|
6
|
-
import { watch as
|
|
7
|
-
import { readdir as
|
|
8
|
-
import { join as h, extname as
|
|
9
|
-
import { parse as
|
|
10
|
-
import { parseDialogue as k } from "@doodle-engine/core";
|
|
11
|
-
import
|
|
12
|
-
function
|
|
13
|
-
const
|
|
14
|
-
for (const
|
|
15
|
-
const
|
|
16
|
-
|
|
2
|
+
import { Command as M } from "commander";
|
|
3
|
+
import { crayon as c } from "crayon.js";
|
|
4
|
+
import { createServer as F, build as P } from "vite";
|
|
5
|
+
import G from "@vitejs/plugin-react";
|
|
6
|
+
import { watch as L } from "chokidar";
|
|
7
|
+
import { stat as B, readdir as b, readFile as p, mkdir as T, writeFile as w } from "fs/promises";
|
|
8
|
+
import { join as h, extname as f, relative as E, dirname as U } from "path";
|
|
9
|
+
import { parse as y } from "yaml";
|
|
10
|
+
import { extractAssetPaths as W, getAssetType as R, parseDialogue as k } from "@doodle-engine/core";
|
|
11
|
+
import J from "prompts";
|
|
12
|
+
function C(t, o) {
|
|
13
|
+
const s = [];
|
|
14
|
+
for (const r of Object.values(t.dialogues)) {
|
|
15
|
+
const n = o.get(r.id) || `dialogue:${r.id}`;
|
|
16
|
+
s.push(...Q(r, n));
|
|
17
17
|
}
|
|
18
|
-
for (const
|
|
19
|
-
if (
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
file:
|
|
23
|
-
message: `Character "${
|
|
24
|
-
suggestion: `Create dialogue "${
|
|
18
|
+
for (const r of Object.values(t.characters))
|
|
19
|
+
if (r.dialogue && !t.dialogues[r.dialogue]) {
|
|
20
|
+
const n = o.get(r.id) || `character:${r.id}`;
|
|
21
|
+
s.push({
|
|
22
|
+
file: n,
|
|
23
|
+
message: `Character "${r.id}" references non-existent dialogue "${r.dialogue}"`,
|
|
24
|
+
suggestion: `Create dialogue "${r.dialogue}" or fix the reference`
|
|
25
25
|
});
|
|
26
26
|
}
|
|
27
|
-
return
|
|
27
|
+
return s.push(...K(t, o)), s;
|
|
28
28
|
}
|
|
29
|
-
function
|
|
30
|
-
const
|
|
31
|
-
for (const
|
|
32
|
-
|
|
33
|
-
file:
|
|
34
|
-
message: `Duplicate node ID "${
|
|
29
|
+
function Q(t, o) {
|
|
30
|
+
const s = [], r = /* @__PURE__ */ new Set();
|
|
31
|
+
for (const n of t.nodes)
|
|
32
|
+
r.has(n.id) && s.push({
|
|
33
|
+
file: o,
|
|
34
|
+
message: `Duplicate node ID "${n.id}"`,
|
|
35
35
|
suggestion: "Node IDs must be unique within a dialogue"
|
|
36
|
-
}),
|
|
37
|
-
|
|
38
|
-
file:
|
|
39
|
-
message: `Start node "${
|
|
40
|
-
suggestion: `Add a NODE ${
|
|
36
|
+
}), r.add(n.id);
|
|
37
|
+
r.has(t.startNode) || s.push({
|
|
38
|
+
file: o,
|
|
39
|
+
message: `Start node "${t.startNode}" not found`,
|
|
40
|
+
suggestion: `Add a NODE ${t.startNode} or fix the startNode reference`
|
|
41
41
|
});
|
|
42
|
-
for (const
|
|
43
|
-
|
|
44
|
-
return
|
|
42
|
+
for (const n of t.nodes)
|
|
43
|
+
s.push(...Y(n, r, o));
|
|
44
|
+
return s;
|
|
45
45
|
}
|
|
46
|
-
function
|
|
47
|
-
const
|
|
48
|
-
if (
|
|
49
|
-
file:
|
|
50
|
-
message: `Node "${
|
|
51
|
-
suggestion: `Add NODE ${
|
|
52
|
-
}),
|
|
53
|
-
for (const
|
|
54
|
-
|
|
55
|
-
file:
|
|
56
|
-
message: `Node "${
|
|
57
|
-
suggestion: `Add NODE ${
|
|
58
|
-
}),
|
|
59
|
-
for (const
|
|
60
|
-
if (!
|
|
46
|
+
function Y(t, o, s) {
|
|
47
|
+
const r = [];
|
|
48
|
+
if (t.next && !o.has(t.next) && r.push({
|
|
49
|
+
file: s,
|
|
50
|
+
message: `Node "${t.id}" GOTO "${t.next}" points to non-existent node`,
|
|
51
|
+
suggestion: `Add NODE ${t.next} or fix the GOTO target`
|
|
52
|
+
}), t.conditionalNext)
|
|
53
|
+
for (const n of t.conditionalNext)
|
|
54
|
+
o.has(n.next) || r.push({
|
|
55
|
+
file: s,
|
|
56
|
+
message: `Node "${t.id}" IF block GOTO "${n.next}" points to non-existent node`,
|
|
57
|
+
suggestion: `Add NODE ${n.next} or fix the GOTO target`
|
|
58
|
+
}), r.push(...O(n.condition, t.id, s));
|
|
59
|
+
for (const n of t.choices) {
|
|
60
|
+
if (!n.effects?.some(
|
|
61
61
|
(e) => e.type === "endDialogue" || e.type === "goToLocation" || e.type === "startDialogue"
|
|
62
|
-
) &&
|
|
63
|
-
file:
|
|
64
|
-
message: `Node "${
|
|
65
|
-
suggestion: `Add NODE ${
|
|
66
|
-
}),
|
|
67
|
-
for (const e of
|
|
68
|
-
|
|
69
|
-
if (
|
|
70
|
-
for (const e of
|
|
71
|
-
|
|
62
|
+
) && n.next && !o.has(n.next) && r.push({
|
|
63
|
+
file: s,
|
|
64
|
+
message: `Node "${t.id}" choice "${n.id}" GOTO "${n.next}" points to non-existent node`,
|
|
65
|
+
suggestion: `Add NODE ${n.next} or fix the GOTO target`
|
|
66
|
+
}), n.conditions)
|
|
67
|
+
for (const e of n.conditions)
|
|
68
|
+
r.push(...O(e, t.id, s));
|
|
69
|
+
if (n.effects)
|
|
70
|
+
for (const e of n.effects)
|
|
71
|
+
r.push(...A(e, t.id, s));
|
|
72
72
|
}
|
|
73
|
-
if (
|
|
74
|
-
for (const
|
|
75
|
-
|
|
76
|
-
if (
|
|
77
|
-
for (const
|
|
78
|
-
|
|
79
|
-
return
|
|
73
|
+
if (t.conditions)
|
|
74
|
+
for (const n of t.conditions)
|
|
75
|
+
r.push(...O(n, t.id, s));
|
|
76
|
+
if (t.effects)
|
|
77
|
+
for (const n of t.effects)
|
|
78
|
+
r.push(...A(n, t.id, s));
|
|
79
|
+
return r;
|
|
80
80
|
}
|
|
81
|
-
const
|
|
81
|
+
const z = {
|
|
82
82
|
hasFlag: ["flag"],
|
|
83
83
|
notFlag: ["flag"],
|
|
84
84
|
hasItem: ["itemId"],
|
|
@@ -94,7 +94,7 @@ const P = {
|
|
|
94
94
|
relationshipBelow: ["characterId", "value"],
|
|
95
95
|
itemAt: ["itemId", "locationId"],
|
|
96
96
|
roll: ["min", "max", "threshold"]
|
|
97
|
-
},
|
|
97
|
+
}, V = {
|
|
98
98
|
setFlag: ["flag"],
|
|
99
99
|
clearFlag: ["flag"],
|
|
100
100
|
setVariable: ["variable", "value"],
|
|
@@ -123,142 +123,188 @@ const P = {
|
|
|
123
123
|
showInterlude: ["interludeId"],
|
|
124
124
|
roll: ["variable", "min", "max"]
|
|
125
125
|
};
|
|
126
|
-
function
|
|
127
|
-
const
|
|
128
|
-
if (!
|
|
129
|
-
return
|
|
130
|
-
file:
|
|
131
|
-
message: `Node "${
|
|
132
|
-
}),
|
|
133
|
-
if (
|
|
134
|
-
return
|
|
135
|
-
file:
|
|
136
|
-
message: `Node "${
|
|
137
|
-
}),
|
|
138
|
-
const
|
|
139
|
-
if (!
|
|
140
|
-
return
|
|
141
|
-
for (const
|
|
142
|
-
(
|
|
143
|
-
file:
|
|
144
|
-
message: `Node "${
|
|
126
|
+
function O(t, o, s) {
|
|
127
|
+
const r = [];
|
|
128
|
+
if (!t.type)
|
|
129
|
+
return r.push({
|
|
130
|
+
file: s,
|
|
131
|
+
message: `Node "${o}" has condition with missing type`
|
|
132
|
+
}), r;
|
|
133
|
+
if (t.type === "timeIs")
|
|
134
|
+
return t.hour === void 0 && t.day === void 0 && r.push({
|
|
135
|
+
file: s,
|
|
136
|
+
message: `Node "${o}" condition "timeIs" must have at least one of "hour" or "day" argument`
|
|
137
|
+
}), r;
|
|
138
|
+
const n = z[t.type];
|
|
139
|
+
if (!n)
|
|
140
|
+
return r;
|
|
141
|
+
for (const a of n)
|
|
142
|
+
(t[a] === void 0 || t[a] === null || t[a] === "") && r.push({
|
|
143
|
+
file: s,
|
|
144
|
+
message: `Node "${o}" condition "${t.type}" missing required "${a}" argument`
|
|
145
145
|
});
|
|
146
|
-
return
|
|
146
|
+
return r;
|
|
147
147
|
}
|
|
148
|
-
function
|
|
149
|
-
const
|
|
150
|
-
if (!
|
|
151
|
-
return
|
|
152
|
-
file:
|
|
153
|
-
message: `Node "${
|
|
154
|
-
}),
|
|
155
|
-
const
|
|
156
|
-
if (!
|
|
157
|
-
return
|
|
158
|
-
for (const
|
|
159
|
-
(
|
|
160
|
-
file:
|
|
161
|
-
message: `Node "${
|
|
148
|
+
function A(t, o, s) {
|
|
149
|
+
const r = [];
|
|
150
|
+
if (!t.type)
|
|
151
|
+
return r.push({
|
|
152
|
+
file: s,
|
|
153
|
+
message: `Node "${o}" has effect with missing type`
|
|
154
|
+
}), r;
|
|
155
|
+
const n = V[t.type];
|
|
156
|
+
if (!n)
|
|
157
|
+
return r;
|
|
158
|
+
for (const a of n)
|
|
159
|
+
(t[a] === void 0 || t[a] === null || t[a] === "") && r.push({
|
|
160
|
+
file: s,
|
|
161
|
+
message: `Node "${o}" effect "${t.type}" missing required "${a}" argument`
|
|
162
162
|
});
|
|
163
|
-
return
|
|
163
|
+
return r;
|
|
164
164
|
}
|
|
165
|
-
function
|
|
166
|
-
const
|
|
167
|
-
for (const e of Object.values(
|
|
168
|
-
for (const
|
|
169
|
-
|
|
170
|
-
const
|
|
165
|
+
function K(t, o) {
|
|
166
|
+
const s = [], r = /* @__PURE__ */ new Set();
|
|
167
|
+
for (const e of Object.values(t.locales))
|
|
168
|
+
for (const i of Object.keys(e))
|
|
169
|
+
r.add(i);
|
|
170
|
+
const n = (e) => e.startsWith("@"), a = (e, i, l) => {
|
|
171
171
|
const d = e.slice(1);
|
|
172
|
-
if (!
|
|
173
|
-
const u =
|
|
174
|
-
|
|
172
|
+
if (!r.has(d)) {
|
|
173
|
+
const u = o.get(i) || `${l}:${i}`;
|
|
174
|
+
s.push({
|
|
175
175
|
file: u,
|
|
176
176
|
message: `Localization key "${e}" not found in any locale file`,
|
|
177
177
|
suggestion: `Add "${d}: ..." to your locale files`
|
|
178
178
|
});
|
|
179
179
|
}
|
|
180
180
|
};
|
|
181
|
-
for (const e of Object.values(
|
|
182
|
-
|
|
183
|
-
for (const e of Object.values(
|
|
184
|
-
|
|
185
|
-
for (const e of Object.values(
|
|
186
|
-
|
|
187
|
-
for (const e of Object.values(
|
|
188
|
-
|
|
189
|
-
for (const
|
|
190
|
-
|
|
181
|
+
for (const e of Object.values(t.locations))
|
|
182
|
+
n(e.name) && a(e.name, e.id, "location"), n(e.description) && a(e.description, e.id, "location");
|
|
183
|
+
for (const e of Object.values(t.characters))
|
|
184
|
+
n(e.name) && a(e.name, e.id, "character"), n(e.biography) && a(e.biography, e.id, "character");
|
|
185
|
+
for (const e of Object.values(t.items))
|
|
186
|
+
n(e.name) && a(e.name, e.id, "item"), n(e.description) && a(e.description, e.id, "item");
|
|
187
|
+
for (const e of Object.values(t.quests)) {
|
|
188
|
+
n(e.name) && a(e.name, e.id, "quest"), n(e.description) && a(e.description, e.id, "quest");
|
|
189
|
+
for (const i of e.stages)
|
|
190
|
+
n(i.description) && a(i.description, e.id, "quest");
|
|
191
191
|
}
|
|
192
|
-
for (const e of Object.values(
|
|
193
|
-
|
|
194
|
-
for (const e of Object.values(
|
|
195
|
-
for (const
|
|
196
|
-
|
|
197
|
-
for (const l of
|
|
198
|
-
|
|
192
|
+
for (const e of Object.values(t.journalEntries))
|
|
193
|
+
n(e.title) && a(e.title, e.id, "journal"), n(e.text) && a(e.text, e.id, "journal");
|
|
194
|
+
for (const e of Object.values(t.dialogues))
|
|
195
|
+
for (const i of e.nodes) {
|
|
196
|
+
n(i.text) && a(i.text, e.id, "dialogue");
|
|
197
|
+
for (const l of i.choices)
|
|
198
|
+
n(l.text) && a(l.text, e.id, "dialogue");
|
|
199
199
|
}
|
|
200
|
-
for (const e of Object.values(
|
|
201
|
-
|
|
202
|
-
return
|
|
200
|
+
for (const e of Object.values(t.interludes))
|
|
201
|
+
n(e.text) && a(e.text, e.id, "interlude");
|
|
202
|
+
return s;
|
|
203
203
|
}
|
|
204
|
-
function I(
|
|
205
|
-
if (
|
|
206
|
-
console.log(
|
|
204
|
+
function I(t) {
|
|
205
|
+
if (t.length === 0) {
|
|
206
|
+
console.log(c.green("â No validation errors"));
|
|
207
207
|
return;
|
|
208
208
|
}
|
|
209
|
-
console.log(
|
|
210
|
-
â Found ${
|
|
209
|
+
console.log(c.red(`
|
|
210
|
+
â Found ${t.length} validation error${t.length === 1 ? "" : "s"}:
|
|
211
211
|
`));
|
|
212
|
-
for (const
|
|
213
|
-
console.log(
|
|
212
|
+
for (const o of t)
|
|
213
|
+
console.log(c.bold(o.file) + (o.line ? `:${o.line}` : "")), console.log(" " + c.red(o.message)), o.suggestion && console.log(" " + c.dim(o.suggestion)), console.log();
|
|
214
|
+
}
|
|
215
|
+
async function q(t, o, s, r, n = Date.now().toString()) {
|
|
216
|
+
const { shell: a, game: e } = W(s, r);
|
|
217
|
+
async function i(g) {
|
|
218
|
+
const _ = h(o, g.startsWith("/") ? g.slice(1) : g);
|
|
219
|
+
try {
|
|
220
|
+
return (await B(_)).size;
|
|
221
|
+
} catch {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
const l = await Promise.all(
|
|
226
|
+
a.map(async (g) => ({
|
|
227
|
+
path: g,
|
|
228
|
+
type: R(g),
|
|
229
|
+
size: await i(g),
|
|
230
|
+
tier: 1
|
|
231
|
+
}))
|
|
232
|
+
), d = await Promise.all(
|
|
233
|
+
e.map(async (g) => ({
|
|
234
|
+
path: g,
|
|
235
|
+
type: R(g),
|
|
236
|
+
size: await i(g),
|
|
237
|
+
tier: 2
|
|
238
|
+
}))
|
|
239
|
+
), u = l.reduce((g, _) => g + (_.size ?? 0), 0), m = d.reduce((g, _) => g + (_.size ?? 0), 0);
|
|
240
|
+
return {
|
|
241
|
+
version: n,
|
|
242
|
+
shell: l,
|
|
243
|
+
game: d,
|
|
244
|
+
shellSize: u,
|
|
245
|
+
totalSize: u + m
|
|
246
|
+
};
|
|
214
247
|
}
|
|
215
|
-
const
|
|
216
|
-
async function
|
|
217
|
-
const
|
|
218
|
-
console.log(""), console.log(
|
|
219
|
-
const
|
|
248
|
+
const S = "ðū", X = "âĻ", Z = "âïļ", ee = "â";
|
|
249
|
+
async function te() {
|
|
250
|
+
const t = process.cwd(), o = h(t, "content");
|
|
251
|
+
console.log(""), console.log(c.bold.magenta(` ${S} Doodle Engine Dev Server ${S}`)), console.log("");
|
|
252
|
+
const s = {
|
|
220
253
|
name: "doodle-content-loader",
|
|
221
|
-
configureServer(
|
|
222
|
-
|
|
254
|
+
configureServer(n) {
|
|
255
|
+
n.middlewares.use("/api/content", async (d, u) => {
|
|
223
256
|
try {
|
|
224
|
-
const m = await
|
|
257
|
+
const m = await j(o);
|
|
225
258
|
u.setHeader("Content-Type", "application/json"), u.end(JSON.stringify(m));
|
|
226
259
|
} catch (m) {
|
|
227
|
-
console.error(
|
|
260
|
+
console.error(c.red(" Error loading content:"), m), u.statusCode = 500, u.end(JSON.stringify({ error: "Failed to load content" }));
|
|
261
|
+
}
|
|
262
|
+
}), n.middlewares.use("/api/manifest", async (d, u) => {
|
|
263
|
+
try {
|
|
264
|
+
const { registry: m, config: g } = await j(o), _ = await q(
|
|
265
|
+
h(t, "assets"),
|
|
266
|
+
t,
|
|
267
|
+
m,
|
|
268
|
+
g,
|
|
269
|
+
"dev"
|
|
270
|
+
);
|
|
271
|
+
u.setHeader("Content-Type", "application/json"), u.end(JSON.stringify(_));
|
|
272
|
+
} catch (m) {
|
|
273
|
+
console.error(c.red(" Error generating manifest:"), m), u.statusCode = 500, u.end(JSON.stringify({ error: "Failed to generate manifest" }));
|
|
228
274
|
}
|
|
229
275
|
});
|
|
230
|
-
const
|
|
276
|
+
const a = L(o, {
|
|
231
277
|
ignored: /(^|[\/\\])\../,
|
|
232
278
|
persistent: !0
|
|
233
279
|
});
|
|
234
280
|
let e = !1;
|
|
235
|
-
|
|
281
|
+
a.on("ready", () => {
|
|
236
282
|
e = !0;
|
|
237
283
|
});
|
|
238
|
-
let
|
|
284
|
+
let i = null;
|
|
239
285
|
const l = (d) => {
|
|
240
|
-
|
|
241
|
-
|
|
286
|
+
i && clearTimeout(i), i = setTimeout(async () => {
|
|
287
|
+
i = null, console.log(d), await ne(o), n.ws.send({ type: "full-reload", path: "*" });
|
|
242
288
|
}, 50);
|
|
243
289
|
};
|
|
244
|
-
|
|
245
|
-
l(
|
|
246
|
-
}),
|
|
247
|
-
e && l(
|
|
290
|
+
a.on("change", (d) => {
|
|
291
|
+
l(c.yellow(` ${Z} Content changed: ${d}`));
|
|
292
|
+
}), a.on("add", (d) => {
|
|
293
|
+
e && l(c.green(` ${ee} Content added: ${d}`));
|
|
248
294
|
});
|
|
249
295
|
}
|
|
250
|
-
},
|
|
251
|
-
root:
|
|
252
|
-
plugins: [
|
|
296
|
+
}, r = await F({
|
|
297
|
+
root: t,
|
|
298
|
+
plugins: [G(), s],
|
|
253
299
|
server: {
|
|
254
300
|
port: 3e3,
|
|
255
301
|
open: !0
|
|
256
302
|
}
|
|
257
303
|
});
|
|
258
|
-
await
|
|
304
|
+
await r.listen(), r.printUrls(), console.log(""), console.log(c.dim(` ${X} Watching content files for changes...`)), console.log("");
|
|
259
305
|
}
|
|
260
|
-
async function
|
|
261
|
-
const
|
|
306
|
+
async function j(t) {
|
|
307
|
+
const o = {
|
|
262
308
|
locations: {},
|
|
263
309
|
characters: {},
|
|
264
310
|
items: {},
|
|
@@ -269,8 +315,8 @@ async function K(n) {
|
|
|
269
315
|
interludes: {},
|
|
270
316
|
locales: {}
|
|
271
317
|
};
|
|
272
|
-
let
|
|
273
|
-
const
|
|
318
|
+
let s = null;
|
|
319
|
+
const r = [
|
|
274
320
|
{ dir: "locations", key: "locations" },
|
|
275
321
|
{ dir: "characters", key: "characters" },
|
|
276
322
|
{ dir: "items", key: "items" },
|
|
@@ -279,41 +325,41 @@ async function K(n) {
|
|
|
279
325
|
{ dir: "journal", key: "journalEntries" },
|
|
280
326
|
{ dir: "interludes", key: "interludes" }
|
|
281
327
|
];
|
|
282
|
-
for (const { dir:
|
|
283
|
-
const e = h(
|
|
328
|
+
for (const { dir: n, key: a } of r) {
|
|
329
|
+
const e = h(t, n);
|
|
284
330
|
try {
|
|
285
|
-
const
|
|
286
|
-
for (const l of
|
|
287
|
-
if (
|
|
288
|
-
const d = h(e, l), u = await
|
|
289
|
-
m && m.id && (a
|
|
331
|
+
const i = await b(e);
|
|
332
|
+
for (const l of i)
|
|
333
|
+
if (f(l) === ".yaml" || f(l) === ".yml") {
|
|
334
|
+
const d = h(e, l), u = await p(d, "utf-8"), m = y(u);
|
|
335
|
+
m && m.id && (o[a][m.id] = m);
|
|
290
336
|
}
|
|
291
337
|
} catch {
|
|
292
338
|
}
|
|
293
339
|
}
|
|
294
340
|
try {
|
|
295
|
-
const
|
|
296
|
-
for (const e of
|
|
297
|
-
if (
|
|
298
|
-
const
|
|
299
|
-
|
|
341
|
+
const n = h(t, "locales"), a = await b(n);
|
|
342
|
+
for (const e of a)
|
|
343
|
+
if (f(e) === ".yaml" || f(e) === ".yml") {
|
|
344
|
+
const i = h(n, e), l = await p(i, "utf-8"), d = y(l), u = e.replace(/\.(yaml|yml)$/, "");
|
|
345
|
+
o.locales[u] = d ?? {};
|
|
300
346
|
}
|
|
301
347
|
} catch {
|
|
302
348
|
}
|
|
303
349
|
try {
|
|
304
|
-
const
|
|
305
|
-
for (const e of
|
|
306
|
-
if (
|
|
307
|
-
const
|
|
308
|
-
|
|
350
|
+
const n = h(t, "dialogues"), a = await b(n);
|
|
351
|
+
for (const e of a)
|
|
352
|
+
if (f(e) === ".dlg") {
|
|
353
|
+
const i = h(n, e), l = await p(i, "utf-8"), d = e.replace(".dlg", ""), u = k(l, d);
|
|
354
|
+
o.dialogues[u.id] = u;
|
|
309
355
|
}
|
|
310
356
|
} catch {
|
|
311
357
|
}
|
|
312
358
|
try {
|
|
313
|
-
const
|
|
314
|
-
|
|
359
|
+
const n = h(t, "game.yaml"), a = await p(n, "utf-8");
|
|
360
|
+
s = y(a);
|
|
315
361
|
} catch {
|
|
316
|
-
console.warn(
|
|
362
|
+
console.warn(c.yellow(" No game.yaml found, using defaults")), s = {
|
|
317
363
|
startLocation: "tavern",
|
|
318
364
|
startTime: { day: 1, hour: 8 },
|
|
319
365
|
startFlags: {},
|
|
@@ -321,18 +367,18 @@ async function K(n) {
|
|
|
321
367
|
startInventory: []
|
|
322
368
|
};
|
|
323
369
|
}
|
|
324
|
-
return { registry:
|
|
370
|
+
return { registry: o, config: s };
|
|
325
371
|
}
|
|
326
|
-
async function
|
|
372
|
+
async function ne(t) {
|
|
327
373
|
try {
|
|
328
|
-
const { registry:
|
|
329
|
-
|
|
330
|
-
} catch (
|
|
331
|
-
console.error(
|
|
374
|
+
const { registry: o, fileMap: s } = await oe(t), r = C(o, s);
|
|
375
|
+
r.length > 0 && (console.log(""), I(r), console.log(""));
|
|
376
|
+
} catch (o) {
|
|
377
|
+
console.error(c.red(" Error running validation:"), o);
|
|
332
378
|
}
|
|
333
379
|
}
|
|
334
|
-
async function
|
|
335
|
-
const
|
|
380
|
+
async function oe(t) {
|
|
381
|
+
const o = {
|
|
336
382
|
locations: {},
|
|
337
383
|
characters: {},
|
|
338
384
|
items: {},
|
|
@@ -342,7 +388,7 @@ async function X(n) {
|
|
|
342
388
|
journalEntries: {},
|
|
343
389
|
interludes: {},
|
|
344
390
|
locales: {}
|
|
345
|
-
},
|
|
391
|
+
}, s = /* @__PURE__ */ new Map(), r = [
|
|
346
392
|
{ dir: "locations", key: "locations" },
|
|
347
393
|
{ dir: "characters", key: "characters" },
|
|
348
394
|
{ dir: "items", key: "items" },
|
|
@@ -351,66 +397,153 @@ async function X(n) {
|
|
|
351
397
|
{ dir: "journal", key: "journalEntries" },
|
|
352
398
|
{ dir: "interludes", key: "interludes" }
|
|
353
399
|
];
|
|
354
|
-
for (const { dir:
|
|
355
|
-
const e = h(
|
|
400
|
+
for (const { dir: n, key: a } of r) {
|
|
401
|
+
const e = h(t, n);
|
|
356
402
|
try {
|
|
357
|
-
const
|
|
358
|
-
for (const l of
|
|
359
|
-
if (
|
|
360
|
-
const d = h(e, l), u = await
|
|
361
|
-
m && m.id && (a
|
|
403
|
+
const i = await b(e);
|
|
404
|
+
for (const l of i)
|
|
405
|
+
if (f(l) === ".yaml" || f(l) === ".yml") {
|
|
406
|
+
const d = h(e, l), u = await p(d, "utf-8"), m = y(u);
|
|
407
|
+
m && m.id && (o[a][m.id] = m, s.set(m.id, E(process.cwd(), d)));
|
|
362
408
|
}
|
|
363
409
|
} catch {
|
|
364
410
|
}
|
|
365
411
|
}
|
|
366
412
|
try {
|
|
367
|
-
const
|
|
368
|
-
for (const e of
|
|
369
|
-
if (
|
|
370
|
-
const
|
|
371
|
-
|
|
413
|
+
const n = h(t, "locales"), a = await b(n);
|
|
414
|
+
for (const e of a)
|
|
415
|
+
if (f(e) === ".yaml" || f(e) === ".yml") {
|
|
416
|
+
const i = h(n, e), l = await p(i, "utf-8"), d = y(l), u = e.replace(/\.(yaml|yml)$/, "");
|
|
417
|
+
o.locales[u] = d ?? {};
|
|
372
418
|
}
|
|
373
419
|
} catch {
|
|
374
420
|
}
|
|
375
421
|
try {
|
|
376
|
-
const
|
|
377
|
-
for (const e of
|
|
378
|
-
if (
|
|
379
|
-
const
|
|
380
|
-
|
|
422
|
+
const n = h(t, "dialogues"), a = await b(n);
|
|
423
|
+
for (const e of a)
|
|
424
|
+
if (f(e) === ".dlg") {
|
|
425
|
+
const i = h(n, e), l = await p(i, "utf-8"), d = e.replace(".dlg", ""), u = k(l, d);
|
|
426
|
+
o.dialogues[u.id] = u, s.set(u.id, E(process.cwd(), i));
|
|
381
427
|
}
|
|
382
428
|
} catch {
|
|
383
429
|
}
|
|
384
|
-
return { registry:
|
|
430
|
+
return { registry: o, fileMap: s };
|
|
385
431
|
}
|
|
386
|
-
|
|
387
|
-
const
|
|
388
|
-
|
|
389
|
-
|
|
432
|
+
function ae(t) {
|
|
433
|
+
const o = `doodle-engine-assets-${t.version}`, s = [
|
|
434
|
+
...t.shell.map((n) => n.path),
|
|
435
|
+
...t.game.map((n) => n.path)
|
|
436
|
+
], r = JSON.stringify(s, null, 2);
|
|
437
|
+
return `/**
|
|
438
|
+
* Doodle Engine Service Worker
|
|
439
|
+
* Generated at build time â do not edit manually.
|
|
440
|
+
* Cache version: ${t.version}
|
|
441
|
+
*/
|
|
442
|
+
|
|
443
|
+
const CACHE_NAME = ${JSON.stringify(o)};
|
|
444
|
+
const PRECACHE_URLS = ${r};
|
|
445
|
+
|
|
446
|
+
// Install: precache all manifest assets
|
|
447
|
+
self.addEventListener('install', (event) => {
|
|
448
|
+
event.waitUntil(
|
|
449
|
+
caches.open(CACHE_NAME).then((cache) => {
|
|
450
|
+
// Cache assets individually so one failure doesn't block everything
|
|
451
|
+
return Promise.allSettled(
|
|
452
|
+
PRECACHE_URLS.map((url) =>
|
|
453
|
+
cache.add(url).catch((err) => {
|
|
454
|
+
console.warn('[sw] Failed to precache:', url, err);
|
|
455
|
+
})
|
|
456
|
+
)
|
|
457
|
+
);
|
|
458
|
+
}).then(() => self.skipWaiting())
|
|
459
|
+
);
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
// Activate: clean up old caches
|
|
463
|
+
self.addEventListener('activate', (event) => {
|
|
464
|
+
event.waitUntil(
|
|
465
|
+
caches.keys().then((keys) =>
|
|
466
|
+
Promise.all(
|
|
467
|
+
keys
|
|
468
|
+
.filter((key) => key.startsWith('doodle-engine-assets-') && key !== CACHE_NAME)
|
|
469
|
+
.map((key) => caches.delete(key))
|
|
470
|
+
)
|
|
471
|
+
).then(() => self.clients.claim())
|
|
472
|
+
);
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
// Fetch: cache-first for precached assets, network-first for everything else
|
|
476
|
+
self.addEventListener('fetch', (event) => {
|
|
477
|
+
const url = new URL(event.request.url);
|
|
478
|
+
|
|
479
|
+
// Only handle same-origin GET requests
|
|
480
|
+
if (event.request.method !== 'GET' || url.origin !== self.location.origin) {
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Skip API and non-asset requests â serve from network
|
|
485
|
+
if (url.pathname.startsWith('/api/')) {
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
event.respondWith(
|
|
490
|
+
caches.match(event.request).then((cached) => {
|
|
491
|
+
if (cached) return cached;
|
|
492
|
+
|
|
493
|
+
return fetch(event.request).then((response) => {
|
|
494
|
+
// Cache successful responses for precached paths
|
|
495
|
+
if (response.ok && PRECACHE_URLS.includes(url.pathname)) {
|
|
496
|
+
caches.open(CACHE_NAME).then((cache) => {
|
|
497
|
+
cache.put(event.request, response.clone());
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
return response;
|
|
501
|
+
});
|
|
502
|
+
})
|
|
503
|
+
);
|
|
504
|
+
});
|
|
505
|
+
`;
|
|
506
|
+
}
|
|
507
|
+
async function re() {
|
|
508
|
+
const t = process.cwd(), o = h(t, "content");
|
|
509
|
+
console.log(""), console.log(c.bold.magenta("ð Building Doodle Engine game...")), console.log(""), console.log(c.dim("Validating content..."));
|
|
510
|
+
let s, r, n;
|
|
390
511
|
try {
|
|
391
|
-
const
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
console.
|
|
512
|
+
const a = await se(o);
|
|
513
|
+
r = a.registry, n = a.config;
|
|
514
|
+
const { fileMap: e } = a, i = C(r, e);
|
|
515
|
+
I(i), i.length > 0 && (console.log(c.red("Build failed due to validation errors.")), console.log(""), process.exit(1)), s = { registry: r, config: n };
|
|
516
|
+
} catch (a) {
|
|
517
|
+
console.error(c.red("Error loading content:"), a), process.exit(1);
|
|
395
518
|
}
|
|
396
519
|
console.log("");
|
|
397
520
|
try {
|
|
398
|
-
await
|
|
399
|
-
root:
|
|
400
|
-
plugins: [
|
|
521
|
+
await P({
|
|
522
|
+
root: t,
|
|
523
|
+
plugins: [G()],
|
|
401
524
|
build: {
|
|
402
525
|
outDir: "dist",
|
|
403
526
|
emptyOutDir: !0
|
|
404
527
|
}
|
|
405
528
|
});
|
|
406
|
-
const
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
529
|
+
const a = h(t, "dist"), e = t;
|
|
530
|
+
console.log(c.dim("Generating asset manifest..."));
|
|
531
|
+
const i = await q(
|
|
532
|
+
h(t, "assets"),
|
|
533
|
+
e,
|
|
534
|
+
r,
|
|
535
|
+
n,
|
|
536
|
+
Date.now().toString()
|
|
537
|
+
), l = h(a, "api");
|
|
538
|
+
await T(l, { recursive: !0 }), await w(h(l, "content"), JSON.stringify(s)), await w(h(l, "manifest"), JSON.stringify(i)), await w(h(a, "asset-manifest.json"), JSON.stringify(i, null, 2)), console.log(c.dim("Generating service worker..."));
|
|
539
|
+
const d = ae(i);
|
|
540
|
+
await w(h(a, "sw.js"), d), console.log(""), console.log(c.green("â
Build complete! Output in dist/")), console.log(""), console.log("To preview the build:"), console.log(c.dim(" yarn preview")), console.log("");
|
|
541
|
+
} catch (a) {
|
|
542
|
+
console.error(c.red("Build failed:"), a), process.exit(1);
|
|
410
543
|
}
|
|
411
544
|
}
|
|
412
|
-
async function
|
|
413
|
-
const
|
|
545
|
+
async function se(t) {
|
|
546
|
+
const o = {
|
|
414
547
|
locations: {},
|
|
415
548
|
characters: {},
|
|
416
549
|
items: {},
|
|
@@ -420,7 +553,7 @@ async function ee(n) {
|
|
|
420
553
|
journalEntries: {},
|
|
421
554
|
interludes: {},
|
|
422
555
|
locales: {}
|
|
423
|
-
},
|
|
556
|
+
}, s = /* @__PURE__ */ new Map(), r = [
|
|
424
557
|
{ dir: "locations", key: "locations" },
|
|
425
558
|
{ dir: "characters", key: "characters" },
|
|
426
559
|
{ dir: "items", key: "items" },
|
|
@@ -429,57 +562,57 @@ async function ee(n) {
|
|
|
429
562
|
{ dir: "journal", key: "journalEntries" },
|
|
430
563
|
{ dir: "interludes", key: "interludes" }
|
|
431
564
|
];
|
|
432
|
-
for (const { dir:
|
|
433
|
-
const
|
|
565
|
+
for (const { dir: a, key: e } of r) {
|
|
566
|
+
const i = h(t, a);
|
|
434
567
|
try {
|
|
435
|
-
const l = await
|
|
568
|
+
const l = await b(i);
|
|
436
569
|
for (const d of l)
|
|
437
|
-
if (
|
|
438
|
-
const u = h(
|
|
439
|
-
|
|
570
|
+
if (f(d) === ".yaml" || f(d) === ".yml") {
|
|
571
|
+
const u = h(i, d), m = await p(u, "utf-8"), g = y(m);
|
|
572
|
+
g && g.id && (o[e][g.id] = g, s.set(g.id, E(process.cwd(), u)));
|
|
440
573
|
}
|
|
441
574
|
} catch {
|
|
442
575
|
}
|
|
443
576
|
}
|
|
444
577
|
try {
|
|
445
|
-
const
|
|
446
|
-
for (const
|
|
447
|
-
if (
|
|
448
|
-
const l = h(
|
|
449
|
-
|
|
578
|
+
const a = h(t, "locales"), e = await b(a);
|
|
579
|
+
for (const i of e)
|
|
580
|
+
if (f(i) === ".yaml" || f(i) === ".yml") {
|
|
581
|
+
const l = h(a, i), d = await p(l, "utf-8"), u = y(d), m = i.replace(/\.(yaml|yml)$/, "");
|
|
582
|
+
o.locales[m] = u ?? {};
|
|
450
583
|
}
|
|
451
584
|
} catch {
|
|
452
585
|
}
|
|
453
586
|
try {
|
|
454
|
-
const
|
|
455
|
-
for (const
|
|
456
|
-
if (
|
|
457
|
-
const l = h(
|
|
458
|
-
|
|
587
|
+
const a = h(t, "dialogues"), e = await b(a);
|
|
588
|
+
for (const i of e)
|
|
589
|
+
if (f(i) === ".dlg") {
|
|
590
|
+
const l = h(a, i), d = await p(l, "utf-8"), u = i.replace(".dlg", ""), m = k(d, u);
|
|
591
|
+
o.dialogues[m.id] = m, s.set(m.id, E(process.cwd(), l));
|
|
459
592
|
}
|
|
460
593
|
} catch {
|
|
461
594
|
}
|
|
462
|
-
let
|
|
595
|
+
let n = null;
|
|
463
596
|
try {
|
|
464
|
-
const
|
|
465
|
-
|
|
597
|
+
const a = h(t, "game.yaml"), e = await p(a, "utf-8");
|
|
598
|
+
n = y(e);
|
|
466
599
|
} catch {
|
|
467
|
-
|
|
600
|
+
n = { id: "game", startLocation: "", startTime: { day: 1, hour: 8 }, startFlags: {}, startVariables: {}, startInventory: [] };
|
|
468
601
|
}
|
|
469
|
-
return { registry:
|
|
602
|
+
return { registry: o, fileMap: s, config: n };
|
|
470
603
|
}
|
|
471
|
-
async function
|
|
472
|
-
const
|
|
473
|
-
console.log(""), console.log(
|
|
604
|
+
async function ie() {
|
|
605
|
+
const t = process.cwd(), o = h(t, "content");
|
|
606
|
+
console.log(""), console.log(c.bold.magenta("ðū Validating Doodle Engine content...")), console.log("");
|
|
474
607
|
try {
|
|
475
|
-
const { registry:
|
|
476
|
-
I(
|
|
477
|
-
} catch (
|
|
478
|
-
console.error(
|
|
608
|
+
const { registry: s, fileMap: r } = await ce(o), n = C(s, r);
|
|
609
|
+
I(n), n.length > 0 && process.exit(1);
|
|
610
|
+
} catch (s) {
|
|
611
|
+
console.error(c.red("Error loading content:"), s), process.exit(1);
|
|
479
612
|
}
|
|
480
613
|
}
|
|
481
|
-
async function
|
|
482
|
-
const
|
|
614
|
+
async function ce(t) {
|
|
615
|
+
const o = {
|
|
483
616
|
locations: {},
|
|
484
617
|
characters: {},
|
|
485
618
|
items: {},
|
|
@@ -488,7 +621,7 @@ async function te(n) {
|
|
|
488
621
|
quests: {},
|
|
489
622
|
journalEntries: {},
|
|
490
623
|
locales: {}
|
|
491
|
-
},
|
|
624
|
+
}, s = /* @__PURE__ */ new Map(), r = [
|
|
492
625
|
{ dir: "locations", key: "locations" },
|
|
493
626
|
{ dir: "characters", key: "characters" },
|
|
494
627
|
{ dir: "items", key: "items" },
|
|
@@ -496,43 +629,43 @@ async function te(n) {
|
|
|
496
629
|
{ dir: "quests", key: "quests" },
|
|
497
630
|
{ dir: "journal", key: "journalEntries" }
|
|
498
631
|
];
|
|
499
|
-
for (const { dir:
|
|
500
|
-
const e = h(
|
|
632
|
+
for (const { dir: n, key: a } of r) {
|
|
633
|
+
const e = h(t, n);
|
|
501
634
|
try {
|
|
502
|
-
const
|
|
503
|
-
for (const l of
|
|
504
|
-
if (
|
|
505
|
-
const d = h(e, l), u = await
|
|
506
|
-
m && m.id && (a
|
|
635
|
+
const i = await b(e);
|
|
636
|
+
for (const l of i)
|
|
637
|
+
if (f(l) === ".yaml" || f(l) === ".yml") {
|
|
638
|
+
const d = h(e, l), u = await p(d, "utf-8"), m = y(u);
|
|
639
|
+
m && m.id && (o[a][m.id] = m, s.set(m.id, E(process.cwd(), d)));
|
|
507
640
|
}
|
|
508
641
|
} catch {
|
|
509
642
|
}
|
|
510
643
|
}
|
|
511
644
|
try {
|
|
512
|
-
const
|
|
513
|
-
for (const e of
|
|
514
|
-
if (
|
|
515
|
-
const
|
|
516
|
-
|
|
645
|
+
const n = h(t, "locales"), a = await b(n);
|
|
646
|
+
for (const e of a)
|
|
647
|
+
if (f(e) === ".yaml" || f(e) === ".yml") {
|
|
648
|
+
const i = h(n, e), l = await p(i, "utf-8"), d = y(l), u = e.replace(/\.(yaml|yml)$/, "");
|
|
649
|
+
o.locales[u] = d ?? {};
|
|
517
650
|
}
|
|
518
651
|
} catch {
|
|
519
652
|
}
|
|
520
653
|
try {
|
|
521
|
-
const
|
|
522
|
-
for (const e of
|
|
523
|
-
if (
|
|
524
|
-
const
|
|
525
|
-
|
|
654
|
+
const n = h(t, "dialogues"), a = await b(n);
|
|
655
|
+
for (const e of a)
|
|
656
|
+
if (f(e) === ".dlg") {
|
|
657
|
+
const i = h(n, e), l = await p(i, "utf-8"), d = e.replace(".dlg", ""), u = k(l, d);
|
|
658
|
+
o.dialogues[u.id] = u, s.set(u.id, E(process.cwd(), i));
|
|
526
659
|
}
|
|
527
660
|
} catch {
|
|
528
661
|
}
|
|
529
|
-
return { registry:
|
|
662
|
+
return { registry: o, fileMap: s };
|
|
530
663
|
}
|
|
531
|
-
const
|
|
664
|
+
const le = `node_modules
|
|
532
665
|
dist
|
|
533
666
|
.DS_Store
|
|
534
667
|
*.log
|
|
535
|
-
`,
|
|
668
|
+
`, de = `<!doctype html>
|
|
536
669
|
<html lang="en">
|
|
537
670
|
<head>
|
|
538
671
|
<meta charset="UTF-8" />
|
|
@@ -544,7 +677,7 @@ dist
|
|
|
544
677
|
<script type="module" src="/src/main.tsx"><\/script>
|
|
545
678
|
</body>
|
|
546
679
|
</html>
|
|
547
|
-
`,
|
|
680
|
+
`, ue = `{
|
|
548
681
|
"compilerOptions": {
|
|
549
682
|
"target": "ES2024",
|
|
550
683
|
"lib": ["ES2024", "DOM", "DOM.Iterable"],
|
|
@@ -557,25 +690,26 @@ dist
|
|
|
557
690
|
"forceConsistentCasingInFileNames": true,
|
|
558
691
|
"resolveJsonModule": true,
|
|
559
692
|
"isolatedModules": true,
|
|
560
|
-
"noEmit": true
|
|
693
|
+
"noEmit": true,
|
|
694
|
+
"types": ["vite/client"]
|
|
561
695
|
},
|
|
562
696
|
"include": ["src"]
|
|
563
697
|
}
|
|
564
|
-
`,
|
|
698
|
+
`, he = `id: bartender
|
|
565
699
|
name: "@character.bartender.name"
|
|
566
700
|
biography: "@character.bartender.bio"
|
|
567
701
|
portrait: ""
|
|
568
702
|
location: tavern
|
|
569
703
|
dialogue: bartender_greeting
|
|
570
704
|
stats: {}
|
|
571
|
-
`,
|
|
705
|
+
`, me = `id: merchant
|
|
572
706
|
name: "@character.merchant.name"
|
|
573
707
|
biography: "@character.merchant.bio"
|
|
574
708
|
portrait: ""
|
|
575
709
|
location: market
|
|
576
710
|
dialogue: merchant_intro
|
|
577
711
|
stats: {}
|
|
578
|
-
`,
|
|
712
|
+
`, ge = `# This dialogue is triggered by clicking the bartender character.
|
|
579
713
|
# SPEAKER: lines set who's talking â matched to character ID (case-insensitive).
|
|
580
714
|
# Nodes can have multiple CHOICE blocks; REQUIRE hides a choice if the condition fails.
|
|
581
715
|
|
|
@@ -757,7 +891,7 @@ NODE work_done
|
|
|
757
891
|
NODE farewell
|
|
758
892
|
BARTENDER: @bartender.farewell
|
|
759
893
|
END dialogue
|
|
760
|
-
`,
|
|
894
|
+
`, fe = `# Standalone skill-check demo â demonstrates dice syntax in isolation.
|
|
761
895
|
#
|
|
762
896
|
# ROLL <variable> <min> <max> â rolls a random integer and stores it in a variable.
|
|
763
897
|
# {varName} â in dialogue text, replaced with the variable's value.
|
|
@@ -821,7 +955,7 @@ NODE failure
|
|
|
821
955
|
CHOICE @bluff.choice.walk_away
|
|
822
956
|
END dialogue
|
|
823
957
|
END
|
|
824
|
-
`,
|
|
958
|
+
`, pe = `# One-time narrator intro for the market. Same pattern as tavern_intro.dlg.
|
|
825
959
|
|
|
826
960
|
TRIGGER market
|
|
827
961
|
REQUIRE notFlag seenMarketIntro
|
|
@@ -836,7 +970,7 @@ NODE start
|
|
|
836
970
|
CHOICE @narrator.choice.look_around
|
|
837
971
|
END dialogue
|
|
838
972
|
END
|
|
839
|
-
`,
|
|
973
|
+
`, be = `# Merchant dialogue. Same speaker-line and CHOICE syntax as bartender_greeting.dlg.
|
|
840
974
|
# The quest choices here demonstrate multi-stage quest gating with questAtStage.
|
|
841
975
|
|
|
842
976
|
NODE start
|
|
@@ -939,7 +1073,7 @@ NODE about_market
|
|
|
939
1073
|
NODE farewell
|
|
940
1074
|
MERCHANT: @merchant.farewell
|
|
941
1075
|
END dialogue
|
|
942
|
-
`,
|
|
1076
|
+
`, ye = `# This dialogue triggers automatically when the player enters the tavern.
|
|
943
1077
|
# TRIGGER <locationId> fires on arrival. REQUIRE conditions guard the trigger.
|
|
944
1078
|
# Use notFlag to make it a one-time intro.
|
|
945
1079
|
|
|
@@ -961,7 +1095,33 @@ NODE start
|
|
|
961
1095
|
CHOICE @narrator.choice.look_around
|
|
962
1096
|
END dialogue
|
|
963
1097
|
END
|
|
964
|
-
`,
|
|
1098
|
+
`, _e = `# Game Configuration
|
|
1099
|
+
|
|
1100
|
+
# Shell screen configuration
|
|
1101
|
+
# Uncomment and provide your own assets to customize the game shell.
|
|
1102
|
+
# The game will work without these â screens will use styled gradients and skip sounds.
|
|
1103
|
+
#
|
|
1104
|
+
# shell:
|
|
1105
|
+
# splash:
|
|
1106
|
+
# logo: /assets/images/studio-logo.png
|
|
1107
|
+
# background: /assets/images/splash-bg.jpg
|
|
1108
|
+
# sound: /assets/audio/sfx/splash-sting.ogg
|
|
1109
|
+
# duration: 2000
|
|
1110
|
+
#
|
|
1111
|
+
# loading:
|
|
1112
|
+
# background: /assets/images/loading-bg.jpg
|
|
1113
|
+
#
|
|
1114
|
+
# title:
|
|
1115
|
+
# logo: /assets/images/game-logo.png
|
|
1116
|
+
# background: /assets/images/title-bg.jpg
|
|
1117
|
+
# music: /assets/audio/music/title-theme.ogg
|
|
1118
|
+
#
|
|
1119
|
+
# uiSounds:
|
|
1120
|
+
# click: /assets/audio/sfx/ui-click.ogg
|
|
1121
|
+
# hover: /assets/audio/sfx/ui-hover.ogg
|
|
1122
|
+
# menuOpen: /assets/audio/sfx/menu-open.ogg
|
|
1123
|
+
# menuClose: /assets/audio/sfx/menu-close.ogg
|
|
1124
|
+
|
|
965
1125
|
startLocation: tavern
|
|
966
1126
|
startTime:
|
|
967
1127
|
day: 1
|
|
@@ -972,7 +1132,7 @@ startVariables:
|
|
|
972
1132
|
reputation: 0
|
|
973
1133
|
_drinksBought: 0
|
|
974
1134
|
startInventory: []
|
|
975
|
-
`,
|
|
1135
|
+
`, we = `id: chapter_one
|
|
976
1136
|
# Background image shown fullscreen during the interlude.
|
|
977
1137
|
# Replace with your own image in assets/images/.
|
|
978
1138
|
background: /assets/images/banners/tavern_banner.jpg
|
|
@@ -1007,26 +1167,26 @@ triggerConditions:
|
|
|
1007
1167
|
effects:
|
|
1008
1168
|
- type: setFlag
|
|
1009
1169
|
flag: seenChapterOne
|
|
1010
|
-
`,
|
|
1170
|
+
`, Ee = `id: old_coin
|
|
1011
1171
|
name: "@item.old_coin.name"
|
|
1012
1172
|
description: "@item.old_coin.description"
|
|
1013
1173
|
icon: ""
|
|
1014
1174
|
image: ""
|
|
1015
1175
|
location: tavern
|
|
1016
1176
|
stats: {}
|
|
1017
|
-
`,
|
|
1177
|
+
`, ve = `id: market_square
|
|
1018
1178
|
title: "@journal.market_square.title"
|
|
1019
1179
|
text: "@journal.market_square.text"
|
|
1020
1180
|
category: places
|
|
1021
|
-
`,
|
|
1181
|
+
`, ke = `id: odd_jobs_accepted
|
|
1022
1182
|
title: "@journal.odd_jobs_accepted.title"
|
|
1023
1183
|
text: "@journal.odd_jobs_accepted.text"
|
|
1024
1184
|
category: quests
|
|
1025
|
-
`,
|
|
1185
|
+
`, Oe = `id: tavern_discovery
|
|
1026
1186
|
title: "@journal.tavern_discovery.title"
|
|
1027
1187
|
text: "@journal.tavern_discovery.text"
|
|
1028
1188
|
category: places
|
|
1029
|
-
`,
|
|
1189
|
+
`, De = `# ===================
|
|
1030
1190
|
# Narrator Intros
|
|
1031
1191
|
# ===================
|
|
1032
1192
|
narrator.tavern_intro: "You push open the heavy oak door and step inside. The warmth hits you first, then the smell â stale ale, wood smoke, and something frying in the kitchen. A few patrons hunch over their mugs. Behind the bar, a broad-shouldered man wipes down glasses, watching you with quiet interest."
|
|
@@ -1171,19 +1331,19 @@ bluff.failure.bartender: "Nope. That'll be five gold."
|
|
|
1171
1331
|
bluff.choice.cheers: "Cheers!"
|
|
1172
1332
|
bluff.choice.fine_pay: "Fine. Here's five gold."
|
|
1173
1333
|
bluff.choice.walk_away: "Never mind then."
|
|
1174
|
-
`,
|
|
1334
|
+
`, Te = `id: market
|
|
1175
1335
|
name: "@location.market.name"
|
|
1176
1336
|
description: "@location.market.description"
|
|
1177
1337
|
banner: ""
|
|
1178
1338
|
music: ""
|
|
1179
1339
|
ambient: ""
|
|
1180
|
-
`,
|
|
1340
|
+
`, Ne = `id: tavern
|
|
1181
1341
|
name: "@location.tavern.name"
|
|
1182
1342
|
description: "@location.tavern.description"
|
|
1183
1343
|
banner: ""
|
|
1184
1344
|
music: ""
|
|
1185
1345
|
ambient: ""
|
|
1186
|
-
`,
|
|
1346
|
+
`, Ce = `id: town
|
|
1187
1347
|
name: "@map.town.name"
|
|
1188
1348
|
image: ""
|
|
1189
1349
|
scale: 1
|
|
@@ -1194,7 +1354,7 @@ locations:
|
|
|
1194
1354
|
- id: market
|
|
1195
1355
|
x: 300
|
|
1196
1356
|
y: 150
|
|
1197
|
-
`,
|
|
1357
|
+
`, Ie = `id: odd_jobs
|
|
1198
1358
|
name: "@quest.odd_jobs.name"
|
|
1199
1359
|
description: "@quest.odd_jobs.description"
|
|
1200
1360
|
stages:
|
|
@@ -1204,10 +1364,10 @@ stages:
|
|
|
1204
1364
|
description: "@quest.odd_jobs.stage.talked_to_merchant"
|
|
1205
1365
|
- id: complete
|
|
1206
1366
|
description: "@quest.odd_jobs.stage.complete"
|
|
1207
|
-
`,
|
|
1367
|
+
`, Re = `import { useEffect, useState } from 'react'
|
|
1208
1368
|
import { Engine } from '@doodle-engine/core'
|
|
1209
1369
|
import type { GameState, Snapshot } from '@doodle-engine/core'
|
|
1210
|
-
import { GameProvider,
|
|
1370
|
+
import { GameProvider, useGame } from '@doodle-engine/react'
|
|
1211
1371
|
|
|
1212
1372
|
export function App() {
|
|
1213
1373
|
const [game, setGame] = useState<{ engine: Engine; snapshot: Snapshot } | null>(null)
|
|
@@ -1223,7 +1383,7 @@ export function App() {
|
|
|
1223
1383
|
}, [])
|
|
1224
1384
|
|
|
1225
1385
|
if (!game) {
|
|
1226
|
-
return <
|
|
1386
|
+
return <div className="app-bootstrap"><div className="spinner" /></div>
|
|
1227
1387
|
}
|
|
1228
1388
|
|
|
1229
1389
|
return (
|
|
@@ -1296,36 +1456,49 @@ function createEmptyState(): GameState {
|
|
|
1296
1456
|
currentLocale: 'en',
|
|
1297
1457
|
}
|
|
1298
1458
|
}
|
|
1299
|
-
`,
|
|
1300
|
-
import type { ContentRegistry, GameConfig } from '@doodle-engine/core'
|
|
1301
|
-
import { GameShell
|
|
1459
|
+
`, Ae = `import { useEffect, useState } from 'react'
|
|
1460
|
+
import type { ContentRegistry, GameConfig, AssetManifest } from '@doodle-engine/core'
|
|
1461
|
+
import { GameShell } from '@doodle-engine/react'
|
|
1302
1462
|
|
|
1303
1463
|
export function App() {
|
|
1304
|
-
const [content, setContent] = useState<{
|
|
1464
|
+
const [content, setContent] = useState<{
|
|
1465
|
+
registry: ContentRegistry
|
|
1466
|
+
config: GameConfig
|
|
1467
|
+
} | null>(null)
|
|
1468
|
+
const [manifest, setManifest] = useState<AssetManifest | null>(null)
|
|
1305
1469
|
|
|
1306
1470
|
useEffect(() => {
|
|
1307
|
-
|
|
1308
|
-
.then(res => res.json())
|
|
1309
|
-
.then(
|
|
1471
|
+
Promise.all([
|
|
1472
|
+
fetch('/api/content').then(res => res.json()),
|
|
1473
|
+
fetch('/api/manifest').then(res => res.json()),
|
|
1474
|
+
]).then(([contentData, manifestData]) => {
|
|
1475
|
+
setContent({ registry: contentData.registry, config: contentData.config })
|
|
1476
|
+
setManifest(manifestData)
|
|
1477
|
+
})
|
|
1310
1478
|
}, [])
|
|
1311
1479
|
|
|
1312
|
-
|
|
1313
|
-
|
|
1480
|
+
// Minimal bootstrap state while fetching manifest/content
|
|
1481
|
+
if (!content || !manifest) {
|
|
1482
|
+
return (
|
|
1483
|
+
<div className="app-bootstrap">
|
|
1484
|
+
<div className="spinner" />
|
|
1485
|
+
</div>
|
|
1486
|
+
)
|
|
1314
1487
|
}
|
|
1315
1488
|
|
|
1316
1489
|
return (
|
|
1317
1490
|
<GameShell
|
|
1318
1491
|
registry={content.registry}
|
|
1319
1492
|
config={content.config}
|
|
1493
|
+
manifest={manifest}
|
|
1320
1494
|
title="My Doodle Game"
|
|
1321
1495
|
subtitle="A text-based adventure"
|
|
1322
|
-
splashDuration={2000}
|
|
1323
1496
|
availableLocales={[{ code: 'en', label: 'English' }]}
|
|
1324
1497
|
devTools={import.meta.env.DEV}
|
|
1325
1498
|
/>
|
|
1326
1499
|
)
|
|
1327
1500
|
}
|
|
1328
|
-
`,
|
|
1501
|
+
`, Se = `body {
|
|
1329
1502
|
margin: 0;
|
|
1330
1503
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
|
1331
1504
|
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
|
@@ -1333,66 +1506,110 @@ export function App() {
|
|
|
1333
1506
|
-webkit-font-smoothing: antialiased;
|
|
1334
1507
|
-moz-osx-font-smoothing: grayscale;
|
|
1335
1508
|
}
|
|
1336
|
-
|
|
1509
|
+
|
|
1510
|
+
/* ââ Shell CSS variables ââââââââââââââââââââââââââââââââââââââââââââ */
|
|
1511
|
+
/* Override these to theme the loading, splash, and title screens. */
|
|
1512
|
+
|
|
1513
|
+
:root {
|
|
1514
|
+
--doodle-bg-primary: #0f0f23;
|
|
1515
|
+
--doodle-bg-secondary: #1a1a2e;
|
|
1516
|
+
--doodle-text-primary: #ffffff;
|
|
1517
|
+
--doodle-text-secondary: #a0a0b0;
|
|
1518
|
+
--doodle-accent: #6366f1;
|
|
1519
|
+
--doodle-accent-hover: #818cf8;
|
|
1520
|
+
--doodle-font-family: system-ui, -apple-system, sans-serif;
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
/* ââ Bootstrap loader (shown before manifest/content are fetched) âââ */
|
|
1524
|
+
|
|
1525
|
+
.app-bootstrap {
|
|
1526
|
+
position: fixed;
|
|
1527
|
+
inset: 0;
|
|
1528
|
+
display: flex;
|
|
1529
|
+
align-items: center;
|
|
1530
|
+
justify-content: center;
|
|
1531
|
+
background: var(--doodle-bg-primary);
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
.app-bootstrap .spinner {
|
|
1535
|
+
width: 40px;
|
|
1536
|
+
height: 40px;
|
|
1537
|
+
border: 3px solid rgba(255, 255, 255, 0.1);
|
|
1538
|
+
border-top-color: var(--doodle-accent);
|
|
1539
|
+
border-radius: 50%;
|
|
1540
|
+
animation: spin 0.8s linear infinite;
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
@keyframes spin {
|
|
1544
|
+
to { transform: rotate(360deg); }
|
|
1545
|
+
}
|
|
1546
|
+
`, je = `import { StrictMode } from 'react'
|
|
1337
1547
|
import { createRoot } from 'react-dom/client'
|
|
1338
1548
|
import { App } from './App'
|
|
1339
1549
|
import './index.css'
|
|
1340
1550
|
|
|
1551
|
+
// Register service worker in production for offline asset caching
|
|
1552
|
+
if ('serviceWorker' in navigator && import.meta.env.PROD) {
|
|
1553
|
+
navigator.serviceWorker.register('/sw.js').catch(() => {
|
|
1554
|
+
// SW registration failure is non-fatal
|
|
1555
|
+
})
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1341
1558
|
createRoot(document.getElementById('root')!).render(
|
|
1342
1559
|
<StrictMode>
|
|
1343
1560
|
<App />
|
|
1344
1561
|
</StrictMode>,
|
|
1345
1562
|
)
|
|
1346
|
-
`,
|
|
1347
|
-
"./templates/_root/_gitignore":
|
|
1348
|
-
"./templates/_root/index.html":
|
|
1349
|
-
"./templates/_root/tsconfig.json":
|
|
1350
|
-
"./templates/content/characters/bartender.yaml":
|
|
1351
|
-
"./templates/content/characters/merchant.yaml":
|
|
1352
|
-
"./templates/content/dialogues/bartender_greeting.dlg":
|
|
1353
|
-
"./templates/content/dialogues/bluff_check.dlg":
|
|
1354
|
-
"./templates/content/dialogues/market_intro.dlg":
|
|
1355
|
-
"./templates/content/dialogues/merchant_intro.dlg":
|
|
1356
|
-
"./templates/content/dialogues/tavern_intro.dlg":
|
|
1357
|
-
"./templates/content/game.yaml":
|
|
1358
|
-
"./templates/content/interludes/chapter_one.yaml":
|
|
1359
|
-
"./templates/content/items/old_coin.yaml":
|
|
1360
|
-
"./templates/content/journal/market_square.yaml":
|
|
1361
|
-
"./templates/content/journal/odd_jobs_accepted.yaml":
|
|
1362
|
-
"./templates/content/journal/tavern_discovery.yaml":
|
|
1363
|
-
"./templates/content/locales/en.yaml":
|
|
1364
|
-
"./templates/content/locations/market.yaml":
|
|
1365
|
-
"./templates/content/locations/tavern.yaml":
|
|
1366
|
-
"./templates/content/maps/town.yaml":
|
|
1367
|
-
"./templates/content/quests/odd_jobs.yaml":
|
|
1368
|
-
"./templates/src/App.custom.tsx":
|
|
1369
|
-
"./templates/src/App.default.tsx":
|
|
1370
|
-
"./templates/src/index.css":
|
|
1371
|
-
"./templates/src/main.tsx":
|
|
1563
|
+
`, D = "ðū", xe = "ð", H = "ðĶī", Ge = "âĻ", $ = "ð", N = "â
", qe = "ð", x = /* @__PURE__ */ Object.assign({
|
|
1564
|
+
"./templates/_root/_gitignore": le,
|
|
1565
|
+
"./templates/_root/index.html": de,
|
|
1566
|
+
"./templates/_root/tsconfig.json": ue,
|
|
1567
|
+
"./templates/content/characters/bartender.yaml": he,
|
|
1568
|
+
"./templates/content/characters/merchant.yaml": me,
|
|
1569
|
+
"./templates/content/dialogues/bartender_greeting.dlg": ge,
|
|
1570
|
+
"./templates/content/dialogues/bluff_check.dlg": fe,
|
|
1571
|
+
"./templates/content/dialogues/market_intro.dlg": pe,
|
|
1572
|
+
"./templates/content/dialogues/merchant_intro.dlg": be,
|
|
1573
|
+
"./templates/content/dialogues/tavern_intro.dlg": ye,
|
|
1574
|
+
"./templates/content/game.yaml": _e,
|
|
1575
|
+
"./templates/content/interludes/chapter_one.yaml": we,
|
|
1576
|
+
"./templates/content/items/old_coin.yaml": Ee,
|
|
1577
|
+
"./templates/content/journal/market_square.yaml": ve,
|
|
1578
|
+
"./templates/content/journal/odd_jobs_accepted.yaml": ke,
|
|
1579
|
+
"./templates/content/journal/tavern_discovery.yaml": Oe,
|
|
1580
|
+
"./templates/content/locales/en.yaml": De,
|
|
1581
|
+
"./templates/content/locations/market.yaml": Te,
|
|
1582
|
+
"./templates/content/locations/tavern.yaml": Ne,
|
|
1583
|
+
"./templates/content/maps/town.yaml": Ce,
|
|
1584
|
+
"./templates/content/quests/odd_jobs.yaml": Ie,
|
|
1585
|
+
"./templates/src/App.custom.tsx": Re,
|
|
1586
|
+
"./templates/src/App.default.tsx": Ae,
|
|
1587
|
+
"./templates/src/index.css": Se,
|
|
1588
|
+
"./templates/src/main.tsx": je
|
|
1372
1589
|
});
|
|
1373
|
-
function
|
|
1374
|
-
const
|
|
1375
|
-
if (
|
|
1376
|
-
if (
|
|
1377
|
-
const
|
|
1378
|
-
return
|
|
1590
|
+
function He(t) {
|
|
1591
|
+
const o = t.replace("./templates/", "");
|
|
1592
|
+
if (o === "src/App.default.tsx" || o === "src/App.custom.tsx") return null;
|
|
1593
|
+
if (o.startsWith("_root/")) {
|
|
1594
|
+
const s = o.slice(6);
|
|
1595
|
+
return s.startsWith("_") ? "." + s.slice(1) : s;
|
|
1379
1596
|
}
|
|
1380
|
-
return
|
|
1597
|
+
return o;
|
|
1381
1598
|
}
|
|
1382
|
-
async function
|
|
1383
|
-
const
|
|
1384
|
-
console.log(""), console.log(
|
|
1385
|
-
const { useDefaultRenderer:
|
|
1599
|
+
async function $e(t) {
|
|
1600
|
+
const o = h(process.cwd(), t);
|
|
1601
|
+
console.log(""), console.log(c.bold.magenta(` ${D} Doodle Engine ${D}`)), console.log(c.dim(" Text-based RPG and Adventure Game Scaffolder")), console.log(""), console.log(` ${xe} Creating new game: ${c.bold.cyan(t)}`), console.log("");
|
|
1602
|
+
const { useDefaultRenderer: s } = await J({
|
|
1386
1603
|
type: "confirm",
|
|
1387
1604
|
name: "useDefaultRenderer",
|
|
1388
1605
|
message: "Use default renderer?",
|
|
1389
1606
|
initial: !0
|
|
1390
1607
|
});
|
|
1391
|
-
|
|
1392
|
-
${
|
|
1608
|
+
s === void 0 && (console.log(c.yellow(`
|
|
1609
|
+
${H} No worries, maybe next time! Woof!`)), process.exit(0)), console.log(""), await Me(o, t, s), console.log(""), console.log(c.bold.green(` ${N} Project created successfully!`)), console.log(""), console.log(c.dim(` ${$} ${o}`)), console.log(""), console.log(c.bold(" Next steps:")), console.log(c.cyan(` cd ${t}`)), console.log(c.cyan(" npm install ") + c.dim("# or: yarn install / pnpm install")), console.log(c.cyan(" npm run dev ") + c.dim("# or: yarn dev / pnpm dev")), console.log(""), console.log(c.dim(` ${qe} Happy game making! ${D}`)), console.log("");
|
|
1393
1610
|
}
|
|
1394
|
-
async function
|
|
1395
|
-
const
|
|
1611
|
+
async function Me(t, o, s) {
|
|
1612
|
+
const r = [
|
|
1396
1613
|
"content/locations",
|
|
1397
1614
|
"content/characters",
|
|
1398
1615
|
"content/items",
|
|
@@ -1411,12 +1628,12 @@ async function je(n, a, r) {
|
|
|
1411
1628
|
"assets/audio/voice",
|
|
1412
1629
|
"src"
|
|
1413
1630
|
];
|
|
1414
|
-
console.log(` ${
|
|
1415
|
-
for (const e of
|
|
1416
|
-
await
|
|
1417
|
-
console.log(
|
|
1418
|
-
const
|
|
1419
|
-
name:
|
|
1631
|
+
console.log(` ${$} ${c.bold("Creating directories...")}`);
|
|
1632
|
+
for (const e of r)
|
|
1633
|
+
await T(h(t, e), { recursive: !0 });
|
|
1634
|
+
console.log(c.green(` ${N} Directories created`)), console.log("");
|
|
1635
|
+
const n = {
|
|
1636
|
+
name: o,
|
|
1420
1637
|
version: "0.1.0",
|
|
1421
1638
|
type: "module",
|
|
1422
1639
|
scripts: {
|
|
@@ -1439,28 +1656,28 @@ async function je(n, a, r) {
|
|
|
1439
1656
|
vite: "^6.0.0"
|
|
1440
1657
|
}
|
|
1441
1658
|
};
|
|
1442
|
-
console.log(` ${
|
|
1443
|
-
for (const [e,
|
|
1444
|
-
const l =
|
|
1659
|
+
console.log(` ${Ge} ${c.bold("Writing project files...")}`), await w(h(t, "package.json"), JSON.stringify(n, null, 2));
|
|
1660
|
+
for (const [e, i] of Object.entries(x)) {
|
|
1661
|
+
const l = He(e);
|
|
1445
1662
|
if (l === null) continue;
|
|
1446
|
-
const d = h(
|
|
1447
|
-
await
|
|
1663
|
+
const d = h(t, l);
|
|
1664
|
+
await T(U(d), { recursive: !0 }), await w(d, i);
|
|
1448
1665
|
}
|
|
1449
|
-
const
|
|
1450
|
-
await w(h(
|
|
1666
|
+
const a = s ? "./templates/src/App.default.tsx" : "./templates/src/App.custom.tsx";
|
|
1667
|
+
await w(h(t, "src/App.tsx"), x[a]), console.log(c.green(` ${N} Source files created`)), console.log(""), console.log(` ${H} ${c.bold("Starter content written")}`), console.log(""), console.log(c.dim(" Content includes:")), console.log(c.dim(" 2 locations (tavern, market)")), console.log(c.dim(" 2 characters (bartender, merchant)")), console.log(c.dim(" 1 item (old coin)")), console.log(c.dim(" 1 map (town with 2 locations)")), console.log(c.dim(" 1 quest (odd jobs, 3 stages)")), console.log(c.dim(" 3 journal entries")), console.log(c.dim(" 1 interlude (chapter one, auto-triggers at tavern)")), console.log(c.dim(" 5 dialogues (2 narrator intros, 2 NPC conversations, 1 skill check)")), console.log(c.dim(" English locale with all strings"));
|
|
1451
1668
|
}
|
|
1452
|
-
const
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
await
|
|
1669
|
+
const v = new M();
|
|
1670
|
+
v.name("doodle").description(c.magenta("ðū Doodle Engine") + c.dim(" â Narrative RPG development tools")).version("0.0.1");
|
|
1671
|
+
v.command("create <project-name>").description("Scaffold a new Doodle Engine game project").action(async (t) => {
|
|
1672
|
+
await $e(t);
|
|
1456
1673
|
});
|
|
1457
|
-
|
|
1458
|
-
await
|
|
1674
|
+
v.command("dev").description("Start development server with hot reload").action(async () => {
|
|
1675
|
+
await te();
|
|
1459
1676
|
});
|
|
1460
|
-
|
|
1461
|
-
await
|
|
1677
|
+
v.command("build").description("Build game for production").action(async () => {
|
|
1678
|
+
await re();
|
|
1462
1679
|
});
|
|
1463
|
-
|
|
1464
|
-
await
|
|
1680
|
+
v.command("validate").description("Validate game content").action(async () => {
|
|
1681
|
+
await ie();
|
|
1465
1682
|
});
|
|
1466
|
-
|
|
1683
|
+
v.parse();
|