kawaijs 0.1.0 → 0.1.2
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/README.md +109 -0
- package/dist/commands/build.d.ts +5 -0
- package/dist/commands/build.d.ts.map +1 -0
- package/dist/commands/build.js +384 -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 +485 -0
- package/dist/commands/dev.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -5
- package/dist/index.js.map +1 -1
- package/package.json +7 -6
package/README.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# 🌸 Kawaijs
|
|
4
|
+
|
|
5
|
+
**A modern, web-native visual novel engine and toolchain inspired by Ren'Py.**
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/kawaijs)
|
|
8
|
+
[](https://github.com/biagio-scaglia/KawaiJS/blob/main/LICENSE)
|
|
9
|
+
[](https://github.com/biagio-scaglia/KawaiJS)
|
|
10
|
+
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 📖 Introduction
|
|
16
|
+
|
|
17
|
+
**Kawaijs** is a developer-friendly, web-native visual novel engine and toolchain. It bridges the clean, human-friendly authoring experience of Ren'Py with modern Web technologies (HTML5, standard CSS, and TypeScript).
|
|
18
|
+
|
|
19
|
+
Write your story in **Kawa Script** (`.kawa`), style dialogue boxes and choices with **standard CSS**, and deploy anywhere as a pure static web application.
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
character yumia "Yumia" #f43f5e
|
|
23
|
+
|
|
24
|
+
label start:
|
|
25
|
+
scene bg classroom with fade
|
|
26
|
+
show yumia happy at center
|
|
27
|
+
|
|
28
|
+
yumia "Good morning! Are you ready for the festival?"
|
|
29
|
+
|
|
30
|
+
menu:
|
|
31
|
+
"Yes, absolutely!":
|
|
32
|
+
jump ready
|
|
33
|
+
|
|
34
|
+
"Not yet...":
|
|
35
|
+
jump not_ready
|
|
36
|
+
|
|
37
|
+
label ready:
|
|
38
|
+
show yumia excited
|
|
39
|
+
yumia "Awesome! Let's get to work."
|
|
40
|
+
return
|
|
41
|
+
|
|
42
|
+
label not_ready:
|
|
43
|
+
yumia "Don't worry, we still have time."
|
|
44
|
+
return
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 🚀 Quick Start
|
|
50
|
+
|
|
51
|
+
### 1. Scaffold a New Visual Novel
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npx kawaijs create my-novel
|
|
55
|
+
cd my-novel
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 2. Validate Your Script
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
npx kawaijs validate
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## ✨ Features
|
|
67
|
+
|
|
68
|
+
- **🌐 Web-Native**: Runs directly in modern browsers with zero backend dependencies. Deploy to GitHub Pages, Netlify, Vercel, Cloudflare Pages, or itch.io.
|
|
69
|
+
- **✍️ Writer-Friendly**: Indentation-based Kawa Script designed for narrative designers and story writers.
|
|
70
|
+
- **🎨 Deep CSS Theming**: Customize dialogue boxes, choice overlays, buttons, fonts, and animations using standard CSS and CSS custom properties.
|
|
71
|
+
- **⏱️ Deterministic Rollback**: Step backwards through dialogue and choices with instant snapshot rollbacks (`vm.rollback()`).
|
|
72
|
+
- **💾 Save & Load**: Built-in persistence for game save slots using LocalStorage / IndexedDB.
|
|
73
|
+
- **📦 Modular Architecture**: Decoupled compiler (`@kawaijs/parser`), headless state machine (`@kawaijs/runtime`), and presentation layer (`@kawaijs/renderer-dom`).
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## 💻 CLI Usage
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# Create a new visual novel project
|
|
81
|
+
kawa create <project-name>
|
|
82
|
+
|
|
83
|
+
# Validate syntax, jump labels, and asset links
|
|
84
|
+
kawa validate [path]
|
|
85
|
+
|
|
86
|
+
# Show version
|
|
87
|
+
kawa version
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## 📦 Modular Packages
|
|
93
|
+
|
|
94
|
+
Kawaijs is built as a clean modular toolkit:
|
|
95
|
+
|
|
96
|
+
| Package | Description |
|
|
97
|
+
| :--- | :--- |
|
|
98
|
+
| [`kawaijs`](https://www.npmjs.com/package/kawaijs) | All-in-one engine bundle & CLI |
|
|
99
|
+
| [`@kawaijs/ast`](https://www.npmjs.com/package/@kawaijs/ast) | Syntax AST & Story IR type definitions |
|
|
100
|
+
| [`@kawaijs/parser`](https://www.npmjs.com/package/@kawaijs/parser) | Indentation Lexer, Parser & Diagnostic error reporter |
|
|
101
|
+
| [`@kawaijs/runtime`](https://www.npmjs.com/package/@kawaijs/runtime) | Headless Story VM, rollback engine & persistence |
|
|
102
|
+
| [`@kawaijs/renderer-dom`](https://www.npmjs.com/package/@kawaijs/renderer-dom) | Semantic DOM renderer & CSS design system |
|
|
103
|
+
| [`@kawaijs/cli`](https://www.npmjs.com/package/@kawaijs/cli) | Dedicated CLI binary package |
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## 📜 License
|
|
108
|
+
|
|
109
|
+
MIT License © 2026 [Biagio Scaglia](https://github.com/biagio-scaglia)
|
|
@@ -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,CAmXlF"}
|
|
@@ -0,0 +1,384 @@
|
|
|
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
|
+
}
|
|
97
|
+
function evaluateCondition(cond, vars) {
|
|
98
|
+
const t = (cond || '').trim();
|
|
99
|
+
if (!t || t === 'true') return true;
|
|
100
|
+
if (t === 'false') return false;
|
|
101
|
+
const ops = ['>=', '<=', '!=', '==', '>', '<'];
|
|
102
|
+
for (const op of ops) {
|
|
103
|
+
const idx = t.indexOf(op);
|
|
104
|
+
if (idx !== -1) {
|
|
105
|
+
const l = resolveVal(t.slice(0, idx), vars);
|
|
106
|
+
const r = resolveVal(t.slice(idx + op.length), vars);
|
|
107
|
+
if (op === '>=') return Number(l) >= Number(r);
|
|
108
|
+
if (op === '<=') return Number(l) <= Number(r);
|
|
109
|
+
if (op === '>') return Number(l) > Number(r);
|
|
110
|
+
if (op === '<') return Number(l) < Number(r);
|
|
111
|
+
if (op === '==') return l == r;
|
|
112
|
+
if (op === '!=') return l != r;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (t.startsWith('!')) return !Boolean(vars[t.slice(1).trim()]);
|
|
116
|
+
return Boolean(vars[t]);
|
|
117
|
+
}
|
|
118
|
+
function resolveVal(token, vars) {
|
|
119
|
+
const t = token.trim();
|
|
120
|
+
if (t === 'true') return true;
|
|
121
|
+
if (t === 'false') return false;
|
|
122
|
+
if (!isNaN(Number(t)) && t !== '') return Number(t);
|
|
123
|
+
if ((t.startsWith('"') && t.endsWith('"')) || (t.startsWith("'") && t.endsWith("'"))) return t.slice(1, -1);
|
|
124
|
+
if (t in vars) return vars[t];
|
|
125
|
+
return t;
|
|
126
|
+
}
|
|
127
|
+
function applySetOp(curr, op, val, vars) {
|
|
128
|
+
const res = (typeof val === 'string' && val in vars) ? vars[val] : val;
|
|
129
|
+
if (op === '+=') return Number(curr || 0) + Number(res);
|
|
130
|
+
if (op === '-=') return Number(curr || 0) - Number(res);
|
|
131
|
+
return res;
|
|
132
|
+
}
|
|
133
|
+
class StoryVM {
|
|
134
|
+
constructor(story) {
|
|
135
|
+
this.story = story;
|
|
136
|
+
this.state = {
|
|
137
|
+
currentLabel: story.meta.startLabel || 'start',
|
|
138
|
+
instructionPointer: 0,
|
|
139
|
+
callStack: [],
|
|
140
|
+
variables: {},
|
|
141
|
+
visual: { background: null, transition: null, characters: {} },
|
|
142
|
+
audio: { music: null, voice: null },
|
|
143
|
+
dialogue: null,
|
|
144
|
+
choices: null,
|
|
145
|
+
isWaitingForInput: false,
|
|
146
|
+
isFinished: false
|
|
147
|
+
};
|
|
148
|
+
this.snapshotStack = [];
|
|
149
|
+
this.listeners = new Set();
|
|
150
|
+
this.saveManager = new SaveManager();
|
|
151
|
+
}
|
|
152
|
+
getState() { return this.state; }
|
|
153
|
+
onStateChange(cb) { this.listeners.add(cb); return () => this.listeners.delete(cb); }
|
|
154
|
+
start() { this.execute(); }
|
|
155
|
+
next() {
|
|
156
|
+
if (this.state.isFinished || (this.state.choices && this.state.choices.length > 0)) return;
|
|
157
|
+
this.state.isWaitingForInput = false;
|
|
158
|
+
this.execute();
|
|
159
|
+
}
|
|
160
|
+
choose(idx) {
|
|
161
|
+
if (!this.state.choices || !this.state.choices[idx]) return;
|
|
162
|
+
const choice = this.state.choices[idx];
|
|
163
|
+
this.state.choices = null;
|
|
164
|
+
this.state.isWaitingForInput = false;
|
|
165
|
+
this.state.currentLabel = choice.targetLabel;
|
|
166
|
+
this.state.instructionPointer = 0;
|
|
167
|
+
this.execute();
|
|
168
|
+
}
|
|
169
|
+
rollback() {
|
|
170
|
+
if (this.snapshotStack.length <= 1) return false;
|
|
171
|
+
this.snapshotStack.pop();
|
|
172
|
+
const prev = this.snapshotStack[this.snapshotStack.length - 1];
|
|
173
|
+
if (prev) {
|
|
174
|
+
this.state = JSON.parse(JSON.stringify(prev));
|
|
175
|
+
this.notify();
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
canRollback() { return this.snapshotStack.length > 1; }
|
|
181
|
+
async save(slot) {
|
|
182
|
+
await this.saveManager.saveSlot(slot, this.state, this.state.dialogue ? this.state.dialogue.text : '');
|
|
183
|
+
}
|
|
184
|
+
async load(slot) {
|
|
185
|
+
const data = await this.saveManager.loadSlot(slot);
|
|
186
|
+
if (data && data.snapshot) {
|
|
187
|
+
this.state = JSON.parse(JSON.stringify(data.snapshot));
|
|
188
|
+
this.snapshotStack = [this.state];
|
|
189
|
+
this.notify();
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
execute() {
|
|
195
|
+
while (!this.state.isWaitingForInput && !this.state.isFinished) {
|
|
196
|
+
const list = this.story.labels[this.state.currentLabel];
|
|
197
|
+
if (!list || this.state.instructionPointer >= list.length) {
|
|
198
|
+
if (this.state.callStack.length > 0) {
|
|
199
|
+
const frame = this.state.callStack.pop();
|
|
200
|
+
this.state.currentLabel = frame.returnLabel;
|
|
201
|
+
this.state.instructionPointer = frame.returnPointer;
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
this.state.isFinished = true;
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
const inst = list[this.state.instructionPointer++];
|
|
208
|
+
if (inst.type === 'scene') {
|
|
209
|
+
this.state.visual.background = inst.background;
|
|
210
|
+
this.state.visual.characters = {};
|
|
211
|
+
} else if (inst.type === 'show') {
|
|
212
|
+
this.state.visual.characters[inst.character] = {
|
|
213
|
+
expression: inst.expression,
|
|
214
|
+
position: inst.position || 'center'
|
|
215
|
+
};
|
|
216
|
+
} else if (inst.type === 'hide') {
|
|
217
|
+
delete this.state.visual.characters[inst.character];
|
|
218
|
+
} else if (inst.type === 'dialogue') {
|
|
219
|
+
const charDef = inst.speaker ? this.story.characters[inst.speaker] : null;
|
|
220
|
+
this.state.dialogue = {
|
|
221
|
+
speaker: inst.speaker,
|
|
222
|
+
speakerDisplayName: charDef ? charDef.name : inst.speaker,
|
|
223
|
+
speakerColor: charDef ? charDef.color : null,
|
|
224
|
+
text: inst.text
|
|
225
|
+
};
|
|
226
|
+
this.state.isWaitingForInput = true;
|
|
227
|
+
} else if (inst.type === 'choice') {
|
|
228
|
+
this.state.choices = inst.choices;
|
|
229
|
+
this.state.isWaitingForInput = true;
|
|
230
|
+
} else if (inst.type === 'jump') {
|
|
231
|
+
this.state.currentLabel = inst.targetLabel;
|
|
232
|
+
this.state.instructionPointer = 0;
|
|
233
|
+
} else if (inst.type === 'set') {
|
|
234
|
+
const curr = this.state.variables[inst.variable];
|
|
235
|
+
this.state.variables[inst.variable] = applySetOp(curr, inst.operator, inst.value, this.state.variables);
|
|
236
|
+
} else if (inst.type === 'branch') {
|
|
237
|
+
const ok = evaluateCondition(inst.condition, this.state.variables);
|
|
238
|
+
this.state.currentLabel = ok ? inst.thenLabel : (inst.elseLabel || inst.thenLabel);
|
|
239
|
+
this.state.instructionPointer = 0;
|
|
240
|
+
} else if (inst.type === 'return') {
|
|
241
|
+
this.state.isFinished = true;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (this.state.isWaitingForInput) {
|
|
245
|
+
this.snapshotStack.push(JSON.parse(JSON.stringify(this.state)));
|
|
246
|
+
}
|
|
247
|
+
this.notify();
|
|
248
|
+
}
|
|
249
|
+
notify() { for (const l of this.listeners) l(this.state); }
|
|
250
|
+
}
|
|
251
|
+
class DOMRenderer {
|
|
252
|
+
constructor(vm, container) {
|
|
253
|
+
this.vm = vm;
|
|
254
|
+
this.container = container;
|
|
255
|
+
this.build();
|
|
256
|
+
vm.onStateChange(s => this.render(s));
|
|
257
|
+
this.render(vm.getState());
|
|
258
|
+
}
|
|
259
|
+
build() {
|
|
260
|
+
this.container.innerHTML = \`
|
|
261
|
+
<div class="kawa-root">
|
|
262
|
+
<div class="kawa-stage">
|
|
263
|
+
<div class="kawa-background"></div>
|
|
264
|
+
<div class="kawa-characters kawa-sprites"></div>
|
|
265
|
+
<div class="kawa-ui-layer">
|
|
266
|
+
<div class="kawa-choice-container kawa-choices" style="display:none"></div>
|
|
267
|
+
<div class="kawa-dialogue-box kawa-dialogue">
|
|
268
|
+
<div class="kawa-speaker-tag kawa-speaker" style="display:none"></div>
|
|
269
|
+
<div class="kawa-dialogue-text kawa-text"></div>
|
|
270
|
+
<div class="kawa-continue-indicator">▼</div>
|
|
271
|
+
</div>
|
|
272
|
+
<nav class="kawa-quick-menu">
|
|
273
|
+
<button class="kawa-btn kawa-back">Back</button>
|
|
274
|
+
<button class="kawa-btn kawa-save">Save</button>
|
|
275
|
+
<button class="kawa-btn kawa-load">Load</button>
|
|
276
|
+
</nav>
|
|
277
|
+
</div>
|
|
278
|
+
</div>
|
|
279
|
+
</div>\`;
|
|
280
|
+
this.bgEl = this.container.querySelector('.kawa-background');
|
|
281
|
+
this.charsEl = this.container.querySelector('.kawa-characters');
|
|
282
|
+
this.boxEl = this.container.querySelector('.kawa-dialogue-box');
|
|
283
|
+
this.spkEl = this.container.querySelector('.kawa-speaker-tag');
|
|
284
|
+
this.txtEl = this.container.querySelector('.kawa-dialogue-text');
|
|
285
|
+
this.choiceEl = this.container.querySelector('.kawa-choice-container');
|
|
286
|
+
this.backBtn = this.container.querySelector('.kawa-back');
|
|
287
|
+
this.saveBtn = this.container.querySelector('.kawa-save');
|
|
288
|
+
this.loadBtn = this.container.querySelector('.kawa-load');
|
|
289
|
+
|
|
290
|
+
this.boxEl.addEventListener('click', () => this.vm.next());
|
|
291
|
+
this.backBtn.addEventListener('click', (e) => { e.stopPropagation(); this.vm.rollback(); });
|
|
292
|
+
this.saveBtn.addEventListener('click', async (e) => { e.stopPropagation(); await this.vm.save('1'); alert('Saved to Slot 1'); });
|
|
293
|
+
this.loadBtn.addEventListener('click', async (e) => { e.stopPropagation(); if (await this.vm.load('1')) alert('Loaded Slot 1'); });
|
|
294
|
+
window.addEventListener('keydown', (e) => {
|
|
295
|
+
if (e.code === 'Space' || e.code === 'Enter') { e.preventDefault(); this.vm.next(); }
|
|
296
|
+
if (e.code === 'Backspace') { e.preventDefault(); this.vm.rollback(); }
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
render(state) {
|
|
300
|
+
this.backBtn.disabled = !this.vm.canRollback();
|
|
301
|
+
if (state.visual.background) {
|
|
302
|
+
const bg = state.visual.background.replace(/^bg\\s+/, '');
|
|
303
|
+
this.bgEl.style.backgroundImage = 'url("./assets/backgrounds/' + bg + (bg.includes('.') ? '' : '.png') + '")';
|
|
304
|
+
this.bgEl.style.opacity = '1';
|
|
305
|
+
} else {
|
|
306
|
+
this.bgEl.style.opacity = '0';
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
this.charsEl.innerHTML = '';
|
|
310
|
+
for (const [id, char] of Object.entries(state.visual.characters)) {
|
|
311
|
+
const div = document.createElement('div');
|
|
312
|
+
div.className = 'kawa-sprite kawa-pos-' + (char.position || 'center');
|
|
313
|
+
const img = document.createElement('img');
|
|
314
|
+
const expr = char.expression ? '/' + char.expression : '';
|
|
315
|
+
img.src = './assets/characters/' + id + expr + '.png';
|
|
316
|
+
img.alt = id;
|
|
317
|
+
img.onerror = () => {
|
|
318
|
+
img.style.display = 'none';
|
|
319
|
+
div.style.width = '200px'; div.style.height = '400px';
|
|
320
|
+
div.style.background = 'rgba(244,63,94,0.3)'; div.style.border = '2px dashed #f43f5e';
|
|
321
|
+
div.style.borderRadius = '16px'; div.style.display = 'flex'; div.style.alignItems = 'center';
|
|
322
|
+
div.style.justifyContent = 'center'; div.style.color = '#fff';
|
|
323
|
+
div.textContent = id + (char.expression ? ' (' + char.expression + ')' : '');
|
|
324
|
+
};
|
|
325
|
+
div.appendChild(img);
|
|
326
|
+
this.charsEl.appendChild(div);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (state.dialogue) {
|
|
330
|
+
this.boxEl.style.display = 'block';
|
|
331
|
+
if (state.dialogue.speakerDisplayName) {
|
|
332
|
+
this.spkEl.style.display = 'inline-block';
|
|
333
|
+
this.spkEl.textContent = state.dialogue.speakerDisplayName;
|
|
334
|
+
if (state.dialogue.speakerColor) this.spkEl.style.backgroundColor = state.dialogue.speakerColor;
|
|
335
|
+
} else {
|
|
336
|
+
this.spkEl.style.display = 'none';
|
|
337
|
+
}
|
|
338
|
+
this.txtEl.textContent = state.dialogue.text;
|
|
339
|
+
} else {
|
|
340
|
+
this.boxEl.style.display = 'none';
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (state.choices && state.choices.length > 0) {
|
|
344
|
+
this.choiceEl.innerHTML = '';
|
|
345
|
+
this.choiceEl.style.display = 'flex';
|
|
346
|
+
state.choices.forEach((c, idx) => {
|
|
347
|
+
const btn = document.createElement('button');
|
|
348
|
+
btn.className = 'kawa-choice-btn kawa-choice';
|
|
349
|
+
btn.textContent = c.text;
|
|
350
|
+
btn.addEventListener('click', (e) => { e.stopPropagation(); this.vm.choose(idx); });
|
|
351
|
+
this.choiceEl.appendChild(btn);
|
|
352
|
+
});
|
|
353
|
+
} else {
|
|
354
|
+
this.choiceEl.style.display = 'none';
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const vm = new StoryVM(story);
|
|
360
|
+
new DOMRenderer(vm, document.getElementById('app'));
|
|
361
|
+
vm.start();
|
|
362
|
+
</script>
|
|
363
|
+
</body>
|
|
364
|
+
</html>`;
|
|
365
|
+
fs.writeFileSync(path.join(outDir, 'index.html'), htmlContent, 'utf-8');
|
|
366
|
+
console.log(`✅ Static web application generated: dist/index.html`);
|
|
367
|
+
console.log(`\n🎉 Production build complete! Ready for GitHub Pages, Netlify, itch.io, etc.\n`);
|
|
368
|
+
return true;
|
|
369
|
+
}
|
|
370
|
+
function copyDirectoryRecursive(src, dest) {
|
|
371
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
372
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
373
|
+
for (const entry of entries) {
|
|
374
|
+
const srcPath = path.join(src, entry.name);
|
|
375
|
+
const destPath = path.join(dest, entry.name);
|
|
376
|
+
if (entry.isDirectory()) {
|
|
377
|
+
copyDirectoryRecursive(srcPath, destPath);
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
fs.copyFileSync(srcPath, destPath);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
//# 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"}
|
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
import * as http from 'node:http';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import { compileScript, formatDiagnostic, KawaError } from '@kawaijs/parser';
|
|
5
|
+
export function startDevServer(projectDir = '.', options = {}) {
|
|
6
|
+
const rootDir = path.resolve(process.cwd(), projectDir);
|
|
7
|
+
const scriptPath = path.join(rootDir, 'game', 'script.kawa');
|
|
8
|
+
const stylePath = path.join(rootDir, 'game', 'style.css');
|
|
9
|
+
const assetsDir = path.join(rootDir, 'game', 'assets');
|
|
10
|
+
let port = options.port ?? 3000;
|
|
11
|
+
if (!fs.existsSync(scriptPath)) {
|
|
12
|
+
console.error(`❌ Error: Cannot find '${scriptPath}'. Make sure you are inside a Kawaijs project directory.`);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const clients = new Set();
|
|
16
|
+
// File watcher for auto-reload
|
|
17
|
+
const gameDir = path.join(rootDir, 'game');
|
|
18
|
+
if (fs.existsSync(gameDir)) {
|
|
19
|
+
fs.watch(gameDir, { recursive: true }, (_eventType, filename) => {
|
|
20
|
+
if (filename && (filename.endsWith('.kawa') || filename.endsWith('.css') || filename.startsWith('assets'))) {
|
|
21
|
+
console.log(`🔄 [Kawa Dev] File changed: ${filename}. Reloading...`);
|
|
22
|
+
for (const client of clients) {
|
|
23
|
+
client.write(`data: reload\n\n`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
const server = http.createServer((req, res) => {
|
|
29
|
+
const url = req.url ?? '/';
|
|
30
|
+
// 1. SSE Live Reload Endpoint
|
|
31
|
+
if (url === '/__kawa_reload') {
|
|
32
|
+
res.writeHead(200, {
|
|
33
|
+
'Content-Type': 'text/event-stream',
|
|
34
|
+
'Cache-Control': 'no-cache',
|
|
35
|
+
'Connection': 'keep-alive',
|
|
36
|
+
'Access-Control-Allow-Origin': '*'
|
|
37
|
+
});
|
|
38
|
+
clients.add(res);
|
|
39
|
+
req.on('close', () => clients.delete(res));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
// 2. Dynamic Story JSON API
|
|
43
|
+
if (url === '/api/story.json') {
|
|
44
|
+
try {
|
|
45
|
+
const source = fs.readFileSync(scriptPath, 'utf-8');
|
|
46
|
+
const story = compileScript(source, path.basename(scriptPath));
|
|
47
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
48
|
+
res.end(JSON.stringify(story));
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
52
|
+
let errMsg = 'Compilation Error';
|
|
53
|
+
if (err instanceof KawaError) {
|
|
54
|
+
const source = fs.readFileSync(scriptPath, 'utf-8');
|
|
55
|
+
errMsg = formatDiagnostic(err.diagnostic, source);
|
|
56
|
+
}
|
|
57
|
+
else if (err instanceof Error) {
|
|
58
|
+
errMsg = err.message;
|
|
59
|
+
}
|
|
60
|
+
res.end(JSON.stringify({ error: errMsg }));
|
|
61
|
+
}
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
// 3. User & Default Stylesheet
|
|
65
|
+
if (url === '/style.css') {
|
|
66
|
+
res.writeHead(200, { 'Content-Type': 'text/css' });
|
|
67
|
+
let combinedCss = `/* Kawaijs Base Theme */\n`;
|
|
68
|
+
// Load default renderer theme CSS
|
|
69
|
+
const themeCssPath = path.resolve(path.dirname(import.meta.url.replace('file:///', '')), '..', '..', '..', 'renderer-dom', 'src', 'theme.css');
|
|
70
|
+
if (fs.existsSync(themeCssPath)) {
|
|
71
|
+
combinedCss += fs.readFileSync(themeCssPath, 'utf-8') + '\n';
|
|
72
|
+
}
|
|
73
|
+
if (fs.existsSync(stylePath)) {
|
|
74
|
+
combinedCss += `/* User Custom Styles */\n` + fs.readFileSync(stylePath, 'utf-8');
|
|
75
|
+
}
|
|
76
|
+
res.end(combinedCss);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
// 4. Game Assets (/assets/*)
|
|
80
|
+
if (url.startsWith('/assets/')) {
|
|
81
|
+
const relPath = decodeURIComponent(url.replace('/assets/', ''));
|
|
82
|
+
let filePath = path.join(assetsDir, relPath);
|
|
83
|
+
if (!fs.existsSync(filePath)) {
|
|
84
|
+
// Try fallback extensions (.svg, .png, .webp, .jpg, .jpeg, .mp3, .ogg)
|
|
85
|
+
const parsed = path.parse(filePath);
|
|
86
|
+
for (const ext of ['.svg', '.png', '.webp', '.jpg', '.jpeg', '.mp3', '.ogg']) {
|
|
87
|
+
const candidate = path.join(parsed.dir, parsed.name + ext);
|
|
88
|
+
if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
|
|
89
|
+
filePath = candidate;
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
|
|
95
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
96
|
+
const mimeTypes = {
|
|
97
|
+
'.png': 'image/png',
|
|
98
|
+
'.jpg': 'image/jpeg',
|
|
99
|
+
'.jpeg': 'image/jpeg',
|
|
100
|
+
'.webp': 'image/webp',
|
|
101
|
+
'.svg': 'image/svg+xml',
|
|
102
|
+
'.mp3': 'audio/mpeg',
|
|
103
|
+
'.ogg': 'audio/ogg',
|
|
104
|
+
'.wav': 'audio/wav'
|
|
105
|
+
};
|
|
106
|
+
res.writeHead(200, { 'Content-Type': mimeTypes[ext] ?? 'application/octet-stream' });
|
|
107
|
+
fs.createReadStream(filePath).pipe(res);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
112
|
+
res.end('Asset not found');
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// 5. HTML Shell & Web Runtime
|
|
117
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
118
|
+
res.end(`<!DOCTYPE html>
|
|
119
|
+
<html lang="en">
|
|
120
|
+
<head>
|
|
121
|
+
<meta charset="UTF-8">
|
|
122
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
123
|
+
<title>Kawaijs Visual Novel</title>
|
|
124
|
+
<link rel="stylesheet" href="/style.css">
|
|
125
|
+
<style>
|
|
126
|
+
body { margin: 0; padding: 0; background: #000; overflow: hidden; }
|
|
127
|
+
#error-overlay {
|
|
128
|
+
display: none; position: fixed; inset: 0; background: rgba(15, 23, 42, 0.95);
|
|
129
|
+
color: #f43f5e; font-family: monospace; padding: 32px; z-index: 9999;
|
|
130
|
+
white-space: pre-wrap; font-size: 1.1rem; line-height: 1.5;
|
|
131
|
+
}
|
|
132
|
+
</style>
|
|
133
|
+
</head>
|
|
134
|
+
<body>
|
|
135
|
+
<div id="error-overlay"></div>
|
|
136
|
+
<div id="app"></div>
|
|
137
|
+
|
|
138
|
+
<script type="module">
|
|
139
|
+
// Live Reload Connection
|
|
140
|
+
const sse = new EventSource('/__kawa_reload');
|
|
141
|
+
sse.onmessage = (e) => {
|
|
142
|
+
if (e.data === 'reload') window.location.reload();
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// Load story and start game
|
|
146
|
+
async function init() {
|
|
147
|
+
const errorEl = document.getElementById('error-overlay');
|
|
148
|
+
try {
|
|
149
|
+
const res = await fetch('/api/story.json');
|
|
150
|
+
const story = await res.json();
|
|
151
|
+
if (story.error) {
|
|
152
|
+
errorEl.style.display = 'block';
|
|
153
|
+
errorEl.textContent = story.error;
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Inline runtime VM + DOM Renderer
|
|
158
|
+
${getInlineRuntimeScript()}
|
|
159
|
+
|
|
160
|
+
window.__kawa_app = mountKawaApp(story, document.getElementById('app'));
|
|
161
|
+
} catch (err) {
|
|
162
|
+
errorEl.style.display = 'block';
|
|
163
|
+
errorEl.textContent = err.stack || err.message;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
init();
|
|
168
|
+
</script>
|
|
169
|
+
</body>
|
|
170
|
+
</html>`);
|
|
171
|
+
});
|
|
172
|
+
server.on('error', (e) => {
|
|
173
|
+
if (e.code === 'EADDRINUSE') {
|
|
174
|
+
console.log(`Port ${port} is in use, trying ${port + 1}...`);
|
|
175
|
+
port += 1;
|
|
176
|
+
server.listen(port);
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
console.error('Server error:', e);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
server.listen(port, () => {
|
|
183
|
+
console.log(`\n🌸 Kawaijs Dev Server running at:`);
|
|
184
|
+
console.log(` > Local: \x1b[36mhttp://localhost:${port}\x1b[0m`);
|
|
185
|
+
console.log(` > Project: ${rootDir}`);
|
|
186
|
+
console.log(` > Watching: game/script.kawa, game/style.css, game/assets/\n`);
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
function getInlineRuntimeScript() {
|
|
190
|
+
return `
|
|
191
|
+
class MemoryStorageAdapter {
|
|
192
|
+
constructor() { this.store = new Map(); }
|
|
193
|
+
getItem(k) { return this.store.get(k) || null; }
|
|
194
|
+
setItem(k, v) { this.store.set(k, v); }
|
|
195
|
+
removeItem(k) { this.store.delete(k); }
|
|
196
|
+
}
|
|
197
|
+
class LocalStorageAdapter {
|
|
198
|
+
getItem(k) { return localStorage.getItem(k); }
|
|
199
|
+
setItem(k, v) { localStorage.setItem(k, v); }
|
|
200
|
+
removeItem(k) { localStorage.removeItem(k); }
|
|
201
|
+
}
|
|
202
|
+
class SaveManager {
|
|
203
|
+
constructor() { this.storage = typeof localStorage !== 'undefined' ? new LocalStorageAdapter() : new MemoryStorageAdapter(); }
|
|
204
|
+
async saveSlot(id, snapshot, previewText) {
|
|
205
|
+
const slot = { id, name: 'Slot ' + id, timestamp: Date.now(), snapshot, previewText };
|
|
206
|
+
this.storage.setItem('kawaijs_save_' + id, JSON.stringify(slot));
|
|
207
|
+
return slot;
|
|
208
|
+
}
|
|
209
|
+
async loadSlot(id) {
|
|
210
|
+
const raw = this.storage.getItem('kawaijs_save_' + id);
|
|
211
|
+
return raw ? JSON.parse(raw) : null;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
function evaluateCondition(cond, vars) {
|
|
215
|
+
const t = (cond || '').trim();
|
|
216
|
+
if (!t || t === 'true') return true;
|
|
217
|
+
if (t === 'false') return false;
|
|
218
|
+
const ops = ['>=', '<=', '!=', '==', '>', '<'];
|
|
219
|
+
for (const op of ops) {
|
|
220
|
+
const idx = t.indexOf(op);
|
|
221
|
+
if (idx !== -1) {
|
|
222
|
+
const l = resolveVal(t.slice(0, idx), vars);
|
|
223
|
+
const r = resolveVal(t.slice(idx + op.length), vars);
|
|
224
|
+
if (op === '>=') return Number(l) >= Number(r);
|
|
225
|
+
if (op === '<=') return Number(l) <= Number(r);
|
|
226
|
+
if (op === '>') return Number(l) > Number(r);
|
|
227
|
+
if (op === '<') return Number(l) < Number(r);
|
|
228
|
+
if (op === '==') return l == r;
|
|
229
|
+
if (op === '!=') return l != r;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (t.startsWith('!')) return !Boolean(vars[t.slice(1).trim()]);
|
|
233
|
+
return Boolean(vars[t]);
|
|
234
|
+
}
|
|
235
|
+
function resolveVal(token, vars) {
|
|
236
|
+
const t = token.trim();
|
|
237
|
+
if (t === 'true') return true;
|
|
238
|
+
if (t === 'false') return false;
|
|
239
|
+
if (!isNaN(Number(t)) && t !== '') return Number(t);
|
|
240
|
+
if ((t.startsWith('"') && t.endsWith('"')) || (t.startsWith("'") && t.endsWith("'"))) return t.slice(1, -1);
|
|
241
|
+
if (t in vars) return vars[t];
|
|
242
|
+
return t;
|
|
243
|
+
}
|
|
244
|
+
function applySetOp(curr, op, val, vars) {
|
|
245
|
+
const res = (typeof val === 'string' && val in vars) ? vars[val] : val;
|
|
246
|
+
if (op === '+=') return Number(curr || 0) + Number(res);
|
|
247
|
+
if (op === '-=') return Number(curr || 0) - Number(res);
|
|
248
|
+
return res;
|
|
249
|
+
}
|
|
250
|
+
class StoryVM {
|
|
251
|
+
constructor(story) {
|
|
252
|
+
this.story = story;
|
|
253
|
+
this.state = {
|
|
254
|
+
currentLabel: story.meta.startLabel || 'start',
|
|
255
|
+
instructionPointer: 0,
|
|
256
|
+
callStack: [],
|
|
257
|
+
variables: {},
|
|
258
|
+
visual: { background: null, transition: null, characters: {} },
|
|
259
|
+
audio: { music: null, voice: null },
|
|
260
|
+
dialogue: null,
|
|
261
|
+
choices: null,
|
|
262
|
+
isWaitingForInput: false,
|
|
263
|
+
isFinished: false
|
|
264
|
+
};
|
|
265
|
+
this.snapshotStack = [];
|
|
266
|
+
this.listeners = new Set();
|
|
267
|
+
this.history = [];
|
|
268
|
+
this.saveManager = new SaveManager();
|
|
269
|
+
}
|
|
270
|
+
getState() { return this.state; }
|
|
271
|
+
onStateChange(cb) { this.listeners.add(cb); return () => this.listeners.delete(cb); }
|
|
272
|
+
start() { this.execute(); }
|
|
273
|
+
next() {
|
|
274
|
+
if (this.state.isFinished || (this.state.choices && this.state.choices.length > 0)) return;
|
|
275
|
+
this.state.isWaitingForInput = false;
|
|
276
|
+
this.execute();
|
|
277
|
+
}
|
|
278
|
+
choose(idx) {
|
|
279
|
+
if (!this.state.choices || !this.state.choices[idx]) return;
|
|
280
|
+
const choice = this.state.choices[idx];
|
|
281
|
+
this.state.choices = null;
|
|
282
|
+
this.state.isWaitingForInput = false;
|
|
283
|
+
this.state.currentLabel = choice.targetLabel;
|
|
284
|
+
this.state.instructionPointer = 0;
|
|
285
|
+
this.execute();
|
|
286
|
+
}
|
|
287
|
+
rollback() {
|
|
288
|
+
if (this.snapshotStack.length <= 1) return false;
|
|
289
|
+
this.snapshotStack.pop();
|
|
290
|
+
const prev = this.snapshotStack[this.snapshotStack.length - 1];
|
|
291
|
+
if (prev) {
|
|
292
|
+
this.state = JSON.parse(JSON.stringify(prev));
|
|
293
|
+
this.notify();
|
|
294
|
+
return true;
|
|
295
|
+
}
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
canRollback() { return this.snapshotStack.length > 1; }
|
|
299
|
+
async save(slot) {
|
|
300
|
+
await this.saveManager.saveSlot(slot, this.state, this.state.dialogue ? this.state.dialogue.text : '');
|
|
301
|
+
}
|
|
302
|
+
async load(slot) {
|
|
303
|
+
const data = await this.saveManager.loadSlot(slot);
|
|
304
|
+
if (data && data.snapshot) {
|
|
305
|
+
this.state = JSON.parse(JSON.stringify(data.snapshot));
|
|
306
|
+
this.snapshotStack = [this.state];
|
|
307
|
+
this.notify();
|
|
308
|
+
return true;
|
|
309
|
+
}
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
execute() {
|
|
313
|
+
while (!this.state.isWaitingForInput && !this.state.isFinished) {
|
|
314
|
+
const list = this.story.labels[this.state.currentLabel];
|
|
315
|
+
if (!list || this.state.instructionPointer >= list.length) {
|
|
316
|
+
if (this.state.callStack.length > 0) {
|
|
317
|
+
const frame = this.state.callStack.pop();
|
|
318
|
+
this.state.currentLabel = frame.returnLabel;
|
|
319
|
+
this.state.instructionPointer = frame.returnPointer;
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
this.state.isFinished = true;
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
const inst = list[this.state.instructionPointer++];
|
|
326
|
+
if (inst.type === 'scene') {
|
|
327
|
+
this.state.visual.background = inst.background;
|
|
328
|
+
this.state.visual.characters = {};
|
|
329
|
+
} else if (inst.type === 'show') {
|
|
330
|
+
this.state.visual.characters[inst.character] = {
|
|
331
|
+
expression: inst.expression,
|
|
332
|
+
position: inst.position || 'center'
|
|
333
|
+
};
|
|
334
|
+
} else if (inst.type === 'hide') {
|
|
335
|
+
delete this.state.visual.characters[inst.character];
|
|
336
|
+
} else if (inst.type === 'dialogue') {
|
|
337
|
+
const charDef = inst.speaker ? this.story.characters[inst.speaker] : null;
|
|
338
|
+
this.state.dialogue = {
|
|
339
|
+
speaker: inst.speaker,
|
|
340
|
+
speakerDisplayName: charDef ? charDef.name : inst.speaker,
|
|
341
|
+
speakerColor: charDef ? charDef.color : null,
|
|
342
|
+
text: inst.text
|
|
343
|
+
};
|
|
344
|
+
this.state.isWaitingForInput = true;
|
|
345
|
+
this.history.push(this.state.dialogue);
|
|
346
|
+
} else if (inst.type === 'choice') {
|
|
347
|
+
this.state.choices = inst.choices;
|
|
348
|
+
this.state.isWaitingForInput = true;
|
|
349
|
+
} else if (inst.type === 'jump') {
|
|
350
|
+
this.state.currentLabel = inst.targetLabel;
|
|
351
|
+
this.state.instructionPointer = 0;
|
|
352
|
+
} else if (inst.type === 'set') {
|
|
353
|
+
const curr = this.state.variables[inst.variable];
|
|
354
|
+
this.state.variables[inst.variable] = applySetOp(curr, inst.operator, inst.value, this.state.variables);
|
|
355
|
+
} else if (inst.type === 'branch') {
|
|
356
|
+
const ok = evaluateCondition(inst.condition, this.state.variables);
|
|
357
|
+
this.state.currentLabel = ok ? inst.thenLabel : (inst.elseLabel || inst.thenLabel);
|
|
358
|
+
this.state.instructionPointer = 0;
|
|
359
|
+
} else if (inst.type === 'return') {
|
|
360
|
+
this.state.isFinished = true;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
if (this.state.isWaitingForInput) {
|
|
364
|
+
this.snapshotStack.push(JSON.parse(JSON.stringify(this.state)));
|
|
365
|
+
}
|
|
366
|
+
this.notify();
|
|
367
|
+
}
|
|
368
|
+
notify() { for (const l of this.listeners) l(this.state); }
|
|
369
|
+
}
|
|
370
|
+
class DOMRenderer {
|
|
371
|
+
constructor(vm, container) {
|
|
372
|
+
this.vm = vm;
|
|
373
|
+
this.container = container;
|
|
374
|
+
this.build();
|
|
375
|
+
vm.onStateChange(s => this.render(s));
|
|
376
|
+
this.render(vm.getState());
|
|
377
|
+
}
|
|
378
|
+
build() {
|
|
379
|
+
this.container.innerHTML = \`
|
|
380
|
+
<div class="kawa-root">
|
|
381
|
+
<div class="kawa-stage">
|
|
382
|
+
<div class="kawa-background"></div>
|
|
383
|
+
<div class="kawa-characters kawa-sprites"></div>
|
|
384
|
+
<div class="kawa-ui-layer">
|
|
385
|
+
<div class="kawa-choice-container kawa-choices" style="display:none"></div>
|
|
386
|
+
<div class="kawa-dialogue-box kawa-dialogue">
|
|
387
|
+
<div class="kawa-speaker-tag kawa-speaker" style="display:none"></div>
|
|
388
|
+
<div class="kawa-dialogue-text kawa-text"></div>
|
|
389
|
+
<div class="kawa-continue-indicator">▼</div>
|
|
390
|
+
</div>
|
|
391
|
+
<nav class="kawa-quick-menu">
|
|
392
|
+
<button class="kawa-btn kawa-back">Back</button>
|
|
393
|
+
<button class="kawa-btn kawa-save">Save</button>
|
|
394
|
+
<button class="kawa-btn kawa-load">Load</button>
|
|
395
|
+
</nav>
|
|
396
|
+
</div>
|
|
397
|
+
</div>
|
|
398
|
+
</div>\`;
|
|
399
|
+
this.bgEl = this.container.querySelector('.kawa-background');
|
|
400
|
+
this.charsEl = this.container.querySelector('.kawa-characters');
|
|
401
|
+
this.boxEl = this.container.querySelector('.kawa-dialogue-box');
|
|
402
|
+
this.spkEl = this.container.querySelector('.kawa-speaker-tag');
|
|
403
|
+
this.txtEl = this.container.querySelector('.kawa-dialogue-text');
|
|
404
|
+
this.choiceEl = this.container.querySelector('.kawa-choice-container');
|
|
405
|
+
this.backBtn = this.container.querySelector('.kawa-back');
|
|
406
|
+
this.saveBtn = this.container.querySelector('.kawa-save');
|
|
407
|
+
this.loadBtn = this.container.querySelector('.kawa-load');
|
|
408
|
+
|
|
409
|
+
this.boxEl.addEventListener('click', () => this.vm.next());
|
|
410
|
+
this.backBtn.addEventListener('click', (e) => { e.stopPropagation(); this.vm.rollback(); });
|
|
411
|
+
this.saveBtn.addEventListener('click', async (e) => { e.stopPropagation(); await this.vm.save('1'); alert('Saved to Slot 1'); });
|
|
412
|
+
this.loadBtn.addEventListener('click', async (e) => { e.stopPropagation(); if (await this.vm.load('1')) alert('Loaded Slot 1'); });
|
|
413
|
+
window.addEventListener('keydown', (e) => {
|
|
414
|
+
if (e.code === 'Space' || e.code === 'Enter') { e.preventDefault(); this.vm.next(); }
|
|
415
|
+
if (e.code === 'Backspace') { e.preventDefault(); this.vm.rollback(); }
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
render(state) {
|
|
419
|
+
this.backBtn.disabled = !this.vm.canRollback();
|
|
420
|
+
if (state.visual.background) {
|
|
421
|
+
const bg = state.visual.background.replace(/^bg\\s+/, '');
|
|
422
|
+
this.bgEl.style.backgroundImage = 'url("/assets/backgrounds/' + bg + (bg.includes('.') ? '' : '.png') + '")';
|
|
423
|
+
this.bgEl.style.opacity = '1';
|
|
424
|
+
} else {
|
|
425
|
+
this.bgEl.style.opacity = '0';
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
this.charsEl.innerHTML = '';
|
|
429
|
+
for (const [id, char] of Object.entries(state.visual.characters)) {
|
|
430
|
+
const div = document.createElement('div');
|
|
431
|
+
div.className = 'kawa-sprite kawa-pos-' + (char.position || 'center');
|
|
432
|
+
const img = document.createElement('img');
|
|
433
|
+
const expr = char.expression ? '/' + char.expression : '';
|
|
434
|
+
img.src = '/assets/characters/' + id + expr + '.png';
|
|
435
|
+
img.alt = id;
|
|
436
|
+
img.onerror = () => {
|
|
437
|
+
img.style.display = 'none';
|
|
438
|
+
div.style.width = '200px'; div.style.height = '400px';
|
|
439
|
+
div.style.background = 'rgba(244,63,94,0.3)'; div.style.border = '2px dashed #f43f5e';
|
|
440
|
+
div.style.borderRadius = '16px'; div.style.display = 'flex'; div.style.alignItems = 'center';
|
|
441
|
+
div.style.justifyContent = 'center'; div.style.color = '#fff';
|
|
442
|
+
div.textContent = id + (char.expression ? ' (' + char.expression + ')' : '');
|
|
443
|
+
};
|
|
444
|
+
div.appendChild(img);
|
|
445
|
+
this.charsEl.appendChild(div);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (state.dialogue) {
|
|
449
|
+
this.boxEl.style.display = 'block';
|
|
450
|
+
if (state.dialogue.speakerDisplayName) {
|
|
451
|
+
this.spkEl.style.display = 'inline-block';
|
|
452
|
+
this.spkEl.textContent = state.dialogue.speakerDisplayName;
|
|
453
|
+
if (state.dialogue.speakerColor) this.spkEl.style.backgroundColor = state.dialogue.speakerColor;
|
|
454
|
+
} else {
|
|
455
|
+
this.spkEl.style.display = 'none';
|
|
456
|
+
}
|
|
457
|
+
this.txtEl.textContent = state.dialogue.text;
|
|
458
|
+
} else {
|
|
459
|
+
this.boxEl.style.display = 'none';
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if (state.choices && state.choices.length > 0) {
|
|
463
|
+
this.choiceEl.innerHTML = '';
|
|
464
|
+
this.choiceEl.style.display = 'flex';
|
|
465
|
+
state.choices.forEach((c, idx) => {
|
|
466
|
+
const btn = document.createElement('button');
|
|
467
|
+
btn.className = 'kawa-choice-btn kawa-choice';
|
|
468
|
+
btn.textContent = c.text;
|
|
469
|
+
btn.addEventListener('click', (e) => { e.stopPropagation(); this.vm.choose(idx); });
|
|
470
|
+
this.choiceEl.appendChild(btn);
|
|
471
|
+
});
|
|
472
|
+
} else {
|
|
473
|
+
this.choiceEl.style.display = 'none';
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
function mountKawaApp(story, container) {
|
|
478
|
+
const vm = new StoryVM(story);
|
|
479
|
+
const renderer = new DOMRenderer(vm, container);
|
|
480
|
+
vm.start();
|
|
481
|
+
return { vm, renderer };
|
|
482
|
+
}
|
|
483
|
+
`;
|
|
484
|
+
}
|
|
485
|
+
//# sourceMappingURL=dev.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev.js","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAO7E,MAAM,UAAU,cAAc,CAAC,UAAU,GAAG,GAAG,EAAE,UAA4B,EAAE;IAC7E,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,UAAU,CAAC,CAAC;IACxD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,CAAC,CAAC;IAC7D,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC;IAC1D,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;IACvD,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC;IAEhC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC/B,OAAO,CAAC,KAAK,CAAC,yBAAyB,UAAU,0DAA0D,CAAC,CAAC;QAC7G,OAAO;IACT,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,GAAG,EAAuB,CAAC;IAE/C,+BAA+B;IAC/B,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC3C,IAAI,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3B,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,CAAC,UAAU,EAAE,QAAQ,EAAE,EAAE;YAC9D,IAAI,QAAQ,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;gBAC3G,OAAO,CAAC,GAAG,CAAC,+BAA+B,QAAQ,gBAAgB,CAAC,CAAC;gBACrE,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;oBAC7B,MAAM,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC;gBACnC,CAAC;YACH,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QAC5C,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC;QAE3B,8BAA8B;QAC9B,IAAI,GAAG,KAAK,gBAAgB,EAAE,CAAC;YAC7B,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE;gBACjB,cAAc,EAAE,mBAAmB;gBACnC,eAAe,EAAE,UAAU;gBAC3B,YAAY,EAAE,YAAY;gBAC1B,6BAA6B,EAAE,GAAG;aACnC,CAAC,CAAC;YACH,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACjB,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;YAC3C,OAAO;QACT,CAAC;QAED,4BAA4B;QAC5B,IAAI,GAAG,KAAK,iBAAiB,EAAE,CAAC;YAC9B,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;gBACpD,MAAM,KAAK,GAAG,aAAa,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC;gBAC/D,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;gBAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;YACjC,CAAC;YAAC,OAAO,GAAY,EAAE,CAAC;gBACtB,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;gBAC3D,IAAI,MAAM,GAAG,mBAAmB,CAAC;gBACjC,IAAI,GAAG,YAAY,SAAS,EAAE,CAAC;oBAC7B,MAAM,MAAM,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;oBACpD,MAAM,GAAG,gBAAgB,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;gBACpD,CAAC;qBAAM,IAAI,GAAG,YAAY,KAAK,EAAE,CAAC;oBAChC,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC;gBACvB,CAAC;gBACD,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;YAC7C,CAAC;YACD,OAAO;QACT,CAAC;QAED,+BAA+B;QAC/B,IAAI,GAAG,KAAK,YAAY,EAAE,CAAC;YACzB,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,UAAU,EAAE,CAAC,CAAC;YACnD,IAAI,WAAW,GAAG,4BAA4B,CAAC;YAE/C,kCAAkC;YAClC,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC;YAC/I,IAAI,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;gBAChC,WAAW,IAAI,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;YAC/D,CAAC;YAED,IAAI,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;gBAC7B,WAAW,IAAI,4BAA4B,GAAG,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;YACpF,CAAC;YACD,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;YACrB,OAAO;QACT,CAAC;QAED,6BAA6B;QAC7B,IAAI,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC/B,MAAM,OAAO,GAAG,kBAAkB,CAAC,GAAG,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,CAAC;YAChE,IAAI,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;YAE7C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAC7B,uEAAuE;gBACvE,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;gBACpC,KAAK,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;oBAC7E,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC;oBAC3D,IAAI,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC;wBAChE,QAAQ,GAAG,SAAS,CAAC;wBACrB,MAAM;oBACR,CAAC;gBACH,CAAC;YACH,CAAC;YAED,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC;gBAC9D,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC;gBACjD,MAAM,SAAS,GAA2B;oBACxC,MAAM,EAAE,WAAW;oBACnB,MAAM,EAAE,YAAY;oBACpB,OAAO,EAAE,YAAY;oBACrB,OAAO,EAAE,YAAY;oBACrB,MAAM,EAAE,eAAe;oBACvB,MAAM,EAAE,YAAY;oBACpB,MAAM,EAAE,WAAW;oBACnB,MAAM,EAAE,WAAW;iBACpB,CAAC;gBACF,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,SAAS,CAAC,GAAG,CAAC,IAAI,0BAA0B,EAAE,CAAC,CAAC;gBACrF,EAAE,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBACxC,OAAO;YACT,CAAC;iBAAM,CAAC;gBACN,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,YAAY,EAAE,CAAC,CAAC;gBACrD,GAAG,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;gBAC3B,OAAO;YACT,CAAC;QACH,CAAC;QAED,8BAA8B;QAC9B,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,0BAA0B,EAAE,CAAC,CAAC;QACnE,GAAG,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAwCF,sBAAsB,EAAE;;;;;;;;;;;;QAY1B,CAAC,CAAC;IACR,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAwB,EAAE,EAAE;QAC9C,IAAI,CAAC,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;YAC5B,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,sBAAsB,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;YAC7D,IAAI,IAAI,CAAC,CAAC;YACV,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACtB,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,KAAK,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC;QACpC,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;QACvB,OAAO,CAAC,GAAG,CAAC,qCAAqC,CAAC,CAAC;QACnD,OAAO,CAAC,GAAG,CAAC,0CAA0C,IAAI,SAAS,CAAC,CAAC;QACrE,OAAO,CAAC,GAAG,CAAC,iBAAiB,OAAO,EAAE,CAAC,CAAC;QACxC,OAAO,CAAC,GAAG,CAAC,iEAAiE,CAAC,CAAC;IACjF,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,sBAAsB;IAC7B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqSN,CAAC;AACJ,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ export * from '@kawaijs/runtime';
|
|
|
4
4
|
export * from '@kawaijs/renderer-dom';
|
|
5
5
|
import { createProject } from './commands/create.js';
|
|
6
6
|
import { validateProject } from './commands/validate.js';
|
|
7
|
-
|
|
7
|
+
import { startDevServer } from './commands/dev.js';
|
|
8
|
+
import { buildProject } from './commands/build.js';
|
|
9
|
+
export { createProject, validateProject, startDevServer, buildProject };
|
|
8
10
|
export declare function runCLI(args: string[]): void;
|
|
9
11
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AAEtC,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AAEtC,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEnD,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,cAAc,EAAE,YAAY,EAAE,CAAC;AAExE,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAoE3C"}
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,9 @@ export * from '@kawaijs/runtime';
|
|
|
4
4
|
export * from '@kawaijs/renderer-dom';
|
|
5
5
|
import { createProject } from './commands/create.js';
|
|
6
6
|
import { validateProject } from './commands/validate.js';
|
|
7
|
-
|
|
7
|
+
import { startDevServer } from './commands/dev.js';
|
|
8
|
+
import { buildProject } from './commands/build.js';
|
|
9
|
+
export { createProject, validateProject, startDevServer, buildProject };
|
|
8
10
|
export function runCLI(args) {
|
|
9
11
|
const command = args[0];
|
|
10
12
|
switch (command) {
|
|
@@ -13,6 +15,19 @@ export function runCLI(args) {
|
|
|
13
15
|
createProject(projectName);
|
|
14
16
|
break;
|
|
15
17
|
}
|
|
18
|
+
case 'dev': {
|
|
19
|
+
const targetPath = args[1] ?? '.';
|
|
20
|
+
startDevServer(targetPath);
|
|
21
|
+
break;
|
|
22
|
+
}
|
|
23
|
+
case 'build': {
|
|
24
|
+
const targetPath = args[1] ?? '.';
|
|
25
|
+
const ok = buildProject(targetPath);
|
|
26
|
+
if (!ok) {
|
|
27
|
+
process.exitCode = 1;
|
|
28
|
+
}
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
16
31
|
case 'validate': {
|
|
17
32
|
const targetPath = args[1] ?? '.';
|
|
18
33
|
const ok = validateProject(targetPath);
|
|
@@ -24,7 +39,7 @@ export function runCLI(args) {
|
|
|
24
39
|
case 'version':
|
|
25
40
|
case '-v':
|
|
26
41
|
case '--version': {
|
|
27
|
-
console.log('Kawaijs v0.1.
|
|
42
|
+
console.log('Kawaijs v0.1.1');
|
|
28
43
|
break;
|
|
29
44
|
}
|
|
30
45
|
case 'help':
|
|
@@ -40,16 +55,16 @@ Usage:
|
|
|
40
55
|
|
|
41
56
|
Commands:
|
|
42
57
|
create <name> Scaffold a new visual novel project
|
|
58
|
+
dev [path] Start the local development server (with live reload)
|
|
59
|
+
build [path] Build a static production web bundle (dist/)
|
|
43
60
|
validate [path] Validate Kawa Script syntax, labels, and links
|
|
44
|
-
dev Start the local development server (with HMR)
|
|
45
|
-
build Build a static production web bundle
|
|
46
61
|
help Show this help message
|
|
47
62
|
version Show version information
|
|
48
63
|
|
|
49
64
|
Example:
|
|
50
65
|
kawa create my-novel
|
|
51
66
|
cd my-novel
|
|
52
|
-
kawa
|
|
67
|
+
kawa dev
|
|
53
68
|
`);
|
|
54
69
|
break;
|
|
55
70
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AAEtC,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AAEtC,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEnD,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,cAAc,EAAE,YAAY,EAAE,CAAC;AAExE,MAAM,UAAU,MAAM,CAAC,IAAc;IACnC,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IAExB,QAAQ,OAAO,EAAE,CAAC;QAChB,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,MAAM,WAAW,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC;YAC1C,aAAa,CAAC,WAAW,CAAC,CAAC;YAC3B,MAAM;QACR,CAAC;QAED,KAAK,KAAK,CAAC,CAAC,CAAC;YACX,MAAM,UAAU,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC;YAClC,cAAc,CAAC,UAAU,CAAC,CAAC;YAC3B,MAAM;QACR,CAAC;QAED,KAAK,OAAO,CAAC,CAAC,CAAC;YACb,MAAM,UAAU,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC;YAClC,MAAM,EAAE,GAAG,YAAY,CAAC,UAAU,CAAC,CAAC;YACpC,IAAI,CAAC,EAAE,EAAE,CAAC;gBACR,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;YACvB,CAAC;YACD,MAAM;QACR,CAAC;QAED,KAAK,UAAU,CAAC,CAAC,CAAC;YAChB,MAAM,UAAU,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC;YAClC,MAAM,EAAE,GAAG,eAAe,CAAC,UAAU,CAAC,CAAC;YACvC,IAAI,CAAC,EAAE,EAAE,CAAC;gBACR,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;YACvB,CAAC;YACD,MAAM;QACR,CAAC;QAED,KAAK,SAAS,CAAC;QACf,KAAK,IAAI,CAAC;QACV,KAAK,WAAW,CAAC,CAAC,CAAC;YACjB,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;YAC9B,MAAM;QACR,CAAC;QAED,KAAK,MAAM,CAAC;QACZ,KAAK,IAAI,CAAC;QACV,KAAK,QAAQ,CAAC;QACd,OAAO,CAAC,CAAC,CAAC;YACR,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;;CAmBjB,CAAC,CAAC;YACG,MAAM;QACR,CAAC;IACH,CAAC;AACH,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kawaijs",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Web-native visual novel engine and toolchain inspired by Ren'Py",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
},
|
|
12
12
|
"files": [
|
|
13
13
|
"dist",
|
|
14
|
-
"bin"
|
|
14
|
+
"bin",
|
|
15
|
+
"README.md"
|
|
15
16
|
],
|
|
16
17
|
"publishConfig": {
|
|
17
18
|
"access": "public"
|
|
@@ -30,10 +31,10 @@
|
|
|
30
31
|
"typecheck": "tsc --noEmit"
|
|
31
32
|
},
|
|
32
33
|
"dependencies": {
|
|
33
|
-
"@kawaijs/ast": "^0.1.
|
|
34
|
-
"@kawaijs/parser": "^0.1.
|
|
35
|
-
"@kawaijs/runtime": "^0.1.
|
|
36
|
-
"@kawaijs/renderer-dom": "^0.1.
|
|
34
|
+
"@kawaijs/ast": "^0.1.2",
|
|
35
|
+
"@kawaijs/parser": "^0.1.2",
|
|
36
|
+
"@kawaijs/runtime": "^0.1.2",
|
|
37
|
+
"@kawaijs/renderer-dom": "^0.1.2"
|
|
37
38
|
},
|
|
38
39
|
"keywords": [
|
|
39
40
|
"kawaijs",
|