@novely/core 0.54.0 → 0.55.0
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/index.d.mts +1319 -0
- package/dist/index.mjs +2623 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +12 -12
- package/dist/index.d.ts +0 -1260
- package/dist/index.js +0 -2771
- package/dist/index.js.map +0 -1
package/dist/index.js
DELETED
|
@@ -1,2771 +0,0 @@
|
|
|
1
|
-
// ../../node_modules/.pnpm/dequal@2.0.3/node_modules/dequal/lite/index.mjs
|
|
2
|
-
var has = Object.prototype.hasOwnProperty;
|
|
3
|
-
function dequal(foo, bar) {
|
|
4
|
-
var ctor, len;
|
|
5
|
-
if (foo === bar) return true;
|
|
6
|
-
if (foo && bar && (ctor = foo.constructor) === bar.constructor) {
|
|
7
|
-
if (ctor === Date) return foo.getTime() === bar.getTime();
|
|
8
|
-
if (ctor === RegExp) return foo.toString() === bar.toString();
|
|
9
|
-
if (ctor === Array) {
|
|
10
|
-
if ((len = foo.length) === bar.length) {
|
|
11
|
-
while (len-- && dequal(foo[len], bar[len])) ;
|
|
12
|
-
}
|
|
13
|
-
return len === -1;
|
|
14
|
-
}
|
|
15
|
-
if (!ctor || typeof foo === "object") {
|
|
16
|
-
len = 0;
|
|
17
|
-
for (ctor in foo) {
|
|
18
|
-
if (has.call(foo, ctor) && ++len && !has.call(bar, ctor)) return false;
|
|
19
|
-
if (!(ctor in bar) || !dequal(foo[ctor], bar[ctor])) return false;
|
|
20
|
-
}
|
|
21
|
-
return Object.keys(bar).length === len;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
return foo !== foo && bar !== bar;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// src/novely.ts
|
|
28
|
-
import { memoize as memoize5, throttle } from "es-toolkit/function";
|
|
29
|
-
import { merge as deepmerge } from "es-toolkit/object";
|
|
30
|
-
import { DEV as DEV5 } from "esm-env";
|
|
31
|
-
|
|
32
|
-
// ../../node_modules/.pnpm/klona@2.0.6/node_modules/klona/full/index.mjs
|
|
33
|
-
function set(obj, key, val) {
|
|
34
|
-
if (typeof val.value === "object") val.value = klona(val.value);
|
|
35
|
-
if (!val.enumerable || val.get || val.set || !val.configurable || !val.writable || key === "__proto__") {
|
|
36
|
-
Object.defineProperty(obj, key, val);
|
|
37
|
-
} else obj[key] = val.value;
|
|
38
|
-
}
|
|
39
|
-
function klona(x) {
|
|
40
|
-
if (typeof x !== "object") return x;
|
|
41
|
-
var i = 0, k, list, tmp, str = Object.prototype.toString.call(x);
|
|
42
|
-
if (str === "[object Object]") {
|
|
43
|
-
tmp = Object.create(x.__proto__ || null);
|
|
44
|
-
} else if (str === "[object Array]") {
|
|
45
|
-
tmp = Array(x.length);
|
|
46
|
-
} else if (str === "[object Set]") {
|
|
47
|
-
tmp = /* @__PURE__ */ new Set();
|
|
48
|
-
x.forEach(function(val) {
|
|
49
|
-
tmp.add(klona(val));
|
|
50
|
-
});
|
|
51
|
-
} else if (str === "[object Map]") {
|
|
52
|
-
tmp = /* @__PURE__ */ new Map();
|
|
53
|
-
x.forEach(function(val, key) {
|
|
54
|
-
tmp.set(klona(key), klona(val));
|
|
55
|
-
});
|
|
56
|
-
} else if (str === "[object Date]") {
|
|
57
|
-
tmp = /* @__PURE__ */ new Date(+x);
|
|
58
|
-
} else if (str === "[object RegExp]") {
|
|
59
|
-
tmp = new RegExp(x.source, x.flags);
|
|
60
|
-
} else if (str === "[object DataView]") {
|
|
61
|
-
tmp = new x.constructor(klona(x.buffer));
|
|
62
|
-
} else if (str === "[object ArrayBuffer]") {
|
|
63
|
-
tmp = x.slice(0);
|
|
64
|
-
} else if (str.slice(-6) === "Array]") {
|
|
65
|
-
tmp = new x.constructor(x);
|
|
66
|
-
}
|
|
67
|
-
if (tmp) {
|
|
68
|
-
for (list = Object.getOwnPropertySymbols(x); i < list.length; i++) {
|
|
69
|
-
set(tmp, list[i], Object.getOwnPropertyDescriptor(x, list[i]));
|
|
70
|
-
}
|
|
71
|
-
for (i = 0, list = Object.getOwnPropertyNames(x); i < list.length; i++) {
|
|
72
|
-
if (Object.hasOwnProperty.call(tmp, k = list[i]) && tmp[k] === x[k]) continue;
|
|
73
|
-
set(tmp, k, Object.getOwnPropertyDescriptor(x, k));
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
return tmp || x;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// src/novely.ts
|
|
80
|
-
import pLimit from "p-limit";
|
|
81
|
-
|
|
82
|
-
// src/constants.ts
|
|
83
|
-
var SKIPPED_DURING_RESTORE = /* @__PURE__ */ new Set(["dialog", "choice", "input", "vibrate", "text"]);
|
|
84
|
-
var BLOCK_EXIT_STATEMENTS = /* @__PURE__ */ new Set(["choice:exit", "condition:exit", "block:exit"]);
|
|
85
|
-
var BLOCK_STATEMENTS = /* @__PURE__ */ new Set(["choice", "condition", "block"]);
|
|
86
|
-
var AUDIO_ACTIONS = /* @__PURE__ */ new Set(["playMusic", "stopMusic", "playSound", "stopSound", "voice", "stopVoice"]);
|
|
87
|
-
var EMPTY_SET = /* @__PURE__ */ new Set();
|
|
88
|
-
var DEFAULT_TYPEWRITER_SPEED = "Medium";
|
|
89
|
-
var HOWLER_SUPPORTED_FILE_FORMATS = /* @__PURE__ */ new Set([
|
|
90
|
-
"mp3",
|
|
91
|
-
"mpeg",
|
|
92
|
-
"opus",
|
|
93
|
-
"ogg",
|
|
94
|
-
"oga",
|
|
95
|
-
"wav",
|
|
96
|
-
"aac",
|
|
97
|
-
"caf",
|
|
98
|
-
"m4a",
|
|
99
|
-
"m4b",
|
|
100
|
-
"mp4",
|
|
101
|
-
"weba",
|
|
102
|
-
"webm",
|
|
103
|
-
"dolby",
|
|
104
|
-
"flac"
|
|
105
|
-
]);
|
|
106
|
-
var SUPPORTED_IMAGE_FILE_FORMATS = /* @__PURE__ */ new Set([
|
|
107
|
-
"apng",
|
|
108
|
-
"avif",
|
|
109
|
-
"gif",
|
|
110
|
-
"jpg",
|
|
111
|
-
"jpeg",
|
|
112
|
-
"jfif",
|
|
113
|
-
"pjpeg",
|
|
114
|
-
"pjp",
|
|
115
|
-
"png",
|
|
116
|
-
"svg",
|
|
117
|
-
"webp",
|
|
118
|
-
"bmp"
|
|
119
|
-
]);
|
|
120
|
-
var MAIN_CONTEXT_KEY = "$MAIN";
|
|
121
|
-
|
|
122
|
-
// src/shared.ts
|
|
123
|
-
var STACK_MAP = /* @__PURE__ */ new Map();
|
|
124
|
-
var CUSTOM_ACTION_MAP = /* @__PURE__ */ new Map();
|
|
125
|
-
var CUSTOM_ACTION_CLEANUP_MAP = /* @__PURE__ */ new Map();
|
|
126
|
-
var PRELOADED_ASSETS = /* @__PURE__ */ new Set();
|
|
127
|
-
var ASSETS_TO_PRELOAD = /* @__PURE__ */ new Set();
|
|
128
|
-
|
|
129
|
-
// src/utilities/assertions.ts
|
|
130
|
-
var isNumber = (val) => {
|
|
131
|
-
return typeof val === "number";
|
|
132
|
-
};
|
|
133
|
-
var isNull = (val) => {
|
|
134
|
-
return val === null;
|
|
135
|
-
};
|
|
136
|
-
var isString = (val) => {
|
|
137
|
-
return typeof val === "string";
|
|
138
|
-
};
|
|
139
|
-
var isFunction = (val) => {
|
|
140
|
-
return typeof val === "function";
|
|
141
|
-
};
|
|
142
|
-
var isPromise = (val) => {
|
|
143
|
-
return Boolean(val) && (typeof val === "object" || isFunction(val)) && isFunction(val.then);
|
|
144
|
-
};
|
|
145
|
-
var isEmpty = (val) => {
|
|
146
|
-
return typeof val === "object" && !isNull(val) && Object.keys(val).length === 0;
|
|
147
|
-
};
|
|
148
|
-
var isUserRequiredAction = ([action, ...meta]) => {
|
|
149
|
-
return Boolean(action === "custom" && meta[0] && meta[0].requireUserAction);
|
|
150
|
-
};
|
|
151
|
-
var isBlockStatement = (statement) => {
|
|
152
|
-
return BLOCK_STATEMENTS.has(statement);
|
|
153
|
-
};
|
|
154
|
-
var isBlockExitStatement = (statement) => {
|
|
155
|
-
return BLOCK_EXIT_STATEMENTS.has(statement);
|
|
156
|
-
};
|
|
157
|
-
var isSkippedDuringRestore = (item) => {
|
|
158
|
-
return SKIPPED_DURING_RESTORE.has(item);
|
|
159
|
-
};
|
|
160
|
-
var isAudioAction = (action) => {
|
|
161
|
-
return AUDIO_ACTIONS.has(action);
|
|
162
|
-
};
|
|
163
|
-
var isAction = (element) => {
|
|
164
|
-
return Array.isArray(element) && isString(element[0]);
|
|
165
|
-
};
|
|
166
|
-
var isBlockingAction = (action) => {
|
|
167
|
-
return isUserRequiredAction(action) || isSkippedDuringRestore(action[0]) && action[0] !== "vibrate";
|
|
168
|
-
};
|
|
169
|
-
var isAsset = (suspect) => {
|
|
170
|
-
return suspect !== null && typeof suspect === "object" && "source" in suspect && "type" in suspect;
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
// src/utilities/match-action.ts
|
|
174
|
-
var matchAction = (callbacks, values) => {
|
|
175
|
-
const { getContext, onBeforeActionCall, push, forward } = callbacks;
|
|
176
|
-
const match = (action, props, { ctx, data }) => {
|
|
177
|
-
const context = typeof ctx === "string" ? getContext(ctx) : ctx;
|
|
178
|
-
onBeforeActionCall({
|
|
179
|
-
action,
|
|
180
|
-
props,
|
|
181
|
-
ctx: context
|
|
182
|
-
});
|
|
183
|
-
return values[action](
|
|
184
|
-
{
|
|
185
|
-
ctx: context,
|
|
186
|
-
data,
|
|
187
|
-
push() {
|
|
188
|
-
if (context.meta.preview) return;
|
|
189
|
-
push(context);
|
|
190
|
-
},
|
|
191
|
-
forward() {
|
|
192
|
-
if (context.meta.preview) return;
|
|
193
|
-
forward(context);
|
|
194
|
-
}
|
|
195
|
-
},
|
|
196
|
-
props
|
|
197
|
-
);
|
|
198
|
-
};
|
|
199
|
-
return {
|
|
200
|
-
match,
|
|
201
|
-
nativeActions: Object.keys(values)
|
|
202
|
-
};
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
// src/asset.ts
|
|
206
|
-
import { memoize, once } from "es-toolkit/function";
|
|
207
|
-
import { DEV } from "esm-env";
|
|
208
|
-
|
|
209
|
-
// src/audio-codecs.ts
|
|
210
|
-
var cut = (str) => str.replace(/^no$/, "");
|
|
211
|
-
var audio = new Audio();
|
|
212
|
-
var canPlay = (type) => !!cut(audio.canPlayType(type));
|
|
213
|
-
var canPlayMultiple = (...types) => types.some((type) => canPlay(type));
|
|
214
|
-
var supportsMap = {
|
|
215
|
-
mp3: canPlayMultiple("audio/mpeg;", "audio/mp3;"),
|
|
216
|
-
mpeg: canPlay("audio/mpeg;"),
|
|
217
|
-
opus: canPlay('audio/ogg; codecs="opus"'),
|
|
218
|
-
ogg: canPlay('audio/ogg; codecs="vorbis"'),
|
|
219
|
-
oga: canPlay('audio/ogg; codecs="vorbis"'),
|
|
220
|
-
wav: canPlayMultiple('audio/wav; codecs="1"', "audio/wav;"),
|
|
221
|
-
aac: canPlay("audio/aac;"),
|
|
222
|
-
caf: canPlay("audio/x-caf;"),
|
|
223
|
-
m4a: canPlayMultiple("audio/x-m4a;", "audio/m4a;", "audio/aac;"),
|
|
224
|
-
m4b: canPlayMultiple("audio/x-m4b;", "audio/m4b;", "audio/aac;"),
|
|
225
|
-
mp4: canPlayMultiple("audio/x-mp4;", "audio/mp4;", "audio/aac;"),
|
|
226
|
-
weba: canPlay('audio/webm; codecs="vorbis"'),
|
|
227
|
-
webm: canPlay('audio/webm; codecs="vorbis"'),
|
|
228
|
-
dolby: canPlay('audio/mp4; codecs="ec-3"'),
|
|
229
|
-
flac: canPlayMultiple("audio/x-flac;", "audio/flac;")
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
// src/image-formats.ts
|
|
233
|
-
var avif = "data:image/avif;base64,AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEAAAABAAABGgAAAB0AAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAAAamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAAIAAAACAAAAEHBpeGkAAAAAAwgICAAAAAxhdjFDgQ0MAAAAABNjb2xybmNseAACAAIAAYAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAAACVtZGF0EgAKCBgANogQEAwgMg8f8D///8WfhwB8+ErK42A=";
|
|
234
|
-
var jxl = "data:image/jxl;base64,/woIAAAMABKIAgC4AF3lEgAAFSqjjBu8nOv58kOHxbSN6wxttW1hSaLIODZJJ3BIEkkaoCUzGM6qJAE=";
|
|
235
|
-
var webp = "data:image/webp;base64,UklGRjoAAABXRUJQVlA4IC4AAACyAgCdASoCAAIALmk0mk0iIiIiIgBoSygABc6WWgAA/veff/0PP8bA//LwYAAA";
|
|
236
|
-
var supportsFormat = (source) => {
|
|
237
|
-
const { promise, resolve } = Promise.withResolvers();
|
|
238
|
-
const img = Object.assign(document.createElement("img"), {
|
|
239
|
-
src: source
|
|
240
|
-
});
|
|
241
|
-
img.onload = img.onerror = () => {
|
|
242
|
-
resolve(img.height === 2);
|
|
243
|
-
};
|
|
244
|
-
return promise;
|
|
245
|
-
};
|
|
246
|
-
var supportsMap2 = {
|
|
247
|
-
avif: false,
|
|
248
|
-
jxl: false,
|
|
249
|
-
webp: false
|
|
250
|
-
};
|
|
251
|
-
var formatsMap = {
|
|
252
|
-
avif,
|
|
253
|
-
jxl,
|
|
254
|
-
webp
|
|
255
|
-
};
|
|
256
|
-
var loadImageFormatsSupport = async () => {
|
|
257
|
-
const promises = [];
|
|
258
|
-
for (const [format, source] of Object.entries(formatsMap)) {
|
|
259
|
-
const promise = supportsFormat(source).then((supported) => {
|
|
260
|
-
supportsMap2[format] = supported;
|
|
261
|
-
});
|
|
262
|
-
promises.push(promise);
|
|
263
|
-
}
|
|
264
|
-
await Promise.all(promises);
|
|
265
|
-
};
|
|
266
|
-
loadImageFormatsSupport();
|
|
267
|
-
|
|
268
|
-
// src/asset.ts
|
|
269
|
-
var generateRandomId = () => Math.random().toString(36);
|
|
270
|
-
var getType = memoize(
|
|
271
|
-
(extensions) => {
|
|
272
|
-
if (extensions.every((extension) => HOWLER_SUPPORTED_FILE_FORMATS.has(extension))) {
|
|
273
|
-
return "audio";
|
|
274
|
-
}
|
|
275
|
-
if (extensions.every((extension) => SUPPORTED_IMAGE_FILE_FORMATS.has(extension))) {
|
|
276
|
-
return "image";
|
|
277
|
-
}
|
|
278
|
-
if (DEV) {
|
|
279
|
-
throw new Error(`Unsupported file extensions: ${JSON.stringify(extensions)}`);
|
|
280
|
-
}
|
|
281
|
-
throw extensions;
|
|
282
|
-
},
|
|
283
|
-
{
|
|
284
|
-
getCacheKey: (extensions) => extensions.join("~")
|
|
285
|
-
}
|
|
286
|
-
);
|
|
287
|
-
var SUPPORT_MAPS = {
|
|
288
|
-
image: supportsMap2,
|
|
289
|
-
audio: supportsMap
|
|
290
|
-
};
|
|
291
|
-
var assetPrivate = memoize(
|
|
292
|
-
(variants) => {
|
|
293
|
-
if (DEV && variants.length === 0) {
|
|
294
|
-
throw new Error(`Attempt to use "asset" function without arguments`);
|
|
295
|
-
}
|
|
296
|
-
const map = {};
|
|
297
|
-
const extensions = [];
|
|
298
|
-
for (const v of variants) {
|
|
299
|
-
const e = getUrlFileExtension(v);
|
|
300
|
-
map[e] = v;
|
|
301
|
-
extensions.push(e);
|
|
302
|
-
}
|
|
303
|
-
const type = getType(extensions);
|
|
304
|
-
const getSource = once(() => {
|
|
305
|
-
const support = SUPPORT_MAPS[type];
|
|
306
|
-
for (const extension of extensions) {
|
|
307
|
-
if (extension in support) {
|
|
308
|
-
if (support[extension]) {
|
|
309
|
-
return map[extension];
|
|
310
|
-
}
|
|
311
|
-
} else {
|
|
312
|
-
return map[extension];
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
if (DEV) {
|
|
316
|
-
throw new Error(`No matching asset was found for ${variants.map((v) => `"${v}"`).join(", ")}`);
|
|
317
|
-
}
|
|
318
|
-
return "";
|
|
319
|
-
});
|
|
320
|
-
return {
|
|
321
|
-
get source() {
|
|
322
|
-
return getSource();
|
|
323
|
-
},
|
|
324
|
-
get type() {
|
|
325
|
-
return type;
|
|
326
|
-
},
|
|
327
|
-
id: generateRandomId()
|
|
328
|
-
};
|
|
329
|
-
},
|
|
330
|
-
{
|
|
331
|
-
getCacheKey: (variants) => variants.join("~")
|
|
332
|
-
}
|
|
333
|
-
);
|
|
334
|
-
var asset = (...variants) => {
|
|
335
|
-
return assetPrivate(variants);
|
|
336
|
-
};
|
|
337
|
-
asset.image = (source) => {
|
|
338
|
-
if (assetPrivate.cache.has(source)) {
|
|
339
|
-
return assetPrivate.cache.get(source);
|
|
340
|
-
}
|
|
341
|
-
const asset2 = {
|
|
342
|
-
type: "image",
|
|
343
|
-
source,
|
|
344
|
-
id: generateRandomId()
|
|
345
|
-
};
|
|
346
|
-
assetPrivate.cache.set(source, asset2);
|
|
347
|
-
return asset2;
|
|
348
|
-
};
|
|
349
|
-
asset.audio = (source) => {
|
|
350
|
-
if (assetPrivate.cache.has(source)) {
|
|
351
|
-
return assetPrivate.cache.get(source);
|
|
352
|
-
}
|
|
353
|
-
const asset2 = {
|
|
354
|
-
type: "audio",
|
|
355
|
-
source,
|
|
356
|
-
id: generateRandomId()
|
|
357
|
-
};
|
|
358
|
-
assetPrivate.cache.set(source, asset2);
|
|
359
|
-
return asset2;
|
|
360
|
-
};
|
|
361
|
-
var unwrapAsset = (asset2) => {
|
|
362
|
-
return isAsset(asset2) ? asset2.source : asset2;
|
|
363
|
-
};
|
|
364
|
-
var unwrapAudioAsset = (asset2) => {
|
|
365
|
-
if (DEV && isAsset(asset2) && asset2.type !== "audio") {
|
|
366
|
-
throw new Error("Attempt to use non-audio asset in audio action", { cause: asset2 });
|
|
367
|
-
}
|
|
368
|
-
return unwrapAsset(asset2);
|
|
369
|
-
};
|
|
370
|
-
var unwrapImageAsset = (asset2) => {
|
|
371
|
-
if (DEV && isAsset(asset2) && asset2.type !== "image") {
|
|
372
|
-
throw new Error("Attempt to use non-image asset in action that requires image assets", { cause: asset2 });
|
|
373
|
-
}
|
|
374
|
-
return unwrapAsset(asset2);
|
|
375
|
-
};
|
|
376
|
-
|
|
377
|
-
// src/utilities/actions-processing.ts
|
|
378
|
-
import { DEV as DEV2 } from "esm-env";
|
|
379
|
-
var isExitImpossible = (path) => {
|
|
380
|
-
const blockStatements = path.filter(([item]) => isBlockStatement(item));
|
|
381
|
-
const blockExitStatements = path.filter(([item]) => isBlockExitStatement(item));
|
|
382
|
-
if (blockStatements.length === 0 && blockExitStatements.length === 0) {
|
|
383
|
-
return true;
|
|
384
|
-
}
|
|
385
|
-
if (blockStatements.length > blockExitStatements.length) {
|
|
386
|
-
return false;
|
|
387
|
-
}
|
|
388
|
-
return !blockExitStatements.every(([name], i) => name && name.startsWith(blockStatements[i][0]));
|
|
389
|
-
};
|
|
390
|
-
var createReferFunction = ({ story, onUnknownSceneHit }) => {
|
|
391
|
-
const refer = async (path) => {
|
|
392
|
-
const { promise: ready, resolve: setReady } = Promise.withResolvers();
|
|
393
|
-
let current = story;
|
|
394
|
-
let precurrent = story;
|
|
395
|
-
const blocks = [];
|
|
396
|
-
const refer2 = async () => {
|
|
397
|
-
for (const [type, val] of path) {
|
|
398
|
-
if (type === "jump") {
|
|
399
|
-
if (!current[val]) {
|
|
400
|
-
setReady(true);
|
|
401
|
-
await onUnknownSceneHit(val);
|
|
402
|
-
}
|
|
403
|
-
if (DEV2 && !story[val]) {
|
|
404
|
-
throw new Error(`Attempt to jump to unknown scene "${val}"`);
|
|
405
|
-
}
|
|
406
|
-
if (DEV2 && story[val].length === 0) {
|
|
407
|
-
throw new Error(`Attempt to jump to empty scene "${val}"`);
|
|
408
|
-
}
|
|
409
|
-
precurrent = story;
|
|
410
|
-
current = current[val];
|
|
411
|
-
} else if (type === null) {
|
|
412
|
-
precurrent = current;
|
|
413
|
-
current = current[val];
|
|
414
|
-
} else if (type === "choice") {
|
|
415
|
-
blocks.push(precurrent);
|
|
416
|
-
current = current[val + 1][1];
|
|
417
|
-
} else if (type === "condition") {
|
|
418
|
-
blocks.push(precurrent);
|
|
419
|
-
current = current[2][val];
|
|
420
|
-
} else if (type === "block") {
|
|
421
|
-
blocks.push(precurrent);
|
|
422
|
-
current = story[val];
|
|
423
|
-
} else if (type === "block:exit" || type === "choice:exit" || type === "condition:exit") {
|
|
424
|
-
current = blocks.pop();
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
setReady(false);
|
|
428
|
-
return current;
|
|
429
|
-
};
|
|
430
|
-
const value = refer2();
|
|
431
|
-
const found = await ready;
|
|
432
|
-
return {
|
|
433
|
-
found,
|
|
434
|
-
value
|
|
435
|
-
};
|
|
436
|
-
};
|
|
437
|
-
const referGuarded = async (path) => {
|
|
438
|
-
return await (await refer(path)).value;
|
|
439
|
-
};
|
|
440
|
-
return {
|
|
441
|
-
refer,
|
|
442
|
-
referGuarded
|
|
443
|
-
};
|
|
444
|
-
};
|
|
445
|
-
var exitPath = async ({ path, refer, onExitImpossible }) => {
|
|
446
|
-
const last = path.at(-1);
|
|
447
|
-
const ignore = [];
|
|
448
|
-
let wasExitImpossible = false;
|
|
449
|
-
if (!isAction(await refer(path))) {
|
|
450
|
-
if (last && isNull(last[0]) && isNumber(last[1])) {
|
|
451
|
-
last[1]--;
|
|
452
|
-
} else {
|
|
453
|
-
path.pop();
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
if (isExitImpossible(path)) {
|
|
457
|
-
const referred = await refer(path);
|
|
458
|
-
if (isAction(referred) && isSkippedDuringRestore(referred[0])) {
|
|
459
|
-
onExitImpossible?.();
|
|
460
|
-
}
|
|
461
|
-
wasExitImpossible = true;
|
|
462
|
-
return {
|
|
463
|
-
exitImpossible: wasExitImpossible
|
|
464
|
-
};
|
|
465
|
-
}
|
|
466
|
-
for (let i = path.length - 1; i > 0; i--) {
|
|
467
|
-
const [name] = path[i];
|
|
468
|
-
if (isBlockExitStatement(name)) {
|
|
469
|
-
ignore.push(name);
|
|
470
|
-
}
|
|
471
|
-
if (!isBlockStatement(name)) continue;
|
|
472
|
-
if (ignore.at(-1)?.startsWith(name)) {
|
|
473
|
-
ignore.pop();
|
|
474
|
-
continue;
|
|
475
|
-
}
|
|
476
|
-
path.push([`${name}:exit`]);
|
|
477
|
-
const prev = findLastPathItemBeforeItemOfType(path.slice(0, i + 1), name);
|
|
478
|
-
if (prev) path.push([null, prev[1] + 1]);
|
|
479
|
-
if (!isAction(await refer(path))) {
|
|
480
|
-
path.pop();
|
|
481
|
-
continue;
|
|
482
|
-
}
|
|
483
|
-
break;
|
|
484
|
-
}
|
|
485
|
-
return {
|
|
486
|
-
exitImpossible: wasExitImpossible
|
|
487
|
-
};
|
|
488
|
-
};
|
|
489
|
-
var nextPath = (path) => {
|
|
490
|
-
const last = path.at(-1);
|
|
491
|
-
if (last && (isNull(last[0]) || last[0] === "jump") && isNumber(last[1])) {
|
|
492
|
-
last[1]++;
|
|
493
|
-
} else {
|
|
494
|
-
path.push([null, 0]);
|
|
495
|
-
}
|
|
496
|
-
return path;
|
|
497
|
-
};
|
|
498
|
-
var collectActionsBeforeBlockingAction = async ({
|
|
499
|
-
path,
|
|
500
|
-
refer,
|
|
501
|
-
clone
|
|
502
|
-
}) => {
|
|
503
|
-
const collection = [];
|
|
504
|
-
let action = await refer(path);
|
|
505
|
-
while (true) {
|
|
506
|
-
if (action == void 0) {
|
|
507
|
-
const { exitImpossible } = await exitPath({
|
|
508
|
-
path,
|
|
509
|
-
refer
|
|
510
|
-
});
|
|
511
|
-
if (exitImpossible) {
|
|
512
|
-
break;
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
if (!action) {
|
|
516
|
-
break;
|
|
517
|
-
}
|
|
518
|
-
if (isBlockingAction(action)) {
|
|
519
|
-
const [name, ...props] = action;
|
|
520
|
-
if (name === "choice") {
|
|
521
|
-
const choiceProps = props;
|
|
522
|
-
for (let i = 0; i < choiceProps.length; i++) {
|
|
523
|
-
const branchContent = choiceProps[i];
|
|
524
|
-
if (!Array.isArray(branchContent)) continue;
|
|
525
|
-
const virtualPath = clone(path);
|
|
526
|
-
virtualPath.push(["choice", i], [null, 0]);
|
|
527
|
-
const innerActions = await collectActionsBeforeBlockingAction({
|
|
528
|
-
path: virtualPath,
|
|
529
|
-
refer,
|
|
530
|
-
clone
|
|
531
|
-
});
|
|
532
|
-
collection.push(...innerActions);
|
|
533
|
-
}
|
|
534
|
-
} else if (name === "condition") {
|
|
535
|
-
const conditionProps = props;
|
|
536
|
-
const conditions = Object.keys(conditionProps[1]);
|
|
537
|
-
for (const condition of conditions) {
|
|
538
|
-
const virtualPath = clone(path);
|
|
539
|
-
virtualPath.push(["condition", condition], [null, 0]);
|
|
540
|
-
const innerActions = await collectActionsBeforeBlockingAction({
|
|
541
|
-
path: virtualPath,
|
|
542
|
-
refer,
|
|
543
|
-
clone
|
|
544
|
-
});
|
|
545
|
-
collection.push(...innerActions);
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
break;
|
|
549
|
-
}
|
|
550
|
-
collection.push(action);
|
|
551
|
-
if (action[0] === "jump") {
|
|
552
|
-
path = [
|
|
553
|
-
["jump", action[1]],
|
|
554
|
-
[null, 0]
|
|
555
|
-
];
|
|
556
|
-
} else if (action[0] == "block") {
|
|
557
|
-
path.push(["block", action[1]], [null, 0]);
|
|
558
|
-
} else {
|
|
559
|
-
nextPath(path);
|
|
560
|
-
}
|
|
561
|
-
action = await refer(path);
|
|
562
|
-
}
|
|
563
|
-
return collection;
|
|
564
|
-
};
|
|
565
|
-
var findLastPathItemBeforeItemOfType = (path, name) => {
|
|
566
|
-
const item = path.findLast(([_name, _value], i, array) => {
|
|
567
|
-
const next = array[i + 1];
|
|
568
|
-
return isNull(_name) && isNumber(_value) && next != null && next[0] === name;
|
|
569
|
-
});
|
|
570
|
-
return item;
|
|
571
|
-
};
|
|
572
|
-
var getOppositeAction = (action) => {
|
|
573
|
-
const MAP = {
|
|
574
|
-
showCharacter: "hideCharacter",
|
|
575
|
-
playSound: "stopSound",
|
|
576
|
-
playMusic: "stopMusic",
|
|
577
|
-
voice: "stopVoice"
|
|
578
|
-
};
|
|
579
|
-
return MAP[action];
|
|
580
|
-
};
|
|
581
|
-
var getActionsFromPath = async ({ story, path, filter, referGuarded }) => {
|
|
582
|
-
let current = story;
|
|
583
|
-
let precurrent;
|
|
584
|
-
let ignoreNestedBefore = null;
|
|
585
|
-
let index = 0;
|
|
586
|
-
let skipPreserve = void 0;
|
|
587
|
-
const skip = /* @__PURE__ */ new Set();
|
|
588
|
-
const max = path.reduce((acc, [type, val]) => {
|
|
589
|
-
if (isNull(type) && isNumber(val)) {
|
|
590
|
-
return acc + 1;
|
|
591
|
-
}
|
|
592
|
-
return acc;
|
|
593
|
-
}, 0);
|
|
594
|
-
const queue = [];
|
|
595
|
-
const blocks = [];
|
|
596
|
-
await referGuarded(path);
|
|
597
|
-
for (const [type, val] of path) {
|
|
598
|
-
if (type === "jump") {
|
|
599
|
-
precurrent = story;
|
|
600
|
-
current = current[val];
|
|
601
|
-
} else if (type === null) {
|
|
602
|
-
precurrent = current;
|
|
603
|
-
if (isNumber(val)) {
|
|
604
|
-
index++;
|
|
605
|
-
let startIndex = 0;
|
|
606
|
-
if (ignoreNestedBefore) {
|
|
607
|
-
const prev = findLastPathItemBeforeItemOfType(path.slice(0, index), ignoreNestedBefore);
|
|
608
|
-
if (prev) {
|
|
609
|
-
startIndex = prev[1];
|
|
610
|
-
ignoreNestedBefore = null;
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
for (let i = startIndex; i <= val; i++) {
|
|
614
|
-
const item = current[i];
|
|
615
|
-
if (!isAction(item)) continue;
|
|
616
|
-
const [action] = item;
|
|
617
|
-
const last = index === max && i === val;
|
|
618
|
-
const shouldSkip = isSkippedDuringRestore(action) || isUserRequiredAction(item);
|
|
619
|
-
if (shouldSkip) {
|
|
620
|
-
skip.add(item);
|
|
621
|
-
}
|
|
622
|
-
if (shouldSkip && last) {
|
|
623
|
-
skipPreserve = item;
|
|
624
|
-
}
|
|
625
|
-
if (filter && shouldSkip && !last) {
|
|
626
|
-
continue;
|
|
627
|
-
} else {
|
|
628
|
-
queue.push(item);
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
current = current[val];
|
|
633
|
-
} else if (type === "choice") {
|
|
634
|
-
blocks.push(precurrent);
|
|
635
|
-
current = current[val + 1][1];
|
|
636
|
-
} else if (type === "condition") {
|
|
637
|
-
blocks.push(precurrent);
|
|
638
|
-
current = current[2][val];
|
|
639
|
-
} else if (type === "block") {
|
|
640
|
-
blocks.push(precurrent);
|
|
641
|
-
current = story[val];
|
|
642
|
-
} else if (type === "block:exit" || type === "choice:exit" || type === "condition:exit") {
|
|
643
|
-
current = blocks.pop();
|
|
644
|
-
ignoreNestedBefore = type.slice(0, -5);
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
return {
|
|
648
|
-
queue,
|
|
649
|
-
skip,
|
|
650
|
-
skipPreserve
|
|
651
|
-
};
|
|
652
|
-
};
|
|
653
|
-
var createQueueProcessor = (queue, options) => {
|
|
654
|
-
const processedQueue = [];
|
|
655
|
-
const keep = /* @__PURE__ */ new Set();
|
|
656
|
-
const characters = /* @__PURE__ */ new Set();
|
|
657
|
-
const audio2 = {
|
|
658
|
-
music: /* @__PURE__ */ new Set(),
|
|
659
|
-
sounds: /* @__PURE__ */ new Set()
|
|
660
|
-
};
|
|
661
|
-
const next = (i) => queue.slice(i + 1);
|
|
662
|
-
for (const [i, item] of queue.entries()) {
|
|
663
|
-
const [action, ...params] = item;
|
|
664
|
-
if (options.skip.has(item) && item !== options.skipPreserve) {
|
|
665
|
-
continue;
|
|
666
|
-
}
|
|
667
|
-
keep.add(action);
|
|
668
|
-
if (action === "function" || action === "custom") {
|
|
669
|
-
if (action === "custom") {
|
|
670
|
-
const fn = params[0];
|
|
671
|
-
if (fn.callOnlyLatest) {
|
|
672
|
-
const notLatest = next(i).some(([name, func]) => {
|
|
673
|
-
if (name !== "custom") return;
|
|
674
|
-
const isIdenticalId = Boolean(func.id && fn.id && func.id === fn.id);
|
|
675
|
-
const isIdenticalByReference = func === fn;
|
|
676
|
-
const isIdenticalByCode = String(func) === String(fn);
|
|
677
|
-
return isIdenticalId || isIdenticalByReference || isIdenticalByCode;
|
|
678
|
-
});
|
|
679
|
-
if (notLatest) continue;
|
|
680
|
-
} else if (fn.skipOnRestore) {
|
|
681
|
-
if (fn.skipOnRestore(next(i))) {
|
|
682
|
-
continue;
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
processedQueue.push(item);
|
|
687
|
-
} else if (action === "playSound") {
|
|
688
|
-
const closing = getOppositeAction(action);
|
|
689
|
-
const skip = next(i).some((item2) => {
|
|
690
|
-
if (isUserRequiredAction(item2) || isSkippedDuringRestore(item2[0])) {
|
|
691
|
-
return true;
|
|
692
|
-
}
|
|
693
|
-
const [_action, target] = item2;
|
|
694
|
-
if (target !== params[0]) {
|
|
695
|
-
return false;
|
|
696
|
-
}
|
|
697
|
-
return _action === closing || _action === action;
|
|
698
|
-
});
|
|
699
|
-
if (skip) continue;
|
|
700
|
-
audio2.sounds.add(unwrapAsset(params[0]));
|
|
701
|
-
processedQueue.push(item);
|
|
702
|
-
} else if (action === "showCharacter" || action === "playMusic" || action === "voice") {
|
|
703
|
-
const closing = getOppositeAction(action);
|
|
704
|
-
const skip = next(i).some(([_action, target]) => {
|
|
705
|
-
if (target !== params[0] && action !== "voice") {
|
|
706
|
-
return false;
|
|
707
|
-
}
|
|
708
|
-
const musicWillBePaused = action === "playMusic" && _action === "pauseMusic";
|
|
709
|
-
return musicWillBePaused || _action === closing || _action === action;
|
|
710
|
-
});
|
|
711
|
-
if (skip) continue;
|
|
712
|
-
if (action === "showCharacter") {
|
|
713
|
-
characters.add(params[0]);
|
|
714
|
-
} else if (action === "playMusic") {
|
|
715
|
-
audio2.music.add(unwrapAsset(params[0]));
|
|
716
|
-
}
|
|
717
|
-
processedQueue.push(item);
|
|
718
|
-
} else if (action === "showBackground" || action === "preload") {
|
|
719
|
-
const skip = next(i).some(([_action]) => action === _action);
|
|
720
|
-
if (skip) continue;
|
|
721
|
-
processedQueue.push(item);
|
|
722
|
-
} else if (action === "animateCharacter") {
|
|
723
|
-
const skip = next(i).some(([_action, character], j, array) => {
|
|
724
|
-
if (action === _action && character === params[0]) {
|
|
725
|
-
return true;
|
|
726
|
-
}
|
|
727
|
-
const next2 = array.slice(j);
|
|
728
|
-
const characterWillAnimate = next2.some(([__action, __character]) => action === __action);
|
|
729
|
-
const hasBlockingActions = next2.some((item2) => options.skip.has(item2));
|
|
730
|
-
const differentCharacterWillAnimate = !hasBlockingActions && next2.some(([__action, __character]) => __action === action && __character !== params[0]);
|
|
731
|
-
return characterWillAnimate && hasBlockingActions || differentCharacterWillAnimate;
|
|
732
|
-
});
|
|
733
|
-
if (skip) continue;
|
|
734
|
-
processedQueue.push(item);
|
|
735
|
-
} else {
|
|
736
|
-
processedQueue.push(item);
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
const run = async (match) => {
|
|
740
|
-
for (const item of processedQueue) {
|
|
741
|
-
const result = match(item);
|
|
742
|
-
if (isPromise(result)) {
|
|
743
|
-
await result;
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
processedQueue.length = 0;
|
|
747
|
-
};
|
|
748
|
-
return {
|
|
749
|
-
run,
|
|
750
|
-
keep: {
|
|
751
|
-
keep,
|
|
752
|
-
characters,
|
|
753
|
-
audio: audio2
|
|
754
|
-
}
|
|
755
|
-
};
|
|
756
|
-
};
|
|
757
|
-
|
|
758
|
-
// src/utilities/controlled-promise.ts
|
|
759
|
-
var createControlledPromise = () => {
|
|
760
|
-
const object = {
|
|
761
|
-
resolve: null,
|
|
762
|
-
reject: null,
|
|
763
|
-
promise: null,
|
|
764
|
-
cancel: null
|
|
765
|
-
};
|
|
766
|
-
const init = () => {
|
|
767
|
-
const promise = new Promise((resolve, reject) => {
|
|
768
|
-
object.reject = reject;
|
|
769
|
-
object.resolve = (value) => {
|
|
770
|
-
resolve({ cancelled: false, value });
|
|
771
|
-
};
|
|
772
|
-
object.cancel = () => {
|
|
773
|
-
resolve({ cancelled: true, value: null });
|
|
774
|
-
init();
|
|
775
|
-
};
|
|
776
|
-
});
|
|
777
|
-
object.promise = promise;
|
|
778
|
-
};
|
|
779
|
-
return init(), object;
|
|
780
|
-
};
|
|
781
|
-
|
|
782
|
-
// src/utilities/resources.ts
|
|
783
|
-
import { memoize as memoize2 } from "es-toolkit/function";
|
|
784
|
-
import { DEV as DEV3 } from "esm-env";
|
|
785
|
-
var getUrlFileExtension = (address) => {
|
|
786
|
-
try {
|
|
787
|
-
const { pathname } = new URL(address, location.href);
|
|
788
|
-
return pathname.split(".").at(-1).split("!")[0].split(":")[0];
|
|
789
|
-
} catch (error) {
|
|
790
|
-
if (DEV3) {
|
|
791
|
-
console.error(new Error(`Could not construct URL "${address}".`, { cause: error }));
|
|
792
|
-
}
|
|
793
|
-
return "";
|
|
794
|
-
}
|
|
795
|
-
};
|
|
796
|
-
var fetchContentType = async (url, request) => {
|
|
797
|
-
try {
|
|
798
|
-
const response = await request(url, {
|
|
799
|
-
method: "HEAD"
|
|
800
|
-
});
|
|
801
|
-
return response.headers.get("Content-Type") || "";
|
|
802
|
-
} catch (error) {
|
|
803
|
-
if (DEV3) {
|
|
804
|
-
console.error(new Error(`Failed to fetch file at "${url}"`, { cause: error }));
|
|
805
|
-
}
|
|
806
|
-
return "";
|
|
807
|
-
}
|
|
808
|
-
};
|
|
809
|
-
var getResourseType = memoize2(
|
|
810
|
-
async ({ url, request }) => {
|
|
811
|
-
const extension = getUrlFileExtension(url);
|
|
812
|
-
if (HOWLER_SUPPORTED_FILE_FORMATS.has(extension)) {
|
|
813
|
-
return "audio";
|
|
814
|
-
}
|
|
815
|
-
if (SUPPORTED_IMAGE_FILE_FORMATS.has(extension)) {
|
|
816
|
-
return "image";
|
|
817
|
-
}
|
|
818
|
-
const contentType = await fetchContentType(url, request);
|
|
819
|
-
if (contentType.includes("audio")) {
|
|
820
|
-
return "audio";
|
|
821
|
-
}
|
|
822
|
-
if (contentType.includes("image")) {
|
|
823
|
-
return "image";
|
|
824
|
-
}
|
|
825
|
-
return "other";
|
|
826
|
-
},
|
|
827
|
-
{
|
|
828
|
-
getCacheKey: ({ url }) => url
|
|
829
|
-
}
|
|
830
|
-
);
|
|
831
|
-
|
|
832
|
-
// src/utilities/stack.ts
|
|
833
|
-
import { memoize as memoize3 } from "es-toolkit/function";
|
|
834
|
-
var getStack = memoize3(
|
|
835
|
-
(_) => {
|
|
836
|
-
return [];
|
|
837
|
-
},
|
|
838
|
-
{
|
|
839
|
-
cache: STACK_MAP,
|
|
840
|
-
getCacheKey: (ctx) => ctx.id
|
|
841
|
-
}
|
|
842
|
-
);
|
|
843
|
-
var createUseStackFunction = (renderer) => {
|
|
844
|
-
const useStack = (context) => {
|
|
845
|
-
const ctx = typeof context === "string" ? renderer.getContext(context) : context;
|
|
846
|
-
const stack = getStack(ctx);
|
|
847
|
-
return {
|
|
848
|
-
get previous() {
|
|
849
|
-
return stack.previous;
|
|
850
|
-
},
|
|
851
|
-
get value() {
|
|
852
|
-
return stack.at(-1);
|
|
853
|
-
},
|
|
854
|
-
set value(value) {
|
|
855
|
-
stack[stack.length - 1] = value;
|
|
856
|
-
},
|
|
857
|
-
back() {
|
|
858
|
-
stack.previous = stack.length > 1 ? stack.pop() : this.value;
|
|
859
|
-
ctx.meta.goingBack = true;
|
|
860
|
-
},
|
|
861
|
-
push(value) {
|
|
862
|
-
stack.push(value);
|
|
863
|
-
},
|
|
864
|
-
clear() {
|
|
865
|
-
stack.previous = void 0;
|
|
866
|
-
stack.length = 0;
|
|
867
|
-
stack.length = 1;
|
|
868
|
-
}
|
|
869
|
-
};
|
|
870
|
-
};
|
|
871
|
-
return useStack;
|
|
872
|
-
};
|
|
873
|
-
|
|
874
|
-
// src/utilities/story.ts
|
|
875
|
-
var flatActions = (item) => {
|
|
876
|
-
return item.flatMap((data) => {
|
|
877
|
-
const type = data[0];
|
|
878
|
-
if (Array.isArray(type)) return flatActions(data);
|
|
879
|
-
return [data];
|
|
880
|
-
});
|
|
881
|
-
};
|
|
882
|
-
var flatStory = (story) => {
|
|
883
|
-
for (const key in story) {
|
|
884
|
-
story[key] = flatActions(story[key]);
|
|
885
|
-
}
|
|
886
|
-
return story;
|
|
887
|
-
};
|
|
888
|
-
|
|
889
|
-
// src/utilities/internationalization.ts
|
|
890
|
-
import { memoize as memoize4 } from "es-toolkit/function";
|
|
891
|
-
var getLanguage = (languages) => {
|
|
892
|
-
let { language } = navigator;
|
|
893
|
-
if (languages.includes(language)) {
|
|
894
|
-
return language;
|
|
895
|
-
} else if (languages.includes(language = language.slice(0, 2))) {
|
|
896
|
-
return language;
|
|
897
|
-
} else if (language = languages.find((value) => navigator.languages.includes(value))) {
|
|
898
|
-
return language;
|
|
899
|
-
}
|
|
900
|
-
return languages[0];
|
|
901
|
-
};
|
|
902
|
-
var getIntlLanguageDisplayName = memoize4((lang) => {
|
|
903
|
-
try {
|
|
904
|
-
const intl = new Intl.DisplayNames([lang], {
|
|
905
|
-
type: "language"
|
|
906
|
-
});
|
|
907
|
-
return intl.of(lang) || lang;
|
|
908
|
-
} catch {
|
|
909
|
-
return lang;
|
|
910
|
-
}
|
|
911
|
-
});
|
|
912
|
-
var capitalize = (str) => {
|
|
913
|
-
return str[0].toUpperCase() + str.slice(1);
|
|
914
|
-
};
|
|
915
|
-
|
|
916
|
-
// src/utilities/noop.ts
|
|
917
|
-
var noop = () => {
|
|
918
|
-
};
|
|
919
|
-
|
|
920
|
-
// src/utilities/store.ts
|
|
921
|
-
var getLanguageFromStore = (store2) => {
|
|
922
|
-
return store2.get().meta[0];
|
|
923
|
-
};
|
|
924
|
-
var getVolumeFromStore = (store2) => {
|
|
925
|
-
const { meta } = store2.get();
|
|
926
|
-
return {
|
|
927
|
-
music: meta[2],
|
|
928
|
-
sound: meta[3],
|
|
929
|
-
voice: meta[4]
|
|
930
|
-
};
|
|
931
|
-
};
|
|
932
|
-
|
|
933
|
-
// src/utilities/array.ts
|
|
934
|
-
var mapSet = (set2, fn) => {
|
|
935
|
-
return [...set2].map(fn);
|
|
936
|
-
};
|
|
937
|
-
var toArray = (target) => {
|
|
938
|
-
return Array.isArray(target) ? target : [target];
|
|
939
|
-
};
|
|
940
|
-
|
|
941
|
-
// src/utilities/else.ts
|
|
942
|
-
var getCharactersData = (characters) => {
|
|
943
|
-
const entries = Object.entries(characters);
|
|
944
|
-
const mapped = entries.map(([key, value]) => [key, { name: value.name, emotions: Object.keys(value.emotions) }]);
|
|
945
|
-
return Object.fromEntries(mapped);
|
|
946
|
-
};
|
|
947
|
-
|
|
948
|
-
// src/store.ts
|
|
949
|
-
var store = (current, subscribers = /* @__PURE__ */ new Set()) => {
|
|
950
|
-
const subscribe = (cb) => {
|
|
951
|
-
subscribers.add(cb), cb(current);
|
|
952
|
-
return () => {
|
|
953
|
-
subscribers.delete(cb);
|
|
954
|
-
};
|
|
955
|
-
};
|
|
956
|
-
const push = (value) => {
|
|
957
|
-
for (const cb of subscribers) cb(value);
|
|
958
|
-
};
|
|
959
|
-
const update = (fn) => {
|
|
960
|
-
push(current = fn(current));
|
|
961
|
-
};
|
|
962
|
-
const set2 = (val) => {
|
|
963
|
-
update(() => val);
|
|
964
|
-
};
|
|
965
|
-
const get = () => {
|
|
966
|
-
return current;
|
|
967
|
-
};
|
|
968
|
-
return { subscribe, update, set: set2, get };
|
|
969
|
-
};
|
|
970
|
-
var derive = (input, map) => {
|
|
971
|
-
return {
|
|
972
|
-
get: () => map(input.get()),
|
|
973
|
-
subscribe: (subscriber) => {
|
|
974
|
-
return input.subscribe((value) => {
|
|
975
|
-
return subscriber(map(value));
|
|
976
|
-
});
|
|
977
|
-
}
|
|
978
|
-
};
|
|
979
|
-
};
|
|
980
|
-
var immutable = (value) => {
|
|
981
|
-
return {
|
|
982
|
-
get: () => value,
|
|
983
|
-
subscribe: (subscriber) => {
|
|
984
|
-
subscriber(value);
|
|
985
|
-
return noop;
|
|
986
|
-
}
|
|
987
|
-
};
|
|
988
|
-
};
|
|
989
|
-
|
|
990
|
-
// src/custom-action.ts
|
|
991
|
-
import { once as once2 } from "es-toolkit/function";
|
|
992
|
-
var createCustomActionNode = (id) => {
|
|
993
|
-
const div = document.createElement("div");
|
|
994
|
-
div.setAttribute("data-id", id);
|
|
995
|
-
return div;
|
|
996
|
-
};
|
|
997
|
-
var getCustomActionHolder = (ctx, fn) => {
|
|
998
|
-
const cached = CUSTOM_ACTION_MAP.get(ctx.id + fn.key);
|
|
999
|
-
if (cached) {
|
|
1000
|
-
return cached;
|
|
1001
|
-
}
|
|
1002
|
-
const holder = {
|
|
1003
|
-
node: null,
|
|
1004
|
-
fn,
|
|
1005
|
-
localData: {}
|
|
1006
|
-
};
|
|
1007
|
-
CUSTOM_ACTION_MAP.set(ctx.id + fn.key, holder);
|
|
1008
|
-
return holder;
|
|
1009
|
-
};
|
|
1010
|
-
var getCustomActionCleanupHolder = (ctx) => {
|
|
1011
|
-
const existing = CUSTOM_ACTION_CLEANUP_MAP.get(ctx.id);
|
|
1012
|
-
if (existing) {
|
|
1013
|
-
return existing;
|
|
1014
|
-
}
|
|
1015
|
-
const holder = [];
|
|
1016
|
-
CUSTOM_ACTION_CLEANUP_MAP.set(ctx.id, holder);
|
|
1017
|
-
return holder;
|
|
1018
|
-
};
|
|
1019
|
-
var cleanCleanupSource = ({ list }) => {
|
|
1020
|
-
while (list.length) {
|
|
1021
|
-
try {
|
|
1022
|
-
list.pop()();
|
|
1023
|
-
} catch (e) {
|
|
1024
|
-
console.error(e);
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
|
-
};
|
|
1028
|
-
var handleCustomAction = (ctx, fn, {
|
|
1029
|
-
lang,
|
|
1030
|
-
state,
|
|
1031
|
-
setMountElement,
|
|
1032
|
-
remove: renderersRemove,
|
|
1033
|
-
getStack: getStack2,
|
|
1034
|
-
templateReplace,
|
|
1035
|
-
paused,
|
|
1036
|
-
ticker,
|
|
1037
|
-
request
|
|
1038
|
-
}) => {
|
|
1039
|
-
const holder = getCustomActionHolder(ctx, fn);
|
|
1040
|
-
const cleanupHolder = getCustomActionCleanupHolder(ctx);
|
|
1041
|
-
const cleanupNode = () => {
|
|
1042
|
-
if (!cleanupHolder.some((item) => item.fn.id === fn.id && item.fn.key === fn.key)) {
|
|
1043
|
-
holder.node = null;
|
|
1044
|
-
setMountElement(null);
|
|
1045
|
-
}
|
|
1046
|
-
};
|
|
1047
|
-
const cleanupSource = {
|
|
1048
|
-
fn,
|
|
1049
|
-
list: [ticker.detach],
|
|
1050
|
-
node: cleanupNode
|
|
1051
|
-
};
|
|
1052
|
-
cleanupHolder.push(cleanupSource);
|
|
1053
|
-
const getDomNodes = (insert = true) => {
|
|
1054
|
-
if (holder.node || !insert) {
|
|
1055
|
-
setMountElement(holder.node);
|
|
1056
|
-
return {
|
|
1057
|
-
element: holder.node,
|
|
1058
|
-
root: ctx.root
|
|
1059
|
-
};
|
|
1060
|
-
}
|
|
1061
|
-
holder.node = insert ? createCustomActionNode(fn.key) : null;
|
|
1062
|
-
setMountElement(holder.node);
|
|
1063
|
-
return {
|
|
1064
|
-
element: holder.node,
|
|
1065
|
-
root: ctx.root
|
|
1066
|
-
};
|
|
1067
|
-
};
|
|
1068
|
-
const clear = (func) => {
|
|
1069
|
-
cleanupSource.list.push(once2(func));
|
|
1070
|
-
};
|
|
1071
|
-
const data = (updatedData) => {
|
|
1072
|
-
if (updatedData) {
|
|
1073
|
-
return holder.localData = updatedData;
|
|
1074
|
-
}
|
|
1075
|
-
return holder.localData;
|
|
1076
|
-
};
|
|
1077
|
-
const remove = () => {
|
|
1078
|
-
cleanCleanupSource(cleanupSource);
|
|
1079
|
-
holder.node = null;
|
|
1080
|
-
setMountElement(null);
|
|
1081
|
-
renderersRemove();
|
|
1082
|
-
};
|
|
1083
|
-
const stack = getStack2(ctx);
|
|
1084
|
-
const getSave = () => {
|
|
1085
|
-
return stack.value;
|
|
1086
|
-
};
|
|
1087
|
-
return fn({
|
|
1088
|
-
flags: ctx.meta,
|
|
1089
|
-
lang,
|
|
1090
|
-
state,
|
|
1091
|
-
data,
|
|
1092
|
-
dataAtKey: (key) => CUSTOM_ACTION_MAP.get(ctx.id + key)?.localData || null,
|
|
1093
|
-
templateReplace,
|
|
1094
|
-
clear,
|
|
1095
|
-
remove,
|
|
1096
|
-
rendererContext: ctx,
|
|
1097
|
-
getDomNodes,
|
|
1098
|
-
getSave,
|
|
1099
|
-
contextKey: ctx.id,
|
|
1100
|
-
paused: ctx.meta.preview ? immutable(false) : paused,
|
|
1101
|
-
ticker,
|
|
1102
|
-
request
|
|
1103
|
-
});
|
|
1104
|
-
};
|
|
1105
|
-
|
|
1106
|
-
// src/preloading.ts
|
|
1107
|
-
var ACTION_NAME_TO_VOLUME_MAP = {
|
|
1108
|
-
playMusic: "music",
|
|
1109
|
-
playSound: "sound",
|
|
1110
|
-
voice: "voice"
|
|
1111
|
-
};
|
|
1112
|
-
var enqueueAssetForPreloading = (asset2) => {
|
|
1113
|
-
if (!PRELOADED_ASSETS.has(asset2)) {
|
|
1114
|
-
ASSETS_TO_PRELOAD.add(asset2);
|
|
1115
|
-
}
|
|
1116
|
-
};
|
|
1117
|
-
var handleAssetsPreloading = async ({
|
|
1118
|
-
request,
|
|
1119
|
-
limiter,
|
|
1120
|
-
preloadAudioBlocking,
|
|
1121
|
-
preloadImageBlocking
|
|
1122
|
-
}) => {
|
|
1123
|
-
const list = mapSet(ASSETS_TO_PRELOAD, (asset2) => {
|
|
1124
|
-
return limiter(async () => {
|
|
1125
|
-
const type = await getResourseType({
|
|
1126
|
-
url: asset2,
|
|
1127
|
-
request
|
|
1128
|
-
});
|
|
1129
|
-
switch (type) {
|
|
1130
|
-
case "audio": {
|
|
1131
|
-
await preloadAudioBlocking(asset2);
|
|
1132
|
-
break;
|
|
1133
|
-
}
|
|
1134
|
-
case "image": {
|
|
1135
|
-
await preloadImageBlocking(asset2);
|
|
1136
|
-
break;
|
|
1137
|
-
}
|
|
1138
|
-
}
|
|
1139
|
-
ASSETS_TO_PRELOAD.delete(asset2);
|
|
1140
|
-
PRELOADED_ASSETS.add(asset2);
|
|
1141
|
-
});
|
|
1142
|
-
});
|
|
1143
|
-
await Promise.allSettled(list);
|
|
1144
|
-
ASSETS_TO_PRELOAD.clear();
|
|
1145
|
-
};
|
|
1146
|
-
var huntAssets = async ({ volume, lang, characters, action, props, handle, request }) => {
|
|
1147
|
-
if (action === "showBackground") {
|
|
1148
|
-
if (isAsset(props[0]) || isString(props[0])) {
|
|
1149
|
-
handle(unwrapImageAsset(props[0]));
|
|
1150
|
-
return;
|
|
1151
|
-
}
|
|
1152
|
-
if (props[0] && typeof props[0] === "object") {
|
|
1153
|
-
for (const value of Object.values(props[0])) {
|
|
1154
|
-
if (isAsset(value)) {
|
|
1155
|
-
handle(unwrapImageAsset(value));
|
|
1156
|
-
} else {
|
|
1157
|
-
handle(value);
|
|
1158
|
-
}
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
return;
|
|
1162
|
-
}
|
|
1163
|
-
const getVolumeFor = (action2) => {
|
|
1164
|
-
if (action2 in ACTION_NAME_TO_VOLUME_MAP) {
|
|
1165
|
-
return volume[ACTION_NAME_TO_VOLUME_MAP[action2]];
|
|
1166
|
-
}
|
|
1167
|
-
return 0;
|
|
1168
|
-
};
|
|
1169
|
-
if (isAudioAction(action) && isString(props[0])) {
|
|
1170
|
-
if (getVolumeFor(action) > 0) {
|
|
1171
|
-
handle(unwrapAudioAsset(props[0]));
|
|
1172
|
-
}
|
|
1173
|
-
return;
|
|
1174
|
-
}
|
|
1175
|
-
if (action === "voice" && typeof props[0] === "object") {
|
|
1176
|
-
if (getVolumeFor("voice") == 0) {
|
|
1177
|
-
return;
|
|
1178
|
-
}
|
|
1179
|
-
for (const [language, value] of Object.entries(props[0])) {
|
|
1180
|
-
if (language === lang) {
|
|
1181
|
-
value && handle(unwrapAudioAsset(value));
|
|
1182
|
-
}
|
|
1183
|
-
}
|
|
1184
|
-
return;
|
|
1185
|
-
}
|
|
1186
|
-
if (action === "showCharacter" && isString(props[0]) && isString(props[1])) {
|
|
1187
|
-
const images = toArray(characters[props[0]].emotions[props[1]]);
|
|
1188
|
-
for (const asset2 of images) {
|
|
1189
|
-
handle(unwrapImageAsset(asset2));
|
|
1190
|
-
}
|
|
1191
|
-
return;
|
|
1192
|
-
}
|
|
1193
|
-
if (action === "custom" && props[0].assets) {
|
|
1194
|
-
const assets = props[0].assets;
|
|
1195
|
-
let resolved = [];
|
|
1196
|
-
if (typeof assets === "function") {
|
|
1197
|
-
resolved = await Promise.race([
|
|
1198
|
-
assets({ request }),
|
|
1199
|
-
new Promise((resolve) => setTimeout(resolve, 250, []))
|
|
1200
|
-
]);
|
|
1201
|
-
Object.defineProperty(props[0], "assets", {
|
|
1202
|
-
value: async () => resolved,
|
|
1203
|
-
writable: false
|
|
1204
|
-
});
|
|
1205
|
-
} else {
|
|
1206
|
-
resolved = assets;
|
|
1207
|
-
}
|
|
1208
|
-
for (const asset2 of resolved) {
|
|
1209
|
-
isAsset(asset2) ? handle(asset2.source) : handle(asset2);
|
|
1210
|
-
}
|
|
1211
|
-
return;
|
|
1212
|
-
}
|
|
1213
|
-
if (action === "choice") {
|
|
1214
|
-
for (let i = 1; i < props.length; i++) {
|
|
1215
|
-
const data = props[i];
|
|
1216
|
-
if (Array.isArray(data)) {
|
|
1217
|
-
if (data[5]) {
|
|
1218
|
-
handle(unwrapImageAsset(data[5]));
|
|
1219
|
-
}
|
|
1220
|
-
}
|
|
1221
|
-
}
|
|
1222
|
-
}
|
|
1223
|
-
};
|
|
1224
|
-
|
|
1225
|
-
// src/storage.ts
|
|
1226
|
-
var storageAdapterLocal = ({ key }) => {
|
|
1227
|
-
return {
|
|
1228
|
-
async get() {
|
|
1229
|
-
const fallback = { saves: [], data: {}, meta: [] };
|
|
1230
|
-
try {
|
|
1231
|
-
const value = localStorage.getItem(key);
|
|
1232
|
-
return value ? JSON.parse(value) : fallback;
|
|
1233
|
-
} catch {
|
|
1234
|
-
return fallback;
|
|
1235
|
-
}
|
|
1236
|
-
},
|
|
1237
|
-
async set(data) {
|
|
1238
|
-
try {
|
|
1239
|
-
localStorage.setItem(key, JSON.stringify(data));
|
|
1240
|
-
} catch {
|
|
1241
|
-
}
|
|
1242
|
-
}
|
|
1243
|
-
};
|
|
1244
|
-
};
|
|
1245
|
-
|
|
1246
|
-
// src/translation.ts
|
|
1247
|
-
var RGX = /{{(.*?)}}/g;
|
|
1248
|
-
var split = (input, delimeters) => {
|
|
1249
|
-
const output = [];
|
|
1250
|
-
for (const delimeter of delimeters) {
|
|
1251
|
-
if (!input) break;
|
|
1252
|
-
const [start, end] = input.split(delimeter, 2);
|
|
1253
|
-
output.push(start);
|
|
1254
|
-
input = end;
|
|
1255
|
-
}
|
|
1256
|
-
output.push(input);
|
|
1257
|
-
return output;
|
|
1258
|
-
};
|
|
1259
|
-
var flattenAllowedContent = (c, state) => {
|
|
1260
|
-
if (Array.isArray(c)) {
|
|
1261
|
-
return c.map((item) => flattenAllowedContent(item, state)).join("<br>");
|
|
1262
|
-
}
|
|
1263
|
-
if (typeof c === "function") {
|
|
1264
|
-
return flattenAllowedContent(c(state), state);
|
|
1265
|
-
}
|
|
1266
|
-
return c;
|
|
1267
|
-
};
|
|
1268
|
-
var replace = (input, data, pluralization, actions, pr) => {
|
|
1269
|
-
return input.replaceAll(RGX, (x, key, y) => {
|
|
1270
|
-
x = 0;
|
|
1271
|
-
y = data;
|
|
1272
|
-
const [pathstr, plural, action] = split(key.trim(), ["@", "%"]);
|
|
1273
|
-
if (!pathstr) {
|
|
1274
|
-
return "";
|
|
1275
|
-
}
|
|
1276
|
-
const path = pathstr.split(".");
|
|
1277
|
-
while (y && x < path.length) y = y[path[x++]];
|
|
1278
|
-
if (plural && pluralization && y && pr) {
|
|
1279
|
-
y = pluralization[plural][pr.select(y)];
|
|
1280
|
-
}
|
|
1281
|
-
const actionHandler = actions && action ? actions[action] : void 0;
|
|
1282
|
-
if (actionHandler) y = actionHandler(y);
|
|
1283
|
-
return y == null ? "" : y;
|
|
1284
|
-
});
|
|
1285
|
-
};
|
|
1286
|
-
|
|
1287
|
-
// src/utilities/actions.ts
|
|
1288
|
-
import { DEV as DEV4 } from "esm-env";
|
|
1289
|
-
var VIRTUAL_ACTIONS = ["say"];
|
|
1290
|
-
var buildActionObject = ({
|
|
1291
|
-
rendererActions,
|
|
1292
|
-
nativeActions,
|
|
1293
|
-
characters
|
|
1294
|
-
}) => {
|
|
1295
|
-
const allActions = [...nativeActions, ...VIRTUAL_ACTIONS];
|
|
1296
|
-
const object = { ...rendererActions };
|
|
1297
|
-
for (let action of allActions) {
|
|
1298
|
-
object[action] = (...props) => {
|
|
1299
|
-
if (action === "say") {
|
|
1300
|
-
action = "dialog";
|
|
1301
|
-
const [character] = props;
|
|
1302
|
-
if (DEV4 && !characters[character]) {
|
|
1303
|
-
throw new Error(`Attempt to call Say action with unknown character "${character}"`);
|
|
1304
|
-
}
|
|
1305
|
-
} else if (action === "choice") {
|
|
1306
|
-
if (props.slice(1).every((choice) => !Array.isArray(choice))) {
|
|
1307
|
-
for (let i = 1; i < props.length; i++) {
|
|
1308
|
-
const choice = props[i];
|
|
1309
|
-
props[i] = [
|
|
1310
|
-
choice.title,
|
|
1311
|
-
flatActions(choice.children),
|
|
1312
|
-
choice.active,
|
|
1313
|
-
choice.visible,
|
|
1314
|
-
choice.onSelect,
|
|
1315
|
-
choice.image
|
|
1316
|
-
];
|
|
1317
|
-
}
|
|
1318
|
-
} else {
|
|
1319
|
-
for (let i = 1; i < props.length; i++) {
|
|
1320
|
-
const choice = props[i];
|
|
1321
|
-
if (Array.isArray(choice)) {
|
|
1322
|
-
choice[1] = flatActions(choice[1]);
|
|
1323
|
-
}
|
|
1324
|
-
}
|
|
1325
|
-
}
|
|
1326
|
-
} else if (action === "condition") {
|
|
1327
|
-
const actions = props[1];
|
|
1328
|
-
for (const key in actions) {
|
|
1329
|
-
actions[key] = flatActions(actions[key]);
|
|
1330
|
-
}
|
|
1331
|
-
}
|
|
1332
|
-
return [action, ...props];
|
|
1333
|
-
};
|
|
1334
|
-
}
|
|
1335
|
-
return object;
|
|
1336
|
-
};
|
|
1337
|
-
|
|
1338
|
-
// src/utilities/dialog-overview.ts
|
|
1339
|
-
var getDialogOverview = async function() {
|
|
1340
|
-
const { value: save } = this.getStack();
|
|
1341
|
-
const stateSnapshots = save[3];
|
|
1342
|
-
if (stateSnapshots.length == 0) {
|
|
1343
|
-
return [];
|
|
1344
|
-
}
|
|
1345
|
-
const { queue } = await getActionsFromPath({
|
|
1346
|
-
story: this.story,
|
|
1347
|
-
path: save[0],
|
|
1348
|
-
filter: false,
|
|
1349
|
-
referGuarded: this.referGuarded
|
|
1350
|
-
});
|
|
1351
|
-
const lang = this.getLanguage();
|
|
1352
|
-
const dialogItems = [];
|
|
1353
|
-
for (let p = 0, a = stateSnapshots.length, i = queue.length - 1; a > 0 && i > 0; i--) {
|
|
1354
|
-
const action = queue[i];
|
|
1355
|
-
if (action[0] === "dialog") {
|
|
1356
|
-
const [_, name, text] = action;
|
|
1357
|
-
let voice = void 0;
|
|
1358
|
-
for (let j = i - 1; j > p && j > 0; j--) {
|
|
1359
|
-
const action2 = queue[j];
|
|
1360
|
-
if (isUserRequiredAction(action2) || isSkippedDuringRestore(action2[0])) break;
|
|
1361
|
-
if (action2[0] === "stopVoice") break;
|
|
1362
|
-
if (action2[0] === "voice") {
|
|
1363
|
-
voice = action2[1];
|
|
1364
|
-
break;
|
|
1365
|
-
}
|
|
1366
|
-
}
|
|
1367
|
-
dialogItems.push({
|
|
1368
|
-
name,
|
|
1369
|
-
text,
|
|
1370
|
-
voice
|
|
1371
|
-
});
|
|
1372
|
-
p = i;
|
|
1373
|
-
a--;
|
|
1374
|
-
}
|
|
1375
|
-
}
|
|
1376
|
-
const entries = dialogItems.reverse().map(({ name, text, voice }, i) => {
|
|
1377
|
-
const state = stateSnapshots[i];
|
|
1378
|
-
const audioSource = isString(voice) ? voice : isAsset(voice) ? voice : voice == void 0 ? voice : voice[lang];
|
|
1379
|
-
name = name ? this.getCharacterName(name) : "";
|
|
1380
|
-
return {
|
|
1381
|
-
name: this.templateReplace(name, state),
|
|
1382
|
-
text: this.templateReplace(text, state),
|
|
1383
|
-
voice: audioSource ? unwrapAudioAsset(audioSource) : ""
|
|
1384
|
-
};
|
|
1385
|
-
});
|
|
1386
|
-
return entries;
|
|
1387
|
-
};
|
|
1388
|
-
|
|
1389
|
-
// src/utilities/document.ts
|
|
1390
|
-
var setDocumentLanguage = (language) => {
|
|
1391
|
-
document.documentElement.lang = language;
|
|
1392
|
-
};
|
|
1393
|
-
|
|
1394
|
-
// src/ticker.ts
|
|
1395
|
-
var Ticker = class {
|
|
1396
|
-
listeners = /* @__PURE__ */ new Set();
|
|
1397
|
-
running = false;
|
|
1398
|
-
_factory;
|
|
1399
|
-
constructor(factory) {
|
|
1400
|
-
this._factory = factory;
|
|
1401
|
-
}
|
|
1402
|
-
get deltaTime() {
|
|
1403
|
-
return this._factory.deltaTime;
|
|
1404
|
-
}
|
|
1405
|
-
get lastTime() {
|
|
1406
|
-
return this._factory.lastTime;
|
|
1407
|
-
}
|
|
1408
|
-
add(cb) {
|
|
1409
|
-
this.listeners.add(cb);
|
|
1410
|
-
if (this.listeners.size === 1) {
|
|
1411
|
-
this._factory.check(true);
|
|
1412
|
-
}
|
|
1413
|
-
return () => {
|
|
1414
|
-
this.remove(cb);
|
|
1415
|
-
};
|
|
1416
|
-
}
|
|
1417
|
-
remove(cb) {
|
|
1418
|
-
this.listeners.delete(cb);
|
|
1419
|
-
if (this.listeners.size === 0) {
|
|
1420
|
-
this._factory.check(false);
|
|
1421
|
-
}
|
|
1422
|
-
}
|
|
1423
|
-
start = () => {
|
|
1424
|
-
this.running = true;
|
|
1425
|
-
if (this.listeners.size > 0) {
|
|
1426
|
-
this._factory.check(true);
|
|
1427
|
-
}
|
|
1428
|
-
};
|
|
1429
|
-
stop = () => {
|
|
1430
|
-
this.running = false;
|
|
1431
|
-
};
|
|
1432
|
-
detach = () => {
|
|
1433
|
-
this.listeners.clear();
|
|
1434
|
-
this.stop();
|
|
1435
|
-
this._factory.detach(this);
|
|
1436
|
-
};
|
|
1437
|
-
};
|
|
1438
|
-
var TickerFactory = class {
|
|
1439
|
-
_children = /* @__PURE__ */ new Set();
|
|
1440
|
-
_raf = -1;
|
|
1441
|
-
_running = false;
|
|
1442
|
-
_unsubscribe;
|
|
1443
|
-
deltaTime = 0;
|
|
1444
|
-
lastTime = performance.now();
|
|
1445
|
-
constructor(paused) {
|
|
1446
|
-
this._unsubscribe = paused.subscribe((paused2) => {
|
|
1447
|
-
if (paused2) {
|
|
1448
|
-
this.stop();
|
|
1449
|
-
} else if (Array.from(this._children).some((ticker) => ticker.running && ticker.listeners.size > 0)) {
|
|
1450
|
-
this.start();
|
|
1451
|
-
}
|
|
1452
|
-
});
|
|
1453
|
-
}
|
|
1454
|
-
start() {
|
|
1455
|
-
if (this._running) {
|
|
1456
|
-
return;
|
|
1457
|
-
}
|
|
1458
|
-
cancelAnimationFrame(this._raf);
|
|
1459
|
-
this.lastTime = performance.now();
|
|
1460
|
-
this._running = true;
|
|
1461
|
-
this._raf = requestAnimationFrame(this.update);
|
|
1462
|
-
}
|
|
1463
|
-
stop() {
|
|
1464
|
-
cancelAnimationFrame(this._raf);
|
|
1465
|
-
this._running = false;
|
|
1466
|
-
this._raf = -1;
|
|
1467
|
-
}
|
|
1468
|
-
fork() {
|
|
1469
|
-
const ticker = new Ticker(this);
|
|
1470
|
-
this._children.add(ticker);
|
|
1471
|
-
return ticker;
|
|
1472
|
-
}
|
|
1473
|
-
check(positive) {
|
|
1474
|
-
if (positive) {
|
|
1475
|
-
this.start();
|
|
1476
|
-
} else if (Array.from(this._children).every((ticker) => !ticker.running || ticker.listeners.size === 0)) {
|
|
1477
|
-
this.stop();
|
|
1478
|
-
}
|
|
1479
|
-
}
|
|
1480
|
-
destroy() {
|
|
1481
|
-
this._unsubscribe();
|
|
1482
|
-
this._children.forEach((child) => child.detach());
|
|
1483
|
-
}
|
|
1484
|
-
detach(ticker) {
|
|
1485
|
-
this._children.delete(ticker);
|
|
1486
|
-
this.check(false);
|
|
1487
|
-
}
|
|
1488
|
-
update = (currentTime) => {
|
|
1489
|
-
this.deltaTime = currentTime - this.lastTime;
|
|
1490
|
-
this._children.forEach((ticker) => {
|
|
1491
|
-
if (ticker.running) {
|
|
1492
|
-
ticker.listeners.forEach((tick) => {
|
|
1493
|
-
tick(ticker);
|
|
1494
|
-
});
|
|
1495
|
-
}
|
|
1496
|
-
});
|
|
1497
|
-
if (!this._running) {
|
|
1498
|
-
return;
|
|
1499
|
-
}
|
|
1500
|
-
this.lastTime = currentTime;
|
|
1501
|
-
this._raf = requestAnimationFrame(this.update);
|
|
1502
|
-
};
|
|
1503
|
-
};
|
|
1504
|
-
|
|
1505
|
-
// src/novely.ts
|
|
1506
|
-
var novely = ({
|
|
1507
|
-
characters,
|
|
1508
|
-
characterAssetSizes = {},
|
|
1509
|
-
defaultEmotions = {},
|
|
1510
|
-
storage = storageAdapterLocal({ key: "novely-game-storage" }),
|
|
1511
|
-
storageDelay = Promise.resolve(),
|
|
1512
|
-
renderer: createRenderer,
|
|
1513
|
-
initialScreen = "mainmenu",
|
|
1514
|
-
translation,
|
|
1515
|
-
state: defaultState = {},
|
|
1516
|
-
data: defaultData = {},
|
|
1517
|
-
autosaves = true,
|
|
1518
|
-
migrations = [],
|
|
1519
|
-
throttleTimeout = 850,
|
|
1520
|
-
getLanguage: getLanguage2 = getLanguage,
|
|
1521
|
-
overrideLanguage = false,
|
|
1522
|
-
askBeforeExit = true,
|
|
1523
|
-
preloadAssets = "automatic",
|
|
1524
|
-
parallelAssetsDownloadLimit = 15,
|
|
1525
|
-
fetch: request = fetch,
|
|
1526
|
-
cloneFunction: clone = klona,
|
|
1527
|
-
saveOnUnload = true,
|
|
1528
|
-
startKey = "start",
|
|
1529
|
-
defaultTypewriterSpeed = DEFAULT_TYPEWRITER_SPEED,
|
|
1530
|
-
storyOptions = { mode: "static" },
|
|
1531
|
-
onLanguageChange
|
|
1532
|
-
}) => {
|
|
1533
|
-
const languages = Object.keys(translation);
|
|
1534
|
-
const limitScript = pLimit(1);
|
|
1535
|
-
const limitAssetsDownload = pLimit(parallelAssetsDownloadLimit);
|
|
1536
|
-
const story = {};
|
|
1537
|
-
const times = /* @__PURE__ */ new Set();
|
|
1538
|
-
const dataLoaded = createControlledPromise();
|
|
1539
|
-
let initialScreenWasShown = false;
|
|
1540
|
-
let destroyed = false;
|
|
1541
|
-
if (storyOptions.mode === "dynamic") {
|
|
1542
|
-
storyOptions.preloadSaves ??= 4;
|
|
1543
|
-
}
|
|
1544
|
-
const storyLoad = storyOptions.mode === "static" ? noop : storyOptions.load;
|
|
1545
|
-
const onUnknownSceneHit = memoize5(async (scene) => {
|
|
1546
|
-
const part = await storyLoad(scene);
|
|
1547
|
-
if (part) {
|
|
1548
|
-
await script(part);
|
|
1549
|
-
}
|
|
1550
|
-
});
|
|
1551
|
-
const intime = (value) => {
|
|
1552
|
-
return times.add(value), value;
|
|
1553
|
-
};
|
|
1554
|
-
const scriptBase = async (part) => {
|
|
1555
|
-
if (destroyed) return;
|
|
1556
|
-
Object.assign(story, flatStory(part));
|
|
1557
|
-
if (!initialScreenWasShown) {
|
|
1558
|
-
renderer.ui.showLoading();
|
|
1559
|
-
}
|
|
1560
|
-
await dataLoaded.promise;
|
|
1561
|
-
renderer.ui.hideLoading();
|
|
1562
|
-
if (!initialScreenWasShown) {
|
|
1563
|
-
initialScreenWasShown = true;
|
|
1564
|
-
if (initialScreen === "game") {
|
|
1565
|
-
restore(void 0);
|
|
1566
|
-
} else {
|
|
1567
|
-
renderer.ui.showScreen(initialScreen);
|
|
1568
|
-
}
|
|
1569
|
-
}
|
|
1570
|
-
};
|
|
1571
|
-
const script = (part) => {
|
|
1572
|
-
return limitScript(() => scriptBase(part));
|
|
1573
|
-
};
|
|
1574
|
-
const getDefaultSave = (state) => {
|
|
1575
|
-
return [
|
|
1576
|
-
[
|
|
1577
|
-
["jump", startKey],
|
|
1578
|
-
[null, 0]
|
|
1579
|
-
],
|
|
1580
|
-
state,
|
|
1581
|
-
[intime(Date.now()), "auto"],
|
|
1582
|
-
[]
|
|
1583
|
-
];
|
|
1584
|
-
};
|
|
1585
|
-
const getLanguageWithoutParameters = () => {
|
|
1586
|
-
const language = getLanguage2(languages, getLanguage);
|
|
1587
|
-
if (languages.includes(language)) {
|
|
1588
|
-
setDocumentLanguage(language);
|
|
1589
|
-
return language;
|
|
1590
|
-
}
|
|
1591
|
-
if (DEV5) {
|
|
1592
|
-
throw new Error(
|
|
1593
|
-
`Attempt to use unsupported language "${language}". Supported languages: ${languages.join(", ")}.`
|
|
1594
|
-
);
|
|
1595
|
-
}
|
|
1596
|
-
throw 0;
|
|
1597
|
-
};
|
|
1598
|
-
const initialData = {
|
|
1599
|
-
saves: [],
|
|
1600
|
-
data: clone(defaultData),
|
|
1601
|
-
meta: [getLanguageWithoutParameters(), DEFAULT_TYPEWRITER_SPEED, 1, 1, 1]
|
|
1602
|
-
};
|
|
1603
|
-
const storageData = store(initialData);
|
|
1604
|
-
const coreData = store({
|
|
1605
|
-
dataLoaded: false,
|
|
1606
|
-
paused: false,
|
|
1607
|
-
focused: document.visibilityState === "visible"
|
|
1608
|
-
});
|
|
1609
|
-
const paused = derive(coreData, (s) => s.paused || !s.focused);
|
|
1610
|
-
const onDataLoadedPromise = async ({ cancelled }) => {
|
|
1611
|
-
if (cancelled) {
|
|
1612
|
-
dataLoaded.promise.then(onDataLoadedPromise);
|
|
1613
|
-
return;
|
|
1614
|
-
}
|
|
1615
|
-
const preload = () => {
|
|
1616
|
-
const saves = [...storageData.get().saves].reverse();
|
|
1617
|
-
const sliced = saves.slice(0, storyOptions.mode === "dynamic" ? storyOptions.preloadSaves : 0);
|
|
1618
|
-
for (const [path] of sliced) {
|
|
1619
|
-
referGuarded(path);
|
|
1620
|
-
}
|
|
1621
|
-
};
|
|
1622
|
-
preload();
|
|
1623
|
-
coreData.update((data2) => {
|
|
1624
|
-
data2.dataLoaded = true;
|
|
1625
|
-
return data2;
|
|
1626
|
-
});
|
|
1627
|
-
};
|
|
1628
|
-
dataLoaded.promise.then(onDataLoadedPromise);
|
|
1629
|
-
const onStorageDataChange = (value) => {
|
|
1630
|
-
if (!coreData.get().dataLoaded) return;
|
|
1631
|
-
const data2 = clone(value);
|
|
1632
|
-
for (const save2 of data2.saves) {
|
|
1633
|
-
save2[3] = [];
|
|
1634
|
-
}
|
|
1635
|
-
storage.set(data2);
|
|
1636
|
-
};
|
|
1637
|
-
const throttledShortOnStorageDataChange = throttle(() => onStorageDataChange(storageData.get()), 10);
|
|
1638
|
-
const throttledOnStorageDataChange = throttle(throttledShortOnStorageDataChange, throttleTimeout);
|
|
1639
|
-
storageData.subscribe(throttledOnStorageDataChange);
|
|
1640
|
-
if (saveOnUnload === true || saveOnUnload === "prod" && !DEV5) {
|
|
1641
|
-
addEventListener("beforeunload", throttledShortOnStorageDataChange);
|
|
1642
|
-
}
|
|
1643
|
-
const getStoredData = async () => {
|
|
1644
|
-
let stored = await storage.get();
|
|
1645
|
-
for (const migration of migrations) {
|
|
1646
|
-
stored = migration(stored);
|
|
1647
|
-
if (DEV5 && !stored) {
|
|
1648
|
-
throw new Error("Migrations should return a value.");
|
|
1649
|
-
}
|
|
1650
|
-
}
|
|
1651
|
-
if (overrideLanguage || !stored.meta[0]) {
|
|
1652
|
-
stored.meta[0] = getLanguageWithoutParameters();
|
|
1653
|
-
}
|
|
1654
|
-
stored.meta[1] ||= defaultTypewriterSpeed;
|
|
1655
|
-
stored.meta[2] ??= 1;
|
|
1656
|
-
stored.meta[3] ??= 1;
|
|
1657
|
-
stored.meta[4] ??= 1;
|
|
1658
|
-
if (isEmpty(stored.data)) {
|
|
1659
|
-
stored.data = defaultData;
|
|
1660
|
-
}
|
|
1661
|
-
dataLoaded.resolve();
|
|
1662
|
-
storageData.set(stored);
|
|
1663
|
-
};
|
|
1664
|
-
storageDelay.then(getStoredData);
|
|
1665
|
-
const initial = getDefaultSave(clone(defaultState));
|
|
1666
|
-
const save = (type) => {
|
|
1667
|
-
if (!coreData.get().dataLoaded) return;
|
|
1668
|
-
if (!autosaves && type === "auto") return;
|
|
1669
|
-
const stack = useStack(MAIN_CONTEXT_KEY);
|
|
1670
|
-
const current = clone(stack.value);
|
|
1671
|
-
storageData.update((prev) => {
|
|
1672
|
-
const replace2 = () => {
|
|
1673
|
-
prev.saves[prev.saves.length - 1] = current;
|
|
1674
|
-
return prev;
|
|
1675
|
-
};
|
|
1676
|
-
const add = () => {
|
|
1677
|
-
prev.saves.push(current);
|
|
1678
|
-
return prev;
|
|
1679
|
-
};
|
|
1680
|
-
const last = prev.saves.at(-1);
|
|
1681
|
-
if (!last) return add();
|
|
1682
|
-
current[2][0] = intime(Date.now());
|
|
1683
|
-
current[2][1] = type;
|
|
1684
|
-
current[3] = [];
|
|
1685
|
-
const isIdentical = dequal(last[0], current[0]) && dequal(last[1], current[1]);
|
|
1686
|
-
const isLastMadeInCurrentSession = times.has(last[2][0]);
|
|
1687
|
-
if (isLastMadeInCurrentSession && last[2][1] === "auto" && type === "manual") {
|
|
1688
|
-
return replace2();
|
|
1689
|
-
}
|
|
1690
|
-
if (last[2][1] === "manual" && type === "auto" && isIdentical) {
|
|
1691
|
-
return prev;
|
|
1692
|
-
}
|
|
1693
|
-
if (isLastMadeInCurrentSession && last[2][1] === "auto" && type === "auto") {
|
|
1694
|
-
return replace2();
|
|
1695
|
-
}
|
|
1696
|
-
return add();
|
|
1697
|
-
});
|
|
1698
|
-
};
|
|
1699
|
-
const newGame = () => {
|
|
1700
|
-
if (!coreData.get().dataLoaded) return;
|
|
1701
|
-
const save2 = getDefaultSave(clone(defaultState));
|
|
1702
|
-
if (autosaves) {
|
|
1703
|
-
storageData.update((prev) => {
|
|
1704
|
-
return prev.saves.push(save2), prev;
|
|
1705
|
-
});
|
|
1706
|
-
}
|
|
1707
|
-
const context = renderer.getContext(MAIN_CONTEXT_KEY);
|
|
1708
|
-
const stack = useStack(context);
|
|
1709
|
-
stack.value = save2;
|
|
1710
|
-
context.meta.restoring = context.meta.goingBack = false;
|
|
1711
|
-
renderer.ui.showScreen("game");
|
|
1712
|
-
render(context);
|
|
1713
|
-
};
|
|
1714
|
-
const set2 = (save2, ctx) => {
|
|
1715
|
-
const stack = useStack(ctx || MAIN_CONTEXT_KEY);
|
|
1716
|
-
stack.value = save2;
|
|
1717
|
-
return restore(save2);
|
|
1718
|
-
};
|
|
1719
|
-
let interacted = 0;
|
|
1720
|
-
const restore = async (save2) => {
|
|
1721
|
-
if (isEmpty(story)) {
|
|
1722
|
-
if (DEV5) {
|
|
1723
|
-
throw new Error(
|
|
1724
|
-
"Story is empty. You should call an `enine.script` function [https://novely.pages.dev/guide/story.html]"
|
|
1725
|
-
);
|
|
1726
|
-
}
|
|
1727
|
-
return;
|
|
1728
|
-
}
|
|
1729
|
-
if (!coreData.get().dataLoaded) return;
|
|
1730
|
-
let latest = save2 || storageData.get().saves.at(-1);
|
|
1731
|
-
if (!latest) {
|
|
1732
|
-
latest = clone(initial);
|
|
1733
|
-
storageData.update((prev) => {
|
|
1734
|
-
prev.saves.push(latest);
|
|
1735
|
-
return prev;
|
|
1736
|
-
});
|
|
1737
|
-
}
|
|
1738
|
-
const context = renderer.getContext(MAIN_CONTEXT_KEY);
|
|
1739
|
-
const stack = useStack(context);
|
|
1740
|
-
context.meta.restoring = true;
|
|
1741
|
-
const previous = stack.previous;
|
|
1742
|
-
const [path] = stack.value = latest;
|
|
1743
|
-
renderer.ui.showScreen("game");
|
|
1744
|
-
const { found } = await refer(path);
|
|
1745
|
-
if (found) context.loading(true);
|
|
1746
|
-
const { queue, skip, skipPreserve } = await getActionsFromPath({
|
|
1747
|
-
story,
|
|
1748
|
-
path,
|
|
1749
|
-
filter: false,
|
|
1750
|
-
referGuarded
|
|
1751
|
-
});
|
|
1752
|
-
const cleanupHolder = getCustomActionCleanupHolder(context);
|
|
1753
|
-
if (previous) {
|
|
1754
|
-
const { queue: prevQueue } = await getActionsFromPath({
|
|
1755
|
-
story,
|
|
1756
|
-
path: previous[0],
|
|
1757
|
-
filter: false,
|
|
1758
|
-
referGuarded
|
|
1759
|
-
});
|
|
1760
|
-
const futures = [];
|
|
1761
|
-
const isFromDifferentBranches = previous[0][0][1] !== path[0][1];
|
|
1762
|
-
const end = isFromDifferentBranches ? 0 : queue.length - 1;
|
|
1763
|
-
for (let i = prevQueue.length - 1; i >= end; i--) {
|
|
1764
|
-
const [action2, fn] = prevQueue[i];
|
|
1765
|
-
if (action2 === "custom") {
|
|
1766
|
-
futures.push(fn);
|
|
1767
|
-
}
|
|
1768
|
-
}
|
|
1769
|
-
futures.reverse();
|
|
1770
|
-
const nodeCleanup = /* @__PURE__ */ new Set();
|
|
1771
|
-
for (const future of futures) {
|
|
1772
|
-
inner: for (let i = cleanupHolder.length - 1; i >= 0; i--) {
|
|
1773
|
-
const item = cleanupHolder[i];
|
|
1774
|
-
if (future === item.fn) {
|
|
1775
|
-
cleanCleanupSource(item);
|
|
1776
|
-
nodeCleanup.add(item.node);
|
|
1777
|
-
cleanupHolder.splice(i, 1);
|
|
1778
|
-
break inner;
|
|
1779
|
-
}
|
|
1780
|
-
}
|
|
1781
|
-
}
|
|
1782
|
-
nodeCleanup.forEach((f) => f());
|
|
1783
|
-
}
|
|
1784
|
-
const {
|
|
1785
|
-
run,
|
|
1786
|
-
keep: { keep, characters: characters2, audio: audio2 }
|
|
1787
|
-
} = createQueueProcessor(queue, {
|
|
1788
|
-
skip,
|
|
1789
|
-
skipPreserve
|
|
1790
|
-
});
|
|
1791
|
-
if (context.meta.goingBack) {
|
|
1792
|
-
match("clear", [keep, characters2, audio2], {
|
|
1793
|
-
ctx: context,
|
|
1794
|
-
data: latest[1]
|
|
1795
|
-
});
|
|
1796
|
-
}
|
|
1797
|
-
context.loading(false);
|
|
1798
|
-
const lastQueueItem = queue.at(-1);
|
|
1799
|
-
const lastQueueItemRequiresUserAction = lastQueueItem && isBlockingAction(lastQueueItem);
|
|
1800
|
-
await run((item) => {
|
|
1801
|
-
if (!latest) return;
|
|
1802
|
-
if (lastQueueItem === item && lastQueueItemRequiresUserAction) {
|
|
1803
|
-
return;
|
|
1804
|
-
}
|
|
1805
|
-
const [action2, ...props] = item;
|
|
1806
|
-
if (action2 === "custom") {
|
|
1807
|
-
if (cleanupHolder.some((holder) => holder.fn === props[0])) {
|
|
1808
|
-
return;
|
|
1809
|
-
}
|
|
1810
|
-
}
|
|
1811
|
-
return match(action2, props, {
|
|
1812
|
-
ctx: context,
|
|
1813
|
-
data: latest[1]
|
|
1814
|
-
});
|
|
1815
|
-
});
|
|
1816
|
-
if (!context.meta.goingBack) {
|
|
1817
|
-
context.meta.restoring = false;
|
|
1818
|
-
}
|
|
1819
|
-
await render(context);
|
|
1820
|
-
context.meta.restoring = context.meta.goingBack = false;
|
|
1821
|
-
};
|
|
1822
|
-
const { refer, referGuarded } = createReferFunction({
|
|
1823
|
-
story,
|
|
1824
|
-
onUnknownSceneHit
|
|
1825
|
-
});
|
|
1826
|
-
const exit = (force = false, saving = true) => {
|
|
1827
|
-
const ctx = renderer.getContext(MAIN_CONTEXT_KEY);
|
|
1828
|
-
const stack = useStack(ctx);
|
|
1829
|
-
const current = stack.value;
|
|
1830
|
-
const isSaved = () => {
|
|
1831
|
-
const { saves } = storageData.get();
|
|
1832
|
-
const [currentPath, currentData] = stack.value;
|
|
1833
|
-
return saves.some(
|
|
1834
|
-
([path, data2, [date, type2]]) => type2 === "manual" && times.has(date) && dequal(path, currentPath) && dequal(data2, currentData)
|
|
1835
|
-
);
|
|
1836
|
-
};
|
|
1837
|
-
if (interacted > 1 && !force && askBeforeExit && !isSaved()) {
|
|
1838
|
-
renderer.ui.showExitPrompt();
|
|
1839
|
-
return;
|
|
1840
|
-
}
|
|
1841
|
-
if (interacted > 0 && saving) {
|
|
1842
|
-
save("auto");
|
|
1843
|
-
}
|
|
1844
|
-
stack.clear();
|
|
1845
|
-
clearCustomActionsAtContext(ctx);
|
|
1846
|
-
ctx.clear(EMPTY_SET, EMPTY_SET, { music: EMPTY_SET, sounds: EMPTY_SET }, noop);
|
|
1847
|
-
renderer.ui.showScreen("mainmenu");
|
|
1848
|
-
ctx.audio.destroy();
|
|
1849
|
-
const [time, type] = current[2];
|
|
1850
|
-
if (type === "auto" && interacted <= 1 && times.has(time)) {
|
|
1851
|
-
storageData.update((prev) => {
|
|
1852
|
-
prev.saves = prev.saves.filter((save2) => save2 !== current);
|
|
1853
|
-
return prev;
|
|
1854
|
-
});
|
|
1855
|
-
}
|
|
1856
|
-
interactivity(false);
|
|
1857
|
-
times.clear();
|
|
1858
|
-
};
|
|
1859
|
-
const back = async () => {
|
|
1860
|
-
const stack = useStack(MAIN_CONTEXT_KEY);
|
|
1861
|
-
const valueBeforeBack = stack.value;
|
|
1862
|
-
stack.back();
|
|
1863
|
-
if (dequal(valueBeforeBack, stack.value) && !stack.previous) {
|
|
1864
|
-
return;
|
|
1865
|
-
}
|
|
1866
|
-
await restore(stack.value);
|
|
1867
|
-
};
|
|
1868
|
-
const t = (key, lang) => {
|
|
1869
|
-
return translation[lang].internal[key];
|
|
1870
|
-
};
|
|
1871
|
-
const preview = async (save2, name) => {
|
|
1872
|
-
if (isEmpty(story)) {
|
|
1873
|
-
return Promise.resolve({
|
|
1874
|
-
assets: []
|
|
1875
|
-
});
|
|
1876
|
-
}
|
|
1877
|
-
const [path, data2] = save2;
|
|
1878
|
-
const ctx = renderer.getContext(name);
|
|
1879
|
-
const { found } = await refer(path);
|
|
1880
|
-
if (found) ctx.loading(true);
|
|
1881
|
-
const { queue } = await getActionsFromPath({
|
|
1882
|
-
story,
|
|
1883
|
-
path,
|
|
1884
|
-
filter: true,
|
|
1885
|
-
referGuarded
|
|
1886
|
-
});
|
|
1887
|
-
ctx.loading(false);
|
|
1888
|
-
ctx.meta.restoring = true;
|
|
1889
|
-
ctx.meta.preview = true;
|
|
1890
|
-
const processor = createQueueProcessor(queue, {
|
|
1891
|
-
skip: EMPTY_SET
|
|
1892
|
-
});
|
|
1893
|
-
useStack(ctx).push(clone(save2));
|
|
1894
|
-
const assets = [];
|
|
1895
|
-
const huntPromises = [];
|
|
1896
|
-
await processor.run(([action2, ...props]) => {
|
|
1897
|
-
if (isAudioAction(action2)) return;
|
|
1898
|
-
if (action2 === "vibrate") return;
|
|
1899
|
-
if (action2 === "end") return;
|
|
1900
|
-
const huntPromise = huntAssets({
|
|
1901
|
-
action: action2,
|
|
1902
|
-
props,
|
|
1903
|
-
characters,
|
|
1904
|
-
lang: getLanguageFromStore(storageData),
|
|
1905
|
-
volume: getVolumeFromStore(storageData),
|
|
1906
|
-
handle: assets.push.bind(assets),
|
|
1907
|
-
request
|
|
1908
|
-
});
|
|
1909
|
-
huntPromises.push(huntPromise);
|
|
1910
|
-
return match(action2, props, {
|
|
1911
|
-
ctx,
|
|
1912
|
-
data: data2
|
|
1913
|
-
});
|
|
1914
|
-
});
|
|
1915
|
-
await Promise.all(huntPromises);
|
|
1916
|
-
return {
|
|
1917
|
-
assets
|
|
1918
|
-
};
|
|
1919
|
-
};
|
|
1920
|
-
const removeContext = (name) => {
|
|
1921
|
-
STACK_MAP.delete(name);
|
|
1922
|
-
};
|
|
1923
|
-
const getStateAtCtx = (context) => {
|
|
1924
|
-
return useStack(context).value[1];
|
|
1925
|
-
};
|
|
1926
|
-
const getStateFunction = (context) => {
|
|
1927
|
-
const stack = useStack(context);
|
|
1928
|
-
const state = (value) => {
|
|
1929
|
-
const _state = getStateAtCtx(context);
|
|
1930
|
-
if (!value) {
|
|
1931
|
-
return _state;
|
|
1932
|
-
}
|
|
1933
|
-
const prev = _state;
|
|
1934
|
-
const val = isFunction(value) ? value(prev) : deepmerge(prev, value);
|
|
1935
|
-
stack.value[1] = val;
|
|
1936
|
-
return void 0;
|
|
1937
|
-
};
|
|
1938
|
-
return state;
|
|
1939
|
-
};
|
|
1940
|
-
const getLanguageDisplayName = (lang) => {
|
|
1941
|
-
const language = translation[lang];
|
|
1942
|
-
if (DEV5 && !language) {
|
|
1943
|
-
throw new Error(
|
|
1944
|
-
`Attempt to use unsupported language "${language}". Supported languages: ${languages.join(", ")}.`
|
|
1945
|
-
);
|
|
1946
|
-
}
|
|
1947
|
-
return capitalize(language.nameOverride || getIntlLanguageDisplayName(lang));
|
|
1948
|
-
};
|
|
1949
|
-
const clearCustomActionsAtContext = (ctx) => {
|
|
1950
|
-
const cleanupHolder = getCustomActionCleanupHolder(ctx);
|
|
1951
|
-
const nodeCleanup = /* @__PURE__ */ new Set();
|
|
1952
|
-
for (const item of cleanupHolder) {
|
|
1953
|
-
cleanCleanupSource(item);
|
|
1954
|
-
nodeCleanup.add(item.node);
|
|
1955
|
-
}
|
|
1956
|
-
cleanupHolder.length = 0;
|
|
1957
|
-
nodeCleanup.forEach((fn) => fn());
|
|
1958
|
-
};
|
|
1959
|
-
const getResourseTypeWrapper = (url) => {
|
|
1960
|
-
return getResourseType({
|
|
1961
|
-
url,
|
|
1962
|
-
request
|
|
1963
|
-
});
|
|
1964
|
-
};
|
|
1965
|
-
const getCharacterColor = (c) => {
|
|
1966
|
-
return c in characters ? characters[c].color : "#000000";
|
|
1967
|
-
};
|
|
1968
|
-
const getCharacterAssets = (character, emotion) => {
|
|
1969
|
-
return toArray(characters[character].emotions[emotion]).map(unwrapImageAsset);
|
|
1970
|
-
};
|
|
1971
|
-
const getCharacterName = (character) => {
|
|
1972
|
-
const c = character;
|
|
1973
|
-
const cs = characters;
|
|
1974
|
-
const lang = getLanguageFromStore(storageData);
|
|
1975
|
-
if (c && c in cs) {
|
|
1976
|
-
const block = cs[c].name;
|
|
1977
|
-
if (typeof block === "string") {
|
|
1978
|
-
return block;
|
|
1979
|
-
}
|
|
1980
|
-
if (lang in block) {
|
|
1981
|
-
return block[lang];
|
|
1982
|
-
}
|
|
1983
|
-
}
|
|
1984
|
-
return String(c);
|
|
1985
|
-
};
|
|
1986
|
-
const setLanguage = (lang) => {
|
|
1987
|
-
storageData.update((prev) => {
|
|
1988
|
-
if (languages.includes(lang)) {
|
|
1989
|
-
prev.meta[0] = lang;
|
|
1990
|
-
}
|
|
1991
|
-
if (lang === prev.meta[0]) {
|
|
1992
|
-
setDocumentLanguage(lang);
|
|
1993
|
-
onLanguageChange?.(lang);
|
|
1994
|
-
}
|
|
1995
|
-
return prev;
|
|
1996
|
-
});
|
|
1997
|
-
};
|
|
1998
|
-
const renderer = createRenderer({
|
|
1999
|
-
mainContextKey: MAIN_CONTEXT_KEY,
|
|
2000
|
-
characters: getCharactersData(characters),
|
|
2001
|
-
characterAssetSizes,
|
|
2002
|
-
set: set2,
|
|
2003
|
-
restore,
|
|
2004
|
-
save,
|
|
2005
|
-
newGame,
|
|
2006
|
-
exit,
|
|
2007
|
-
back,
|
|
2008
|
-
t,
|
|
2009
|
-
preview,
|
|
2010
|
-
removeContext,
|
|
2011
|
-
getStateFunction,
|
|
2012
|
-
clearCustomActionsAtContext,
|
|
2013
|
-
languages,
|
|
2014
|
-
storageData,
|
|
2015
|
-
coreData,
|
|
2016
|
-
getLanguageDisplayName,
|
|
2017
|
-
getCharacterColor,
|
|
2018
|
-
getCharacterAssets,
|
|
2019
|
-
getDialogOverview: getDialogOverview.bind({
|
|
2020
|
-
referGuarded,
|
|
2021
|
-
story,
|
|
2022
|
-
getCharacterName,
|
|
2023
|
-
getLanguage: () => getLanguageFromStore(storageData),
|
|
2024
|
-
getStack: () => useStack(MAIN_CONTEXT_KEY),
|
|
2025
|
-
templateReplace: (...args) => templateReplace(...args)
|
|
2026
|
-
}),
|
|
2027
|
-
getResourseType: getResourseTypeWrapper,
|
|
2028
|
-
setLanguage
|
|
2029
|
-
});
|
|
2030
|
-
const useStack = createUseStackFunction(renderer);
|
|
2031
|
-
useStack(MAIN_CONTEXT_KEY).push(initial);
|
|
2032
|
-
const UIInstance = renderer.ui.start();
|
|
2033
|
-
const enmemory = (ctx) => {
|
|
2034
|
-
if (ctx.meta.restoring) return;
|
|
2035
|
-
const stack = useStack(ctx);
|
|
2036
|
-
const current = clone(stack.value);
|
|
2037
|
-
current[2][1] = "auto";
|
|
2038
|
-
stack.push(current);
|
|
2039
|
-
save("auto");
|
|
2040
|
-
};
|
|
2041
|
-
const next = (ctx) => {
|
|
2042
|
-
const stack = useStack(ctx);
|
|
2043
|
-
const path = stack.value[0];
|
|
2044
|
-
nextPath(path);
|
|
2045
|
-
};
|
|
2046
|
-
const matchActionOptions = {
|
|
2047
|
-
getContext: renderer.getContext,
|
|
2048
|
-
push(ctx) {
|
|
2049
|
-
if (ctx.meta.restoring) return;
|
|
2050
|
-
next(ctx);
|
|
2051
|
-
render(ctx);
|
|
2052
|
-
},
|
|
2053
|
-
forward(ctx) {
|
|
2054
|
-
if (!ctx.meta.preview) enmemory(ctx);
|
|
2055
|
-
matchActionOptions.push(ctx);
|
|
2056
|
-
if (!ctx.meta.preview) interactivity(true);
|
|
2057
|
-
},
|
|
2058
|
-
async onBeforeActionCall({ action: action2, props, ctx }) {
|
|
2059
|
-
if (preloadAssets !== "automatic") return;
|
|
2060
|
-
if (ctx.meta.preview || ctx.meta.restoring) return;
|
|
2061
|
-
if (!isBlockingAction([action2, ...props])) return;
|
|
2062
|
-
try {
|
|
2063
|
-
const collection = await collectActionsBeforeBlockingAction({
|
|
2064
|
-
path: nextPath(clone(useStack(ctx).value[0])),
|
|
2065
|
-
refer: referGuarded,
|
|
2066
|
-
clone
|
|
2067
|
-
});
|
|
2068
|
-
const queue = collection.map(([action3, ...props2]) => {
|
|
2069
|
-
return huntAssets({
|
|
2070
|
-
action: action3,
|
|
2071
|
-
props: props2,
|
|
2072
|
-
characters,
|
|
2073
|
-
lang: getLanguageFromStore(storageData),
|
|
2074
|
-
volume: getVolumeFromStore(storageData),
|
|
2075
|
-
handle: enqueueAssetForPreloading,
|
|
2076
|
-
request
|
|
2077
|
-
});
|
|
2078
|
-
});
|
|
2079
|
-
await Promise.all(queue);
|
|
2080
|
-
handleAssetsPreloading({
|
|
2081
|
-
...renderer.misc,
|
|
2082
|
-
request,
|
|
2083
|
-
limiter: limitAssetsDownload
|
|
2084
|
-
});
|
|
2085
|
-
} catch (cause) {
|
|
2086
|
-
console.error(cause);
|
|
2087
|
-
}
|
|
2088
|
-
}
|
|
2089
|
-
};
|
|
2090
|
-
const ticker = new TickerFactory(paused);
|
|
2091
|
-
const { match, nativeActions } = matchAction(matchActionOptions, {
|
|
2092
|
-
wait({ ctx, data: data2, push }, [time]) {
|
|
2093
|
-
if (ctx.meta.restoring) return;
|
|
2094
|
-
setTimeout(push, isFunction(time) ? time(data2) : time);
|
|
2095
|
-
},
|
|
2096
|
-
showBackground({ ctx, push }, [background]) {
|
|
2097
|
-
if (isString(background) || isAsset(background)) {
|
|
2098
|
-
ctx.background({
|
|
2099
|
-
all: unwrapImageAsset(background)
|
|
2100
|
-
});
|
|
2101
|
-
} else {
|
|
2102
|
-
ctx.background(
|
|
2103
|
-
Object.fromEntries(Object.entries(background).map(([media, asset2]) => [media, unwrapImageAsset(asset2)]))
|
|
2104
|
-
);
|
|
2105
|
-
}
|
|
2106
|
-
push();
|
|
2107
|
-
},
|
|
2108
|
-
playMusic({ ctx, push }, [source]) {
|
|
2109
|
-
ctx.audio.music(unwrapAudioAsset(source), paused, "music").play(true);
|
|
2110
|
-
push();
|
|
2111
|
-
},
|
|
2112
|
-
pauseMusic({ ctx, push }, [source]) {
|
|
2113
|
-
ctx.audio.music(unwrapAudioAsset(source), paused, "music").pause();
|
|
2114
|
-
push();
|
|
2115
|
-
},
|
|
2116
|
-
stopMusic({ ctx, push }, [source]) {
|
|
2117
|
-
ctx.audio.music(unwrapAudioAsset(source), paused, "music").stop();
|
|
2118
|
-
push();
|
|
2119
|
-
},
|
|
2120
|
-
playSound({ ctx, push }, [source, loop]) {
|
|
2121
|
-
ctx.audio.music(unwrapAudioAsset(source), paused, "sound").play(loop || false);
|
|
2122
|
-
push();
|
|
2123
|
-
},
|
|
2124
|
-
pauseSound({ ctx, push }, [source]) {
|
|
2125
|
-
ctx.audio.music(unwrapAudioAsset(source), paused, "sound").pause();
|
|
2126
|
-
push();
|
|
2127
|
-
},
|
|
2128
|
-
stopSound({ ctx, push }, [source]) {
|
|
2129
|
-
ctx.audio.music(unwrapAudioAsset(source), paused, "sound").stop();
|
|
2130
|
-
push();
|
|
2131
|
-
},
|
|
2132
|
-
voice({ ctx, push }, [source]) {
|
|
2133
|
-
const lang = getLanguageFromStore(storageData);
|
|
2134
|
-
const audioSource = isString(source) ? source : isAsset(source) ? source : source[lang];
|
|
2135
|
-
if (!audioSource) {
|
|
2136
|
-
push();
|
|
2137
|
-
return;
|
|
2138
|
-
}
|
|
2139
|
-
ctx.audio.voice(unwrapAudioAsset(audioSource), paused);
|
|
2140
|
-
push();
|
|
2141
|
-
},
|
|
2142
|
-
stopVoice({ ctx, push }) {
|
|
2143
|
-
ctx.audio.voiceStop();
|
|
2144
|
-
push();
|
|
2145
|
-
},
|
|
2146
|
-
showCharacter({ ctx, push }, [character, emotion, className, style]) {
|
|
2147
|
-
emotion ??= defaultEmotions[character];
|
|
2148
|
-
if (DEV5 && !emotion) {
|
|
2149
|
-
throw new Error(`Attemp to show character "${character}" without emotion provided.`);
|
|
2150
|
-
}
|
|
2151
|
-
if (!emotion) return;
|
|
2152
|
-
if (DEV5 && !characters[character].emotions[emotion]) {
|
|
2153
|
-
throw new Error(`Attempt to show character "${character}" with unknown emotion "${emotion}"`);
|
|
2154
|
-
}
|
|
2155
|
-
const handle = ctx.character(character);
|
|
2156
|
-
handle.append(className, style, ctx.meta.restoring);
|
|
2157
|
-
handle.emotion(emotion, true);
|
|
2158
|
-
push();
|
|
2159
|
-
},
|
|
2160
|
-
hideCharacter({ ctx, push }, [character, className, style, duration]) {
|
|
2161
|
-
ctx.character(character).remove(className, style, duration, ctx.meta.restoring).then(push);
|
|
2162
|
-
},
|
|
2163
|
-
dialog({ ctx, data: data2, forward }, [character, content, emotion]) {
|
|
2164
|
-
const name = getCharacterName(character);
|
|
2165
|
-
const stack = useStack(ctx);
|
|
2166
|
-
if (!ctx.meta.restoring && !ctx.meta.goingBack) {
|
|
2167
|
-
stack.value[3].push(clone(data2));
|
|
2168
|
-
}
|
|
2169
|
-
ctx.clearBlockingActions("dialog");
|
|
2170
|
-
ctx.dialog(templateReplace(content, data2), templateReplace(name, data2), character, emotion, forward);
|
|
2171
|
-
},
|
|
2172
|
-
function({ ctx, push }, [fn]) {
|
|
2173
|
-
const { restoring, goingBack, preview: preview2 } = ctx.meta;
|
|
2174
|
-
const result = fn({
|
|
2175
|
-
lang: getLanguageFromStore(storageData),
|
|
2176
|
-
goingBack,
|
|
2177
|
-
restoring,
|
|
2178
|
-
preview: preview2,
|
|
2179
|
-
state: getStateFunction(ctx)
|
|
2180
|
-
});
|
|
2181
|
-
if (!ctx.meta.restoring) {
|
|
2182
|
-
result ? result.then(push) : push();
|
|
2183
|
-
}
|
|
2184
|
-
return result;
|
|
2185
|
-
},
|
|
2186
|
-
choice({ ctx, data: data2 }, [question, ...choices]) {
|
|
2187
|
-
const isWithoutQuestion = Array.isArray(question);
|
|
2188
|
-
if (isWithoutQuestion) {
|
|
2189
|
-
choices.unshift(question);
|
|
2190
|
-
question = "";
|
|
2191
|
-
}
|
|
2192
|
-
const transformedChoices = choices.map(([content, _children, active, visible, onSelect, image]) => {
|
|
2193
|
-
const active$ = store(false);
|
|
2194
|
-
const visible$ = store(false);
|
|
2195
|
-
const lang = getLanguageFromStore(storageData);
|
|
2196
|
-
const getCheckValue = (fn) => {
|
|
2197
|
-
if (!fn) {
|
|
2198
|
-
return true;
|
|
2199
|
-
}
|
|
2200
|
-
return fn({
|
|
2201
|
-
lang,
|
|
2202
|
-
state: getStateAtCtx(ctx)
|
|
2203
|
-
});
|
|
2204
|
-
};
|
|
2205
|
-
const update = () => {
|
|
2206
|
-
active$.set(getCheckValue(active));
|
|
2207
|
-
visible$.set(getCheckValue(visible));
|
|
2208
|
-
};
|
|
2209
|
-
update();
|
|
2210
|
-
const onSelectGuarded = onSelect || noop;
|
|
2211
|
-
const onSelectWrapped = () => {
|
|
2212
|
-
onSelectGuarded({
|
|
2213
|
-
recompute: update
|
|
2214
|
-
});
|
|
2215
|
-
};
|
|
2216
|
-
const imageValue = image ? unwrapImageAsset(image) : "";
|
|
2217
|
-
return [templateReplace(content, data2), active$, visible$, onSelectWrapped, imageValue];
|
|
2218
|
-
});
|
|
2219
|
-
if (DEV5 && transformedChoices.length === 0) {
|
|
2220
|
-
throw new Error(
|
|
2221
|
-
`Running choice without variants to choose from, look at how to use Choice action properly [https://novely.pages.dev/guide/actions/choice#usage]`
|
|
2222
|
-
);
|
|
2223
|
-
}
|
|
2224
|
-
ctx.clearBlockingActions("choice");
|
|
2225
|
-
ctx.choices(templateReplace(question, data2), transformedChoices, (selected) => {
|
|
2226
|
-
if (!ctx.meta.preview) {
|
|
2227
|
-
enmemory(ctx);
|
|
2228
|
-
}
|
|
2229
|
-
const stack = useStack(ctx);
|
|
2230
|
-
const offset = isWithoutQuestion ? 0 : 1;
|
|
2231
|
-
if (DEV5 && !transformedChoices[selected]) {
|
|
2232
|
-
throw new Error("Choice children is empty, either add content there or make item not selectable");
|
|
2233
|
-
}
|
|
2234
|
-
stack.value[0].push(["choice", selected + offset], [null, 0]);
|
|
2235
|
-
render(ctx);
|
|
2236
|
-
interactivity(true);
|
|
2237
|
-
});
|
|
2238
|
-
},
|
|
2239
|
-
jump({ ctx, data: data2 }, [scene]) {
|
|
2240
|
-
const stack = useStack(ctx);
|
|
2241
|
-
stack.value[0] = [
|
|
2242
|
-
["jump", scene],
|
|
2243
|
-
[null, -1]
|
|
2244
|
-
];
|
|
2245
|
-
stack.value[3] = [];
|
|
2246
|
-
match("clear", [], {
|
|
2247
|
-
ctx,
|
|
2248
|
-
data: data2
|
|
2249
|
-
});
|
|
2250
|
-
},
|
|
2251
|
-
clear({ ctx, push }, [keep, characters2, audio2]) {
|
|
2252
|
-
ctx.vibrate(0);
|
|
2253
|
-
ctx.clear(
|
|
2254
|
-
keep || EMPTY_SET,
|
|
2255
|
-
characters2 || EMPTY_SET,
|
|
2256
|
-
audio2 || { music: EMPTY_SET, sounds: EMPTY_SET },
|
|
2257
|
-
push
|
|
2258
|
-
);
|
|
2259
|
-
},
|
|
2260
|
-
condition({ ctx, data: data2 }, [condition, variants]) {
|
|
2261
|
-
if (DEV5 && Object.values(variants).length === 0) {
|
|
2262
|
-
throw new Error(`Attempt to use Condition action with empty variants object`);
|
|
2263
|
-
}
|
|
2264
|
-
if (!ctx.meta.restoring) {
|
|
2265
|
-
const val = String(condition(data2));
|
|
2266
|
-
if (DEV5 && !variants[val]) {
|
|
2267
|
-
throw new Error(`Attempt to go to unknown variant "${val}"`);
|
|
2268
|
-
}
|
|
2269
|
-
if (DEV5 && variants[val].length === 0) {
|
|
2270
|
-
throw new Error(`Attempt to go to empty variant "${val}"`);
|
|
2271
|
-
}
|
|
2272
|
-
const stack = useStack(ctx);
|
|
2273
|
-
stack.value[0].push(["condition", val], [null, 0]);
|
|
2274
|
-
render(ctx);
|
|
2275
|
-
}
|
|
2276
|
-
},
|
|
2277
|
-
end({ ctx }) {
|
|
2278
|
-
if (ctx.meta.preview) return;
|
|
2279
|
-
exit(true, false);
|
|
2280
|
-
},
|
|
2281
|
-
input({ ctx, data: data2, forward }, [question, onInput, setup]) {
|
|
2282
|
-
ctx.clearBlockingActions("input");
|
|
2283
|
-
ctx.input(templateReplace(question, data2), onInput, setup || noop, forward);
|
|
2284
|
-
},
|
|
2285
|
-
custom({ ctx, push }, [fn]) {
|
|
2286
|
-
if (fn.requireUserAction) {
|
|
2287
|
-
ctx.clearBlockingActions(void 0);
|
|
2288
|
-
}
|
|
2289
|
-
const state = getStateFunction(ctx);
|
|
2290
|
-
const lang = getLanguageFromStore(storageData);
|
|
2291
|
-
const result = handleCustomAction(ctx, fn, {
|
|
2292
|
-
...ctx.custom(fn),
|
|
2293
|
-
state,
|
|
2294
|
-
lang,
|
|
2295
|
-
getStack: useStack,
|
|
2296
|
-
paused,
|
|
2297
|
-
ticker: ticker.fork(),
|
|
2298
|
-
templateReplace,
|
|
2299
|
-
request
|
|
2300
|
-
});
|
|
2301
|
-
const next2 = () => {
|
|
2302
|
-
if (fn.requireUserAction && !ctx.meta.preview) {
|
|
2303
|
-
enmemory(ctx);
|
|
2304
|
-
interactivity(true);
|
|
2305
|
-
}
|
|
2306
|
-
push();
|
|
2307
|
-
};
|
|
2308
|
-
if (!ctx.meta.restoring || ctx.meta.goingBack) {
|
|
2309
|
-
if (isPromise(result)) {
|
|
2310
|
-
result.then(next2);
|
|
2311
|
-
} else {
|
|
2312
|
-
next2();
|
|
2313
|
-
}
|
|
2314
|
-
}
|
|
2315
|
-
return result;
|
|
2316
|
-
},
|
|
2317
|
-
vibrate({ ctx, push }, pattern) {
|
|
2318
|
-
ctx.vibrate(pattern);
|
|
2319
|
-
push();
|
|
2320
|
-
},
|
|
2321
|
-
next({ push }) {
|
|
2322
|
-
push();
|
|
2323
|
-
},
|
|
2324
|
-
animateCharacter({ ctx, push }, [character, className]) {
|
|
2325
|
-
const classes = className.split(" ");
|
|
2326
|
-
if (DEV5 && classes.length === 0) {
|
|
2327
|
-
throw new Error(
|
|
2328
|
-
"Attempt to use AnimateCharacter without classes. Classes should be provided [https://novely.pages.dev/guide/actions/animateCharacter.html]"
|
|
2329
|
-
);
|
|
2330
|
-
}
|
|
2331
|
-
if (ctx.meta.preview) return;
|
|
2332
|
-
ctx.character(character).animate(classes);
|
|
2333
|
-
push();
|
|
2334
|
-
},
|
|
2335
|
-
text({ ctx, data: data2, forward }, text) {
|
|
2336
|
-
const string = text.map((content) => templateReplace(content, data2)).join(" ");
|
|
2337
|
-
if (DEV5 && string.length === 0) {
|
|
2338
|
-
throw new Error(`Action Text was called with empty string or array`);
|
|
2339
|
-
}
|
|
2340
|
-
ctx.clearBlockingActions("text");
|
|
2341
|
-
ctx.text(string, forward);
|
|
2342
|
-
},
|
|
2343
|
-
async exit({ ctx, data: data2 }) {
|
|
2344
|
-
if (ctx.meta.restoring) return;
|
|
2345
|
-
const { exitImpossible } = await exitPath({
|
|
2346
|
-
path: useStack(ctx).value[0],
|
|
2347
|
-
refer: referGuarded,
|
|
2348
|
-
onExitImpossible: () => {
|
|
2349
|
-
match("end", [], {
|
|
2350
|
-
ctx,
|
|
2351
|
-
data: data2
|
|
2352
|
-
});
|
|
2353
|
-
}
|
|
2354
|
-
});
|
|
2355
|
-
if (exitImpossible) {
|
|
2356
|
-
ctx.clearBlockingActions(void 0);
|
|
2357
|
-
return;
|
|
2358
|
-
}
|
|
2359
|
-
render(ctx);
|
|
2360
|
-
},
|
|
2361
|
-
preload({ ctx, push }, [source]) {
|
|
2362
|
-
if (DEV5 && preloadAssets !== "lazy") {
|
|
2363
|
-
console.error(
|
|
2364
|
-
`You do not need a preload action because "preloadAssets" strategy was set to "${preloadAssets}"`
|
|
2365
|
-
);
|
|
2366
|
-
push();
|
|
2367
|
-
return;
|
|
2368
|
-
}
|
|
2369
|
-
const src = unwrapAsset(source);
|
|
2370
|
-
if (!ctx.meta.goingBack && !ctx.meta.restoring && !PRELOADED_ASSETS.has(src)) {
|
|
2371
|
-
const process = async () => {
|
|
2372
|
-
const type = isAsset(source) ? source.type : await getResourseTypeWrapper(src);
|
|
2373
|
-
if (type === "image") {
|
|
2374
|
-
renderer.misc.preloadAudioBlocking(src);
|
|
2375
|
-
} else if (type === "audio") {
|
|
2376
|
-
renderer.misc.preloadImage(src);
|
|
2377
|
-
} else {
|
|
2378
|
-
if (DEV5) {
|
|
2379
|
-
console.error(`Preload error: Unknown type of the following resource: `, source);
|
|
2380
|
-
}
|
|
2381
|
-
return;
|
|
2382
|
-
}
|
|
2383
|
-
PRELOADED_ASSETS.add(src);
|
|
2384
|
-
};
|
|
2385
|
-
process();
|
|
2386
|
-
}
|
|
2387
|
-
push();
|
|
2388
|
-
},
|
|
2389
|
-
block({ ctx }, [scene]) {
|
|
2390
|
-
if (DEV5 && !story[scene]) {
|
|
2391
|
-
throw new Error(`Attempt to call Block action with unknown scene "${scene}"`);
|
|
2392
|
-
}
|
|
2393
|
-
if (DEV5 && story[scene].length === 0) {
|
|
2394
|
-
throw new Error(`Attempt to call Block action with empty scene "${scene}"`);
|
|
2395
|
-
}
|
|
2396
|
-
if (!ctx.meta.restoring) {
|
|
2397
|
-
const stack = useStack(ctx);
|
|
2398
|
-
stack.value[0].push(["block", scene], [null, 0]);
|
|
2399
|
-
render(ctx);
|
|
2400
|
-
}
|
|
2401
|
-
}
|
|
2402
|
-
});
|
|
2403
|
-
const action = buildActionObject({
|
|
2404
|
-
rendererActions: renderer.actions,
|
|
2405
|
-
nativeActions,
|
|
2406
|
-
characters
|
|
2407
|
-
});
|
|
2408
|
-
const render = async (ctx) => {
|
|
2409
|
-
const stack = useStack(ctx);
|
|
2410
|
-
const [path, state] = stack.value;
|
|
2411
|
-
const { found, value } = await refer(path);
|
|
2412
|
-
if (found) {
|
|
2413
|
-
ctx.loading(true);
|
|
2414
|
-
}
|
|
2415
|
-
const referred = await value;
|
|
2416
|
-
if (found) {
|
|
2417
|
-
ctx.loading(false);
|
|
2418
|
-
}
|
|
2419
|
-
if (isAction(referred)) {
|
|
2420
|
-
const [action2, ...props] = referred;
|
|
2421
|
-
match(action2, props, {
|
|
2422
|
-
ctx,
|
|
2423
|
-
data: state
|
|
2424
|
-
});
|
|
2425
|
-
} else if (Object.values(story).some((branch) => branch === referred)) {
|
|
2426
|
-
match("end", [], {
|
|
2427
|
-
ctx,
|
|
2428
|
-
data: state
|
|
2429
|
-
});
|
|
2430
|
-
} else {
|
|
2431
|
-
match("exit", [], {
|
|
2432
|
-
ctx,
|
|
2433
|
-
data: state
|
|
2434
|
-
});
|
|
2435
|
-
}
|
|
2436
|
-
};
|
|
2437
|
-
const interactivity = (value = false) => {
|
|
2438
|
-
interacted = value ? interacted + 1 : 0;
|
|
2439
|
-
};
|
|
2440
|
-
const templateReplace = (content, values) => {
|
|
2441
|
-
const {
|
|
2442
|
-
data: data2,
|
|
2443
|
-
meta: [lang]
|
|
2444
|
-
} = storageData.get();
|
|
2445
|
-
const obj = values || data2;
|
|
2446
|
-
const str = flattenAllowedContent(!isFunction(content) && !isString(content) ? content[lang] : content, obj);
|
|
2447
|
-
const t2 = translation[lang];
|
|
2448
|
-
const pluralRules = (t2.plural || t2.actions) && new Intl.PluralRules(t2.tag || lang);
|
|
2449
|
-
return replace(str, obj, t2.plural, t2.actions, pluralRules);
|
|
2450
|
-
};
|
|
2451
|
-
const data = (value) => {
|
|
2452
|
-
const _data = storageData.get().data;
|
|
2453
|
-
if (!value) return _data;
|
|
2454
|
-
const val = isFunction(value) ? value(_data) : deepmerge(_data, value);
|
|
2455
|
-
storageData.update((prev) => {
|
|
2456
|
-
prev.data = val;
|
|
2457
|
-
return prev;
|
|
2458
|
-
});
|
|
2459
|
-
return void 0;
|
|
2460
|
-
};
|
|
2461
|
-
const getCurrentStorageData = () => {
|
|
2462
|
-
return coreData.get().dataLoaded ? clone(storageData.get()) : null;
|
|
2463
|
-
};
|
|
2464
|
-
const setStorageData = (data2) => {
|
|
2465
|
-
if (destroyed) {
|
|
2466
|
-
if (DEV5) {
|
|
2467
|
-
throw new Error(
|
|
2468
|
-
`function \`setStorageData\` was called after novely instance was destroyed. Data is not updater nor synced after destroy.`
|
|
2469
|
-
);
|
|
2470
|
-
}
|
|
2471
|
-
return;
|
|
2472
|
-
}
|
|
2473
|
-
storageData.set(data2);
|
|
2474
|
-
};
|
|
2475
|
-
return {
|
|
2476
|
-
/**
|
|
2477
|
-
* Function to set game script
|
|
2478
|
-
*
|
|
2479
|
-
* @example
|
|
2480
|
-
* ```ts
|
|
2481
|
-
* engine.script({
|
|
2482
|
-
* start: [
|
|
2483
|
-
* action.function(() => {})
|
|
2484
|
-
* ]
|
|
2485
|
-
* })
|
|
2486
|
-
* ```
|
|
2487
|
-
*/
|
|
2488
|
-
script,
|
|
2489
|
-
/**
|
|
2490
|
-
* Get actions
|
|
2491
|
-
*
|
|
2492
|
-
* @example
|
|
2493
|
-
* ```ts
|
|
2494
|
-
* engine.script({
|
|
2495
|
-
* start: [
|
|
2496
|
-
* action.function(() => {})
|
|
2497
|
-
* ]
|
|
2498
|
-
* })
|
|
2499
|
-
* ```
|
|
2500
|
-
*/
|
|
2501
|
-
action,
|
|
2502
|
-
/**
|
|
2503
|
-
* State bound to `$MAIN` game context
|
|
2504
|
-
* @deprecated Use `state` function provided from action arguments
|
|
2505
|
-
*/
|
|
2506
|
-
state: getStateFunction(MAIN_CONTEXT_KEY),
|
|
2507
|
-
/**
|
|
2508
|
-
* Store data between games
|
|
2509
|
-
*
|
|
2510
|
-
* @example
|
|
2511
|
-
* ```ts
|
|
2512
|
-
* engine.script({
|
|
2513
|
-
* start: [
|
|
2514
|
-
* action.function(() => {
|
|
2515
|
-
* // Paid content should be purchased only once
|
|
2516
|
-
* // So it will be available in any save
|
|
2517
|
-
* data({ paid_content_purchased: true })
|
|
2518
|
-
* })
|
|
2519
|
-
* ]
|
|
2520
|
-
* })
|
|
2521
|
-
* ```
|
|
2522
|
-
*/
|
|
2523
|
-
data,
|
|
2524
|
-
/**
|
|
2525
|
-
* Used in combination with type utilities
|
|
2526
|
-
* @example
|
|
2527
|
-
* ```ts
|
|
2528
|
-
* import type { TypesFromEngine, ConditionParams, StateFunction } from '@novely/core';
|
|
2529
|
-
*
|
|
2530
|
-
* type Types = TypesFromEngine<typeof engine>;
|
|
2531
|
-
*
|
|
2532
|
-
* const conditionCheck = (state: StateFunction<ConditionParams<Types>>) => {
|
|
2533
|
-
* return state.age >= 18;
|
|
2534
|
-
* }
|
|
2535
|
-
* ```
|
|
2536
|
-
*/
|
|
2537
|
-
types: null,
|
|
2538
|
-
/**
|
|
2539
|
-
* Replaces content inside {{braces}} using global data
|
|
2540
|
-
* @example
|
|
2541
|
-
* ```ts
|
|
2542
|
-
* data({ name: 'Alexei' })
|
|
2543
|
-
*
|
|
2544
|
-
* templateReplace('{{name}} is our hero')
|
|
2545
|
-
* templateReplace({
|
|
2546
|
-
* en: (data) => 'Hello, ' + data.name
|
|
2547
|
-
* })
|
|
2548
|
-
* ```
|
|
2549
|
-
*/
|
|
2550
|
-
templateReplace(content) {
|
|
2551
|
-
return templateReplace(content);
|
|
2552
|
-
},
|
|
2553
|
-
/**
|
|
2554
|
-
* Same as `templateReplace` but uses state and requires explicitly providing it
|
|
2555
|
-
*/
|
|
2556
|
-
templateReplaceState(content, state) {
|
|
2557
|
-
return templateReplace(content, state);
|
|
2558
|
-
},
|
|
2559
|
-
/**
|
|
2560
|
-
* Cancel data loading, hide UI, ignore page change events
|
|
2561
|
-
* Data updates still will work in case Novely already was loaded
|
|
2562
|
-
*/
|
|
2563
|
-
destroy() {
|
|
2564
|
-
if (destroyed) return;
|
|
2565
|
-
dataLoaded.cancel();
|
|
2566
|
-
UIInstance.unmount();
|
|
2567
|
-
ticker.destroy();
|
|
2568
|
-
removeEventListener("beforeunload", throttledShortOnStorageDataChange);
|
|
2569
|
-
destroyed = true;
|
|
2570
|
-
},
|
|
2571
|
-
/**
|
|
2572
|
-
* Funtion to get current storage data
|
|
2573
|
-
*
|
|
2574
|
-
* @example
|
|
2575
|
-
* ```ts
|
|
2576
|
-
* const currentStorageData = engine.getCurrentStorageData();
|
|
2577
|
-
* ```
|
|
2578
|
-
*/
|
|
2579
|
-
getCurrentStorageData,
|
|
2580
|
-
/**
|
|
2581
|
-
* Function to set storage data. Using this function is not recommended.
|
|
2582
|
-
*
|
|
2583
|
-
* @deprecated
|
|
2584
|
-
* @example
|
|
2585
|
-
* ```ts
|
|
2586
|
-
* const currentStorageData = engine.getCurrentStorageData();
|
|
2587
|
-
*
|
|
2588
|
-
* if (currentStorageData) {
|
|
2589
|
-
* // update music volume
|
|
2590
|
-
* currentStorageData.meta[2] = 1;
|
|
2591
|
-
*
|
|
2592
|
-
* setStorageData(currentStorageData)
|
|
2593
|
-
* }
|
|
2594
|
-
* ```
|
|
2595
|
-
*/
|
|
2596
|
-
setStorageData,
|
|
2597
|
-
/**
|
|
2598
|
-
* Function to control paused state. Custom Actions are provided with `paused` store they can subscribe to.
|
|
2599
|
-
* This function will notify Custom Actions. Pause state can be used when showing ads.
|
|
2600
|
-
* @example
|
|
2601
|
-
* ```ts
|
|
2602
|
-
* sdk.on('pause' () => engine.setPaused(true));
|
|
2603
|
-
* sdk.on('resume', () => engine.setPaused(false));
|
|
2604
|
-
* ```
|
|
2605
|
-
*/
|
|
2606
|
-
setPaused: (paused2) => {
|
|
2607
|
-
coreData.update((prev) => {
|
|
2608
|
-
prev.paused = paused2;
|
|
2609
|
-
return prev;
|
|
2610
|
-
});
|
|
2611
|
-
},
|
|
2612
|
-
/**
|
|
2613
|
-
* Function to control focused state. It will affect `paused` store passed to Custom Actions.
|
|
2614
|
-
* This function can be used to pause game when it's not focused.
|
|
2615
|
-
* @example
|
|
2616
|
-
* ```ts
|
|
2617
|
-
* import { pauseOnBlur } from '@novely/core';
|
|
2618
|
-
*
|
|
2619
|
-
* // Will subscribe to blur/focus events and call `setFocused`
|
|
2620
|
-
* pauseOnBlur(engine);
|
|
2621
|
-
*
|
|
2622
|
-
* // OR
|
|
2623
|
-
*
|
|
2624
|
-
* sdk.on('focus' () => engine.setFocused(true));
|
|
2625
|
-
* sdk.on('blur', () => engine.setFocused(false));
|
|
2626
|
-
* ```
|
|
2627
|
-
*/
|
|
2628
|
-
setFocused: (focused) => {
|
|
2629
|
-
coreData.update((prev) => {
|
|
2630
|
-
prev.focused = focused;
|
|
2631
|
-
return prev;
|
|
2632
|
-
});
|
|
2633
|
-
}
|
|
2634
|
-
};
|
|
2635
|
-
};
|
|
2636
|
-
|
|
2637
|
-
// src/extend-actions.ts
|
|
2638
|
-
var extendAction = (base, extension) => {
|
|
2639
|
-
return {
|
|
2640
|
-
...extension,
|
|
2641
|
-
...base
|
|
2642
|
-
};
|
|
2643
|
-
};
|
|
2644
|
-
|
|
2645
|
-
// src/translations.ts
|
|
2646
|
-
var RU = {
|
|
2647
|
-
NewGame: "Новая игра",
|
|
2648
|
-
HomeScreen: "Главный экран",
|
|
2649
|
-
ToTheGame: "К игре",
|
|
2650
|
-
Language: "Язык",
|
|
2651
|
-
NoSaves: "Сохранений нет",
|
|
2652
|
-
LoadSave: "Загрузить",
|
|
2653
|
-
Saves: "Сохранения",
|
|
2654
|
-
Settings: "Настройки",
|
|
2655
|
-
Sumbit: "Подтвердить",
|
|
2656
|
-
GoBack: "Назад",
|
|
2657
|
-
DoSave: "Сохранение",
|
|
2658
|
-
Auto: "Авто",
|
|
2659
|
-
Stop: "Стоп",
|
|
2660
|
-
Exit: "Выход",
|
|
2661
|
-
Automatic: "Автоматическое",
|
|
2662
|
-
Manual: "Ручное",
|
|
2663
|
-
Remove: "Удалить",
|
|
2664
|
-
LoadASaveFrom: "Загрузить сохранение от",
|
|
2665
|
-
DeleteASaveFrom: "Удалить сохранение от",
|
|
2666
|
-
TextSpeed: "Скорость текста",
|
|
2667
|
-
TextSpeedSlow: "Медленная",
|
|
2668
|
-
TextSpeedMedium: "Средняя",
|
|
2669
|
-
TextSpeedFast: "Быстрая",
|
|
2670
|
-
TextSpeedAuto: "Автоматическая",
|
|
2671
|
-
CompleteText: "Завершить текст",
|
|
2672
|
-
GoForward: "Перейти вперёд",
|
|
2673
|
-
ExitDialogWarning: "Вы уверены, что хотите выйти? Прогресс будет сохранён.",
|
|
2674
|
-
ExitDialogExit: "Выйти",
|
|
2675
|
-
ExitDialogBack: "Вернуться в игру",
|
|
2676
|
-
OpenMenu: "Открыть меню",
|
|
2677
|
-
CloseMenu: "Закрыть меню",
|
|
2678
|
-
MusicVolume: "Громкость музыки",
|
|
2679
|
-
SoundVolume: "Громкость звуков",
|
|
2680
|
-
VoiceVolume: "Громкость речи",
|
|
2681
|
-
Close: "Закрыть",
|
|
2682
|
-
DialogOverview: "Обзор диалога"
|
|
2683
|
-
};
|
|
2684
|
-
var EN = {
|
|
2685
|
-
NewGame: "New Game",
|
|
2686
|
-
HomeScreen: "Home Screen",
|
|
2687
|
-
ToTheGame: "To the Game",
|
|
2688
|
-
Language: "Language",
|
|
2689
|
-
NoSaves: "No saves",
|
|
2690
|
-
LoadSave: "Load",
|
|
2691
|
-
Saves: "Saves",
|
|
2692
|
-
Settings: "Settings",
|
|
2693
|
-
Sumbit: "Submit",
|
|
2694
|
-
GoBack: "Go back",
|
|
2695
|
-
DoSave: "Save",
|
|
2696
|
-
Auto: "Auto",
|
|
2697
|
-
Stop: "Stop",
|
|
2698
|
-
Exit: "Exit",
|
|
2699
|
-
Automatic: "Automatic",
|
|
2700
|
-
Manual: "Manual",
|
|
2701
|
-
Remove: "Remove",
|
|
2702
|
-
LoadASaveFrom: "Load a save from",
|
|
2703
|
-
DeleteASaveFrom: "Delete a save from",
|
|
2704
|
-
TextSpeed: "Text Speed",
|
|
2705
|
-
TextSpeedSlow: "Slow",
|
|
2706
|
-
TextSpeedMedium: "Medium",
|
|
2707
|
-
TextSpeedFast: "Fast",
|
|
2708
|
-
TextSpeedAuto: "Auto",
|
|
2709
|
-
CompleteText: "Complete text",
|
|
2710
|
-
GoForward: "Go forward",
|
|
2711
|
-
ExitDialogWarning: "Are you sure you want to exit? Progress will be saved.",
|
|
2712
|
-
ExitDialogExit: "Exit",
|
|
2713
|
-
ExitDialogBack: "Return to game",
|
|
2714
|
-
OpenMenu: "Open menu",
|
|
2715
|
-
CloseMenu: "Close menu",
|
|
2716
|
-
MusicVolume: "Music volume",
|
|
2717
|
-
SoundVolume: "Sound volume",
|
|
2718
|
-
VoiceVolume: "Voice volume",
|
|
2719
|
-
Close: "Close",
|
|
2720
|
-
DialogOverview: "Dialog Overview"
|
|
2721
|
-
};
|
|
2722
|
-
|
|
2723
|
-
// src/browser-events.ts
|
|
2724
|
-
var BLUR_HANDLERS = /* @__PURE__ */ new Set();
|
|
2725
|
-
var FOCUS_HANDLERS = /* @__PURE__ */ new Set();
|
|
2726
|
-
var registerEventListeners = (listeners) => {
|
|
2727
|
-
BLUR_HANDLERS.add(listeners.blur);
|
|
2728
|
-
FOCUS_HANDLERS.add(listeners.focus);
|
|
2729
|
-
return () => {
|
|
2730
|
-
BLUR_HANDLERS.delete(listeners.blur);
|
|
2731
|
-
FOCUS_HANDLERS.delete(listeners.focus);
|
|
2732
|
-
};
|
|
2733
|
-
};
|
|
2734
|
-
addEventListener("focus", function(event) {
|
|
2735
|
-
for (const handler of FOCUS_HANDLERS) {
|
|
2736
|
-
try {
|
|
2737
|
-
handler.call(this.document, event);
|
|
2738
|
-
} catch {
|
|
2739
|
-
}
|
|
2740
|
-
}
|
|
2741
|
-
});
|
|
2742
|
-
addEventListener("blur", function(event) {
|
|
2743
|
-
for (const handler of BLUR_HANDLERS) {
|
|
2744
|
-
try {
|
|
2745
|
-
handler.call(this.document, event);
|
|
2746
|
-
} catch {
|
|
2747
|
-
}
|
|
2748
|
-
}
|
|
2749
|
-
});
|
|
2750
|
-
var pauseOnBlur = (engine) => {
|
|
2751
|
-
return {
|
|
2752
|
-
unsubscribe: registerEventListeners({
|
|
2753
|
-
focus: () => {
|
|
2754
|
-
engine.setFocused(true);
|
|
2755
|
-
},
|
|
2756
|
-
blur: () => {
|
|
2757
|
-
engine.setFocused(false);
|
|
2758
|
-
}
|
|
2759
|
-
})
|
|
2760
|
-
};
|
|
2761
|
-
};
|
|
2762
|
-
export {
|
|
2763
|
-
EN,
|
|
2764
|
-
RU,
|
|
2765
|
-
asset,
|
|
2766
|
-
extendAction,
|
|
2767
|
-
novely,
|
|
2768
|
-
pauseOnBlur,
|
|
2769
|
-
storageAdapterLocal
|
|
2770
|
-
};
|
|
2771
|
-
//# sourceMappingURL=index.js.map
|