@optolith/entity-descriptions 0.3.2 → 0.4.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.
Files changed (93) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/lib/creator.d.ts +2 -2
  3. package/lib/creator.js +1 -1
  4. package/lib/entities/activatable.js +35 -64
  5. package/lib/entities/culture.js +18 -26
  6. package/lib/entities/curriculum.js +30 -33
  7. package/lib/entities/derivedCharacteristic.js +1 -1
  8. package/lib/entities/disease.js +3 -6
  9. package/lib/entities/elixir.js +6 -4
  10. package/lib/entities/equipment.js +17 -17
  11. package/lib/entities/equipmentPackage.js +4 -3
  12. package/lib/entities/experienceLevel.js +7 -7
  13. package/lib/entities/focusRule.js +2 -2
  14. package/lib/entities/influence.js +2 -2
  15. package/lib/entities/liturgicalChant.d.ts +1 -1
  16. package/lib/entities/partial/adventurePointsValue.js +14 -19
  17. package/lib/entities/partial/enhancements.d.ts +1 -1
  18. package/lib/entities/partial/enhancements.js +1 -1
  19. package/lib/entities/partial/herbary.js +13 -9
  20. package/lib/entities/partial/map.d.ts +3 -1
  21. package/lib/entities/partial/map.js +21 -8
  22. package/lib/entities/partial/mathOperation.js +12 -17
  23. package/lib/entities/partial/prerequisites/displayOption.d.ts +2 -2
  24. package/lib/entities/partial/prerequisites/index.d.ts +3 -3
  25. package/lib/entities/partial/prerequisites/index.js +10 -4
  26. package/lib/entities/partial/prerequisites/part.d.ts +1 -1
  27. package/lib/entities/partial/prerequisites/prerequisiteGroups.d.ts +4 -4
  28. package/lib/entities/partial/prerequisites/prerequisiteGroups.js +1 -1
  29. package/lib/entities/partial/prerequisites/single/activatable.d.ts +3 -3
  30. package/lib/entities/partial/prerequisites/single/animistPower.d.ts +2 -2
  31. package/lib/entities/partial/prerequisites/single/animistPower.js +1 -1
  32. package/lib/entities/partial/prerequisites/single/blessedTradition.d.ts +2 -2
  33. package/lib/entities/partial/prerequisites/single/commonSuggestedByRCP.d.ts +2 -2
  34. package/lib/entities/partial/prerequisites/single/culture.d.ts +4 -4
  35. package/lib/entities/partial/prerequisites/single/enhancement.d.ts +4 -4
  36. package/lib/entities/partial/prerequisites/single/influence.d.ts +3 -3
  37. package/lib/entities/partial/prerequisites/single/magicalTradition.d.ts +2 -2
  38. package/lib/entities/partial/prerequisites/single/noOtherAncestorBloodAdvantage.d.ts +2 -2
  39. package/lib/entities/partial/prerequisites/single/pact.d.ts +3 -3
  40. package/lib/entities/partial/prerequisites/single/personalityTrait.d.ts +4 -4
  41. package/lib/entities/partial/prerequisites/single/primaryAttribute.d.ts +3 -3
  42. package/lib/entities/partial/prerequisites/single/primaryAttribute.js +1 -1
  43. package/lib/entities/partial/prerequisites/single/profession.d.ts +4 -4
  44. package/lib/entities/partial/prerequisites/single/publication.d.ts +4 -4
  45. package/lib/entities/partial/prerequisites/single/race.d.ts +4 -4
  46. package/lib/entities/partial/prerequisites/single/rated.d.ts +2 -2
  47. package/lib/entities/partial/prerequisites/single/rated.js +2 -2
  48. package/lib/entities/partial/prerequisites/single/ratedMinimumNumber.d.ts +3 -3
  49. package/lib/entities/partial/prerequisites/single/ratedMinimumNumber.js +1 -1
  50. package/lib/entities/partial/prerequisites/single/ratedSum.d.ts +4 -4
  51. package/lib/entities/partial/prerequisites/single/rule.d.ts +3 -3
  52. package/lib/entities/partial/prerequisites/single/sex.d.ts +1 -1
  53. package/lib/entities/partial/prerequisites/single/sexualCharacteristic.d.ts +3 -3
  54. package/lib/entities/partial/prerequisites/single/socialStatus.d.ts +3 -3
  55. package/lib/entities/partial/prerequisites/single/state.d.ts +4 -4
  56. package/lib/entities/partial/prerequisites/single/text.d.ts +3 -3
  57. package/lib/entities/partial/rated/activatable/castingTime.d.ts +1 -1
  58. package/lib/entities/partial/rated/activatable/castingTime.js +6 -8
  59. package/lib/entities/partial/rated/activatable/cost.d.ts +1 -1
  60. package/lib/entities/partial/rated/activatable/cost.js +17 -14
  61. package/lib/entities/partial/rated/activatable/duration.js +4 -4
  62. package/lib/entities/partial/rated/activatable/effect.js +1 -1
  63. package/lib/entities/partial/rated/activatable/isMinimumMaximum.js +2 -2
  64. package/lib/entities/partial/rated/activatable/range.js +6 -6
  65. package/lib/entities/partial/rated/activatable/speed.d.ts +1 -1
  66. package/lib/entities/partial/rated/activatable/targetCategory.js +2 -2
  67. package/lib/entities/partial/reader.d.ts +4 -2
  68. package/lib/entities/partial/responsiveText.d.ts +5 -1
  69. package/lib/entities/partial/responsiveText.js +3 -5
  70. package/lib/entities/partial/units/length.d.ts +1 -1
  71. package/lib/entities/partial/units/length.js +2 -10
  72. package/lib/entities/partial/units/timeSpan.d.ts +4 -4
  73. package/lib/entities/partial/units/timeSpan.js +21 -17
  74. package/lib/entities/personalityTrait.js +3 -4
  75. package/lib/entities/poison.js +4 -4
  76. package/lib/entities/profession.d.ts +1 -1
  77. package/lib/entities/profession.js +19 -14
  78. package/lib/entities/race.js +11 -13
  79. package/lib/entities/sexPractice.js +1 -3
  80. package/lib/entities/skill.js +2 -3
  81. package/lib/entities/spell.js +14 -4
  82. package/lib/helpers/getTypes.d.ts +2 -2
  83. package/lib/helpers/locale.d.ts +8 -2
  84. package/lib/helpers/translate.d.ts +1 -1
  85. package/lib/index.d.ts +11 -2
  86. package/lib/index.js +5 -0
  87. package/lib/references/index.d.ts +1 -1
  88. package/lib/references/index.js +1 -1
  89. package/lib/references/page.d.ts +3 -3
  90. package/lib/references/page.js +1 -1
  91. package/lib/references/pageRange.d.ts +2 -2
  92. package/lib/tsconfig.tsbuildinfo +1 -1
  93. package/package.json +4 -3
