@doodle-engine/cli 0.0.21 → 0.0.22
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 +49 -40
- package/dist/cli.js +387 -366
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/manifest.d.ts +3 -3
- package/dist/manifest.d.ts.map +1 -1
- package/dist/service-worker.d.ts +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/validate.d.ts +1 -1
- package/dist/validate.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -9,73 +9,73 @@ import { join as h, extname as f, relative as E, dirname as U } from "path";
|
|
|
9
9
|
import { parse as y } from "yaml";
|
|
10
10
|
import { extractAssetPaths as W, getAssetType as R, parseDialogue as k } from "@doodle-engine/core";
|
|
11
11
|
import J from "prompts";
|
|
12
|
-
function C(
|
|
12
|
+
function C(t, o) {
|
|
13
13
|
const s = [];
|
|
14
|
-
for (const r of Object.values(
|
|
15
|
-
const
|
|
16
|
-
s.push(...Q(r,
|
|
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 r of Object.values(
|
|
19
|
-
if (r.dialogue && !
|
|
20
|
-
const
|
|
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
21
|
s.push({
|
|
22
|
-
file:
|
|
22
|
+
file: n,
|
|
23
23
|
message: `Character "${r.id}" references non-existent dialogue "${r.dialogue}"`,
|
|
24
24
|
suggestion: `Create dialogue "${r.dialogue}" or fix the reference`
|
|
25
25
|
});
|
|
26
26
|
}
|
|
27
|
-
return s.push(...K(
|
|
27
|
+
return s.push(...K(t, o)), s;
|
|
28
28
|
}
|
|
29
|
-
function Q(
|
|
29
|
+
function Q(t, o) {
|
|
30
30
|
const s = [], r = /* @__PURE__ */ new Set();
|
|
31
|
-
for (const
|
|
32
|
-
r.has(
|
|
31
|
+
for (const n of t.nodes)
|
|
32
|
+
r.has(n.id) && s.push({
|
|
33
33
|
file: o,
|
|
34
|
-
message: `Duplicate node ID "${
|
|
34
|
+
message: `Duplicate node ID "${n.id}"`,
|
|
35
35
|
suggestion: "Node IDs must be unique within a dialogue"
|
|
36
|
-
}), r.add(
|
|
37
|
-
r.has(
|
|
36
|
+
}), r.add(n.id);
|
|
37
|
+
r.has(t.startNode) || s.push({
|
|
38
38
|
file: o,
|
|
39
|
-
message: `Start node "${
|
|
40
|
-
suggestion: `Add a NODE ${
|
|
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
|
-
s.push(...Y(
|
|
42
|
+
for (const n of t.nodes)
|
|
43
|
+
s.push(...Y(n, r, o));
|
|
44
44
|
return s;
|
|
45
45
|
}
|
|
46
|
-
function Y(
|
|
46
|
+
function Y(t, o, s) {
|
|
47
47
|
const r = [];
|
|
48
|
-
if (
|
|
48
|
+
if (t.next && !o.has(t.next) && r.push({
|
|
49
49
|
file: s,
|
|
50
|
-
message: `Node "${
|
|
51
|
-
suggestion: `Add NODE ${
|
|
52
|
-
}),
|
|
53
|
-
for (const
|
|
54
|
-
o.has(
|
|
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
55
|
file: s,
|
|
56
|
-
message: `Node "${
|
|
57
|
-
suggestion: `Add NODE ${
|
|
58
|
-
}), r.push(...O(
|
|
59
|
-
for (const
|
|
60
|
-
if (!
|
|
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
|
-
) &&
|
|
62
|
+
) && n.next && !o.has(n.next) && r.push({
|
|
63
63
|
file: s,
|
|
64
|
-
message: `Node "${
|
|
65
|
-
suggestion: `Add NODE ${
|
|
66
|
-
}),
|
|
67
|
-
for (const e of
|
|
68
|
-
r.push(...O(e,
|
|
69
|
-
if (
|
|
70
|
-
for (const e of
|
|
71
|
-
r.push(...A(e,
|
|
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
|
-
r.push(...O(
|
|
76
|
-
if (
|
|
77
|
-
for (const
|
|
78
|
-
r.push(...A(
|
|
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
79
|
return r;
|
|
80
80
|
}
|
|
81
81
|
const z = {
|
|
@@ -123,51 +123,51 @@ const z = {
|
|
|
123
123
|
showInterlude: ["interludeId"],
|
|
124
124
|
roll: ["variable", "min", "max"]
|
|
125
125
|
};
|
|
126
|
-
function O(
|
|
126
|
+
function O(t, o, s) {
|
|
127
127
|
const r = [];
|
|
128
|
-
if (!
|
|
128
|
+
if (!t.type)
|
|
129
129
|
return r.push({
|
|
130
130
|
file: s,
|
|
131
131
|
message: `Node "${o}" has condition with missing type`
|
|
132
132
|
}), r;
|
|
133
|
-
if (
|
|
134
|
-
return
|
|
133
|
+
if (t.type === "timeIs")
|
|
134
|
+
return t.hour === void 0 && t.day === void 0 && r.push({
|
|
135
135
|
file: s,
|
|
136
136
|
message: `Node "${o}" condition "timeIs" must have at least one of "hour" or "day" argument`
|
|
137
137
|
}), r;
|
|
138
|
-
const
|
|
139
|
-
if (!
|
|
138
|
+
const n = z[t.type];
|
|
139
|
+
if (!n)
|
|
140
140
|
return r;
|
|
141
|
-
for (const a of
|
|
142
|
-
(
|
|
141
|
+
for (const a of n)
|
|
142
|
+
(t[a] === void 0 || t[a] === null || t[a] === "") && r.push({
|
|
143
143
|
file: s,
|
|
144
|
-
message: `Node "${o}" condition "${
|
|
144
|
+
message: `Node "${o}" condition "${t.type}" missing required "${a}" argument`
|
|
145
145
|
});
|
|
146
146
|
return r;
|
|
147
147
|
}
|
|
148
|
-
function A(
|
|
148
|
+
function A(t, o, s) {
|
|
149
149
|
const r = [];
|
|
150
|
-
if (!
|
|
150
|
+
if (!t.type)
|
|
151
151
|
return r.push({
|
|
152
152
|
file: s,
|
|
153
153
|
message: `Node "${o}" has effect with missing type`
|
|
154
154
|
}), r;
|
|
155
|
-
const
|
|
156
|
-
if (!
|
|
155
|
+
const n = V[t.type];
|
|
156
|
+
if (!n)
|
|
157
157
|
return r;
|
|
158
|
-
for (const a of
|
|
159
|
-
(
|
|
158
|
+
for (const a of n)
|
|
159
|
+
(t[a] === void 0 || t[a] === null || t[a] === "") && r.push({
|
|
160
160
|
file: s,
|
|
161
|
-
message: `Node "${o}" effect "${
|
|
161
|
+
message: `Node "${o}" effect "${t.type}" missing required "${a}" argument`
|
|
162
162
|
});
|
|
163
163
|
return r;
|
|
164
164
|
}
|
|
165
|
-
function K(
|
|
165
|
+
function K(t, o) {
|
|
166
166
|
const s = [], r = /* @__PURE__ */ new Set();
|
|
167
|
-
for (const e of Object.values(
|
|
167
|
+
for (const e of Object.values(t.locales))
|
|
168
168
|
for (const i of Object.keys(e))
|
|
169
169
|
r.add(i);
|
|
170
|
-
const
|
|
170
|
+
const n = (e) => e.startsWith("@"), a = (e, i, l) => {
|
|
171
171
|
const d = e.slice(1);
|
|
172
172
|
if (!r.has(d)) {
|
|
173
173
|
const u = o.get(i) || `${l}:${i}`;
|
|
@@ -178,45 +178,47 @@ function K(n, o) {
|
|
|
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
|
-
|
|
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
189
|
for (const i of e.stages)
|
|
190
|
-
|
|
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(
|
|
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
195
|
for (const i of e.nodes) {
|
|
196
|
-
|
|
196
|
+
n(i.text) && a(i.text, e.id, "dialogue");
|
|
197
197
|
for (const l of i.choices)
|
|
198
|
-
|
|
198
|
+
n(l.text) && a(l.text, e.id, "dialogue");
|
|
199
199
|
}
|
|
200
|
-
for (const e of Object.values(
|
|
201
|
-
|
|
200
|
+
for (const e of Object.values(t.interludes))
|
|
201
|
+
n(e.text) && a(e.text, e.id, "interlude");
|
|
202
202
|
return s;
|
|
203
203
|
}
|
|
204
|
-
function I(
|
|
205
|
-
if (
|
|
204
|
+
function I(t) {
|
|
205
|
+
if (t.length === 0) {
|
|
206
206
|
console.log(c.green("✓ No validation errors"));
|
|
207
207
|
return;
|
|
208
208
|
}
|
|
209
209
|
console.log(
|
|
210
210
|
c.red(
|
|
211
211
|
`
|
|
212
|
-
✗ Found ${
|
|
212
|
+
✗ Found ${t.length} validation error${t.length === 1 ? "" : "s"}:
|
|
213
213
|
`
|
|
214
214
|
)
|
|
215
215
|
);
|
|
216
|
-
for (const o of
|
|
217
|
-
console.log(
|
|
216
|
+
for (const o of t)
|
|
217
|
+
console.log(
|
|
218
|
+
c.bold(o.file) + (o.line ? `:${o.line}` : "")
|
|
219
|
+
), console.log(" " + c.red(o.message)), o.suggestion && console.log(" " + c.dim(o.suggestion)), console.log();
|
|
218
220
|
}
|
|
219
|
-
async function q(
|
|
221
|
+
async function q(t, o, s, r, n = Date.now().toString()) {
|
|
220
222
|
const { shell: a, game: e } = W(
|
|
221
223
|
s,
|
|
222
224
|
r
|
|
@@ -252,7 +254,7 @@ async function q(n, o, s, r, t = Date.now().toString()) {
|
|
|
252
254
|
)
|
|
253
255
|
), u = l.reduce((g, _) => g + (_.size ?? 0), 0), m = d.reduce((g, _) => g + (_.size ?? 0), 0);
|
|
254
256
|
return {
|
|
255
|
-
version:
|
|
257
|
+
version: n,
|
|
256
258
|
shell: l,
|
|
257
259
|
game: d,
|
|
258
260
|
shellSize: u,
|
|
@@ -260,31 +262,43 @@ async function q(n, o, s, r, t = Date.now().toString()) {
|
|
|
260
262
|
};
|
|
261
263
|
}
|
|
262
264
|
const S = "🐾", X = "✨", Z = "✏️", ee = "➕";
|
|
263
|
-
async function
|
|
264
|
-
const
|
|
265
|
-
console.log(""), console.log(
|
|
265
|
+
async function te() {
|
|
266
|
+
const t = process.cwd(), o = h(t, "content");
|
|
267
|
+
console.log(""), console.log(
|
|
268
|
+
c.bold.magenta(` ${S} Doodle Engine Dev Server ${S}`)
|
|
269
|
+
), console.log("");
|
|
266
270
|
const s = {
|
|
267
271
|
name: "doodle-content-loader",
|
|
268
|
-
configureServer(
|
|
269
|
-
|
|
272
|
+
configureServer(n) {
|
|
273
|
+
n.middlewares.use("/api/content", async (d, u) => {
|
|
270
274
|
try {
|
|
271
275
|
const m = await j(o);
|
|
272
276
|
u.setHeader("Content-Type", "application/json"), u.end(JSON.stringify(m));
|
|
273
277
|
} catch (m) {
|
|
274
|
-
console.error(
|
|
278
|
+
console.error(
|
|
279
|
+
c.red(" Error loading content:"),
|
|
280
|
+
m
|
|
281
|
+
), u.statusCode = 500, u.end(
|
|
282
|
+
JSON.stringify({ error: "Failed to load content" })
|
|
283
|
+
);
|
|
275
284
|
}
|
|
276
|
-
}),
|
|
285
|
+
}), n.middlewares.use("/api/manifest", async (d, u) => {
|
|
277
286
|
try {
|
|
278
287
|
const { registry: m, config: g } = await j(o), _ = await q(
|
|
279
|
-
h(
|
|
280
|
-
|
|
288
|
+
h(t, "assets"),
|
|
289
|
+
t,
|
|
281
290
|
m,
|
|
282
291
|
g,
|
|
283
292
|
"dev"
|
|
284
293
|
);
|
|
285
294
|
u.setHeader("Content-Type", "application/json"), u.end(JSON.stringify(_));
|
|
286
295
|
} catch (m) {
|
|
287
|
-
console.error(
|
|
296
|
+
console.error(
|
|
297
|
+
c.red(" Error generating manifest:"),
|
|
298
|
+
m
|
|
299
|
+
), u.statusCode = 500, u.end(
|
|
300
|
+
JSON.stringify({ error: "Failed to generate manifest" })
|
|
301
|
+
);
|
|
288
302
|
}
|
|
289
303
|
});
|
|
290
304
|
const a = L(o, {
|
|
@@ -298,26 +312,32 @@ async function ne() {
|
|
|
298
312
|
let i = null;
|
|
299
313
|
const l = (d) => {
|
|
300
314
|
i && clearTimeout(i), i = setTimeout(async () => {
|
|
301
|
-
i = null, console.log(d), await
|
|
315
|
+
i = null, console.log(d), await ne(o), n.ws.send({ type: "full-reload", path: "*" });
|
|
302
316
|
}, 50);
|
|
303
317
|
};
|
|
304
318
|
a.on("change", (d) => {
|
|
305
|
-
l(
|
|
319
|
+
l(
|
|
320
|
+
c.yellow(` ${Z} Content changed: ${d}`)
|
|
321
|
+
);
|
|
306
322
|
}), a.on("add", (d) => {
|
|
307
|
-
e && l(
|
|
323
|
+
e && l(
|
|
324
|
+
c.green(` ${ee} Content added: ${d}`)
|
|
325
|
+
);
|
|
308
326
|
});
|
|
309
327
|
}
|
|
310
328
|
}, r = await F({
|
|
311
|
-
root:
|
|
329
|
+
root: t,
|
|
312
330
|
plugins: [G(), s],
|
|
313
331
|
server: {
|
|
314
332
|
port: 3e3,
|
|
315
333
|
open: !0
|
|
316
334
|
}
|
|
317
335
|
});
|
|
318
|
-
await r.listen(), r.printUrls(), console.log(""), console.log(
|
|
336
|
+
await r.listen(), r.printUrls(), console.log(""), console.log(
|
|
337
|
+
c.dim(` ${X} Watching content files for changes...`)
|
|
338
|
+
), console.log("");
|
|
319
339
|
}
|
|
320
|
-
async function j(
|
|
340
|
+
async function j(t) {
|
|
321
341
|
const o = {
|
|
322
342
|
locations: {},
|
|
323
343
|
characters: {},
|
|
@@ -339,8 +359,8 @@ async function j(n) {
|
|
|
339
359
|
{ dir: "journal", key: "journalEntries" },
|
|
340
360
|
{ dir: "interludes", key: "interludes" }
|
|
341
361
|
];
|
|
342
|
-
for (const { dir:
|
|
343
|
-
const e = h(
|
|
362
|
+
for (const { dir: n, key: a } of r) {
|
|
363
|
+
const e = h(t, n);
|
|
344
364
|
try {
|
|
345
365
|
const i = await b(e);
|
|
346
366
|
for (const l of i)
|
|
@@ -352,25 +372,25 @@ async function j(n) {
|
|
|
352
372
|
}
|
|
353
373
|
}
|
|
354
374
|
try {
|
|
355
|
-
const
|
|
375
|
+
const n = h(t, "locales"), a = await b(n);
|
|
356
376
|
for (const e of a)
|
|
357
377
|
if (f(e) === ".yaml" || f(e) === ".yml") {
|
|
358
|
-
const i = h(
|
|
378
|
+
const i = h(n, e), l = await p(i, "utf-8"), d = y(l), u = e.replace(/\.(yaml|yml)$/, "");
|
|
359
379
|
o.locales[u] = d ?? {};
|
|
360
380
|
}
|
|
361
381
|
} catch {
|
|
362
382
|
}
|
|
363
383
|
try {
|
|
364
|
-
const
|
|
384
|
+
const n = h(t, "dialogues"), a = await b(n);
|
|
365
385
|
for (const e of a)
|
|
366
386
|
if (f(e) === ".dlg") {
|
|
367
|
-
const i = h(
|
|
387
|
+
const i = h(n, e), l = await p(i, "utf-8"), d = e.replace(".dlg", ""), u = k(l, d);
|
|
368
388
|
o.dialogues[u.id] = u;
|
|
369
389
|
}
|
|
370
390
|
} catch {
|
|
371
391
|
}
|
|
372
392
|
try {
|
|
373
|
-
const
|
|
393
|
+
const n = h(t, "game.yaml"), a = await p(n, "utf-8");
|
|
374
394
|
s = y(a);
|
|
375
395
|
} catch {
|
|
376
396
|
console.warn(c.yellow(" No game.yaml found, using defaults")), s = {
|
|
@@ -383,15 +403,15 @@ async function j(n) {
|
|
|
383
403
|
}
|
|
384
404
|
return { registry: o, config: s };
|
|
385
405
|
}
|
|
386
|
-
async function
|
|
406
|
+
async function ne(t) {
|
|
387
407
|
try {
|
|
388
|
-
const { registry: o, fileMap: s } = await oe(
|
|
408
|
+
const { registry: o, fileMap: s } = await oe(t), r = C(o, s);
|
|
389
409
|
r.length > 0 && (console.log(""), I(r), console.log(""));
|
|
390
410
|
} catch (o) {
|
|
391
411
|
console.error(c.red(" Error running validation:"), o);
|
|
392
412
|
}
|
|
393
413
|
}
|
|
394
|
-
async function oe(
|
|
414
|
+
async function oe(t) {
|
|
395
415
|
const o = {
|
|
396
416
|
locations: {},
|
|
397
417
|
characters: {},
|
|
@@ -411,8 +431,8 @@ async function oe(n) {
|
|
|
411
431
|
{ dir: "journal", key: "journalEntries" },
|
|
412
432
|
{ dir: "interludes", key: "interludes" }
|
|
413
433
|
];
|
|
414
|
-
for (const { dir:
|
|
415
|
-
const e = h(
|
|
434
|
+
for (const { dir: n, key: a } of r) {
|
|
435
|
+
const e = h(t, n);
|
|
416
436
|
try {
|
|
417
437
|
const i = await b(e);
|
|
418
438
|
for (const l of i)
|
|
@@ -424,34 +444,34 @@ async function oe(n) {
|
|
|
424
444
|
}
|
|
425
445
|
}
|
|
426
446
|
try {
|
|
427
|
-
const
|
|
447
|
+
const n = h(t, "locales"), a = await b(n);
|
|
428
448
|
for (const e of a)
|
|
429
449
|
if (f(e) === ".yaml" || f(e) === ".yml") {
|
|
430
|
-
const i = h(
|
|
450
|
+
const i = h(n, e), l = await p(i, "utf-8"), d = y(l), u = e.replace(/\.(yaml|yml)$/, "");
|
|
431
451
|
o.locales[u] = d ?? {};
|
|
432
452
|
}
|
|
433
453
|
} catch {
|
|
434
454
|
}
|
|
435
455
|
try {
|
|
436
|
-
const
|
|
456
|
+
const n = h(t, "dialogues"), a = await b(n);
|
|
437
457
|
for (const e of a)
|
|
438
458
|
if (f(e) === ".dlg") {
|
|
439
|
-
const i = h(
|
|
459
|
+
const i = h(n, e), l = await p(i, "utf-8"), d = e.replace(".dlg", ""), u = k(l, d);
|
|
440
460
|
o.dialogues[u.id] = u, s.set(u.id, E(process.cwd(), i));
|
|
441
461
|
}
|
|
442
462
|
} catch {
|
|
443
463
|
}
|
|
444
464
|
return { registry: o, fileMap: s };
|
|
445
465
|
}
|
|
446
|
-
function ae(
|
|
447
|
-
const o = `doodle-engine-assets-${
|
|
448
|
-
...
|
|
449
|
-
...
|
|
466
|
+
function ae(t) {
|
|
467
|
+
const o = `doodle-engine-assets-${t.version}`, s = [
|
|
468
|
+
...t.shell.map((n) => n.path),
|
|
469
|
+
...t.game.map((n) => n.path)
|
|
450
470
|
], r = JSON.stringify(s, null, 2);
|
|
451
471
|
return `/**
|
|
452
472
|
* Doodle Engine Service Worker
|
|
453
473
|
* Generated at build time — do not edit manually.
|
|
454
|
-
* Cache version: ${
|
|
474
|
+
* Cache version: ${t.version}
|
|
455
475
|
*/
|
|
456
476
|
|
|
457
477
|
const CACHE_NAME = ${JSON.stringify(o)};
|
|
@@ -519,34 +539,34 @@ self.addEventListener('fetch', (event) => {
|
|
|
519
539
|
`;
|
|
520
540
|
}
|
|
521
541
|
async function re() {
|
|
522
|
-
const
|
|
542
|
+
const t = process.cwd(), o = h(t, "content");
|
|
523
543
|
console.log(""), console.log(c.bold.magenta("🐕 Building Doodle Engine game...")), console.log(""), console.log(c.dim("Validating content..."));
|
|
524
|
-
let s, r,
|
|
544
|
+
let s, r, n;
|
|
525
545
|
try {
|
|
526
546
|
const a = await se(o);
|
|
527
|
-
r = a.registry,
|
|
547
|
+
r = a.registry, n = a.config;
|
|
528
548
|
const { fileMap: e } = a, i = C(r, e);
|
|
529
|
-
I(i), i.length > 0 && (console.log(c.red("Build failed due to validation errors.")), console.log(""), process.exit(1)), s = { registry: r, config:
|
|
549
|
+
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 };
|
|
530
550
|
} catch (a) {
|
|
531
551
|
console.error(c.red("Error loading content:"), a), process.exit(1);
|
|
532
552
|
}
|
|
533
553
|
console.log("");
|
|
534
554
|
try {
|
|
535
555
|
await P({
|
|
536
|
-
root:
|
|
556
|
+
root: t,
|
|
537
557
|
plugins: [G()],
|
|
538
558
|
build: {
|
|
539
559
|
outDir: "dist",
|
|
540
560
|
emptyOutDir: !0
|
|
541
561
|
}
|
|
542
562
|
});
|
|
543
|
-
const a = h(
|
|
563
|
+
const a = h(t, "dist"), e = t;
|
|
544
564
|
console.log(c.dim("Generating asset manifest..."));
|
|
545
565
|
const i = await q(
|
|
546
|
-
h(
|
|
566
|
+
h(t, "assets"),
|
|
547
567
|
e,
|
|
548
568
|
r,
|
|
549
|
-
|
|
569
|
+
n,
|
|
550
570
|
Date.now().toString()
|
|
551
571
|
), l = h(a, "api");
|
|
552
572
|
await T(l, { recursive: !0 }), await w(h(l, "content"), JSON.stringify(s)), await w(h(l, "manifest"), JSON.stringify(i)), await w(
|
|
@@ -559,7 +579,7 @@ async function re() {
|
|
|
559
579
|
console.error(c.red("Build failed:"), a), process.exit(1);
|
|
560
580
|
}
|
|
561
581
|
}
|
|
562
|
-
async function se(
|
|
582
|
+
async function se(t) {
|
|
563
583
|
const o = {
|
|
564
584
|
locations: {},
|
|
565
585
|
characters: {},
|
|
@@ -580,7 +600,7 @@ async function se(n) {
|
|
|
580
600
|
{ dir: "interludes", key: "interludes" }
|
|
581
601
|
];
|
|
582
602
|
for (const { dir: a, key: e } of r) {
|
|
583
|
-
const i = h(
|
|
603
|
+
const i = h(t, a);
|
|
584
604
|
try {
|
|
585
605
|
const l = await b(i);
|
|
586
606
|
for (const d of l)
|
|
@@ -592,7 +612,7 @@ async function se(n) {
|
|
|
592
612
|
}
|
|
593
613
|
}
|
|
594
614
|
try {
|
|
595
|
-
const a = h(
|
|
615
|
+
const a = h(t, "locales"), e = await b(a);
|
|
596
616
|
for (const i of e)
|
|
597
617
|
if (f(i) === ".yaml" || f(i) === ".yml") {
|
|
598
618
|
const l = h(a, i), d = await p(l, "utf-8"), u = y(d), m = i.replace(/\.(yaml|yml)$/, "");
|
|
@@ -601,7 +621,7 @@ async function se(n) {
|
|
|
601
621
|
} catch {
|
|
602
622
|
}
|
|
603
623
|
try {
|
|
604
|
-
const a = h(
|
|
624
|
+
const a = h(t, "dialogues"), e = await b(a);
|
|
605
625
|
for (const i of e)
|
|
606
626
|
if (f(i) === ".dlg") {
|
|
607
627
|
const l = h(a, i), d = await p(l, "utf-8"), u = i.replace(".dlg", ""), m = k(d, u);
|
|
@@ -609,12 +629,12 @@ async function se(n) {
|
|
|
609
629
|
}
|
|
610
630
|
} catch {
|
|
611
631
|
}
|
|
612
|
-
let
|
|
632
|
+
let n = null;
|
|
613
633
|
try {
|
|
614
|
-
const a = h(
|
|
615
|
-
|
|
634
|
+
const a = h(t, "game.yaml"), e = await p(a, "utf-8");
|
|
635
|
+
n = y(e);
|
|
616
636
|
} catch {
|
|
617
|
-
|
|
637
|
+
n = {
|
|
618
638
|
id: "game",
|
|
619
639
|
startLocation: "",
|
|
620
640
|
startTime: { day: 1, hour: 8 },
|
|
@@ -623,19 +643,19 @@ async function se(n) {
|
|
|
623
643
|
startInventory: []
|
|
624
644
|
};
|
|
625
645
|
}
|
|
626
|
-
return { registry: o, fileMap: s, config:
|
|
646
|
+
return { registry: o, fileMap: s, config: n };
|
|
627
647
|
}
|
|
628
648
|
async function ie() {
|
|
629
|
-
const
|
|
649
|
+
const t = process.cwd(), o = h(t, "content");
|
|
630
650
|
console.log(""), console.log(c.bold.magenta("🐾 Validating Doodle Engine content...")), console.log("");
|
|
631
651
|
try {
|
|
632
|
-
const { registry: s, fileMap: r } = await ce(o),
|
|
633
|
-
I(
|
|
652
|
+
const { registry: s, fileMap: r } = await ce(o), n = C(s, r);
|
|
653
|
+
I(n), n.length > 0 && process.exit(1);
|
|
634
654
|
} catch (s) {
|
|
635
655
|
console.error(c.red("Error loading content:"), s), process.exit(1);
|
|
636
656
|
}
|
|
637
657
|
}
|
|
638
|
-
async function ce(
|
|
658
|
+
async function ce(t) {
|
|
639
659
|
const o = {
|
|
640
660
|
locations: {},
|
|
641
661
|
characters: {},
|
|
@@ -653,8 +673,8 @@ async function ce(n) {
|
|
|
653
673
|
{ dir: "quests", key: "quests" },
|
|
654
674
|
{ dir: "journal", key: "journalEntries" }
|
|
655
675
|
];
|
|
656
|
-
for (const { dir:
|
|
657
|
-
const e = h(
|
|
676
|
+
for (const { dir: n, key: a } of r) {
|
|
677
|
+
const e = h(t, n);
|
|
658
678
|
try {
|
|
659
679
|
const i = await b(e);
|
|
660
680
|
for (const l of i)
|
|
@@ -666,19 +686,19 @@ async function ce(n) {
|
|
|
666
686
|
}
|
|
667
687
|
}
|
|
668
688
|
try {
|
|
669
|
-
const
|
|
689
|
+
const n = h(t, "locales"), a = await b(n);
|
|
670
690
|
for (const e of a)
|
|
671
691
|
if (f(e) === ".yaml" || f(e) === ".yml") {
|
|
672
|
-
const i = h(
|
|
692
|
+
const i = h(n, e), l = await p(i, "utf-8"), d = y(l), u = e.replace(/\.(yaml|yml)$/, "");
|
|
673
693
|
o.locales[u] = d ?? {};
|
|
674
694
|
}
|
|
675
695
|
} catch {
|
|
676
696
|
}
|
|
677
697
|
try {
|
|
678
|
-
const
|
|
698
|
+
const n = h(t, "dialogues"), a = await b(n);
|
|
679
699
|
for (const e of a)
|
|
680
700
|
if (f(e) === ".dlg") {
|
|
681
|
-
const i = h(
|
|
701
|
+
const i = h(n, e), l = await p(i, "utf-8"), d = e.replace(".dlg", ""), u = k(l, d);
|
|
682
702
|
o.dialogues[u.id] = u, s.set(u.id, E(process.cwd(), i));
|
|
683
703
|
}
|
|
684
704
|
} catch {
|
|
@@ -691,33 +711,33 @@ dist
|
|
|
691
711
|
*.log
|
|
692
712
|
`, de = `<!doctype html>
|
|
693
713
|
<html lang="en">
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
714
|
+
<head>
|
|
715
|
+
<meta charset="UTF-8" />
|
|
716
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
717
|
+
<title>Doodle Engine Game</title>
|
|
718
|
+
</head>
|
|
719
|
+
<body>
|
|
720
|
+
<div id="root"></div>
|
|
721
|
+
<script type="module" src="/src/main.tsx"><\/script>
|
|
722
|
+
</body>
|
|
703
723
|
</html>
|
|
704
724
|
`, ue = `{
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
725
|
+
"compilerOptions": {
|
|
726
|
+
"target": "ES2024",
|
|
727
|
+
"lib": ["ES2024", "DOM", "DOM.Iterable"],
|
|
728
|
+
"module": "ESNext",
|
|
729
|
+
"moduleResolution": "bundler",
|
|
730
|
+
"jsx": "react-jsx",
|
|
731
|
+
"strict": true,
|
|
732
|
+
"skipLibCheck": true,
|
|
733
|
+
"esModuleInterop": true,
|
|
734
|
+
"forceConsistentCasingInFileNames": true,
|
|
735
|
+
"resolveJsonModule": true,
|
|
736
|
+
"isolatedModules": true,
|
|
737
|
+
"noEmit": true,
|
|
738
|
+
"types": ["vite/client"]
|
|
739
|
+
},
|
|
740
|
+
"include": ["src"]
|
|
721
741
|
}
|
|
722
742
|
`, he = `id: bartender
|
|
723
743
|
name: "@character.bartender.name"
|
|
@@ -1388,189 +1408,190 @@ stages:
|
|
|
1388
1408
|
description: "@quest.odd_jobs.stage.talked_to_merchant"
|
|
1389
1409
|
- id: complete
|
|
1390
1410
|
description: "@quest.odd_jobs.stage.complete"
|
|
1391
|
-
`, Re = `import { useEffect, useState } from
|
|
1392
|
-
import { Engine } from
|
|
1393
|
-
import type { GameState, Snapshot } from
|
|
1394
|
-
import { GameProvider, useGame } from
|
|
1411
|
+
`, Re = `import { useEffect, useState } from 'react';
|
|
1412
|
+
import { Engine } from '@doodle-engine/core';
|
|
1413
|
+
import type { GameState, Snapshot } from '@doodle-engine/core';
|
|
1414
|
+
import { GameProvider, useGame } from '@doodle-engine/react';
|
|
1395
1415
|
|
|
1396
1416
|
export function App() {
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1417
|
+
const [game, setGame] = useState<{
|
|
1418
|
+
engine: Engine;
|
|
1419
|
+
snapshot: Snapshot;
|
|
1420
|
+
} | null>(null);
|
|
1421
|
+
|
|
1422
|
+
useEffect(() => {
|
|
1423
|
+
fetch('/api/content')
|
|
1424
|
+
.then((res) => res.json())
|
|
1425
|
+
.then((data) => {
|
|
1426
|
+
const engine = new Engine(data.registry, createEmptyState());
|
|
1427
|
+
const snapshot = engine.newGame(data.config);
|
|
1428
|
+
setGame({ engine, snapshot });
|
|
1429
|
+
});
|
|
1430
|
+
}, []);
|
|
1431
|
+
|
|
1432
|
+
if (!game) {
|
|
1433
|
+
return (
|
|
1434
|
+
<div className="app-bootstrap">
|
|
1435
|
+
<div className="spinner" />
|
|
1436
|
+
</div>
|
|
1437
|
+
);
|
|
1438
|
+
}
|
|
1411
1439
|
|
|
1412
|
-
if (!game) {
|
|
1413
1440
|
return (
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1441
|
+
<GameProvider
|
|
1442
|
+
engine={game.engine}
|
|
1443
|
+
initialSnapshot={game.snapshot}
|
|
1444
|
+
devTools={import.meta.env.DEV}
|
|
1445
|
+
>
|
|
1446
|
+
<GameUI />
|
|
1447
|
+
</GameProvider>
|
|
1417
1448
|
);
|
|
1418
|
-
}
|
|
1419
|
-
|
|
1420
|
-
return (
|
|
1421
|
-
<GameProvider
|
|
1422
|
-
engine={game.engine}
|
|
1423
|
-
initialSnapshot={game.snapshot}
|
|
1424
|
-
devTools={import.meta.env.DEV}
|
|
1425
|
-
>
|
|
1426
|
-
<GameUI />
|
|
1427
|
-
</GameProvider>
|
|
1428
|
-
);
|
|
1429
1449
|
}
|
|
1430
1450
|
|
|
1431
1451
|
function GameUI() {
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
<div
|
|
1436
|
-
style={{
|
|
1437
|
-
padding: "2rem",
|
|
1438
|
-
fontFamily: "sans-serif",
|
|
1439
|
-
maxWidth: "800px",
|
|
1440
|
-
margin: "0 auto",
|
|
1441
|
-
}}
|
|
1442
|
-
>
|
|
1443
|
-
<h1>{snapshot.location.name}</h1>
|
|
1444
|
-
<p>{snapshot.location.description}</p>
|
|
1445
|
-
|
|
1446
|
-
{snapshot.dialogue && (
|
|
1452
|
+
const { snapshot, actions } = useGame();
|
|
1453
|
+
|
|
1454
|
+
return (
|
|
1447
1455
|
<div
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1456
|
+
style={{
|
|
1457
|
+
padding: '2rem',
|
|
1458
|
+
fontFamily: 'sans-serif',
|
|
1459
|
+
maxWidth: '800px',
|
|
1460
|
+
margin: '0 auto',
|
|
1461
|
+
}}
|
|
1454
1462
|
>
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1463
|
+
<h1>{snapshot.location.name}</h1>
|
|
1464
|
+
<p>{snapshot.location.description}</p>
|
|
1465
|
+
|
|
1466
|
+
{snapshot.dialogue && (
|
|
1467
|
+
<div
|
|
1468
|
+
style={{
|
|
1469
|
+
background: '#f0f0f0',
|
|
1470
|
+
padding: '1rem',
|
|
1471
|
+
borderRadius: '8px',
|
|
1472
|
+
margin: '1rem 0',
|
|
1473
|
+
}}
|
|
1474
|
+
>
|
|
1475
|
+
<strong>{snapshot.dialogue.speakerName}:</strong>
|
|
1476
|
+
<p>{snapshot.dialogue.text}</p>
|
|
1477
|
+
{snapshot.choices.map((choice) => (
|
|
1478
|
+
<button
|
|
1479
|
+
key={choice.id}
|
|
1480
|
+
onClick={() => actions.selectChoice(choice.id)}
|
|
1481
|
+
style={{
|
|
1482
|
+
display: 'block',
|
|
1483
|
+
margin: '0.5rem 0',
|
|
1484
|
+
padding: '0.5rem 1rem',
|
|
1485
|
+
cursor: 'pointer',
|
|
1486
|
+
}}
|
|
1487
|
+
>
|
|
1488
|
+
{choice.text}
|
|
1489
|
+
</button>
|
|
1490
|
+
))}
|
|
1491
|
+
</div>
|
|
1492
|
+
)}
|
|
1493
|
+
|
|
1494
|
+
{!snapshot.dialogue && snapshot.charactersHere.length > 0 && (
|
|
1495
|
+
<div>
|
|
1496
|
+
<h2>Characters here</h2>
|
|
1497
|
+
{snapshot.charactersHere.map((char) => (
|
|
1498
|
+
<button
|
|
1499
|
+
key={char.id}
|
|
1500
|
+
onClick={() => actions.talkTo(char.id)}
|
|
1501
|
+
style={{
|
|
1502
|
+
display: 'block',
|
|
1503
|
+
margin: '0.5rem 0',
|
|
1504
|
+
padding: '0.5rem 1rem',
|
|
1505
|
+
cursor: 'pointer',
|
|
1506
|
+
}}
|
|
1507
|
+
>
|
|
1508
|
+
Talk to {char.name}
|
|
1509
|
+
</button>
|
|
1510
|
+
))}
|
|
1511
|
+
</div>
|
|
1512
|
+
)}
|
|
1491
1513
|
</div>
|
|
1492
|
-
|
|
1493
|
-
</div>
|
|
1494
|
-
);
|
|
1514
|
+
);
|
|
1495
1515
|
}
|
|
1496
1516
|
|
|
1497
1517
|
function createEmptyState(): GameState {
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1518
|
+
return {
|
|
1519
|
+
currentLocation: '',
|
|
1520
|
+
currentTime: { day: 1, hour: 0 },
|
|
1521
|
+
flags: {},
|
|
1522
|
+
variables: {},
|
|
1523
|
+
inventory: [],
|
|
1524
|
+
questProgress: {},
|
|
1525
|
+
unlockedJournalEntries: [],
|
|
1526
|
+
playerNotes: [],
|
|
1527
|
+
dialogueState: null,
|
|
1528
|
+
characterState: {},
|
|
1529
|
+
itemLocations: {},
|
|
1530
|
+
mapEnabled: true,
|
|
1531
|
+
notifications: [],
|
|
1532
|
+
pendingSounds: [],
|
|
1533
|
+
pendingVideo: null,
|
|
1534
|
+
pendingInterlude: null,
|
|
1535
|
+
currentLocale: 'en',
|
|
1536
|
+
};
|
|
1517
1537
|
}
|
|
1518
|
-
`, Ae = `import { useEffect, useState } from
|
|
1538
|
+
`, Ae = `import { useEffect, useState } from 'react';
|
|
1519
1539
|
import type {
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
} from
|
|
1524
|
-
import { GameShell } from
|
|
1540
|
+
ContentRegistry,
|
|
1541
|
+
GameConfig,
|
|
1542
|
+
AssetManifest,
|
|
1543
|
+
} from '@doodle-engine/core';
|
|
1544
|
+
import { GameShell } from '@doodle-engine/react';
|
|
1525
1545
|
|
|
1526
1546
|
export function App() {
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1547
|
+
const [content, setContent] = useState<{
|
|
1548
|
+
registry: ContentRegistry;
|
|
1549
|
+
config: GameConfig;
|
|
1550
|
+
} | null>(null);
|
|
1551
|
+
const [manifest, setManifest] = useState<AssetManifest | null>(null);
|
|
1552
|
+
|
|
1553
|
+
useEffect(() => {
|
|
1554
|
+
Promise.all([
|
|
1555
|
+
fetch('/api/content').then((res) => res.json()),
|
|
1556
|
+
fetch('/api/manifest').then((res) => res.json()),
|
|
1557
|
+
]).then(([contentData, manifestData]) => {
|
|
1558
|
+
setContent({
|
|
1559
|
+
registry: contentData.registry,
|
|
1560
|
+
config: contentData.config,
|
|
1561
|
+
});
|
|
1562
|
+
setManifest(manifestData);
|
|
1563
|
+
});
|
|
1564
|
+
}, []);
|
|
1565
|
+
|
|
1566
|
+
// Minimal bootstrap state while fetching manifest/content
|
|
1567
|
+
if (!content || !manifest) {
|
|
1568
|
+
return (
|
|
1569
|
+
<div className="app-bootstrap">
|
|
1570
|
+
<div className="spinner" />
|
|
1571
|
+
</div>
|
|
1572
|
+
);
|
|
1573
|
+
}
|
|
1545
1574
|
|
|
1546
|
-
// Minimal bootstrap state while fetching manifest/content
|
|
1547
|
-
if (!content || !manifest) {
|
|
1548
1575
|
return (
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1576
|
+
<GameShell
|
|
1577
|
+
registry={content.registry}
|
|
1578
|
+
config={content.config}
|
|
1579
|
+
manifest={manifest}
|
|
1580
|
+
title="My Doodle Game"
|
|
1581
|
+
subtitle="A text-based adventure"
|
|
1582
|
+
availableLocales={[{ code: 'en', label: 'English' }]}
|
|
1583
|
+
devTools={import.meta.env.DEV}
|
|
1584
|
+
/>
|
|
1552
1585
|
);
|
|
1553
|
-
}
|
|
1554
|
-
|
|
1555
|
-
return (
|
|
1556
|
-
<GameShell
|
|
1557
|
-
registry={content.registry}
|
|
1558
|
-
config={content.config}
|
|
1559
|
-
manifest={manifest}
|
|
1560
|
-
title="My Doodle Game"
|
|
1561
|
-
subtitle="A text-based adventure"
|
|
1562
|
-
availableLocales={[{ code: "en", label: "English" }]}
|
|
1563
|
-
devTools={import.meta.env.DEV}
|
|
1564
|
-
/>
|
|
1565
|
-
);
|
|
1566
1586
|
}
|
|
1567
1587
|
`, Se = `body {
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1588
|
+
margin: 0;
|
|
1589
|
+
font-family:
|
|
1590
|
+
-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
|
1591
|
+
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
|
1592
|
+
sans-serif;
|
|
1593
|
+
-webkit-font-smoothing: antialiased;
|
|
1594
|
+
-moz-osx-font-smoothing: grayscale;
|
|
1574
1595
|
}
|
|
1575
1596
|
|
|
1576
1597
|
/* ── Theme overrides ──────────────────────────────────────────────── */
|
|
@@ -1584,23 +1605,23 @@ export function App() {
|
|
|
1584
1605
|
--doodle-accent: #6366f1;
|
|
1585
1606
|
}
|
|
1586
1607
|
*/
|
|
1587
|
-
`, je = `import { StrictMode } from
|
|
1588
|
-
import { createRoot } from
|
|
1589
|
-
import
|
|
1590
|
-
import { App } from
|
|
1591
|
-
import
|
|
1608
|
+
`, je = `import { StrictMode } from 'react';
|
|
1609
|
+
import { createRoot } from 'react-dom/client';
|
|
1610
|
+
import '@doodle-engine/react/style.css';
|
|
1611
|
+
import { App } from './App';
|
|
1612
|
+
import './index.css';
|
|
1592
1613
|
|
|
1593
1614
|
// Register service worker in production for offline asset caching
|
|
1594
|
-
if (
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1615
|
+
if ('serviceWorker' in navigator && import.meta.env.PROD) {
|
|
1616
|
+
navigator.serviceWorker.register('/sw.js').catch(() => {
|
|
1617
|
+
// SW registration failure is non-fatal
|
|
1618
|
+
});
|
|
1598
1619
|
}
|
|
1599
1620
|
|
|
1600
|
-
createRoot(document.getElementById(
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1621
|
+
createRoot(document.getElementById('root')!).render(
|
|
1622
|
+
<StrictMode>
|
|
1623
|
+
<App />
|
|
1624
|
+
</StrictMode>
|
|
1604
1625
|
);
|
|
1605
1626
|
`, D = "🐾", xe = "🐕", H = "🦴", Ge = "✨", $ = "📁", N = "✅", qe = "🚀", x = /* @__PURE__ */ Object.assign({
|
|
1606
1627
|
"./templates/_root/_gitignore": le,
|
|
@@ -1629,8 +1650,8 @@ createRoot(document.getElementById("root")!).render(
|
|
|
1629
1650
|
"./templates/src/index.css": Se,
|
|
1630
1651
|
"./templates/src/main.tsx": je
|
|
1631
1652
|
});
|
|
1632
|
-
function He(
|
|
1633
|
-
const o =
|
|
1653
|
+
function He(t) {
|
|
1654
|
+
const o = t.replace("./templates/", "");
|
|
1634
1655
|
if (o === "src/App.default.tsx" || o === "src/App.custom.tsx")
|
|
1635
1656
|
return null;
|
|
1636
1657
|
if (o.startsWith("_root/")) {
|
|
@@ -1639,9 +1660,9 @@ function He(n) {
|
|
|
1639
1660
|
}
|
|
1640
1661
|
return o;
|
|
1641
1662
|
}
|
|
1642
|
-
async function $e(
|
|
1643
|
-
const o = h(process.cwd(),
|
|
1644
|
-
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(
|
|
1663
|
+
async function $e(t) {
|
|
1664
|
+
const o = h(process.cwd(), t);
|
|
1665
|
+
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("");
|
|
1645
1666
|
const { useDefaultRenderer: s } = await J({
|
|
1646
1667
|
type: "confirm",
|
|
1647
1668
|
name: "useDefaultRenderer",
|
|
@@ -1651,13 +1672,13 @@ async function $e(n) {
|
|
|
1651
1672
|
s === void 0 && (console.log(
|
|
1652
1673
|
c.yellow(`
|
|
1653
1674
|
${H} No worries, maybe next time! Woof!`)
|
|
1654
|
-
), process.exit(0)), console.log(""), await Me(o,
|
|
1675
|
+
), 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(
|
|
1655
1676
|
c.cyan(" npm install ") + c.dim("# or: yarn install / pnpm install")
|
|
1656
1677
|
), console.log(
|
|
1657
1678
|
c.cyan(" npm run dev ") + c.dim("# or: yarn dev / pnpm dev")
|
|
1658
1679
|
), console.log(""), console.log(c.dim(` ${qe} Happy game making! ${D}`)), console.log("");
|
|
1659
1680
|
}
|
|
1660
|
-
async function Me(
|
|
1681
|
+
async function Me(t, o, s) {
|
|
1661
1682
|
const r = [
|
|
1662
1683
|
"content/locations",
|
|
1663
1684
|
"content/characters",
|
|
@@ -1679,9 +1700,9 @@ async function Me(n, o, s) {
|
|
|
1679
1700
|
];
|
|
1680
1701
|
console.log(` ${$} ${c.bold("Creating directories...")}`);
|
|
1681
1702
|
for (const e of r)
|
|
1682
|
-
await T(h(
|
|
1703
|
+
await T(h(t, e), { recursive: !0 });
|
|
1683
1704
|
console.log(c.green(` ${N} Directories created`)), console.log("");
|
|
1684
|
-
const
|
|
1705
|
+
const n = {
|
|
1685
1706
|
name: o,
|
|
1686
1707
|
version: "0.1.0",
|
|
1687
1708
|
type: "module",
|
|
@@ -1706,17 +1727,17 @@ async function Me(n, o, s) {
|
|
|
1706
1727
|
}
|
|
1707
1728
|
};
|
|
1708
1729
|
console.log(` ${Ge} ${c.bold("Writing project files...")}`), await w(
|
|
1709
|
-
h(
|
|
1710
|
-
JSON.stringify(
|
|
1730
|
+
h(t, "package.json"),
|
|
1731
|
+
JSON.stringify(n, null, 2)
|
|
1711
1732
|
);
|
|
1712
1733
|
for (const [e, i] of Object.entries(x)) {
|
|
1713
1734
|
const l = He(e);
|
|
1714
1735
|
if (l === null) continue;
|
|
1715
|
-
const d = h(
|
|
1736
|
+
const d = h(t, l);
|
|
1716
1737
|
await T(U(d), { recursive: !0 }), await w(d, i);
|
|
1717
1738
|
}
|
|
1718
1739
|
const a = s ? "./templates/src/App.default.tsx" : "./templates/src/App.custom.tsx";
|
|
1719
|
-
await w(h(
|
|
1740
|
+
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(
|
|
1720
1741
|
c.dim(" 1 interlude (chapter one, auto-triggers at tavern)")
|
|
1721
1742
|
), console.log(
|
|
1722
1743
|
c.dim(
|
|
@@ -1728,11 +1749,11 @@ const v = new M();
|
|
|
1728
1749
|
v.name("doodle").description(
|
|
1729
1750
|
c.magenta("🐾 Doodle Engine") + c.dim(" — Narrative RPG development tools")
|
|
1730
1751
|
).version("0.0.1");
|
|
1731
|
-
v.command("create <project-name>").description("Scaffold a new Doodle Engine game project").action(async (
|
|
1732
|
-
await $e(
|
|
1752
|
+
v.command("create <project-name>").description("Scaffold a new Doodle Engine game project").action(async (t) => {
|
|
1753
|
+
await $e(t);
|
|
1733
1754
|
});
|
|
1734
1755
|
v.command("dev").description("Start development server with hot reload").action(async () => {
|
|
1735
|
-
await
|
|
1756
|
+
await te();
|
|
1736
1757
|
});
|
|
1737
1758
|
v.command("build").description("Build game for production").action(async () => {
|
|
1738
1759
|
await re();
|