@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.
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) => {
@@ -141,15 +157,199 @@ var isExitImpossible = (path) => {
141
157
  if (blockStatements.length > blockExitStatements.length) {
142
158
  return false;
143
159
  }
144
- for (let i = 0; i++; i < blockStatements.length) {
145
- const b = blockStatements[i][0];
146
- const be = blockExitStatements[i][0];
147
- if (!b || !be)
148
- return false;
149
- if (!be.startsWith(b))
150
- return false;
160
+ return !blockExitStatements.every(([name], i) => name && name.startsWith(blockStatements[i][0]));
161
+ };
162
+ var getOppositeAction = (action) => {
163
+ const MAP = {
164
+ "showCharacter": "hideCharacter",
165
+ "playSound": "stopSound",
166
+ "playMusic": "stopMusic",
167
+ "voice": "stopVoice"
168
+ };
169
+ return MAP[action];
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
+ }
151
292
  }
152
- return true;
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;
153
353
  };
154
354
 
155
355
  // src/global.ts
@@ -170,10 +370,13 @@ var store = (current, subscribers = /* @__PURE__ */ new Set()) => {
170
370
  const update = (fn) => {
171
371
  push(current = fn(current));
172
372
  };
373
+ const set = (val) => {
374
+ update(() => val);
375
+ };
173
376
  const get = () => {
174
377
  return current;
175
378
  };
176
- return { subscribe, update, get };
379
+ return { subscribe, update, set, get };
177
380
  };
178
381
 
179
382
  // ../deepmerge/dist/index.js
@@ -421,6 +624,7 @@ var novely = ({
421
624
  }
422
625
  });
