@doodle-engine/cli 0.0.13 â 0.0.15
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 +274 -268
- package/dist/commands/dev.d.ts.map +1 -1
- 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
|
|
2
|
+
import { Command as q } from "commander";
|
|
3
3
|
import { crayon as i } from "crayon.js";
|
|
4
|
-
import { createServer as
|
|
5
|
-
import
|
|
6
|
-
import { watch as
|
|
4
|
+
import { createServer as G, build as $ } from "vite";
|
|
5
|
+
import S from "@vitejs/plugin-react";
|
|
6
|
+
import { watch as H } from "chokidar";
|
|
7
7
|
import { readdir as p, readFile as f, mkdir as D, writeFile as k } from "fs/promises";
|
|
8
|
-
import { join as
|
|
8
|
+
import { join as u, extname as g, relative as b, dirname as M } from "path";
|
|
9
9
|
import { parse as y } from "yaml";
|
|
10
10
|
import { parseDialogue as E } from "@doodle-engine/core";
|
|
11
|
-
import
|
|
12
|
-
function I(
|
|
11
|
+
import F from "prompts";
|
|
12
|
+
function I(t, a) {
|
|
13
13
|
const r = [];
|
|
14
|
-
for (const
|
|
15
|
-
const
|
|
16
|
-
r.push(...
|
|
14
|
+
for (const o of Object.values(t.dialogues)) {
|
|
15
|
+
const n = a.get(o.id) || `dialogue:${o.id}`;
|
|
16
|
+
r.push(...P(o, n));
|
|
17
17
|
}
|
|
18
|
-
for (const
|
|
19
|
-
if (
|
|
20
|
-
const
|
|
18
|
+
for (const o of Object.values(t.characters))
|
|
19
|
+
if (o.dialogue && !t.dialogues[o.dialogue]) {
|
|
20
|
+
const n = a.get(o.id) || `character:${o.id}`;
|
|
21
21
|
r.push({
|
|
22
|
-
file:
|
|
23
|
-
message: `Character "${
|
|
24
|
-
suggestion: `Create dialogue "${
|
|
22
|
+
file: n,
|
|
23
|
+
message: `Character "${o.id}" references non-existent dialogue "${o.dialogue}"`,
|
|
24
|
+
suggestion: `Create dialogue "${o.dialogue}" or fix the reference`
|
|
25
25
|
});
|
|
26
26
|
}
|
|
27
|
-
return r.push(...
|
|
27
|
+
return r.push(...W(t, a)), r;
|
|
28
28
|
}
|
|
29
|
-
function
|
|
30
|
-
const r = [],
|
|
31
|
-
for (const
|
|
32
|
-
|
|
33
|
-
file:
|
|
34
|
-
message: `Duplicate node ID "${
|
|
29
|
+
function P(t, a) {
|
|
30
|
+
const r = [], o = /* @__PURE__ */ new Set();
|
|
31
|
+
for (const n of t.nodes)
|
|
32
|
+
o.has(n.id) && r.push({
|
|
33
|
+
file: a,
|
|
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
|
+
}), o.add(n.id);
|
|
37
|
+
o.has(t.startNode) || r.push({
|
|
38
|
+
file: a,
|
|
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
|
-
r.push(...
|
|
42
|
+
for (const n of t.nodes)
|
|
43
|
+
r.push(...B(n, o, a));
|
|
44
44
|
return r;
|
|
45
45
|
}
|
|
46
|
-
function
|
|
47
|
-
const
|
|
48
|
-
if (
|
|
46
|
+
function B(t, a, r) {
|
|
47
|
+
const o = [];
|
|
48
|
+
if (t.next && !a.has(t.next) && o.push({
|
|
49
49
|
file: r,
|
|
50
|
-
message: `Node "${
|
|
51
|
-
suggestion: `Add NODE ${
|
|
52
|
-
}),
|
|
53
|
-
for (const
|
|
54
|
-
|
|
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
|
+
a.has(n.next) || o.push({
|
|
55
55
|
file: r,
|
|
56
|
-
message: `Node "${
|
|
57
|
-
suggestion: `Add NODE ${
|
|
58
|
-
}),
|
|
59
|
-
for (const
|
|
60
|
-
if (!
|
|
61
|
-
(e) => e.type === "endDialogue" || e.type === "goToLocation"
|
|
62
|
-
) && !
|
|
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
|
+
}), o.push(...v(n.condition, t.id, r));
|
|
59
|
+
for (const n of t.choices) {
|
|
60
|
+
if (!n.effects?.some(
|
|
61
|
+
(e) => e.type === "endDialogue" || e.type === "goToLocation" || e.type === "startDialogue"
|
|
62
|
+
) && n.next && !a.has(n.next) && o.push({
|
|
63
63
|
file: r,
|
|
64
|
-
message: `Node "${
|
|
65
|
-
suggestion: `Add NODE ${
|
|
66
|
-
}),
|
|
67
|
-
for (const e of
|
|
68
|
-
|
|
69
|
-
if (
|
|
70
|
-
for (const e of
|
|
71
|
-
|
|
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
|
+
o.push(...v(e, t.id, r));
|
|
69
|
+
if (n.effects)
|
|
70
|
+
for (const e of n.effects)
|
|
71
|
+
o.push(...C(e, t.id, r));
|
|
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
|
+
o.push(...v(n, t.id, r));
|
|
76
|
+
if (t.effects)
|
|
77
|
+
for (const n of t.effects)
|
|
78
|
+
o.push(...C(n, t.id, r));
|
|
79
|
+
return o;
|
|
80
80
|
}
|
|
81
|
-
const
|
|
81
|
+
const L = {
|
|
82
82
|
hasFlag: ["flag"],
|
|
83
83
|
notFlag: ["flag"],
|
|
84
84
|
hasItem: ["itemId"],
|
|
@@ -94,7 +94,7 @@ const U = {
|
|
|
94
94
|
relationshipBelow: ["characterId", "value"],
|
|
95
95
|
itemAt: ["itemId", "locationId"],
|
|
96
96
|
roll: ["min", "max", "threshold"]
|
|
97
|
-
},
|
|
97
|
+
}, U = {
|
|
98
98
|
setFlag: ["flag"],
|
|
99
99
|
clearFlag: ["flag"],
|
|
100
100
|
setVariable: ["variable", "value"],
|
|
@@ -123,138 +123,138 @@ const U = {
|
|
|
123
123
|
showInterlude: ["interludeId"],
|
|
124
124
|
roll: ["variable", "min", "max"]
|
|
125
125
|
};
|
|
126
|
-
function v(
|
|
127
|
-
const
|
|
128
|
-
if (!
|
|
129
|
-
return
|
|
126
|
+
function v(t, a, r) {
|
|
127
|
+
const o = [];
|
|
128
|
+
if (!t.type)
|
|
129
|
+
return o.push({
|
|
130
130
|
file: r,
|
|
131
|
-
message: `Node "${
|
|
132
|
-
}),
|
|
133
|
-
if (
|
|
134
|
-
return
|
|
131
|
+
message: `Node "${a}" has condition with missing type`
|
|
132
|
+
}), o;
|
|
133
|
+
if (t.type === "timeIs")
|
|
134
|
+
return t.hour === void 0 && t.day === void 0 && o.push({
|
|
135
135
|
file: r,
|
|
136
|
-
message: `Node "${
|
|
137
|
-
}),
|
|
138
|
-
const
|
|
139
|
-
if (!
|
|
140
|
-
return
|
|
141
|
-
for (const s of
|
|
142
|
-
(
|
|
136
|
+
message: `Node "${a}" condition "timeIs" must have at least one of "hour" or "day" argument`
|
|
137
|
+
}), o;
|
|
138
|
+
const n = L[t.type];
|
|
139
|
+
if (!n)
|
|
140
|
+
return o;
|
|
141
|
+
for (const s of n)
|
|
142
|
+
(t[s] === void 0 || t[s] === null || t[s] === "") && o.push({
|
|
143
143
|
file: r,
|
|
144
|
-
message: `Node "${
|
|
144
|
+
message: `Node "${a}" condition "${t.type}" missing required "${s}" argument`
|
|
145
145
|
});
|
|
146
|
-
return
|
|
146
|
+
return o;
|
|
147
147
|
}
|
|
148
|
-
function C(
|
|
149
|
-
const
|
|
150
|
-
if (!
|
|
151
|
-
return
|
|
148
|
+
function C(t, a, r) {
|
|
149
|
+
const o = [];
|
|
150
|
+
if (!t.type)
|
|
151
|
+
return o.push({
|
|
152
152
|
file: r,
|
|
153
|
-
message: `Node "${
|
|
154
|
-
}),
|
|
155
|
-
const
|
|
156
|
-
if (!
|
|
157
|
-
return
|
|
158
|
-
for (const s of
|
|
159
|
-
(
|
|
153
|
+
message: `Node "${a}" has effect with missing type`
|
|
154
|
+
}), o;
|
|
155
|
+
const n = U[t.type];
|
|
156
|
+
if (!n)
|
|
157
|
+
return o;
|
|
158
|
+
for (const s of n)
|
|
159
|
+
(t[s] === void 0 || t[s] === null || t[s] === "") && o.push({
|
|
160
160
|
file: r,
|
|
161
|
-
message: `Node "${
|
|
161
|
+
message: `Node "${a}" effect "${t.type}" missing required "${s}" argument`
|
|
162
162
|
});
|
|
163
|
-
return
|
|
163
|
+
return o;
|
|
164
164
|
}
|
|
165
|
-
function
|
|
166
|
-
const r = [],
|
|
167
|
-
for (const e of Object.values(
|
|
165
|
+
function W(t, a) {
|
|
166
|
+
const r = [], o = /* @__PURE__ */ new Set();
|
|
167
|
+
for (const e of Object.values(t.locales))
|
|
168
168
|
for (const c of Object.keys(e))
|
|
169
|
-
|
|
170
|
-
const
|
|
171
|
-
const
|
|
172
|
-
if (!
|
|
173
|
-
const h =
|
|
169
|
+
o.add(c);
|
|
170
|
+
const n = (e) => e.startsWith("@"), s = (e, c, l) => {
|
|
171
|
+
const d = e.slice(1);
|
|
172
|
+
if (!o.has(d)) {
|
|
173
|
+
const h = a.get(c) || `${l}:${c}`;
|
|
174
174
|
r.push({
|
|
175
175
|
file: h,
|
|
176
176
|
message: `Localization key "${e}" not found in any locale file`,
|
|
177
|
-
suggestion: `Add "${
|
|
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
|
-
|
|
181
|
+
for (const e of Object.values(t.locations))
|
|
182
|
+
n(e.name) && s(e.name, e.id, "location"), n(e.description) && s(e.description, e.id, "location");
|
|
183
|
+
for (const e of Object.values(t.characters))
|
|
184
|
+
n(e.name) && s(e.name, e.id, "character"), n(e.biography) && s(e.biography, e.id, "character");
|
|
185
|
+
for (const e of Object.values(t.items))
|
|
186
|
+
n(e.name) && s(e.name, e.id, "item"), n(e.description) && s(e.description, e.id, "item");
|
|
187
|
+
for (const e of Object.values(t.quests)) {
|
|
188
|
+
n(e.name) && s(e.name, e.id, "quest"), n(e.description) && s(e.description, e.id, "quest");
|
|
189
189
|
for (const c of e.stages)
|
|
190
|
-
|
|
190
|
+
n(c.description) && s(c.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) && s(e.title, e.id, "journal"), n(e.text) && s(e.text, e.id, "journal");
|
|
194
|
+
for (const e of Object.values(t.dialogues))
|
|
195
195
|
for (const c of e.nodes) {
|
|
196
|
-
|
|
196
|
+
n(c.text) && s(c.text, e.id, "dialogue");
|
|
197
197
|
for (const l of c.choices)
|
|
198
|
-
|
|
198
|
+
n(l.text) && s(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) && s(e.text, e.id, "interlude");
|
|
202
202
|
return r;
|
|
203
203
|
}
|
|
204
|
-
function N(
|
|
205
|
-
if (
|
|
204
|
+
function N(t) {
|
|
205
|
+
if (t.length === 0) {
|
|
206
206
|
console.log(i.green("â No validation errors"));
|
|
207
207
|
return;
|
|
208
208
|
}
|
|
209
209
|
console.log(i.red(`
|
|
210
|
-
â Found ${
|
|
210
|
+
â Found ${t.length} validation error${t.length === 1 ? "" : "s"}:
|
|
211
211
|
`));
|
|
212
|
-
for (const
|
|
213
|
-
console.log(i.bold(
|
|
212
|
+
for (const a of t)
|
|
213
|
+
console.log(i.bold(a.file) + (a.line ? `:${a.line}` : "")), console.log(" " + i.red(a.message)), a.suggestion && console.log(" " + i.dim(a.suggestion)), console.log();
|
|
214
214
|
}
|
|
215
|
-
const R = "ðū",
|
|
216
|
-
async function
|
|
217
|
-
const
|
|
215
|
+
const R = "ðū", J = "âĻ", Q = "âïļ", Y = "â";
|
|
216
|
+
async function V() {
|
|
217
|
+
const t = process.cwd(), a = u(t, "content");
|
|
218
218
|
console.log(""), console.log(i.bold.magenta(` ${R} Doodle Engine Dev Server ${R}`)), console.log("");
|
|
219
219
|
const r = {
|
|
220
220
|
name: "doodle-content-loader",
|
|
221
|
-
configureServer(
|
|
222
|
-
|
|
221
|
+
configureServer(n) {
|
|
222
|
+
n.middlewares.use("/api/content", async (l, d) => {
|
|
223
223
|
try {
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
} catch (
|
|
227
|
-
console.error(i.red(" Error loading content:"),
|
|
224
|
+
const h = await K(a);
|
|
225
|
+
d.setHeader("Content-Type", "application/json"), d.end(JSON.stringify(h));
|
|
226
|
+
} catch (h) {
|
|
227
|
+
console.error(i.red(" Error loading content:"), h), d.statusCode = 500, d.end(JSON.stringify({ error: "Failed to load content" }));
|
|
228
228
|
}
|
|
229
229
|
});
|
|
230
|
-
const s =
|
|
230
|
+
const s = H(a, {
|
|
231
231
|
ignored: /(^|[\/\\])\../,
|
|
232
232
|
persistent: !0
|
|
233
233
|
});
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
path: "*"
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
});
|
|
234
|
+
let e = null;
|
|
235
|
+
const c = (l) => {
|
|
236
|
+
e && clearTimeout(e), e = setTimeout(async () => {
|
|
237
|
+
e = null, console.log(l), await z(a), n.ws.send({ type: "full-reload", path: "*" });
|
|
238
|
+
}, 50);
|
|
239
|
+
};
|
|
240
|
+
s.on("change", (l) => {
|
|
241
|
+
c(i.yellow(` ${Q} Content changed: ${l}`));
|
|
242
|
+
}), s.on("add", (l) => {
|
|
243
|
+
c(i.green(` ${Y} Content added: ${l}`));
|
|
244
244
|
});
|
|
245
245
|
}
|
|
246
|
-
},
|
|
247
|
-
root:
|
|
248
|
-
plugins: [
|
|
246
|
+
}, o = await G({
|
|
247
|
+
root: t,
|
|
248
|
+
plugins: [S(), r],
|
|
249
249
|
server: {
|
|
250
250
|
port: 3e3,
|
|
251
251
|
open: !0
|
|
252
252
|
}
|
|
253
253
|
});
|
|
254
|
-
await
|
|
254
|
+
await o.listen(), o.printUrls(), console.log(""), console.log(i.dim(` ${J} Watching content files for changes...`)), console.log("");
|
|
255
255
|
}
|
|
256
|
-
async function
|
|
257
|
-
const
|
|
256
|
+
async function K(t) {
|
|
257
|
+
const a = {
|
|
258
258
|
locations: {},
|
|
259
259
|
characters: {},
|
|
260
260
|
items: {},
|
|
@@ -266,7 +266,7 @@ async function z(n) {
|
|
|
266
266
|
locales: {}
|
|
267
267
|
};
|
|
268
268
|
let r = null;
|
|
269
|
-
const
|
|
269
|
+
const o = [
|
|
270
270
|
{ dir: "locations", key: "locations" },
|
|
271
271
|
{ dir: "characters", key: "characters" },
|
|
272
272
|
{ dir: "items", key: "items" },
|
|
@@ -275,38 +275,38 @@ async function z(n) {
|
|
|
275
275
|
{ dir: "journal", key: "journalEntries" },
|
|
276
276
|
{ dir: "interludes", key: "interludes" }
|
|
277
277
|
];
|
|
278
|
-
for (const { dir:
|
|
279
|
-
const e =
|
|
278
|
+
for (const { dir: n, key: s } of o) {
|
|
279
|
+
const e = u(t, n);
|
|
280
280
|
try {
|
|
281
281
|
const c = await p(e);
|
|
282
282
|
for (const l of c)
|
|
283
283
|
if (g(l) === ".yaml" || g(l) === ".yml") {
|
|
284
|
-
const
|
|
285
|
-
m && m.id && (
|
|
284
|
+
const d = u(e, l), h = await f(d, "utf-8"), m = y(h);
|
|
285
|
+
m && m.id && (a[s][m.id] = m);
|
|
286
286
|
}
|
|
287
287
|
} catch {
|
|
288
288
|
}
|
|
289
289
|
}
|
|
290
290
|
try {
|
|
291
|
-
const
|
|
291
|
+
const n = u(t, "locales"), s = await p(n);
|
|
292
292
|
for (const e of s)
|
|
293
293
|
if (g(e) === ".yaml" || g(e) === ".yml") {
|
|
294
|
-
const c =
|
|
295
|
-
|
|
294
|
+
const c = u(n, e), l = await f(c, "utf-8"), d = y(l), h = e.replace(/\.(yaml|yml)$/, "");
|
|
295
|
+
a.locales[h] = d ?? {};
|
|
296
296
|
}
|
|
297
297
|
} catch {
|
|
298
298
|
}
|
|
299
299
|
try {
|
|
300
|
-
const
|
|
300
|
+
const n = u(t, "dialogues"), s = await p(n);
|
|
301
301
|
for (const e of s)
|
|
302
302
|
if (g(e) === ".dlg") {
|
|
303
|
-
const c =
|
|
304
|
-
|
|
303
|
+
const c = u(n, e), l = await f(c, "utf-8"), d = e.replace(".dlg", ""), h = E(l, d);
|
|
304
|
+
a.dialogues[h.id] = h;
|
|
305
305
|
}
|
|
306
306
|
} catch {
|
|
307
307
|
}
|
|
308
308
|
try {
|
|
309
|
-
const
|
|
309
|
+
const n = u(t, "game.yaml"), s = await f(n, "utf-8");
|
|
310
310
|
r = y(s);
|
|
311
311
|
} catch {
|
|
312
312
|
console.warn(i.yellow(" No game.yaml found, using defaults")), r = {
|
|
@@ -317,18 +317,18 @@ async function z(n) {
|
|
|
317
317
|
startInventory: []
|
|
318
318
|
};
|
|
319
319
|
}
|
|
320
|
-
return { registry:
|
|
320
|
+
return { registry: a, config: r };
|
|
321
321
|
}
|
|
322
|
-
async function
|
|
322
|
+
async function z(t) {
|
|
323
323
|
try {
|
|
324
|
-
const { registry:
|
|
325
|
-
|
|
326
|
-
} catch (
|
|
327
|
-
console.error(i.red(" Error running validation:"),
|
|
324
|
+
const { registry: a, fileMap: r } = await X(t), o = I(a, r);
|
|
325
|
+
o.length > 0 && (console.log(""), N(o), console.log(""));
|
|
326
|
+
} catch (a) {
|
|
327
|
+
console.error(i.red(" Error running validation:"), a);
|
|
328
328
|
}
|
|
329
329
|
}
|
|
330
|
-
async function X(
|
|
331
|
-
const
|
|
330
|
+
async function X(t) {
|
|
331
|
+
const a = {
|
|
332
332
|
locations: {},
|
|
333
333
|
characters: {},
|
|
334
334
|
items: {},
|
|
@@ -338,7 +338,7 @@ async function X(n) {
|
|
|
338
338
|
journalEntries: {},
|
|
339
339
|
interludes: {},
|
|
340
340
|
locales: {}
|
|
341
|
-
}, r = /* @__PURE__ */ new Map(),
|
|
341
|
+
}, r = /* @__PURE__ */ new Map(), o = [
|
|
342
342
|
{ dir: "locations", key: "locations" },
|
|
343
343
|
{ dir: "characters", key: "characters" },
|
|
344
344
|
{ dir: "items", key: "items" },
|
|
@@ -347,66 +347,66 @@ async function X(n) {
|
|
|
347
347
|
{ dir: "journal", key: "journalEntries" },
|
|
348
348
|
{ dir: "interludes", key: "interludes" }
|
|
349
349
|
];
|
|
350
|
-
for (const { dir:
|
|
351
|
-
const e =
|
|
350
|
+
for (const { dir: n, key: s } of o) {
|
|
351
|
+
const e = u(t, n);
|
|
352
352
|
try {
|
|
353
353
|
const c = await p(e);
|
|
354
354
|
for (const l of c)
|
|
355
355
|
if (g(l) === ".yaml" || g(l) === ".yml") {
|
|
356
|
-
const
|
|
357
|
-
m && m.id && (
|
|
356
|
+
const d = u(e, l), h = await f(d, "utf-8"), m = y(h);
|
|
357
|
+
m && m.id && (a[s][m.id] = m, r.set(m.id, b(process.cwd(), d)));
|
|
358
358
|
}
|
|
359
359
|
} catch {
|
|
360
360
|
}
|
|
361
361
|
}
|
|
362
362
|
try {
|
|
363
|
-
const
|
|
363
|
+
const n = u(t, "locales"), s = await p(n);
|
|
364
364
|
for (const e of s)
|
|
365
365
|
if (g(e) === ".yaml" || g(e) === ".yml") {
|
|
366
|
-
const c =
|
|
367
|
-
|
|
366
|
+
const c = u(n, e), l = await f(c, "utf-8"), d = y(l), h = e.replace(/\.(yaml|yml)$/, "");
|
|
367
|
+
a.locales[h] = d ?? {};
|
|
368
368
|
}
|
|
369
369
|
} catch {
|
|
370
370
|
}
|
|
371
371
|
try {
|
|
372
|
-
const
|
|
372
|
+
const n = u(t, "dialogues"), s = await p(n);
|
|
373
373
|
for (const e of s)
|
|
374
374
|
if (g(e) === ".dlg") {
|
|
375
|
-
const c =
|
|
376
|
-
|
|
375
|
+
const c = u(n, e), l = await f(c, "utf-8"), d = e.replace(".dlg", ""), h = E(l, d);
|
|
376
|
+
a.dialogues[h.id] = h, r.set(h.id, b(process.cwd(), c));
|
|
377
377
|
}
|
|
378
378
|
} catch {
|
|
379
379
|
}
|
|
380
|
-
return { registry:
|
|
380
|
+
return { registry: a, fileMap: r };
|
|
381
381
|
}
|
|
382
382
|
async function Z() {
|
|
383
|
-
const
|
|
383
|
+
const t = process.cwd(), a = u(t, "content");
|
|
384
384
|
console.log(""), console.log(i.bold.magenta("ð Building Doodle Engine game...")), console.log(""), console.log(i.dim("Validating content..."));
|
|
385
385
|
let r;
|
|
386
386
|
try {
|
|
387
|
-
const { registry:
|
|
388
|
-
N(e), e.length > 0 && (console.log(i.red("Build failed due to validation errors.")), console.log(""), process.exit(1)), r = { registry:
|
|
389
|
-
} catch (
|
|
390
|
-
console.error(i.red("Error loading content:"),
|
|
387
|
+
const { registry: o, fileMap: n, config: s } = await ee(a), e = I(o, n);
|
|
388
|
+
N(e), e.length > 0 && (console.log(i.red("Build failed due to validation errors.")), console.log(""), process.exit(1)), r = { registry: o, config: s };
|
|
389
|
+
} catch (o) {
|
|
390
|
+
console.error(i.red("Error loading content:"), o), process.exit(1);
|
|
391
391
|
}
|
|
392
392
|
console.log("");
|
|
393
393
|
try {
|
|
394
|
-
await
|
|
395
|
-
root:
|
|
396
|
-
plugins: [
|
|
394
|
+
await $({
|
|
395
|
+
root: t,
|
|
396
|
+
plugins: [S()],
|
|
397
397
|
build: {
|
|
398
398
|
outDir: "dist",
|
|
399
399
|
emptyOutDir: !0
|
|
400
400
|
}
|
|
401
401
|
});
|
|
402
|
-
const
|
|
403
|
-
await D(
|
|
404
|
-
} catch (
|
|
405
|
-
console.error(i.red("Build failed:"),
|
|
402
|
+
const o = u(t, "dist", "api");
|
|
403
|
+
await D(o, { recursive: !0 }), await k(u(o, "content"), JSON.stringify(r)), 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("");
|
|
404
|
+
} catch (o) {
|
|
405
|
+
console.error(i.red("Build failed:"), o), process.exit(1);
|
|
406
406
|
}
|
|
407
407
|
}
|
|
408
|
-
async function ee(
|
|
409
|
-
const
|
|
408
|
+
async function ee(t) {
|
|
409
|
+
const a = {
|
|
410
410
|
locations: {},
|
|
411
411
|
characters: {},
|
|
412
412
|
items: {},
|
|
@@ -416,7 +416,7 @@ async function ee(n) {
|
|
|
416
416
|
journalEntries: {},
|
|
417
417
|
interludes: {},
|
|
418
418
|
locales: {}
|
|
419
|
-
}, r = /* @__PURE__ */ new Map(),
|
|
419
|
+
}, r = /* @__PURE__ */ new Map(), o = [
|
|
420
420
|
{ dir: "locations", key: "locations" },
|
|
421
421
|
{ dir: "characters", key: "characters" },
|
|
422
422
|
{ dir: "items", key: "items" },
|
|
@@ -425,57 +425,57 @@ async function ee(n) {
|
|
|
425
425
|
{ dir: "journal", key: "journalEntries" },
|
|
426
426
|
{ dir: "interludes", key: "interludes" }
|
|
427
427
|
];
|
|
428
|
-
for (const { dir: s, key: e } of
|
|
429
|
-
const c =
|
|
428
|
+
for (const { dir: s, key: e } of o) {
|
|
429
|
+
const c = u(t, s);
|
|
430
430
|
try {
|
|
431
431
|
const l = await p(c);
|
|
432
|
-
for (const
|
|
433
|
-
if (g(
|
|
434
|
-
const h =
|
|
435
|
-
w && w.id && (
|
|
432
|
+
for (const d of l)
|
|
433
|
+
if (g(d) === ".yaml" || g(d) === ".yml") {
|
|
434
|
+
const h = u(c, d), m = await f(h, "utf-8"), w = y(m);
|
|
435
|
+
w && w.id && (a[e][w.id] = w, r.set(w.id, b(process.cwd(), h)));
|
|
436
436
|
}
|
|
437
437
|
} catch {
|
|
438
438
|
}
|
|
439
439
|
}
|
|
440
440
|
try {
|
|
441
|
-
const s =
|
|
441
|
+
const s = u(t, "locales"), e = await p(s);
|
|
442
442
|
for (const c of e)
|
|
443
443
|
if (g(c) === ".yaml" || g(c) === ".yml") {
|
|
444
|
-
const l =
|
|
445
|
-
|
|
444
|
+
const l = u(s, c), d = await f(l, "utf-8"), h = y(d), m = c.replace(/\.(yaml|yml)$/, "");
|
|
445
|
+
a.locales[m] = h ?? {};
|
|
446
446
|
}
|
|
447
447
|
} catch {
|
|
448
448
|
}
|
|
449
449
|
try {
|
|
450
|
-
const s =
|
|
450
|
+
const s = u(t, "dialogues"), e = await p(s);
|
|
451
451
|
for (const c of e)
|
|
452
452
|
if (g(c) === ".dlg") {
|
|
453
|
-
const l =
|
|
454
|
-
|
|
453
|
+
const l = u(s, c), d = await f(l, "utf-8"), h = c.replace(".dlg", ""), m = E(d, h);
|
|
454
|
+
a.dialogues[m.id] = m, r.set(m.id, b(process.cwd(), l));
|
|
455
455
|
}
|
|
456
456
|
} catch {
|
|
457
457
|
}
|
|
458
|
-
let
|
|
458
|
+
let n = null;
|
|
459
459
|
try {
|
|
460
|
-
const s =
|
|
461
|
-
|
|
460
|
+
const s = u(t, "game.yaml"), e = await f(s, "utf-8");
|
|
461
|
+
n = y(e);
|
|
462
462
|
} catch {
|
|
463
|
-
|
|
463
|
+
n = { id: "game", startLocation: "", startTime: { day: 1, hour: 8 }, startFlags: {}, startVariables: {}, startInventory: [] };
|
|
464
464
|
}
|
|
465
|
-
return { registry:
|
|
465
|
+
return { registry: a, fileMap: r, config: n };
|
|
466
466
|
}
|
|
467
|
-
async function
|
|
468
|
-
const
|
|
467
|
+
async function te() {
|
|
468
|
+
const t = process.cwd(), a = u(t, "content");
|
|
469
469
|
console.log(""), console.log(i.bold.magenta("ðū Validating Doodle Engine content...")), console.log("");
|
|
470
470
|
try {
|
|
471
|
-
const { registry: r, fileMap:
|
|
472
|
-
N(
|
|
471
|
+
const { registry: r, fileMap: o } = await ne(a), n = I(r, o);
|
|
472
|
+
N(n), n.length > 0 && process.exit(1);
|
|
473
473
|
} catch (r) {
|
|
474
474
|
console.error(i.red("Error loading content:"), r), process.exit(1);
|
|
475
475
|
}
|
|
476
476
|
}
|
|
477
|
-
async function
|
|
478
|
-
const
|
|
477
|
+
async function ne(t) {
|
|
478
|
+
const a = {
|
|
479
479
|
locations: {},
|
|
480
480
|
characters: {},
|
|
481
481
|
items: {},
|
|
@@ -484,7 +484,7 @@ async function te(n) {
|
|
|
484
484
|
quests: {},
|
|
485
485
|
journalEntries: {},
|
|
486
486
|
locales: {}
|
|
487
|
-
}, r = /* @__PURE__ */ new Map(),
|
|
487
|
+
}, r = /* @__PURE__ */ new Map(), o = [
|
|
488
488
|
{ dir: "locations", key: "locations" },
|
|
489
489
|
{ dir: "characters", key: "characters" },
|
|
490
490
|
{ dir: "items", key: "items" },
|
|
@@ -492,37 +492,37 @@ async function te(n) {
|
|
|
492
492
|
{ dir: "quests", key: "quests" },
|
|
493
493
|
{ dir: "journal", key: "journalEntries" }
|
|
494
494
|
];
|
|
495
|
-
for (const { dir:
|
|
496
|
-
const e =
|
|
495
|
+
for (const { dir: n, key: s } of o) {
|
|
496
|
+
const e = u(t, n);
|
|
497
497
|
try {
|
|
498
498
|
const c = await p(e);
|
|
499
499
|
for (const l of c)
|
|
500
500
|
if (g(l) === ".yaml" || g(l) === ".yml") {
|
|
501
|
-
const
|
|
502
|
-
m && m.id && (
|
|
501
|
+
const d = u(e, l), h = await f(d, "utf-8"), m = y(h);
|
|
502
|
+
m && m.id && (a[s][m.id] = m, r.set(m.id, b(process.cwd(), d)));
|
|
503
503
|
}
|
|
504
504
|
} catch {
|
|
505
505
|
}
|
|
506
506
|
}
|
|
507
507
|
try {
|
|
508
|
-
const
|
|
508
|
+
const n = u(t, "locales"), s = await p(n);
|
|
509
509
|
for (const e of s)
|
|
510
510
|
if (g(e) === ".yaml" || g(e) === ".yml") {
|
|
511
|
-
const c =
|
|
512
|
-
|
|
511
|
+
const c = u(n, e), l = await f(c, "utf-8"), d = y(l), h = e.replace(/\.(yaml|yml)$/, "");
|
|
512
|
+
a.locales[h] = d ?? {};
|
|
513
513
|
}
|
|
514
514
|
} catch {
|
|
515
515
|
}
|
|
516
516
|
try {
|
|
517
|
-
const
|
|
517
|
+
const n = u(t, "dialogues"), s = await p(n);
|
|
518
518
|
for (const e of s)
|
|
519
519
|
if (g(e) === ".dlg") {
|
|
520
|
-
const c =
|
|
521
|
-
|
|
520
|
+
const c = u(n, e), l = await f(c, "utf-8"), d = e.replace(".dlg", ""), h = E(l, d);
|
|
521
|
+
a.dialogues[h.id] = h, r.set(h.id, b(process.cwd(), c));
|
|
522
522
|
}
|
|
523
523
|
} catch {
|
|
524
524
|
}
|
|
525
|
-
return { registry:
|
|
525
|
+
return { registry: a, fileMap: r };
|
|
526
526
|
}
|
|
527
527
|
const oe = `node_modules
|
|
528
528
|
dist
|
|
@@ -648,6 +648,11 @@ NODE order_drink
|
|
|
648
648
|
GOTO after_drink
|
|
649
649
|
END
|
|
650
650
|
|
|
651
|
+
# Try to bluff a free drink â launches the skill check dialogue (demonstrates: START dialogue)
|
|
652
|
+
CHOICE @bartender.choice.try_bluff
|
|
653
|
+
START dialogue bluff_check
|
|
654
|
+
END
|
|
655
|
+
|
|
651
656
|
# Always available as an out
|
|
652
657
|
CHOICE @bartender.choice.too_rich
|
|
653
658
|
GOTO start
|
|
@@ -957,11 +962,11 @@ category: places
|
|
|
957
962
|
title: "@journal.odd_jobs_accepted.title"
|
|
958
963
|
text: "@journal.odd_jobs_accepted.text"
|
|
959
964
|
category: quests
|
|
960
|
-
`,
|
|
965
|
+
`, be = `id: tavern_discovery
|
|
961
966
|
title: "@journal.tavern_discovery.title"
|
|
962
967
|
text: "@journal.tavern_discovery.text"
|
|
963
968
|
category: places
|
|
964
|
-
`,
|
|
969
|
+
`, _e = `# ===================
|
|
965
970
|
# Narrator Intros
|
|
966
971
|
# ===================
|
|
967
972
|
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."
|
|
@@ -1030,6 +1035,7 @@ bartender.choice.nevermind: "Never mind, just passing through."
|
|
|
1030
1035
|
bartender.choice.tell_me_more: "Tell me more about that."
|
|
1031
1036
|
bartender.choice.interesting: "Interesting. I'll keep that in mind."
|
|
1032
1037
|
bartender.choice.sure_pay: "Sure, here's five gold."
|
|
1038
|
+
bartender.choice.try_bluff: "Try to talk your way to a free drink."
|
|
1033
1039
|
bartender.choice.too_rich: "On second thought, I'll pass."
|
|
1034
1040
|
bartender.choice.back_to_chat: "So, what else?"
|
|
1035
1041
|
bartender.choice.accept_work: "Sure, I could use the coin."
|
|
@@ -1272,7 +1278,7 @@ createRoot(document.getElementById('root')!).render(
|
|
|
1272
1278
|
<App />
|
|
1273
1279
|
</StrictMode>,
|
|
1274
1280
|
)
|
|
1275
|
-
`, O = "ðū", Ne = "ð",
|
|
1281
|
+
`, O = "ðū", Ne = "ð", j = "ðĶī", Ce = "âĻ", x = "ð", T = "â
", Re = "ð", A = /* @__PURE__ */ Object.assign({
|
|
1276
1282
|
"./templates/_root/_gitignore": oe,
|
|
1277
1283
|
"./templates/_root/index.html": ae,
|
|
1278
1284
|
"./templates/_root/tsconfig.json": re,
|
|
@@ -1288,8 +1294,8 @@ createRoot(document.getElementById('root')!).render(
|
|
|
1288
1294
|
"./templates/content/items/old_coin.yaml": fe,
|
|
1289
1295
|
"./templates/content/journal/market_square.yaml": pe,
|
|
1290
1296
|
"./templates/content/journal/odd_jobs_accepted.yaml": ye,
|
|
1291
|
-
"./templates/content/journal/tavern_discovery.yaml":
|
|
1292
|
-
"./templates/content/locales/en.yaml":
|
|
1297
|
+
"./templates/content/journal/tavern_discovery.yaml": be,
|
|
1298
|
+
"./templates/content/locales/en.yaml": _e,
|
|
1293
1299
|
"./templates/content/locations/market.yaml": we,
|
|
1294
1300
|
"./templates/content/locations/tavern.yaml": ke,
|
|
1295
1301
|
"./templates/content/maps/town.yaml": Ee,
|
|
@@ -1299,29 +1305,29 @@ createRoot(document.getElementById('root')!).render(
|
|
|
1299
1305
|
"./templates/src/index.css": Te,
|
|
1300
1306
|
"./templates/src/main.tsx": Ie
|
|
1301
1307
|
});
|
|
1302
|
-
function Ae(
|
|
1303
|
-
const
|
|
1304
|
-
if (
|
|
1305
|
-
if (
|
|
1306
|
-
const r =
|
|
1308
|
+
function Ae(t) {
|
|
1309
|
+
const a = t.replace("./templates/", "");
|
|
1310
|
+
if (a === "src/App.default.tsx" || a === "src/App.custom.tsx") return null;
|
|
1311
|
+
if (a.startsWith("_root/")) {
|
|
1312
|
+
const r = a.slice(6);
|
|
1307
1313
|
return r.startsWith("_") ? "." + r.slice(1) : r;
|
|
1308
1314
|
}
|
|
1309
|
-
return
|
|
1315
|
+
return a;
|
|
1310
1316
|
}
|
|
1311
|
-
async function Se(
|
|
1312
|
-
const
|
|
1313
|
-
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(` ${Ne} Creating new game: ${i.bold.cyan(
|
|
1314
|
-
const { useDefaultRenderer: r } = await
|
|
1317
|
+
async function Se(t) {
|
|
1318
|
+
const a = u(process.cwd(), t);
|
|
1319
|
+
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(` ${Ne} Creating new game: ${i.bold.cyan(t)}`), console.log("");
|
|
1320
|
+
const { useDefaultRenderer: r } = await F({
|
|
1315
1321
|
type: "confirm",
|
|
1316
1322
|
name: "useDefaultRenderer",
|
|
1317
1323
|
message: "Use default renderer?",
|
|
1318
1324
|
initial: !0
|
|
1319
1325
|
});
|
|
1320
1326
|
r === void 0 && (console.log(i.yellow(`
|
|
1321
|
-
${
|
|
1327
|
+
${j} No worries, maybe next time! Woof!`)), process.exit(0)), console.log(""), await je(a, t, r), console.log(""), console.log(i.bold.green(` ${T} Project created successfully!`)), console.log(""), console.log(i.dim(` ${x} ${a}`)), console.log(""), console.log(i.bold(" Next steps:")), console.log(i.cyan(` cd ${t}`)), 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(` ${Re} Happy game making! ${O}`)), console.log("");
|
|
1322
1328
|
}
|
|
1323
|
-
async function je(
|
|
1324
|
-
const
|
|
1329
|
+
async function je(t, a, r) {
|
|
1330
|
+
const o = [
|
|
1325
1331
|
"content/locations",
|
|
1326
1332
|
"content/characters",
|
|
1327
1333
|
"content/items",
|
|
@@ -1340,12 +1346,12 @@ async function je(n, o, r) {
|
|
|
1340
1346
|
"assets/audio/voice",
|
|
1341
1347
|
"src"
|
|
1342
1348
|
];
|
|
1343
|
-
console.log(` ${
|
|
1344
|
-
for (const e of
|
|
1345
|
-
await D(
|
|
1349
|
+
console.log(` ${x} ${i.bold("Creating directories...")}`);
|
|
1350
|
+
for (const e of o)
|
|
1351
|
+
await D(u(t, e), { recursive: !0 });
|
|
1346
1352
|
console.log(i.green(` ${T} Directories created`)), console.log("");
|
|
1347
|
-
const
|
|
1348
|
-
name:
|
|
1353
|
+
const n = {
|
|
1354
|
+
name: a,
|
|
1349
1355
|
version: "0.1.0",
|
|
1350
1356
|
type: "module",
|
|
1351
1357
|
scripts: {
|
|
@@ -1368,28 +1374,28 @@ async function je(n, o, r) {
|
|
|
1368
1374
|
vite: "^6.0.0"
|
|
1369
1375
|
}
|
|
1370
1376
|
};
|
|
1371
|
-
console.log(` ${Ce} ${i.bold("Writing project files...")}`), await k(
|
|
1372
|
-
for (const [e, c] of Object.entries(
|
|
1377
|
+
console.log(` ${Ce} ${i.bold("Writing project files...")}`), await k(u(t, "package.json"), JSON.stringify(n, null, 2));
|
|
1378
|
+
for (const [e, c] of Object.entries(A)) {
|
|
1373
1379
|
const l = Ae(e);
|
|
1374
1380
|
if (l === null) continue;
|
|
1375
|
-
const
|
|
1376
|
-
await D(
|
|
1381
|
+
const d = u(t, l);
|
|
1382
|
+
await D(M(d), { recursive: !0 }), await k(d, c);
|
|
1377
1383
|
}
|
|
1378
1384
|
const s = r ? "./templates/src/App.default.tsx" : "./templates/src/App.custom.tsx";
|
|
1379
|
-
await k(
|
|
1385
|
+
await k(u(t, "src/App.tsx"), A[s]), console.log(i.green(` ${T} Source files created`)), console.log(""), console.log(` ${j} ${i.bold("Starter content written")}`), 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 with 2 locations)")), console.log(i.dim(" 1 quest (odd jobs, 3 stages)")), console.log(i.dim(" 3 journal entries")), console.log(i.dim(" 1 interlude (chapter one, auto-triggers at tavern)")), console.log(i.dim(" 5 dialogues (2 narrator intros, 2 NPC conversations, 1 skill check)")), console.log(i.dim(" English locale with all strings"));
|
|
1380
1386
|
}
|
|
1381
|
-
const
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
await Se(
|
|
1387
|
+
const _ = new q();
|
|
1388
|
+
_.name("doodle").description(i.magenta("ðū Doodle Engine") + i.dim(" â Narrative RPG development tools")).version("0.0.1");
|
|
1389
|
+
_.command("create <project-name>").description("Scaffold a new Doodle Engine game project").action(async (t) => {
|
|
1390
|
+
await Se(t);
|
|
1385
1391
|
});
|
|
1386
|
-
|
|
1387
|
-
await
|
|
1392
|
+
_.command("dev").description("Start development server with hot reload").action(async () => {
|
|
1393
|
+
await V();
|
|
1388
1394
|
});
|
|
1389
|
-
|
|
1395
|
+
_.command("build").description("Build game for production").action(async () => {
|
|
1390
1396
|
await Z();
|
|
1391
1397
|
});
|
|
1392
|
-
|
|
1393
|
-
await
|
|
1398
|
+
_.command("validate").description("Validate game content").action(async () => {
|
|
1399
|
+
await te();
|
|
1394
1400
|
});
|
|
1395
|
-
|
|
1401
|
+
_.parse();
|