@novely/core 0.35.0 → 0.36.0-beta.1

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.d.ts CHANGED
@@ -452,7 +452,7 @@ interface NovelyInit<$Language extends Lang, $Characters extends Record<string,
452
452
  /**
453
453
  * @default "lazy"
454
454
  */
455
- preloadAssets?: 'lazy' | 'blocking';
455
+ preloadAssets?: 'lazy' | 'blocking' | 'automatic';
456
456
  /**
457
457
  * Fetching function
458
458
  */
@@ -245,9 +245,16 @@ var Novely = (() => {
245
245
  }
246
246
 
247
247
  // src/utils.ts
248
- var matchAction = ({ getContext, push, forward }, values) => {
248
+ var matchAction = ({ getContext, onBeforeActionCall, push, forward }, values) => {
249
249
  return (action, props, { ctx, data }) => {
250
250
  const context = typeof ctx === "string" ? getContext(ctx) : ctx;
251
+ if (action !== "say") {
252
+ onBeforeActionCall({
253
+ action,
254
+ props,
255
+ ctx: context
256
+ });
257
+ }
251
258
  return values[action]({
252
259
  ctx: context,
253
260
  data,
@@ -412,7 +419,7 @@ var Novely = (() => {
412
419
  var getActionsFromPath = (story, path, filter) => {
413
420
  let current = story;
414
421
  let precurrent;
415
- let ignoreNested = false;
422
+ let ignoreNestedBefore = null;
416
423
  let index = 0;
417
424
  let skipPreserve = void 0;
418
425
  const skip = /* @__PURE__ */ new Set();
@@ -433,11 +440,11 @@ var Novely = (() => {
433
440
  if (isNumber(val)) {
434
441
  index++;
435
442
  let startIndex = 0;
436
- if (ignoreNested) {
437
- const prev = findLastPathItemBeforeItemOfType(path.slice(0, index), "block");
443
+ if (ignoreNestedBefore) {
444
+ const prev = findLastPathItemBeforeItemOfType(path.slice(0, index), ignoreNestedBefore);
438
445
  if (prev) {
439
446
  startIndex = prev[1];
440
- ignoreNested = false;
447
+ ignoreNestedBefore = null;
441
448
  }
442
449
  }
443
450
  for (let i = startIndex; i <= val; i++) {
@@ -472,7 +479,7 @@ var Novely = (() => {
472
479
  current = story[val];
473
480
  } else if (type === "block:exit" || type === "choice:exit" || type === "condition:exit") {
474
481
  current = blocks.pop();
475
- ignoreNested = true;
482
+ ignoreNestedBefore = type.slice(0, -5);
476
483
  }
477
484
  }
478
485
  return {
@@ -656,6 +663,9 @@ var Novely = (() => {
656
663
  }
657
664
  };
658
665
  var getResourseType = async (request, url) => {
666
+ if (!isCSSImage(url)) {
667
+ return "other";
668
+ }
659
669
  const extension = getUrlFileExtension(url);
660
670
  if (HOWLER_SUPPORTED_FILE_FORMATS.has(extension)) {
661
671
  return "audio";
@@ -769,6 +779,68 @@ var Novely = (() => {
769
779
  }
770
780
  return path;
771
781
  };
782
+ var isBlockingAction = (action) => {
783
+ return isUserRequiredAction(action) || isSkippedDuringRestore(action[0]) && action[0] !== "vibrate";
784
+ };
785
+ var collectActionsBeforeBlockingAction = ({ path, refer }) => {
786
+ const collection = [];
787
+ let action = refer(path);
788
+ while (true) {
789
+ if (action == void 0) {
790
+ const { exitImpossible } = exitPath({
791
+ path,
792
+ refer
793
+ });
794
+ if (exitImpossible) {
795
+ break;
796
+ }
797
+ }
798
+ if (!action) {
799
+ break;
800
+ }
801
+ if (isBlockingAction(action)) {
802
+ const [name, ...props] = action;
803
+ if (name === "choice") {
804
+ const choiceProps = props;
805
+ for (let i = 0; i < choiceProps.length; i++) {
806
+ const branchContent = choiceProps[i];
807
+ if (!Array.isArray(branchContent))
808
+ continue;
809
+ const virtualPath = klona(path);
810
+ virtualPath.push(["choice", i], [null, 0]);
811
+ const innerActions = collectActionsBeforeBlockingAction({
812
+ path: virtualPath,
813
+ refer
814
+ });
815
+ collection.push(...innerActions);
816
+ }
817
+ } else if (name === "condition") {
818
+ const conditionProps = props;
819
+ const conditions = Object.keys(conditionProps[1]);
820
+ for (const condition of conditions) {
821
+ const virtualPath = klona(path);
822
+ virtualPath.push(["condition", condition], [null, 0]);
823
+ const innerActions = collectActionsBeforeBlockingAction({
824
+ path: virtualPath,
825
+ refer
826
+ });
827
+ collection.push(...innerActions);
828
+ }
829
+ }
830
+ break;
831
+ }
832
+ collection.push(action);
833
+ if (action[0] === "jump") {
834
+ path = [["jump", action[1]], [null, 0]];
835
+ } else if (action[0] == "block") {
836
+ path.push(["block", action[1]], [null, 0]);
837
+ } else {
838
+ nextPath(path);
839
+ }
840
+ action = refer(path);
841
+ }
842
+ return collection;
843
+ };
772
844
 
773
845
  // ../../node_modules/.pnpm/dequal@2.0.3/node_modules/dequal/lite/index.mjs
774
846
  var has = Object.prototype.hasOwnProperty;
@@ -1087,6 +1159,31 @@ var Novely = (() => {
1087
1159
  const intime = (value) => {
1088
1160
  return times.add(value), value;
1089
1161
  };
1162
+ const handleAssetsPreloading = async () => {
1163
+ const { preloadAudioBlocking, preloadImageBlocking } = renderer.misc;
1164
+ const list = mapSet(ASSETS_TO_PRELOAD, (asset) => {
1165
+ return limitAssetsDownload(async () => {
1166
+ const type = await getResourseType(request, asset);
1167
+ switch (type) {
1168
+ case "audio": {
1169
+ await preloadAudioBlocking(asset);
1170
+ break;
1171
+ }
1172
+ case "image": {
1173
+ await preloadImageBlocking(asset);
1174
+ break;
1175
+ }
1176
+ }
1177
+ });
1178
+ });
1179
+ await Promise.allSettled(list);
1180
+ ASSETS_TO_PRELOAD.clear();
1181
+ };
1182
+ const assetsToPreloadAdd = (asset) => {
1183
+ if (!PRELOADED_ASSETS.has(asset)) {
1184
+ ASSETS_TO_PRELOAD.add(asset);
1185
+ }
1186
+ };
1090
1187
  const scriptBase = async (part) => {
1091
1188
  Object.assign(story, flattenStory(part));
1092
1189
  let loadingIsShown = false;
@@ -1098,31 +1195,14 @@ var Novely = (() => {
1098
1195
  if (!loadingIsShown) {
1099
1196
  renderer.ui.showLoading();
1100
1197
  }
1101
- const { preloadAudioBlocking, preloadImageBlocking } = renderer.misc;
1102
- const list = mapSet(ASSETS_TO_PRELOAD, (asset) => {
1103
- return limitAssetsDownload(async () => {
1104
- const type = await getResourseType(request, asset);
1105
- switch (type) {
1106
- case "audio": {
1107
- await preloadAudioBlocking(asset);
1108
- break;
1109
- }
1110
- case "image": {
1111
- await preloadImageBlocking(asset);
1112
- break;
1113
- }
1114
- }
1115
- });
1116
- });
1117
- await Promise.allSettled(list);
1198
+ await handleAssetsPreloading();
1118
1199
  }
1119
- ASSETS_TO_PRELOAD.clear();
1120
1200
  await dataLoaded.promise;
1121
1201
  renderer.ui.hideLoading();
1122
1202
  if (!initialScreenWasShown) {
1123
1203
  initialScreenWasShown = true;
1124
1204
  if (initialScreen === "game") {
1125
- restore();
1205
+ restore(void 0);
1126
1206
  } else {
1127
1207
  renderer.ui.showScreen(initialScreen);
1128
1208
  }
@@ -1131,6 +1211,33 @@ var Novely = (() => {
1131
1211
  const script = (part) => {
1132
1212
  return limitScript(() => scriptBase(part));
1133
1213
  };
1214
+ const checkAndAddToPreloadingList = (action2, props) => {
1215
+ if (action2 === "showBackground") {
1216
+ if (isImageAsset(props[0])) {
1217
+ assetsToPreloadAdd(props[0]);
1218
+ }
1219
+ if (props[0] && typeof props[0] === "object") {
1220
+ for (const value of Object.values(props[0])) {
1221
+ if (!isImageAsset(value))
1222
+ continue;
1223
+ assetsToPreloadAdd(value);
1224
+ }
1225
+ }
1226
+ }
1227
+ if (isAudioAction(action2) && isString(props[0])) {
1228
+ assetsToPreloadAdd(props[0]);
1229
+ }
1230
+ if (action2 === "showCharacter" && isString(props[0]) && isString(props[1])) {
1231
+ const images = characters[props[0]].emotions[props[1]];
1232
+ if (Array.isArray(images)) {
1233
+ for (const asset of images) {
1234
+ assetsToPreloadAdd(asset);
1235
+ }
1236
+ } else {
1237
+ assetsToPreloadAdd(images);
1238
+ }
1239
+ }
1240
+ };
1134
1241
  const action = new Proxy({}, {
1135
1242
  get(_, action2) {
1136
1243
  if (action2 in renderer.actions) {
@@ -1138,31 +1245,7 @@ var Novely = (() => {
1138
1245
  }
1139
1246
  return (...props) => {
1140
1247
  if (preloadAssets === "blocking") {
1141
- if (action2 === "showBackground") {
1142
- if (isImageAsset(props[0])) {
1143
- ASSETS_TO_PRELOAD.add(props[0]);
1144
- }
1145
- if (props[0] && typeof props[0] === "object") {
1146
- for (const value of Object.values(props[0])) {
1147
- if (!isImageAsset(value))
1148
- continue;
1149
- ASSETS_TO_PRELOAD.add(value);
1150
- }
1151
- }
1152
- }
1153
- if (isAudioAction(action2) && isString(props[0])) {
1154
- ASSETS_TO_PRELOAD.add(props[0]);
1155
- }
1156
- if (action2 === "showCharacter" && isString(props[0]) && isString(props[1])) {
1157
- const images = characters[props[0]].emotions[props[1]];
1158
- if (Array.isArray(images)) {
1159
- for (const asset of images) {
1160
- ASSETS_TO_PRELOAD.add(asset);
1161
- }
1162
- } else {
1163
- ASSETS_TO_PRELOAD.add(images);
1164
- }
1165
- }
1248
+ checkAndAddToPreloadingList(action2, props);
1166
1249
  }
1167
1250
  return [action2, ...props];
1168
1251
  };
@@ -1289,7 +1372,12 @@ var Novely = (() => {
1289
1372
  return prev.saves.push(save2), prev;
1290
1373
  });
1291
1374
  }
1292
- restore(save2);
1375
+ const context = renderer.getContext(MAIN_CONTEXT_KEY);
1376
+ const stack = useStack(context);
1377
+ stack.value = save2;
1378
+ context.meta.restoring = context.meta.goingBack = false;
1379
+ renderer.ui.showScreen("game");
1380
+ render(context);
1293
1381
  };
1294
1382
  const set = (save2, ctx) => {
1295
1383
  const stack = useStack(ctx || MAIN_CONTEXT_KEY);
@@ -1344,8 +1432,8 @@ var Novely = (() => {
1344
1432
  data: latest[1]
1345
1433
  });
1346
1434
  }
1347
- const lastQueueItem = queue.at(-1) || [];
1348
- const lastQueueItemRequiresUserAction = isSkippedDuringRestore(lastQueueItem[0]) || isUserRequiredAction(lastQueueItem);
1435
+ const lastQueueItem = queue.at(-1);
1436
+ const lastQueueItemRequiresUserAction = lastQueueItem && isBlockingAction(lastQueueItem);
1349
1437
  await run((item) => {
1350
1438
  if (!latest)
1351
1439
  return;
@@ -1512,6 +1600,40 @@ var Novely = (() => {
1512
1600
  matchActionOptions.push(ctx);
1513
1601
  if (!ctx.meta.preview)
1514
1602
  interactivity(true);
1603
+ },
1604
+ onBeforeActionCall({ action: action2, props, ctx }) {
1605
+ if (preloadAssets !== "automatic")
1606
+ return;
1607
+ if (ctx.meta.preview || ctx.meta.restoring)
1608
+ return;
1609
+ if (!isBlockingAction([action2, ...props]))
1610
+ return;
1611
+ try {
1612
+ const collection = collectActionsBeforeBlockingAction({
1613
+ path: nextPath(klona(useStack(ctx).value[0])),
1614
+ refer
1615
+ });
1616
+ for (const [action3, ...props2] of collection) {
1617
+ checkAndAddToPreloadingList(action3, props2);
1618
+ }
1619
+ const { preloadAudioBlocking, preloadImage } = renderer.misc;
1620
+ ASSETS_TO_PRELOAD.forEach(async (asset) => {
1621
+ ASSETS_TO_PRELOAD.delete(asset);
1622
+ const type = await getResourseType(request, asset);
1623
+ switch (type) {
1624
+ case "audio": {
1625
+ preloadAudioBlocking(asset);
1626
+ break;
1627
+ }
1628
+ case "image": {
1629
+ preloadImage(asset);
1630
+ break;
1631
+ }
1632
+ }
1633
+ });
1634
+ } catch (cause) {
1635
+ console.error(cause);
1636
+ }
1515
1637
  }
1516
1638
  };
1517
1639
  const match = matchAction(matchActionOptions, {
@@ -1793,6 +1915,9 @@ var Novely = (() => {
1793
1915
  render(ctx);
1794
1916
  },
1795
1917
  preload({ ctx, push }, [source]) {
1918
+ if (DEV && preloadAssets !== "lazy") {
1919
+ console.error(`You do not need a preload action becase "preloadAssets" strategy was set to "${preloadAssets}"`);
1920
+ }
1796
1921
  if (!ctx.meta.goingBack && !ctx.meta.restoring && !PRELOADED_ASSETS.has(source)) {
1797
1922
  PRELOADED_ASSETS.add(renderer.misc.preloadImage(source));
1798
1923
  }