@novely/core 0.19.4 → 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) => {
@@ -303,15 +319,199 @@ var Novely = (() => {
303
319
  if (blockStatements.length > blockExitStatements.length) {
304
320
  return false;
305
321
  }
306
- for (let i = 0; i++; i < blockStatements.length) {
307
- const b = blockStatements[i][0];
308
- const be = blockExitStatements[i][0];
309
- if (!b || !be)
310
- return false;
311
- if (!be.startsWith(b))
312
- return false;
322
+ return !blockExitStatements.every(([name], i) => name && name.startsWith(blockStatements[i][0]));
323
+ };
324
+ var getOppositeAction = (action) => {
325
+ const MAP = {
326
+ "showCharacter": "hideCharacter",
327
+ "playSound": "stopSound",
328
+ "playMusic": "stopMusic",
329
+ "voice": "stopVoice"
330
+ };
331
+ return MAP[action];
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
+ }
313
454
  }
314
- return true;
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;
315
515
  };
316
516
 
317
517
  // src/global.ts
@@ -332,10 +532,13 @@ var Novely = (() => {
332
532
  const update = (fn) => {
333
533
  push(current = fn(current));
334
534
  };
535
+ const set = (val) => {
536
+ update(() => val);
537
+ };
335
538
  const get = () => {
336
539
  return current;
337
540
  };
338
- return { subscribe, update, get };
541
+ return { subscribe, update, set, get };
339
542
  };
340
543
 
341
544
  // ../deepmerge/dist/index.js
@@ -587,6 +790,7 @@ var Novely = (() => {
587
790
  }
588
791
  });
589
792
  function state(value) {
793
+ const stack = useStack(MAIN_CONTEXT_KEY);
590
794
  if (!value)
591
795
  return stack.value[1];
592
796
  const prev = stack.value[1];
@@ -603,26 +807,6 @@ var Novely = (() => {
603
807
  [intime(Date.now()), "auto"]
604
808
  ];
605
809
  };
606
- const createStack = (current, stack2 = [current]) => {
607
- return {
608
- get value() {
609
- return stack2.at(-1);
610
- },
611
- set value(value) {
612
- stack2[stack2.length - 1] = value;
613
- },
614
- back() {
615
- if (stack2.length > 1)
616
- stack2.pop(), goingBack = true;
617
- },
618
- push(value) {
619
- stack2.push(value);
620
- },
621
- clear() {
622
- stack2 = [getDefaultSave(klona(defaultState))];
623
- }
624
- };
625
- };
626
810
  const getLanguageWithoutParameters = () => {
627
811
  return getLanguage2(languages, getLanguage);
628
812
  };
@@ -664,11 +848,10 @@ var Novely = (() => {
664
848
  }
665
849
  $$.update((prev) => (prev.dataLoaded = true, prev));
666
850
  dataLoaded.resolve();
667
- $.update(() => stored);
851
+ $.set(stored);
668
852
  };
669
853
  storageDelay.then(getStoredData);
670
854
  const initial = getDefaultSave(klona(defaultState));
671
- const stack = createStack(initial);
672
855
  addEventListener("visibilitychange", () => {
673
856
  if (document.visibilityState === "hidden") {
674
857
  throttledEmergencyOnStorageDataChange();
@@ -680,6 +863,7 @@ var Novely = (() => {
680
863
  return;
681
864
  if (!autosaves && type === "auto")
682
865
  return;
866
+ const stack = useStack(MAIN_CONTEXT_KEY);
683
867
  const current = klona(stack.value);
684
868
  $.update((prev) => {
685
869
  const isLatest = findLastIndex(prev.saves, (value) => times.has(value[2][0])) === prev.saves.length - 1;
@@ -709,139 +893,65 @@ var Novely = (() => {
709
893
  }
710
894
  restore(save2);
711
895
  };
712
- const set = (save2) => {
896
+ const set = (save2, ctx) => {
897
+ const stack = useStack(ctx || renderer.getContext(MAIN_CONTEXT_KEY));
713
898
  stack.value = save2;
714
899
  return restore(save2);
715
900
  };
716
- let restoring = false;
717
- let goingBack = false;
718
901
  let interacted = 0;
719
902
  const restore = async (save2) => {
720
903
  if (!$$.get().dataLoaded)
721
904
  return;
722
905
  let latest = save2 || $.get().saves.at(-1);
723
906
  if (!latest) {
724
- $.update(() => ({
907
+ $.set({
725
908
  saves: [initial],
726
909
  data: klona(defaultData),
727
910
  meta: [getLanguageWithoutParameters(), DEFAULT_TYPEWRITER_SPEED, 1, 1, 1]
728
- }));
911
+ });
729
912
  latest = klona(initial);
730
913
  }
731
- restoring = true, stack.value = latest;
732
- const path = stack.value[0];
733
- let current = story;
734
- let precurrent;
735
- let ignoreNested = false;
736
- let index = 0;
737
- const max = stack.value[0].reduce((acc, [type, val]) => {
738
- if (isNull(type) && isNumber(val)) {
739
- return acc + 1;
740
- }
741
- return acc;
742
- }, 0);
743
- const queue = [];
744
- const keep = /* @__PURE__ */ new Set();
745
- const characters2 = /* @__PURE__ */ new Set();
746
- const blocks = [];
747
- for (const [type, val] of path) {
748
- if (type === "jump") {
749
- precurrent = story;
750
- current = current[val];
751
- } else if (type === null) {
752
- precurrent = current;
753
- if (isNumber(val)) {
754
- index++;
755
- let startIndex = 0;
756
- if (ignoreNested) {
757
- const prev = findLastPathItemBeforeItemOfType(path.slice(0, index), "block");
758
- if (prev) {
759
- startIndex = prev[1];
760
- ignoreNested = false;
761
- }
762
- }
763
- for (let i = startIndex; i <= val; i++) {
764
- const item = current[i];
765
- if (!isAction(item))
766
- continue;
767
- const [action2, ...meta] = item;
768
- const push2 = () => {
769
- keep.add(action2);
770
- queue.push([action2, meta]);
771
- };
772
- if (action2 === "showCharacter")
773
- characters2.add(meta[0]);
774
- if (isSkippedDuringRestore(action2) || isUserRequiredAction(action2, meta)) {
775
- if (index === max && i === val) {
776
- push2();
777
- } else {
778
- continue;
779
- }
780
- }
781
- push2();
782
- }
783
- }
784
- current = current[val];
785
- } else if (type === "choice") {
786
- blocks.push(precurrent);
787
- current = current[val + 1][1];
788
- } else if (type === "condition") {
789
- blocks.push(precurrent);
790
- current = current[2][val];
791
- } else if (type === "block") {
792
- blocks.push(precurrent);
793
- current = story[val];
794
- } else if (type === "block:exit" || type === "choice:exit" || type === "condition:exit") {
795
- current = blocks.pop();
796
- ignoreNested = true;
797
- }
798
- }
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;
799
919
  renderer.ui.showScreen("game");
800
- match("clear", [keep, characters2]);
801
- const next2 = (i) => queue.slice(i + 1);
802
- for await (const [i, [action2, meta]] of queue.entries()) {
803
- if (action2 === "function" || action2 === "custom") {
804
- if (action2 === "custom" && meta[0].callOnlyLatest) {
805
- const notLatest = next2(i).some(([, _meta]) => {
806
- if (!_meta || !meta)
807
- return false;
808
- const c0 = _meta[0];
809
- const c1 = meta[0];
810
- const isIdenticalID = c0.id && c1.id && c0.id === c1.id;
811
- const isIdenticalByReference = c0 === c1;
812
- return isIdenticalID || isIdenticalByReference || str(c0) === str(c1);
813
- });
814
- if (notLatest)
815
- continue;
816
- }
817
- const result = match(action2, meta);
818
- if (isPromise(result)) {
819
- 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
+ }
820
933
  }
821
- } else if (action2 === "showCharacter" || action2 === "playSound" || action2 === "playMusic") {
822
- const closing = action2 === "showCharacter" ? "hideCharacter" : action2 === "playSound" ? "stopSound" : "stopMusic";
823
- const skip = next2(i).some(([_action, _meta]) => {
824
- if (!_meta || !meta)
825
- return false;
826
- const hidden = _action === closing && _meta[0] === meta[0];
827
- const notLatest = _action === action2 && _meta[0] === meta[0];
828
- return hidden || notLatest;
829
- });
830
- if (skip)
831
- continue;
832
- match(action2, meta);
833
- } else if (action2 === "showBackground" || action2 === "animateCharacter" || action2 === "preload" || action2 === "voice") {
834
- const notLatest = next2(i).some(([_action]) => action2 === _action);
835
- if (notLatest)
836
- continue;
837
- match(action2, meta);
838
- } else {
839
- match(action2, meta);
840
934
  }
841
935
  }
842
- 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);
843
950
  };
844
- const refer = (path = stack.value[0]) => {
951
+ const refer = (path) => {
952
+ if (!path) {
953
+ path = useStack(MAIN_CONTEXT_KEY).value[0];
954
+ }
845
955
  let current = story;
846
956
  let precurrent = story;
847
957
  const blocks = [];
@@ -872,10 +982,12 @@ var Novely = (() => {
872
982
  renderer.ui.showExitPrompt();
873
983
  return;
874
984
  }
985
+ const ctx = renderer.getContext(MAIN_CONTEXT_KEY);
986
+ const stack = useStack(ctx);
875
987
  const current = stack.value;
876
988
  stack.clear();
877
989
  renderer.ui.showScreen("mainmenu");
878
- renderer.audio.destroy();
990
+ ctx.audio.destroy();
879
991
  const [time, type] = current[2];
880
992
  if (type === "auto" && interacted <= 1 && times.has(time)) {
881
993
  $.update((prev) => {
@@ -886,13 +998,38 @@ var Novely = (() => {
886
998
  interactivity(false);
887
999
  times.clear();
888
1000
  };
889
- const back = () => {
890
- 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);
891
1005
  };
892
1006
  const t = (key, lang) => {
893
1007
  return translation[lang].internal[key];
894
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
+ };
895
1031
  const renderer = createRenderer({
1032
+ mainContextKey: MAIN_CONTEXT_KEY,
896
1033
  characters,
897
1034
  set,
898
1035
  restore,
@@ -901,62 +1038,67 @@ var Novely = (() => {
901
1038
  exit,
902
1039
  back,
903
1040
  t,
904
- stack,
1041
+ preview,
1042
+ removeContext,
905
1043
  languages,
906
1044
  $,
907
1045
  $$
908
1046
  });
1047
+ const useStack = createUseStackFunction(renderer);
1048
+ useStack(MAIN_CONTEXT_KEY).push(initial);
909
1049
  renderer.ui.start();
910
- const match = matchAction({
911
- wait([time]) {
912
- if (!restoring)
1050
+ const match = matchAction(renderer.getContext, {
1051
+ wait({ ctx }, [time]) {
1052
+ if (!ctx.meta.restoring)
913
1053
  setTimeout(push, isFunction(time) ? time() : time);
914
1054
  },
915
- showBackground([background]) {
916
- renderer.background(background);
917
- push();
1055
+ showBackground({ ctx }, [background]) {
1056
+ ctx.background(background);
1057
+ push(ctx);
918
1058
  },
919
- playMusic([source]) {
920
- renderer.audio.music(source, "music", true).play();
921
- push();
1059
+ playMusic({ ctx }, [source]) {
1060
+ ctx.audio.music(source, "music", true).play();
1061
+ push(ctx);
922
1062
  },
923
- stopMusic([source]) {
924
- renderer.audio.music(source, "music").stop();
925
- push();
1063
+ stopMusic({ ctx }, [source]) {
1064
+ ctx.audio.music(source, "music").stop();
1065
+ push(ctx);
926
1066
  },
927
- playSound([source, loop]) {
928
- renderer.audio.music(source, "sound", loop || false).play();
929
- push();
1067
+ playSound({ ctx }, [source, loop]) {
1068
+ ctx.audio.music(source, "sound", loop || false).play();
1069
+ push(ctx);
930
1070
  },
931
- stopSound([source]) {
932
- renderer.audio.music(source, "sound").stop();
933
- push();
1071
+ stopSound({ ctx }, [source]) {
1072
+ ctx.audio.music(source, "sound").stop();
1073
+ push(ctx);
934
1074
  },
935
- voice([source]) {
936
- renderer.audio.voice(source);
937
- push();
1075
+ voice({ ctx }, [source]) {
1076
+ ctx.audio.voice(source);
1077
+ push(ctx);
938
1078
  },
939
- stopVoice() {
940
- renderer.audio.voiceStop();
941
- push();
1079
+ stopVoice({ ctx }) {
1080
+ ctx.audio.voiceStop();
1081
+ push(ctx);
942
1082
  },
943
- showCharacter([character, emotion, className, style]) {
1083
+ showCharacter({ ctx }, [character, emotion, className, style]) {
944
1084
  if (DEV && !characters[character].emotions[emotion]) {
945
1085
  throw new Error(`Attempt to show character "${character}" with unknown emotion "${emotion}"`);
946
1086
  }
947
- const handle = renderer.character(character);
948
- handle.append(className, style, restoring);
949
- handle.withEmotion(emotion)();
950
- push();
1087
+ const handle = ctx.character(character);
1088
+ handle.append(className, style, ctx.meta.restoring);
1089
+ handle.emotion(emotion, true);
1090
+ push(ctx);
951
1091
  },
952
- hideCharacter([character, className, style, duration]) {
953
- 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
+ });
954
1096
  },
955
- dialog([character, content, emotion]) {
1097
+ dialog({ ctx, data: data2 }, [character, content, emotion]) {
956
1098
  const name = (() => {
957
1099
  const c = character;
958
1100
  const cs = characters;
959
- const lang = $.get().meta[0];
1101
+ const [lang] = $.get().meta;
960
1102
  if (c && c in cs) {
961
1103
  const block = cs[c].name;
962
1104
  if (typeof block === "string") {
@@ -968,16 +1110,17 @@ var Novely = (() => {
968
1110
  }
969
1111
  return c || "";
970
1112
  })();
971
- const run = renderer.dialog(unwrap2(content), unwrap2(name), character, emotion);
972
- run(forward, goingBack);
1113
+ const run = ctx.dialog(unwrap2(content, data2), unwrap2(name, data2), character, emotion);
1114
+ run(() => forward(ctx));
973
1115
  },
974
- function([fn]) {
975
- const result = fn(restoring, goingBack);
976
- if (!restoring)
977
- 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
+ }
978
1121
  return result;
979
1122
  },
980
- choice([question, ...choices]) {
1123
+ choice({ ctx, data: data2 }, [question, ...choices]) {
981
1124
  const isWithoutQuestion = Array.isArray(question);
982
1125
  if (isWithoutQuestion) {
983
1126
  choices.unshift(question);
@@ -987,46 +1130,57 @@ var Novely = (() => {
987
1130
  if (DEV && action2.length === 0 && (!visible || visible())) {
988
1131
  console.warn(`Choice children should not be empty, either add content there or make item not selectable`);
989
1132
  }
990
- return [unwrap2(content), action2, visible];
1133
+ return [unwrap2(content, data2), action2, visible];
991
1134
  });
992
1135
  if (DEV && unwrapped.length === 0) {
993
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]`);
994
1137
  }
995
- const run = renderer.choices(unwrap2(question), unwrapped);
1138
+ const run = ctx.choices(unwrap2(question, data2), unwrapped);
996
1139
  run((selected) => {
997
- enmemory();
1140
+ if (!ctx.meta.preview) {
1141
+ enmemory(ctx);
1142
+ }
1143
+ const stack = useStack(ctx);
998
1144
  const offset = isWithoutQuestion ? 0 : 1;
999
1145
  if (DEV && !unwrapped[selected + offset]) {
1000
1146
  throw new Error("Choice children is empty, either add content there or make item not selectable");
1001
1147
  }
1002
1148
  stack.value[0].push(["choice", selected + offset], [null, 0]);
1003
- render();
1149
+ render(ctx);
1004
1150
  interactivity(true);
1005
1151
  });
1006
1152
  },
1007
- jump([scene]) {
1153
+ jump({ ctx, data: data2 }, [scene]) {
1008
1154
  if (DEV && !story[scene]) {
1009
1155
  throw new Error(`Attempt to jump to unknown scene "${scene}"`);
1010
1156
  }
1011
1157
  if (DEV && story[scene].length === 0) {
1012
1158
  throw new Error(`Attempt to jump to empty scene "${scene}"`);
1013
1159
  }
1160
+ const stack = useStack(ctx);
1014
1161
  stack.value[0] = [
1015
1162
  ["jump", scene],
1016
1163
  [null, -1]
1017
1164
  ];
1018
- match("clear", []);
1165
+ match("clear", [], {
1166
+ ctx,
1167
+ data: data2
1168
+ });
1019
1169
  },
1020
- clear([keep, characters2]) {
1021
- renderer.vibrate(0);
1022
- renderer.clear(goingBack, keep || EMPTY_SET, characters2 || EMPTY_SET)(push);
1023
- 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));
1024
1178
  },
1025
- condition([condition, variants]) {
1179
+ condition({ ctx }, [condition, variants]) {
1026
1180
  if (DEV && Object.values(variants).length === 0) {
1027
1181
  throw new Error(`Attempt to use Condition action with empty variants object`);
1028
1182
  }
1029
- if (!restoring) {
1183
+ if (!ctx.meta.restoring) {
1030
1184
  const val = String(condition());
1031
1185
  if (DEV && !variants[val]) {
1032
1186
  throw new Error(`Attempt to go to unknown variant "${val}"`);
@@ -1034,46 +1188,56 @@ var Novely = (() => {
1034
1188
  if (DEV && variants[val].length === 0) {
1035
1189
  throw new Error(`Attempt to go to empty variant "${val}"`);
1036
1190
  }
1191
+ const stack = useStack(MAIN_CONTEXT_KEY);
1037
1192
  stack.value[0].push(["condition", val], [null, 0]);
1038
- render();
1193
+ render(ctx);
1039
1194
  }
1040
1195
  },
1041
- end() {
1042
- renderer.vibrate(0);
1043
- 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);
1044
1201
  renderer.ui.showScreen("mainmenu");
1045
1202
  interactivity(false);
1046
1203
  times.clear();
1047
1204
  },
1048
- input([question, onInput, setup]) {
1049
- 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
+ });
1050
1209
  },
1051
- custom([handler]) {
1052
- const result = renderer.custom(handler, goingBack, () => {
1053
- if (!restoring && handler.requireUserAction)
1054
- enmemory(), interactivity(true);
1055
- if (!restoring)
1056
- 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);
1057
1219
  });
1058
1220
  return result;
1059
1221
  },
1060
- vibrate(pattern) {
1061
- renderer.vibrate(pattern);
1062
- push();
1222
+ vibrate({ ctx }, pattern) {
1223
+ ctx.vibrate(pattern);
1224
+ push(ctx);
1063
1225
  },
1064
- next() {
1065
- push();
1226
+ next({ ctx }) {
1227
+ push(ctx);
1066
1228
  },
1067
- animateCharacter([character, timeout, ...classes]) {
1229
+ animateCharacter({ ctx, data: data2 }, [character, timeout, ...classes]) {
1068
1230
  if (DEV && classes.length === 0) {
1069
1231
  throw new Error("Attempt to use AnimateCharacter without classes. Classes should be provided [https://novely.pages.dev/guide/actions/animateCharacter.html]");
1070
1232
  }
1071
1233
  if (DEV && (timeout <= 0 || !Number.isFinite(timeout) || Number.isNaN(timeout))) {
1072
1234
  throw new Error("Attempt to use AnimateCharacter with unacceptable timeout. It should be finite and greater than zero");
1073
1235
  }
1236
+ if (ctx.meta.preview)
1237
+ return;
1074
1238
  const handler = (get) => {
1075
- const { clear } = get("@@internal-animate-character", false);
1076
- const char = renderer.store.characters[character];
1239
+ const { clear } = get(false);
1240
+ const char = ctx.getCharacter(character);
1077
1241
  if (!char)
1078
1242
  return;
1079
1243
  const target = char.canvas;
@@ -1089,18 +1253,23 @@ var Novely = (() => {
1089
1253
  clearTimeout(timeoutId);
1090
1254
  });
1091
1255
  };
1092
- match("custom", [handler]);
1256
+ handler.key = "@@internal-animate-character";
1257
+ match("custom", [handler], {
1258
+ ctx,
1259
+ data: data2
1260
+ });
1093
1261
  },
1094
- text(text) {
1095
- const string = text.map((content) => unwrap2(content)).join(" ");
1262
+ text({ ctx, data: data2 }, text) {
1263
+ const string = text.map((content) => unwrap2(content, data2)).join(" ");
1096
1264
  if (DEV && string.length === 0) {
1097
1265
  throw new Error(`Action Text was called with empty string or array`);
1098
1266
  }
1099
- renderer.text(string, forward, goingBack);
1267
+ ctx.text(string, () => forward(ctx));
1100
1268
  },
1101
- exit() {
1102
- if (restoring)
1269
+ exit({ ctx, data: data2 }) {
1270
+ if (ctx.meta.restoring)
1103
1271
  return;
1272
+ const stack = useStack(ctx);
1104
1273
  const path = stack.value[0];
1105
1274
  const last = path.at(-1);
1106
1275
  const ignore = [];
@@ -1114,7 +1283,10 @@ var Novely = (() => {
1114
1283
  if (isExitImpossible(path)) {
1115
1284
  const referred = refer(path);
1116
1285
  if (isAction(referred) && isSkippedDuringRestore(referred[0])) {
1117
- match("end", []);
1286
+ match("end", [], {
1287
+ ctx,
1288
+ data: data2
1289
+ });
1118
1290
  }
1119
1291
  return;
1120
1292
  }
@@ -1139,36 +1311,39 @@ var Novely = (() => {
1139
1311
  }
1140
1312
  break;
1141
1313
  }
1142
- render();
1314
+ render(ctx);
1143
1315
  },
1144
- preload([source]) {
1145
- if (!goingBack && !restoring && !PRELOADED_ASSETS.has(source)) {
1316
+ preload({ ctx }, [source]) {
1317
+ if (!ctx.meta.goingBack && !ctx.meta.restoring && !PRELOADED_ASSETS.has(source)) {
1146
1318
  PRELOADED_ASSETS.add(renderer.misc.preloadImage(source));
1147
1319
  }
1148
- push();
1320
+ push(ctx);
1149
1321
  },
1150
- block([scene]) {
1322
+ block({ ctx }, [scene]) {
1151
1323
  if (DEV && !story[scene]) {
1152
1324
  throw new Error(`Attempt to call Block action with unknown scene "${scene}"`);
1153
1325
  }
1154
1326
  if (DEV && story[scene].length === 0) {
1155
1327
  throw new Error(`Attempt to call Block action with empty scene "${scene}"`);
1156
1328
  }
1157
- if (!restoring) {
1329
+ if (!ctx.meta.restoring) {
1330
+ const stack = useStack(ctx);
1158
1331
  stack.value[0].push(["block", scene], [null, 0]);
1159
- render();
1332
+ render(ctx);
1160
1333
  }
1161
1334
  }
1162
1335
  });
1163
- const enmemory = () => {
1164
- if (restoring)
1336
+ const enmemory = (ctx) => {
1337
+ if (ctx.meta.restoring)
1165
1338
  return;
1339
+ const stack = useStack(ctx);
1166
1340
  const current = klona(stack.value);
1167
1341
  current[2][1] = "auto";
1168
1342
  stack.push(current);
1169
1343
  save(true, "auto");
1170
1344
  };
1171
- const next = () => {
1345
+ const next = (ctx) => {
1346
+ const stack = useStack(ctx);
1172
1347
  const path = stack.value[0];
1173
1348
  const last = path.at(-1);
1174
1349
  if (last && (isNull(last[0]) || last[0] === "jump") && isNumber(last[1])) {
@@ -1177,33 +1352,42 @@ var Novely = (() => {
1177
1352
  path.push([null, 0]);
1178
1353
  }
1179
1354
  };
1180
- const render = () => {
1181
- const referred = refer();
1355
+ const render = (ctx) => {
1356
+ const stack = useStack(ctx);
1357
+ const referred = refer(stack.value[0]);
1182
1358
  if (isAction(referred)) {
1183
1359
  const [action2, ...props] = referred;
1184
- match(action2, props);
1360
+ match(action2, props, {
1361
+ ctx,
1362
+ data: stack.value[1]
1363
+ });
1185
1364
  } else {
1186
- match("exit", []);
1365
+ match("exit", [], {
1366
+ ctx,
1367
+ data: stack.value[1]
1368
+ });
1187
1369
  }
1188
1370
  };
1189
- const push = () => {
1190
- if (!restoring)
1191
- next(), render();
1371
+ const push = (ctx) => {
1372
+ if (!ctx.meta.restoring)
1373
+ next(ctx), render(ctx);
1192
1374
  };
1193
- const forward = () => {
1194
- enmemory();
1195
- push();
1196
- 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);
1197
1381
  };
1198
1382
  const interactivity = (value = false) => {
1199
1383
  interacted = value ? interacted + 1 : 0;
1200
1384
  };
1201
- const unwrap2 = (content, global = false) => {
1385
+ const unwrap2 = (content, values) => {
1202
1386
  const {
1203
1387
  data: data2,
1204
1388
  meta: [lang]
1205
1389
  } = $.get();
1206
- const obj = global ? data2 : state();
1390
+ const obj = values ? values : data2;
1207
1391
  const cnt = isFunction(content) ? content() : typeof content === "string" ? content : content[lang];
1208
1392
  const str2 = isFunction(cnt) ? cnt() : cnt;
1209
1393
  const trans = translation[lang];
@@ -1243,7 +1427,7 @@ var Novely = (() => {
1243
1427
  * Unwraps translatable content to a string value
1244
1428
  */
1245
1429
  unwrap(content) {
1246
- return unwrap2(content, true);
1430
+ return unwrap2(content, $.get().data);
1247
1431
  }
1248
1432
  };
1249
1433
  };