@novely/core 0.47.1 → 0.48.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,7 +2,7 @@
2
2
  import { dequal } from "dequal/lite";
3
3
  import { throttle } from "es-toolkit/function";
4
4
  import { merge as deepmerge } from "es-toolkit/object";
5
- import { DEV as DEV3 } from "esm-env";
5
+ import { DEV as DEV4 } from "esm-env";
6
6
 
7
7
  // ../../node_modules/.pnpm/klona@2.0.6/node_modules/klona/full/index.mjs
8
8
  function set(obj, key, val) {
@@ -148,7 +148,7 @@ var isAsset = (suspect) => {
148
148
  // src/utilities/match-action.ts
149
149
  var matchAction = (callbacks, values) => {
150
150
  const { getContext, onBeforeActionCall, push, forward } = callbacks;
151
- return (action, props, { ctx, data }) => {
151
+ const match = (action, props, { ctx, data }) => {
152
152
  const context = typeof ctx === "string" ? getContext(ctx) : ctx;
153
153
  onBeforeActionCall({
154
154
  action,
@@ -171,6 +171,10 @@ var matchAction = (callbacks, values) => {
171
171
  props
172
172
  );
173
173
  };
174
+ return {
175
+ match,
176
+ nativeActions: Object.keys(values)
177
+ };
174
178
  };
175
179
 
176
180
  // src/utilities/ungrupped.ts
@@ -490,7 +494,7 @@ var createQueueProcessor = (queue, options) => {
490
494
  const characters = /* @__PURE__ */ new Set();
491
495
  const audio2 = {
492
496
  music: /* @__PURE__ */ new Set(),
493
- sound: /* @__PURE__ */ new Set()
497
+ sounds: /* @__PURE__ */ new Set()
494
498
  };
495
499
  const next = (i) => queue.slice(i + 1);
496
500
  for (const [i, item] of queue.entries()) {
@@ -503,39 +507,50 @@ var createQueueProcessor = (queue, options) => {
503
507
  if (action === "custom") {
504
508
  const fn = params[0];
505
509
  if ("callOnlyLatest" in fn && fn.callOnlyLatest) {
506
- const notLatest = next(i).some(([, func]) => {
507
- if (!isFunction(func)) return false;
508
- const c0 = func;
509
- const c1 = fn;
510
- const isIdenticalID = Boolean(c0.id && c1.id && c0.id === c1.id);
511
- const isIdenticalByReference = c0 === c1;
512
- return isIdenticalID || isIdenticalByReference || String(c0) === String(c1);
510
+ const notLatest = next(i).some(([name, func]) => {
511
+ if (name !== "custom") return;
512
+ const isIdenticalId = Boolean(func.id && fn.id && func.id === fn.id);
513
+ const isIdenticalByReference = func === fn;
514
+ const isIdenticalByCode = String(func) === String(fn);
515
+ return isIdenticalId || isIdenticalByReference || isIdenticalByCode;
513
516
  });
514
517
  if (notLatest) continue;
515
518
  } else if ("skipOnRestore" in fn && fn.skipOnRestore) {
516
- if (fn.skipOnRestore(() => next(i))) {
519
+ if (fn.skipOnRestore(next(i))) {
517
520
  continue;
518
521
  }
519
522
  }
520
523
  }
521
524
  processedQueue.push(item);
522
- } else if (action === "showCharacter" || action === "playSound" || action === "playMusic" || action === "voice") {
525
+ } else if (action === "playSound") {
526
+ const closing = getOppositeAction(action);
527
+ const skip = next(i).some((item2) => {
528
+ if (isUserRequiredAction(item2) || isSkippedDuringRestore(item2[0])) {
529
+ return true;
530
+ }
531
+ const [_action, target] = item2;
532
+ if (target !== params[0]) {
533
+ return false;
534
+ }
535
+ return _action === closing || _action === action;
536
+ });
537
+ if (skip) continue;
538
+ audio2.sounds.add(unwrapAsset(params[0]));
539
+ processedQueue.push(item);
540
+ } else if (action === "showCharacter" || action === "playMusic" || action === "voice") {
523
541
  const closing = getOppositeAction(action);
524
542
  const skip = next(i).some(([_action, target]) => {
525
543
  if (target !== params[0] && action !== "voice") {
526
544
  return false;
527
545
  }
528
- const musicGonnaBePaused = action === "playMusic" && _action === "pauseMusic";
529
- const soundGonnaBePaused = action === "playSound" && _action === "pauseSound";
530
- return musicGonnaBePaused || soundGonnaBePaused || _action === closing || _action === action;
546
+ const musicWillBePaused = action === "playMusic" && _action === "pauseMusic";
547
+ return musicWillBePaused || _action === closing || _action === action;
531
548
  });
532
549
  if (skip) continue;
533
550
  if (action === "showCharacter") {
534
551
  characters.add(params[0]);
535
552
  } else if (action === "playMusic") {
536
553
  audio2.music.add(unwrapAsset(params[0]));
537
- } else if (action === "playSound") {
538
- audio2.sound.add(unwrapAsset(params[0]));
539
554
  }
540
555
  processedQueue.push(item);
541
556
  } else if (action === "showBackground" || action === "preload") {
@@ -550,7 +565,8 @@ var createQueueProcessor = (queue, options) => {
550
565
  const next2 = array.slice(j);
551
566
  const characterWillAnimate = next2.some(([__action, __character]) => action === __action);
552
567
  const hasBlockingActions = next2.some((item2) => options.skip.has(item2));
553
- return characterWillAnimate && hasBlockingActions;
568
+ const differentCharacterWillAnimate = !hasBlockingActions && next2.some(([__action, __character]) => __action === action && __character !== params[0]);
569
+ return characterWillAnimate && hasBlockingActions || differentCharacterWillAnimate;
554
570
  });
555
571
  if (skip) continue;
556
572
  processedQueue.push(item);
@@ -1014,6 +1030,70 @@ var replace = (input, data, pluralization, actions, pr) => {
1014
1030
  });
1015
1031
  };
1016
1032
 
1033
+ // src/utilities/actions.ts
1034
+ import { DEV as DEV3 } from "esm-env";
1035
+ var VIRTUAL_ACTIONS = ["say"];
1036
+ var buildActionObject = ({
1037
+ rendererActions,
1038
+ nativeActions,
1039
+ characters,
1040
+ preloadAssets,
1041
+ storageData
1042
+ }) => {
1043
+ const allActions = [...nativeActions, ...VIRTUAL_ACTIONS];
1044
+ const object = { ...rendererActions };
1045
+ for (let action of allActions) {
1046
+ object[action] = (...props) => {
1047
+ if (action === "say") {
1048
+ action = "dialog";
1049
+ const [character] = props;
1050
+ if (DEV3 && !characters[character]) {
1051
+ throw new Error(`Attempt to call Say action with unknown character "${character}"`);
1052
+ }
1053
+ } else if (action === "choice") {
1054
+ if (props.slice(1).every((choice) => !Array.isArray(choice))) {
1055
+ for (let i = 1; i < props.length; i++) {
1056
+ const choice = props[i];
1057
+ props[i] = [
1058
+ choice.title,
1059
+ flatActions(choice.children),
1060
+ choice.active,
1061
+ choice.visible,
1062
+ choice.onSelect,
1063
+ choice.image
1064
+ ];
1065
+ }
1066
+ } else {
1067
+ for (let i = 1; i < props.length; i++) {
1068
+ const choice = props[i];
1069
+ if (Array.isArray(choice)) {
1070
+ choice[1] = flatActions(choice[1]);
1071
+ }
1072
+ }
1073
+ }
1074
+ } else if (action === "condition") {
1075
+ const actions = props[1];
1076
+ for (const key in actions) {
1077
+ actions[key] = flatActions(actions[key]);
1078
+ }
1079
+ }
1080
+ if (preloadAssets === "blocking") {
1081
+ huntAssets({
1082
+ action,
1083
+ props,
1084
+ mode: preloadAssets,
1085
+ characters,
1086
+ lang: getLanguageFromStore(storageData),
1087
+ volume: getVolumeFromStore(storageData),
1088
+ handle: enqueueAssetForPreloading
1089
+ });
1090
+ }
1091
+ return [action, ...props];
1092
+ };
1093
+ }
1094
+ return object;
1095
+ };
1096
+
1017
1097
  // src/novely.ts
1018
1098
  var novely = ({
1019
1099
  characters,
@@ -1037,7 +1117,8 @@ var novely = ({
1037
1117
  fetch: request = fetch,
1038
1118
  cloneFunction: clone = klona,
1039
1119
  saveOnUnload = true,
1040
- startKey = "start"
1120
+ startKey = "start",
1121
+ defaultTypewriterSpeed = DEFAULT_TYPEWRITER_SPEED
1041
1122
  }) => {
1042
1123
  const languages = Object.keys(translation);
1043
1124
  const limitScript = pLimit(1);
@@ -1082,61 +1163,6 @@ var novely = ({
1082
1163
  const script = (part) => {
1083
1164
  return limitScript(() => scriptBase(part));
1084
1165
  };
1085
- const action = new Proxy({}, {
1086
- get(_, action2) {
1087
- if (action2 in renderer.actions) {
1088
- return renderer.actions[action2];
1089
- }
1090
- return (...props) => {
1091
- if (action2 === "say") {
1092
- action2 = "dialog";
1093
- const [character] = props;
1094
- if (DEV3 && !characters[character]) {
1095
- throw new Error(`Attempt to call Say action with unknown character "${character}"`);
1096
- }
1097
- } else if (action2 === "choice") {
1098
- const actions = props.slice(1);
1099
- if (actions.every((choice) => !Array.isArray(choice))) {
1100
- for (let i = 1; i < props.length; i++) {
1101
- const choice = props[i];
1102
- props[i] = [
1103
- choice.title,
1104
- flatActions(choice.children),
1105
- choice.active,
1106
- choice.visible,
1107
- choice.onSelect,
1108
- choice.image
1109
- ];
1110
- }
1111
- } else {
1112
- for (let i = 1; i < props.length; i++) {
1113
- const choice = props[i];
1114
- if (Array.isArray(choice)) {
1115
- choice[1] = flatActions(choice[1]);
1116
- }
1117
- }
1118
- }
1119
- } else if (action2 === "condition") {
1120
- const actions = props[1];
1121
- for (const key in actions) {
1122
- actions[key] = flatActions(actions[key]);
1123
- }
1124
- }
1125
- if (preloadAssets === "blocking") {
1126
- huntAssets({
1127
- action: action2,
1128
- props,
1129
- mode: preloadAssets,
1130
- characters,
1131
- lang: getLanguageFromStore(storageData),
1132
- volume: getVolumeFromStore(storageData),
1133
- handle: enqueueAssetForPreloading
1134
- });
1135
- }
1136
- return [action2, ...props];
1137
- };
1138
- }
1139
- });
1140
1166
  const getDefaultSave = (state) => {
1141
1167
  return [
1142
1168
  [
@@ -1153,7 +1179,7 @@ var novely = ({
1153
1179
  if (languages.includes(language)) {
1154
1180
  return language;
1155
1181
  }
1156
- if (DEV3) {
1182
+ if (DEV4) {
1157
1183
  throw new Error(
1158
1184
  `Attempt to use unsupported language "${language}". Supported languages: ${languages.join(", ")}.`
1159
1185
  );
@@ -1190,7 +1216,7 @@ var novely = ({
1190
1216
  };
1191
1217
  const throttledOnStorageDataChange = throttle(onStorageDataChange, throttleTimeout);
1192
1218
  const throttledEmergencyOnStorageDataChange = throttle(() => {
1193
- if (saveOnUnload === true || saveOnUnload === "prod" && !DEV3) {
1219
+ if (saveOnUnload === true || saveOnUnload === "prod" && !DEV4) {
1194
1220
  onStorageDataChange(storageData.get());
1195
1221
  }
1196
1222
  }, 10);
@@ -1199,14 +1225,14 @@ var novely = ({
1199
1225
  let stored = await storage.get();
1200
1226
  for (const migration of migrations) {
1201
1227
  stored = migration(stored);
1202
- if (DEV3 && !stored) {
1228
+ if (DEV4 && !stored) {
1203
1229
  throw new Error("Migrations should return a value.");
1204
1230
  }
1205
1231
  }
1206
1232
  if (overrideLanguage || !stored.meta[0]) {
1207
1233
  stored.meta[0] = getLanguageWithoutParameters();
1208
1234
  }
1209
- stored.meta[1] ||= DEFAULT_TYPEWRITER_SPEED;
1235
+ stored.meta[1] ||= defaultTypewriterSpeed;
1210
1236
  stored.meta[2] ??= 1;
1211
1237
  stored.meta[3] ??= 1;
1212
1238
  stored.meta[4] ??= 1;
@@ -1277,7 +1303,7 @@ var novely = ({
1277
1303
  let interacted = 0;
1278
1304
  const restore = async (save2) => {
1279
1305
  if (isEmpty(story)) {
1280
- if (DEV3) {
1306
+ if (DEV4) {
1281
1307
  throw new Error(
1282
1308
  "Story is empty. You should call an `enine.script` function [https://novely.pages.dev/guide/story.html]"
1283
1309
  );
@@ -1450,7 +1476,7 @@ var novely = ({
1450
1476
  };
1451
1477
  const getLanguageDisplayName = (lang) => {
1452
1478
  const language = translation[lang];
1453
- if (DEV3 && !language) {
1479
+ if (DEV4 && !language) {
1454
1480
  throw new Error(
1455
1481
  `Attempt to use unsupported language "${language}". Supported languages: ${languages.join(", ")}.`
1456
1482
  );
@@ -1614,7 +1640,7 @@ var novely = ({
1614
1640
  }
1615
1641
  }
1616
1642
  };
1617
- const match = matchAction(matchActionOptions, {
1643
+ const { match, nativeActions } = matchAction(matchActionOptions, {
1618
1644
  wait({ ctx, data: data2, push }, [time]) {
1619
1645
  if (ctx.meta.restoring) return;
1620
1646
  setTimeout(push, isFunction(time) ? time(data2) : time);
@@ -1671,11 +1697,11 @@ var novely = ({
1671
1697
  },
1672
1698
  showCharacter({ ctx, push }, [character, emotion, className, style]) {
1673
1699
  emotion ??= defaultEmotions[character];
1674
- if (DEV3 && !emotion) {
1700
+ if (DEV4 && !emotion) {
1675
1701
  throw new Error(`Attemp to show character "${character}" without emotion provided.`);
1676
1702
  }
1677
1703
  if (!emotion) return;
1678
- if (DEV3 && !characters[character].emotions[emotion]) {
1704
+ if (DEV4 && !characters[character].emotions[emotion]) {
1679
1705
  throw new Error(`Attempt to show character "${character}" with unknown emotion "${emotion}"`);
1680
1706
  }
1681
1707
  const handle = ctx.character(character);
@@ -1742,7 +1768,7 @@ var novely = ({
1742
1768
  const imageValue = image ? handleImageAsset(image) : "";
1743
1769
  return [templateReplace(content, data2), active$, visible$, onSelectWrapped, imageValue];
1744
1770
  });
1745
- if (DEV3 && transformedChoices.length === 0) {
1771
+ if (DEV4 && transformedChoices.length === 0) {
1746
1772
  throw new Error(
1747
1773
  `Running choice without variants to choose from, look at how to use Choice action properly [https://novely.pages.dev/guide/actions/choice#usage]`
1748
1774
  );
@@ -1754,7 +1780,7 @@ var novely = ({
1754
1780
  }
1755
1781
  const stack = useStack(ctx);
1756
1782
  const offset = isWithoutQuestion ? 0 : 1;
1757
- if (DEV3 && !transformedChoices[selected]) {
1783
+ if (DEV4 && !transformedChoices[selected]) {
1758
1784
  throw new Error("Choice children is empty, either add content there or make item not selectable");
1759
1785
  }
1760
1786
  stack.value[0].push(["choice", selected + offset], [null, 0]);
@@ -1763,10 +1789,10 @@ var novely = ({
1763
1789
  });
1764
1790
  },
1765
1791
  jump({ ctx, data: data2 }, [scene]) {
1766
- if (DEV3 && !story[scene]) {
1792
+ if (DEV4 && !story[scene]) {
1767
1793
  throw new Error(`Attempt to jump to unknown scene "${scene}"`);
1768
1794
  }
1769
- if (DEV3 && story[scene].length === 0) {
1795
+ if (DEV4 && story[scene].length === 0) {
1770
1796
  throw new Error(`Attempt to jump to empty scene "${scene}"`);
1771
1797
  }
1772
1798
  const stack = useStack(ctx);
@@ -1790,15 +1816,15 @@ var novely = ({
1790
1816
  );
1791
1817
  },
1792
1818
  condition({ ctx, data: data2 }, [condition, variants]) {
1793
- if (DEV3 && Object.values(variants).length === 0) {
1819
+ if (DEV4 && Object.values(variants).length === 0) {
1794
1820
  throw new Error(`Attempt to use Condition action with empty variants object`);
1795
1821
  }
1796
1822
  if (!ctx.meta.restoring) {
1797
1823
  const val = String(condition(data2));
1798
- if (DEV3 && !variants[val]) {
1824
+ if (DEV4 && !variants[val]) {
1799
1825
  throw new Error(`Attempt to go to unknown variant "${val}"`);
1800
1826
  }
1801
- if (DEV3 && variants[val].length === 0) {
1827
+ if (DEV4 && variants[val].length === 0) {
1802
1828
  throw new Error(`Attempt to go to empty variant "${val}"`);
1803
1829
  }
1804
1830
  const stack = useStack(ctx);
@@ -1852,7 +1878,7 @@ var novely = ({
1852
1878
  },
1853
1879
  animateCharacter({ ctx, push }, [character, className]) {
1854
1880
  const classes = className.split(" ");
1855
- if (DEV3 && classes.length === 0) {
1881
+ if (DEV4 && classes.length === 0) {
1856
1882
  throw new Error(
1857
1883
  "Attempt to use AnimateCharacter without classes. Classes should be provided [https://novely.pages.dev/guide/actions/animateCharacter.html]"
1858
1884
  );
@@ -1863,7 +1889,7 @@ var novely = ({
1863
1889
  },
1864
1890
  text({ ctx, data: data2, forward }, text) {
1865
1891
  const string = text.map((content) => templateReplace(content, data2)).join(" ");
1866
- if (DEV3 && string.length === 0) {
1892
+ if (DEV4 && string.length === 0) {
1867
1893
  throw new Error(`Action Text was called with empty string or array`);
1868
1894
  }
1869
1895
  ctx.clearBlockingActions("text");
@@ -1888,7 +1914,7 @@ var novely = ({
1888
1914
  render(ctx);
1889
1915
  },
1890
1916
  preload({ ctx, push }, [source]) {
1891
- if (DEV3 && preloadAssets !== "lazy") {
1917
+ if (DEV4 && preloadAssets !== "lazy") {
1892
1918
  console.error(
1893
1919
  `You do not need a preload action becase "preloadAssets" strategy was set to "${preloadAssets}"`
1894
1920
  );
@@ -1899,10 +1925,10 @@ var novely = ({
1899
1925
  push();
1900
1926
  },
1901
1927
  block({ ctx }, [scene]) {
1902
- if (DEV3 && !story[scene]) {
1928
+ if (DEV4 && !story[scene]) {
1903
1929
  throw new Error(`Attempt to call Block action with unknown scene "${scene}"`);
1904
1930
  }
1905
- if (DEV3 && story[scene].length === 0) {
1931
+ if (DEV4 && story[scene].length === 0) {
1906
1932
  throw new Error(`Attempt to call Block action with empty scene "${scene}"`);
1907
1933
  }
1908
1934
  if (!ctx.meta.restoring) {
@@ -1912,6 +1938,13 @@ var novely = ({
1912
1938
  }
1913
1939
  }
1914
1940
  });
1941
+ const action = buildActionObject({
1942
+ rendererActions: renderer.actions,
1943
+ nativeActions,
1944
+ characters,
1945
+ preloadAssets,
1946
+ storageData
1947
+ });
1915
1948
  const render = (ctx) => {
1916
1949
  const stack = useStack(ctx);
1917
1950
  const [path, state] = stack.value;
@@ -1969,7 +2002,7 @@ var novely = ({
1969
2002
  };
1970
2003
  const setStorageData = (data2) => {
1971
2004
  if (destroyed) {
1972
- if (DEV3) {
2005
+ if (DEV4) {
1973
2006
  throw new Error(
1974
2007
  `function \`setStorageData\` was called after novely instance was destroyed. Data is not updater nor synced after destroy.`
1975
2008
  );
@@ -2006,7 +2039,8 @@ var novely = ({
2006
2039
  */
2007
2040
  action,
2008
2041
  /**
2009
- * @deprecated Will be removed BUT replaced with state passed into actions as a parameter
2042
+ * State bound to `$MAIN` game context
2043
+ * @deprecated Use `state` function provided from action arguments
2010
2044
  */
2011
2045
  state: getStateFunction(MAIN_CONTEXT_KEY),
2012
2046
  /**
@@ -2101,11 +2135,10 @@ var novely = ({
2101
2135
 
2102
2136
  // src/extend-actions.ts
2103
2137
  var extendAction = (base, extension) => {
2104
- return new Proxy({}, {
2105
- get(_, key, receiver) {
2106
- return Reflect.get(key in extension ? extension : base, key, receiver);
2107
- }
2108
- });
2138
+ return {
2139
+ ...extension,
2140
+ ...base
2141
+ };
2109
2142
  };
2110
2143
 
2111
2144
  // src/translations.ts
@@ -2264,7 +2297,7 @@ var JP = {
2264
2297
 
2265
2298
  // src/asset.ts
2266
2299
  import { memoize as memoize4, once } from "es-toolkit/function";
2267
- import { DEV as DEV4 } from "esm-env";
2300
+ import { DEV as DEV5 } from "esm-env";
2268
2301
 
2269
2302
  // src/audio-codecs.ts
2270
2303
  var cut = (str) => str.replace(/^no$/, "");
@@ -2347,7 +2380,7 @@ var SUPPORT_MAPS = {
2347
2380
  };
2348
2381
  var assetPrivate = memoize4(
2349
2382
  (variants) => {
2350
- if (DEV4 && variants.length === 0) {
2383
+ if (DEV5 && variants.length === 0) {
2351
2384
  throw new Error(`Attempt to use "asset" function without arguments`);
2352
2385
  }
2353
2386
  const map = {};
@@ -2369,7 +2402,7 @@ var assetPrivate = memoize4(
2369
2402
  return map[extension];
2370
2403
  }
2371
2404
  }
2372
- if (DEV4) {
2405
+ if (DEV5) {
2373
2406
  throw new Error(`No matching asset was found for ${variants.map((v) => `"${v}"`).join(", ")}`);
2374
2407
  }
2375
2408
  return "";