@doodle-engine/cli 0.0.6 → 0.0.8
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/cli.js +590 -115
- package/dist/commands/build.d.ts.map +1 -1
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/validate.d.ts +7 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/validate.d.ts +25 -0
- package/dist/validate.d.ts.map +1 -0
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -1,57 +1,358 @@
|
|
|
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
|
|
9
|
-
import { parse as
|
|
10
|
-
import { parseDialogue as
|
|
11
|
-
import
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
2
|
+
import { Command as I } from "commander";
|
|
3
|
+
import { crayon as i } from "crayon.js";
|
|
4
|
+
import { createServer as x, build as G } from "vite";
|
|
5
|
+
import $ from "@vitejs/plugin-react";
|
|
6
|
+
import { watch as R } from "chokidar";
|
|
7
|
+
import { readdir as y, readFile as f, mkdir as j, writeFile as h } from "fs/promises";
|
|
8
|
+
import { join as s, extname as p, relative as w } from "path";
|
|
9
|
+
import { parse as b } from "yaml";
|
|
10
|
+
import { parseDialogue as E } from "@doodle-engine/core";
|
|
11
|
+
import H from "prompts";
|
|
12
|
+
function N(e, o) {
|
|
13
|
+
const a = [];
|
|
14
|
+
for (const n of Object.values(e.dialogues)) {
|
|
15
|
+
const r = o.get(n.id) || `dialogue:${n.id}`;
|
|
16
|
+
a.push(...M(n, r));
|
|
17
|
+
}
|
|
18
|
+
for (const n of Object.values(e.characters))
|
|
19
|
+
if (n.dialogue && !e.dialogues[n.dialogue]) {
|
|
20
|
+
const r = o.get(n.id) || `character:${n.id}`;
|
|
21
|
+
a.push({
|
|
22
|
+
file: r,
|
|
23
|
+
message: `Character "${n.id}" references non-existent dialogue "${n.dialogue}"`,
|
|
24
|
+
suggestion: `Create dialogue "${n.dialogue}" or fix the reference`
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return a.push(...B(e, o)), a;
|
|
28
|
+
}
|
|
29
|
+
function M(e, o) {
|
|
30
|
+
const a = [], n = /* @__PURE__ */ new Set();
|
|
31
|
+
for (const r of e.nodes)
|
|
32
|
+
n.has(r.id) && a.push({
|
|
33
|
+
file: o,
|
|
34
|
+
message: `Duplicate node ID "${r.id}"`,
|
|
35
|
+
suggestion: "Node IDs must be unique within a dialogue"
|
|
36
|
+
}), n.add(r.id);
|
|
37
|
+
n.has(e.startNode) || a.push({
|
|
38
|
+
file: o,
|
|
39
|
+
message: `Start node "${e.startNode}" not found`,
|
|
40
|
+
suggestion: `Add a NODE ${e.startNode} or fix the startNode reference`
|
|
41
|
+
});
|
|
42
|
+
for (const r of e.nodes)
|
|
43
|
+
a.push(...F(r, n, o));
|
|
44
|
+
return a;
|
|
45
|
+
}
|
|
46
|
+
function F(e, o, a) {
|
|
47
|
+
const n = [];
|
|
48
|
+
if (e.next && !o.has(e.next) && n.push({
|
|
49
|
+
file: a,
|
|
50
|
+
message: `Node "${e.id}" GOTO "${e.next}" points to non-existent node`,
|
|
51
|
+
suggestion: `Add NODE ${e.next} or fix the GOTO target`
|
|
52
|
+
}), e.conditionalNext)
|
|
53
|
+
for (const r of e.conditionalNext)
|
|
54
|
+
o.has(r.next) || n.push({
|
|
55
|
+
file: a,
|
|
56
|
+
message: `Node "${e.id}" IF block GOTO "${r.next}" points to non-existent node`,
|
|
57
|
+
suggestion: `Add NODE ${r.next} or fix the GOTO target`
|
|
58
|
+
}), n.push(...v(r.condition, e.id, a));
|
|
59
|
+
for (const r of e.choices) {
|
|
60
|
+
if (o.has(r.next) || n.push({
|
|
61
|
+
file: a,
|
|
62
|
+
message: `Node "${e.id}" choice "${r.id}" GOTO "${r.next}" points to non-existent node`,
|
|
63
|
+
suggestion: `Add NODE ${r.next} or fix the GOTO target`
|
|
64
|
+
}), r.conditions)
|
|
65
|
+
for (const c of r.conditions)
|
|
66
|
+
n.push(...v(c, e.id, a));
|
|
67
|
+
if (r.effects)
|
|
68
|
+
for (const c of r.effects)
|
|
69
|
+
n.push(...T(c, e.id, a));
|
|
70
|
+
}
|
|
71
|
+
if (e.conditions)
|
|
72
|
+
for (const r of e.conditions)
|
|
73
|
+
n.push(...v(r, e.id, a));
|
|
74
|
+
if (e.effects)
|
|
75
|
+
for (const r of e.effects)
|
|
76
|
+
n.push(...T(r, e.id, a));
|
|
77
|
+
return n;
|
|
78
|
+
}
|
|
79
|
+
function v(e, o, a) {
|
|
80
|
+
const n = [];
|
|
81
|
+
if (!e.type)
|
|
82
|
+
return n.push({
|
|
83
|
+
file: a,
|
|
84
|
+
message: `Node "${o}" has condition with missing type`
|
|
85
|
+
}), n;
|
|
86
|
+
switch (e.type) {
|
|
87
|
+
case "hasFlag":
|
|
88
|
+
case "notFlag":
|
|
89
|
+
e.flag || n.push({
|
|
90
|
+
file: a,
|
|
91
|
+
message: `Node "${o}" condition "${e.type}" missing required "flag" argument`
|
|
92
|
+
});
|
|
93
|
+
break;
|
|
94
|
+
case "hasItem":
|
|
95
|
+
case "notItem":
|
|
96
|
+
e.item || n.push({
|
|
97
|
+
file: a,
|
|
98
|
+
message: `Node "${o}" condition "${e.type}" missing required "item" argument`
|
|
99
|
+
});
|
|
100
|
+
break;
|
|
101
|
+
case "questAtStage":
|
|
102
|
+
e.quest || n.push({
|
|
103
|
+
file: a,
|
|
104
|
+
message: `Node "${o}" condition "questAtStage" missing required "quest" argument`
|
|
105
|
+
}), e.stage || n.push({
|
|
106
|
+
file: a,
|
|
107
|
+
message: `Node "${o}" condition "questAtStage" missing required "stage" argument`
|
|
108
|
+
});
|
|
109
|
+
break;
|
|
110
|
+
case "variableEquals":
|
|
111
|
+
case "variableGreaterThan":
|
|
112
|
+
case "variableLessThan":
|
|
113
|
+
e.variable || n.push({
|
|
114
|
+
file: a,
|
|
115
|
+
message: `Node "${o}" condition "${e.type}" missing required "variable" argument`
|
|
116
|
+
}), e.value === void 0 && n.push({
|
|
117
|
+
file: a,
|
|
118
|
+
message: `Node "${o}" condition "${e.type}" missing required "value" argument`
|
|
119
|
+
});
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
return n;
|
|
123
|
+
}
|
|
124
|
+
function T(e, o, a) {
|
|
125
|
+
const n = [];
|
|
126
|
+
if (!e.type)
|
|
127
|
+
return n.push({
|
|
128
|
+
file: a,
|
|
129
|
+
message: `Node "${o}" has effect with missing type`
|
|
130
|
+
}), n;
|
|
131
|
+
switch (e.type) {
|
|
132
|
+
case "setFlag":
|
|
133
|
+
case "clearFlag":
|
|
134
|
+
e.flag || n.push({
|
|
135
|
+
file: a,
|
|
136
|
+
message: `Node "${o}" effect "${e.type}" missing required "flag" argument`
|
|
137
|
+
});
|
|
138
|
+
break;
|
|
139
|
+
case "setVariable":
|
|
140
|
+
case "addVariable":
|
|
141
|
+
e.variable || n.push({
|
|
142
|
+
file: a,
|
|
143
|
+
message: `Node "${o}" effect "${e.type}" missing required "variable" argument`
|
|
144
|
+
}), e.value === void 0 && n.push({
|
|
145
|
+
file: a,
|
|
146
|
+
message: `Node "${o}" effect "${e.type}" missing required "value" argument`
|
|
147
|
+
});
|
|
148
|
+
break;
|
|
149
|
+
case "addItem":
|
|
150
|
+
case "removeItem":
|
|
151
|
+
e.item || n.push({
|
|
152
|
+
file: a,
|
|
153
|
+
message: `Node "${o}" effect "${e.type}" missing required "item" argument`
|
|
154
|
+
});
|
|
155
|
+
break;
|
|
156
|
+
case "moveItem":
|
|
157
|
+
e.item || n.push({
|
|
158
|
+
file: a,
|
|
159
|
+
message: `Node "${o}" effect "moveItem" missing required "item" argument`
|
|
160
|
+
}), e.location || n.push({
|
|
161
|
+
file: a,
|
|
162
|
+
message: `Node "${o}" effect "moveItem" missing required "location" argument`
|
|
163
|
+
});
|
|
164
|
+
break;
|
|
165
|
+
case "setQuestStage":
|
|
166
|
+
e.quest || n.push({
|
|
167
|
+
file: a,
|
|
168
|
+
message: `Node "${o}" effect "setQuestStage" missing required "quest" argument`
|
|
169
|
+
}), e.stage || n.push({
|
|
170
|
+
file: a,
|
|
171
|
+
message: `Node "${o}" effect "setQuestStage" missing required "stage" argument`
|
|
172
|
+
});
|
|
173
|
+
break;
|
|
174
|
+
case "addJournalEntry":
|
|
175
|
+
e.entry || n.push({
|
|
176
|
+
file: a,
|
|
177
|
+
message: `Node "${o}" effect "addJournalEntry" missing required "entry" argument`
|
|
178
|
+
});
|
|
179
|
+
break;
|
|
180
|
+
case "setCharacterLocation":
|
|
181
|
+
e.character || n.push({
|
|
182
|
+
file: a,
|
|
183
|
+
message: `Node "${o}" effect "setCharacterLocation" missing required "character" argument`
|
|
184
|
+
}), e.location || n.push({
|
|
185
|
+
file: a,
|
|
186
|
+
message: `Node "${o}" effect "setCharacterLocation" missing required "location" argument`
|
|
187
|
+
});
|
|
188
|
+
break;
|
|
189
|
+
case "addToParty":
|
|
190
|
+
case "removeFromParty":
|
|
191
|
+
e.character || n.push({
|
|
192
|
+
file: a,
|
|
193
|
+
message: `Node "${o}" effect "${e.type}" missing required "character" argument`
|
|
194
|
+
});
|
|
195
|
+
break;
|
|
196
|
+
case "setRelationship":
|
|
197
|
+
case "addRelationship":
|
|
198
|
+
e.character || n.push({
|
|
199
|
+
file: a,
|
|
200
|
+
message: `Node "${o}" effect "${e.type}" missing required "character" argument`
|
|
201
|
+
}), e.value === void 0 && n.push({
|
|
202
|
+
file: a,
|
|
203
|
+
message: `Node "${o}" effect "${e.type}" missing required "value" argument`
|
|
204
|
+
});
|
|
205
|
+
break;
|
|
206
|
+
case "setCharacterStat":
|
|
207
|
+
case "addCharacterStat":
|
|
208
|
+
e.character || n.push({
|
|
209
|
+
file: a,
|
|
210
|
+
message: `Node "${o}" effect "${e.type}" missing required "character" argument`
|
|
211
|
+
}), e.stat || n.push({
|
|
212
|
+
file: a,
|
|
213
|
+
message: `Node "${o}" effect "${e.type}" missing required "stat" argument`
|
|
214
|
+
}), e.value === void 0 && n.push({
|
|
215
|
+
file: a,
|
|
216
|
+
message: `Node "${o}" effect "${e.type}" missing required "value" argument`
|
|
217
|
+
});
|
|
218
|
+
break;
|
|
219
|
+
case "setMapEnabled":
|
|
220
|
+
e.enabled === void 0 && n.push({
|
|
221
|
+
file: a,
|
|
222
|
+
message: `Node "${o}" effect "setMapEnabled" missing required "enabled" argument`
|
|
223
|
+
});
|
|
224
|
+
break;
|
|
225
|
+
case "advanceTime":
|
|
226
|
+
e.hours === void 0 && n.push({
|
|
227
|
+
file: a,
|
|
228
|
+
message: `Node "${o}" effect "advanceTime" missing required "hours" argument`
|
|
229
|
+
});
|
|
230
|
+
break;
|
|
231
|
+
case "goToLocation":
|
|
232
|
+
e.location || n.push({
|
|
233
|
+
file: a,
|
|
234
|
+
message: `Node "${o}" effect "goToLocation" missing required "location" argument`
|
|
235
|
+
});
|
|
236
|
+
break;
|
|
237
|
+
case "startDialogue":
|
|
238
|
+
e.dialogue || n.push({
|
|
239
|
+
file: a,
|
|
240
|
+
message: `Node "${o}" effect "startDialogue" missing required "dialogue" argument`
|
|
241
|
+
});
|
|
242
|
+
break;
|
|
243
|
+
case "playMusic":
|
|
244
|
+
case "playSound":
|
|
245
|
+
e.file || n.push({
|
|
246
|
+
file: a,
|
|
247
|
+
message: `Node "${o}" effect "${e.type}" missing required "file" argument`
|
|
248
|
+
});
|
|
249
|
+
break;
|
|
250
|
+
case "playVideo":
|
|
251
|
+
e.file || n.push({
|
|
252
|
+
file: a,
|
|
253
|
+
message: `Node "${o}" effect "playVideo" missing required "file" argument`
|
|
254
|
+
});
|
|
255
|
+
break;
|
|
256
|
+
case "notify":
|
|
257
|
+
e.message || n.push({
|
|
258
|
+
file: a,
|
|
259
|
+
message: `Node "${o}" effect "notify" missing required "message" argument`
|
|
260
|
+
});
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
return n;
|
|
264
|
+
}
|
|
265
|
+
function B(e, o) {
|
|
266
|
+
const a = [], n = /* @__PURE__ */ new Set();
|
|
267
|
+
for (const t of Object.values(e.locales))
|
|
268
|
+
for (const l of Object.keys(t))
|
|
269
|
+
n.add(l);
|
|
270
|
+
const r = (t) => t.startsWith("@"), c = (t, l, d) => {
|
|
271
|
+
const m = t.slice(1);
|
|
272
|
+
if (!n.has(m)) {
|
|
273
|
+
const u = o.get(l) || `${d}:${l}`;
|
|
274
|
+
a.push({
|
|
275
|
+
file: u,
|
|
276
|
+
message: `Localization key "${t}" not found in any locale file`,
|
|
277
|
+
suggestion: `Add "${m}: ..." to your locale files`
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
for (const t of Object.values(e.locations))
|
|
282
|
+
r(t.name) && c(t.name, t.id, "location"), r(t.description) && c(t.description, t.id, "location");
|
|
283
|
+
for (const t of Object.values(e.characters))
|
|
284
|
+
r(t.name) && c(t.name, t.id, "character"), r(t.biography) && c(t.biography, t.id, "character");
|
|
285
|
+
for (const t of Object.values(e.items))
|
|
286
|
+
r(t.name) && c(t.name, t.id, "item"), r(t.description) && c(t.description, t.id, "item");
|
|
287
|
+
for (const t of Object.values(e.quests)) {
|
|
288
|
+
r(t.name) && c(t.name, t.id, "quest"), r(t.description) && c(t.description, t.id, "quest");
|
|
289
|
+
for (const l of t.stages)
|
|
290
|
+
r(l.description) && c(l.description, t.id, "quest");
|
|
291
|
+
}
|
|
292
|
+
for (const t of Object.values(e.journalEntries))
|
|
293
|
+
r(t.title) && c(t.title, t.id, "journal"), r(t.text) && c(t.text, t.id, "journal");
|
|
294
|
+
for (const t of Object.values(e.dialogues))
|
|
295
|
+
for (const l of t.nodes) {
|
|
296
|
+
r(l.text) && c(l.text, t.id, "dialogue");
|
|
297
|
+
for (const d of l.choices)
|
|
298
|
+
r(d.text) && c(d.text, t.id, "dialogue");
|
|
299
|
+
}
|
|
300
|
+
return a;
|
|
301
|
+
}
|
|
302
|
+
function D(e) {
|
|
303
|
+
if (e.length === 0) {
|
|
304
|
+
console.log(i.green("✓ No validation errors"));
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
console.log(i.red(`
|
|
308
|
+
✗ Found ${e.length} validation error${e.length === 1 ? "" : "s"}:
|
|
309
|
+
`));
|
|
310
|
+
for (const o of e)
|
|
311
|
+
console.log(i.bold(o.file) + (o.line ? `:${o.line}` : "")), console.log(" " + i.red(o.message)), o.suggestion && console.log(" " + i.dim(o.suggestion)), console.log();
|
|
312
|
+
}
|
|
313
|
+
const C = "🐾", L = "✨", J = "✏️", U = "➕";
|
|
314
|
+
async function W() {
|
|
315
|
+
const e = process.cwd(), o = s(e, "content");
|
|
316
|
+
console.log(""), console.log(i.bold.magenta(` ${C} Doodle Engine Dev Server ${C}`)), console.log("");
|
|
317
|
+
const a = {
|
|
17
318
|
name: "doodle-content-loader",
|
|
18
319
|
configureServer(r) {
|
|
19
|
-
r.middlewares.use("/api/content", async (
|
|
320
|
+
r.middlewares.use("/api/content", async (t, l) => {
|
|
20
321
|
try {
|
|
21
|
-
const
|
|
22
|
-
l.setHeader("Content-Type", "application/json"), l.end(JSON.stringify(
|
|
23
|
-
} catch (
|
|
24
|
-
console.error(
|
|
322
|
+
const d = await Q(o);
|
|
323
|
+
l.setHeader("Content-Type", "application/json"), l.end(JSON.stringify(d));
|
|
324
|
+
} catch (d) {
|
|
325
|
+
console.error(i.red(" Error loading content:"), d), l.statusCode = 500, l.end(JSON.stringify({ error: "Failed to load content" }));
|
|
25
326
|
}
|
|
26
327
|
});
|
|
27
|
-
const c =
|
|
328
|
+
const c = R(s(o, "**/*"), {
|
|
28
329
|
ignored: /(^|[\/\\])\../,
|
|
29
330
|
persistent: !0
|
|
30
331
|
});
|
|
31
|
-
c.on("change", (
|
|
32
|
-
console.log(
|
|
332
|
+
c.on("change", async (t) => {
|
|
333
|
+
console.log(i.yellow(` ${J} Content changed: ${t}`)), await q(o), r.ws.send({
|
|
33
334
|
type: "full-reload",
|
|
34
335
|
path: "*"
|
|
35
336
|
});
|
|
36
|
-
}), c.on("add", (
|
|
37
|
-
console.log(
|
|
337
|
+
}), c.on("add", async (t) => {
|
|
338
|
+
console.log(i.green(` ${U} Content added: ${t}`)), await q(o), r.ws.send({
|
|
38
339
|
type: "full-reload",
|
|
39
340
|
path: "*"
|
|
40
341
|
});
|
|
41
342
|
});
|
|
42
343
|
}
|
|
43
|
-
},
|
|
344
|
+
}, n = await x({
|
|
44
345
|
root: e,
|
|
45
|
-
plugins: [
|
|
346
|
+
plugins: [$(), a],
|
|
46
347
|
server: {
|
|
47
348
|
port: 3e3,
|
|
48
349
|
open: !0
|
|
49
350
|
}
|
|
50
351
|
});
|
|
51
|
-
await
|
|
352
|
+
await n.listen(), n.printUrls(), console.log(""), console.log(i.dim(` ${L} Watching content files for changes...`)), console.log("");
|
|
52
353
|
}
|
|
53
|
-
async function
|
|
54
|
-
const
|
|
354
|
+
async function Q(e) {
|
|
355
|
+
const o = {
|
|
55
356
|
locations: {},
|
|
56
357
|
characters: {},
|
|
57
358
|
items: {},
|
|
@@ -61,8 +362,8 @@ async function x(e) {
|
|
|
61
362
|
journalEntries: {},
|
|
62
363
|
locales: {}
|
|
63
364
|
};
|
|
64
|
-
let
|
|
65
|
-
const
|
|
365
|
+
let a = null;
|
|
366
|
+
const n = [
|
|
66
367
|
{ dir: "locations", key: "locations" },
|
|
67
368
|
{ dir: "characters", key: "characters" },
|
|
68
369
|
{ dir: "items", key: "items" },
|
|
@@ -70,41 +371,41 @@ async function x(e) {
|
|
|
70
371
|
{ dir: "quests", key: "quests" },
|
|
71
372
|
{ dir: "journal", key: "journalEntries" }
|
|
72
373
|
];
|
|
73
|
-
for (const { dir: r, key: c } of
|
|
74
|
-
const
|
|
374
|
+
for (const { dir: r, key: c } of n) {
|
|
375
|
+
const t = s(e, r);
|
|
75
376
|
try {
|
|
76
|
-
const l = await
|
|
77
|
-
for (const
|
|
78
|
-
if (
|
|
79
|
-
const
|
|
80
|
-
|
|
377
|
+
const l = await y(t);
|
|
378
|
+
for (const d of l)
|
|
379
|
+
if (p(d) === ".yaml" || p(d) === ".yml") {
|
|
380
|
+
const m = s(t, d), u = await f(m, "utf-8"), g = b(u);
|
|
381
|
+
g && g.id && (o[c][g.id] = g);
|
|
81
382
|
}
|
|
82
383
|
} catch {
|
|
83
384
|
}
|
|
84
385
|
}
|
|
85
386
|
try {
|
|
86
|
-
const r =
|
|
87
|
-
for (const
|
|
88
|
-
if (
|
|
89
|
-
const l =
|
|
90
|
-
|
|
387
|
+
const r = s(e, "locales"), c = await y(r);
|
|
388
|
+
for (const t of c)
|
|
389
|
+
if (p(t) === ".yaml" || p(t) === ".yml") {
|
|
390
|
+
const l = s(r, t), d = await f(l, "utf-8"), m = b(d), u = t.replace(/\.(yaml|yml)$/, "");
|
|
391
|
+
o.locales[u] = m ?? {};
|
|
91
392
|
}
|
|
92
393
|
} catch {
|
|
93
394
|
}
|
|
94
395
|
try {
|
|
95
|
-
const r =
|
|
96
|
-
for (const
|
|
97
|
-
if (
|
|
98
|
-
const l =
|
|
99
|
-
|
|
396
|
+
const r = s(e, "dialogues"), c = await y(r);
|
|
397
|
+
for (const t of c)
|
|
398
|
+
if (p(t) === ".dlg") {
|
|
399
|
+
const l = s(r, t), d = await f(l, "utf-8"), m = t.replace(".dlg", ""), u = E(d, m);
|
|
400
|
+
o.dialogues[u.id] = u;
|
|
100
401
|
}
|
|
101
402
|
} catch {
|
|
102
403
|
}
|
|
103
404
|
try {
|
|
104
|
-
const r =
|
|
105
|
-
|
|
405
|
+
const r = s(e, "game.yaml"), c = await f(r, "utf-8");
|
|
406
|
+
a = b(c);
|
|
106
407
|
} catch {
|
|
107
|
-
console.warn(
|
|
408
|
+
console.warn(i.yellow(" No game.yaml found, using defaults")), a = {
|
|
108
409
|
startLocation: "tavern",
|
|
109
410
|
startTime: { day: 1, hour: 8 },
|
|
110
411
|
startFlags: {},
|
|
@@ -112,43 +413,214 @@ async function x(e) {
|
|
|
112
413
|
startInventory: []
|
|
113
414
|
};
|
|
114
415
|
}
|
|
115
|
-
return { registry:
|
|
416
|
+
return { registry: o, config: a };
|
|
116
417
|
}
|
|
117
|
-
async function
|
|
118
|
-
const e = process.cwd();
|
|
119
|
-
console.log(`🐕🎮 Building Doodle Engine game...
|
|
120
|
-
`);
|
|
418
|
+
async function q(e) {
|
|
121
419
|
try {
|
|
122
|
-
await N(
|
|
420
|
+
const { registry: o, fileMap: a } = await V(e), n = N(o, a);
|
|
421
|
+
n.length > 0 && (console.log(""), D(n), console.log(""));
|
|
422
|
+
} catch (o) {
|
|
423
|
+
console.error(i.red(" Error running validation:"), o);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
async function V(e) {
|
|
427
|
+
const o = {
|
|
428
|
+
locations: {},
|
|
429
|
+
characters: {},
|
|
430
|
+
items: {},
|
|
431
|
+
maps: {},
|
|
432
|
+
dialogues: {},
|
|
433
|
+
quests: {},
|
|
434
|
+
journalEntries: {},
|
|
435
|
+
locales: {}
|
|
436
|
+
}, a = /* @__PURE__ */ new Map(), n = [
|
|
437
|
+
{ dir: "locations", key: "locations" },
|
|
438
|
+
{ dir: "characters", key: "characters" },
|
|
439
|
+
{ dir: "items", key: "items" },
|
|
440
|
+
{ dir: "maps", key: "maps" },
|
|
441
|
+
{ dir: "quests", key: "quests" },
|
|
442
|
+
{ dir: "journal", key: "journalEntries" }
|
|
443
|
+
];
|
|
444
|
+
for (const { dir: r, key: c } of n) {
|
|
445
|
+
const t = s(e, r);
|
|
446
|
+
try {
|
|
447
|
+
const l = await y(t);
|
|
448
|
+
for (const d of l)
|
|
449
|
+
if (p(d) === ".yaml" || p(d) === ".yml") {
|
|
450
|
+
const m = s(t, d), u = await f(m, "utf-8"), g = b(u);
|
|
451
|
+
g && g.id && (o[c][g.id] = g, a.set(g.id, w(process.cwd(), m)));
|
|
452
|
+
}
|
|
453
|
+
} catch {
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
try {
|
|
457
|
+
const r = s(e, "locales"), c = await y(r);
|
|
458
|
+
for (const t of c)
|
|
459
|
+
if (p(t) === ".yaml" || p(t) === ".yml") {
|
|
460
|
+
const l = s(r, t), d = await f(l, "utf-8"), m = b(d), u = t.replace(/\.(yaml|yml)$/, "");
|
|
461
|
+
o.locales[u] = m ?? {};
|
|
462
|
+
}
|
|
463
|
+
} catch {
|
|
464
|
+
}
|
|
465
|
+
try {
|
|
466
|
+
const r = s(e, "dialogues"), c = await y(r);
|
|
467
|
+
for (const t of c)
|
|
468
|
+
if (p(t) === ".dlg") {
|
|
469
|
+
const l = s(r, t), d = await f(l, "utf-8"), m = t.replace(".dlg", ""), u = E(d, m);
|
|
470
|
+
o.dialogues[u.id] = u, a.set(u.id, w(process.cwd(), l));
|
|
471
|
+
}
|
|
472
|
+
} catch {
|
|
473
|
+
}
|
|
474
|
+
return { registry: o, fileMap: a };
|
|
475
|
+
}
|
|
476
|
+
async function Y() {
|
|
477
|
+
const e = process.cwd(), o = s(e, "content");
|
|
478
|
+
console.log(""), console.log(i.bold.magenta("🐕 Building Doodle Engine game...")), console.log(""), console.log(i.dim("Validating content..."));
|
|
479
|
+
try {
|
|
480
|
+
const { registry: a, fileMap: n } = await P(o), r = N(a, n);
|
|
481
|
+
D(r), r.length > 0 && (console.log(i.red("Build failed due to validation errors.")), console.log(""), process.exit(1));
|
|
482
|
+
} catch (a) {
|
|
483
|
+
console.error(i.red("Error loading content:"), a), process.exit(1);
|
|
484
|
+
}
|
|
485
|
+
console.log("");
|
|
486
|
+
try {
|
|
487
|
+
await G({
|
|
123
488
|
root: e,
|
|
124
|
-
plugins: [
|
|
489
|
+
plugins: [$()],
|
|
125
490
|
build: {
|
|
126
491
|
outDir: "dist",
|
|
127
492
|
emptyOutDir: !0
|
|
128
493
|
}
|
|
129
|
-
}), console.log(
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
`);
|
|
133
|
-
} catch (s) {
|
|
134
|
-
console.error("Build failed:", s), process.exit(1);
|
|
494
|
+
}), console.log(""), console.log(i.green("✅ Build complete! Output in dist/")), console.log(""), console.log("To preview the build:"), console.log(i.dim(" yarn preview")), console.log("");
|
|
495
|
+
} catch (a) {
|
|
496
|
+
console.error(i.red("Build failed:"), a), process.exit(1);
|
|
135
497
|
}
|
|
136
498
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
499
|
+
async function P(e) {
|
|
500
|
+
const o = {
|
|
501
|
+
locations: {},
|
|
502
|
+
characters: {},
|
|
503
|
+
items: {},
|
|
504
|
+
maps: {},
|
|
505
|
+
dialogues: {},
|
|
506
|
+
quests: {},
|
|
507
|
+
journalEntries: {},
|
|
508
|
+
locales: {}
|
|
509
|
+
}, a = /* @__PURE__ */ new Map(), n = [
|
|
510
|
+
{ dir: "locations", key: "locations" },
|
|
511
|
+
{ dir: "characters", key: "characters" },
|
|
512
|
+
{ dir: "items", key: "items" },
|
|
513
|
+
{ dir: "maps", key: "maps" },
|
|
514
|
+
{ dir: "quests", key: "quests" },
|
|
515
|
+
{ dir: "journal", key: "journalEntries" }
|
|
516
|
+
];
|
|
517
|
+
for (const { dir: r, key: c } of n) {
|
|
518
|
+
const t = s(e, r);
|
|
519
|
+
try {
|
|
520
|
+
const l = await y(t);
|
|
521
|
+
for (const d of l)
|
|
522
|
+
if (p(d) === ".yaml" || p(d) === ".yml") {
|
|
523
|
+
const m = s(t, d), u = await f(m, "utf-8"), g = b(u);
|
|
524
|
+
g && g.id && (o[c][g.id] = g, a.set(g.id, w(process.cwd(), m)));
|
|
525
|
+
}
|
|
526
|
+
} catch {
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
try {
|
|
530
|
+
const r = s(e, "locales"), c = await y(r);
|
|
531
|
+
for (const t of c)
|
|
532
|
+
if (p(t) === ".yaml" || p(t) === ".yml") {
|
|
533
|
+
const l = s(r, t), d = await f(l, "utf-8"), m = b(d), u = t.replace(/\.(yaml|yml)$/, "");
|
|
534
|
+
o.locales[u] = m ?? {};
|
|
535
|
+
}
|
|
536
|
+
} catch {
|
|
537
|
+
}
|
|
538
|
+
try {
|
|
539
|
+
const r = s(e, "dialogues"), c = await y(r);
|
|
540
|
+
for (const t of c)
|
|
541
|
+
if (p(t) === ".dlg") {
|
|
542
|
+
const l = s(r, t), d = await f(l, "utf-8"), m = t.replace(".dlg", ""), u = E(d, m);
|
|
543
|
+
o.dialogues[u.id] = u, a.set(u.id, w(process.cwd(), l));
|
|
544
|
+
}
|
|
545
|
+
} catch {
|
|
546
|
+
}
|
|
547
|
+
return { registry: o, fileMap: a };
|
|
548
|
+
}
|
|
549
|
+
async function K() {
|
|
550
|
+
const e = process.cwd(), o = s(e, "content");
|
|
551
|
+
console.log(""), console.log(i.bold.magenta("🐾 Validating Doodle Engine content...")), console.log("");
|
|
552
|
+
try {
|
|
553
|
+
const { registry: a, fileMap: n } = await z(o), r = N(a, n);
|
|
554
|
+
D(r), r.length > 0 && process.exit(1);
|
|
555
|
+
} catch (a) {
|
|
556
|
+
console.error(i.red("Error loading content:"), a), process.exit(1);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
async function z(e) {
|
|
560
|
+
const o = {
|
|
561
|
+
locations: {},
|
|
562
|
+
characters: {},
|
|
563
|
+
items: {},
|
|
564
|
+
maps: {},
|
|
565
|
+
dialogues: {},
|
|
566
|
+
quests: {},
|
|
567
|
+
journalEntries: {},
|
|
568
|
+
locales: {}
|
|
569
|
+
}, a = /* @__PURE__ */ new Map(), n = [
|
|
570
|
+
{ dir: "locations", key: "locations" },
|
|
571
|
+
{ dir: "characters", key: "characters" },
|
|
572
|
+
{ dir: "items", key: "items" },
|
|
573
|
+
{ dir: "maps", key: "maps" },
|
|
574
|
+
{ dir: "quests", key: "quests" },
|
|
575
|
+
{ dir: "journal", key: "journalEntries" }
|
|
576
|
+
];
|
|
577
|
+
for (const { dir: r, key: c } of n) {
|
|
578
|
+
const t = s(e, r);
|
|
579
|
+
try {
|
|
580
|
+
const l = await y(t);
|
|
581
|
+
for (const d of l)
|
|
582
|
+
if (p(d) === ".yaml" || p(d) === ".yml") {
|
|
583
|
+
const m = s(t, d), u = await f(m, "utf-8"), g = b(u);
|
|
584
|
+
g && g.id && (o[c][g.id] = g, a.set(g.id, w(process.cwd(), m)));
|
|
585
|
+
}
|
|
586
|
+
} catch {
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
try {
|
|
590
|
+
const r = s(e, "locales"), c = await y(r);
|
|
591
|
+
for (const t of c)
|
|
592
|
+
if (p(t) === ".yaml" || p(t) === ".yml") {
|
|
593
|
+
const l = s(r, t), d = await f(l, "utf-8"), m = b(d), u = t.replace(/\.(yaml|yml)$/, "");
|
|
594
|
+
o.locales[u] = m ?? {};
|
|
595
|
+
}
|
|
596
|
+
} catch {
|
|
597
|
+
}
|
|
598
|
+
try {
|
|
599
|
+
const r = s(e, "dialogues"), c = await y(r);
|
|
600
|
+
for (const t of c)
|
|
601
|
+
if (p(t) === ".dlg") {
|
|
602
|
+
const l = s(r, t), d = await f(l, "utf-8"), m = t.replace(".dlg", ""), u = E(d, m);
|
|
603
|
+
o.dialogues[u.id] = u, a.set(u.id, w(process.cwd(), l));
|
|
604
|
+
}
|
|
605
|
+
} catch {
|
|
606
|
+
}
|
|
607
|
+
return { registry: o, fileMap: a };
|
|
608
|
+
}
|
|
609
|
+
const O = "🐾", X = "🐕", S = "🦴", Z = "✨", A = "📁", _ = "✅", ee = "🚀";
|
|
610
|
+
async function te(e) {
|
|
611
|
+
const o = s(process.cwd(), e);
|
|
612
|
+
console.log(""), console.log(i.bold.magenta(` ${O} Doodle Engine ${O}`)), console.log(i.dim(" Text-based RPG and Adventure Game Scaffolder")), console.log(""), console.log(` ${X} Creating new game: ${i.bold.cyan(e)}`), console.log("");
|
|
613
|
+
const { useDefaultRenderer: a } = await H({
|
|
142
614
|
type: "confirm",
|
|
143
615
|
name: "useDefaultRenderer",
|
|
144
616
|
message: "Use default renderer?",
|
|
145
617
|
initial: !0
|
|
146
618
|
});
|
|
147
|
-
|
|
148
|
-
${
|
|
619
|
+
a === void 0 && (console.log(i.yellow(`
|
|
620
|
+
${S} No worries, maybe next time! Woof!`)), process.exit(0)), console.log(""), await oe(o, e, a), console.log(""), console.log(i.bold.green(` ${_} Project created successfully!`)), console.log(""), console.log(i.dim(` ${A} ${o}`)), console.log(""), console.log(i.bold(" Next steps:")), console.log(i.cyan(` cd ${e}`)), console.log(i.cyan(" npm install ") + i.dim("# or: yarn install / pnpm install")), console.log(i.cyan(" npm run dev ") + i.dim("# or: yarn dev / pnpm dev")), console.log(""), console.log(i.dim(` ${ee} Happy game making! ${O}`)), console.log("");
|
|
149
621
|
}
|
|
150
|
-
async function
|
|
151
|
-
const
|
|
622
|
+
async function oe(e, o, a) {
|
|
623
|
+
const n = [
|
|
152
624
|
"content/locations",
|
|
153
625
|
"content/characters",
|
|
154
626
|
"content/items",
|
|
@@ -166,12 +638,12 @@ async function J(e, s, d) {
|
|
|
166
638
|
"assets/audio/voice",
|
|
167
639
|
"src"
|
|
168
640
|
];
|
|
169
|
-
console.log(` ${
|
|
170
|
-
for (const
|
|
171
|
-
await
|
|
172
|
-
console.log(
|
|
641
|
+
console.log(` ${A} ${i.bold("Creating directories...")}`);
|
|
642
|
+
for (const u of n)
|
|
643
|
+
await j(s(e, u), { recursive: !0 });
|
|
644
|
+
console.log(i.green(` ${_} Directories created`)), console.log("");
|
|
173
645
|
const r = {
|
|
174
|
-
name:
|
|
646
|
+
name: o,
|
|
175
647
|
version: "0.1.0",
|
|
176
648
|
type: "module",
|
|
177
649
|
scripts: {
|
|
@@ -194,8 +666,8 @@ async function J(e, s, d) {
|
|
|
194
666
|
vite: "^6.0.0"
|
|
195
667
|
}
|
|
196
668
|
};
|
|
197
|
-
console.log(` ${
|
|
198
|
-
|
|
669
|
+
console.log(` ${Z} ${i.bold("Writing project files...")}`), await h(
|
|
670
|
+
s(e, "package.json"),
|
|
199
671
|
JSON.stringify(r, null, 2)
|
|
200
672
|
);
|
|
201
673
|
const c = {
|
|
@@ -215,10 +687,10 @@ async function J(e, s, d) {
|
|
|
215
687
|
},
|
|
216
688
|
include: ["src"]
|
|
217
689
|
};
|
|
218
|
-
await
|
|
219
|
-
|
|
690
|
+
await h(
|
|
691
|
+
s(e, "tsconfig.json"),
|
|
220
692
|
JSON.stringify(c, null, 2)
|
|
221
|
-
), await
|
|
693
|
+
), await h(s(e, "index.html"), `<!doctype html>
|
|
222
694
|
<html lang="en">
|
|
223
695
|
<head>
|
|
224
696
|
<meta charset="UTF-8" />
|
|
@@ -230,7 +702,7 @@ async function J(e, s, d) {
|
|
|
230
702
|
<script type="module" src="/src/main.tsx"><\/script>
|
|
231
703
|
</body>
|
|
232
704
|
</html>
|
|
233
|
-
`), await
|
|
705
|
+
`), await h(s(e, "src/main.tsx"), `import { StrictMode } from 'react'
|
|
234
706
|
import { createRoot } from 'react-dom/client'
|
|
235
707
|
import { App } from './App'
|
|
236
708
|
import './index.css'
|
|
@@ -241,8 +713,8 @@ createRoot(document.getElementById('root')!).render(
|
|
|
241
713
|
</StrictMode>,
|
|
242
714
|
)
|
|
243
715
|
`);
|
|
244
|
-
let
|
|
245
|
-
|
|
716
|
+
let d;
|
|
717
|
+
a ? d = `import { useEffect, useState } from 'react'
|
|
246
718
|
import type { ContentRegistry, GameConfig } from '@doodle-engine/core'
|
|
247
719
|
import { GameShell } from '@doodle-engine/react'
|
|
248
720
|
|
|
@@ -270,7 +742,7 @@ export function App() {
|
|
|
270
742
|
/>
|
|
271
743
|
)
|
|
272
744
|
}
|
|
273
|
-
` :
|
|
745
|
+
` : d = `import { useEffect, useState } from 'react'
|
|
274
746
|
import { Engine } from '@doodle-engine/core'
|
|
275
747
|
import type { GameState, Snapshot } from '@doodle-engine/core'
|
|
276
748
|
import { GameProvider, useGame } from '@doodle-engine/react'
|
|
@@ -361,7 +833,7 @@ function createEmptyState(): GameState {
|
|
|
361
833
|
currentLocale: 'en',
|
|
362
834
|
}
|
|
363
835
|
}
|
|
364
|
-
`, await
|
|
836
|
+
`, await h(s(e, "src/App.tsx"), d), await h(s(e, "src/index.css"), `body {
|
|
365
837
|
margin: 0;
|
|
366
838
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
|
367
839
|
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
|
@@ -369,7 +841,7 @@ function createEmptyState(): GameState {
|
|
|
369
841
|
-webkit-font-smoothing: antialiased;
|
|
370
842
|
-moz-osx-font-smoothing: grayscale;
|
|
371
843
|
}
|
|
372
|
-
`), console.log(
|
|
844
|
+
`), console.log(i.green(` ${_} Source files created`)), console.log(""), console.log(` ${S} ${i.bold("Writing starter content...")}`), await h(s(e, "content/game.yaml"), `# Game Configuration
|
|
373
845
|
startLocation: tavern
|
|
374
846
|
startTime:
|
|
375
847
|
day: 1
|
|
@@ -380,40 +852,40 @@ startVariables:
|
|
|
380
852
|
reputation: 0
|
|
381
853
|
_drinksBought: 0
|
|
382
854
|
startInventory: []
|
|
383
|
-
`), await
|
|
855
|
+
`), await h(s(e, "content/locations/tavern.yaml"), `id: tavern
|
|
384
856
|
name: "@location.tavern.name"
|
|
385
857
|
description: "@location.tavern.description"
|
|
386
858
|
banner: ""
|
|
387
859
|
music: ""
|
|
388
860
|
ambient: ""
|
|
389
|
-
`), await
|
|
861
|
+
`), await h(s(e, "content/locations/market.yaml"), `id: market
|
|
390
862
|
name: "@location.market.name"
|
|
391
863
|
description: "@location.market.description"
|
|
392
864
|
banner: ""
|
|
393
865
|
music: ""
|
|
394
866
|
ambient: ""
|
|
395
|
-
`), await
|
|
867
|
+
`), await h(s(e, "content/characters/bartender.yaml"), `id: bartender
|
|
396
868
|
name: "@character.bartender.name"
|
|
397
869
|
biography: "@character.bartender.bio"
|
|
398
870
|
portrait: ""
|
|
399
871
|
location: tavern
|
|
400
872
|
dialogue: bartender_greeting
|
|
401
873
|
stats: {}
|
|
402
|
-
`), await
|
|
874
|
+
`), await h(s(e, "content/characters/merchant.yaml"), `id: merchant
|
|
403
875
|
name: "@character.merchant.name"
|
|
404
876
|
biography: "@character.merchant.bio"
|
|
405
877
|
portrait: ""
|
|
406
878
|
location: market
|
|
407
879
|
dialogue: merchant_intro
|
|
408
880
|
stats: {}
|
|
409
|
-
`), await
|
|
881
|
+
`), await h(s(e, "content/items/old_coin.yaml"), `id: old_coin
|
|
410
882
|
name: "@item.old_coin.name"
|
|
411
883
|
description: "@item.old_coin.description"
|
|
412
884
|
icon: ""
|
|
413
885
|
image: ""
|
|
414
886
|
location: tavern
|
|
415
887
|
stats: {}
|
|
416
|
-
`), await
|
|
888
|
+
`), await h(s(e, "content/maps/town.yaml"), `id: town
|
|
417
889
|
name: "@map.town.name"
|
|
418
890
|
image: ""
|
|
419
891
|
scale: 1
|
|
@@ -424,7 +896,7 @@ locations:
|
|
|
424
896
|
- id: market
|
|
425
897
|
x: 300
|
|
426
898
|
y: 150
|
|
427
|
-
`), await
|
|
899
|
+
`), await h(s(e, "content/quests/odd_jobs.yaml"), `id: odd_jobs
|
|
428
900
|
name: "@quest.odd_jobs.name"
|
|
429
901
|
description: "@quest.odd_jobs.description"
|
|
430
902
|
stages:
|
|
@@ -434,19 +906,19 @@ stages:
|
|
|
434
906
|
description: "@quest.odd_jobs.stage.talked_to_merchant"
|
|
435
907
|
- id: complete
|
|
436
908
|
description: "@quest.odd_jobs.stage.complete"
|
|
437
|
-
`), await
|
|
909
|
+
`), await h(s(e, "content/journal/tavern_discovery.yaml"), `id: tavern_discovery
|
|
438
910
|
title: "@journal.tavern_discovery.title"
|
|
439
911
|
text: "@journal.tavern_discovery.text"
|
|
440
912
|
category: places
|
|
441
|
-
`), await
|
|
913
|
+
`), await h(s(e, "content/journal/odd_jobs_accepted.yaml"), `id: odd_jobs_accepted
|
|
442
914
|
title: "@journal.odd_jobs_accepted.title"
|
|
443
915
|
text: "@journal.odd_jobs_accepted.text"
|
|
444
916
|
category: quests
|
|
445
|
-
`), await
|
|
917
|
+
`), await h(s(e, "content/journal/market_square.yaml"), `id: market_square
|
|
446
918
|
title: "@journal.market_square.title"
|
|
447
919
|
text: "@journal.market_square.text"
|
|
448
920
|
category: places
|
|
449
|
-
`), await
|
|
921
|
+
`), await h(s(e, "content/dialogues/tavern_intro.dlg"), `TRIGGER tavern
|
|
450
922
|
REQUIRE notFlag seenTavernIntro
|
|
451
923
|
|
|
452
924
|
NODE start
|
|
@@ -456,7 +928,7 @@ NODE start
|
|
|
456
928
|
CHOICE @narrator.choice.look_around
|
|
457
929
|
END dialogue
|
|
458
930
|
END
|
|
459
|
-
`), await
|
|
931
|
+
`), await h(s(e, "content/dialogues/market_intro.dlg"), `TRIGGER market
|
|
460
932
|
REQUIRE notFlag seenMarketIntro
|
|
461
933
|
|
|
462
934
|
NODE start
|
|
@@ -467,7 +939,7 @@ NODE start
|
|
|
467
939
|
CHOICE @narrator.choice.look_around
|
|
468
940
|
END dialogue
|
|
469
941
|
END
|
|
470
|
-
`), await
|
|
942
|
+
`), await h(s(e, "content/dialogues/bartender_greeting.dlg"), `NODE start
|
|
471
943
|
BARTENDER: @bartender.greeting
|
|
472
944
|
|
|
473
945
|
# Always available — ask for rumors (demonstrates: flag, relationship, journalEntry)
|
|
@@ -605,7 +1077,7 @@ NODE work_done
|
|
|
605
1077
|
NODE farewell
|
|
606
1078
|
BARTENDER: @bartender.farewell
|
|
607
1079
|
END dialogue
|
|
608
|
-
`), await
|
|
1080
|
+
`), await h(s(e, "content/dialogues/merchant_intro.dlg"), `NODE start
|
|
609
1081
|
MERCHANT: @merchant.greeting
|
|
610
1082
|
|
|
611
1083
|
CHOICE @merchant.choice.browse_wares
|
|
@@ -705,7 +1177,7 @@ NODE about_market
|
|
|
705
1177
|
NODE farewell
|
|
706
1178
|
MERCHANT: @merchant.farewell
|
|
707
1179
|
END dialogue
|
|
708
|
-
`), await
|
|
1180
|
+
`), await h(s(e, "content/locales/en.yaml"), `# ===================
|
|
709
1181
|
# Narrator Intros
|
|
710
1182
|
# ===================
|
|
711
1183
|
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."
|
|
@@ -833,21 +1305,24 @@ notification.quest_updated: "Quest Updated: Odd Jobs"
|
|
|
833
1305
|
notification.quest_complete: "Quest Complete: Odd Jobs (+50 gold, +10 reputation)"
|
|
834
1306
|
notification.bought_drink: "Bought a drink (-5 gold)"
|
|
835
1307
|
notification.bought_map: "Bought a map (-20 gold)"
|
|
836
|
-
`), console.log(
|
|
1308
|
+
`), console.log(i.green(` ${_} Starter content created`)), console.log(""), console.log(i.dim(" Content includes:")), console.log(i.dim(" 2 locations (tavern, market)")), console.log(i.dim(" 2 characters (bartender, merchant)")), console.log(i.dim(" 1 item (old coin)")), console.log(i.dim(" 1 map (town)")), console.log(i.dim(" 1 quest with 3 stages")), console.log(i.dim(" 3 journal entries")), console.log(i.dim(" 4 dialogues (2 narrator intros, 2 NPC conversations)")), console.log(i.dim(" English locale with all strings")), await h(s(e, ".gitignore"), `node_modules
|
|
837
1309
|
dist
|
|
838
1310
|
.DS_Store
|
|
839
1311
|
*.log
|
|
840
1312
|
`);
|
|
841
1313
|
}
|
|
842
|
-
const
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
await
|
|
1314
|
+
const k = new I();
|
|
1315
|
+
k.name("doodle").description(i.magenta("🐾 Doodle Engine") + i.dim(" — Narrative RPG development tools")).version("0.0.1");
|
|
1316
|
+
k.command("create <project-name>").description("Scaffold a new Doodle Engine game project").action(async (e) => {
|
|
1317
|
+
await te(e);
|
|
1318
|
+
});
|
|
1319
|
+
k.command("dev").description("Start development server with hot reload").action(async () => {
|
|
1320
|
+
await W();
|
|
846
1321
|
});
|
|
847
|
-
|
|
848
|
-
await
|
|
1322
|
+
k.command("build").description("Build game for production").action(async () => {
|
|
1323
|
+
await Y();
|
|
849
1324
|
});
|
|
850
|
-
|
|
851
|
-
await
|
|
1325
|
+
k.command("validate").description("Validate game content").action(async () => {
|
|
1326
|
+
await K();
|
|
852
1327
|
});
|
|
853
|
-
|
|
1328
|
+
k.parse();
|