@novely/core 0.22.1 → 0.23.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.ts +24 -10
- package/dist/index.global.js +232 -119
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +245 -129
- package/dist/index.js.map +1 -1
- package/package.json +62 -62
package/dist/index.js
CHANGED
|
@@ -12,18 +12,60 @@ var AUDIO_ACTIONS = /* @__PURE__ */ new Set([
|
|
|
12
12
|
]);
|
|
13
13
|
var EMPTY_SET = /* @__PURE__ */ new Set();
|
|
14
14
|
var DEFAULT_TYPEWRITER_SPEED = "Medium";
|
|
15
|
+
var HOWLER_SUPPORTED_FILE_FORMATS = /* @__PURE__ */ new Set([
|
|
16
|
+
"mp3",
|
|
17
|
+
"mpeg",
|
|
18
|
+
"opus",
|
|
19
|
+
"ogg",
|
|
20
|
+
"oga",
|
|
21
|
+
"wav",
|
|
22
|
+
"aac",
|
|
23
|
+
"caf",
|
|
24
|
+
"m4a",
|
|
25
|
+
"m4b",
|
|
26
|
+
"mp4",
|
|
27
|
+
"weba",
|
|
28
|
+
"webm",
|
|
29
|
+
"dolby",
|
|
30
|
+
"flac"
|
|
31
|
+
]);
|
|
32
|
+
var SUPPORTED_IMAGE_FILE_FORMATS = /* @__PURE__ */ new Set([
|
|
33
|
+
"apng",
|
|
34
|
+
"avif",
|
|
35
|
+
"gif",
|
|
36
|
+
"jpg",
|
|
37
|
+
"jpeg",
|
|
38
|
+
"jfif",
|
|
39
|
+
"pjpeg",
|
|
40
|
+
"pjp",
|
|
41
|
+
"png",
|
|
42
|
+
"svg",
|
|
43
|
+
"webp",
|
|
44
|
+
"bmp"
|
|
45
|
+
]);
|
|
15
46
|
var MAIN_CONTEXT_KEY = "$MAIN";
|
|
16
47
|
|
|
17
48
|
// src/shared.ts
|
|
18
49
|
var STACK_MAP = /* @__PURE__ */ new Map();
|
|
19
50
|
|
|
20
51
|
// src/utils.ts
|
|
21
|
-
|
|
52
|
+
import { DEV } from "esm-env";
|
|
53
|
+
var matchAction = ({ getContext, push, forward }, values) => {
|
|
22
54
|
return (action, props, { ctx, data }) => {
|
|
23
55
|
const context = typeof ctx === "string" ? getContext(ctx) : ctx;
|
|
24
56
|
return values[action]({
|
|
25
57
|
ctx: context,
|
|
26
|
-
data
|
|
58
|
+
data,
|
|
59
|
+
push() {
|
|
60
|
+
if (context.meta.preview)
|
|
61
|
+
return;
|
|
62
|
+
push(context);
|
|
63
|
+
},
|
|
64
|
+
forward() {
|
|
65
|
+
if (context.meta.preview)
|
|
66
|
+
return;
|
|
67
|
+
forward(context);
|
|
68
|
+
}
|
|
27
69
|
}, props);
|
|
28
70
|
};
|
|
29
71
|
};
|
|
@@ -130,6 +172,9 @@ var isBlockExitStatement = (statement) => {
|
|
|
130
172
|
var isSkippedDuringRestore = (item) => {
|
|
131
173
|
return SKIPPED_DURING_RESTORE.has(item);
|
|
132
174
|
};
|
|
175
|
+
var isAudioAction = (action) => {
|
|
176
|
+
return AUDIO_ACTIONS.has(action);
|
|
177
|
+
};
|
|
133
178
|
var noop = () => {
|
|
134
179
|
};
|
|
135
180
|
var isAction = (element) => {
|
|
@@ -352,6 +397,53 @@ var createUseStackFunction = (renderer) => {
|
|
|
352
397
|
};
|
|
353
398
|
return useStack;
|
|
354
399
|
};
|
|
400
|
+
var mapSet = (set, fn) => {
|
|
401
|
+
return [...set].map(fn);
|
|
402
|
+
};
|
|
403
|
+
var isImageAsset = (asset) => {
|
|
404
|
+
return isString(asset) && isCSSImage(asset);
|
|
405
|
+
};
|
|
406
|
+
var getUrlFileExtension = (address) => {
|
|
407
|
+
try {
|
|
408
|
+
const { pathname } = new URL(address, location.href);
|
|
409
|
+
return pathname.split(".").at(-1).split("!")[0].split(":")[0];
|
|
410
|
+
} catch (error) {
|
|
411
|
+
if (DEV) {
|
|
412
|
+
console.error(new Error(`Could not construct URL "${address}".`, { cause: error }));
|
|
413
|
+
}
|
|
414
|
+
return "";
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
var fetchContentType = async (request, url) => {
|
|
418
|
+
try {
|
|
419
|
+
const response = await request(url, {
|
|
420
|
+
method: "HEAD"
|
|
421
|
+
});
|
|
422
|
+
return response.headers.get("Content-Type") || "";
|
|
423
|
+
} catch (error) {
|
|
424
|
+
if (DEV) {
|
|
425
|
+
console.error(new Error(`Failed to fetch file at "${url}"`, { cause: error }));
|
|
426
|
+
}
|
|
427
|
+
return "";
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
var getResourseType = async (request, url) => {
|
|
431
|
+
const extension = getUrlFileExtension(url);
|
|
432
|
+
if (HOWLER_SUPPORTED_FILE_FORMATS.has(extension)) {
|
|
433
|
+
return "audio";
|
|
434
|
+
}
|
|
435
|
+
if (SUPPORTED_IMAGE_FILE_FORMATS.has(extension)) {
|
|
436
|
+
return "image";
|
|
437
|
+
}
|
|
438
|
+
const contentType = await fetchContentType(request, url);
|
|
439
|
+
if (contentType.includes("audio")) {
|
|
440
|
+
return "audio";
|
|
441
|
+
}
|
|
442
|
+
if (contentType.includes("image")) {
|
|
443
|
+
return "image";
|
|
444
|
+
}
|
|
445
|
+
return "other";
|
|
446
|
+
};
|
|
355
447
|
|
|
356
448
|
// src/global.ts
|
|
357
449
|
var PRELOADED_ASSETS = /* @__PURE__ */ new Set();
|
|
@@ -547,7 +639,7 @@ var localStorageStorage = (options) => {
|
|
|
547
639
|
|
|
548
640
|
// src/novely.ts
|
|
549
641
|
import pLimit from "p-limit";
|
|
550
|
-
import { DEV } from "esm-env";
|
|
642
|
+
import { DEV as DEV2 } from "esm-env";
|
|
551
643
|
var novely = ({
|
|
552
644
|
characters,
|
|
553
645
|
storage = localStorageStorage({ key: "novely-game-storage" }),
|
|
@@ -564,9 +656,12 @@ var novely = ({
|
|
|
564
656
|
getLanguage: getLanguage2 = getLanguage,
|
|
565
657
|
overrideLanguage = false,
|
|
566
658
|
askBeforeExit = true,
|
|
567
|
-
preloadAssets = "lazy"
|
|
659
|
+
preloadAssets = "lazy",
|
|
660
|
+
parallelAssetsDownloadLimit = 15,
|
|
661
|
+
fetch: request = fetch
|
|
568
662
|
}) => {
|
|
569
663
|
const limitScript = pLimit(1);
|
|
664
|
+
const limitAssetsDownload = pLimit(parallelAssetsDownloadLimit);
|
|
570
665
|
const story = {};
|
|
571
666
|
const times = /* @__PURE__ */ new Set();
|
|
572
667
|
const ASSETS_TO_PRELOAD = /* @__PURE__ */ new Set();
|
|
@@ -582,7 +677,23 @@ var novely = ({
|
|
|
582
677
|
Object.assign(story, flattenStory(part));
|
|
583
678
|
if (preloadAssets === "blocking" && ASSETS_TO_PRELOAD.size > 0) {
|
|
584
679
|
renderer.ui.showScreen("loading");
|
|
585
|
-
|
|
680
|
+
const { preloadAudioBlocking, preloadImageBlocking } = renderer.misc;
|
|
681
|
+
const list = mapSet(ASSETS_TO_PRELOAD, (asset) => {
|
|
682
|
+
return limitAssetsDownload(async () => {
|
|
683
|
+
const type = await getResourseType(request, asset);
|
|
684
|
+
switch (type) {
|
|
685
|
+
case "audio": {
|
|
686
|
+
await preloadAudioBlocking(asset);
|
|
687
|
+
break;
|
|
688
|
+
}
|
|
689
|
+
case "image": {
|
|
690
|
+
await preloadImageBlocking(asset);
|
|
691
|
+
break;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
});
|
|
695
|
+
});
|
|
696
|
+
await Promise.allSettled(list);
|
|
586
697
|
}
|
|
587
698
|
const screen = renderer.ui.getScreen();
|
|
588
699
|
const nextScreen = scriptCalled ? screen : initialScreen;
|
|
@@ -603,13 +714,25 @@ var novely = ({
|
|
|
603
714
|
return limitScript(() => scriptBase(part));
|
|
604
715
|
};
|
|
605
716
|
const action = new Proxy({}, {
|
|
606
|
-
get(_,
|
|
717
|
+
get(_, action2) {
|
|
607
718
|
return (...props) => {
|
|
608
719
|
if (preloadAssets === "blocking") {
|
|
609
|
-
if (
|
|
720
|
+
if (action2 === "showBackground") {
|
|
721
|
+
if (isImageAsset(props[0])) {
|
|
722
|
+
ASSETS_TO_PRELOAD.add(props[0]);
|
|
723
|
+
}
|
|
724
|
+
if (props[0] && typeof props[0] === "object") {
|
|
725
|
+
for (const value of Object.values(props[0])) {
|
|
726
|
+
if (!isImageAsset(value))
|
|
727
|
+
continue;
|
|
728
|
+
ASSETS_TO_PRELOAD.add(value);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
if (isAudioAction(action2) && isString(props[0])) {
|
|
610
733
|
ASSETS_TO_PRELOAD.add(props[0]);
|
|
611
734
|
}
|
|
612
|
-
if (
|
|
735
|
+
if (action2 === "showCharacter" && isString(props[0]) && isString(props[1])) {
|
|
613
736
|
const images = characters[props[0]].emotions[props[1]];
|
|
614
737
|
if (Array.isArray(images)) {
|
|
615
738
|
for (const asset of images) {
|
|
@@ -620,7 +743,7 @@ var novely = ({
|
|
|
620
743
|
}
|
|
621
744
|
}
|
|
622
745
|
}
|
|
623
|
-
return [
|
|
746
|
+
return [action2, ...props];
|
|
624
747
|
};
|
|
625
748
|
}
|
|
626
749
|
});
|
|
@@ -687,11 +810,12 @@ var novely = ({
|
|
|
687
810
|
};
|
|
688
811
|
storageDelay.then(getStoredData);
|
|
689
812
|
const initial = getDefaultSave(klona(defaultState));
|
|
690
|
-
|
|
813
|
+
const onVisibilityChange = () => {
|
|
691
814
|
if (document.visibilityState === "hidden") {
|
|
692
815
|
throttledEmergencyOnStorageDataChange();
|
|
693
816
|
}
|
|
694
|
-
}
|
|
817
|
+
};
|
|
818
|
+
addEventListener("visibilitychange", onVisibilityChange);
|
|
695
819
|
addEventListener("beforeunload", throttledEmergencyOnStorageDataChange);
|
|
696
820
|
const save = (override = false, type = override ? "auto" : "manual") => {
|
|
697
821
|
if (!$$.get().dataLoaded)
|
|
@@ -739,12 +863,11 @@ var novely = ({
|
|
|
739
863
|
return;
|
|
740
864
|
let latest = save2 || $.get().saves.at(-1);
|
|
741
865
|
if (!latest) {
|
|
742
|
-
$.set({
|
|
743
|
-
saves: [initial],
|
|
744
|
-
data: klona(defaultData),
|
|
745
|
-
meta: [getLanguageWithoutParameters(), DEFAULT_TYPEWRITER_SPEED, 1, 1, 1]
|
|
746
|
-
});
|
|
747
866
|
latest = klona(initial);
|
|
867
|
+
$.update((prev) => {
|
|
868
|
+
prev.saves.push(latest);
|
|
869
|
+
return prev;
|
|
870
|
+
});
|
|
748
871
|
}
|
|
749
872
|
const context = renderer.getContext(MAIN_CONTEXT_KEY);
|
|
750
873
|
const stack = useStack(context);
|
|
@@ -780,7 +903,7 @@ var novely = ({
|
|
|
780
903
|
data: latest[1]
|
|
781
904
|
});
|
|
782
905
|
});
|
|
783
|
-
context.meta.restoring = context.meta.
|
|
906
|
+
context.meta.restoring = context.meta.goingBack = false;
|
|
784
907
|
render(context);
|
|
785
908
|
};
|
|
786
909
|
const refer = (path) => {
|
|
@@ -848,7 +971,7 @@ var novely = ({
|
|
|
848
971
|
ctx.meta.preview = true;
|
|
849
972
|
const processor = createQueueProcessor(queue);
|
|
850
973
|
await processor.run((action2, props) => {
|
|
851
|
-
if (
|
|
974
|
+
if (isAudioAction(action2))
|
|
852
975
|
return;
|
|
853
976
|
if (action2 === "vibrate")
|
|
854
977
|
return;
|
|
@@ -881,55 +1004,89 @@ var novely = ({
|
|
|
881
1004
|
});
|
|
882
1005
|
const useStack = createUseStackFunction(renderer);
|
|
883
1006
|
useStack(MAIN_CONTEXT_KEY).push(initial);
|
|
884
|
-
renderer.ui.start();
|
|
885
|
-
const
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
1007
|
+
const UIInstance = renderer.ui.start();
|
|
1008
|
+
const enmemory = (ctx) => {
|
|
1009
|
+
if (ctx.meta.restoring)
|
|
1010
|
+
return;
|
|
1011
|
+
const stack = useStack(ctx);
|
|
1012
|
+
const current = klona(stack.value);
|
|
1013
|
+
current[2][1] = "auto";
|
|
1014
|
+
stack.push(current);
|
|
1015
|
+
save(true, "auto");
|
|
1016
|
+
};
|
|
1017
|
+
const next = (ctx) => {
|
|
1018
|
+
const stack = useStack(ctx);
|
|
1019
|
+
const path = stack.value[0];
|
|
1020
|
+
const last = path.at(-1);
|
|
1021
|
+
if (last && (isNull(last[0]) || last[0] === "jump") && isNumber(last[1])) {
|
|
1022
|
+
last[1]++;
|
|
1023
|
+
} else {
|
|
1024
|
+
path.push([null, 0]);
|
|
1025
|
+
}
|
|
1026
|
+
};
|
|
1027
|
+
const matchActionInit = {
|
|
1028
|
+
getContext: renderer.getContext,
|
|
1029
|
+
push(ctx) {
|
|
1030
|
+
if (ctx.meta.restoring)
|
|
1031
|
+
return;
|
|
1032
|
+
next(ctx);
|
|
1033
|
+
render(ctx);
|
|
889
1034
|
},
|
|
890
|
-
|
|
1035
|
+
forward(ctx) {
|
|
1036
|
+
if (!ctx.meta.preview)
|
|
1037
|
+
enmemory(ctx);
|
|
1038
|
+
matchActionInit.push(ctx);
|
|
1039
|
+
if (!ctx.meta.preview)
|
|
1040
|
+
interactivity(true);
|
|
1041
|
+
}
|
|
1042
|
+
};
|
|
1043
|
+
const match = matchAction(matchActionInit, {
|
|
1044
|
+
wait({ ctx, push }, [time]) {
|
|
1045
|
+
if (ctx.meta.restoring)
|
|
1046
|
+
return;
|
|
1047
|
+
setTimeout(push, isFunction(time) ? time() : time);
|
|
1048
|
+
},
|
|
1049
|
+
showBackground({ ctx, push }, [background]) {
|
|
891
1050
|
ctx.background(background);
|
|
892
|
-
push(
|
|
1051
|
+
push();
|
|
893
1052
|
},
|
|
894
|
-
playMusic({ ctx }, [source]) {
|
|
1053
|
+
playMusic({ ctx, push }, [source]) {
|
|
895
1054
|
ctx.audio.music(source, "music", true).play();
|
|
896
|
-
push(
|
|
1055
|
+
push();
|
|
897
1056
|
},
|
|
898
|
-
stopMusic({ ctx }, [source]) {
|
|
1057
|
+
stopMusic({ ctx, push }, [source]) {
|
|
899
1058
|
ctx.audio.music(source, "music").stop();
|
|
900
|
-
push(
|
|
1059
|
+
push();
|
|
901
1060
|
},
|
|
902
|
-
playSound({ ctx }, [source, loop]) {
|
|
1061
|
+
playSound({ ctx, push }, [source, loop]) {
|
|
903
1062
|
ctx.audio.music(source, "sound", loop || false).play();
|
|
904
|
-
push(
|
|
1063
|
+
push();
|
|
905
1064
|
},
|
|
906
|
-
stopSound({ ctx }, [source]) {
|
|
1065
|
+
stopSound({ ctx, push }, [source]) {
|
|
907
1066
|
ctx.audio.music(source, "sound").stop();
|
|
908
|
-
push(
|
|
1067
|
+
push();
|
|
909
1068
|
},
|
|
910
|
-
voice({ ctx }, [source]) {
|
|
1069
|
+
voice({ ctx, push }, [source]) {
|
|
911
1070
|
ctx.audio.voice(source);
|
|
912
|
-
push(
|
|
1071
|
+
push();
|
|
913
1072
|
},
|
|
914
|
-
stopVoice({ ctx }) {
|
|
1073
|
+
stopVoice({ ctx, push }) {
|
|
915
1074
|
ctx.audio.voiceStop();
|
|
916
|
-
push(
|
|
1075
|
+
push();
|
|
917
1076
|
},
|
|
918
|
-
showCharacter({ ctx }, [character, emotion, className, style]) {
|
|
919
|
-
if (
|
|
1077
|
+
showCharacter({ ctx, push }, [character, emotion, className, style]) {
|
|
1078
|
+
if (DEV2 && !characters[character].emotions[emotion]) {
|
|
920
1079
|
throw new Error(`Attempt to show character "${character}" with unknown emotion "${emotion}"`);
|
|
921
1080
|
}
|
|
922
1081
|
const handle = ctx.character(character);
|
|
923
1082
|
handle.append(className, style, ctx.meta.restoring);
|
|
924
1083
|
handle.emotion(emotion, true);
|
|
925
|
-
push(
|
|
1084
|
+
push();
|
|
926
1085
|
},
|
|
927
|
-
hideCharacter({ ctx }, [character, className, style, duration]) {
|
|
928
|
-
ctx.character(character).remove(className, style, duration, ctx.meta.restoring).then(
|
|
929
|
-
push(ctx);
|
|
930
|
-
});
|
|
1086
|
+
hideCharacter({ ctx, push }, [character, className, style, duration]) {
|
|
1087
|
+
ctx.character(character).remove(className, style, duration, ctx.meta.restoring).then(push);
|
|
931
1088
|
},
|
|
932
|
-
dialog({ ctx, data: data2 }, [character, content, emotion]) {
|
|
1089
|
+
dialog({ ctx, data: data2, forward }, [character, content, emotion]) {
|
|
933
1090
|
const name = (() => {
|
|
934
1091
|
const c = character;
|
|
935
1092
|
const cs = characters;
|
|
@@ -950,13 +1107,13 @@ var novely = ({
|
|
|
950
1107
|
unwrap2(name, data2),
|
|
951
1108
|
character,
|
|
952
1109
|
emotion,
|
|
953
|
-
|
|
1110
|
+
forward
|
|
954
1111
|
);
|
|
955
1112
|
},
|
|
956
|
-
function({ ctx }, [fn]) {
|
|
1113
|
+
function({ ctx, push }, [fn]) {
|
|
957
1114
|
const result = fn(ctx.meta.restoring, ctx.meta.goingBack, ctx.meta.preview);
|
|
958
1115
|
if (!ctx.meta.restoring) {
|
|
959
|
-
result ? result.then(
|
|
1116
|
+
result ? result.then(push) : push();
|
|
960
1117
|
}
|
|
961
1118
|
return result;
|
|
962
1119
|
},
|
|
@@ -967,12 +1124,12 @@ var novely = ({
|
|
|
967
1124
|
question = "";
|
|
968
1125
|
}
|
|
969
1126
|
const unwrappedChoices = choices.map(([content, action2, visible]) => {
|
|
970
|
-
if (
|
|
1127
|
+
if (DEV2 && action2.length === 0 && (!visible || visible())) {
|
|
971
1128
|
console.warn(`Choice children should not be empty, either add content there or make item not selectable`);
|
|
972
1129
|
}
|
|
973
1130
|
return [unwrap2(content, data2), action2, visible];
|
|
974
1131
|
});
|
|
975
|
-
if (
|
|
1132
|
+
if (DEV2 && unwrappedChoices.length === 0) {
|
|
976
1133
|
throw new Error(`Running choice without variants to choose from, look at how to use Choice action properly [https://novely.pages.dev/guide/actions/choice#usage]`);
|
|
977
1134
|
}
|
|
978
1135
|
ctx.choices(unwrap2(question, data2), unwrappedChoices, (selected) => {
|
|
@@ -981,7 +1138,7 @@ var novely = ({
|
|
|
981
1138
|
}
|
|
982
1139
|
const stack = useStack(ctx);
|
|
983
1140
|
const offset = isWithoutQuestion ? 0 : 1;
|
|
984
|
-
if (
|
|
1141
|
+
if (DEV2 && !unwrappedChoices[selected]) {
|
|
985
1142
|
throw new Error("Choice children is empty, either add content there or make item not selectable");
|
|
986
1143
|
}
|
|
987
1144
|
stack.value[0].push(["choice", selected + offset], [null, 0]);
|
|
@@ -990,10 +1147,10 @@ var novely = ({
|
|
|
990
1147
|
});
|
|
991
1148
|
},
|
|
992
1149
|
jump({ ctx, data: data2 }, [scene]) {
|
|
993
|
-
if (
|
|
1150
|
+
if (DEV2 && !story[scene]) {
|
|
994
1151
|
throw new Error(`Attempt to jump to unknown scene "${scene}"`);
|
|
995
1152
|
}
|
|
996
|
-
if (
|
|
1153
|
+
if (DEV2 && story[scene].length === 0) {
|
|
997
1154
|
throw new Error(`Attempt to jump to empty scene "${scene}"`);
|
|
998
1155
|
}
|
|
999
1156
|
const stack = useStack(ctx);
|
|
@@ -1006,25 +1163,25 @@ var novely = ({
|
|
|
1006
1163
|
data: data2
|
|
1007
1164
|
});
|
|
1008
1165
|
},
|
|
1009
|
-
clear({ ctx }, [keep, characters2, audio]) {
|
|
1166
|
+
clear({ ctx, push }, [keep, characters2, audio]) {
|
|
1010
1167
|
ctx.vibrate(0);
|
|
1011
1168
|
ctx.clear(
|
|
1012
1169
|
keep || EMPTY_SET,
|
|
1013
1170
|
characters2 || EMPTY_SET,
|
|
1014
1171
|
audio || { music: EMPTY_SET, sounds: EMPTY_SET },
|
|
1015
|
-
|
|
1172
|
+
push
|
|
1016
1173
|
);
|
|
1017
1174
|
},
|
|
1018
1175
|
condition({ ctx }, [condition, variants]) {
|
|
1019
|
-
if (
|
|
1176
|
+
if (DEV2 && Object.values(variants).length === 0) {
|
|
1020
1177
|
throw new Error(`Attempt to use Condition action with empty variants object`);
|
|
1021
1178
|
}
|
|
1022
1179
|
if (!ctx.meta.restoring) {
|
|
1023
1180
|
const val = String(condition());
|
|
1024
|
-
if (
|
|
1181
|
+
if (DEV2 && !variants[val]) {
|
|
1025
1182
|
throw new Error(`Attempt to go to unknown variant "${val}"`);
|
|
1026
1183
|
}
|
|
1027
|
-
if (
|
|
1184
|
+
if (DEV2 && variants[val].length === 0) {
|
|
1028
1185
|
throw new Error(`Attempt to go to empty variant "${val}"`);
|
|
1029
1186
|
}
|
|
1030
1187
|
const stack = useStack(MAIN_CONTEXT_KEY);
|
|
@@ -1041,15 +1198,15 @@ var novely = ({
|
|
|
1041
1198
|
interactivity(false);
|
|
1042
1199
|
times.clear();
|
|
1043
1200
|
},
|
|
1044
|
-
input({ ctx, data: data2 }, [question, onInput, setup]) {
|
|
1201
|
+
input({ ctx, data: data2, forward }, [question, onInput, setup]) {
|
|
1045
1202
|
ctx.input(
|
|
1046
1203
|
unwrap2(question, data2),
|
|
1047
1204
|
onInput,
|
|
1048
1205
|
setup || noop,
|
|
1049
|
-
|
|
1206
|
+
forward
|
|
1050
1207
|
);
|
|
1051
1208
|
},
|
|
1052
|
-
custom({ ctx }, [handler]) {
|
|
1209
|
+
custom({ ctx, push }, [handler]) {
|
|
1053
1210
|
const result = ctx.custom(handler, () => {
|
|
1054
1211
|
if (ctx.meta.restoring)
|
|
1055
1212
|
return;
|
|
@@ -1057,56 +1214,35 @@ var novely = ({
|
|
|
1057
1214
|
enmemory(ctx);
|
|
1058
1215
|
interactivity(true);
|
|
1059
1216
|
}
|
|
1060
|
-
push(
|
|
1217
|
+
push();
|
|
1061
1218
|
});
|
|
1062
1219
|
return result;
|
|
1063
1220
|
},
|
|
1064
|
-
vibrate({ ctx }, pattern) {
|
|
1221
|
+
vibrate({ ctx, push }, pattern) {
|
|
1065
1222
|
ctx.vibrate(pattern);
|
|
1066
|
-
push(
|
|
1223
|
+
push();
|
|
1067
1224
|
},
|
|
1068
|
-
next({
|
|
1069
|
-
push(
|
|
1225
|
+
next({ push }) {
|
|
1226
|
+
push();
|
|
1070
1227
|
},
|
|
1071
|
-
animateCharacter({ ctx,
|
|
1072
|
-
if (
|
|
1228
|
+
animateCharacter({ ctx, push }, [character, timeout, ...classes]) {
|
|
1229
|
+
if (DEV2 && classes.length === 0) {
|
|
1073
1230
|
throw new Error("Attempt to use AnimateCharacter without classes. Classes should be provided [https://novely.pages.dev/guide/actions/animateCharacter.html]");
|
|
1074
1231
|
}
|
|
1075
|
-
if (
|
|
1232
|
+
if (DEV2 && (timeout <= 0 || !Number.isFinite(timeout) || Number.isNaN(timeout))) {
|
|
1076
1233
|
throw new Error("Attempt to use AnimateCharacter with unacceptable timeout. It should be finite and greater than zero");
|
|
1077
1234
|
}
|
|
1078
1235
|
if (ctx.meta.preview)
|
|
1079
1236
|
return;
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
const char = ctx.getCharacter(character);
|
|
1083
|
-
if (!char)
|
|
1084
|
-
return;
|
|
1085
|
-
const target = char.canvas;
|
|
1086
|
-
if (!target)
|
|
1087
|
-
return;
|
|
1088
|
-
const classNames = classes.filter((className) => !target.classList.contains(className));
|
|
1089
|
-
target.classList.add(...classNames);
|
|
1090
|
-
const timeoutId = setTimeout(() => {
|
|
1091
|
-
target.classList.remove(...classNames);
|
|
1092
|
-
}, timeout);
|
|
1093
|
-
clear(() => {
|
|
1094
|
-
target.classList.remove(...classNames);
|
|
1095
|
-
clearTimeout(timeoutId);
|
|
1096
|
-
});
|
|
1097
|
-
};
|
|
1098
|
-
handler.key = "@@internal-animate-character";
|
|
1099
|
-
match("custom", [handler], {
|
|
1100
|
-
ctx,
|
|
1101
|
-
data: data2
|
|
1102
|
-
});
|
|
1237
|
+
ctx.character(character).animate(timeout, classes);
|
|
1238
|
+
push();
|
|
1103
1239
|
},
|
|
1104
|
-
text({ ctx, data: data2 }, text) {
|
|
1240
|
+
text({ ctx, data: data2, forward }, text) {
|
|
1105
1241
|
const string = text.map((content) => unwrap2(content, data2)).join(" ");
|
|
1106
|
-
if (
|
|
1242
|
+
if (DEV2 && string.length === 0) {
|
|
1107
1243
|
throw new Error(`Action Text was called with empty string or array`);
|
|
1108
1244
|
}
|
|
1109
|
-
ctx.text(string,
|
|
1245
|
+
ctx.text(string, forward);
|
|
1110
1246
|
},
|
|
1111
1247
|
exit({ ctx, data: data2 }) {
|
|
1112
1248
|
if (ctx.meta.restoring)
|
|
@@ -1155,17 +1291,17 @@ var novely = ({
|
|
|
1155
1291
|
}
|
|
1156
1292
|
render(ctx);
|
|
1157
1293
|
},
|
|
1158
|
-
preload({ ctx }, [source]) {
|
|
1294
|
+
preload({ ctx, push }, [source]) {
|
|
1159
1295
|
if (!ctx.meta.goingBack && !ctx.meta.restoring && !PRELOADED_ASSETS.has(source)) {
|
|
1160
1296
|
PRELOADED_ASSETS.add(renderer.misc.preloadImage(source));
|
|
1161
1297
|
}
|
|
1162
|
-
push(
|
|
1298
|
+
push();
|
|
1163
1299
|
},
|
|
1164
1300
|
block({ ctx }, [scene]) {
|
|
1165
|
-
if (
|
|
1301
|
+
if (DEV2 && !story[scene]) {
|
|
1166
1302
|
throw new Error(`Attempt to call Block action with unknown scene "${scene}"`);
|
|
1167
1303
|
}
|
|
1168
|
-
if (
|
|
1304
|
+
if (DEV2 && story[scene].length === 0) {
|
|
1169
1305
|
throw new Error(`Attempt to call Block action with empty scene "${scene}"`);
|
|
1170
1306
|
}
|
|
1171
1307
|
if (!ctx.meta.restoring) {
|
|
@@ -1175,25 +1311,6 @@ var novely = ({
|
|
|
1175
1311
|
}
|
|
1176
1312
|
}
|
|
1177
1313
|
});
|
|
1178
|
-
const enmemory = (ctx) => {
|
|
1179
|
-
if (ctx.meta.restoring)
|
|
1180
|
-
return;
|
|
1181
|
-
const stack = useStack(ctx);
|
|
1182
|
-
const current = klona(stack.value);
|
|
1183
|
-
current[2][1] = "auto";
|
|
1184
|
-
stack.push(current);
|
|
1185
|
-
save(true, "auto");
|
|
1186
|
-
};
|
|
1187
|
-
const next = (ctx) => {
|
|
1188
|
-
const stack = useStack(ctx);
|
|
1189
|
-
const path = stack.value[0];
|
|
1190
|
-
const last = path.at(-1);
|
|
1191
|
-
if (last && (isNull(last[0]) || last[0] === "jump") && isNumber(last[1])) {
|
|
1192
|
-
last[1]++;
|
|
1193
|
-
} else {
|
|
1194
|
-
path.push([null, 0]);
|
|
1195
|
-
}
|
|
1196
|
-
};
|
|
1197
1314
|
const render = (ctx) => {
|
|
1198
1315
|
const stack = useStack(ctx);
|
|
1199
1316
|
const referred = refer(stack.value[0]);
|
|
@@ -1210,17 +1327,6 @@ var novely = ({
|
|
|
1210
1327
|
});
|
|
1211
1328
|
}
|
|
1212
1329
|
};
|
|
1213
|
-
const push = (ctx) => {
|
|
1214
|
-
if (!ctx.meta.restoring)
|
|
1215
|
-
next(ctx), render(ctx);
|
|
1216
|
-
};
|
|
1217
|
-
const forward = (ctx) => {
|
|
1218
|
-
if (!ctx.meta.preview)
|
|
1219
|
-
enmemory(ctx);
|
|
1220
|
-
push(ctx);
|
|
1221
|
-
if (!ctx.meta.preview)
|
|
1222
|
-
interactivity(true);
|
|
1223
|
-
};
|
|
1224
1330
|
const interactivity = (value = false) => {
|
|
1225
1331
|
interacted = value ? interacted + 1 : 0;
|
|
1226
1332
|
};
|
|
@@ -1270,6 +1376,16 @@ var novely = ({
|
|
|
1270
1376
|
*/
|
|
1271
1377
|
unwrap(content) {
|
|
1272
1378
|
return unwrap2(content, $.get().data);
|
|
1379
|
+
},
|
|
1380
|
+
/**
|
|
1381
|
+
* Cancel data loading, hide UI, ignore page change events
|
|
1382
|
+
* Data updates still will work in case Novely already was loaded
|
|
1383
|
+
*/
|
|
1384
|
+
destroy() {
|
|
1385
|
+
dataLoaded.cancel();
|
|
1386
|
+
UIInstance.unmount();
|
|
1387
|
+
removeEventListener("visibilitychange", onVisibilityChange);
|
|
1388
|
+
removeEventListener("beforeunload", throttledEmergencyOnStorageDataChange);
|
|
1273
1389
|
}
|
|
1274
1390
|
};
|
|
1275
1391
|
};
|