package/CHANGELOG.md CHANGED
@@ -2,6 +2,42 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
4
4
 
5
+ ## [0.4.1](https://github.com/Optolith/entity-descriptions/compare/v0.4.0...v0.4.1) (2026-03-13)
6
+
7
+
8
+ ### Features
9
+
10
+ * apply map style ([414685f](https://github.com/Optolith/entity-descriptions/commit/414685f574e9669943f700564d36a27bd3711ddb))
11
+ * focus rule badge ([c76c4cd](https://github.com/Optolith/entity-descriptions/commit/c76c4cd0c33e0187b080f7a8afb04a99cad6785c))
12
+ * interval style time span as unit ([0c772d1](https://github.com/Optolith/entity-descriptions/commit/0c772d1cc2d65bd97804fe5a96137f4f1a601a02))
13
+ * use more compact diff for activatable skill parameters ([70d7082](https://github.com/Optolith/entity-descriptions/commit/70d70823c24f81a47dbb8f7ced23819464926067))
14
+
15
+
16
+ ### Bug Fixes
17
+
18
+ * do not use typographic formatter for multi-word cost ([fecbae5](https://github.com/Optolith/entity-descriptions/commit/fecbae5bbbccebc45660dbbb6121c8169fce1747))
19
+ * do not wrap maximum duration if replaced ([bac8a49](https://github.com/Optolith/entity-descriptions/commit/bac8a493b2a6d5cf08ab9d461e7f84f8c83c1788))
20
+ * min/max wrapping ([9a435e2](https://github.com/Optolith/entity-descriptions/commit/9a435e25979dea9810332c51a763e093ea403dfb))
21
+ * missing profession specification in title ([2a97e7e](https://github.com/Optolith/entity-descriptions/commit/2a97e7e1f5b8d91a8ed27ac3545c1691d0217aa1))
22
+ * use abbreviated attribute name in prerequisites ([1b2d21b](https://github.com/Optolith/entity-descriptions/commit/1b2d21ba68964ad7dfb542d249842cbc9bea5fa7))
23
+ * use duration level translation if present ([a7790d6](https://github.com/Optolith/entity-descriptions/commit/a7790d68f68fc5762a954c1703a7f6b4fb3dfe07))
24
+
25
+ ## [0.4.0](https://github.com/Optolith/entity-descriptions/compare/v0.3.2...v0.4.0) (2026-03-12)
26
+
27
+
28
+ ### ⚠ BREAKING CHANGES
29
+
30
+ * require external date formatter
31
+
32
+ ### Features
33
+
34
+ * export helpful values and types ([5e73ce0](https://github.com/Optolith/entity-descriptions/commit/5e73ce0cb8098df2919694c72c835134bf520aea))
35
+
36
+
37
+ ### Bug Fixes
38
+
39
+ * require external date formatter ([9cdf694](https://github.com/Optolith/entity-descriptions/commit/9cdf69443781203c86256dea85918960b1f17c9c))
40
+
5
41
  ## [0.3.2](https://github.com/Optolith/entity-descriptions/compare/v0.3.1...v0.3.2) (2026-03-12)
6
42
 
7
43
 
package/lib/creator.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { EntityMap } from "optolith-database-schema/gen";
2
2
  import type { GetInstanceById } from "./helpers/getTypes.js";
3
- import { LocaleEnvironment } from "./helpers/locale.js";
4
- import { EntityDescription, RawEntityDescription } from "./index.js";
3
+ import type { LocaleEnvironment } from "./helpers/locale.js";
4
+ import type { EntityDescription, RawEntityDescription } from "./index.js";
5
5
  /**
6
6
  * A union type of entities with their names as a discriminant property.
7
7
  */
package/lib/creator.js CHANGED
@@ -62,7 +62,7 @@ export const createEntityDescriptionCreator = (fn) => (databaseAccessors, locale
62
62
  ...rawEntry,
63
63
  body: rawEntry.body.filter(isNotNullish).map(mapRawSection),
64
64
  errata: rawEntry.errata?.map(({ date, description }) => ({
65
- date: date.toLocaleDateString(locale.id),
65
+ date: locale.formatDate(date),
66
66
  description: description.trim(),
67
67
  })),
68
68
  references: rawEntry.references === undefined
@@ -20,7 +20,8 @@ const renderPropertyValue = (getInstanceById, translate, translateMap, propertyD
20
20
  case "DependingOnSelection":
21
21
  return translate("As chosen");
22
22
  case "Fixed":
23
- return (translateMap(getInstanceById("Property", propertyDecl.Fixed)?.translations)?.name ?? MISSING_VALUE);
23
+ return (translateMap(getInstanceById("Property", propertyDecl.Fixed)?.translations)?.name ??
24
+ MISSING_VALUE);
24
25
  default:
25
26
  return assertExhaustive(propertyDecl);
26
27
  }
@@ -40,20 +41,14 @@ const renderPenaltyValue = (getInstanceById, translate, translateMap, name, pena
40
41
  switch (penalty.kind) {
41
42
  case "Single":
42
43
  return (sign(penalty.Single.value) +
43
- (penalty.Single.applies_to_parry === true
44
- ? ` (${translate("for parry")})`
45
- : ""));
44
+ (penalty.Single.applies_to_parry === true ? ` (${translate("for parry")})` : ""));
46
45
  case "ByHandedness": {
47
- const appendParry = penalty.ByHandedness.applies_to_parry === true
48
- ? `; ${translate("for parry")}`
49
- : "";
46
+ const appendParry = penalty.ByHandedness.applies_to_parry === true ? `; ${translate("for parry")}` : "";
50
47
  return `${sign(penalty.ByHandedness.one_handed)} (${translate("one-handed weapon") + appendParry}); ${sign(penalty.ByHandedness.two_handed)} (${translate("two-handed weapon") + appendParry})`;
51
48
  }
52
49
  case "ByActivation": {
53
50
  return `${sign(penalty.ByActivation.active)}/${sign(penalty.ByActivation.inactive)} (${[
54
- penalty.ByActivation.applies_to_parry === true
55
- ? translate("for parry")
56
- : undefined,
51
+ penalty.ByActivation.applies_to_parry === true ? translate("for parry") : undefined,
57
52
  translate("for secondary fighters with/without special ability {$name}", { name }),
58
53
  ]
59
54
  .filter(isNotNullish)
@@ -62,9 +57,7 @@ const renderPenaltyValue = (getInstanceById, translate, translateMap, name, pena
62
57
  case "Selection":
63
58
  switch (penalty.Selection.options.kind) {
64
59
  case "Specific":
65
- return penalty.Selection.options.Specific.list
66
- .map(option => sign(option.value))
67
- .join("/");
60
+ return penalty.Selection.options.Specific.list.map(option => sign(option.value)).join("/");
68
61
  case "Range":
69
62
  return translate("{$start} to {$end}", {
70
63
  start: sign(penalty.Selection.options.Range.minimum),
@@ -81,8 +74,8 @@ const renderPenaltyValue = (getInstanceById, translate, translateMap, name, pena
81
74
  if (external === undefined) {
82
75
  return main;
83
76
  }
84
- const externalName = translateMap(getInstanceById(external.kind, fromUniformCase(external))
85
- ?.translations)?.name ?? MISSING_VALUE;
77
+ const externalName = translateMap(getInstanceById(external.kind, fromUniformCase(external))?.translations)
78
+ ?.name ?? MISSING_VALUE;
86
79
  return `${main} (${translate("depending on the level of the special ability {$name}", {
87
80
  name: externalName,
88
81
  })})`;
@@ -90,7 +83,7 @@ const renderPenaltyValue = (getInstanceById, translate, translateMap, name, pena
90
83
  case "ByAttack": {
91
84
  const offset = penalty.ByAttack.initial_order ?? 1;
92
85
  return penalty.ByAttack.list
93
- .map((penaltyByAttack, index) => `${penaltyByAttack.value} (${renderPenaltyByAttackLabel(translate, penalty.ByAttack.attack_replacement, index + offset)})`)
86
+ .map((penaltyByAttack, index) => `${penaltyByAttack.value.toFixed()} (${renderPenaltyByAttackLabel(translate, penalty.ByAttack.attack_replacement, index + offset)})`)
94
87
  .join("; ");
95
88
  }
96
89
  case "DependsOnHitZone":
@@ -135,18 +128,13 @@ const wrapInParens = (args, append = "") => {
135
128
  }
136
129
  return ` (${filteredArgs.join(", ") + append})`;
137
130
  };
138
- const addSpaceIfNoCommaAtStart = (str) => str.startsWith(",") ? str : ` ${str}`;
131
+ const addSpaceIfNoCommaAtStart = (str) => (str.startsWith(",") ? str : ` ${str}`);
139
132
  const renderApplicableCombatTechniquesRestriction = (getInstanceById, locale, main, restriction, translation, weapons, getExcludedInstance) => {
140
133
  switch (restriction.kind) {
141
134
  case "Improvised":
142
- return (main +
143
- wrapInParens([locale.translate("only improvised weapons"), weapons]));
135
+ return main + wrapInParens([locale.translate("only improvised weapons"), weapons]);
144
136
  case "PointedBlade":
145
- return (main +
146
- wrapInParens([
147
- locale.translate("weapon must have a pointed blade"),
148
- weapons,
149
- ]));
137
+ return main + wrapInParens([locale.translate("weapon must have a pointed blade"), weapons]);
150
138
  case "Mount":
151
139
  if (weapons === undefined) {
152
140
  return `${main} ${locale.translate("while mounted")}`;
@@ -183,23 +171,15 @@ const renderApplicableCombatTechniquesRestriction = (getInstanceById, locale, ma
183
171
  addSpaceIfNoCommaAtStart(locale.translate("that may be performed with one-handed weapons")) +
184
172
  wrapInParens([weapons]));
185
173
  case "TwoHanded":
186
- return (locale.translate("All Two-Handed Weapons") + wrapInParens([weapons]));
174
+ return locale.translate("All Two-Handed Weapons") + wrapInParens([weapons]);
187
175
  case "ParryingWeapon":
188
176
  return locale.translate("All Parrying Weapons") + wrapInParens([weapons]);
189
177
  case "Level": {
190
- const nameWithLevel = `${translation} ${romanize(restriction.Level.level)}`;
191
- return (main +
192
- wrapInParens([
193
- locale.translate("only {$nameWithLevel}", { nameWithLevel }),
194
- weapons,
195
- ]));
178
+ const nameWithLevel = `${translation.name_in_library ?? translation.name} ${romanize(restriction.Level.level)}`;
179
+ return (main + wrapInParens([locale.translate("only {$nameWithLevel}", { nameWithLevel }), weapons]));
196
180
  }
197
181
  case "OneBluntSide":
198
- return (main +
199
- wrapInParens([
200
- locale.translate("only those with at least one blunt side"),
201
- weapons,
202
- ]));
182
+ return (main + wrapInParens([locale.translate("only those with at least one blunt side"), weapons]));
203
183
  default:
204
184
  return assertExhaustive(restriction);
205
185
  }
@@ -242,8 +222,8 @@ const renderApplicableCombatTechniquesValue = (getInstanceById, locale, translat
242
222
  ? undefined
243
223
  : locale.translate("only {$weapons}", {
244
224
  weapons: locale.join(specific.weapons
245
- .map(weapon => locale.translateMap(getInstanceById("Weapon", weapon)
246
- ?.translations)?.name ?? MISSING_VALUE)
225
+ .map(weapon => locale.translateMap(getInstanceById("Weapon", weapon)?.translations)
226
+ ?.name ?? MISSING_VALUE)
247
227
  .toSorted(locale.compare), "conjunction"),
248
228
  }), undefined);
249
229
  return mainWithRestriction;
@@ -272,9 +252,7 @@ const renderVolumeValue = (translate, translateMap, getAllResolvedSelectOptions,
272
252
  case "ByLevel":
273
253
  return translate("{$points} points for levels {$levels}", {
274
254
  points: volume.ByLevel.list.map(item => item.points).join("/"),
275
- levels: volume.ByLevel.list
276
- .map((_, index) => romanize(index + 1))
277
- .join("/"),
255
+ levels: volume.ByLevel.list.map((_, index) => romanize(index + 1)).join("/"),
278
256
  });
279
257
  case "Map":
280
258
  return renderResponsiveMap(volume.Map, option => option.points, values => translate("{$points} points", { points: values })).run({
@@ -292,8 +270,7 @@ const renderVolumeValue = (translate, translateMap, getAllResolvedSelectOptions,
292
270
  points: groups.map(group => group[0]).join("/"),
293
271
  })} ${translate("for")} ${groups
294
272
  .map(group => group[1]
295
- .map(groupItem => translateMap(groupItem.content.translations)?.name ??
296
- MISSING_VALUE)
273
+ .map(groupItem => translateMap(groupItem.content.translations)?.name ?? MISSING_VALUE)
297
274
  .join(", "))
298
275
  .join(separator)}`;
299
276
  }
@@ -320,7 +297,7 @@ const renderArcaneEnergyCost = (translate, translateMap, localeJoin, responsiveT
320
297
  if (cost.Fixed.per_level === undefined) {
321
298
  return prev => prev(cost.Fixed.value);
322
299
  }
323
- switch (cost.Fixed.per_level?.kind) {
300
+ switch (cost.Fixed.per_level.kind) {
324
301
  case "Compressed":
325
302
  return prev => translate("{$cost} per level", { cost: prev(cost.Fixed.value) });
326
303
  case "Verbose":
@@ -333,8 +310,7 @@ const renderArcaneEnergyCost = (translate, translateMap, localeJoin, responsiveT
333
310
  }
334
311
  })();
335
312
  const noteInParens = parensIf(mapNullable(translateMap(cost.Fixed.translations)?.note, note => getResponsiveTextOptional(note, responsiveTextSize)));
336
- return (wrapInPerLevel(value => wrapInInterval(translationWrapper(value))) +
337
- noteInParens);
313
+ return wrapInPerLevel(value => wrapInInterval(translationWrapper(value))) + noteInParens;
338
314
  }
339
315
  case "Constant":
340
316
  return (translate("{$value} AE", { value: cost.Constant.value }) +
@@ -369,11 +345,11 @@ const renderArcaneEnergyCost = (translate, translateMap, localeJoin, responsiveT
369
345
  interval: formatTimeSpan(translate, responsiveTextSize, cost.ActivationAndHalfInterval.interval.unit, cost.ActivationAndHalfInterval.interval.value),
370
346
  }));
371
347
  case "Indefinite":
372
- return (mapNullable(translateMap(cost.Indefinite.translations)?.description, description => getResponsiveText(description, responsiveTextSize)) +
348
+ return ((mapNullable(translateMap(cost.Indefinite.translations)?.description, description => getResponsiveText(description, responsiveTextSize)) ?? MISSING_VALUE) +
373
349
  mapNullableDefault(cost.Indefinite.modifier, modifier => ` + ${translate("{$value} AE", { value: modifier.value })}`, ""));
374
350
  case "Disjunction":
375
351
  return translate("{$value} AE", {
376
- value: localeJoin(cost.Disjunction.options.map(option => option.value +
352
+ value: localeJoin(cost.Disjunction.options.map(option => option.value.toFixed() +
377
353
  mapNullableDefault(translateMap(option.translations)?.note, note => parensIf(getResponsiveTextOptional(note, responsiveTextSize)), "")), "disjunction"),
378
354
  });
379
355
  case "Map":
@@ -382,6 +358,7 @@ const renderArcaneEnergyCost = (translate, translateMap, localeJoin, responsiveT
382
358
  surround: values => translate(", {$value} of which are permanent", {
383
359
  value: values,
384
360
  }),
361
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- is checked beforehand
385
362
  getAdditionalValue: option => option.permanent_value,
386
363
  }
387
364
  : undefined).run({
@@ -442,8 +419,7 @@ const renderBindingCost = (translate, translateMap, localeCompare, getAllResolve
442
419
  value: groups.map(group => group[0]).join("/"),
443
420
  })} ${translate("for")} ${groups
444
421
  .map(group => group[1]
445
- .map(groupItem => translateMap(groupItem.content.translations)?.name ??
446
- MISSING_VALUE)
422
+ .map(groupItem => translateMap(groupItem.content.translations)?.name ?? MISSING_VALUE)
447
423
  .toSorted(localeCompare)
448
424
  .join(", "))
449
425
  .join(separator)}`;
@@ -471,8 +447,7 @@ const renderCost = (translate, translateMap, localeJoin, localeCompare, getAllRe
471
447
  label: translate("AE Cost"),
472
448
  value: cost.kind === "ArcaneEnergyCost"
473
449
  ? "ae_cost" in cost.ArcaneEnergyCost
474
- ? renderArcaneEnergyCost(translate, translateMap, localeJoin, responsiveTextSize, levels, cost.ArcaneEnergyCost.ae_cost) +
475
- renderLifePointsCost(translate, cost.ArcaneEnergyCost.lp_cost)
450
+ ? renderArcaneEnergyCost(translate, translateMap, localeJoin, responsiveTextSize, levels, cost.ArcaneEnergyCost.ae_cost) + renderLifePointsCost(translate, cost.ArcaneEnergyCost.lp_cost)
476
451
  : renderArcaneEnergyCost(translate, translateMap, localeJoin, responsiveTextSize, levels, cost.ArcaneEnergyCost)
477
452
  : renderArcaneEnergyCost(translate, translateMap, localeJoin, responsiveTextSize, levels, cost),
478
453
  };
@@ -488,7 +463,7 @@ const renderCost = (translate, translateMap, localeJoin, localeCompare, getAllRe
488
463
  /**
489
464
  * Get a JSON representation of the rules text for a special ability.
490
465
  */
491
- export const getActivatableEntityDescription = createEntityDescriptionCreator(({ getInstanceById, getAllInstances, getResolvedSelectOptionById, getAllResolvedSelectOptions, }, locale, { entity: entityName, content: entry, id }) => {
466
+ export const getActivatableEntityDescription = createEntityDescriptionCreator(({ getInstanceById, getAllInstances, getResolvedSelectOptionById, getAllResolvedSelectOptions }, locale, { entity: entityName, content: entry, id }) => {
492
467
  const { translate, translateMap } = locale;
493
468
  const translation = translateMap(entry.translations);
494
469
  const responsiveTextSize = ResponsiveTextSize.Full;
@@ -500,9 +475,7 @@ export const getActivatableEntityDescription = createEntityDescriptionCreator(({
500
475
  return {
501
476
  title: translation.name_in_library ??
502
477
  translation.name +
503
- (baseEntry.levels !== undefined
504
- ? ` I–${romanize(baseEntry.levels)}`
505
- : ""),
478
+ (baseEntry.levels !== undefined ? ` I–${romanize(baseEntry.levels)}` : ""),
506
479
  subtitle: mapNullable(baseEntry.usage_type, usageType => {
507
480
  switch (usageType.kind) {
508
481
  case "Passive":
@@ -546,8 +519,8 @@ export const getActivatableEntityDescription = createEntityDescriptionCreator(({
546
519
  })),
547
520
  mapNullable(baseEntry.aspect, aspect => ({
548
521
  label: translate("Aspect"),
549
- value: translateMap(getInstanceById("Aspect", aspect)?.translations)
550
- ?.name ?? MISSING_VALUE,
522
+ value: translateMap(getInstanceById("Aspect", aspect)?.translations)?.name ??
523
+ MISSING_VALUE,
551
524
  })),
552
525
  mapNullable(translation.range, range => ({
553
526
  label: translate("Range"),
@@ -559,8 +532,7 @@ export const getActivatableEntityDescription = createEntityDescriptionCreator(({
559
532
  })),
560
533
  mapNullable(entry.prerequisites, prerequisites => ({
561
534
  label: translate("Prerequisites"),
562
- value: wrappedId.kind === "Advantage" ||
563
- wrappedId.kind === "Disadvantage"
535
+ value: wrappedId.kind === "Advantage" || wrappedId.kind === "Disadvantage"
564
536
  ? printAdvantageDisadvantagePrerequisites(getInstanceById, getResolvedSelectOptionById, locale, prerequisites, translation.name, wrappedId.kind)
565
537
  : printGeneralPrerequisites(getInstanceById, getResolvedSelectOptionById, locale, prerequisites, mapNullable(baseEntry.levels, levels => ({
566
538
  id: wrappedId,
@@ -581,12 +553,11 @@ export const getActivatableEntityDescription = createEntityDescriptionCreator(({
581
553
  value: renderPropertyValue(getInstanceById, translate, translateMap, property),
582
554
  })),
583
555
  mapNullable(entry.ap_value, apValue => {
584
- const append = translation.ap_value_append !== undefined
585
- ? ` ${translation.ap_value_append}`
586
- : "";
556
+ const append = translation.ap_value_append !== undefined ? ` ${translation.ap_value_append}` : "";
587
557
  return {
588
558
  label: translate("AP Value"),
589
- value: renderAdventurePointsValue(locale, activatableId => locale.translateMap(getInstanceById(activatableId.kind, fromUniformCase(activatableId))?.translations)?.name, selectOptionId => {
559
+ value: renderAdventurePointsValue(locale, activatableId => locale.translateMap(getInstanceById(activatableId.kind, fromUniformCase(activatableId))
560
+ ?.translations)?.name, selectOptionId => {
590
561
  const selectOption = getResolvedSelectOptionById(wrappedId, selectOptionId);
591
562
  if (selectOption === undefined) {
592
563
  return undefined;
@@ -7,19 +7,21 @@ import { isNotNullish, mapNullable } from "@elyukai/utils/nullable";
7
7
  import { Reader } from "@elyukai/utils/reader";
8
8
  import { sign } from "@elyukai/utils/string/number";
9
9
  import { assertExhaustive } from "@elyukai/utils/typeSafety";
10
+ import { getAdventurePointsForRatingRange } from "@optolith/adventure-points/improvement-cost";
10
11
  import { createEntityDescriptionCreator } from "../creator.js";
11
12
  import { renderCommonnessRatedAdvantagesOrDisadvantages, renderValueWithPossibleTranslation, } from "./partial/commonnessRatedAdvantagesAndDisadvantages.js";
12
13
  import { getProfessionName } from "./partial/professions.js";
13
14
  import { parensIf } from "./partial/rated/activatable/parensIf.js";
14
- import { translateR, } from "./partial/reader.js";
15
+ import { translateR } from "./partial/reader.js";
15
16
  import { MISSING_VALUE } from "./partial/unknown.js";
16
- import { getAdventurePointsForRatingRange } from "@optolith/adventure-points/improvement-cost";
17
17
  const renderListOperation = (operation, list) => {
18
18
  switch (operation.kind) {
19
19
  case "Intersection":
20
20
  return Reader.of(list.join(", "));
21
21
  case "Difference":
22
- return translateR("all but {$excludedProfessions :list type=conjunction}", { excludedProfessions: list });
22
+ return translateR("all but {$excludedProfessions :list type=conjunction}", {
23
+ excludedProfessions: list,
24
+ });
23
25
  default:
24
26
  return assertExhaustive(operation);
25
27
  }
@@ -49,10 +51,7 @@ const renderWeighted = (weightedVariants, getName) => {
49
51
  return Reader.of(undefined);
50
52
  }
51
53
  return Reader.asks(({ translate, localeCompare }) => {
52
- const variants = ensureNonEmpty(weightedVariants.elements
53
- .map(getName)
54
- .filter(isNotNullish)
55
- .toSorted(localeCompare));
54
+ const variants = ensureNonEmpty(weightedVariants.elements.map(getName).filter(isNotNullish).toSorted(localeCompare));
56
55
  if (variants === undefined) {
57
56
  return undefined;
58
57
  }
@@ -74,10 +73,9 @@ const renderProfessionConstraint = (getChildInstancesForInstanceId, constraint)
74
73
  ? Reader.of(undefined)
75
74
  : Reader.sequence([
76
75
  renderRarity(constraint.rarity),
77
- Reader.asks(({ translateMap, getInstanceById, }) => renderWeighted(constraint.weighted_variants, variantId => translateMap(getInstanceById("ProfessionVariant", variantId)
78
- ?.translations)?.name.default)).thenW(identity),
79
- ]).map(notes => baseName +
80
- parensIf(ensureNonEmpty(notes.filter(isNotNullish))?.join("; "))));
76
+ Reader.asks(({ translateMap, getInstanceById }) => renderWeighted(constraint.weighted_variants, variantId => translateMap(getInstanceById("ProfessionVariant", variantId)?.translations)
77
+ ?.name.default)).thenW(identity),
78
+ ]).map(notes => baseName + parensIf(ensureNonEmpty(notes.filter(isNotNullish))?.join("; "))));
81
79
  const renderTraditionConstraint = (getChildInstancesForInstanceId, entity, constraint) => Reader.asks(({ translateMap, getInstanceById }) => {
82
80
  const translation = translateMap(getInstanceById(entity, constraint.id)?.translations);
83
81
  return translation?.nameOfBlessedOnes ?? translation?.name;
@@ -86,8 +84,7 @@ const renderTraditionConstraint = (getChildInstancesForInstanceId, entity, const
86
84
  : Reader.sequence([
87
85
  renderRarity(constraint.rarity),
88
86
  Reader.asks(({ translateMap }) => renderWeighted(constraint.weighted_professions, profId => getProfessionName(translateMap, getChildInstancesForInstanceId, profId))).thenW(identity),
89
- ]).map(notes => baseName +
90
- parensIf(ensureNonEmpty(notes.filter(isNotNullish))?.join("; "))));
87
+ ]).map(notes => baseName + parensIf(ensureNonEmpty(notes.filter(isNotNullish))?.join("; "))));
91
88
  const renderCommonProfessions = (getChildInstancesForInstanceId, commonProfessions) => {
92
89
  switch (commonProfessions.kind) {
93
90
  case "Plain":
@@ -121,8 +118,7 @@ const renderCommonProfessions = (getChildInstancesForInstanceId, commonProfessio
121
118
  const renderCommonSkills = (items) => Reader.asks(({ translate, translateMap, getInstanceById, localeCompare }) => items === undefined || !isNotEmpty(items)
122
119
  ? translate("none")
123
120
  : items
124
- .map(itemId => translateMap(getInstanceById("Skill", itemId)?.translations)
125
- ?.name)
121
+ .map(itemId => translateMap(getInstanceById("Skill", itemId)?.translations)?.name)
126
122
  .filter(isNotNullish)
127
123
  .toSorted(localeCompare)
128
124
  .join(", "));
@@ -161,7 +157,7 @@ const renderCulturalPackage = (items) => Reader.asks(({ translate, translateMap,
161
157
  return [MISSING_VALUE, 0];
162
158
  }
163
159
  return [
164
- `${instanceTranslation.name ?? MISSING_VALUE} ${sign(item.points)}`,
160
+ `${instanceTranslation.name} ${sign(item.points)}`,
165
161
  getAdventurePointsForRatingRange(instance.improvement_cost.kind, 0, item.points),
166
162
  ];
167
163
  });
@@ -201,8 +197,7 @@ export const getCultureEntityDescription = createEntityDescriptionCreator(({ get
201
197
  .map(lang => {
202
198
  const language = getInstanceById("Language", lang.id);
203
199
  const languageTranslation = translateMap(language?.translations);
204
- if (language === undefined ||
205
- languageTranslation === undefined) {
200
+ if (language === undefined || languageTranslation === undefined) {
206
201
  return MISSING_VALUE;
207
202
  }
208
203
  return (languageTranslation.name +
@@ -224,8 +219,7 @@ export const getCultureEntityDescription = createEntityDescriptionCreator(({ get
224
219
  const processedScripts = entry.script.map((scriptId) => {
225
220
  const script = getInstanceById("Script", scriptId);
226
221
  const scriptTranslation = translateMap(script?.translations);
227
- if (script === undefined ||
228
- scriptTranslation === undefined) {
222
+ if (script === undefined || scriptTranslation === undefined) {
229
223
  return [MISSING_VALUE, 0];
230
224
  }
231
225
  return [scriptTranslation.name, script.ap_value ?? 0];
@@ -234,9 +228,7 @@ export const getCultureEntityDescription = createEntityDescriptionCreator(({ get
234
228
  return translate("none");
235
229
  }
236
230
  if (allSame(processedScripts, on(([, apValue]) => apValue, equal))) {
237
- return (localeJoin(processedScripts
238
- .map(([name]) => name)
239
- .toSorted(localeCompare), "disjunction") +
231
+ return (localeJoin(processedScripts.map(([name]) => name).toSorted(localeCompare), "disjunction") +
240
232
  parensIf(translate("{$value} AP", {
241
233
  value: processedScripts[0][1],
242
234
  })));
@@ -265,7 +257,8 @@ export const getCultureEntityDescription = createEntityDescriptionCreator(({ get
265
257
  {
266
258
  label: translate("Social Status"),
267
259
  value: entry.social_status
268
- .map(status => translateMap(getInstanceById("SocialStatus", status)?.translations)?.name ?? MISSING_VALUE)
260
+ .map(status => translateMap(getInstanceById("SocialStatus", status)?.translations)?.name ??
261
+ MISSING_VALUE)
269
262
  .toSorted(localeCompare)
270
263
  .join(", "),
271
264
  },
@@ -295,8 +288,7 @@ export const getCultureEntityDescription = createEntityDescriptionCreator(({ get
295
288
  type: "labeled",
296
289
  label: translate("Cultural Package {$cultureName}", {
297
290
  cultureName: translation.name,
298
- }) +
299
- parensIf(translate("{$value} AP", { value: culturalPackageApValue })),
291
+ }) + parensIf(translate("{$value} AP", { value: culturalPackageApValue })),
300
292
  value: {
301
293
  type: "plain",
302
294
  text: culturePackageText,
@@ -19,8 +19,8 @@ const renderElectiveSpellworks = (translate, translateMap, localeCompare, getIns
19
19
  return name;
20
20
  }
21
21
  return (name +
22
- parensIf(translateMap(getInstanceById("Element", item.restriction.Element)
23
- ?.translations)?.name ?? MISSING_VALUE));
22
+ parensIf(translateMap(getInstanceById("Element", item.restriction.Element)?.translations)
23
+ ?.name ?? MISSING_VALUE));
24
24
  }))
25
25
  .filter(isNotNullish)
26
26
  .toSorted(localeCompare)
@@ -32,11 +32,9 @@ const renderElectiveSpellworks = (translate, translateMap, localeCompare, getIns
32
32
  const renderRestrictedSpellworks = (translate, translateMap, localeCompare, localeJoin, getInstanceById, restrictedSpellworks) => {
33
33
  const restrictedGroups = Dictionary.groupBy(restrictedSpellworks, restriction => restriction.kind === "Spellwork"
34
34
  ? "spellwork"
35
- : restriction.kind === "Property" &&
36
- restriction.Property.maximum !== undefined
35
+ : restriction.kind === "Property" && restriction.Property.maximum !== undefined
37
36
  ? "restrictedProperty"
38
- : restriction.kind === "Property" ||
39
- restriction.kind === "DemonSummoning"
37
+ : restriction.kind === "Property" || restriction.kind === "DemonSummoning"
40
38
  ? "property"
41
39
  : "other");
42
40
  const addExclusion = (excludedSpellworks) => {
@@ -55,16 +53,16 @@ const renderRestrictedSpellworks = (translate, translateMap, localeCompare, loca
55
53
  };
56
54
  const restrictedProperties = restrictedGroups.get("restrictedProperty")?.map(restriction => translate(".input {$count :number} {{only {$count} additional spellworks with the Property {$property}}}", {
57
55
  count: restriction.Property.maximum,
58
- property: translateMap(getInstanceById("Property", restriction.Property.id)
59
- ?.translations)?.name ?? MISSING_VALUE,
56
+ property: translateMap(getInstanceById("Property", restriction.Property.id)?.translations)
57
+ ?.name ?? MISSING_VALUE,
60
58
  }) + addExclusion(restriction.Property.exclude)) ?? [];
61
59
  const propertyGroup = restrictedGroups.get("property");
62
60
  const translatedProperties = propertyGroup
63
61
  ?.map(restriction => {
64
62
  switch (restriction.kind) {
65
63
  case "Property":
66
- return translateMap(getInstanceById("Property", restriction.Property.id)
67
- ?.translations)?.name;
64
+ return translateMap(getInstanceById("Property", restriction.Property.id)?.translations)
65
+ ?.name;
68
66
  case "DemonSummoning":
69
67
  return translate("Demon Summoning");
70
68
  default:
@@ -82,8 +80,7 @@ const renderRestrictedSpellworks = (translate, translateMap, localeCompare, loca
82
80
  : [];
83
81
  const spellworkGroup = restrictedGroups.get("spellwork");
84
82
  const translatedSpellworks = spellworkGroup
85
- ?.map(restriction => translateMap(getInstanceById(restriction.Spellwork)?.translations)
86
- ?.name)
83
+ ?.map(restriction => translateMap(getInstanceById(restriction.Spellwork)?.translations)?.name)
87
84
  .filter(isNotNullish)
88
85
  .toSorted(localeCompare) ?? [];
89
86
  const spellworks = translatedSpellworks.length > 0
@@ -104,29 +101,26 @@ const renderRestrictedSpellworks = (translate, translateMap, localeCompare, loca
104
101
  return assertExhaustive(restriction);
105
102
  }
106
103
  }) ?? [];
107
- return [
108
- ...restrictedProperties,
109
- ...properties,
110
- ...others,
111
- ...spellworks,
112
- ].join("; ");
104
+ return [...restrictedProperties, ...properties, ...others, ...spellworks].join("; ");
113
105
  };
114
- const renderSpellworkAdjustment = (translateMap, getInstanceById, adjustment) => `${translateMap(getInstanceById(adjustment.id)?.translations)?.name ??
115
- MISSING_VALUE} ${adjustment.points}${parensIf(adjustment.tradition === undefined
106
+ const renderSpellworkAdjustment = (translateMap, getInstanceById, adjustment) => `${translateMap(getInstanceById(adjustment.id)?.translations)?.name ?? MISSING_VALUE} ${adjustment.points.toFixed()}${parensIf(adjustment.tradition === undefined
116
107
  ? undefined
117
- : translateMap(getInstanceById("MagicalTradition", adjustment.tradition)
118
- ?.translations)?.name)}`;
108
+ : translateMap(getInstanceById("MagicalTradition", adjustment.tradition)?.translations)?.name)}`;
119
109
  const renderAbilityAdjustmentName = (translateMap, getInstanceById, abilityAdjustment) => {
120
110
  switch (abilityAdjustment.kind) {
121
111
  case "Skill":
122
- return (translateMap(getInstanceById("Skill", abilityAdjustment.Skill.id)?.translations)?.name ?? MISSING_VALUE);
112
+ return (translateMap(getInstanceById("Skill", abilityAdjustment.Skill.id)?.translations)?.name ??
113
+ MISSING_VALUE);
123
114
  case "CombatTechnique":
124
- return (translateMap(getInstanceById(abilityAdjustment.CombatTechnique.id)?.translations)?.name ?? MISSING_VALUE);
115
+ return (translateMap(getInstanceById(abilityAdjustment.CombatTechnique.id)?.translations)?.name ??
116
+ MISSING_VALUE);
125
117
  case "Spellwork":
126
- return ((translateMap(getInstanceById(abilityAdjustment.Spellwork.id)?.translations)?.name ?? MISSING_VALUE) +
118
+ return ((translateMap(getInstanceById(abilityAdjustment.Spellwork.id)?.translations)?.name ??
119
+ MISSING_VALUE) +
127
120
  parensIf(abilityAdjustment.Spellwork.tradition === undefined
128
121
  ? undefined
129
- : translateMap(getInstanceById("MagicalTradition", abilityAdjustment.Spellwork.tradition)?.translations)?.name));
122
+ : translateMap(getInstanceById("MagicalTradition", abilityAdjustment.Spellwork.tradition)
123
+ ?.translations)?.name));
130
124
  default:
131
125
  return assertExhaustive(abilityAdjustment);
132
126
  }
@@ -134,7 +128,8 @@ const renderAbilityAdjustmentName = (translateMap, getInstanceById, abilityAdjus
134
128
  const renderAbilityAdjustmentBaseValue = (baseProfessionPackage, abilityAdjustment) => {
135
129
  switch (abilityAdjustment.kind) {
136
130
  case "Skill":
137
- return baseProfessionPackage.skills?.find(skill => skill.id === abilityAdjustment.Skill.id)?.rating_modifier;
131
+ return baseProfessionPackage.skills?.find(skill => skill.id === abilityAdjustment.Skill.id)
132
+ ?.rating_modifier;
138
133
  case "CombatTechnique":
139
134
  return baseProfessionPackage.combat_techniques?.find(skill => deepEqual(skill.id, abilityAdjustment.CombatTechnique.id))?.rating_modifier;
140
135
  case "Spellwork":
@@ -171,16 +166,17 @@ const renderAbilityAdjustmentDefaultValue = (abilityAdjustment) => {
171
166
  const renderAbilityAdjustment = (translate, translateMap, getInstanceById, baseProfessionPackage) => baseProfessionPackage === undefined
172
167
  ? abilityAdjustment => `${renderAbilityAdjustmentName(translateMap, getInstanceById, abilityAdjustment)} ${sign(renderAbilityAdjustmentModifierValue(abilityAdjustment))}`
173
168
  : abilityAdjustment => {
174
- const basePoints = (renderAbilityAdjustmentBaseValue(baseProfessionPackage, abilityAdjustment) ?? 0) + renderAbilityAdjustmentDefaultValue(abilityAdjustment);
169
+ const basePoints = (renderAbilityAdjustmentBaseValue(baseProfessionPackage, abilityAdjustment) ?? 0) +
170
+ renderAbilityAdjustmentDefaultValue(abilityAdjustment);
175
171
  return translate("{$replacement} instead of {$base}", {
176
172
  base: basePoints,
177
- replacement: `${renderAbilityAdjustmentName(translateMap, getInstanceById, abilityAdjustment)} ${basePoints + renderAbilityAdjustmentModifierValue(abilityAdjustment)}`,
173
+ replacement: `${renderAbilityAdjustmentName(translateMap, getInstanceById, abilityAdjustment)} ${(basePoints + renderAbilityAdjustmentModifierValue(abilityAdjustment)).toFixed()}`,
178
174
  });
179
175
  };
180
176
  const renderAbilityAdjustments = (translate, translateMap, localeCompare, getInstanceById, baseProfessionPackage, list) => list
181
- ?.map(renderAbilityAdjustment(translate, translateMap, getInstanceById, baseProfessionPackage))
177
+ .map(renderAbilityAdjustment(translate, translateMap, getInstanceById, baseProfessionPackage))
182
178
  .toSorted(localeCompare)
183
- .join(", ") ?? translate("none");
179
+ .join(", ");
184
180
  /**
185
181
  * Get a JSON representation of the rules text for a curriculum.
186
182
  */
@@ -199,7 +195,8 @@ export const getCurriculumEntityDescription = createEntityDescriptionCreator(({
199
195
  items: [
200
196
  {
201
197
  label: translate("Guideline"),
202
- value: translateMap(getInstanceById("Guideline", entry.guideline)?.translations)?.name ?? MISSING_VALUE,
198
+ value: translateMap(getInstanceById("Guideline", entry.guideline)?.translations)?.name ??
199
+ MISSING_VALUE,
203
200
  },
204
201
  {
205
202
  label: translate("Elective Spellworks Package"),
@@ -237,7 +234,7 @@ export const getCurriculumEntityDescription = createEntityDescriptionCreator(({
237
234
  items: [
238
235
  {
239
236
  label: translate("Spellwork Changes"),
240
- value: lessonPackageTranslation?.spellwork_changes ??
237
+ value: lessonPackageTranslation.spellwork_changes ??
241
238
  lessonPackage.content.spellwork_changes
242
239
  ?.map(change => translate("{$replacement} instead of {$base}", {
243
240
  base: renderSpellworkAdjustment(translateMap, getInstanceById, change.base),
@@ -34,7 +34,7 @@ const getRaceBaseValue = (translate, translateMap, getInstanceById, idMap, raceB
34
34
  const renderBaseCalculation = (getInstanceById, translate, translateMap, idMap, calculation, style = "full") => renderMathOperation(calculation, value => {
35
35
  switch (value.kind) {
36
36
  case "Constant":
37
- return value.Constant.toString(10);
37
+ return value.Constant.toFixed();
38
38
  case "Attribute":
39
39
  return getAttribute(getInstanceById, translateMap, value.Attribute, style);
40
40
  case "RaceBaseValue":
@@ -2,7 +2,7 @@ import { ensureNonEmpty } from "@elyukai/utils/array/nonEmpty";
2
2
  import { isNotNullish } from "@elyukai/utils/nullable";
3
3
  import { createEntityDescriptionCreator } from "../creator.js";
4
4
  import { renderAnimalTypesSection } from "./partial/animalTypes.js";
5
- import { renderAlternativeNames, renderChance, renderResistance, } from "./partial/herbary.js";
5
+ import { renderAlternativeNames, renderChance, renderResistance } from "./partial/herbary.js";
6
6
  import { parensIf } from "./partial/rated/activatable/parensIf.js";
7
7
  import { MISSING_VALUE } from "./partial/unknown.js";
8
8
  const renderCauses = (translate, translateMap, causes) => causes
@@ -12,10 +12,7 @@ const renderCauses = (translate, translateMap, causes) => causes
12
12
  return MISSING_VALUE;
13
13
  }
14
14
  return (causeTranslation.name +
15
- parensIf(ensureNonEmpty([
16
- renderChance(translate, translateMap, cause, true),
17
- causeTranslation.note,
18
- ].filter(isNotNullish))?.join("; ")));
15
+ parensIf(ensureNonEmpty([renderChance(translate, translateMap, cause, true), causeTranslation.note].filter(isNotNullish))?.join("; ")));
19
16
  })
20
17
  .join(", ");
21
18
  /**
@@ -35,7 +32,7 @@ export const getDiseaseEntityDescription = createEntityDescriptionCreator(({ get
35
32
  type: "definitionList",
36
33
  items: [
37
34
  renderAlternativeNames(translate, translation.alternative_names),
38
- { label: translate("Level"), value: baseEntry.level.toString() },
35
+ { label: translate("Level"), value: baseEntry.level.toFixed() },
39
36
  { label: translate("Progress"), value: translation.progress },
40
37
  {
41
38
  label: translate("Resistance"),