@novely/core 0.36.0 → 0.37.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.d.ts CHANGED
@@ -198,8 +198,15 @@ type Renderer = {
198
198
  getContext: (context: string) => Context;
199
199
  removeContext: (context: string) => void;
200
200
  };
201
+ type RendererInitPreviewReturn = {
202
+ /**
203
+ * Assets that was used in game preview
204
+ */
205
+ assets: string[];
206
+ };
201
207
  type RendererInit<$Language extends Lang, $Characters extends Record<string, Character<$Language>>> = {
202
208
  characters: $Characters;
209
+ characterAssetSizes: CharacterAssetSizes<$Characters>;
203
210
  set: (save: Save<State>) => Promise<void>;
204
211
  restore: (save?: Save<State>) => Promise<void>;
205
212
  save: (type: Save<State>[2][1]) => void;
@@ -223,7 +230,7 @@ type RendererInit<$Language extends Lang, $Characters extends Record<string, Cha
223
230
  * There is different context, and the main one which is used for game
224
231
  */
225
232
  mainContextKey: string;
226
- preview: (save: Save<State>, name: string) => Promise<void>;
233
+ preview: (save: Save<State>, name: string) => Promise<RendererInitPreviewReturn>;
227
234
  removeContext: (name: string) => void;
228
235
  getStateFunction: (context: string) => StateFunction<State>;
229
236
  clearCustomAction: (ctx: Context, customAction: CustomHandler) => void;
@@ -233,7 +240,6 @@ type RendererInit<$Language extends Lang, $Characters extends Record<string, Cha
233
240
  declare const getLanguage: (languages: string[]) => string;
234
241
 
235
242
  type Thenable<T> = T | Promise<T>;
236
- type NoInfer<T> = [T][T extends any ? 0 : never];
237
243
  type PathItem = [null, number | string] | ['jump', string] | ['choice', number] | ['choice:exit'] | ['condition', string] | ['condition:exit'] | ['exit'] | ['block', string] | ['block:exit'];
238
244
  type Path = PathItem[];
239
245
  type State = Record<string, any>;
@@ -302,6 +308,12 @@ type TranslationDescription = {
302
308
  type DefaultEmotions<$Characters extends Record<string, Character<Lang>>> = {
303
309
  [Character in keyof $Characters]?: (keyof $Characters[Character]['emotions'] & string);
304
310
  };
311
+ type CharacterAssetSizes<$Characters extends Record<string, Character<Lang>>> = {
312
+ [Character in keyof $Characters]?: {
313
+ width: number;
314
+ height: number;
315
+ };
316
+ };
305
317
  interface NovelyInit<$Language extends Lang, $Characters extends Record<string, Character<NoInfer<$Language>>>, $State extends State, $Data extends Data, $Actions extends Record<string, (...args: any[]) => ValidAction>> {
306
318
  /**
307
319
  * An object containing the characters in the game.
@@ -350,6 +362,32 @@ interface NovelyInit<$Language extends Lang, $Characters extends Record<string,
350
362
  * ```
351
363
  */
352
364
  defaultEmotions?: DefaultEmotions<NoInfer<$Characters>>;
365
+ /**
366
+ * Character asset sizes. We need width-height pair to render character, but we get it only after the assets are loaded. However, using that option we can use width-height before load.
367
+ * @example
368
+ * ```
369
+ * import peter_the_great from './assets/peter_the_great.png?width=800&height=1200';
370
+ *
371
+ * const engine = novely({
372
+ * characters: {
373
+ * Peter: {
374
+ * name: 'Peter',
375
+ * color: '#c04931',
376
+ * emotions: {
377
+ * normal: peter_the_great
378
+ * }
379
+ * }
380
+ * },
381
+ * characterAssetSizes: {
382
+ * Peter: {
383
+ * width: 800,
384
+ * height: 1200
385
+ * }
386
+ * }
387
+ * })
388
+ * ```
389
+ */
390
+ characterAssetSizes?: CharacterAssetSizes<NoInfer<$Characters>>;
353
391
  /**
354
392
  * An object that provides access to the game's storage system.
355
393
  * @default localStorage // at key `novely-game-storage`
@@ -735,7 +773,7 @@ type ChoiceParams<T> = T extends TypeEssentials<infer $Lang, infer $State, any,
735
773
  type FunctionParams<T> = T extends TypeEssentials<infer $Lang, infer $State, any, any> ? FunctionActionProps<$Lang, $State> : never;
736
774
  type InputHandler<T> = T extends TypeEssentials<infer $Lang, infer $State, any, any> ? ActionInputOnInputMeta<$Lang, $State> : never;
737
775
 
738
- declare const novely: <$Language extends string, $Characters extends Record<string, Character<$Language>>, $State extends State, $Data extends Data, $Actions extends Record<string, (...args: any[]) => ValidAction>>({ characters, defaultEmotions, storage, storageDelay, renderer: createRenderer, initialScreen, translation, state: defaultState, data: defaultData, autosaves, migrations, throttleTimeout, getLanguage, overrideLanguage, askBeforeExit, preloadAssets, parallelAssetsDownloadLimit, fetch: request, saveOnUnload, startKey }: NovelyInit<$Language, $Characters, $State, $Data, $Actions>) => {
776
+ declare const novely: <$Language extends string, $Characters extends Record<string, Character<$Language>>, $State extends State, $Data extends Data, $Actions extends Record<string, (...args: any[]) => ValidAction>>({ characters, characterAssetSizes, defaultEmotions, storage, storageDelay, renderer: createRenderer, initialScreen, translation, state: defaultState, data: defaultData, autosaves, migrations, throttleTimeout, getLanguage, overrideLanguage, askBeforeExit, preloadAssets, parallelAssetsDownloadLimit, fetch: request, saveOnUnload, startKey }: NovelyInit<$Language, $Characters, $State, $Data, $Actions>) => {
739
777
  /**
740
778
  * Function to set game script
741
779
  *
@@ -857,4 +895,4 @@ declare const novely: <$Language extends string, $Characters extends Record<stri
857
895
  */
858
896
  declare const extendAction: <Part0 extends Record<string, (...args: any[]) => ValidAction>, Part1 extends Record<string, (...args: any[]) => ValidAction>>(base: Part0, extension: Part1) => Readonly<Assign<Part0, Part1>>;
859
897
 
860
- export { type ActionInputOnInputMeta, type ActionInputSetup, type ActionInputSetupCleanup, type ActionProxy, type AllowedContent, type AudioHandle, type BackgroundImage, type BaseTranslationStrings, type Character, type CharacterHandle, type ChoiceParams, type ConditionParams, type Context, type CoreData, type CustomActionHandle, type CustomHandler, type CustomHandlerFunctionGetFn, type CustomHandlerFunctionParameters, type CustomHandlerGetResult, type CustomHandlerGetResultDataFunction, type Data, type DeepPartial, type DefaultActionProxy, EN, type Emotions, type FunctionParams, type FunctionableValue, type GetActionParameters, type InputHandler, JP, KK, type Lang, type NovelyInit, type NovelyScreen, type Path, type PluralType, type Pluralization, RU, type Renderer, type RendererInit, type Save, type Stack, type StackHolder, type State, type StateFunction, type Storage, type StorageData, type StorageMeta, type Stored, type Story, type TextContent, type Thenable, type TranslationActions, type TypeEssentials, type TypewriterSpeed, type ValidAction, extendAction, localStorageStorage, novely };
898
+ export { type ActionInputOnInputMeta, type ActionInputSetup, type ActionInputSetupCleanup, type ActionProxy, type AllowedContent, type AudioHandle, type BackgroundImage, type BaseTranslationStrings, type Character, type CharacterHandle, type ChoiceParams, type ConditionParams, type Context, type CoreData, type CustomActionHandle, type CustomHandler, type CustomHandlerFunctionGetFn, type CustomHandlerFunctionParameters, type CustomHandlerGetResult, type CustomHandlerGetResultDataFunction, type Data, type DeepPartial, type DefaultActionProxy, EN, type Emotions, type FunctionParams, type FunctionableValue, type GetActionParameters, type InputHandler, JP, KK, type Lang, type NovelyInit, type NovelyScreen, type Path, type PluralType, type Pluralization, RU, type Renderer, type RendererInit, type RendererInitPreviewReturn, type Save, type Stack, type StackHolder, type State, type StateFunction, type Storage, type StorageData, type StorageMeta, type Stored, type Story, type TextContent, type Thenable, type TranslationActions, type TypeEssentials, type TypewriterSpeed, type ValidAction, extendAction, localStorageStorage, novely };
@@ -212,6 +212,7 @@ var Novely = (() => {
212
212
  var STACK_MAP = /* @__PURE__ */ new Map();
213
213
  var CUSTOM_ACTION_MAP = /* @__PURE__ */ new Map();
214
214
  var PRELOADED_ASSETS = /* @__PURE__ */ new Set();
215
+ var RESOURCE_TYPE_CACHE = /* @__PURE__ */ new Map();
215
216
 
216
217
  // ../../node_modules/.pnpm/esm-env@1.0.0/node_modules/esm-env/prod-ssr.js
217
218
  var DEV = false;
@@ -663,24 +664,31 @@ var Novely = (() => {
663
664
  }
664
665
  };
665
666
  var getResourseType = async (request, url) => {
667
+ if (RESOURCE_TYPE_CACHE.has(url)) {
668
+ return RESOURCE_TYPE_CACHE.get(url);
669
+ }
670
+ const encache = (value) => {
671
+ RESOURCE_TYPE_CACHE.set(url, value);
672
+ return value;
673
+ };
666
674
  if (!isCSSImage(url)) {
667
- return "other";
675
+ return encache("other");
668
676
  }
669
677
  const extension = getUrlFileExtension(url);
670
678
  if (HOWLER_SUPPORTED_FILE_FORMATS.has(extension)) {
671
- return "audio";
679
+ return encache("audio");
672
680
  }
673
681
  if (SUPPORTED_IMAGE_FILE_FORMATS.has(extension)) {
674
- return "image";
682
+ return encache("image");
675
683
  }
676
684
  const contentType = await fetchContentType(request, url);
677
685
  if (contentType.includes("audio")) {
678
- return "audio";
686
+ return encache("audio");
679
687
  }
680
688
  if (contentType.includes("image")) {
681
- return "image";
689
+ return encache("image");
682
690
  }
683
- return "other";
691
+ return encache("other");
684
692
  };
685
693
  var capitalize = (str2) => {
686
694
  return str2[0].toUpperCase() + str2.slice(1);
@@ -1128,6 +1136,7 @@ var Novely = (() => {
1128
1136
  var import_p_limit = __toESM(require_p_limit(), 1);
1129
1137
  var novely = ({
1130
1138
  characters,
1139
+ characterAssetSizes = {},
1131
1140
  defaultEmotions = {},
1132
1141
  storage = localStorageStorage({ key: "novely-game-storage" }),
1133
1142
  storageDelay = Promise.resolve(),
@@ -1214,38 +1223,38 @@ var Novely = (() => {
1214
1223
  const script = (part) => {
1215
1224
  return limitScript(() => scriptBase(part));
1216
1225
  };
1217
- const checkAndAddToPreloadingList = (action2, props) => {
1226
+ const assetPreloadingCheck = (action2, props, doAction = assetsToPreloadAdd) => {
1218
1227
  if (action2 === "showBackground") {
1219
1228
  if (isImageAsset(props[0])) {
1220
- assetsToPreloadAdd(props[0]);
1229
+ doAction(props[0]);
1221
1230
  }
1222
1231
  if (props[0] && typeof props[0] === "object") {
1223
1232
  for (const value of Object.values(props[0])) {
1224
1233
  if (!isImageAsset(value))
1225
1234
  continue;
1226
- assetsToPreloadAdd(value);
1235
+ doAction(value);
1227
1236
  }
1228
1237
  }
1229
1238
  return;
1230
1239
  }
1231
1240
  if (isAudioAction(action2) && isString(props[0])) {
1232
- assetsToPreloadAdd(props[0]);
1241
+ doAction(props[0]);
1233
1242
  return;
1234
1243
  }
1235
1244
  if (action2 === "showCharacter" && isString(props[0]) && isString(props[1])) {
1236
1245
  const images = characters[props[0]].emotions[props[1]];
1237
1246
  if (Array.isArray(images)) {
1238
1247
  for (const asset of images) {
1239
- assetsToPreloadAdd(asset);
1248
+ doAction(asset);
1240
1249
  }
1241
1250
  } else {
1242
- assetsToPreloadAdd(images);
1251
+ doAction(images);
1243
1252
  }
1244
1253
  return;
1245
1254
  }
1246
1255
  if (action2 === "custom" && props[0].assets && props[0].assets.length > 0) {
1247
1256
  for (const asset of props[0].assets) {
1248
- assetsToPreloadAdd(asset);
1257
+ doAction(asset);
1249
1258
  }
1250
1259
  }
1251
1260
  };
@@ -1256,7 +1265,7 @@ var Novely = (() => {
1256
1265
  }
1257
1266
  return (...props) => {
1258
1267
  if (preloadAssets === "blocking") {
1259
- checkAndAddToPreloadingList(action2, props);
1268
+ assetPreloadingCheck(action2, props);
1260
1269
  }
1261
1270
  return [action2, ...props];
1262
1271
  };
@@ -1507,8 +1516,11 @@ var Novely = (() => {
1507
1516
  return translation[lang].internal[key];
1508
1517
  };
1509
1518
  const preview = async (save2, name) => {
1510
- if (isEmpty(story))
1511
- return;
1519
+ if (isEmpty(story)) {
1520
+ return Promise.resolve({
1521
+ assets: []
1522
+ });
1523
+ }
1512
1524
  const [path, data2] = save2;
1513
1525
  const { queue } = getActionsFromPath(story, path, true);
1514
1526
  const ctx = renderer.getContext(name);
@@ -1518,6 +1530,7 @@ var Novely = (() => {
1518
1530
  skip: EMPTY_SET
1519
1531
  });
1520
1532
  useStack(ctx).push(klona(save2));
1533
+ const assets = [];
1521
1534
  await processor.run(([action2, ...props]) => {
1522
1535
  if (isAudioAction(action2))
1523
1536
  return;
@@ -1525,11 +1538,15 @@ var Novely = (() => {
1525
1538
  return;
1526
1539
  if (action2 === "end")
1527
1540
  return;
1541
+ assetPreloadingCheck(action2, props, assets.push.bind(assets));
1528
1542
  return match(action2, props, {
1529
1543
  ctx,
1530
1544
  data: data2
1531
1545
  });
1532
1546
  });
1547
+ return {
1548
+ assets
1549
+ };
1533
1550
  };
1534
1551
  const removeContext = (name) => {
1535
1552
  STACK_MAP.delete(name);
@@ -1564,6 +1581,7 @@ var Novely = (() => {
1564
1581
  const renderer = createRenderer({
1565
1582
  mainContextKey: MAIN_CONTEXT_KEY,
1566
1583
  characters,
1584
+ characterAssetSizes,
1567
1585
  set,
1568
1586
  restore,
1569
1587
  save,
@@ -1625,7 +1643,7 @@ var Novely = (() => {
1625
1643
  refer
1626
1644
  });
1627
1645
  for (const [action3, ...props2] of collection) {
1628
- checkAndAddToPreloadingList(action3, props2);
1646
+ assetPreloadingCheck(action3, props2);
1629
1647
  }
1630
1648
  const { preloadAudioBlocking, preloadImageBlocking } = renderer.misc;
1631
1649
  ASSETS_TO_PRELOAD.forEach(async (asset) => {