@novely/core 0.19.5 → 0.21.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.
@@ -164,13 +164,29 @@ var Novely = (() => {
164
164
  var SKIPPED_DURING_RESTORE = /* @__PURE__ */ new Set(["dialog", "choice", "input", "vibrate", "text"]);
165
165
  var BLOCK_EXIT_STATEMENTS = /* @__PURE__ */ new Set(["choice:exit", "condition:exit", "block:exit"]);
166
166
  var BLOCK_STATEMENTS = /* @__PURE__ */ new Set(["choice", "condition", "block"]);
167
+ var AUDIO_ACTIONS = /* @__PURE__ */ new Set([
168
+ "playMusic",
169
+ "stopMusic",
170
+ "playSound",
171
+ "stopSound",
172
+ "voice",
173
+ "stopVoice"
174
+ ]);
167
175
  var EMPTY_SET = /* @__PURE__ */ new Set();
168
176
  var DEFAULT_TYPEWRITER_SPEED = "Medium";
177
+ var MAIN_CONTEXT_KEY = "$MAIN";
178
+
179
+ // src/shared.ts
180
+ var STACK_MAP = /* @__PURE__ */ new Map();
169
181
 
170
182
  // src/utils.ts
171
- var matchAction = (values) => {
172
- return (action, props) => {
173
- return values[action](props);
183
+ var matchAction = (getContext, values) => {
184
+ return (action, props, { ctx, data }) => {
185
+ const context = typeof ctx === "string" ? getContext(ctx) : ctx;
186
+ return values[action]({
187
+ ctx: context,
188
+ data
189
+ }, props);
174
190
  };
175
191
  };
176
192
  var isNumber = (val) => {
@@ -314,6 +330,189 @@ var Novely = (() => {
314
330
  };
315
331
  return MAP[action];
316
332
  };
333
+ var getActionsFromPath = (story, path, raw = false) => {
334
+ let current = story;
335
+ let precurrent;
336
+ let ignoreNested = false;
337
+ let index = 0;
338
+ const max = path.reduce((acc, [type, val]) => {
339
+ if (isNull(type) && isNumber(val)) {
340
+ return acc + 1;
341
+ }
342
+ return acc;
343
+ }, 0);
344
+ const queue = [];
345
+ const blocks = [];
346
+ for (const [type, val] of path) {
347
+ if (type === "jump") {
348
+ precurrent = story;
349
+ current = current[val];
350
+ } else if (type === null) {
351
+ precurrent = current;
352
+ if (isNumber(val)) {
353
+ index++;
354
+ let startIndex = 0;
355
+ if (ignoreNested) {
356
+ const prev = findLastPathItemBeforeItemOfType(path.slice(0, index), "block");
357
+ if (prev) {
358
+ startIndex = prev[1];
359
+ ignoreNested = false;
360
+ }
361
+ }
362
+ for (let i = startIndex; i <= val; i++) {
363
+ const item = current[i];
364
+ if (!isAction(item))
365
+ continue;
366
+ const [action, ...meta] = item;
367
+ const push = () => {
368
+ queue.push([action, meta]);
369
+ };
370
+ if (raw) {
371
+ push();
372
+ continue;
373
+ }
374
+ if (isSkippedDuringRestore(action) || isUserRequiredAction(action, meta)) {
375
+ if (index === max && i === val) {
376
+ push();
377
+ }
378
+ continue;
379
+ } else {
380
+ push();
381
+ }
382
+ }
383
+ }
384
+ current = current[val];
385
+ } else if (type === "choice") {
386
+ blocks.push(precurrent);
387
+ current = current[val + 1][1];
388
+ } else if (type === "condition") {
389
+ blocks.push(precurrent);
390
+ current = current[2][val];
391
+ } else if (type === "block") {
392
+ blocks.push(precurrent);
393
+ current = story[val];
394
+ } else if (type === "block:exit" || type === "choice:exit" || type === "condition:exit") {
395
+ current = blocks.pop();
396
+ ignoreNested = true;
397
+ }
398
+ }
399
+ return queue;
400
+ };
401
+ var createQueueProcessor = (queue) => {
402
+ const processedQueue = [];
403
+ const keep = /* @__PURE__ */ new Set();
404
+ const characters = /* @__PURE__ */ new Set();
405
+ const audio = {
406
+ music: /* @__PURE__ */ new Set(),
407
+ sound: /* @__PURE__ */ new Set()
408
+ };
409
+ const next = (i) => queue.slice(i + 1);
410
+ for (const [i, [action, meta]] of queue.entries()) {
411
+ keep.add(action);
412
+ if (action === "function" || action === "custom") {
413
+ if (action === "custom" && meta[0].callOnlyLatest) {
414
+ const notLatest = next(i).some(([, _meta]) => {
415
+ if (!_meta || !meta)
416
+ return false;
417
+ const c0 = _meta[0];
418
+ const c1 = meta[0];
419
+ const isIdenticalID = c0.id && c1.id && c0.id === c1.id;
420
+ const isIdenticalByReference = c0 === c1;
421
+ return isIdenticalID || isIdenticalByReference || str(c0) === str(c1);
422
+ });
423
+ if (notLatest)
424
+ continue;
425
+ }
426
+ processedQueue.push([action, meta]);
427
+ } else if (action === "showCharacter" || action === "playSound" || action === "playMusic" || action === "voice") {
428
+ const closing = getOppositeAction(action);
429
+ const skip = next(i).some(([_action, _meta]) => {
430
+ if (!_meta || !meta)
431
+ return false;
432
+ if (_meta[0] !== meta[0])
433
+ return false;
434
+ return _action === closing || _action === action;
435
+ });
436
+ if (skip)
437
+ continue;
438
+ if (action === "showCharacter") {
439
+ characters.add(meta[0]);
440
+ } else if (action === "playMusic") {
441
+ audio.music.add(meta[0]);
442
+ } else if (action === "playSound") {
443
+ audio.sound.add(meta[0]);
444
+ }
445
+ processedQueue.push([action, meta]);
446
+ } else if (action === "showBackground" || action === "animateCharacter" || action === "preload") {
447
+ const skip = next(i).some(([_action], i2, array) => action === _action);
448
+ if (skip)
449
+ continue;
450
+ processedQueue.push([action, meta]);
451
+ } else {
452
+ processedQueue.push([action, meta]);
453
+ }
454
+ }
455
+ const run = async (match) => {
456
+ for await (const [action, meta] of processedQueue) {
457
+ const result = match(action, meta);
458
+ if (isPromise(result)) {
459
+ await result;
460
+ }
461
+ }
462
+ processedQueue.length = 0;
463
+ };
464
+ const getKeep = () => {
465
+ return {
466
+ keep,
467
+ characters,
468
+ audio
469
+ };
470
+ };
471
+ return {
472
+ run,
473
+ getKeep
474
+ };
475
+ };
476
+ var getStack = (ctx) => {
477
+ const { id } = ctx;
478
+ const cached = STACK_MAP.get(id);
479
+ if (cached)
480
+ return cached;
481
+ const stack = [];
482
+ STACK_MAP.set(id, stack);
483
+ return stack;
484
+ };
485
+ var createUseStackFunction = (renderer) => {
486
+ const useStack = (context) => {
487
+ const ctx = typeof context === "string" ? renderer.getContext(context) : context;
488
+ const stack = getStack(ctx);
489
+ return {
490
+ get previous() {
491
+ return stack.previous;
492
+ },
493
+ get value() {
494
+ return stack.at(-1);
495
+ },
496
+ set value(value) {
497
+ stack[stack.length - 1] = value;
498
+ },
499
+ back() {
500
+ if (stack.length > 1) {
501
+ stack.previous = stack.pop();
502
+ ctx.meta.goingBack = true;
503
+ }
504
+ },
505
+ push(value) {
506
+ stack.push(value);
507
+ },
508
+ clear() {
509
+ stack.length = 0;
510
+ stack.length = 1;
511
+ }
512
+ };
513
+ };
514
+ return useStack;
515
+ };
317
516
 
318
517
  // src/global.ts
319
518
  var PRELOADED_ASSETS = /* @__PURE__ */ new Set();
@@ -333,10 +532,13 @@ var Novely = (() => {
333
532
  const update = (fn) => {
334
533
  push(current = fn(current));
335
534
  };
535
+ const set = (val) => {
536
+ update(() => val);
537
+ };
336
538
  const get = () => {
337
539
  return current;
338
540
  };
339
- return { subscribe, update, get };
541
+ return { subscribe, update, set, get };
340
542
  };
341
543
 
342
544
  // ../deepmerge/dist/index.js
@@ -588,6 +790,7 @@ var Novely = (() => {
588
790
  }
589
791
  });
590
792
  function state(value) {
793
+ const stack = useStack(MAIN_CONTEXT_KEY);
591
794
  if (!value)
592
795
  return stack.value[1];
593
796
  const prev = stack.value[1];
@@ -604,26 +807,6 @@ var Novely = (() => {
604
807
  [intime(Date.now()), "auto"]
605
808
  ];
606
809
  };
607
- const createStack = (current, stack2 = [current]) => {
608
- return {
609
- get value() {
610
- return stack2.at(-1);
611
- },
612
- set value(value) {
613
- stack2[stack2.length - 1] = value;
614
- },
615
- back() {
616
- if (stack2.length > 1)
617
- stack2.pop(), goingBack = true;
618
- },
619
- push(value) {
620
- stack2.push(value);
621
- },
622
- clear() {
623
- stack2 = [getDefaultSave(klona(defaultState))];
624
- }
625
- };
626
- };
627
810
  const getLanguageWithoutParameters = () => {
628
811
  return getLanguage2(languages, getLanguage);
629
812
  };
@@ -665,11 +848,10 @@ var Novely = (() => {
665
848
  }
666
849
  $$.update((prev) => (prev.dataLoaded = true, prev));
667
850
  dataLoaded.resolve();
668
- $.update(() => stored);
851
+ $.set(stored);
669
852
  };
670
853
  storageDelay.then(getStoredData);
671
854
  const initial = getDefaultSave(klona(defaultState));
672
- const stack = createStack(initial);
673
855
  addEventListener("visibilitychange", () => {
674
856
  if (document.visibilityState === "hidden") {
675
857
  throttledEmergencyOnStorageDataChange();
@@ -681,6 +863,7 @@ var Novely = (() => {
681
863
  return;
682
864
  if (!autosaves && type === "auto")
683
865
  return;
866
+ const stack = useStack(MAIN_CONTEXT_KEY);
684
867
  const current = klona(stack.value);
685
868
  $.update((prev) => {
686
869
  const isLatest = findLastIndex(prev.saves, (value) => times.has(value[2][0])) === prev.saves.length - 1;
@@ -710,139 +893,65 @@ var Novely = (() => {
710
893
  }
711
894
  restore(save2);
712
895
  };
713
- const set = (save2) => {
896
+ const set = (save2, ctx) => {
897
+ const stack = useStack(ctx || renderer.getContext(MAIN_CONTEXT_KEY));
714
898
  stack.value = save2;
715
899
  return restore(save2);
716
900
  };
717
- let restoring = false;
718
- let goingBack = false;
719
901
  let interacted = 0;
720
902
  const restore = async (save2) => {
721
903
  if (!$$.get().dataLoaded)
722
904
  return;
723
905
  let latest = save2 || $.get().saves.at(-1);
724
906
  if (!latest) {
725
- $.update(() => ({
907
+ $.set({
726
908
  saves: [initial],
727
909
  data: klona(defaultData),
728
910
  meta: [getLanguageWithoutParameters(), DEFAULT_TYPEWRITER_SPEED, 1, 1, 1]
729
- }));
911
+ });
730
912
  latest = klona(initial);
731
913
  }
732
- restoring = true, stack.value = latest;
733
- const path = stack.value[0];
734
- let current = story;
735
- let precurrent;
736
- let ignoreNested = false;
737
- let index = 0;
738
- const max = stack.value[0].reduce((acc, [type, val]) => {
739
- if (isNull(type) && isNumber(val)) {
740
- return acc + 1;
741
- }
742
- return acc;
743
- }, 0);
744
- const queue = [];
745
- const keep = /* @__PURE__ */ new Set();
746
- const characters2 = /* @__PURE__ */ new Set();
747
- const blocks = [];
748
- for (const [type, val] of path) {
749
- if (type === "jump") {
750
- precurrent = story;
751
- current = current[val];
752
- } else if (type === null) {
753
- precurrent = current;
754
- if (isNumber(val)) {
755
- index++;
756
- let startIndex = 0;
757
- if (ignoreNested) {
758
- const prev = findLastPathItemBeforeItemOfType(path.slice(0, index), "block");
759
- if (prev) {
760
- startIndex = prev[1];
761
- ignoreNested = false;
762
- }
763
- }
764
- for (let i = startIndex; i <= val; i++) {
765
- const item = current[i];
766
- if (!isAction(item))
767
- continue;
768
- const [action2, ...meta] = item;
769
- const push2 = () => {
770
- keep.add(action2);
771
- queue.push([action2, meta]);
772
- };
773
- if (action2 === "showCharacter")
774
- characters2.add(meta[0]);
775
- if (isSkippedDuringRestore(action2) || isUserRequiredAction(action2, meta)) {
776
- if (index === max && i === val) {
777
- push2();
778
- } else {
779
- continue;
780
- }
781
- }
782
- push2();
783
- }
784
- }
785
- current = current[val];
786
- } else if (type === "choice") {
787
- blocks.push(precurrent);
788
- current = current[val + 1][1];
789
- } else if (type === "condition") {
790
- blocks.push(precurrent);
791
- current = current[2][val];
792
- } else if (type === "block") {
793
- blocks.push(precurrent);
794
- current = story[val];
795
- } else if (type === "block:exit" || type === "choice:exit" || type === "condition:exit") {
796
- current = blocks.pop();
797
- ignoreNested = true;
798
- }
799
- }
914
+ const context = renderer.getContext(MAIN_CONTEXT_KEY);
915
+ const stack = useStack(context);
916
+ context.meta.restoring = true;
917
+ const previous = stack.previous;
918
+ const [path] = stack.value = latest;
800
919
  renderer.ui.showScreen("game");
801
- match("clear", [keep, characters2]);
802
- const next2 = (i) => queue.slice(i + 1);
803
- for await (const [i, [action2, meta]] of queue.entries()) {
804
- if (action2 === "function" || action2 === "custom") {
805
- if (action2 === "custom" && meta[0].callOnlyLatest) {
806
- const notLatest = next2(i).some(([, _meta]) => {
807
- if (!_meta || !meta)
808
- return false;
809
- const c0 = _meta[0];
810
- const c1 = meta[0];
811
- const isIdenticalID = c0.id && c1.id && c0.id === c1.id;
812
- const isIdenticalByReference = c0 === c1;
813
- return isIdenticalID || isIdenticalByReference || str(c0) === str(c1);
814
- });
815
- if (notLatest)
816
- continue;
817
- }
818
- const result = match(action2, meta);
819
- if (isPromise(result)) {
820
- await result;
920
+ const queue = getActionsFromPath(story, path);
921
+ const processor = createQueueProcessor(queue);
922
+ const { keep, characters: characters2, audio } = processor.getKeep();
923
+ if (previous) {
924
+ const prevQueue = getActionsFromPath(story, previous[0], true);
925
+ const currQueue = getActionsFromPath(story, path, true);
926
+ for (let i = prevQueue.length - 1; i > currQueue.length; i--) {
927
+ const element = prevQueue[i];
928
+ if (isAction(element)) {
929
+ const [action2, props] = element;
930
+ if (action2 === "custom") {
931
+ context.clearCustom(props[0]);
932
+ }
821
933
  }
822
- } else if (action2 === "showCharacter" || action2 === "playSound" || action2 === "playMusic" || action2 === "voice") {
823
- const closing = getOppositeAction(action2);
824
- const skip = next2(i).some(([_action, _meta]) => {
825
- if (!_meta || !meta)
826
- return false;
827
- if (_meta[0] !== meta[0])
828
- return false;
829
- return _action === closing || _action === action2;
830
- });
831
- if (skip)
832
- continue;
833
- match(action2, meta);
834
- } else if (action2 === "showBackground" || action2 === "animateCharacter" || action2 === "preload") {
835
- const notLatest = next2(i).some(([_action]) => action2 === _action);
836
- if (notLatest)
837
- continue;
838
- match(action2, meta);
839
- } else {
840
- match(action2, meta);
841
934
  }
842
935
  }
843
- restoring = goingBack = false, render();
936
+ match("clear", [keep, characters2, audio], {
937
+ ctx: context,
938
+ data: latest[1]
939
+ });
940
+ await processor.run((action2, props) => {
941
+ if (!latest)
942
+ return;
943
+ return match(action2, props, {
944
+ ctx: context,
945
+ data: latest[1]
946
+ });
947
+ });
948
+ context.meta.restoring = context.meta.restoring = false;
949
+ render(context);
844
950
  };
845
- const refer = (path = stack.value[0]) => {
951
+ const refer = (path) => {
952
+ if (!path) {
953
+ path = useStack(MAIN_CONTEXT_KEY).value[0];
954
+ }
846
955
  let current = story;
847
956
  let precurrent = story;
848
957
  const blocks = [];
@@ -873,10 +982,12 @@ var Novely = (() => {
873
982
  renderer.ui.showExitPrompt();
874
983
  return;
875
984
  }
985
+ const ctx = renderer.getContext(MAIN_CONTEXT_KEY);
986
+ const stack = useStack(ctx);
876
987
  const current = stack.value;
877
988
  stack.clear();
878
989
  renderer.ui.showScreen("mainmenu");
879
- renderer.audio.destroy();
990
+ ctx.audio.destroy();
880
991
  const [time, type] = current[2];
881
992
  if (type === "auto" && interacted <= 1 && times.has(time)) {
882
993
  $.update((prev) => {
@@ -887,13 +998,38 @@ var Novely = (() => {
887
998
  interactivity(false);
888
999
  times.clear();
889
1000
  };
890
- const back = () => {
891
- return stack.back(), restore(stack.value);
1001
+ const back = async () => {
1002
+ const stack = useStack(MAIN_CONTEXT_KEY);
1003
+ stack.back();
1004
+ await restore(stack.value);
892
1005
  };
893
1006
  const t = (key, lang) => {
894
1007
  return translation[lang].internal[key];
895
1008
  };
1009
+ const preview = async ([path, data2], name) => {
1010
+ const queue = getActionsFromPath(story, path);
1011
+ const ctx = renderer.getContext(name);
1012
+ ctx.meta.restoring = true;
1013
+ ctx.meta.preview = true;
1014
+ const processor = createQueueProcessor(queue);
1015
+ await processor.run((action2, props) => {
1016
+ if (AUDIO_ACTIONS.has(action2))
1017
+ return;
1018
+ if (action2 === "vibrate")
1019
+ return;
1020
+ if (action2 === "end")
1021
+ return;
1022
+ return match(action2, props, {
1023
+ ctx,
1024
+ data: data2
1025
+ });
1026
+ });
1027
+ };
1028
+ const removeContext = (name) => {
1029
+ STACK_MAP.delete(name);
1030
+ };
896
1031
  const renderer = createRenderer({
1032
+ mainContextKey: MAIN_CONTEXT_KEY,
897
1033
  characters,
898
1034
  set,
899
1035
  restore,
@@ -902,62 +1038,67 @@ var Novely = (() => {
902
1038
  exit,
903
1039
  back,
904
1040
  t,
905
- stack,
1041
+ preview,
1042
+ removeContext,
906
1043
  languages,
907
1044
  $,
908
1045
  $$
909
1046
  });
1047
+ const useStack = createUseStackFunction(renderer);
1048
+ useStack(MAIN_CONTEXT_KEY).push(initial);
910
1049
  renderer.ui.start();
911
- const match = matchAction({
912
- wait([time]) {
913
- if (!restoring)
1050
+ const match = matchAction(renderer.getContext, {
1051
+ wait({ ctx }, [time]) {
1052
+ if (!ctx.meta.restoring)
914
1053
  setTimeout(push, isFunction(time) ? time() : time);
915
1054
  },
916
- showBackground([background]) {
917
- renderer.background(background);
918
- push();
1055
+ showBackground({ ctx }, [background]) {
1056
+ ctx.background(background);
1057
+ push(ctx);
919
1058
  },
920
- playMusic([source]) {
921
- renderer.audio.music(source, "music", true).play();
922
- push();
1059
+ playMusic({ ctx }, [source]) {
1060
+ ctx.audio.music(source, "music", true).play();
1061
+ push(ctx);
923
1062
  },
924
- stopMusic([source]) {
925
- renderer.audio.music(source, "music").stop();
926
- push();
1063
+ stopMusic({ ctx }, [source]) {
1064
+ ctx.audio.music(source, "music").stop();
1065
+ push(ctx);
927
1066
  },
928
- playSound([source, loop]) {
929
- renderer.audio.music(source, "sound", loop || false).play();
930
- push();
1067
+ playSound({ ctx }, [source, loop]) {
1068
+ ctx.audio.music(source, "sound", loop || false).play();
1069
+ push(ctx);
931
1070
  },
932
- stopSound([source]) {
933
- renderer.audio.music(source, "sound").stop();
934
- push();
1071
+ stopSound({ ctx }, [source]) {
1072
+ ctx.audio.music(source, "sound").stop();
1073
+ push(ctx);
935
1074
  },
936
- voice([source]) {
937
- renderer.audio.voice(source);
938
- push();
1075
+ voice({ ctx }, [source]) {
1076
+ ctx.audio.voice(source);
1077
+ push(ctx);
939
1078
  },
940
- stopVoice() {
941
- renderer.audio.voiceStop();
942
- push();
1079
+ stopVoice({ ctx }) {
1080
+ ctx.audio.voiceStop();
1081
+ push(ctx);
943
1082
  },
944
- showCharacter([character, emotion, className, style]) {
1083
+ showCharacter({ ctx }, [character, emotion, className, style]) {
945
1084
  if (DEV && !characters[character].emotions[emotion]) {
946
1085
  throw new Error(`Attempt to show character "${character}" with unknown emotion "${emotion}"`);
947
1086
  }
948
- const handle = renderer.character(character);
949
- handle.append(className, style, restoring);
950
- handle.withEmotion(emotion)();
951
- push();
1087
+ const handle = ctx.character(character);
1088
+ handle.append(className, style, ctx.meta.restoring);
1089
+ handle.emotion(emotion, true);
1090
+ push(ctx);
952
1091
  },
953
- hideCharacter([character, className, style, duration]) {
954
- renderer.character(character).remove(className, style, duration)(push, restoring);
1092
+ hideCharacter({ ctx }, [character, className, style, duration]) {
1093
+ ctx.character(character).remove(className, style, duration, ctx.meta.restoring).then(() => {
1094
+ push(ctx);
1095
+ });
955
1096
  },
956
- dialog([character, content, emotion]) {
1097
+ dialog({ ctx, data: data2 }, [character, content, emotion]) {
957
1098
  const name = (() => {
958
1099
  const c = character;
959
1100
  const cs = characters;
960
- const lang = $.get().meta[0];
1101
+ const [lang] = $.get().meta;
961
1102
  if (c && c in cs) {
962
1103
  const block = cs[c].name;
963
1104
  if (typeof block === "string") {
@@ -969,65 +1110,81 @@ var Novely = (() => {
969
1110
  }
970
1111
  return c || "";
971
1112
  })();
972
- const run = renderer.dialog(unwrap2(content), unwrap2(name), character, emotion);
973
- run(forward, goingBack);
1113
+ ctx.dialog(
1114
+ unwrap2(content, data2),
1115
+ unwrap2(name, data2),
1116
+ character,
1117
+ emotion,
1118
+ () => forward(ctx)
1119
+ );
974
1120
  },
975
- function([fn]) {
976
- const result = fn(restoring, goingBack);
977
- if (!restoring)
978
- result ? result.then(push) : push();
1121
+ function({ ctx }, [fn]) {
1122
+ const result = fn(ctx.meta.restoring, ctx.meta.goingBack, ctx.meta.preview);
1123
+ if (!ctx.meta.restoring) {
1124
+ result ? result.then(() => push(ctx)) : push(ctx);
1125
+ }
979
1126
  return result;
980
1127
  },
981
- choice([question, ...choices]) {
1128
+ choice({ ctx, data: data2 }, [question, ...choices]) {
982
1129
  const isWithoutQuestion = Array.isArray(question);
983
1130
  if (isWithoutQuestion) {
984
1131
  choices.unshift(question);
985
1132
  question = "";
986
1133
  }
987
- const unwrapped = choices.map(([content, action2, visible]) => {
1134
+ const unwrappedChoices = choices.map(([content, action2, visible]) => {
988
1135
  if (DEV && action2.length === 0 && (!visible || visible())) {
989
1136
  console.warn(`Choice children should not be empty, either add content there or make item not selectable`);
990
1137
  }
991
- return [unwrap2(content), action2, visible];
1138
+ return [unwrap2(content, data2), action2, visible];
992
1139
  });
993
- if (DEV && unwrapped.length === 0) {
1140
+ if (DEV && unwrappedChoices.length === 0) {
994
1141
  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]`);
995
1142
  }
996
- const run = renderer.choices(unwrap2(question), unwrapped);
997
- run((selected) => {
998
- enmemory();
1143
+ ctx.choices(unwrap2(question, data2), unwrappedChoices, (selected) => {
1144
+ if (!ctx.meta.preview) {
1145
+ enmemory(ctx);
1146
+ }
1147
+ const stack = useStack(ctx);
999
1148
  const offset = isWithoutQuestion ? 0 : 1;
1000
- if (DEV && !unwrapped[selected + offset]) {
1149
+ if (DEV && !unwrappedChoices[selected + offset]) {
1001
1150
  throw new Error("Choice children is empty, either add content there or make item not selectable");
1002
1151
  }
1003
1152
  stack.value[0].push(["choice", selected + offset], [null, 0]);
1004
- render();
1153
+ render(ctx);
1005
1154
  interactivity(true);
1006
1155
  });
1007
1156
  },
1008
- jump([scene]) {
1157
+ jump({ ctx, data: data2 }, [scene]) {
1009
1158
  if (DEV && !story[scene]) {
1010
1159
  throw new Error(`Attempt to jump to unknown scene "${scene}"`);
1011
1160
  }
1012
1161
  if (DEV && story[scene].length === 0) {
1013
1162
  throw new Error(`Attempt to jump to empty scene "${scene}"`);
1014
1163
  }
1164
+ const stack = useStack(ctx);
1015
1165
  stack.value[0] = [
1016
1166
  ["jump", scene],
1017
1167
  [null, -1]
1018
1168
  ];
1019
- match("clear", []);
1169
+ match("clear", [], {
1170
+ ctx,
1171
+ data: data2
1172
+ });
1020
1173
  },
1021
- clear([keep, characters2]) {
1022
- renderer.vibrate(0);
1023
- renderer.clear(goingBack, keep || EMPTY_SET, characters2 || EMPTY_SET)(push);
1024
- renderer.audio.clear();
1174
+ clear({ ctx }, [keep, characters2, audio]) {
1175
+ ctx.vibrate(0);
1176
+ ctx.clear(
1177
+ keep || EMPTY_SET,
1178
+ characters2 || EMPTY_SET,
1179
+ audio || { music: EMPTY_SET, sounds: EMPTY_SET },
1180
+ () => push(ctx)
1181
+ );
1025
1182
  },
1026
- condition([condition, variants]) {
1183
+ condition({ ctx }, [condition, variants]) {
1027
1184
  if (DEV && Object.values(variants).length === 0) {
1028
1185
  throw new Error(`Attempt to use Condition action with empty variants object`);
1029
1186
  }
1030
- if (!restoring) {
1187
+ if (!ctx.meta.restoring) {
1031
1188
  const val = String(condition());
1032
1189
  if (DEV && !variants[val]) {
1033
1190
  throw new Error(`Attempt to go to unknown variant "${val}"`);
@@ -1035,46 +1192,59 @@ var Novely = (() => {
1035
1192
  if (DEV && variants[val].length === 0) {
1036
1193
  throw new Error(`Attempt to go to empty variant "${val}"`);
1037
1194
  }
1195
+ const stack = useStack(MAIN_CONTEXT_KEY);
1038
1196
  stack.value[0].push(["condition", val], [null, 0]);
1039
- render();
1197
+ render(ctx);
1040
1198
  }
1041
1199
  },
1042
- end() {
1043
- renderer.vibrate(0);
1044
- renderer.clear(goingBack, EMPTY_SET, EMPTY_SET)(noop);
1200
+ end({ ctx }) {
1201
+ if (ctx.meta.preview)
1202
+ return;
1203
+ ctx.vibrate(0);
1204
+ ctx.clear(EMPTY_SET, EMPTY_SET, { music: EMPTY_SET, sounds: EMPTY_SET }, noop);
1045
1205
  renderer.ui.showScreen("mainmenu");
1046
1206
  interactivity(false);
1047
1207
  times.clear();
1048
1208
  },
1049
- input([question, onInput, setup]) {
1050
- renderer.input(unwrap2(question), onInput, setup)(forward);
1209
+ input({ ctx, data: data2 }, [question, onInput, setup]) {
1210
+ ctx.input(
1211
+ unwrap2(question, data2),
1212
+ onInput,
1213
+ setup || noop,
1214
+ () => forward(ctx)
1215
+ );
1051
1216
  },
1052
- custom([handler]) {
1053
- const result = renderer.custom(handler, goingBack, () => {
1054
- if (!restoring && handler.requireUserAction)
1055
- enmemory(), interactivity(true);
1056
- if (!restoring)
1057
- push();
1217
+ custom({ ctx }, [handler]) {
1218
+ const result = ctx.custom(handler, () => {
1219
+ if (ctx.meta.restoring)
1220
+ return;
1221
+ if (handler.requireUserAction && !ctx.meta.preview) {
1222
+ enmemory(ctx);
1223
+ interactivity(true);
1224
+ }
1225
+ push(ctx);
1058
1226
  });
1059
1227
  return result;
1060
1228
  },
1061
- vibrate(pattern) {
1062
- renderer.vibrate(pattern);
1063
- push();
1229
+ vibrate({ ctx }, pattern) {
1230
+ ctx.vibrate(pattern);
1231
+ push(ctx);
1064
1232
  },
1065
- next() {
1066
- push();
1233
+ next({ ctx }) {
1234
+ push(ctx);
1067
1235
  },
1068
- animateCharacter([character, timeout, ...classes]) {
1236
+ animateCharacter({ ctx, data: data2 }, [character, timeout, ...classes]) {
1069
1237
  if (DEV && classes.length === 0) {
1070
1238
  throw new Error("Attempt to use AnimateCharacter without classes. Classes should be provided [https://novely.pages.dev/guide/actions/animateCharacter.html]");
1071
1239
  }
1072
1240
  if (DEV && (timeout <= 0 || !Number.isFinite(timeout) || Number.isNaN(timeout))) {
1073
1241
  throw new Error("Attempt to use AnimateCharacter with unacceptable timeout. It should be finite and greater than zero");
1074
1242
  }
1243
+ if (ctx.meta.preview)
1244
+ return;
1075
1245
  const handler = (get) => {
1076
- const { clear } = get("@@internal-animate-character", false);
1077
- const char = renderer.store.characters[character];
1246
+ const { clear } = get(false);
1247
+ const char = ctx.getCharacter(character);
1078
1248
  if (!char)
1079
1249
  return;
1080
1250
  const target = char.canvas;
@@ -1090,18 +1260,23 @@ var Novely = (() => {
1090
1260
  clearTimeout(timeoutId);
1091
1261
  });
1092
1262
  };
1093
- match("custom", [handler]);
1263
+ handler.key = "@@internal-animate-character";
1264
+ match("custom", [handler], {
1265
+ ctx,
1266
+ data: data2
1267
+ });
1094
1268
  },
1095
- text(text) {
1096
- const string = text.map((content) => unwrap2(content)).join(" ");
1269
+ text({ ctx, data: data2 }, text) {
1270
+ const string = text.map((content) => unwrap2(content, data2)).join(" ");
1097
1271
  if (DEV && string.length === 0) {
1098
1272
  throw new Error(`Action Text was called with empty string or array`);
1099
1273
  }
1100
- renderer.text(string, forward, goingBack);
1274
+ ctx.text(string, () => forward(ctx));
1101
1275
  },
1102
- exit() {
1103
- if (restoring)
1276
+ exit({ ctx, data: data2 }) {
1277
+ if (ctx.meta.restoring)
1104
1278
  return;
1279
+ const stack = useStack(ctx);
1105
1280
  const path = stack.value[0];
1106
1281
  const last = path.at(-1);
1107
1282
  const ignore = [];
@@ -1115,7 +1290,10 @@ var Novely = (() => {
1115
1290
  if (isExitImpossible(path)) {
1116
1291
  const referred = refer(path);
1117
1292
  if (isAction(referred) && isSkippedDuringRestore(referred[0])) {
1118
- match("end", []);
1293
+ match("end", [], {
1294
+ ctx,
1295
+ data: data2
1296
+ });
1119
1297
  }
1120
1298
  return;
1121
1299
  }
@@ -1140,36 +1318,39 @@ var Novely = (() => {
1140
1318
  }
1141
1319
  break;
1142
1320
  }
1143
- render();
1321
+ render(ctx);
1144
1322
  },
1145
- preload([source]) {
1146
- if (!goingBack && !restoring && !PRELOADED_ASSETS.has(source)) {
1323
+ preload({ ctx }, [source]) {
1324
+ if (!ctx.meta.goingBack && !ctx.meta.restoring && !PRELOADED_ASSETS.has(source)) {
1147
1325
  PRELOADED_ASSETS.add(renderer.misc.preloadImage(source));
1148
1326
  }
1149
- push();
1327
+ push(ctx);
1150
1328
  },
1151
- block([scene]) {
1329
+ block({ ctx }, [scene]) {
1152
1330
  if (DEV && !story[scene]) {
1153
1331
  throw new Error(`Attempt to call Block action with unknown scene "${scene}"`);
1154
1332
  }
1155
1333
  if (DEV && story[scene].length === 0) {
1156
1334
  throw new Error(`Attempt to call Block action with empty scene "${scene}"`);
1157
1335
  }
1158
- if (!restoring) {
1336
+ if (!ctx.meta.restoring) {
1337
+ const stack = useStack(ctx);
1159
1338
  stack.value[0].push(["block", scene], [null, 0]);
1160
- render();
1339
+ render(ctx);
1161
1340
  }
1162
1341
  }
1163
1342
  });
1164
- const enmemory = () => {
1165
- if (restoring)
1343
+ const enmemory = (ctx) => {
1344
+ if (ctx.meta.restoring)
1166
1345
  return;
1346
+ const stack = useStack(ctx);
1167
1347
  const current = klona(stack.value);
1168
1348
  current[2][1] = "auto";
1169
1349
  stack.push(current);
1170
1350
  save(true, "auto");
1171
1351
  };
1172
- const next = () => {
1352
+ const next = (ctx) => {
1353
+ const stack = useStack(ctx);
1173
1354
  const path = stack.value[0];
1174
1355
  const last = path.at(-1);
1175
1356
  if (last && (isNull(last[0]) || last[0] === "jump") && isNumber(last[1])) {
@@ -1178,33 +1359,42 @@ var Novely = (() => {
1178
1359
  path.push([null, 0]);
1179
1360
  }
1180
1361
  };
1181
- const render = () => {
1182
- const referred = refer();
1362
+ const render = (ctx) => {
1363
+ const stack = useStack(ctx);
1364
+ const referred = refer(stack.value[0]);
1183
1365
  if (isAction(referred)) {
1184
1366
  const [action2, ...props] = referred;
1185
- match(action2, props);
1367
+ match(action2, props, {
1368
+ ctx,
1369
+ data: stack.value[1]
1370
+ });
1186
1371
  } else {
1187
- match("exit", []);
1372
+ match("exit", [], {
1373
+ ctx,
1374
+ data: stack.value[1]
1375
+ });
1188
1376
  }
1189
1377
  };
1190
- const push = () => {
1191
- if (!restoring)
1192
- next(), render();
1378
+ const push = (ctx) => {
1379
+ if (!ctx.meta.restoring)
1380
+ next(ctx), render(ctx);
1193
1381
  };
1194
- const forward = () => {
1195
- enmemory();
1196
- push();
1197
- interactivity(true);
1382
+ const forward = (ctx) => {
1383
+ if (!ctx.meta.preview)
1384
+ enmemory(ctx);
1385
+ push(ctx);
1386
+ if (!ctx.meta.preview)
1387
+ interactivity(true);
1198
1388
  };
1199
1389
  const interactivity = (value = false) => {
1200
1390
  interacted = value ? interacted + 1 : 0;
1201
1391
  };
1202
- const unwrap2 = (content, global = false) => {
1392
+ const unwrap2 = (content, values) => {
1203
1393
  const {
1204
1394
  data: data2,
1205
1395
  meta: [lang]
1206
1396
  } = $.get();
1207
- const obj = global ? data2 : state();
1397
+ const obj = values ? values : data2;
1208
1398
  const cnt = isFunction(content) ? content() : typeof content === "string" ? content : content[lang];
1209
1399
  const str2 = isFunction(cnt) ? cnt() : cnt;
1210
1400
  const trans = translation[lang];
@@ -1244,7 +1434,7 @@ var Novely = (() => {
1244
1434
  * Unwraps translatable content to a string value
1245
1435
  */
1246
1436
  unwrap(content) {
1247
- return unwrap2(content, true);
1437
+ return unwrap2(content, $.get().data);
1248
1438
  }
1249
1439
  };
1250
1440
  };