@novely/core 0.19.5 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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();
617
- }
618
- }
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
- }
633
- }
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;
634
753
  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;
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]);
766
+ }
655
767
  }
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
768
  }
676
769
  }
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,65 +944,81 @@ 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
+ ctx.dialog(
948
+ unwrap2(content, data2),
949
+ unwrap2(name, data2),
950
+ character,
951
+ emotion,
952
+ () => forward(ctx)
953
+ );
808
954
  },
809
- function([fn]) {
810
- const result = fn(restoring, goingBack);
811
- if (!restoring)
812
- result ? result.then(push) : push();
955
+ function({ ctx }, [fn]) {
956
+ const result = fn(ctx.meta.restoring, ctx.meta.goingBack, ctx.meta.preview);
957
+ if (!ctx.meta.restoring) {
958
+ result ? result.then(() => push(ctx)) : push(ctx);
959
+ }
813
960
  return result;
814
961
  },
815
- choice([question, ...choices]) {
962
+ choice({ ctx, data: data2 }, [question, ...choices]) {
816
963
  const isWithoutQuestion = Array.isArray(question);
817
964
  if (isWithoutQuestion) {
818
965
  choices.unshift(question);
819
966
  question = "";
820
967
  }
821
- const unwrapped = choices.map(([content, action2, visible]) => {
968
+ const unwrappedChoices = choices.map(([content, action2, visible]) => {
822
969
  if (DEV && action2.length === 0 && (!visible || visible())) {
823
970
  console.warn(`Choice children should not be empty, either add content there or make item not selectable`);
824
971
  }
825
- return [unwrap2(content), action2, visible];
972
+ return [unwrap2(content, data2), action2, visible];
826
973
  });
827
- if (DEV && unwrapped.length === 0) {
974
+ if (DEV && unwrappedChoices.length === 0) {
828
975
  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
976
  }
830
- const run = renderer.choices(unwrap2(question), unwrapped);
831
- run((selected) => {
832
- enmemory();
977
+ ctx.choices(unwrap2(question, data2), unwrappedChoices, (selected) => {
978
+ if (!ctx.meta.preview) {
979
+ enmemory(ctx);
980
+ }
981
+ const stack = useStack(ctx);
833
982
  const offset = isWithoutQuestion ? 0 : 1;
834
- if (DEV && !unwrapped[selected + offset]) {
983
+ if (DEV && !unwrappedChoices[selected + offset]) {
835
984
  throw new Error("Choice children is empty, either add content there or make item not selectable");
836
985
  }
837
986
  stack.value[0].push(["choice", selected + offset], [null, 0]);
838
- render();
987
+ render(ctx);
839
988
  interactivity(true);
840
989
  });
841
990
  },
842
- jump([scene]) {
991
+ jump({ ctx, data: data2 }, [scene]) {
843
992
  if (DEV && !story[scene]) {
844
993
  throw new Error(`Attempt to jump to unknown scene "${scene}"`);
845
994
  }
846
995
  if (DEV && story[scene].length === 0) {
847
996
  throw new Error(`Attempt to jump to empty scene "${scene}"`);
848
997
  }
998
+ const stack = useStack(ctx);
849
999
  stack.value[0] = [
850
1000
  ["jump", scene],
851
1001
  [null, -1]
852
1002
  ];
853
- match("clear", []);
1003
+ match("clear", [], {
1004
+ ctx,
1005
+ data: data2
1006
+ });
854
1007
  },
855
- clear([keep, characters2]) {
856
- renderer.vibrate(0);
857
- renderer.clear(goingBack, keep || EMPTY_SET, characters2 || EMPTY_SET)(push);
858
- renderer.audio.clear();
1008
+ clear({ ctx }, [keep, characters2, audio]) {
1009
+ ctx.vibrate(0);
1010
+ ctx.clear(
1011
+ keep || EMPTY_SET,
1012
+ characters2 || EMPTY_SET,
1013
+ audio || { music: EMPTY_SET, sounds: EMPTY_SET },
1014
+ () => push(ctx)
1015
+ );
859
1016
  },
860
- condition([condition, variants]) {
1017
+ condition({ ctx }, [condition, variants]) {
861
1018
  if (DEV && Object.values(variants).length === 0) {
862
1019
  throw new Error(`Attempt to use Condition action with empty variants object`);
863
1020
  }
864
- if (!restoring) {
1021
+ if (!ctx.meta.restoring) {
865
1022
  const val = String(condition());
866
1023
  if (DEV && !variants[val]) {
867
1024
  throw new Error(`Attempt to go to unknown variant "${val}"`);
@@ -869,46 +1026,59 @@ var novely = ({
869
1026
  if (DEV && variants[val].length === 0) {
870
1027
  throw new Error(`Attempt to go to empty variant "${val}"`);
871
1028
  }
1029
+ const stack = useStack(MAIN_CONTEXT_KEY);
872
1030
  stack.value[0].push(["condition", val], [null, 0]);
873
- render();
1031
+ render(ctx);
874
1032
  }
875
1033
  },
876
- end() {
877
- renderer.vibrate(0);
878
- renderer.clear(goingBack, EMPTY_SET, EMPTY_SET)(noop);
1034
+ end({ ctx }) {
1035
+ if (ctx.meta.preview)
1036
+ return;
1037
+ ctx.vibrate(0);
1038
+ ctx.clear(EMPTY_SET, EMPTY_SET, { music: EMPTY_SET, sounds: EMPTY_SET }, noop);
879
1039
  renderer.ui.showScreen("mainmenu");
880
1040
  interactivity(false);
881
1041
  times.clear();
882
1042
  },
883
- input([question, onInput, setup]) {
884
- renderer.input(unwrap2(question), onInput, setup)(forward);
1043
+ input({ ctx, data: data2 }, [question, onInput, setup]) {
1044
+ ctx.input(
1045
+ unwrap2(question, data2),
1046
+ onInput,
1047
+ setup || noop,
1048
+ () => forward(ctx)
1049
+ );
885
1050
  },
886
- custom([handler]) {
887
- const result = renderer.custom(handler, goingBack, () => {
888
- if (!restoring && handler.requireUserAction)
889
- enmemory(), interactivity(true);
890
- if (!restoring)
891
- push();
1051
+ custom({ ctx }, [handler]) {
1052
+ const result = ctx.custom(handler, () => {
1053
+ if (ctx.meta.restoring)
1054
+ return;
1055
+ if (handler.requireUserAction && !ctx.meta.preview) {
1056
+ enmemory(ctx);
1057
+ interactivity(true);
1058
+ }
1059
+ push(ctx);
892
1060
  });
893
1061
  return result;
894
1062
  },
895
- vibrate(pattern) {
896
- renderer.vibrate(pattern);
897
- push();
1063
+ vibrate({ ctx }, pattern) {
1064
+ ctx.vibrate(pattern);
1065
+ push(ctx);
898
1066
  },
899
- next() {
900
- push();
1067
+ next({ ctx }) {
1068
+ push(ctx);
901
1069
  },
902
- animateCharacter([character, timeout, ...classes]) {
1070
+ animateCharacter({ ctx, data: data2 }, [character, timeout, ...classes]) {
903
1071
  if (DEV && classes.length === 0) {
904
1072
  throw new Error("Attempt to use AnimateCharacter without classes. Classes should be provided [https://novely.pages.dev/guide/actions/animateCharacter.html]");
905
1073
  }
906
1074
  if (DEV && (timeout <= 0 || !Number.isFinite(timeout) || Number.isNaN(timeout))) {
907
1075
  throw new Error("Attempt to use AnimateCharacter with unacceptable timeout. It should be finite and greater than zero");
908
1076
  }
1077
+ if (ctx.meta.preview)
1078
+ return;
909
1079
  const handler = (get) => {
910
- const { clear } = get("@@internal-animate-character", false);
911
- const char = renderer.store.characters[character];
1080
+ const { clear } = get(false);
1081
+ const char = ctx.getCharacter(character);
912
1082
  if (!char)
913
1083
  return;
914
1084
  const target = char.canvas;
@@ -924,18 +1094,23 @@ var novely = ({
924
1094
  clearTimeout(timeoutId);
925
1095
  });
926
1096
  };
927
- match("custom", [handler]);
1097
+ handler.key = "@@internal-animate-character";
1098
+ match("custom", [handler], {
1099
+ ctx,
1100
+ data: data2
1101
+ });
928
1102
  },
929
- text(text) {
930
- const string = text.map((content) => unwrap2(content)).join(" ");
1103
+ text({ ctx, data: data2 }, text) {
1104
+ const string = text.map((content) => unwrap2(content, data2)).join(" ");
931
1105
  if (DEV && string.length === 0) {
932
1106
  throw new Error(`Action Text was called with empty string or array`);
933
1107
  }
934
- renderer.text(string, forward, goingBack);
1108
+ ctx.text(string, () => forward(ctx));
935
1109
  },
936
- exit() {
937
- if (restoring)
1110
+ exit({ ctx, data: data2 }) {
1111
+ if (ctx.meta.restoring)
938
1112
  return;
1113
+ const stack = useStack(ctx);
939
1114
  const path = stack.value[0];
940
1115
  const last = path.at(-1);
941
1116
  const ignore = [];
@@ -949,7 +1124,10 @@ var novely = ({
949
1124
  if (isExitImpossible(path)) {
950
1125
  const referred = refer(path);
951
1126
  if (isAction(referred) && isSkippedDuringRestore(referred[0])) {
952
- match("end", []);
1127
+ match("end", [], {
1128
+ ctx,
1129
+ data: data2
1130
+ });
953
1131
  }
954
1132
  return;
955
1133
  }
@@ -974,36 +1152,39 @@ var novely = ({
974
1152
  }
975
1153
  break;
976
1154
  }
977
- render();
1155
+ render(ctx);
978
1156
  },
979
- preload([source]) {
980
- if (!goingBack && !restoring && !PRELOADED_ASSETS.has(source)) {
1157
+ preload({ ctx }, [source]) {
1158
+ if (!ctx.meta.goingBack && !ctx.meta.restoring && !PRELOADED_ASSETS.has(source)) {
981
1159
  PRELOADED_ASSETS.add(renderer.misc.preloadImage(source));
982
1160
  }
983
- push();
1161
+ push(ctx);
984
1162
  },
985
- block([scene]) {
1163
+ block({ ctx }, [scene]) {
986
1164
  if (DEV && !story[scene]) {
987
1165
  throw new Error(`Attempt to call Block action with unknown scene "${scene}"`);
988
1166
  }
989
1167
  if (DEV && story[scene].length === 0) {
990
1168
  throw new Error(`Attempt to call Block action with empty scene "${scene}"`);
991
1169
  }
992
- if (!restoring) {
1170
+ if (!ctx.meta.restoring) {
1171
+ const stack = useStack(ctx);
993
1172
  stack.value[0].push(["block", scene], [null, 0]);
994
- render();
1173
+ render(ctx);
995
1174
  }
996
1175
  }
997
1176
  });
998
- const enmemory = () => {
999
- if (restoring)
1177
+ const enmemory = (ctx) => {
1178
+ if (ctx.meta.restoring)
1000
1179
  return;
1180
+ const stack = useStack(ctx);
1001
1181
  const current = klona(stack.value);
1002
1182
  current[2][1] = "auto";
1003
1183
  stack.push(current);
1004
1184
  save(true, "auto");
1005
1185
  };
1006
- const next = () => {
1186
+ const next = (ctx) => {
1187
+ const stack = useStack(ctx);
1007
1188
  const path = stack.value[0];
1008
1189
  const last = path.at(-1);
1009
1190
  if (last && (isNull(last[0]) || last[0] === "jump") && isNumber(last[1])) {
@@ -1012,33 +1193,42 @@ var novely = ({
1012
1193
  path.push([null, 0]);
1013
1194
  }
1014
1195
  };
1015
- const render = () => {
1016
- const referred = refer();
1196
+ const render = (ctx) => {
1197
+ const stack = useStack(ctx);
1198
+ const referred = refer(stack.value[0]);
1017
1199
  if (isAction(referred)) {
1018
1200
  const [action2, ...props] = referred;
1019
- match(action2, props);
1201
+ match(action2, props, {
1202
+ ctx,
1203
+ data: stack.value[1]
1204
+ });
1020
1205
  } else {
1021
- match("exit", []);
1206
+ match("exit", [], {
1207
+ ctx,
1208
+ data: stack.value[1]
1209
+ });
1022
1210
  }
1023
1211
  };
1024
- const push = () => {
1025
- if (!restoring)
1026
- next(), render();
1212
+ const push = (ctx) => {
1213
+ if (!ctx.meta.restoring)
1214
+ next(ctx), render(ctx);
1027
1215
  };
1028
- const forward = () => {
1029
- enmemory();
1030
- push();
1031
- interactivity(true);
1216
+ const forward = (ctx) => {
1217
+ if (!ctx.meta.preview)
1218
+ enmemory(ctx);
1219
+ push(ctx);
1220
+ if (!ctx.meta.preview)
1221
+ interactivity(true);
1032
1222
  };
1033
1223
  const interactivity = (value = false) => {
1034
1224
  interacted = value ? interacted + 1 : 0;
1035
1225
  };
1036
- const unwrap2 = (content, global = false) => {
1226
+ const unwrap2 = (content, values) => {
1037
1227
  const {
1038
1228
  data: data2,
1039
1229
  meta: [lang]
1040
1230
  } = $.get();
1041
- const obj = global ? data2 : state();
1231
+ const obj = values ? values : data2;
1042
1232
  const cnt = isFunction(content) ? content() : typeof content === "string" ? content : content[lang];
1043
1233
  const str2 = isFunction(cnt) ? cnt() : cnt;
1044
1234
  const trans = translation[lang];
@@ -1078,7 +1268,7 @@ var novely = ({
1078
1268
  * Unwraps translatable content to a string value
1079
1269
  */
1080
1270
  unwrap(content) {
1081
- return unwrap2(content, true);
1271
+ return unwrap2(content, $.get().data);
1082
1272
  }
1083
1273
  };
1084
1274
  };