@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.
package/dist/index.js CHANGED
@@ -2,13 +2,29 @@
2
2
  var SKIPPED_DURING_RESTORE = /* @__PURE__ */ new Set(["dialog", "choice", "input", "vibrate", "text"]);
3
3
  var BLOCK_EXIT_STATEMENTS = /* @__PURE__ */ new Set(["choice:exit", "condition:exit", "block:exit"]);
4
4
  var BLOCK_STATEMENTS = /* @__PURE__ */ new Set(["choice", "condition", "block"]);
5
+ var AUDIO_ACTIONS = /* @__PURE__ */ new Set([
6
+ "playMusic",
7
+ "stopMusic",
8
+ "playSound",
9
+ "stopSound",
10
+ "voice",
11
+ "stopVoice"
12
+ ]);
5
13
  var EMPTY_SET = /* @__PURE__ */ new Set();
6
14
  var DEFAULT_TYPEWRITER_SPEED = "Medium";
15
+ var MAIN_CONTEXT_KEY = "$MAIN";
16
+
17
+ // src/shared.ts
18
+ var STACK_MAP = /* @__PURE__ */ new Map();
7
19
 
8
20
  // src/utils.ts
9
- var matchAction = (values) => {
10
- return (action, props) => {
11
- return values[action](props);
21
+ var matchAction = (getContext, values) => {
22
+ return (action, props, { ctx, data }) => {
23
+ const context = typeof ctx === "string" ? getContext(ctx) : ctx;
24
+ return values[action]({
25
+ ctx: context,
26
+ data
27
+ }, props);
12
28
  };
13
29
  };
14
30
  var isNumber = (val) => {
@@ -152,6 +168,189 @@ var getOppositeAction = (action) => {
152
168
  };
153
169
  return MAP[action];
154
170
  };
171
+ var getActionsFromPath = (story, path, raw = false) => {
172
+ let current = story;
173
+ let precurrent;
174
+ let ignoreNested = false;
175
+ let index = 0;
176
+ const max = path.reduce((acc, [type, val]) => {
177
+ if (isNull(type) && isNumber(val)) {
178
+ return acc + 1;
179
+ }
180
+ return acc;
181
+ }, 0);
182
+ const queue = [];
183
+ const blocks = [];
184
+ for (const [type, val] of path) {
185
+ if (type === "jump") {
186
+ precurrent = story;
187
+ current = current[val];
188
+ } else if (type === null) {
189
+ precurrent = current;
190
+ if (isNumber(val)) {
191
+ index++;
192
+ let startIndex = 0;
193
+ if (ignoreNested) {
194
+ const prev = findLastPathItemBeforeItemOfType(path.slice(0, index), "block");
195
+ if (prev) {
196
+ startIndex = prev[1];
197
+ ignoreNested = false;
198
+ }
199
+ }
200
+ for (let i = startIndex; i <= val; i++) {
201
+ const item = current[i];
202
+ if (!isAction(item))
203
+ continue;
204
+ const [action, ...meta] = item;
205
+ const push = () => {
206
+ queue.push([action, meta]);
207
+ };
208
+ if (raw) {
209
+ push();
210
+ continue;
211
+ }
212
+ if (isSkippedDuringRestore(action) || isUserRequiredAction(action, meta)) {
213
+ if (index === max && i === val) {
214
+ push();
215
+ }
216
+ continue;
217
+ } else {
218
+ push();
219
+ }
220
+ }
221
+ }
222
+ current = current[val];
223
+ } else if (type === "choice") {
224
+ blocks.push(precurrent);
225
+ current = current[val + 1][1];
226
+ } else if (type === "condition") {
227
+ blocks.push(precurrent);
228
+ current = current[2][val];
229
+ } else if (type === "block") {
230
+ blocks.push(precurrent);
231
+ current = story[val];
232
+ } else if (type === "block:exit" || type === "choice:exit" || type === "condition:exit") {
233
+ current = blocks.pop();
234
+ ignoreNested = true;
235
+ }
236
+ }
237
+ return queue;
238
+ };
239
+ var createQueueProcessor = (queue) => {
240
+ const processedQueue = [];
241
+ const keep = /* @__PURE__ */ new Set();
242
+ const characters = /* @__PURE__ */ new Set();
243
+ const audio = {
244
+ music: /* @__PURE__ */ new Set(),
245
+ sound: /* @__PURE__ */ new Set()
246
+ };
247
+ const next = (i) => queue.slice(i + 1);
248
+ for (const [i, [action, meta]] of queue.entries()) {
249
+ keep.add(action);
250
+ if (action === "function" || action === "custom") {
251
+ if (action === "custom" && meta[0].callOnlyLatest) {
252
+ const notLatest = next(i).some(([, _meta]) => {
253
+ if (!_meta || !meta)
254
+ return false;
255
+ const c0 = _meta[0];
256
+ const c1 = meta[0];
257
+ const isIdenticalID = c0.id && c1.id && c0.id === c1.id;
258
+ const isIdenticalByReference = c0 === c1;
259
+ return isIdenticalID || isIdenticalByReference || str(c0) === str(c1);
260
+ });
261
+ if (notLatest)
262
+ continue;
263
+ }
264
+ processedQueue.push([action, meta]);
265
+ } else if (action === "showCharacter" || action === "playSound" || action === "playMusic" || action === "voice") {
266
+ const closing = getOppositeAction(action);
267
+ const skip = next(i).some(([_action, _meta]) => {
268
+ if (!_meta || !meta)
269
+ return false;
270
+ if (_meta[0] !== meta[0])
271
+ return false;
272
+ return _action === closing || _action === action;
273
+ });
274
+ if (skip)
275
+ continue;
276
+ if (action === "showCharacter") {
277
+ characters.add(meta[0]);
278
+ } else if (action === "playMusic") {
279
+ audio.music.add(meta[0]);
280
+ } else if (action === "playSound") {
281
+ audio.sound.add(meta[0]);
282
+ }
283
+ processedQueue.push([action, meta]);
284
+ } else if (action === "showBackground" || action === "animateCharacter" || action === "preload") {
285
+ const skip = next(i).some(([_action], i2, array) => action === _action);
286
+ if (skip)
287
+ continue;
288
+ processedQueue.push([action, meta]);
289
+ } else {
290
+ processedQueue.push([action, meta]);
291
+ }
292
+ }
293
+ const run = async (match) => {
294
+ for await (const [action, meta] of processedQueue) {
295
+ const result = match(action, meta);
296
+ if (isPromise(result)) {
297
+ await result;
298
+ }
299
+ }
300
+ processedQueue.length = 0;
301
+ };
302
+ const getKeep = () => {
303
+ return {
304
+ keep,
305
+ characters,
306
+ audio
307
+ };
308
+ };
309
+ return {
310
+ run,
311
+ getKeep
312
+ };
313
+ };
314
+ var getStack = (ctx) => {
315
+ const { id } = ctx;
316
+ const cached = STACK_MAP.get(id);
317
+ if (cached)
318
+ return cached;
319
+ const stack = [];
320
+ STACK_MAP.set(id, stack);
321
+ return stack;
322
+ };
323
+ var createUseStackFunction = (renderer) => {
324
+ const useStack = (context) => {
325
+ const ctx = typeof context === "string" ? renderer.getContext(context) : context;
326
+ const stack = getStack(ctx);
327
+ return {
328
+ get previous() {
329
+ return stack.previous;
330
+ },
331
+ get value() {
332
+ return stack.at(-1);
333
+ },
334
+ set value(value) {
335
+ stack[stack.length - 1] = value;
336
+ },
337
+ back() {
338
+ if (stack.length > 1) {
339
+ stack.previous = stack.pop();
340
+ ctx.meta.goingBack = true;
341
+ }
342
+ },
343
+ push(value) {
344
+ stack.push(value);
345
+ },
346
+ clear() {
347
+ stack.length = 0;
348
+ stack.length = 1;
349
+ }
350
+ };
351
+ };
352
+ return useStack;
353
+ };
155
354
 
156
355
  // src/global.ts
157
356
  var PRELOADED_ASSETS = /* @__PURE__ */ new Set();
@@ -171,10 +370,13 @@ var store = (current, subscribers = /* @__PURE__ */ new Set()) => {
171
370
  const update = (fn) => {
172
371
  push(current = fn(current));
173
372
  };
373
+ const set = (val) => {
374
+ update(() => val);
375
+ };
174
376
  const get = () => {
175
377
  return current;
176
378
  };
177
- return { subscribe, update, get };
379
+ return { subscribe, update, set, get };
178
380
  };
179
381
 
180
382
  // ../deepmerge/dist/index.js
@@ -422,6 +624,7 @@ var novely = ({
422
624
  }
423
625
  });
424
626
  function state(value) {
627
+ const stack = useStack(MAIN_CONTEXT_KEY);
425
628
  if (!value)
426
629
  return stack.value[1];
427
630
  const prev = stack.value[1];
@@ -438,26 +641,6 @@ var novely = ({
438
641
  [intime(Date.now()), "auto"]
439
642
  ];
440
643
  };
441
- const createStack = (current, stack2 = [current]) => {
442
- return {
443
- get value() {
444
- return stack2.at(-1);
445
- },
446
- set value(value) {
447
- stack2[stack2.length - 1] = value;
448
- },
449
- back() {
450
- if (stack2.length > 1)
451
- stack2.pop(), goingBack = true;
452
- },
453
- push(value) {
454
- stack2.push(value);
455
- },
456
- clear() {
457
- stack2 = [getDefaultSave(klona(defaultState))];
458
- }
459
- };
460
- };
461
644
  const getLanguageWithoutParameters = () => {
462
645
  return getLanguage2(languages, getLanguage);
463
646
  };
@@ -499,11 +682,10 @@ var novely = ({
499
682
  }
500
683
  $$.update((prev) => (prev.dataLoaded = true, prev));
501
684
  dataLoaded.resolve();
502
- $.update(() => stored);
685
+ $.set(stored);
503
686
  };
504
687
  storageDelay.then(getStoredData);
505
688
  const initial = getDefaultSave(klona(defaultState));
506
- const stack = createStack(initial);
507
689
  addEventListener("visibilitychange", () => {
508
690
  if (document.visibilityState === "hidden") {
509
691
  throttledEmergencyOnStorageDataChange();
@@ -515,6 +697,7 @@ var novely = ({
515
697
  return;
516
698
  if (!autosaves && type === "auto")
517
699
  return;
700
+ const stack = useStack(MAIN_CONTEXT_KEY);
518
701
  const current = klona(stack.value);
519
702
  $.update((prev) => {
520
703
  const isLatest = findLastIndex(prev.saves, (value) => times.has(value[2][0])) === prev.saves.length - 1;
@@ -544,139 +727,65 @@ var novely = ({
544
727
  }
545
728
  restore(save2);
546
729
  };
547
- const set = (save2) => {
730
+ const set = (save2, ctx) => {
731
+ const stack = useStack(ctx || renderer.getContext(MAIN_CONTEXT_KEY));
548
732
  stack.value = save2;
549
733
  return restore(save2);
550
734
  };
551
- let restoring = false;
552
- let goingBack = false;
553
735
  let interacted = 0;
554
736
  const restore = async (save2) => {
555
737
  if (!$$.get().dataLoaded)
556
738
  return;
557
739
  let latest = save2 || $.get().saves.at(-1);
558
740
  if (!latest) {
559
- $.update(() => ({
741
+ $.set({
560
742
  saves: [initial],
561
743
  data: klona(defaultData),
562
744
  meta: [getLanguageWithoutParameters(), DEFAULT_TYPEWRITER_SPEED, 1, 1, 1]
563
- }));
745
+ });
564
746
  latest = klona(initial);
565
747
  }
566
- restoring = true, stack.value = latest;
567
- const path = stack.value[0];
568
- let current = story;
569
- let precurrent;
570
- let ignoreNested = false;
571
- let index = 0;
572
- const max = stack.value[0].reduce((acc, [type, val]) => {
573
- if (isNull(type) && isNumber(val)) {
574
- return acc + 1;
575
- }
576
- return acc;
577
- }, 0);
578
- const queue = [];
579
- const keep = /* @__PURE__ */ new Set();
580
- const characters2 = /* @__PURE__ */ new Set();
581
- const blocks = [];
582
- for (const [type, val] of path) {
583
- if (type === "jump") {
584
- precurrent = story;
585
- current = current[val];
586
- } else if (type === null) {
587
- precurrent = current;
588
- if (isNumber(val)) {
589
- index++;
590
- let startIndex = 0;
591
- if (ignoreNested) {
592
- const prev = findLastPathItemBeforeItemOfType(path.slice(0, index), "block");
593
- if (prev) {
594
- startIndex = prev[1];
595
- ignoreNested = false;
596
- }
597
- }
598
- for (let i = startIndex; i <= val; i++) {
599
- const item = current[i];
600
- if (!isAction(item))
601
- continue;
602
- const [action2, ...meta] = item;
603
- const push2 = () => {
604
- keep.add(action2);
605
- queue.push([action2, meta]);
606
- };
607
- if (action2 === "showCharacter")
608
- characters2.add(meta[0]);
609
- if (isSkippedDuringRestore(action2) || isUserRequiredAction(action2, meta)) {
610
- if (index === max && i === val) {
611
- push2();
612
- } else {
613
- continue;
614
- }
615
- }
616
- push2();
748
+ const context = renderer.getContext(MAIN_CONTEXT_KEY);
749
+ const stack = useStack(context);
750
+ context.meta.restoring = true;
751
+ const previous = stack.previous;
752
+ const [path] = stack.value = latest;
753
+ renderer.ui.showScreen("game");
754
+ const queue = getActionsFromPath(story, path);
755
+ const processor = createQueueProcessor(queue);
756
+ const { keep, characters: characters2, audio } = processor.getKeep();
757
+ if (previous) {
758
+ const prevQueue = getActionsFromPath(story, previous[0], true);
759
+ const currQueue = getActionsFromPath(story, path, true);
760
+ for (let i = prevQueue.length - 1; i > currQueue.length; i--) {
761
+ const element = prevQueue[i];
762
+ if (isAction(element)) {
763
+ const [action2, props] = element;
764
+ if (action2 === "custom") {
765
+ context.clearCustom(props[0]);
617
766
  }
618
767
  }
619
- current = current[val];
620
- } else if (type === "choice") {
621
- blocks.push(precurrent);
622
- current = current[val + 1][1];
623
- } else if (type === "condition") {
624
- blocks.push(precurrent);
625
- current = current[2][val];
626
- } else if (type === "block") {
627
- blocks.push(precurrent);
628
- current = story[val];
629
- } else if (type === "block:exit" || type === "choice:exit" || type === "condition:exit") {
630
- current = blocks.pop();
631
- ignoreNested = true;
632
768
  }
633
769
  }
634
- renderer.ui.showScreen("game");
635
- match("clear", [keep, characters2]);
636
- const next2 = (i) => queue.slice(i + 1);
637
- for await (const [i, [action2, meta]] of queue.entries()) {
638
- if (action2 === "function" || action2 === "custom") {
639
- if (action2 === "custom" && meta[0].callOnlyLatest) {
640
- const notLatest = next2(i).some(([, _meta]) => {
641
- if (!_meta || !meta)
642
- return false;
643
- const c0 = _meta[0];
644
- const c1 = meta[0];
645
- const isIdenticalID = c0.id && c1.id && c0.id === c1.id;
646
- const isIdenticalByReference = c0 === c1;
647
- return isIdenticalID || isIdenticalByReference || str(c0) === str(c1);
648
- });
649
- if (notLatest)
650
- continue;
651
- }
652
- const result = match(action2, meta);
653
- if (isPromise(result)) {
654
- await result;
655
- }
656
- } else if (action2 === "showCharacter" || action2 === "playSound" || action2 === "playMusic" || action2 === "voice") {
657
- const closing = getOppositeAction(action2);
658
- const skip = next2(i).some(([_action, _meta]) => {
659
- if (!_meta || !meta)
660
- return false;
661
- if (_meta[0] !== meta[0])
662
- return false;
663
- return _action === closing || _action === action2;
664
- });
665
- if (skip)
666
- continue;
667
- match(action2, meta);
668
- } else if (action2 === "showBackground" || action2 === "animateCharacter" || action2 === "preload") {
669
- const notLatest = next2(i).some(([_action]) => action2 === _action);
670
- if (notLatest)
671
- continue;
672
- match(action2, meta);
673
- } else {
674
- match(action2, meta);
675
- }
676
- }
677
- restoring = goingBack = false, render();
770
+ match("clear", [keep, characters2, audio], {
771
+ ctx: context,
772
+ data: latest[1]
773
+ });
774
+ await processor.run((action2, props) => {
775
+ if (!latest)
776
+ return;
777
+ return match(action2, props, {
778
+ ctx: context,
779
+ data: latest[1]
780
+ });
781
+ });
782
+ context.meta.restoring = context.meta.restoring = false;
783
+ render(context);
678
784
  };
679
- const refer = (path = stack.value[0]) => {
785
+ const refer = (path) => {
786
+ if (!path) {
787
+ path = useStack(MAIN_CONTEXT_KEY).value[0];
788
+ }
680
789
  let current = story;
681
790
  let precurrent = story;
682
791
  const blocks = [];
@@ -707,10 +816,12 @@ var novely = ({
707
816
  renderer.ui.showExitPrompt();
708
817
  return;
709
818
  }
819
+ const ctx = renderer.getContext(MAIN_CONTEXT_KEY);
820
+ const stack = useStack(ctx);
710
821
  const current = stack.value;
711
822
  stack.clear();
712
823
  renderer.ui.showScreen("mainmenu");
713
- renderer.audio.destroy();
824
+ ctx.audio.destroy();
714
825
  const [time, type] = current[2];
715
826
  if (type === "auto" && interacted <= 1 && times.has(time)) {
716
827
  $.update((prev) => {
@@ -721,13 +832,38 @@ var novely = ({
721
832
  interactivity(false);
722
833
  times.clear();
723
834
  };
724
- const back = () => {
725
- return stack.back(), restore(stack.value);
835
+ const back = async () => {
836
+ const stack = useStack(MAIN_CONTEXT_KEY);
837
+ stack.back();
838
+ await restore(stack.value);
726
839
  };
727
840
  const t = (key, lang) => {
728
841
  return translation[lang].internal[key];
729
842
  };
843
+ const preview = async ([path, data2], name) => {
844
+ const queue = getActionsFromPath(story, path);
845
+ const ctx = renderer.getContext(name);
846
+ ctx.meta.restoring = true;
847
+ ctx.meta.preview = true;
848
+ const processor = createQueueProcessor(queue);
849
+ await processor.run((action2, props) => {
850
+ if (AUDIO_ACTIONS.has(action2))
851
+ return;
852
+ if (action2 === "vibrate")
853
+ return;
854
+ if (action2 === "end")
855
+ return;
856
+ return match(action2, props, {
857
+ ctx,
858
+ data: data2
859
+ });
860
+ });
861
+ };
862
+ const removeContext = (name) => {
863
+ STACK_MAP.delete(name);
864
+ };
730
865
  const renderer = createRenderer({
866
+ mainContextKey: MAIN_CONTEXT_KEY,
731
867
  characters,
732
868
  set,
733
869
  restore,
@@ -736,62 +872,67 @@ var novely = ({
736
872
  exit,
737
873
  back,
738
874
  t,
739
- stack,
875
+ preview,
876
+ removeContext,
740
877
  languages,
741
878
  $,
742
879
  $$
743
880
  });
881
+ const useStack = createUseStackFunction(renderer);
882
+ useStack(MAIN_CONTEXT_KEY).push(initial);
744
883
  renderer.ui.start();
745
- const match = matchAction({
746
- wait([time]) {
747
- if (!restoring)
884
+ const match = matchAction(renderer.getContext, {
885
+ wait({ ctx }, [time]) {
886
+ if (!ctx.meta.restoring)
748
887
  setTimeout(push, isFunction(time) ? time() : time);
749
888
  },
750
- showBackground([background]) {
751
- renderer.background(background);
752
- push();
889
+ showBackground({ ctx }, [background]) {
890
+ ctx.background(background);
891
+ push(ctx);
753
892
  },
754
- playMusic([source]) {
755
- renderer.audio.music(source, "music", true).play();
756
- push();
893
+ playMusic({ ctx }, [source]) {
894
+ ctx.audio.music(source, "music", true).play();
895
+ push(ctx);
757
896
  },
758
- stopMusic([source]) {
759
- renderer.audio.music(source, "music").stop();
760
- push();
897
+ stopMusic({ ctx }, [source]) {
898
+ ctx.audio.music(source, "music").stop();
899
+ push(ctx);
761
900
  },
762
- playSound([source, loop]) {
763
- renderer.audio.music(source, "sound", loop || false).play();
764
- push();
901
+ playSound({ ctx }, [source, loop]) {
902
+ ctx.audio.music(source, "sound", loop || false).play();
903
+ push(ctx);
765
904
  },
766
- stopSound([source]) {
767
- renderer.audio.music(source, "sound").stop();
768
- push();
905
+ stopSound({ ctx }, [source]) {
906
+ ctx.audio.music(source, "sound").stop();
907
+ push(ctx);
769
908
  },
770
- voice([source]) {
771
- renderer.audio.voice(source);
772
- push();
909
+ voice({ ctx }, [source]) {
910
+ ctx.audio.voice(source);
911
+ push(ctx);
773
912
  },
774
- stopVoice() {
775
- renderer.audio.voiceStop();
776
- push();
913
+ stopVoice({ ctx }) {
914
+ ctx.audio.voiceStop();
915
+ push(ctx);
777
916
  },
778
- showCharacter([character, emotion, className, style]) {
917
+ showCharacter({ ctx }, [character, emotion, className, style]) {
779
918
  if (DEV && !characters[character].emotions[emotion]) {
780
919
  throw new Error(`Attempt to show character "${character}" with unknown emotion "${emotion}"`);
781
920
  }
782
- const handle = renderer.character(character);
783
- handle.append(className, style, restoring);
784
- handle.withEmotion(emotion)();
785
- push();
921
+ const handle = ctx.character(character);
922
+ handle.append(className, style, ctx.meta.restoring);
923
+ handle.emotion(emotion, true);
924
+ push(ctx);
786
925
  },
787
- hideCharacter([character, className, style, duration]) {
788
- renderer.character(character).remove(className, style, duration)(push, restoring);
926
+ hideCharacter({ ctx }, [character, className, style, duration]) {
927
+ ctx.character(character).remove(className, style, duration, ctx.meta.restoring).then(() => {
928
+ push(ctx);
929
+ });
789
930
  },
790
- dialog([character, content, emotion]) {
931
+ dialog({ ctx, data: data2 }, [character, content, emotion]) {
791
932
  const name = (() => {
792
933
  const c = character;
793
934
  const cs = characters;
794
- const lang = $.get().meta[0];
935
+ const [lang] = $.get().meta;
795
936
  if (c && c in cs) {
796
937
  const block = cs[c].name;
797
938
  if (typeof block === "string") {
@@ -803,16 +944,17 @@ var novely = ({
803
944
  }
804
945
  return c || "";
805
946
  })();
806
- const run = renderer.dialog(unwrap2(content), unwrap2(name), character, emotion);
807
- run(forward, goingBack);
947
+ const run = ctx.dialog(unwrap2(content, data2), unwrap2(name, data2), character, emotion);
948
+ run(() => forward(ctx));
808
949
  },
809
- function([fn]) {
810
- const result = fn(restoring, goingBack);
811
- if (!restoring)
812
- result ? result.then(push) : push();
950
+ function({ ctx }, [fn]) {
951
+ const result = fn(ctx.meta.restoring, ctx.meta.goingBack, ctx.meta.preview);
952
+ if (!ctx.meta.restoring) {
953
+ result ? result.then(() => push(ctx)) : push(ctx);
954
+ }
813
955
  return result;
814
956
  },
815
- choice([question, ...choices]) {
957
+ choice({ ctx, data: data2 }, [question, ...choices]) {
816
958
  const isWithoutQuestion = Array.isArray(question);
817
959
  if (isWithoutQuestion) {
818
960
  choices.unshift(question);
@@ -822,46 +964,57 @@ var novely = ({
822
964
  if (DEV && action2.length === 0 && (!visible || visible())) {
823
965
  console.warn(`Choice children should not be empty, either add content there or make item not selectable`);
824
966
  }
825
- return [unwrap2(content), action2, visible];
967
+ return [unwrap2(content, data2), action2, visible];
826
968
  });
827
969
  if (DEV && unwrapped.length === 0) {
828
970
  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]`);
829
971
  }
830
- const run = renderer.choices(unwrap2(question), unwrapped);
972
+ const run = ctx.choices(unwrap2(question, data2), unwrapped);
831
973
  run((selected) => {
832
- enmemory();
974
+ if (!ctx.meta.preview) {
975
+ enmemory(ctx);
976
+ }
977
+ const stack = useStack(ctx);
833
978
  const offset = isWithoutQuestion ? 0 : 1;
834
979
  if (DEV && !unwrapped[selected + offset]) {
835
980
  throw new Error("Choice children is empty, either add content there or make item not selectable");
836
981
  }
837
982
  stack.value[0].push(["choice", selected + offset], [null, 0]);
838
- render();
983
+ render(ctx);
839
984
  interactivity(true);
840
985
  });
841
986
  },
842
- jump([scene]) {
987
+ jump({ ctx, data: data2 }, [scene]) {
843
988
  if (DEV && !story[scene]) {
844
989
  throw new Error(`Attempt to jump to unknown scene "${scene}"`);
845
990
  }
846
991
  if (DEV && story[scene].length === 0) {
847
992
  throw new Error(`Attempt to jump to empty scene "${scene}"`);
848
993
  }
994
+ const stack = useStack(ctx);
849
995
  stack.value[0] = [
850
996
  ["jump", scene],
851
997
  [null, -1]
852
998
  ];
853
- match("clear", []);
999
+ match("clear", [], {
1000
+ ctx,
1001
+ data: data2
1002
+ });
854
1003
  },
855
- clear([keep, characters2]) {
856
- renderer.vibrate(0);
857
- renderer.clear(goingBack, keep || EMPTY_SET, characters2 || EMPTY_SET)(push);
858
- renderer.audio.clear();
1004
+ clear({ ctx }, [keep, characters2, audio]) {
1005
+ ctx.vibrate(0);
1006
+ const run = ctx.clear(
1007
+ keep || EMPTY_SET,
1008
+ characters2 || EMPTY_SET,
1009
+ audio || { music: EMPTY_SET, sounds: EMPTY_SET }
1010
+ );
1011
+ run(() => push(ctx));
859
1012
  },
860
- condition([condition, variants]) {
1013
+ condition({ ctx }, [condition, variants]) {
861
1014
  if (DEV && Object.values(variants).length === 0) {
862
1015
  throw new Error(`Attempt to use Condition action with empty variants object`);
863
1016
  }
864
- if (!restoring) {
1017
+ if (!ctx.meta.restoring) {
865
1018
  const val = String(condition());
866
1019
  if (DEV && !variants[val]) {
867
1020
  throw new Error(`Attempt to go to unknown variant "${val}"`);
@@ -869,46 +1022,56 @@ var novely = ({
869
1022
  if (DEV && variants[val].length === 0) {
870
1023
  throw new Error(`Attempt to go to empty variant "${val}"`);
871
1024
  }
1025
+ const stack = useStack(MAIN_CONTEXT_KEY);
872
1026
  stack.value[0].push(["condition", val], [null, 0]);
873
- render();
1027
+ render(ctx);
874
1028
  }
875
1029
  },
876
- end() {
877
- renderer.vibrate(0);
878
- renderer.clear(goingBack, EMPTY_SET, EMPTY_SET)(noop);
1030
+ end({ ctx }) {
1031
+ if (ctx.meta.preview)
1032
+ return;
1033
+ ctx.vibrate(0);
1034
+ ctx.clear(EMPTY_SET, EMPTY_SET, { music: EMPTY_SET, sounds: EMPTY_SET })(noop);
879
1035
  renderer.ui.showScreen("mainmenu");
880
1036
  interactivity(false);
881
1037
  times.clear();
882
1038
  },
883
- input([question, onInput, setup]) {
884
- renderer.input(unwrap2(question), onInput, setup)(forward);
1039
+ input({ ctx, data: data2 }, [question, onInput, setup]) {
1040
+ ctx.input(unwrap2(question, data2), onInput, setup)(() => {
1041
+ forward(ctx);
1042
+ });
885
1043
  },
886
- custom([handler]) {
887
- const result = renderer.custom(handler, goingBack, () => {
888
- if (!restoring && handler.requireUserAction)
889
- enmemory(), interactivity(true);
890
- if (!restoring)
891
- push();
1044
+ custom({ ctx }, [handler]) {
1045
+ const result = ctx.custom(handler, () => {
1046
+ if (ctx.meta.restoring)
1047
+ return;
1048
+ if (handler.requireUserAction && !ctx.meta.preview) {
1049
+ enmemory(ctx);
1050
+ interactivity(true);
1051
+ }
1052
+ push(ctx);
892
1053
  });
893
1054
  return result;
894
1055
  },
895
- vibrate(pattern) {
896
- renderer.vibrate(pattern);
897
- push();
1056
+ vibrate({ ctx }, pattern) {
1057
+ ctx.vibrate(pattern);
1058
+ push(ctx);
898
1059
  },
899
- next() {
900
- push();
1060
+ next({ ctx }) {
1061
+ push(ctx);
901
1062
  },
902
- animateCharacter([character, timeout, ...classes]) {
1063
+ animateCharacter({ ctx, data: data2 }, [character, timeout, ...classes]) {
903
1064
  if (DEV && classes.length === 0) {
904
1065
  throw new Error("Attempt to use AnimateCharacter without classes. Classes should be provided [https://novely.pages.dev/guide/actions/animateCharacter.html]");
905
1066
  }
906
1067
  if (DEV && (timeout <= 0 || !Number.isFinite(timeout) || Number.isNaN(timeout))) {
907
1068
  throw new Error("Attempt to use AnimateCharacter with unacceptable timeout. It should be finite and greater than zero");
908
1069
  }
1070
+ if (ctx.meta.preview)
1071
+ return;
909
1072
  const handler = (get) => {
910
- const { clear } = get("@@internal-animate-character", false);
911
- const char = renderer.store.characters[character];
1073
+ const { clear } = get(false);
1074
+ const char = ctx.getCharacter(character);
912
1075
  if (!char)
913
1076
  return;
914
1077
  const target = char.canvas;
@@ -924,18 +1087,23 @@ var novely = ({
924
1087
  clearTimeout(timeoutId);
925
1088
  });
926
1089
  };
927
- match("custom", [handler]);
1090
+ handler.key = "@@internal-animate-character";
1091
+ match("custom", [handler], {
1092
+ ctx,
1093
+ data: data2
1094
+ });
928
1095
  },
929
- text(text) {
930
- const string = text.map((content) => unwrap2(content)).join(" ");
1096
+ text({ ctx, data: data2 }, text) {
1097
+ const string = text.map((content) => unwrap2(content, data2)).join(" ");
931
1098
  if (DEV && string.length === 0) {
932
1099
  throw new Error(`Action Text was called with empty string or array`);
933
1100
  }
934
- renderer.text(string, forward, goingBack);
1101
+ ctx.text(string, () => forward(ctx));
935
1102
  },
936
- exit() {
937
- if (restoring)
1103
+ exit({ ctx, data: data2 }) {
1104
+ if (ctx.meta.restoring)
938
1105
  return;
1106
+ const stack = useStack(ctx);
939
1107
  const path = stack.value[0];
940
1108
  const last = path.at(-1);
941
1109
  const ignore = [];
@@ -949,7 +1117,10 @@ var novely = ({
949
1117
  if (isExitImpossible(path)) {
950
1118
  const referred = refer(path);
951
1119
  if (isAction(referred) && isSkippedDuringRestore(referred[0])) {
952
- match("end", []);
1120
+ match("end", [], {
1121
+ ctx,
1122
+ data: data2
1123
+ });
953
1124
  }
954
1125
  return;
955
1126
  }
@@ -974,36 +1145,39 @@ var novely = ({
974
1145
  }
975
1146
  break;
976
1147
  }
977
- render();
1148
+ render(ctx);
978
1149
  },
979
- preload([source]) {
980
- if (!goingBack && !restoring && !PRELOADED_ASSETS.has(source)) {
1150
+ preload({ ctx }, [source]) {
1151
+ if (!ctx.meta.goingBack && !ctx.meta.restoring && !PRELOADED_ASSETS.has(source)) {
981
1152
  PRELOADED_ASSETS.add(renderer.misc.preloadImage(source));
982
1153
  }
983
- push();
1154
+ push(ctx);
984
1155
  },
985
- block([scene]) {
1156
+ block({ ctx }, [scene]) {
986
1157
  if (DEV && !story[scene]) {
987
1158
  throw new Error(`Attempt to call Block action with unknown scene "${scene}"`);
988
1159
  }
989
1160
  if (DEV && story[scene].length === 0) {
990
1161
  throw new Error(`Attempt to call Block action with empty scene "${scene}"`);
991
1162
  }
992
- if (!restoring) {
1163
+ if (!ctx.meta.restoring) {
1164
+ const stack = useStack(ctx);
993
1165
  stack.value[0].push(["block", scene], [null, 0]);
994
- render();
1166
+ render(ctx);
995
1167
  }
996
1168
  }
997
1169
  });
998
- const enmemory = () => {
999
- if (restoring)
1170
+ const enmemory = (ctx) => {
1171
+ if (ctx.meta.restoring)
1000
1172
  return;
1173
+ const stack = useStack(ctx);
1001
1174
  const current = klona(stack.value);
1002
1175
  current[2][1] = "auto";
1003
1176
  stack.push(current);
1004
1177
  save(true, "auto");
1005
1178
  };
1006
- const next = () => {
1179
+ const next = (ctx) => {
1180
+ const stack = useStack(ctx);
1007
1181
  const path = stack.value[0];
1008
1182
  const last = path.at(-1);
1009
1183
  if (last && (isNull(last[0]) || last[0] === "jump") && isNumber(last[1])) {
@@ -1012,33 +1186,42 @@ var novely = ({
1012
1186
  path.push([null, 0]);
1013
1187
  }
1014
1188
  };
1015
- const render = () => {
1016
- const referred = refer();
1189
+ const render = (ctx) => {
1190
+ const stack = useStack(ctx);
1191
+ const referred = refer(stack.value[0]);
1017
1192
  if (isAction(referred)) {
1018
1193
  const [action2, ...props] = referred;
1019
- match(action2, props);
1194
+ match(action2, props, {
1195
+ ctx,
1196
+ data: stack.value[1]
1197
+ });
1020
1198
  } else {
1021
- match("exit", []);
1199
+ match("exit", [], {
1200
+ ctx,
1201
+ data: stack.value[1]
1202
+ });
1022
1203
  }
1023
1204
  };
1024
- const push = () => {
1025
- if (!restoring)
1026
- next(), render();
1205
+ const push = (ctx) => {
1206
+ if (!ctx.meta.restoring)
1207
+ next(ctx), render(ctx);
1027
1208
  };
1028
- const forward = () => {
1029
- enmemory();
1030
- push();
1031
- interactivity(true);
1209
+ const forward = (ctx) => {
1210
+ if (!ctx.meta.preview)
1211
+ enmemory(ctx);
1212
+ push(ctx);
1213
+ if (!ctx.meta.preview)
1214
+ interactivity(true);
1032
1215
  };
1033
1216
  const interactivity = (value = false) => {
1034
1217
  interacted = value ? interacted + 1 : 0;
1035
1218
  };
1036
- const unwrap2 = (content, global = false) => {
1219
+ const unwrap2 = (content, values) => {
1037
1220
  const {
1038
1221
  data: data2,
1039
1222
  meta: [lang]
1040
1223
  } = $.get();
1041
- const obj = global ? data2 : state();
1224
+ const obj = values ? values : data2;
1042
1225
  const cnt = isFunction(content) ? content() : typeof content === "string" ? content : content[lang];
1043
1226
  const str2 = isFunction(cnt) ? cnt() : cnt;
1044
1227
  const trans = translation[lang];
@@ -1078,7 +1261,7 @@ var novely = ({
1078
1261
  * Unwraps translatable content to a string value
1079
1262
  */
1080
1263
  unwrap(content) {
1081
- return unwrap2(content, true);
1264
+ return unwrap2(content, $.get().data);
1082
1265
  }
1083
1266
  };
1084
1267
  };