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