@novely/core 0.19.5 → 0.20.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,16 +1110,17 @@ 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
+ const run = ctx.dialog(unwrap2(content, data2), unwrap2(name, data2), character, emotion);
1114
+ run(() => forward(ctx));
974
1115
  },
975
- function([fn]) {
976
- const result = fn(restoring, goingBack);
977
- if (!restoring)
978
- result ? result.then(push) : push();
1116
+ function({ ctx }, [fn]) {
1117
+ const result = fn(ctx.meta.restoring, ctx.meta.goingBack, ctx.meta.preview);
1118
+ if (!ctx.meta.restoring) {
1119
+ result ? result.then(() => push(ctx)) : push(ctx);
1120
+ }
979
1121
  return result;
980
1122
  },
981
- choice([question, ...choices]) {
1123
+ choice({ ctx, data: data2 }, [question, ...choices]) {
982
1124
  const isWithoutQuestion = Array.isArray(question);
983
1125
  if (isWithoutQuestion) {
984
1126
  choices.unshift(question);
@@ -988,46 +1130,57 @@ var Novely = (() => {
988
1130
  if (DEV && action2.length === 0 && (!visible || visible())) {
989
1131
  console.warn(`Choice children should not be empty, either add content there or make item not selectable`);
990
1132
  }
991
- return [unwrap2(content), action2, visible];
1133
+ return [unwrap2(content, data2), action2, visible];
992
1134
  });
993
1135
  if (DEV && unwrapped.length === 0) {
994
1136
  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
1137
  }
996
- const run = renderer.choices(unwrap2(question), unwrapped);
1138
+ const run = ctx.choices(unwrap2(question, data2), unwrapped);
997
1139
  run((selected) => {
998
- enmemory();
1140
+ if (!ctx.meta.preview) {
1141
+ enmemory(ctx);
1142
+ }
1143
+ const stack = useStack(ctx);
999
1144
  const offset = isWithoutQuestion ? 0 : 1;
1000
1145
  if (DEV && !unwrapped[selected + offset]) {
1001
1146
  throw new Error("Choice children is empty, either add content there or make item not selectable");
1002
1147
  }
1003
1148
  stack.value[0].push(["choice", selected + offset], [null, 0]);
1004
- render();
1149
+ render(ctx);
1005
1150
  interactivity(true);
1006
1151
  });
1007
1152
  },
1008
- jump([scene]) {
1153
+ jump({ ctx, data: data2 }, [scene]) {
1009
1154
  if (DEV && !story[scene]) {
1010
1155
  throw new Error(`Attempt to jump to unknown scene "${scene}"`);
1011
1156
  }
1012
1157
  if (DEV && story[scene].length === 0) {
1013
1158
  throw new Error(`Attempt to jump to empty scene "${scene}"`);
1014
1159
  }
1160
+ const stack = useStack(ctx);
1015
1161
  stack.value[0] = [
1016
1162
  ["jump", scene],
1017
1163
  [null, -1]
1018
1164
  ];
1019
- match("clear", []);
1165
+ match("clear", [], {
1166
+ ctx,
1167
+ data: data2
1168
+ });
1020
1169
  },
1021
- clear([keep, characters2]) {
1022
- renderer.vibrate(0);
1023
- renderer.clear(goingBack, keep || EMPTY_SET, characters2 || EMPTY_SET)(push);
1024
- renderer.audio.clear();
1170
+ clear({ ctx }, [keep, characters2, audio]) {
1171
+ ctx.vibrate(0);
1172
+ const run = ctx.clear(
1173
+ keep || EMPTY_SET,
1174
+ characters2 || EMPTY_SET,
1175
+ audio || { music: EMPTY_SET, sounds: EMPTY_SET }
1176
+ );
1177
+ run(() => push(ctx));
1025
1178
  },
1026
- condition([condition, variants]) {
1179
+ condition({ ctx }, [condition, variants]) {
1027
1180
  if (DEV && Object.values(variants).length === 0) {
1028
1181
  throw new Error(`Attempt to use Condition action with empty variants object`);
1029
1182
  }
1030
- if (!restoring) {
1183
+ if (!ctx.meta.restoring) {
1031
1184
  const val = String(condition());
1032
1185
  if (DEV && !variants[val]) {
1033
1186
  throw new Error(`Attempt to go to unknown variant "${val}"`);
@@ -1035,46 +1188,56 @@ var Novely = (() => {
1035
1188
  if (DEV && variants[val].length === 0) {
1036
1189
  throw new Error(`Attempt to go to empty variant "${val}"`);
1037
1190
  }
1191
+ const stack = useStack(MAIN_CONTEXT_KEY);
1038
1192
  stack.value[0].push(["condition", val], [null, 0]);
1039
- render();
1193
+ render(ctx);
1040
1194
  }
1041
1195
  },
1042
- end() {
1043
- renderer.vibrate(0);
1044
- renderer.clear(goingBack, EMPTY_SET, EMPTY_SET)(noop);
1196
+ end({ ctx }) {
1197
+ if (ctx.meta.preview)
1198
+ return;
1199
+ ctx.vibrate(0);
1200
+ ctx.clear(EMPTY_SET, EMPTY_SET, { music: EMPTY_SET, sounds: EMPTY_SET })(noop);
1045
1201
  renderer.ui.showScreen("mainmenu");
1046
1202
  interactivity(false);
1047
1203
  times.clear();
1048
1204
  },
1049
- input([question, onInput, setup]) {
1050
- renderer.input(unwrap2(question), onInput, setup)(forward);
1205
+ input({ ctx, data: data2 }, [question, onInput, setup]) {
1206
+ ctx.input(unwrap2(question, data2), onInput, setup)(() => {
1207
+ forward(ctx);
1208
+ });
1051
1209
  },
1052
- custom([handler]) {
1053
- const result = renderer.custom(handler, goingBack, () => {
1054
- if (!restoring && handler.requireUserAction)
1055
- enmemory(), interactivity(true);
1056
- if (!restoring)
1057
- push();
1210
+ custom({ ctx }, [handler]) {
1211
+ const result = ctx.custom(handler, () => {
1212
+ if (ctx.meta.restoring)
1213
+ return;
1214
+ if (handler.requireUserAction && !ctx.meta.preview) {
1215
+ enmemory(ctx);
1216
+ interactivity(true);
1217
+ }
1218
+ push(ctx);
1058
1219
  });
1059
1220
  return result;
1060
1221
  },
1061
- vibrate(pattern) {
1062
- renderer.vibrate(pattern);
1063
- push();
1222
+ vibrate({ ctx }, pattern) {
1223
+ ctx.vibrate(pattern);
1224
+ push(ctx);
1064
1225
  },
1065
- next() {
1066
- push();
1226
+ next({ ctx }) {
1227
+ push(ctx);
1067
1228
  },
1068
- animateCharacter([character, timeout, ...classes]) {
1229
+ animateCharacter({ ctx, data: data2 }, [character, timeout, ...classes]) {
1069
1230
  if (DEV && classes.length === 0) {
1070
1231
  throw new Error("Attempt to use AnimateCharacter without classes. Classes should be provided [https://novely.pages.dev/guide/actions/animateCharacter.html]");
1071
1232
  }
1072
1233
  if (DEV && (timeout <= 0 || !Number.isFinite(timeout) || Number.isNaN(timeout))) {
1073
1234
  throw new Error("Attempt to use AnimateCharacter with unacceptable timeout. It should be finite and greater than zero");
1074
1235
  }
1236
+ if (ctx.meta.preview)
1237
+ return;
1075
1238
  const handler = (get) => {
1076
- const { clear } = get("@@internal-animate-character", false);
1077
- const char = renderer.store.characters[character];
1239
+ const { clear } = get(false);
1240
+ const char = ctx.getCharacter(character);
1078
1241
  if (!char)
1079
1242
  return;
1080
1243
  const target = char.canvas;
@@ -1090,18 +1253,23 @@ var Novely = (() => {
1090
1253
  clearTimeout(timeoutId);
1091
1254
  });
1092
1255
  };
1093
- match("custom", [handler]);
1256
+ handler.key = "@@internal-animate-character";
1257
+ match("custom", [handler], {
1258
+ ctx,
1259
+ data: data2
1260
+ });
1094
1261
  },
1095
- text(text) {
1096
- const string = text.map((content) => unwrap2(content)).join(" ");
1262
+ text({ ctx, data: data2 }, text) {
1263
+ const string = text.map((content) => unwrap2(content, data2)).join(" ");
1097
1264
  if (DEV && string.length === 0) {
1098
1265
  throw new Error(`Action Text was called with empty string or array`);
1099
1266
  }
1100
- renderer.text(string, forward, goingBack);
1267
+ ctx.text(string, () => forward(ctx));
1101
1268
  },
1102
- exit() {
1103
- if (restoring)
1269
+ exit({ ctx, data: data2 }) {
1270
+ if (ctx.meta.restoring)
1104
1271
  return;
1272
+ const stack = useStack(ctx);
1105
1273
  const path = stack.value[0];
1106
1274
  const last = path.at(-1);
1107
1275
  const ignore = [];
@@ -1115,7 +1283,10 @@ var Novely = (() => {
1115
1283
  if (isExitImpossible(path)) {
1116
1284
  const referred = refer(path);
1117
1285
  if (isAction(referred) && isSkippedDuringRestore(referred[0])) {
1118
- match("end", []);
1286
+ match("end", [], {
1287
+ ctx,
1288
+ data: data2
1289
+ });
1119
1290
  }
1120
1291
  return;
1121
1292
  }
@@ -1140,36 +1311,39 @@ var Novely = (() => {
1140
1311
  }
1141
1312
  break;
1142
1313
  }
1143
- render();
1314
+ render(ctx);
1144
1315
  },
1145
- preload([source]) {
1146
- if (!goingBack && !restoring && !PRELOADED_ASSETS.has(source)) {
1316
+ preload({ ctx }, [source]) {
1317
+ if (!ctx.meta.goingBack && !ctx.meta.restoring && !PRELOADED_ASSETS.has(source)) {
1147
1318
  PRELOADED_ASSETS.add(renderer.misc.preloadImage(source));
1148
1319
  }
1149
- push();
1320
+ push(ctx);
1150
1321
  },
1151
- block([scene]) {
1322
+ block({ ctx }, [scene]) {
1152
1323
  if (DEV && !story[scene]) {
1153
1324
  throw new Error(`Attempt to call Block action with unknown scene "${scene}"`);
1154
1325
  }
1155
1326
  if (DEV && story[scene].length === 0) {
1156
1327
  throw new Error(`Attempt to call Block action with empty scene "${scene}"`);
1157
1328
  }
1158
- if (!restoring) {
1329
+ if (!ctx.meta.restoring) {
1330
+ const stack = useStack(ctx);
1159
1331
  stack.value[0].push(["block", scene], [null, 0]);
1160
- render();
1332
+ render(ctx);
1161
1333
  }
1162
1334
  }
1163
1335
  });
1164
- const enmemory = () => {
1165
- if (restoring)
1336
+ const enmemory = (ctx) => {
1337
+ if (ctx.meta.restoring)
1166
1338
  return;
1339
+ const stack = useStack(ctx);
1167
1340
  const current = klona(stack.value);
1168
1341
  current[2][1] = "auto";
1169
1342
  stack.push(current);
1170
1343
  save(true, "auto");
1171
1344
  };
1172
- const next = () => {
1345
+ const next = (ctx) => {
1346
+ const stack = useStack(ctx);
1173
1347
  const path = stack.value[0];
1174
1348
  const last = path.at(-1);
1175
1349
  if (last && (isNull(last[0]) || last[0] === "jump") && isNumber(last[1])) {
@@ -1178,33 +1352,42 @@ var Novely = (() => {
1178
1352
  path.push([null, 0]);
1179
1353
  }
1180
1354
  };
1181
- const render = () => {
1182
- const referred = refer();
1355
+ const render = (ctx) => {
1356
+ const stack = useStack(ctx);
1357
+ const referred = refer(stack.value[0]);
1183
1358
  if (isAction(referred)) {
1184
1359
  const [action2, ...props] = referred;
1185
- match(action2, props);
1360
+ match(action2, props, {
1361
+ ctx,
1362
+ data: stack.value[1]
1363
+ });
1186
1364
  } else {
1187
- match("exit", []);
1365
+ match("exit", [], {
1366
+ ctx,
1367
+ data: stack.value[1]
1368
+ });
1188
1369
  }
1189
1370
  };
1190
- const push = () => {
1191
- if (!restoring)
1192
- next(), render();
1371
+ const push = (ctx) => {
1372
+ if (!ctx.meta.restoring)
1373
+ next(ctx), render(ctx);
1193
1374
  };
1194
- const forward = () => {
1195
- enmemory();
1196
- push();
1197
- interactivity(true);
1375
+ const forward = (ctx) => {
1376
+ if (!ctx.meta.preview)
1377
+ enmemory(ctx);
1378
+ push(ctx);
1379
+ if (!ctx.meta.preview)
1380
+ interactivity(true);
1198
1381
  };
1199
1382
  const interactivity = (value = false) => {
1200
1383
  interacted = value ? interacted + 1 : 0;
1201
1384
  };
1202
- const unwrap2 = (content, global = false) => {
1385
+ const unwrap2 = (content, values) => {
1203
1386
  const {
1204
1387
  data: data2,
1205
1388
  meta: [lang]
1206
1389
  } = $.get();
1207
- const obj = global ? data2 : state();
1390
+ const obj = values ? values : data2;
1208
1391
  const cnt = isFunction(content) ? content() : typeof content === "string" ? content : content[lang];
1209
1392
  const str2 = isFunction(cnt) ? cnt() : cnt;
1210
1393
  const trans = translation[lang];
@@ -1244,7 +1427,7 @@ var Novely = (() => {
1244
1427
  * Unwraps translatable content to a string value
1245
1428
  */
1246
1429
  unwrap(content) {
1247
- return unwrap2(content, true);
1430
+ return unwrap2(content, $.get().data);
1248
1431
  }
1249
1432
  };
1250
1433
  };