423
626
  function state(value) {
627
+ const stack = useStack(MAIN_CONTEXT_KEY);
424
628
  if (!value)
425
629
  return stack.value[1];
426
630
  const prev = stack.value[1];
@@ -437,26 +641,6 @@ var novely = ({
437
641
  [intime(Date.now()), "auto"]
438
642
  ];
439
643
  };
440
- const createStack = (current, stack2 = [current]) => {
441
- return {
442
- get value() {
443
- return stack2.at(-1);
444
- },
445
- set value(value) {
446
- stack2[stack2.length - 1] = value;
447
- },
448
- back() {
449
- if (stack2.length > 1)
450
- stack2.pop(), goingBack = true;
451
- },
452
- push(value) {
453
- stack2.push(value);
454
- },
455
- clear() {
456
- stack2 = [getDefaultSave(klona(defaultState))];
457
- }
458
- };
459
- };
460
644
  const getLanguageWithoutParameters = () => {
461
645
  return getLanguage2(languages, getLanguage);
462
646
  };
@@ -498,11 +682,10 @@ var novely = ({
498
682
  }
499
683
  $$.update((prev) => (prev.dataLoaded = true, prev));
500
684
  dataLoaded.resolve();
501
- $.update(() => stored);
685
+ $.set(stored);
502
686
  };
503
687
  storageDelay.then(getStoredData);
504
688
  const initial = getDefaultSave(klona(defaultState));
505
- const stack = createStack(initial);
506
689
  addEventListener("visibilitychange", () => {
507
690
  if (document.visibilityState === "hidden") {
508
691
  throttledEmergencyOnStorageDataChange();
@@ -514,6 +697,7 @@ var novely = ({
514
697
  return;
515
698
  if (!autosaves && type === "auto")
516
699
  return;
700
+ const stack = useStack(MAIN_CONTEXT_KEY);
517
701
  const current = klona(stack.value);
518
702
  $.update((prev) => {
519
703
  const isLatest = findLastIndex(prev.saves, (value) => times.has(value[2][0])) === prev.saves.length - 1;
@@ -543,139 +727,65 @@ var novely = ({
543
727
  }
544
728
  restore(save2);
545
729
  };
546
- const set = (save2) => {
730
+ const set = (save2, ctx) => {
731
+ const stack = useStack(ctx || renderer.getContext(MAIN_CONTEXT_KEY));
547
732
  stack.value = save2;
548
733
  return restore(save2);
549
734
  };
550
- let restoring = false;
551
- let goingBack = false;
552
735
  let interacted = 0;
553
736
  const restore = async (save2) => {
554
737
  if (!$$.get().dataLoaded)
555
738
  return;
556
739
  let latest = save2 || $.get().saves.at(-1);
557
740
  if (!latest) {
558
- $.update(() => ({
741
+ $.set({
559
742
  saves: [initial],
560
743
  data: klona(defaultData),
561
744
  meta: [getLanguageWithoutParameters(), DEFAULT_TYPEWRITER_SPEED, 1, 1, 1]
562
- }));
745
+ });
563
746
  latest = klona(initial);
564
747
  }
565
- restoring = true, stack.value = latest;
566
- const path = stack.value[0];
567
- let current = story;
568
- let precurrent;
569
- let ignoreNested = false;
570
- let index = 0;
571
- const max = stack.value[0].reduce((acc, [type, val]) => {
572
- if (isNull(type) && isNumber(val)) {
573
- return acc + 1;
574
- }
575
- return acc;
576
- }, 0);
577
- const queue = [];
578
- const keep = /* @__PURE__ */ new Set();
579
- const characters2 = /* @__PURE__ */ new Set();
580
- const blocks = [];
581
- for (const [type, val] of path) {
582
- if (type === "jump") {
583
- precurrent = story;
584
- current = current[val];
585
- } else if (type === null) {
586
- precurrent = current;
587
- if (isNumber(val)) {
588
- index++;
589
- let startIndex = 0;
590
- if (ignoreNested) {
591
- const prev = findLastPathItemBeforeItemOfType(path.slice(0, index), "block");
592
- if (prev) {
593
- startIndex = prev[1];
594
- ignoreNested = false;
595
- }
596
- }
597
- for (let i = startIndex; i <= val; i++) {
598
- const item = current[i];
599
- if (!isAction(item))
600
- continue;
601
- const [action2, ...meta] = item;
602
- const push2 = () => {
603
- keep.add(action2);
604
- queue.push([action2, meta]);
605
- };
606
- if (action2 === "showCharacter")
607
- characters2.add(meta[0]);
608
- if (isSkippedDuringRestore(action2) || isUserRequiredAction(action2, meta)) {
609
- if (index === max && i === val) {
610
- push2();
611
- } else {
612
- continue;
613
- }
614
- }
615
- push2();
616
- }
617
- }
618
- current = current[val];
619
- } else if (type === "choice") {
620
- blocks.push(precurrent);
621
- current = current[val + 1][1];
622
- } else if (type === "condition") {
623
- blocks.push(precurrent);
624
- current = current[2][val];
625
- } else if (type === "block") {
626
- blocks.push(precurrent);
627
- current = story[val];
628
- } else if (type === "block:exit" || type === "choice:exit" || type === "condition:exit") {
629
- current = blocks.pop();
630
- ignoreNested = true;
631
- }
632
- }
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;
633
753
  renderer.ui.showScreen("game");
634
- match("clear", [keep, characters2]);
635
- const next2 = (i) => queue.slice(i + 1);
636
- for await (const [i, [action2, meta]] of queue.entries()) {
637
- if (action2 === "function" || action2 === "custom") {
638
- if (action2 === "custom" && meta[0].callOnlyLatest) {
639
- const notLatest = next2(i).some(([, _meta]) => {
640
- if (!_meta || !meta)
641
- return false;
642
- const c0 = _meta[0];
643
- const c1 = meta[0];
644
- const isIdenticalID = c0.id && c1.id && c0.id === c1.id;
645
- const isIdenticalByReference = c0 === c1;
646
- return isIdenticalID || isIdenticalByReference || str(c0) === str(c1);
647
- });
648
- if (notLatest)
649
- continue;
650
- }
651
- const result = match(action2, meta);
652
- if (isPromise(result)) {
653
- 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
+ }
654
767
  }
655
- } else if (action2 === "showCharacter" || action2 === "playSound" || action2 === "playMusic") {
656
- const closing = action2 === "showCharacter" ? "hideCharacter" : action2 === "playSound" ? "stopSound" : "stopMusic";
657
- const skip = next2(i).some(([_action, _meta]) => {
658
- if (!_meta || !meta)
659
- return false;
660
- const hidden = _action === closing && _meta[0] === meta[0];
661
- const notLatest = _action === action2 && _meta[0] === meta[0];
662
- return hidden || notLatest;
663
- });
664
- if (skip)
665
- continue;
666
- match(action2, meta);
667
- } else if (action2 === "showBackground" || action2 === "animateCharacter" || action2 === "preload" || action2 === "voice") {
668
- const notLatest = next2(i).some(([_action]) => action2 === _action);
669
- if (notLatest)
670
- continue;
671
- match(action2, meta);
672
- } else {
673
- match(action2, meta);
674
768
  }
675
769
  }
676
- 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);
677
784
  };
678
- const refer = (path = stack.value[0]) => {
785
+ const refer = (path) => {
786
+ if (!path) {
787
+ path = useStack(MAIN_CONTEXT_KEY).value[0];
788
+ }
679
789
  let current = story;
680
790
  let precurrent = story;
681
791
  const blocks = [];
@@ -706,10 +816,12 @@ var novely = ({
706
816
  renderer.ui.showExitPrompt();
707
817
  return;
708
818
  }
819
+ const ctx = renderer.getContext(MAIN_CONTEXT_KEY);
820
+ const stack = useStack(ctx);
709
821
  const current = stack.value;
710
822
  stack.clear();
711
823
  renderer.ui.showScreen("mainmenu");
712
- renderer.audio.destroy();
824
+ ctx.audio.destroy();
713
825
  const [time, type] = current[2];
714
826
  if (type === "auto" && interacted <= 1 && times.has(time)) {
715
827
  $.update((prev) => {
@@ -720,13 +832,38 @@ var novely = ({
720
832
  interactivity(false);
721
833
  times.clear();
722
834
  };
723
- const back = () => {
724
- 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);
725
839
  };
726
840
  const t = (key, lang) => {
727
841
  return translation[lang].internal[key];
728
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
+ };
729
865
  const renderer = createRenderer({
866
+ mainContextKey: MAIN_CONTEXT_KEY,
730
867
  characters,
731
868
  set,
732
869
  restore,
@@ -735,62 +872,67 @@ var novely = ({
735
872
  exit,
736
873
  back,
737
874
  t,
738
- stack,
875
+ preview,
876
+ removeContext,
739
877
  languages,
740
878
  $,
741
879
  $$
742
880
  });
881
+ const useStack = createUseStackFunction(renderer);
882
+ useStack(MAIN_CONTEXT_KEY).push(initial);
743
883
  renderer.ui.start();
744
- const match = matchAction({
745
- wait([time]) {
746
- if (!restoring)
884
+ const match = matchAction(renderer.getContext, {
885
+ wait({ ctx }, [time]) {
886
+ if (!ctx.meta.restoring)
747
887
  setTimeout(push, isFunction(time) ? time() : time);
748
888
  },
749
- showBackground([background]) {
750
- renderer.background(background);
751
- push();
889
+ showBackground({ ctx }, [background]) {
890
+ ctx.background(background);
891
+ push(ctx);
752
892
  },
753
- playMusic([source]) {
754
- renderer.audio.music(source, "music", true).play();
755
- push();
893
+ playMusic({ ctx }, [source]) {
894
+ ctx.audio.music(source, "music", true).play();
895
+ push(ctx);
756
896
  },
757
- stopMusic([source]) {
758
- renderer.audio.music(source, "music").stop();
759
- push();
897
+ stopMusic({ ctx }, [source]) {
898
+ ctx.audio.music(source, "music").stop();
899
+ push(ctx);
760
900
  },
761
- playSound([source, loop]) {
762
- renderer.audio.music(source, "sound", loop || false).play();
763
- push();
901
+ playSound({ ctx }, [source, loop]) {
902
+ ctx.audio.music(source, "sound", loop || false).play();
903
+ push(ctx);
764
904
  },
765
- stopSound([source]) {
766
- renderer.audio.music(source, "sound").stop();
767
- push();
905
+ stopSound({ ctx }, [source]) {
906
+ ctx.audio.music(source, "sound").stop();
907
+ push(ctx);
768
908
  },
769
- voice([source]) {
770
- renderer.audio.voice(source);
771
- push();
909
+ voice({ ctx }, [source]) {
910
+ ctx.audio.voice(source);
911
+ push(ctx);
772
912
  },
773
- stopVoice() {
774
- renderer.audio.voiceStop();
775
- push();
913
+ stopVoice({ ctx }) {
914
+ ctx.audio.voiceStop();
915
+ push(ctx);
776
916
  },
777
- showCharacter([character, emotion, className, style]) {
917
+ showCharacter({ ctx }, [character, emotion, className, style]) {
778
918
  if (DEV && !characters[character].emotions[emotion]) {
779
919
  throw new Error(`Attempt to show character "${character}" with unknown emotion "${emotion}"`);
780
920
  }
781
- const handle = renderer.character(character);
782
- handle.append(className, style, restoring);
783
- handle.withEmotion(emotion)();
784
- push();
921
+ const handle = ctx.character(character);
922
+ handle.append(className, style, ctx.meta.restoring);
923
+ handle.emotion(emotion, true);
924
+ push(ctx);
785
925
  },
786
- hideCharacter([character, className, style, duration]) {
787
- 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
+ });
788
930
  },
789
- dialog([character, content, emotion]) {
931
+ dialog({ ctx, data: data2 }, [character, content, emotion]) {
790
932
  const name = (() => {
791
933
  const c = character;
792
934
  const cs = characters;
793
- const lang = $.get().meta[0];
935
+ const [lang] = $.get().meta;
794
936
  if (c && c in cs) {
795
937
  const block = cs[c].name;
796
938
  if (typeof block === "string") {
@@ -802,16 +944,17 @@ var novely = ({
802
944
  }
803
945
  return c || "";
804
946
  })();
805
- const run = renderer.dialog(unwrap2(content), unwrap2(name), character, emotion);
806
- run(forward, goingBack);
947
+ const run = ctx.dialog(unwrap2(content, data2), unwrap2(name, data2), character, emotion);
948
+ run(() => forward(ctx));
807
949
  },
808
- function([fn]) {
809
- const result = fn(restoring, goingBack);
810
- if (!restoring)
811
- 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
+ }
812
955
  return result;
813
956
  },
814
- choice([question, ...choices]) {
957
+ choice({ ctx, data: data2 }, [question, ...choices]) {
815
958
  const isWithoutQuestion = Array.isArray(question);
816
959
  if (isWithoutQuestion) {
817
960
  choices.unshift(question);
@@ -821,46 +964,57 @@ var novely = ({
821
964
  if (DEV && action2.length === 0 && (!visible || visible())) {
822
965
  console.warn(`Choice children should not be empty, either add content there or make item not selectable`);
823
966
  }
824
- return [unwrap2(content), action2, visible];
967
+ return [unwrap2(content, data2), action2, visible];
825
968
  });
826
969
  if (DEV && unwrapped.length === 0) {
827
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]`);
828
971
  }
829
- const run = renderer.choices(unwrap2(question), unwrapped);
972
+ const run = ctx.choices(unwrap2(question, data2), unwrapped);
830
973
  run((selected) => {
831
- enmemory();
974
+ if (!ctx.meta.preview) {
975
+ enmemory(ctx);
976
+ }
977
+ const stack = useStack(ctx);
832
978
  const offset = isWithoutQuestion ? 0 : 1;
833
979
  if (DEV && !unwrapped[selected + offset]) {
834
980
  throw new Error("Choice children is empty, either add content there or make item not selectable");
835
981
  }
836
982
  stack.value[0].push(["choice", selected + offset], [null, 0]);
837
- render();
983
+ render(ctx);
838
984
  interactivity(true);
839
985
  });
840
986
  },
841
- jump([scene]) {
987
+ jump({ ctx, data: data2 }, [scene]) {
842
988
  if (DEV && !story[scene]) {
843
989
  throw new Error(`Attempt to jump to unknown scene "${scene}"`);
844
990
  }
845
991
  if (DEV && story[scene].length === 0) {
846
992
  throw new Error(`Attempt to jump to empty scene "${scene}"`);
847
993
  }
994
+ const stack = useStack(ctx);
848
995
  stack.value[0] = [
849
996
  ["jump", scene],
850
997
  [null, -1]
851
998
  ];
852
- match("clear", []);
999
+ match("clear", [], {
1000
+ ctx,
1001
+ data: data2
1002
+ });
853
1003
  },
854
- clear([keep, characters2]) {
855
- renderer.vibrate(0);
856
- renderer.clear(goingBack, keep || EMPTY_SET, characters2 || EMPTY_SET)(push);
857
- 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));
858
1012
  },
859
- condition([condition, variants]) {
1013
+ condition({ ctx }, [condition, variants]) {
860
1014
  if (DEV && Object.values(variants).length === 0) {
861
1015
  throw new Error(`Attempt to use Condition action with empty variants object`);
862
1016
  }
863
- if (!restoring) {
1017
+ if (!ctx.meta.restoring) {
864
1018
  const val = String(condition());
865
1019
  if (DEV && !variants[val]) {
866
1020
  throw new Error(`Attempt to go to unknown variant "${val}"`);
@@ -868,46 +1022,56 @@ var novely = ({
868
1022
  if (DEV && variants[val].length === 0) {
869
1023
  throw new Error(`Attempt to go to empty variant "${val}"`);
870
1024
  }
1025
+ const stack = useStack(MAIN_CONTEXT_KEY);
871
1026
  stack.value[0].push(["condition", val], [null, 0]);
872
- render();
1027
+ render(ctx);
873
1028
  }
874
1029
  },
875
- end() {
876
- renderer.vibrate(0);
877
- 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);
878
1035
  renderer.ui.showScreen("mainmenu");
879
1036
  interactivity(false);
880
1037
  times.clear();
881
1038
  },
882
- input([question, onInput, setup]) {
883
- 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
+ });
884
1043
  },
885
- custom([handler]) {
886
- const result = renderer.custom(handler, goingBack, () => {
887
- if (!restoring && handler.requireUserAction)
888
- enmemory(), interactivity(true);
889
- if (!restoring)
890
- 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);
891
1053
  });
892
1054
  return result;
893
1055
  },
894
- vibrate(pattern) {
895
- renderer.vibrate(pattern);
896
- push();
1056
+ vibrate({ ctx }, pattern) {
1057
+ ctx.vibrate(pattern);
1058
+ push(ctx);
897
1059
  },
898
- next() {
899
- push();
1060
+ next({ ctx }) {
1061
+ push(ctx);
900
1062
  },
901
- animateCharacter([character, timeout, ...classes]) {
1063
+ animateCharacter({ ctx, data: data2 }, [character, timeout, ...classes]) {
902
1064
  if (DEV && classes.length === 0) {
903
1065
  throw new Error("Attempt to use AnimateCharacter without classes. Classes should be provided [https://novely.pages.dev/guide/actions/animateCharacter.html]");
904
1066
  }
905
1067
  if (DEV && (timeout <= 0 || !Number.isFinite(timeout) || Number.isNaN(timeout))) {
906
1068
  throw new Error("Attempt to use AnimateCharacter with unacceptable timeout. It should be finite and greater than zero");
907
1069
  }
1070
+ if (ctx.meta.preview)
1071
+ return;
908
1072
  const handler = (get) => {
909
- const { clear } = get("@@internal-animate-character", false);
910
- const char = renderer.store.characters[character];
1073
+ const { clear } = get(false);
1074
+ const char = ctx.getCharacter(character);
911
1075
  if (!char)
912
1076
  return;
913
1077
  const target = char.canvas;
@@ -923,18 +1087,23 @@ var novely = ({
923
1087
  clearTimeout(timeoutId);
924
1088
  });
925
1089
  };
926
- match("custom", [handler]);
1090
+ handler.key = "@@internal-animate-character";
1091
+ match("custom", [handler], {
1092
+ ctx,
1093
+ data: data2
1094
+ });
927
1095
  },
928
- text(text) {
929
- const string = text.map((content) => unwrap2(content)).join(" ");
1096
+ text({ ctx, data: data2 }, text) {
1097
+ const string = text.map((content) => unwrap2(content, data2)).join(" ");
930
1098
  if (DEV && string.length === 0) {
931
1099
  throw new Error(`Action Text was called with empty string or array`);
932
1100
  }
933
- renderer.text(string, forward, goingBack);
1101
+ ctx.text(string, () => forward(ctx));
934
1102
  },
935
- exit() {
936
- if (restoring)
1103
+ exit({ ctx, data: data2 }) {
1104
+ if (ctx.meta.restoring)
937
1105
  return;
1106
+ const stack = useStack(ctx);
938
1107
  const path = stack.value[0];
939
1108
  const last = path.at(-1);
940
1109
  const ignore = [];
@@ -948,7 +1117,10 @@ var novely = ({
948
1117
  if (isExitImpossible(path)) {
949
1118
  const referred = refer(path);
950
1119
  if (isAction(referred) && isSkippedDuringRestore(referred[0])) {
951
- match("end", []);
1120
+ match("end", [], {
1121
+ ctx,
1122
+ data: data2
1123
+ });
952
1124
  }
953
1125
  return;
954
1126
  }
@@ -973,36 +1145,39 @@ var novely = ({
973
1145
  }
974
1146
  break;
975
1147
  }
976
- render();
1148
+ render(ctx);
977
1149
  },
978
- preload([source]) {
979
- if (!goingBack && !restoring && !PRELOADED_ASSETS.has(source)) {
1150
+ preload({ ctx }, [source]) {
1151
+ if (!ctx.meta.goingBack && !ctx.meta.restoring && !PRELOADED_ASSETS.has(source)) {
980
1152
  PRELOADED_ASSETS.add(renderer.misc.preloadImage(source));
981
1153
  }
982
- push();
1154
+ push(ctx);
983
1155
  },
984
- block([scene]) {
1156
+ block({ ctx }, [scene]) {
985
1157
  if (DEV && !story[scene]) {
986
1158
  throw new Error(`Attempt to call Block action with unknown scene "${scene}"`);
987
1159
  }
988
1160
  if (DEV && story[scene].length === 0) {
989
1161
  throw new Error(`Attempt to call Block action with empty scene "${scene}"`);
990
1162
  }
991
- if (!restoring) {
1163
+ if (!ctx.meta.restoring) {
1164
+ const stack = useStack(ctx);
992
1165
  stack.value[0].push(["block", scene], [null, 0]);
993
- render();
1166
+ render(ctx);
994
1167
  }
995
1168
  }
996
1169
  });
997
- const enmemory = () => {
998
- if (restoring)
1170
+ const enmemory = (ctx) => {
1171
+ if (ctx.meta.restoring)
999
1172
  return;
1173
+ const stack = useStack(ctx);
1000
1174
  const current = klona(stack.value);
1001
1175
  current[2][1] = "auto";
1002
1176
  stack.push(current);
1003
1177
  save(true, "auto");
1004
1178
  };
1005
- const next = () => {
1179
+ const next = (ctx) => {
1180
+ const stack = useStack(ctx);
1006
1181
  const path = stack.value[0];
1007
1182
  const last = path.at(-1);
1008
1183
  if (last && (isNull(last[0]) || last[0] === "jump") && isNumber(last[1])) {
@@ -1011,33 +1186,42 @@ var novely = ({
1011
1186
  path.push([null, 0]);
1012
1187
  }
1013
1188
  };
1014
- const render = () => {
1015
- const referred = refer();
1189
+ const render = (ctx) => {
1190
+ const stack = useStack(ctx);
1191
+ const referred = refer(stack.value[0]);
1016
1192
  if (isAction(referred)) {
1017
1193
  const [action2, ...props] = referred;
1018
- match(action2, props);
1194
+ match(action2, props, {
1195
+ ctx,
1196
+ data: stack.value[1]
1197
+ });
1019
1198
  } else {
1020
- match("exit", []);
1199
+ match("exit", [], {
1200
+ ctx,
1201
+ data: stack.value[1]
1202
+ });
1021
1203
  }
1022
1204
  };
1023
- const push = () => {
1024
- if (!restoring)
1025
- next(), render();
1205
+ const push = (ctx) => {
1206
+ if (!ctx.meta.restoring)
1207
+ next(ctx), render(ctx);
1026
1208
  };
1027
- const forward = () => {
1028
- enmemory();
1029
- push();
1030
- 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);
1031
1215
  };
1032
1216
  const interactivity = (value = false) => {
1033
1217
  interacted = value ? interacted + 1 : 0;
1034
1218
  };
1035
- const unwrap2 = (content, global = false) => {
1219
+ const unwrap2 = (content, values) => {
1036
1220
  const {
1037
1221
  data: data2,
1038
1222
  meta: [lang]
1039
1223
  } = $.get();
1040
- const obj = global ? data2 : state();
1224
+ const obj = values ? values : data2;
1041
1225
  const cnt = isFunction(content) ? content() : typeof content === "string" ? content : content[lang];
1042
1226
  const str2 = isFunction(cnt) ? cnt() : cnt;
1043
1227
  const trans = translation[lang];
@@ -1077,7 +1261,7 @@ var novely = ({
1077
1261
  * Unwraps translatable content to a string value
1078
1262
  */
1079
1263
  unwrap(content) {
1080
- return unwrap2(content, true);
1264
+ return unwrap2(content, $.get().data);
1081
1265
  }
1082
1266
  };
1083
1267
  };