@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/dist/cli.js CHANGED
@@ -1,84 +1,84 @@
1
1
  #!/usr/bin/env node
2
- import { Command as G } from "commander";
2
+ import { Command as q } from "commander";
3
3
  import { crayon as i } from "crayon.js";
4
- import { createServer as $, build as H } from "vite";
5
- import j from "@vitejs/plugin-react";
6
- import { watch as M } from "chokidar";
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 d, extname as g, relative as _, dirname as F } from "path";
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 P from "prompts";
12
- function I(n, o) {
11
+ import F from "prompts";
12
+ function I(t, a) {
13
13
  const r = [];
14
- for (const a of Object.values(n.dialogues)) {
15
- const t = o.get(a.id) || `dialogue:${a.id}`;
16
- r.push(...B(a, t));
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 a of Object.values(n.characters))
19
- if (a.dialogue && !n.dialogues[a.dialogue]) {
20
- const t = o.get(a.id) || `character:${a.id}`;
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: t,
23
- message: `Character "${a.id}" references non-existent dialogue "${a.dialogue}"`,
24
- suggestion: `Create dialogue "${a.dialogue}" or fix the reference`
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(...J(n, o)), r;
27
+ return r.push(...W(t, a)), r;
28
28
  }
29
- function B(n, o) {
30
- const r = [], a = /* @__PURE__ */ new Set();
31
- for (const t of n.nodes)
32
- a.has(t.id) && r.push({
33
- file: o,
34
- message: `Duplicate node ID "${t.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
- }), a.add(t.id);
37
- a.has(n.startNode) || r.push({
38
- file: o,
39
- message: `Start node "${n.startNode}" not found`,
40
- suggestion: `Add a NODE ${n.startNode} or fix the startNode reference`
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 t of n.nodes)
43
- r.push(...L(t, a, o));
42
+ for (const n of t.nodes)
43
+ r.push(...B(n, o, a));
44
44
  return r;
45
45
  }
46
- function L(n, o, r) {
47
- const a = [];
48
- if (n.next && !o.has(n.next) && a.push({
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 "${n.id}" GOTO "${n.next}" points to non-existent node`,
51
- suggestion: `Add NODE ${n.next} or fix the GOTO target`
52
- }), n.conditionalNext)
53
- for (const t of n.conditionalNext)
54
- o.has(t.next) || a.push({
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 "${n.id}" IF block GOTO "${t.next}" points to non-existent node`,
57
- suggestion: `Add NODE ${t.next} or fix the GOTO target`
58
- }), a.push(...v(t.condition, n.id, r));
59
- for (const t of n.choices) {
60
- if (!t.effects?.some(
61
- (e) => e.type === "endDialogue" || e.type === "goToLocation"
62
- ) && !o.has(t.next) && a.push({
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 "${n.id}" choice "${t.id}" GOTO "${t.next}" points to non-existent node`,
65
- suggestion: `Add NODE ${t.next} or fix the GOTO target`
66
- }), t.conditions)
67
- for (const e of t.conditions)
68
- a.push(...v(e, n.id, r));
69
- if (t.effects)
70
- for (const e of t.effects)
71
- a.push(...C(e, n.id, r));
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 (n.conditions)
74
- for (const t of n.conditions)
75
- a.push(...v(t, n.id, r));
76
- if (n.effects)
77
- for (const t of n.effects)
78
- a.push(...C(t, n.id, r));
79
- return a;
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 U = {
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
- }, W = {
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(n, o, r) {
127
- const a = [];
128
- if (!n.type)
129
- return a.push({
126
+ function v(t, a, r) {
127
+ const o = [];
128
+ if (!t.type)
129
+ return o.push({
130
130
  file: r,
131
- message: `Node "${o}" has condition with missing type`
132
- }), a;
133
- if (n.type === "timeIs")
134
- return n.hour === void 0 && n.day === void 0 && a.push({
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 "${o}" condition "timeIs" must have at least one of "hour" or "day" argument`
137
- }), a;
138
- const t = U[n.type];
139
- if (!t)
140
- return a;
141
- for (const s of t)
142
- (n[s] === void 0 || n[s] === null || n[s] === "") && a.push({
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 "${o}" condition "${n.type}" missing required "${s}" argument`
144
+ message: `Node "${a}" condition "${t.type}" missing required "${s}" argument`
145
145
  });
146
- return a;
146
+ return o;
147
147
  }
148
- function C(n, o, r) {
149
- const a = [];
150
- if (!n.type)
151
- return a.push({
148
+ function C(t, a, r) {
149
+ const o = [];
150
+ if (!t.type)
151
+ return o.push({
152
152
  file: r,
153
- message: `Node "${o}" has effect with missing type`
154
- }), a;
155
- const t = W[n.type];
156
- if (!t)
157
- return a;
158
- for (const s of t)
159
- (n[s] === void 0 || n[s] === null || n[s] === "") && a.push({
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 "${o}" effect "${n.type}" missing required "${s}" argument`
161
+ message: `Node "${a}" effect "${t.type}" missing required "${s}" argument`
162
162
  });
163
- return a;
163
+ return o;
164
164
  }
165
- function J(n, o) {
166
- const r = [], a = /* @__PURE__ */ new Set();
167
- for (const e of Object.values(n.locales))
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
- a.add(c);
170
- const t = (e) => e.startsWith("@"), s = (e, c, l) => {
171
- const u = e.slice(1);
172
- if (!a.has(u)) {
173
- const h = o.get(c) || `${l}:${c}`;
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 "${u}: ..." to your locale files`
177
+ suggestion: `Add "${d}: ..." to your locale files`
178
178
  });
179
179
  }
180
180
  };
181
- for (const e of Object.values(n.locations))
182
- t(e.name) && s(e.name, e.id, "location"), t(e.description) && s(e.description, e.id, "location");
183
- for (const e of Object.values(n.characters))
184
- t(e.name) && s(e.name, e.id, "character"), t(e.biography) && s(e.biography, e.id, "character");
185
- for (const e of Object.values(n.items))
186
- t(e.name) && s(e.name, e.id, "item"), t(e.description) && s(e.description, e.id, "item");
187
- for (const e of Object.values(n.quests)) {
188
- t(e.name) && s(e.name, e.id, "quest"), t(e.description) && s(e.description, e.id, "quest");
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
- t(c.description) && s(c.description, e.id, "quest");
190
+ n(c.description) && s(c.description, e.id, "quest");
191
191
  }
192
- for (const e of Object.values(n.journalEntries))
193
- t(e.title) && s(e.title, e.id, "journal"), t(e.text) && s(e.text, e.id, "journal");
194
- for (const e of Object.values(n.dialogues))
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
- t(c.text) && s(c.text, e.id, "dialogue");
196
+ n(c.text) && s(c.text, e.id, "dialogue");
197
197
  for (const l of c.choices)
198
- t(l.text) && s(l.text, e.id, "dialogue");
198
+ n(l.text) && s(l.text, e.id, "dialogue");
199
199
  }
200
- for (const e of Object.values(n.interludes))
201
- t(e.text) && s(e.text, e.id, "interlude");
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(n) {
205
- if (n.length === 0) {
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 ${n.length} validation error${n.length === 1 ? "" : "s"}:
210
+ ✗ Found ${t.length} validation error${t.length === 1 ? "" : "s"}:
211
211
  `));
212
- for (const o of n)
213
- 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();
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 = "ðŸū", Q = "âœĻ", Y = "✏ïļ", V = "➕";
216
- async function K() {
217
- const n = process.cwd(), o = d(n, "content");
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(t) {
222
- t.middlewares.use("/api/content", async (e, c) => {
221
+ configureServer(n) {
222
+ n.middlewares.use("/api/content", async (l, d) => {
223
223
  try {
224
- const l = await z(o);
225
- c.setHeader("Content-Type", "application/json"), c.end(JSON.stringify(l));
226
- } catch (l) {
227
- console.error(i.red(" Error loading content:"), l), c.statusCode = 500, c.end(JSON.stringify({ error: "Failed to load 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 = M(o, {
230
+ const s = H(a, {
231
231
  ignored: /(^|[\/\\])\../,
232
232
  persistent: !0
233
233
  });
234
- s.on("change", async (e) => {
235
- console.log(i.yellow(` ${Y} Content changed: ${e}`)), await A(o), t.ws.send({
236
- type: "full-reload",
237
- path: "*"
238
- });
239
- }), s.on("add", async (e) => {
240
- console.log(i.green(` ${V} Content added: ${e}`)), await A(o), t.ws.send({
241
- type: "full-reload",
242
- path: "*"
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
- }, a = await $({
247
- root: n,
248
- plugins: [j(), r],
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 a.listen(), a.printUrls(), console.log(""), console.log(i.dim(` ${Q} Watching content files for changes...`)), console.log("");
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 z(n) {
257
- const o = {
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 a = [
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: t, key: s } of a) {
279
- const e = d(n, t);
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 u = d(e, l), h = await f(u, "utf-8"), m = y(h);
285
- m && m.id && (o[s][m.id] = m);
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 t = d(n, "locales"), s = await p(t);
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 = d(t, e), l = await f(c, "utf-8"), u = y(l), h = e.replace(/\.(yaml|yml)$/, "");
295
- o.locales[h] = u ?? {};
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 t = d(n, "dialogues"), s = await p(t);
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 = d(t, e), l = await f(c, "utf-8"), u = e.replace(".dlg", ""), h = E(l, u);
304
- o.dialogues[h.id] = h;
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 t = d(n, "game.yaml"), s = await f(t, "utf-8");
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: o, config: r };
320
+ return { registry: a, config: r };
321
321
  }
322
- async function A(n) {
322
+ async function z(t) {
323
323
  try {
324
- const { registry: o, fileMap: r } = await X(n), a = I(o, r);
325
- a.length > 0 && (console.log(""), N(a), console.log(""));
326
- } catch (o) {
327
- console.error(i.red(" Error running validation:"), o);
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(n) {
331
- const o = {
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(), a = [
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: t, key: s } of a) {
351
- const e = d(n, t);
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 u = d(e, l), h = await f(u, "utf-8"), m = y(h);
357
- m && m.id && (o[s][m.id] = m, r.set(m.id, _(process.cwd(), u)));
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 t = d(n, "locales"), s = await p(t);
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 = d(t, e), l = await f(c, "utf-8"), u = y(l), h = e.replace(/\.(yaml|yml)$/, "");
367
- o.locales[h] = u ?? {};
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 t = d(n, "dialogues"), s = await p(t);
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 = d(t, e), l = await f(c, "utf-8"), u = e.replace(".dlg", ""), h = E(l, u);
376
- o.dialogues[h.id] = h, r.set(h.id, _(process.cwd(), c));
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: o, fileMap: r };
380
+ return { registry: a, fileMap: r };
381
381
  }
382
382
  async function Z() {
383
- const n = process.cwd(), o = d(n, "content");
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: a, fileMap: t, config: s } = await ee(o), e = I(a, t);
388
- N(e), e.length > 0 && (console.log(i.red("Build failed due to validation errors.")), console.log(""), process.exit(1)), r = { registry: a, config: s };
389
- } catch (a) {
390
- console.error(i.red("Error loading content:"), a), process.exit(1);
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 H({
395
- root: n,
396
- plugins: [j()],
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 a = d(n, "dist", "api");
403
- await D(a, { recursive: !0 }), await k(d(a, "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 (a) {
405
- console.error(i.red("Build failed:"), a), process.exit(1);
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(n) {
409
- const o = {
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(), a = [
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 a) {
429
- const c = d(n, s);
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 u of l)
433
- if (g(u) === ".yaml" || g(u) === ".yml") {
434
- const h = d(c, u), m = await f(h, "utf-8"), w = y(m);
435
- w && w.id && (o[e][w.id] = w, r.set(w.id, _(process.cwd(), h)));
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 = d(n, "locales"), e = await p(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 = d(s, c), u = await f(l, "utf-8"), h = y(u), m = c.replace(/\.(yaml|yml)$/, "");
445
- o.locales[m] = h ?? {};
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 = d(n, "dialogues"), e = await p(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 = d(s, c), u = await f(l, "utf-8"), h = c.replace(".dlg", ""), m = E(u, h);
454
- o.dialogues[m.id] = m, r.set(m.id, _(process.cwd(), l));
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 t = null;
458
+ let n = null;
459
459
  try {
460
- const s = d(n, "game.yaml"), e = await f(s, "utf-8");
461
- t = y(e);
460
+ const s = u(t, "game.yaml"), e = await f(s, "utf-8");
461
+ n = y(e);
462
462
  } catch {
463
- t = { id: "game", startLocation: "", startTime: { day: 1, hour: 8 }, startFlags: {}, startVariables: {}, startInventory: [] };
463
+ n = { id: "game", startLocation: "", startTime: { day: 1, hour: 8 }, startFlags: {}, startVariables: {}, startInventory: [] };
464
464
  }
465
- return { registry: o, fileMap: r, config: t };
465
+ return { registry: a, fileMap: r, config: n };
466
466
  }
467
- async function ne() {
468
- const n = process.cwd(), o = d(n, "content");
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: a } = await te(o), t = I(r, a);
472
- N(t), t.length > 0 && process.exit(1);
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 te(n) {
478
- const o = {
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(), a = [
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: t, key: s } of a) {
496
- const e = d(n, t);
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 u = d(e, l), h = await f(u, "utf-8"), m = y(h);
502
- m && m.id && (o[s][m.id] = m, r.set(m.id, _(process.cwd(), u)));
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 t = d(n, "locales"), s = await p(t);
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 = d(t, e), l = await f(c, "utf-8"), u = y(l), h = e.replace(/\.(yaml|yml)$/, "");
512
- o.locales[h] = u ?? {};
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 t = d(n, "dialogues"), s = await p(t);
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 = d(t, e), l = await f(c, "utf-8"), u = e.replace(".dlg", ""), h = E(l, u);
521
- o.dialogues[h.id] = h, r.set(h.id, _(process.cwd(), c));
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: o, fileMap: r };
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
- `, _e = `id: tavern_discovery
965
+ `, be = `id: tavern_discovery
961
966
  title: "@journal.tavern_discovery.title"
962
967
  text: "@journal.tavern_discovery.text"
963
968
  category: places
964
- `, be = `# ===================
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 = "🐕", x = "ðŸĶī", Ce = "âœĻ", q = "📁", T = "✅", Re = "🚀", S = /* @__PURE__ */ Object.assign({
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": _e,
1292
- "./templates/content/locales/en.yaml": be,
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(n) {
1303
- const o = n.replace("./templates/", "");
1304
- if (o === "src/App.default.tsx" || o === "src/App.custom.tsx") return null;
1305
- if (o.startsWith("_root/")) {
1306
- const r = o.slice(6);
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 o;
1315
+ return a;
1310
1316
  }
1311
- async function Se(n) {
1312
- const o = d(process.cwd(), n);
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(n)}`), console.log("");
1314
- const { useDefaultRenderer: r } = await P({
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
- ${x} No worries, maybe next time! Woof!`)), process.exit(0)), console.log(""), await je(o, n, r), console.log(""), console.log(i.bold.green(` ${T} Project created successfully!`)), console.log(""), console.log(i.dim(` ${q} ${o}`)), console.log(""), console.log(i.bold(" Next steps:")), console.log(i.cyan(` cd ${n}`)), 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("");
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(n, o, r) {
1324
- const a = [
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(` ${q} ${i.bold("Creating directories...")}`);
1344
- for (const e of a)
1345
- await D(d(n, e), { recursive: !0 });
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 t = {
1348
- name: o,
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(d(n, "package.json"), JSON.stringify(t, null, 2));
1372
- for (const [e, c] of Object.entries(S)) {
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 u = d(n, l);
1376
- await D(F(u), { recursive: !0 }), await k(u, c);
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(d(n, "src/App.tsx"), S[s]), console.log(i.green(` ${T} Source files created`)), console.log(""), console.log(` ${x} ${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"));
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 b = new G();
1382
- b.name("doodle").description(i.magenta("ðŸū Doodle Engine") + i.dim(" — Narrative RPG development tools")).version("0.0.1");
1383
- b.command("create <project-name>").description("Scaffold a new Doodle Engine game project").action(async (n) => {
1384
- await Se(n);
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
- b.command("dev").description("Start development server with hot reload").action(async () => {
1387
- await K();
1392
+ _.command("dev").description("Start development server with hot reload").action(async () => {
1393
+ await V();
1388
1394
  });
1389
- b.command("build").description("Build game for production").action(async () => {
1395
+ _.command("build").description("Build game for production").action(async () => {
1390
1396
  await Z();
1391
1397
  });
1392
- b.command("validate").description("Validate game content").action(async () => {
1393
- await ne();
1398
+ _.command("validate").description("Validate game content").action(async () => {
1399
+ await te();
1394
1400
  });
1395
- b.parse();
1401
+ _.parse();