kawaijs 0.1.1 ā 0.1.3
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/commands/build.d.ts +5 -0
- package/dist/commands/build.d.ts.map +1 -0
- package/dist/commands/build.js +530 -0
- package/dist/commands/build.js.map +1 -0
- package/dist/commands/dev.d.ts +6 -0
- package/dist/commands/dev.d.ts.map +1 -0
- package/dist/commands/dev.js +630 -0
- package/dist/commands/dev.js.map +1 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -5
- package/dist/index.js.map +1 -1
- package/package.json +7 -5
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/commands/build.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,YAAY,CAAC,UAAU,SAAM,EAAE,OAAO,GAAE,YAAiB,GAAG,OAAO,CAqgBlF"}
|
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { compileScript, formatDiagnostic, KawaError } from '@kawaijs/parser';
|
|
4
|
+
export function buildProject(projectDir = '.', options = {}) {
|
|
5
|
+
const rootDir = path.resolve(process.cwd(), projectDir);
|
|
6
|
+
const scriptPath = path.join(rootDir, 'game', 'script.kawa');
|
|
7
|
+
const stylePath = path.join(rootDir, 'game', 'style.css');
|
|
8
|
+
const assetsDir = path.join(rootDir, 'game', 'assets');
|
|
9
|
+
const outDir = path.resolve(rootDir, options.outDir ?? 'dist');
|
|
10
|
+
console.log(`\nš¦ Building Kawaijs visual novel...`);
|
|
11
|
+
console.log(` Source: ${rootDir}`);
|
|
12
|
+
console.log(` Output: ${outDir}\n`);
|
|
13
|
+
if (!fs.existsSync(scriptPath)) {
|
|
14
|
+
console.error(`ā Error: Cannot find '${scriptPath}'. Make sure you are inside a Kawaijs project directory.`);
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
// 1. Compile Kawa Script
|
|
18
|
+
let storyPackage;
|
|
19
|
+
try {
|
|
20
|
+
const source = fs.readFileSync(scriptPath, 'utf-8');
|
|
21
|
+
storyPackage = compileScript(source, path.basename(scriptPath));
|
|
22
|
+
console.log(`ā
Script compiled successfully (${Object.keys(storyPackage.labels).length} labels).`);
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
if (err instanceof KawaError) {
|
|
26
|
+
const source = fs.readFileSync(scriptPath, 'utf-8');
|
|
27
|
+
console.error(formatDiagnostic(err.diagnostic, source));
|
|
28
|
+
}
|
|
29
|
+
else if (err instanceof Error) {
|
|
30
|
+
console.error(`ā Compilation error: ${err.message}`);
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
// 2. Prepare output directory
|
|
35
|
+
if (fs.existsSync(outDir)) {
|
|
36
|
+
fs.rmSync(outDir, { recursive: true, force: true });
|
|
37
|
+
}
|
|
38
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
39
|
+
// 3. Copy Assets
|
|
40
|
+
const outAssetsDir = path.join(outDir, 'assets');
|
|
41
|
+
if (fs.existsSync(assetsDir)) {
|
|
42
|
+
copyDirectoryRecursive(assetsDir, outAssetsDir);
|
|
43
|
+
console.log(`ā
Assets copied to dist/assets/`);
|
|
44
|
+
}
|
|
45
|
+
// 4. Generate Combined style.css
|
|
46
|
+
let combinedCss = `/* Kawaijs Bundled Stylesheet */\n`;
|
|
47
|
+
const themeCssPath = path.resolve(path.dirname(import.meta.url.replace('file:///', '')), '..', '..', '..', 'renderer-dom', 'src', 'theme.css');
|
|
48
|
+
if (fs.existsSync(themeCssPath)) {
|
|
49
|
+
combinedCss += fs.readFileSync(themeCssPath, 'utf-8') + '\n\n';
|
|
50
|
+
}
|
|
51
|
+
if (fs.existsSync(stylePath)) {
|
|
52
|
+
combinedCss += fs.readFileSync(stylePath, 'utf-8') + '\n';
|
|
53
|
+
}
|
|
54
|
+
fs.writeFileSync(path.join(outDir, 'style.css'), combinedCss, 'utf-8');
|
|
55
|
+
console.log(`ā
Stylesheet bundled to dist/style.css`);
|
|
56
|
+
// 5. Generate Standalone HTML Application
|
|
57
|
+
const htmlContent = `<!DOCTYPE html>
|
|
58
|
+
<html lang="en">
|
|
59
|
+
<head>
|
|
60
|
+
<meta charset="UTF-8">
|
|
61
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
62
|
+
<title>${storyPackage.meta.title ?? 'Kawaijs Visual Novel'}</title>
|
|
63
|
+
<link rel="stylesheet" href="./style.css">
|
|
64
|
+
<style>
|
|
65
|
+
body { margin: 0; padding: 0; background: #000; overflow: hidden; }
|
|
66
|
+
</style>
|
|
67
|
+
</head>
|
|
68
|
+
<body>
|
|
69
|
+
<div id="app"></div>
|
|
70
|
+
|
|
71
|
+
<script type="module">
|
|
72
|
+
const story = ${JSON.stringify(storyPackage, null, 2)};
|
|
73
|
+
|
|
74
|
+
class MemoryStorageAdapter {
|
|
75
|
+
constructor() { this.store = new Map(); }
|
|
76
|
+
getItem(k) { return this.store.get(k) || null; }
|
|
77
|
+
setItem(k, v) { this.store.set(k, v); }
|
|
78
|
+
removeItem(k) { this.store.delete(k); }
|
|
79
|
+
}
|
|
80
|
+
class LocalStorageAdapter {
|
|
81
|
+
getItem(k) { return localStorage.getItem(k); }
|
|
82
|
+
setItem(k, v) { localStorage.setItem(k, v); }
|
|
83
|
+
removeItem(k) { localStorage.removeItem(k); }
|
|
84
|
+
}
|
|
85
|
+
class SaveManager {
|
|
86
|
+
constructor() { this.storage = typeof localStorage !== 'undefined' ? new LocalStorageAdapter() : new MemoryStorageAdapter(); }
|
|
87
|
+
async saveSlot(id, snapshot, previewText) {
|
|
88
|
+
const slot = { id, name: 'Slot ' + id, timestamp: Date.now(), snapshot, previewText };
|
|
89
|
+
this.storage.setItem('kawaijs_save_' + id, JSON.stringify(slot));
|
|
90
|
+
return slot;
|
|
91
|
+
}
|
|
92
|
+
async loadSlot(id) {
|
|
93
|
+
const raw = this.storage.getItem('kawaijs_save_' + id);
|
|
94
|
+
return raw ? JSON.parse(raw) : null;
|
|
95
|
+
}
|
|
96
|
+
async deleteSlot(id) {
|
|
97
|
+
this.storage.removeItem('kawaijs_save_' + id);
|
|
98
|
+
}
|
|
99
|
+
async listSlots(total = 6) {
|
|
100
|
+
const slots = [];
|
|
101
|
+
for (let i = 1; i <= total; i++) {
|
|
102
|
+
slots.push(await this.loadSlot(String(i)));
|
|
103
|
+
}
|
|
104
|
+
return slots;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function evaluateCondition(cond, vars) {
|
|
108
|
+
const t = (cond || '').trim();
|
|
109
|
+
if (!t || t === 'true') return true;
|
|
110
|
+
if (t === 'false') return false;
|
|
111
|
+
const ops = ['>=', '<=', '!=', '==', '>', '<'];
|
|
112
|
+
for (const op of ops) {
|
|
113
|
+
const idx = t.indexOf(op);
|
|
114
|
+
if (idx !== -1) {
|
|
115
|
+
const l = resolveVal(t.slice(0, idx), vars);
|
|
116
|
+
const r = resolveVal(t.slice(idx + op.length), vars);
|
|
117
|
+
if (op === '>=') return Number(l) >= Number(r);
|
|
118
|
+
if (op === '<=') return Number(l) <= Number(r);
|
|
119
|
+
if (op === '>') return Number(l) > Number(r);
|
|
120
|
+
if (op === '<') return Number(l) < Number(r);
|
|
121
|
+
if (op === '==') return l == r;
|
|
122
|
+
if (op === '!=') return l != r;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (t.startsWith('!')) return !Boolean(vars[t.slice(1).trim()]);
|
|
126
|
+
return Boolean(vars[t]);
|
|
127
|
+
}
|
|
128
|
+
function resolveVal(token, vars) {
|
|
129
|
+
const t = token.trim();
|
|
130
|
+
if (t === 'true') return true;
|
|
131
|
+
if (t === 'false') return false;
|
|
132
|
+
if (!isNaN(Number(t)) && t !== '') return Number(t);
|
|
133
|
+
if ((t.startsWith('"') && t.endsWith('"')) || (t.startsWith("'") && t.endsWith("'"))) return t.slice(1, -1);
|
|
134
|
+
if (t in vars) return vars[t];
|
|
135
|
+
return t;
|
|
136
|
+
}
|
|
137
|
+
function applySetOp(curr, op, val, vars) {
|
|
138
|
+
const res = (typeof val === 'string' && val in vars) ? vars[val] : val;
|
|
139
|
+
if (op === '+=') return Number(curr || 0) + Number(res);
|
|
140
|
+
if (op === '-=') return Number(curr || 0) - Number(res);
|
|
141
|
+
return res;
|
|
142
|
+
}
|
|
143
|
+
class StoryVM {
|
|
144
|
+
constructor(story) {
|
|
145
|
+
this.story = story;
|
|
146
|
+
this.state = {
|
|
147
|
+
currentLabel: story.meta.startLabel || 'start',
|
|
148
|
+
instructionPointer: 0,
|
|
149
|
+
callStack: [],
|
|
150
|
+
variables: {},
|
|
151
|
+
visual: { background: null, transition: null, characters: {} },
|
|
152
|
+
audio: { music: null, voice: null },
|
|
153
|
+
dialogue: null,
|
|
154
|
+
choices: null,
|
|
155
|
+
isWaitingForInput: false,
|
|
156
|
+
isFinished: false
|
|
157
|
+
};
|
|
158
|
+
this.snapshotStack = [];
|
|
159
|
+
this.listeners = new Set();
|
|
160
|
+
this.audioListeners = new Set();
|
|
161
|
+
this.history = [];
|
|
162
|
+
this.saveManager = new SaveManager();
|
|
163
|
+
this.isExecuting = false;
|
|
164
|
+
}
|
|
165
|
+
getState() { return this.state; }
|
|
166
|
+
onStateChange(cb) { this.listeners.add(cb); return () => this.listeners.delete(cb); }
|
|
167
|
+
start() { this.execute(); }
|
|
168
|
+
next() {
|
|
169
|
+
if (this.state.isFinished || this.isExecuting || (this.state.choices && this.state.choices.length > 0)) return;
|
|
170
|
+
this.state.isWaitingForInput = false;
|
|
171
|
+
this.execute();
|
|
172
|
+
}
|
|
173
|
+
choose(idx) {
|
|
174
|
+
if (this.isExecuting || !this.state.choices || !this.state.choices[idx]) return;
|
|
175
|
+
const choice = this.state.choices[idx];
|
|
176
|
+
this.state.choices = null;
|
|
177
|
+
this.state.isWaitingForInput = false;
|
|
178
|
+
this.state.currentLabel = choice.targetLabel;
|
|
179
|
+
this.state.instructionPointer = 0;
|
|
180
|
+
this.execute();
|
|
181
|
+
}
|
|
182
|
+
rollback() {
|
|
183
|
+
if (this.snapshotStack.length <= 1) return false;
|
|
184
|
+
this.snapshotStack.pop();
|
|
185
|
+
const prev = this.snapshotStack[this.snapshotStack.length - 1];
|
|
186
|
+
if (prev) {
|
|
187
|
+
this.state = JSON.parse(JSON.stringify(prev));
|
|
188
|
+
this.notify();
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
canRollback() { return this.snapshotStack.length > 1; }
|
|
194
|
+
async save(slot) {
|
|
195
|
+
await this.saveManager.saveSlot(slot, JSON.parse(JSON.stringify(this.state)), this.state.dialogue ? this.state.dialogue.text : '');
|
|
196
|
+
}
|
|
197
|
+
async load(slot) {
|
|
198
|
+
const data = await this.saveManager.loadSlot(slot);
|
|
199
|
+
if (data && data.snapshot) {
|
|
200
|
+
this.state = JSON.parse(JSON.stringify(data.snapshot.state || data.snapshot));
|
|
201
|
+
this.snapshotStack = [JSON.parse(JSON.stringify(this.state))];
|
|
202
|
+
this.notify();
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
execute() {
|
|
208
|
+
if (this.isExecuting) return;
|
|
209
|
+
this.isExecuting = true;
|
|
210
|
+
try {
|
|
211
|
+
while (!this.state.isWaitingForInput && !this.state.isFinished) {
|
|
212
|
+
const list = this.story.labels[this.state.currentLabel];
|
|
213
|
+
if (!list || this.state.instructionPointer >= list.length) {
|
|
214
|
+
if (this.state.callStack.length > 0) {
|
|
215
|
+
const frame = this.state.callStack.pop();
|
|
216
|
+
this.state.currentLabel = frame.returnLabel;
|
|
217
|
+
this.state.instructionPointer = frame.returnPointer;
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
this.state.isFinished = true;
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
const inst = list[this.state.instructionPointer++];
|
|
224
|
+
if (inst.type === 'scene') {
|
|
225
|
+
this.state.visual.background = inst.background;
|
|
226
|
+
this.state.visual.characters = {};
|
|
227
|
+
} else if (inst.type === 'show') {
|
|
228
|
+
this.state.visual.characters[inst.character] = {
|
|
229
|
+
expression: inst.expression,
|
|
230
|
+
position: inst.position || 'center'
|
|
231
|
+
};
|
|
232
|
+
} else if (inst.type === 'hide') {
|
|
233
|
+
delete this.state.visual.characters[inst.character];
|
|
234
|
+
} else if (inst.type === 'dialogue') {
|
|
235
|
+
const charDef = inst.speaker ? this.story.characters[inst.speaker] : null;
|
|
236
|
+
this.state.dialogue = {
|
|
237
|
+
speaker: inst.speaker,
|
|
238
|
+
speakerDisplayName: charDef ? charDef.name : inst.speaker,
|
|
239
|
+
speakerColor: charDef ? charDef.color : null,
|
|
240
|
+
text: inst.text
|
|
241
|
+
};
|
|
242
|
+
this.state.isWaitingForInput = true;
|
|
243
|
+
this.history.push(this.state.dialogue);
|
|
244
|
+
} else if (inst.type === 'choice') {
|
|
245
|
+
this.state.choices = inst.choices;
|
|
246
|
+
this.state.isWaitingForInput = true;
|
|
247
|
+
} else if (inst.type === 'jump') {
|
|
248
|
+
this.state.currentLabel = inst.targetLabel;
|
|
249
|
+
this.state.instructionPointer = 0;
|
|
250
|
+
} else if (inst.type === 'set') {
|
|
251
|
+
const curr = this.state.variables[inst.variable];
|
|
252
|
+
this.state.variables[inst.variable] = applySetOp(curr, inst.operator, inst.value, this.state.variables);
|
|
253
|
+
} else if (inst.type === 'branch') {
|
|
254
|
+
const ok = evaluateCondition(inst.condition, this.state.variables);
|
|
255
|
+
this.state.currentLabel = ok ? inst.thenLabel : (inst.elseLabel || inst.thenLabel);
|
|
256
|
+
this.state.instructionPointer = 0;
|
|
257
|
+
} else if (inst.type === 'return') {
|
|
258
|
+
this.state.isFinished = true;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (this.state.isWaitingForInput) {
|
|
262
|
+
this.snapshotStack.push(JSON.parse(JSON.stringify(this.state)));
|
|
263
|
+
}
|
|
264
|
+
} finally {
|
|
265
|
+
this.isExecuting = false;
|
|
266
|
+
}
|
|
267
|
+
this.notify();
|
|
268
|
+
}
|
|
269
|
+
notify() { for (const l of this.listeners) l(this.state); }
|
|
270
|
+
}
|
|
271
|
+
class DOMRenderer {
|
|
272
|
+
constructor(vm, container) {
|
|
273
|
+
this.vm = vm;
|
|
274
|
+
this.container = container;
|
|
275
|
+
this.isChoicePending = false;
|
|
276
|
+
this.build();
|
|
277
|
+
vm.onStateChange(s => this.render(s));
|
|
278
|
+
this.render(vm.getState());
|
|
279
|
+
}
|
|
280
|
+
build() {
|
|
281
|
+
this.container.innerHTML = \`
|
|
282
|
+
<div class="kawa-root">
|
|
283
|
+
<div class="kawa-stage">
|
|
284
|
+
<div class="kawa-background"></div>
|
|
285
|
+
<div class="kawa-characters kawa-sprites"></div>
|
|
286
|
+
<div class="kawa-ui-layer">
|
|
287
|
+
<div class="kawa-choice-container kawa-choices" style="display:none"></div>
|
|
288
|
+
<div class="kawa-dialogue-box kawa-dialogue">
|
|
289
|
+
<div class="kawa-speaker-tag kawa-speaker" style="display:none"></div>
|
|
290
|
+
<div class="kawa-dialogue-text kawa-text"></div>
|
|
291
|
+
<div class="kawa-continue-indicator">▼</div>
|
|
292
|
+
</div>
|
|
293
|
+
<nav class="kawa-quick-menu">
|
|
294
|
+
<button class="kawa-btn kawa-back">Back</button>
|
|
295
|
+
<button class="kawa-btn kawa-hist">History</button>
|
|
296
|
+
<button class="kawa-btn kawa-save">Save</button>
|
|
297
|
+
<button class="kawa-btn kawa-load">Load</button>
|
|
298
|
+
</nav>
|
|
299
|
+
</div>
|
|
300
|
+
</div>
|
|
301
|
+
</div>\`;
|
|
302
|
+
this.rootEl = this.container.querySelector('.kawa-root');
|
|
303
|
+
this.bgEl = this.container.querySelector('.kawa-background');
|
|
304
|
+
this.charsEl = this.container.querySelector('.kawa-characters');
|
|
305
|
+
this.boxEl = this.container.querySelector('.kawa-dialogue-box');
|
|
306
|
+
this.spkEl = this.container.querySelector('.kawa-speaker-tag');
|
|
307
|
+
this.txtEl = this.container.querySelector('.kawa-dialogue-text');
|
|
308
|
+
this.choiceEl = this.container.querySelector('.kawa-choice-container');
|
|
309
|
+
this.backBtn = this.container.querySelector('.kawa-back');
|
|
310
|
+
this.histBtn = this.container.querySelector('.kawa-hist');
|
|
311
|
+
this.saveBtn = this.container.querySelector('.kawa-save');
|
|
312
|
+
this.loadBtn = this.container.querySelector('.kawa-load');
|
|
313
|
+
|
|
314
|
+
this.boxEl.addEventListener('click', () => this.vm.next());
|
|
315
|
+
this.backBtn.addEventListener('click', (e) => { e.stopPropagation(); this.vm.rollback(); });
|
|
316
|
+
this.histBtn.addEventListener('click', (e) => { e.stopPropagation(); this.showHistory(); });
|
|
317
|
+
this.saveBtn.addEventListener('click', (e) => { e.stopPropagation(); this.showSaveLoad('save'); });
|
|
318
|
+
this.loadBtn.addEventListener('click', (e) => { e.stopPropagation(); this.showSaveLoad('load'); });
|
|
319
|
+
window.addEventListener('keydown', (e) => {
|
|
320
|
+
const modal = this.rootEl.querySelector('.kawa-modal-overlay');
|
|
321
|
+
if (modal) {
|
|
322
|
+
if (e.key === 'Escape') modal.remove();
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
if (e.code === 'Space' || e.code === 'Enter') { e.preventDefault(); this.vm.next(); }
|
|
326
|
+
if (e.code === 'Backspace') { e.preventDefault(); this.vm.rollback(); }
|
|
327
|
+
if (e.key === 's' || e.key === 'S') this.showSaveLoad('save');
|
|
328
|
+
if (e.key === 'l' || e.key === 'L') this.showSaveLoad('load');
|
|
329
|
+
if (e.key === 'h' || e.key === 'H') this.showHistory();
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
render(state) {
|
|
333
|
+
this.isChoicePending = false;
|
|
334
|
+
this.backBtn.disabled = !this.vm.canRollback();
|
|
335
|
+
if (state.visual.background) {
|
|
336
|
+
const bg = state.visual.background.replace(/^bg\\s+/, '');
|
|
337
|
+
this.bgEl.style.backgroundImage = 'url("./assets/backgrounds/' + bg + (bg.includes('.') ? '' : '.svg') + '"), url("./assets/backgrounds/' + bg + '.png")';
|
|
338
|
+
this.bgEl.style.opacity = '1';
|
|
339
|
+
} else {
|
|
340
|
+
this.bgEl.style.opacity = '0';
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
this.charsEl.innerHTML = '';
|
|
344
|
+
for (const [id, char] of Object.entries(state.visual.characters)) {
|
|
345
|
+
const div = document.createElement('div');
|
|
346
|
+
div.className = 'kawa-sprite kawa-pos-' + (char.position || 'center');
|
|
347
|
+
const img = document.createElement('img');
|
|
348
|
+
const expr = char.expression ? '/' + char.expression : '';
|
|
349
|
+
img.src = './assets/characters/' + id + expr + '.svg';
|
|
350
|
+
img.alt = id;
|
|
351
|
+
let fallback = 0;
|
|
352
|
+
img.onerror = () => {
|
|
353
|
+
fallback++;
|
|
354
|
+
if (fallback === 1) img.src = './assets/characters/' + id + expr + '.png';
|
|
355
|
+
else if (fallback === 2 && char.expression) img.src = './assets/characters/' + id + '_' + char.expression + '.svg';
|
|
356
|
+
else if (fallback === 3 && char.expression) img.src = './assets/characters/' + id + '_' + char.expression + '.png';
|
|
357
|
+
else {
|
|
358
|
+
img.style.display = 'none';
|
|
359
|
+
div.style.width = '220px'; div.style.height = '420px';
|
|
360
|
+
div.style.background = 'rgba(244,63,94,0.25)'; div.style.border = '2px dashed #f43f5e';
|
|
361
|
+
div.style.borderRadius = '16px'; div.style.display = 'flex'; div.style.alignItems = 'center';
|
|
362
|
+
div.style.justifyContent = 'center'; div.style.color = '#fff'; div.style.fontWeight = '600';
|
|
363
|
+
div.textContent = id + (char.expression ? ' (' + char.expression + ')' : '');
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
div.appendChild(img);
|
|
367
|
+
this.charsEl.appendChild(div);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (state.dialogue) {
|
|
371
|
+
this.boxEl.style.display = 'block';
|
|
372
|
+
if (state.dialogue.speakerDisplayName) {
|
|
373
|
+
this.spkEl.style.display = 'inline-block';
|
|
374
|
+
this.spkEl.textContent = state.dialogue.speakerDisplayName;
|
|
375
|
+
if (state.dialogue.speakerColor) this.spkEl.style.backgroundColor = state.dialogue.speakerColor;
|
|
376
|
+
} else {
|
|
377
|
+
this.spkEl.style.display = 'none';
|
|
378
|
+
}
|
|
379
|
+
this.txtEl.textContent = state.dialogue.text;
|
|
380
|
+
} else {
|
|
381
|
+
this.boxEl.style.display = 'none';
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (state.choices && state.choices.length > 0) {
|
|
385
|
+
this.choiceEl.innerHTML = '';
|
|
386
|
+
this.choiceEl.style.display = 'flex';
|
|
387
|
+
this.choiceEl.style.pointerEvents = 'auto';
|
|
388
|
+
this.choiceEl.style.opacity = '1';
|
|
389
|
+
state.choices.forEach((c, idx) => {
|
|
390
|
+
const btn = document.createElement('button');
|
|
391
|
+
btn.className = 'kawa-choice-btn kawa-choice';
|
|
392
|
+
btn.textContent = c.text;
|
|
393
|
+
btn.addEventListener('click', (e) => {
|
|
394
|
+
e.stopPropagation();
|
|
395
|
+
if (this.isChoicePending) return;
|
|
396
|
+
this.isChoicePending = true;
|
|
397
|
+
this.choiceEl.style.pointerEvents = 'none';
|
|
398
|
+
this.choiceEl.style.opacity = '0.5';
|
|
399
|
+
this.vm.choose(idx);
|
|
400
|
+
});
|
|
401
|
+
this.choiceEl.appendChild(btn);
|
|
402
|
+
});
|
|
403
|
+
} else {
|
|
404
|
+
this.choiceEl.style.display = 'none';
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
async showSaveLoad(mode) {
|
|
408
|
+
const ex = this.rootEl.querySelector('.kawa-modal-overlay');
|
|
409
|
+
if (ex) ex.remove();
|
|
410
|
+
const ov = document.createElement('div');
|
|
411
|
+
ov.className = 'kawa-modal-overlay';
|
|
412
|
+
const card = document.createElement('div');
|
|
413
|
+
card.className = 'kawa-modal-card';
|
|
414
|
+
card.innerHTML = \`
|
|
415
|
+
<div class="kawa-modal-header">
|
|
416
|
+
<div class="kawa-modal-title">\${mode === 'save' ? 'š¾ Save Game' : 'š Load Game'}</div>
|
|
417
|
+
<button class="kawa-btn kawa-close-btn">ā Close</button>
|
|
418
|
+
</div>
|
|
419
|
+
<div class="kawa-modal-body"><div class="kawa-slots-grid"></div></div>\`;
|
|
420
|
+
card.querySelector('.kawa-close-btn').addEventListener('click', () => ov.remove());
|
|
421
|
+
const grid = card.querySelector('.kawa-slots-grid');
|
|
422
|
+
const slots = await this.vm.saveManager.listSlots(6);
|
|
423
|
+
slots.forEach((s, i) => {
|
|
424
|
+
const num = String(i + 1);
|
|
425
|
+
const c = document.createElement('div');
|
|
426
|
+
c.className = 'kawa-slot-card';
|
|
427
|
+
c.innerHTML = \`
|
|
428
|
+
<div class="kawa-slot-header">
|
|
429
|
+
<span class="kawa-slot-badge">Slot \${num}</span>
|
|
430
|
+
<span class="kawa-slot-time">\${s ? new Date(s.timestamp).toLocaleTimeString() : 'Empty'}</span>
|
|
431
|
+
</div>
|
|
432
|
+
<div class="kawa-slot-preview">\${s ? (s.previewText || 'Game in progress') : '<span class="kawa-slot-empty-text">No save data</span>'}</div>
|
|
433
|
+
<div class="kawa-slot-actions"></div>\`;
|
|
434
|
+
const act = c.querySelector('.kawa-slot-actions');
|
|
435
|
+
if (mode === 'save') {
|
|
436
|
+
const b = document.createElement('button');
|
|
437
|
+
b.className = 'kawa-slot-btn kawa-slot-btn-save';
|
|
438
|
+
b.textContent = 'Save Here';
|
|
439
|
+
b.addEventListener('click', async () => {
|
|
440
|
+
await this.vm.save(num);
|
|
441
|
+
ov.remove();
|
|
442
|
+
this.showSaveLoad('save');
|
|
443
|
+
});
|
|
444
|
+
act.appendChild(b);
|
|
445
|
+
} else if (s) {
|
|
446
|
+
const b = document.createElement('button');
|
|
447
|
+
b.className = 'kawa-slot-btn kawa-slot-btn-load';
|
|
448
|
+
b.textContent = 'Load';
|
|
449
|
+
b.addEventListener('click', async () => {
|
|
450
|
+
if (await this.vm.load(num)) ov.remove();
|
|
451
|
+
});
|
|
452
|
+
act.appendChild(b);
|
|
453
|
+
}
|
|
454
|
+
if (s) {
|
|
455
|
+
const d = document.createElement('button');
|
|
456
|
+
d.className = 'kawa-slot-btn kawa-slot-btn-del';
|
|
457
|
+
d.textContent = 'š';
|
|
458
|
+
d.addEventListener('click', async () => {
|
|
459
|
+
await this.vm.saveManager.deleteSlot(num);
|
|
460
|
+
ov.remove();
|
|
461
|
+
this.showSaveLoad(mode);
|
|
462
|
+
});
|
|
463
|
+
act.appendChild(d);
|
|
464
|
+
}
|
|
465
|
+
grid.appendChild(c);
|
|
466
|
+
});
|
|
467
|
+
ov.appendChild(card);
|
|
468
|
+
this.rootEl.appendChild(ov);
|
|
469
|
+
}
|
|
470
|
+
showHistory() {
|
|
471
|
+
const ex = this.rootEl.querySelector('.kawa-modal-overlay');
|
|
472
|
+
if (ex) ex.remove();
|
|
473
|
+
const ov = document.createElement('div');
|
|
474
|
+
ov.className = 'kawa-modal-overlay';
|
|
475
|
+
const card = document.createElement('div');
|
|
476
|
+
card.className = 'kawa-modal-card';
|
|
477
|
+
card.innerHTML = \`
|
|
478
|
+
<div class="kawa-modal-header">
|
|
479
|
+
<div class="kawa-modal-title">š Dialogue History</div>
|
|
480
|
+
<button class="kawa-btn kawa-close-btn">ā Close</button>
|
|
481
|
+
</div>
|
|
482
|
+
<div class="kawa-modal-body"></div>\`;
|
|
483
|
+
card.querySelector('.kawa-close-btn').addEventListener('click', () => ov.remove());
|
|
484
|
+
const body = card.querySelector('.kawa-modal-body');
|
|
485
|
+
if (this.vm.history.length === 0) {
|
|
486
|
+
body.innerHTML = '<div style="opacity:0.6">No dialogue history yet.</div>';
|
|
487
|
+
} else {
|
|
488
|
+
this.vm.history.forEach(h => {
|
|
489
|
+
const item = document.createElement('div');
|
|
490
|
+
item.className = 'kawa-history-item';
|
|
491
|
+
if (h.speakerDisplayName) {
|
|
492
|
+
item.innerHTML = '<div class="kawa-history-speaker">' + h.speakerDisplayName + '</div>';
|
|
493
|
+
}
|
|
494
|
+
const txt = document.createElement('div');
|
|
495
|
+
txt.textContent = h.text;
|
|
496
|
+
item.appendChild(txt);
|
|
497
|
+
body.appendChild(item);
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
ov.appendChild(card);
|
|
501
|
+
this.rootEl.appendChild(ov);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
const vm = new StoryVM(story);
|
|
506
|
+
new DOMRenderer(vm, document.getElementById('app'));
|
|
507
|
+
vm.start();
|
|
508
|
+
</script>
|
|
509
|
+
</body>
|
|
510
|
+
</html>`;
|
|
511
|
+
fs.writeFileSync(path.join(outDir, 'index.html'), htmlContent, 'utf-8');
|
|
512
|
+
console.log(`ā
Static web application generated: dist/index.html`);
|
|
513
|
+
console.log(`\nš Production build complete! Ready for GitHub Pages, Netlify, itch.io, etc.\n`);
|
|
514
|
+
return true;
|
|
515
|
+
}
|
|
516
|
+
function copyDirectoryRecursive(src, dest) {
|
|
517
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
518
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
519
|
+
for (const entry of entries) {
|
|
520
|
+
const srcPath = path.join(src, entry.name);
|
|
521
|
+
const destPath = path.join(dest, entry.name);
|
|
522
|
+
if (entry.isDirectory()) {
|
|
523
|
+
copyDirectoryRecursive(srcPath, destPath);
|
|
524
|
+
}
|
|
525
|
+
else {
|
|
526
|
+
fs.copyFileSync(srcPath, destPath);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
//# sourceMappingURL=build.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build.js","sourceRoot":"","sources":["../../src/commands/build.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,wBAAgB,cAAc,CAAC,UAAU,SAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,IAAI,CAkMrF"}
|