@optolith/entity-descriptions 0.3.1 → 0.3.2

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/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
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.3.2](https://github.com/Optolith/entity-descriptions/compare/v0.3.1...v0.3.2) (2026-03-12)
6
+
7
+
8
+ ### Features
9
+
10
+ * render enhancements ([5159f07](https://github.com/Optolith/entity-descriptions/commit/5159f07d772e75166dce4298e3817ea2ec61f8e1))
11
+
5
12
  ## [0.3.1](https://github.com/Optolith/entity-descriptions/compare/v0.3.0...v0.3.1) (2026-02-25)
6
13
 
7
14
  ## [0.3.0](https://github.com/Optolith/entity-descriptions/compare/v0.2.1...v0.3.0) (2026-02-25)
@@ -11,9 +11,9 @@ import { createEntityDescriptionCreator } from "../creator.js";
11
11
  import { renderCommonnessRatedAdvantagesOrDisadvantages, renderValueWithPossibleTranslation, } from "./partial/commonnessRatedAdvantagesAndDisadvantages.js";
12
12
  import { getProfessionName } from "./partial/professions.js";
13
13
  import { parensIf } from "./partial/rated/activatable/parensIf.js";
14
- import { calculateAdventurePointsFromImprovementCost } from "./partial/rated/improvementCost.js";
15
14
  import { translateR, } from "./partial/reader.js";
16
15
  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":
@@ -162,7 +162,7 @@ const renderCulturalPackage = (items) => Reader.asks(({ translate, translateMap,
162
162
  }
163
163
  return [
164
164
  `${instanceTranslation.name ?? MISSING_VALUE} ${sign(item.points)}`,
165
- calculateAdventurePointsFromImprovementCost(instance.improvement_cost, [1, item.points]),
165
+ getAdventurePointsForRatingRange(instance.improvement_cost.kind, 0, item.points),
166
166
  ];
167
167
  });
168
168
  return {
@@ -18,7 +18,7 @@ export declare const renderRangedWeapon: <Damage>(translate: Translate, translat
18
18
  /**
19
19
  * Get a JSON representation of the rules text for equipment.
20
20
  */
21
- export declare const getEquipmentEntityDescription: import("../creator.js").EntityDescriptionCreator<"Weapon" | "Armor" | "Ammunition" | "Animal" | "AnimalCare" | "BandageOrRemedy" | "Book" | "CeremonialItem" | "Clothes" | "ClothingPackage" | "Container" | "EquipmentOfBlessedOnes" | "GemOrPreciousStone" | "IlluminationLightSource" | "IlluminationRefillOrSupply" | "Jewelry" | "Laboratory" | "Liebesspielzeug" | "LuxuryGood" | "MagicalArtifact" | "MusicalInstrument" | "Newspaper" | "OrienteeringAid" | "RopeOrChain" | "Stationery" | "ThievesTool" | "ToolOfTheTrade" | "TravelGearOrTool" | "Vehicle" | "WeaponAccessory", {
21
+ export declare const getEquipmentEntityDescription: import("../creator.js").EntityDescriptionCreator<"Weapon" | "Armor" | "Ammunition" | "Animal" | "AnimalCare" | "BandageOrRemedy" | "Book" | "CeremonialItem" | "Clothes" | "ClothingPackage" | "Container" | "EquipmentOfBlessedOnes" | "GemOrPreciousStone" | "IlluminationLightSource" | "IlluminationRefillOrSupply" | "Jewelry" | "Laboratory" | "Liebesspielzeug" | "LuxuryGood" | "MagicalArtifact" | "MusicalInstrument" | "Newspaper" | "OrienteeringAid" | "RopeOrChain" | "Stationery" | "ThievesTool" | "ToolOfTheTrade" | "TravelGearOrTool" | "Vehicle" | "WeaponAccessory" | "WorkingSupernaturalCreature", {
22
22
  getInstanceById: GetInstanceById<"Publication" | "Attribute" | "Reach" | "SocialStatus" | "CloseCombatTechnique" | "RangedCombatTechnique" | "MagicalTradition" | "BlessedTradition" | "DerivedCharacteristic" | "Ammunition" | "Race" | "Culture" | "Profession">;
23
23
  idMap: IdMap;
24
24
  }, import("../index.js").EntityDescription>;
@@ -3,9 +3,9 @@ import { isNotNullish } from "@elyukai/utils/nullable";
3
3
  import { sign } from "@elyukai/utils/string/number";
4
4
  import { assertExhaustive } from "@elyukai/utils/typeSafety";
5
5
  import { mapNullable } from "@optolith/helpers/nullable";
6
- import { createEntityDescriptionCreator, } from "../creator.js";
6
+ import { createEntityDescriptionCreator } from "../creator.js";
7
7
  import { renderDice, renderDiceAndFlat } from "./partial/dice.js";
8
- import { additionFormatter, subtractionFormatter, } from "./partial/mathOperation.js";
8
+ import { additionFormatter, subtractionFormatter } from "./partial/mathOperation.js";
9
9
  import { parensIf } from "./partial/rated/activatable/parensIf.js";
10
10
  import { ResponsiveTextSize } from "./partial/responsiveText.js";
11
11
  import { formatTimeSpan } from "./partial/units/timeSpan.js";
@@ -16,8 +16,8 @@ import { MISSING_VALUE, UNHANDLED_VALUE } from "./partial/unknown.js";
16
16
  export const getEquipmentName = (translate, translateMap, getInstanceById, entry) => {
17
17
  switch (entry.entity) {
18
18
  case "ClothingPackage": {
19
- const socialStatusName = translateMap(getInstanceById("SocialStatus", entry.content.socialStatus)
20
- ?.translations)?.name ?? MISSING_VALUE;
19
+ const socialStatusName = translateMap(getInstanceById("SocialStatus", entry.content.socialStatus)?.translations)
20
+ ?.name ?? MISSING_VALUE;
21
21
  return translate("Clothing Package {$socialStatus}", {
22
22
  socialStatus: socialStatusName,
23
23
  });
@@ -53,8 +53,8 @@ export const getEquipmentName = (translate, translateMap, getInstanceById, entry
53
53
  case "Vehicle":
54
54
  case "Weapon":
55
55
  case "WeaponAccessory":
56
- return (translateMap(entry.content.translations)?.name ??
57
- MISSING_VALUE);
56
+ case "WorkingSupernaturalCreature":
57
+ return translateMap(entry.content.translations)?.name ?? MISSING_VALUE;
58
58
  default:
59
59
  return assertExhaustive(entry);
60
60
  }
@@ -63,24 +63,18 @@ const renderPrimaryAttributeAndDamageThreshold = (translateMap, getInstanceById,
63
63
  if (damageThreshold === undefined) {
64
64
  return "—";
65
65
  }
66
- const getAttrAbbrv = (attrId) => translateMap(getInstanceById("Attribute", attrId)?.translations)
67
- ?.abbreviation ?? MISSING_VALUE;
66
+ const getAttrAbbrv = (attrId) => translateMap(getInstanceById("Attribute", attrId)?.translations)?.abbreviation ?? MISSING_VALUE;
68
67
  switch (damageThreshold.kind) {
69
68
  case "Default":
70
- return `${closeCombatTechnique?.primary_attribute.map(getAttrAbbrv).join("/") ??
71
- MISSING_VALUE} ${damageThreshold.Default.threshold}`;
69
+ return `${closeCombatTechnique?.primary_attribute.map(getAttrAbbrv).join("/") ?? MISSING_VALUE} ${damageThreshold.Default.threshold}`;
72
70
  case "List":
73
71
  if (isNotEmpty(damageThreshold.List.list)) {
74
72
  const { list } = damageThreshold.List;
75
73
  if (list.some(item => item.threshold !== list[0].threshold)) {
76
- return list
77
- .map(item => `${getAttrAbbrv(item.attribute)} ${item.threshold}`)
78
- .join("/");
74
+ return list.map(item => `${getAttrAbbrv(item.attribute)} ${item.threshold}`).join("/");
79
75
  }
80
76
  else {
81
- return `${list
82
- .map(item => getAttrAbbrv(item.attribute))
83
- .join("/")} ${list[0].threshold}`;
77
+ return `${list.map(item => getAttrAbbrv(item.attribute)).join("/")} ${list[0].threshold}`;
84
78
  }
85
79
  }
86
80
  else {
@@ -95,8 +89,7 @@ const renderAttackParryModifier = (attackModifier, parryModifier) => attackModif
95
89
  : `${attackModifier === undefined ? "—" : sign(attackModifier)}/${parryModifier === undefined ? "—" : sign(parryModifier)}`;
96
90
  const renderReach = (translateMap, getInstanceById, reach) => reach === undefined
97
91
  ? "—"
98
- : (translateMap(getInstanceById("Reach", reach)?.translations)?.name ??
99
- MISSING_VALUE);
92
+ : (translateMap(getInstanceById("Reach", reach)?.translations)?.name ?? MISSING_VALUE);
100
93
  const renderLength = (translate, measurements, length) => length === undefined
101
94
  ? "—"
102
95
  : translate(".input {$value :number} {{{$value} inches}}", {
@@ -164,8 +157,7 @@ const renderReloadTime = (translate, reloadTime) => isNotEmpty(reloadTime)
164
157
  const renderRangeBrackets = (rangeBrackets) => `${rangeBrackets.close}/${rangeBrackets.medium}/${rangeBrackets.far}`;
165
158
  const renderAmmunition = (translateMap, getInstanceById, ammunition) => ammunition === undefined
166
159
  ? "—"
167
- : (translateMap(getInstanceById("Ammunition", ammunition)?.translations)
168
- ?.name ?? MISSING_VALUE);
160
+ : (translateMap(getInstanceById("Ammunition", ammunition)?.translations)?.name ?? MISSING_VALUE);
169
161
  const renderMeleeDamage = (translate) => (damage) => renderDiceAndFlat(translate, damage.dice, damage.flat);
170
162
  /**
171
163
  * Render combat values of a ranged weapon.
@@ -242,8 +234,7 @@ const renderComplexity = (translate, complexity) => mapNullable(complexity, c =>
242
234
  })(),
243
235
  }));
244
236
  const renderBlessedTraditionRestriction = (translate, translateMap, localeJoin, getInstanceById, name, restriction) => {
245
- const getName = (traditionId) => translateMap(getInstanceById("BlessedTradition", traditionId)?.translations)
246
- ?.name;
237
+ const getName = (traditionId) => translateMap(getInstanceById("BlessedTradition", traditionId)?.translations)?.name;
247
238
  if (restriction.isSanctifiedBy) {
248
239
  switch (restriction.scope.kind) {
249
240
  case "Specific":
@@ -275,8 +266,8 @@ const renderBlessedTraditionRestriction = (translate, translateMap, localeJoin,
275
266
  case "Specific":
276
267
  return translate("To buy a {$itemName} during hero creation, the character must have Tradition ({$traditions}).", {
277
268
  itemName: name,
278
- traditions: localeJoin(restriction.scope.Specific.map(traditionId => translateMap(getInstanceById("BlessedTradition", traditionId)
279
- ?.translations)?.name).filter(isNotNullish), "disjunction"),
269
+ traditions: localeJoin(restriction.scope.Specific.map(traditionId => translateMap(getInstanceById("BlessedTradition", traditionId)?.translations)
270
+ ?.name).filter(isNotNullish), "disjunction"),
280
271
  });
281
272
  case "Church":
282
273
  return UNHANDLED_VALUE;
@@ -298,15 +289,13 @@ const renderMagicalTraditionRestriction = (translate, translateMap, getInstanceB
298
289
  const renderRaceRestriction = (translate, translateMap, getInstanceById, localeJoin, name, restriction) => translate("To buy a {$name} during hero creation, the character must be from a culture common to the race of {$races}.", {
299
290
  name,
300
291
  races: localeJoin(restriction.scope
301
- .map(id => translateMap(getInstanceById("Race", id)?.translations)?.name ??
302
- MISSING_VALUE)
292
+ .map(id => translateMap(getInstanceById("Race", id)?.translations)?.name ?? MISSING_VALUE)
303
293
  .filter(isNotNullish), "disjunction"),
304
294
  });
305
295
  const renderCultureRestriction = (translate, translateMap, getInstanceById, localeJoin, name, restriction) => translate("To buy a {$name} during hero creation, the character must be from the culture of the {$cultures}.", {
306
296
  name,
307
297
  cultures: localeJoin(restriction.scope
308
- .map(id => translateMap(getInstanceById("Culture", id)?.translations)
309
- ?.name ?? MISSING_VALUE)
298
+ .map(id => translateMap(getInstanceById("Culture", id)?.translations)?.name ?? MISSING_VALUE)
310
299
  .filter(isNotNullish), "disjunction"),
311
300
  });
312
301
  const renderProfessionRestriction = (_translate, _translateMap, _getInstanceById, _localeJoin, _name, _restriction) => UNHANDLED_VALUE;
@@ -361,8 +350,7 @@ const renderCost = (translate, translateMap, cost) => {
361
350
  }
362
351
  case "Indefinite": {
363
352
  const translation = translateMap(bookCostVariant.Indefinite.translations);
364
- return ((translation?.description ?? MISSING_VALUE) +
365
- parensIf(translation?.label));
353
+ return (translation?.description ?? MISSING_VALUE) + parensIf(translation?.label);
366
354
  }
367
355
  default:
368
356
  return assertExhaustive(bookCostVariant);
@@ -421,11 +409,9 @@ const renderArmorValues = (translate, translateMap, localeCompare, getInstanceBy
421
409
  {
422
410
  label: translate("Additional Penalties"),
423
411
  value: values.has_additional_penalties
424
- ? [
425
- idMap.DerivedCharacteristic.Movement,
426
- idMap.DerivedCharacteristic.Initiative,
427
- ]
428
- .map(dcId => `${sign(-1)} ${translateMap(getInstanceById("DerivedCharacteristic", dcId)?.translations)?.abbreviation ?? MISSING_VALUE}`)
412
+ ? [idMap.DerivedCharacteristic.Movement, idMap.DerivedCharacteristic.Initiative]
413
+ .map(dcId => `${sign(-1)} ${translateMap(getInstanceById("DerivedCharacteristic", dcId)?.translations)
414
+ ?.abbreviation ?? MISSING_VALUE}`)
429
415
  .toSorted(localeCompare)
430
416
  .join(", ")
431
417
  : "—",
@@ -470,7 +456,8 @@ const normalizeCombatValues = (entity) => {
470
456
  case "ToolOfTheTrade":
471
457
  case "TravelGearOrTool":
472
458
  case "Vehicle":
473
- case "WeaponAccessory": {
459
+ case "WeaponAccessory":
460
+ case "WorkingSupernaturalCreature": {
474
461
  const baseItem = entity.content;
475
462
  if (baseItem.combat_use === undefined) {
476
463
  return undefined;
@@ -499,9 +486,7 @@ const normalizeCombatValues = (entity) => {
499
486
  */
500
487
  export const getEquipmentEntityDescription = createEntityDescriptionCreator(({ getInstanceById, idMap }, locale, entry) => {
501
488
  const { translate, translateMap } = locale;
502
- const translation = entry.entity === "ClothingPackage"
503
- ? undefined
504
- : translateMap(entry.content.translations);
489
+ const translation = entry.entity === "ClothingPackage" ? undefined : translateMap(entry.content.translations);
505
490
  if (entry.entity !== "ClothingPackage" && translation === undefined) {
506
491
  return undefined;
507
492
  }
@@ -537,36 +522,29 @@ export const getEquipmentEntityDescription = createEntityDescriptionCreator(({ g
537
522
  label: translate("Color"),
538
523
  value: color,
539
524
  })),
540
- baseItemTranslation?.language !== undefined ||
541
- baseItemTranslation?.script !== undefined
525
+ baseItemTranslation?.language !== undefined || baseItemTranslation?.script !== undefined
542
526
  ? {
543
527
  label: translate("Language/Script"),
544
- value: [
545
- baseItemTranslation?.language,
546
- baseItemTranslation?.script,
547
- ]
528
+ value: [baseItemTranslation?.language, baseItemTranslation?.script]
548
529
  .map(value => value ?? "—")
549
530
  .join(" / "),
550
531
  }
551
532
  : undefined,
552
- baseItem.structure_points !== undefined &&
553
- isNotEmpty(baseItem.structure_points)
533
+ baseItem.structure_points !== undefined && isNotEmpty(baseItem.structure_points)
554
534
  ? {
555
535
  label: translate("Structure Points"),
556
536
  value: baseItem.structure_points.length === 1
557
- ? translate(".input {$value :number} {{{$value} Structure Points}}", { value: baseItem.structure_points[0].points })
537
+ ? translate(".input {$value :number} {{{$value} Structure Points}}", {
538
+ value: baseItem.structure_points[0].points,
539
+ })
558
540
  : translate("{$value} Structure Points", {
559
- value: baseItem.structure_points
560
- .map(elem => elem.points)
561
- .join("/"),
541
+ value: baseItem.structure_points.map(elem => elem.points).join("/"),
562
542
  }),
563
543
  }
564
544
  : undefined,
565
545
  mapNullable(baseItem.weight, weight => renderWeight(translate, locale.measurementAdjustments, weight)),
566
546
  mapNullable(baseItem.cost, cost => renderCost(translate, translateMap, cost)),
567
- mapNullable(renderNote(translate, translateMap, getInstanceById, locale.join, combatValues?.type === "Weapon"
568
- ? combatValues.values.melee_uses
569
- : undefined, combatValues?.values.restrictedTo, name, baseItemTranslation?.note), note => ({
547
+ mapNullable(renderNote(translate, translateMap, getInstanceById, locale.join, combatValues?.type === "Weapon" ? combatValues.values.melee_uses : undefined, combatValues?.values.restrictedTo, name, baseItemTranslation?.note), note => ({
570
548
  label: translate("Note"),
571
549
  value: note,
572
550
  })),
@@ -576,34 +554,48 @@ export const getEquipmentEntityDescription = createEntityDescriptionCreator(({ g
576
554
  value: baseItemTranslation.rules,
577
555
  }
578
556
  : undefined,
579
- combatValues?.type === "Weapon" &&
580
- combatTranslation?.advantage !== undefined
557
+ combatValues?.type === "Weapon" && combatTranslation?.advantage !== undefined
581
558
  ? {
582
559
  label: translate("Weapon Advantage"),
583
560
  value: combatTranslation.advantage,
584
561
  }
585
562
  : undefined,
586
- combatValues?.type === "Weapon" &&
587
- combatTranslation?.disadvantage !== undefined
563
+ combatValues?.type === "Weapon" && combatTranslation?.disadvantage !== undefined
588
564
  ? {
589
565
  label: translate("Weapon Disadvantage"),
590
566
  value: combatTranslation.disadvantage,
591
567
  }
592
568
  : undefined,
593
- combatValues?.type === "Armor" &&
594
- combatTranslation?.advantage !== undefined
569
+ combatValues?.type === "Armor" && combatTranslation?.advantage !== undefined
595
570
  ? {
596
571
  label: translate("Armor Advantage"),
597
572
  value: combatTranslation.advantage,
598
573
  }
599
574
  : undefined,
600
- combatValues?.type === "Armor" &&
601
- combatTranslation?.disadvantage !== undefined
575
+ combatValues?.type === "Armor" && combatTranslation?.disadvantage !== undefined
602
576
  ? {
603
577
  label: translate("Armor Disadvantage"),
604
578
  value: combatTranslation.disadvantage,
605
579
  }
606
580
  : undefined,
581
+ baseItemTranslation?.appearance !== undefined
582
+ ? {
583
+ label: translate("Appearance"),
584
+ value: baseItemTranslation.appearance,
585
+ }
586
+ : undefined,
587
+ baseItemTranslation?.appearance !== undefined
588
+ ? {
589
+ label: translate("Components"),
590
+ value: baseItemTranslation.components,
591
+ }
592
+ : undefined,
593
+ baseItemTranslation?.appearance !== undefined
594
+ ? {
595
+ label: translate("Use"),
596
+ value: baseItemTranslation.use,
597
+ }
598
+ : undefined,
607
599
  ],
608
600
  },
609
601
  ],
@@ -1,6 +1,6 @@
1
1
  import { on } from "@elyukai/utils/function";
2
2
  import { assertExhaustive } from "@elyukai/utils/typeSafety";
3
- import { createEntityDescriptionCreator, } from "../creator.js";
3
+ import { createEntityDescriptionCreator } from "../creator.js";
4
4
  import { getEquipmentName } from "./equipment.js";
5
5
  const sumAtomicEquipmentCost = (acc, current) => {
6
6
  if (typeof acc === "string") {
@@ -49,10 +49,7 @@ const rangeAtomicEquipmentCost = (acc, current) => {
49
49
  }
50
50
  const normAcc = typeof acc === "number" ? [acc, acc] : acc;
51
51
  const normCurrent = typeof current === "number" ? [current, current] : current;
52
- return [
53
- Math.min(normAcc[0], normCurrent[0]),
54
- Math.max(normAcc[1], normCurrent[1]),
55
- ];
52
+ return [Math.min(normAcc[0], normCurrent[0]), Math.max(normAcc[1], normCurrent[1])];
56
53
  };
57
54
  const getAtomicCost = (cost) => {
58
55
  switch (cost.kind) {
@@ -147,14 +144,13 @@ const getAtomicEquipmentCost = (entry) => {
147
144
  case "Vehicle":
148
145
  case "Weapon":
149
146
  case "WeaponAccessory":
147
+ case "WorkingSupernaturalCreature":
150
148
  return getAtomicCost(entry.content.cost);
151
149
  case "Elixir":
152
- return [
153
- entry.content.cost_per_ingredient_level,
154
- entry.content.cost_per_ingredient_level * 6,
155
- ];
150
+ return [entry.content.cost_per_ingredient_level, entry.content.cost_per_ingredient_level * 6];
156
151
  case "Poison":
157
- switch (entry.content.cost.kind) {
152
+ switch (entry.content.cost?.kind) {
153
+ case undefined:
158
154
  case "CannotBeExtracted":
159
155
  case "None":
160
156
  return 0;
@@ -162,6 +158,8 @@ const getAtomicEquipmentCost = (entry) => {
162
158
  return entry.content.cost.Constant;
163
159
  case "Indefinite":
164
160
  return "Various";
161
+ case "DependingOnPurchaseOrSale":
162
+ return entry.content.cost.DependingOnPurchaseOrSale.purchase;
165
163
  default:
166
164
  return assertExhaustive(entry.content.cost);
167
165
  }
@@ -178,6 +176,7 @@ const getAtomicEquipmentWeight = (entry) => {
178
176
  case "EquipmentOfBlessedOnes":
179
177
  case "Newspaper":
180
178
  case "Poison":
179
+ case "WorkingSupernaturalCreature":
181
180
  return 0;
182
181
  case "AnimalCare":
183
182
  switch (entry.content.type.kind) {
@@ -1,7 +1,11 @@
1
- import type { GetInstanceById } from "../helpers/getTypes.js";
1
+ import type { ActivatableIdentifier } from "optolith-database-schema/gen";
2
+ import type { GetAllChildInstancesForParent, GetInstanceById } from "../helpers/getTypes.js";
3
+ import type { GetResolvedSelectOptionById } from "./partial/prerequisites/single/activatable.js";
2
4
  /**
3
5
  * Get a JSON representation of the rules text for an influence.
4
6
  */
5
7
  export declare const getInfluenceEntityDescription: import("../creator.js").EntityDescriptionCreator<"Influence", {
6
- getInstanceById: GetInstanceById<"Publication" | "Influence">;
8
+ getInstanceById: GetInstanceById<"Publication" | "Influence" | "Race" | ActivatableIdentifier["kind"] | "Aspect">;
9
+ getResolvedSelectOptionById: GetResolvedSelectOptionById;
10
+ getChildInstancesForInstanceId: GetAllChildInstancesForParent<"ProfessionVersion">;
7
11
  }, import("../index.js").EntityDescription>;
@@ -3,7 +3,7 @@ import { printInfluencePrerequisites } from "./partial/prerequisites/index.js";
3
3
  /**
4
4
  * Get a JSON representation of the rules text for an influence.
5
5
  */
6
- export const getInfluenceEntityDescription = createEntityDescriptionCreator(({ getInstanceById }, locale, { content: entry }) => {
6
+ export const getInfluenceEntityDescription = createEntityDescriptionCreator(({ getInstanceById, getResolvedSelectOptionById, getChildInstancesForInstanceId }, locale, { content: entry }) => {
7
7
  const { translate, translateMap } = locale;
8
8
  const translation = translateMap(entry.translations);
9
9
  if (translation === undefined) {
@@ -13,21 +13,29 @@ export const getInfluenceEntityDescription = createEntityDescriptionCreator(({ g
13
13
  title: translation.name,
14
14
  className: "influence",
15
15
  body: [
16
- {
17
- type: "definitionList",
18
- items: [
19
- ...(translation.effects?.map(effect => ({
20
- label: effect.label,
21
- value: effect.text,
22
- })) ?? []),
23
- entry.prerequisites === undefined
24
- ? undefined
25
- : {
26
- label: translate("Prerequisites"),
27
- value: printInfluencePrerequisites(getInstanceById, locale, entry.prerequisites),
28
- },
29
- ],
30
- },
16
+ translation.rules === undefined
17
+ ? undefined
18
+ : {
19
+ type: "plain",
20
+ text: translation.rules,
21
+ },
22
+ translation.effects === undefined || translation.effects.length === 0
23
+ ? undefined
24
+ : {
25
+ type: "definitionList",
26
+ items: [
27
+ ...(translation.effects?.map(effect => ({
28
+ label: effect.label,
29
+ value: effect.text,
30
+ })) ?? []),
31
+ entry.prerequisites === undefined
32
+ ? undefined
33
+ : {
34
+ label: translate("Prerequisites"),
35
+ value: printInfluencePrerequisites(getInstanceById, getResolvedSelectOptionById, getChildInstancesForInstanceId, locale, entry.prerequisites),
36
+ },
37
+ ],
38
+ },
31
39
  ],
32
40
  errata: translation.errata,
33
41
  references: entry.src,
@@ -1,4 +1,5 @@
1
- import type { GetInstanceById } from "../helpers/getTypes.js";
1
+ import { type RatedIdentifier } from "optolith-database-schema/gen";
2
+ import type { GetAllChildInstancesForParent, GetInstanceById } from "../helpers/getTypes.js";
2
3
  import { type IdMap } from "../index.js";
3
4
  /**
4
5
  * Get a JSON representation of the rules text for a blessing.
@@ -10,13 +11,15 @@ export declare const getBlessingEntityDescription: import("../creator.js").Entit
10
11
  * Get a JSON representation of the rules text for a liturgical chant.
11
12
  */
12
13
  export declare const getLiturgicalChantEntityDescription: import("../creator.js").EntityDescriptionCreator<"LiturgicalChant", {
13
- getInstanceById: GetInstanceById<"Publication" | "Attribute" | "SkillModificationLevel" | "TargetCategory" | "Aspect" | "BlessedTradition" | "DerivedCharacteristic">;
14
+ getInstanceById: GetInstanceById<"Publication" | "Attribute" | "SkillModificationLevel" | "TargetCategory" | "Aspect" | "BlessedTradition" | "DerivedCharacteristic" | RatedIdentifier["kind"] | "Enhancement">;
15
+ getChildInstancesForInstanceId: GetAllChildInstancesForParent<"Enhancement">;
14
16
  idMap: IdMap;
15
17
  }, import("../index.js").EntityDescription>;
16
18
  /**
17
19
  * Get a JSON representation of the rules text for a ceremony.
18
20
  */
19
21
  export declare const getCeremonyEntityDescription: import("../creator.js").EntityDescriptionCreator<"Ceremony", {
20
- getInstanceById: GetInstanceById<"Publication" | "Attribute" | "SkillModificationLevel" | "TargetCategory" | "Aspect" | "BlessedTradition" | "DerivedCharacteristic">;
22
+ getInstanceById: GetInstanceById<"Publication" | "Attribute" | "SkillModificationLevel" | "TargetCategory" | "Aspect" | "BlessedTradition" | "DerivedCharacteristic" | RatedIdentifier["kind"] | "Enhancement">;
23
+ getChildInstancesForInstanceId: GetAllChildInstancesForParent<"Enhancement">;
21
24
  idMap: IdMap;
22
25
  }, import("../index.js").EntityDescription>;
@@ -1,6 +1,8 @@
1
1
  import { isNotNullish } from "@optolith/helpers/nullable";
2
2
  import { assertExhaustive } from "@optolith/helpers/typeSafety";
3
+ import { Case } from "tsondb/schema/gen";
3
4
  import { createEntityDescriptionCreator } from "../creator.js";
5
+ import { renderEnhancements } from "./partial/enhancements.js";
4
6
  import { renderOneTimeDuration } from "./partial/rated/activatable/duration.js";
5
7
  import { renderEffect } from "./partial/rated/activatable/effect.js";
6
8
  import { renderFastPerformanceParameters, renderSlowPerformanceParameters, } from "./partial/rated/activatable/index.js";
@@ -11,16 +13,14 @@ import { renderImprovementCost } from "./partial/rated/improvementCost.js";
11
13
  import { renderSkillCheckWithPenalty } from "./partial/rated/skillCheck.js";
12
14
  import { ResponsiveTextSize } from "./partial/responsiveText.js";
13
15
  const getTextForTraditions = (deps, values) => {
14
- const getAspectName = (aspectId) => deps.translateMap(deps.getInstanceById("Aspect", aspectId)?.translations)
15
- ?.name;
16
+ const getAspectName = (aspectId) => deps.translateMap(deps.getInstanceById("Aspect", aspectId)?.translations)?.name;
16
17
  const text = values
17
18
  .map(trad => {
18
19
  switch (trad.kind) {
19
20
  case "GeneralAspect":
20
21
  return getAspectName(trad.GeneralAspect);
21
22
  case "Tradition": {
22
- const traditionTranslation = deps.translateMap(deps.getInstanceById("BlessedTradition", trad.Tradition.tradition)
23
- ?.translations);
23
+ const traditionTranslation = deps.translateMap(deps.getInstanceById("BlessedTradition", trad.Tradition.tradition)?.translations);
24
24
  const name = traditionTranslation?.name_compressed ?? traditionTranslation?.name;
25
25
  if (name === undefined) {
26
26
  return undefined;
@@ -75,9 +75,7 @@ export const getBlessingEntityDescription = createEntityDescriptionCreator(({ ge
75
75
  },
76
76
  {
77
77
  label: translate("Range"),
78
- value: range !== translation.range
79
- ? `***${range}*** (${translation.range})`
80
- : range,
78
+ value: range !== translation.range ? `***${range}*** (${translation.range})` : range,
81
79
  },
82
80
  {
83
81
  label: translate("Duration"),
@@ -96,7 +94,7 @@ export const getBlessingEntityDescription = createEntityDescriptionCreator(({ ge
96
94
  /**
97
95
  * Get a JSON representation of the rules text for a liturgical chant.
98
96
  */
99
- export const getLiturgicalChantEntityDescription = createEntityDescriptionCreator(({ getInstanceById, idMap }, locale, { content: entry }) => {
97
+ export const getLiturgicalChantEntityDescription = createEntityDescriptionCreator(({ getInstanceById, getChildInstancesForInstanceId, idMap }, locale, { content: entry, entity, id }) => {
100
98
  const { translate, translateMap, compare: localeCompare } = locale;
101
99
  const translation = translateMap(entry.translations);
102
100
  if (translation === undefined) {
@@ -106,7 +104,9 @@ export const getLiturgicalChantEntityDescription = createEntityDescriptionCreato
106
104
  translate,
107
105
  translateMap,
108
106
  getInstanceById,
107
+ getChildInstancesForInstanceId,
109
108
  localeJoin: locale.join,
109
+ localeCompare: locale.compare,
110
110
  energyUnit: "KarmaPoints",
111
111
  responsiveTextSize: ResponsiveTextSize.Full,
112
112
  nonModifiableSuffix: (param) => {
@@ -134,8 +134,7 @@ export const getLiturgicalChantEntityDescription = createEntityDescriptionCreato
134
134
  renderEffect(translation.effect).run(env),
135
135
  {
136
136
  label: translate("Liturgical Time"),
137
- value: translation.casting_time &&
138
- castingTime !== translation.casting_time.full
137
+ value: translation.casting_time && castingTime !== translation.casting_time.full
139
138
  ? `***${castingTime}*** (${translation.casting_time.full})`
140
139
  : castingTime,
141
140
  },
@@ -167,6 +166,7 @@ export const getLiturgicalChantEntityDescription = createEntityDescriptionCreato
167
166
  renderImprovementCost(entry.improvement_cost).run(env),
168
167
  ],
169
168
  },
169
+ renderEnhancements(Case(entity, id), entry.improvement_cost).run(env),
170
170
  ],
171
171
  errata: translation.errata,
172
172
  references: entry.src,
@@ -175,7 +175,7 @@ export const getLiturgicalChantEntityDescription = createEntityDescriptionCreato
175
175
  /**
176
176
  * Get a JSON representation of the rules text for a ceremony.
177
177
  */
178
- export const getCeremonyEntityDescription = createEntityDescriptionCreator(({ getInstanceById, idMap }, locale, { content: entry }) => {
178
+ export const getCeremonyEntityDescription = createEntityDescriptionCreator(({ getInstanceById, getChildInstancesForInstanceId, idMap }, locale, { content: entry, entity, id }) => {
179
179
  const { translate, translateMap, compare: localeCompare } = locale;
180
180
  const translation = translateMap(entry.translations);
181
181
  if (translation === undefined) {
@@ -185,7 +185,9 @@ export const getCeremonyEntityDescription = createEntityDescriptionCreator(({ ge
185
185
  translate,
186
186
  translateMap,
187
187
  getInstanceById,
188
+ getChildInstancesForInstanceId,
188
189
  localeJoin: locale.join,
190
+ localeCompare: locale.compare,
189
191
  energyUnit: "KarmaPoints",
190
192
  responsiveTextSize: ResponsiveTextSize.Full,
191
193
  nonModifiableSuffix: (param) => {
@@ -213,8 +215,7 @@ export const getCeremonyEntityDescription = createEntityDescriptionCreator(({ ge
213
215
  renderEffect(translation.effect).run(env),
214
216
  {
215
217
  label: translate("Ceremonial Time"),
216
- value: translation.casting_time &&
217
- castingTime !== translation.casting_time.full
218
+ value: translation.casting_time && castingTime !== translation.casting_time.full
218
219
  ? `***${castingTime}*** (${translation.casting_time.full})`
219
220
  : castingTime,
220
221
  },
@@ -246,6 +247,7 @@ export const getCeremonyEntityDescription = createEntityDescriptionCreator(({ ge
246
247
  renderImprovementCost(entry.improvement_cost).run(env),
247
248
  ],
248
249
  },
250
+ renderEnhancements(Case(entity, id), entry.improvement_cost).run(env),
249
251
  ],
250
252
  errata: translation.errata,
251
253
  references: entry.src,
@@ -0,0 +1,7 @@
1
+ import { RatedIdentifier, type ImprovementCost, type SkillWithEnhancementsIdentifier } from "optolith-database-schema/gen";
2
+ import type { RawEntityDescriptionSection } from "../../index.js";
3
+ import { type StdReader } from "./reader.js";
4
+ /**
5
+ * Render the enhancements section for an entity description, if applicable.
6
+ */
7
+ export declare const renderEnhancements: (parentId: SkillWithEnhancementsIdentifier, parentImprovementCost: ImprovementCost) => StdReader<RawEntityDescriptionSection | undefined, "t" | "tm" | "lc" | "lj" | "ibi" | "acibp", RatedIdentifier["kind"] | "Enhancement", "Enhancement">;
@@ -0,0 +1,26 @@
1
+ import { on } from "@elyukai/utils/function";
2
+ import { isNotNullish } from "@elyukai/utils/nullable";
3
+ import { compareNumber } from "@elyukai/utils/ordering";
4
+ import { Reader } from "@elyukai/utils/reader";
5
+ import { getAdventurePointsForActivation } from "@optolith/adventure-points/improvement-cost";
6
+ import { printEnhancementPrerequisites } from "./prerequisites/index.js";
7
+ import { getChildInstancesForInstanceIdR, translateMapR, translateR, } from "./reader.js";
8
+ /**
9
+ * Render the enhancements section for an entity description, if applicable.
10
+ */
11
+ export const renderEnhancements = (parentId, parentImprovementCost) => getChildInstancesForInstanceIdR("Enhancement", parentId).thenW(enhancements => Reader.traverse(enhancements.toSorted(on(e => e.content.skill_rating, compareNumber)), enhancement => translateMapR(enhancement.content.translations).thenW(translation => Reader.ask().map(env => translation === undefined
12
+ ? undefined
13
+ : `- ^[${translation.name}](entity: "Enhancement") (${env.translate("SR {$value}", { value: enhancement.content.skill_rating })}, ${env.translate("{$value} AP", { value: enhancement.content.adventure_points_modifier * getAdventurePointsForActivation(parentImprovementCost.kind) })}): ${translation.effect ?? ""}${enhancement.content.prerequisites === undefined ? "" : ` ${env.translate(".input {$hiddenCount :number} {{Prerequisites}}", { hiddenCount: enhancement.content.prerequisites.length })}: ${printEnhancementPrerequisites(env.getInstanceById, { translate: env.translate, translateMap: env.translateMap, compare: env.localeCompare, join: env.localeJoin }, enhancement.content.prerequisites)}`}`))).thenW(enhancementDescriptions => {
14
+ const nonNullishDescriptions = enhancementDescriptions.filter(isNotNullish);
15
+ if (nonNullishDescriptions.length === 0) {
16
+ return Reader.of(undefined);
17
+ }
18
+ return translateR("Enhancements").map((label) => ({
19
+ type: "labeled",
20
+ label,
21
+ value: {
22
+ type: "plain",
23
+ text: nonNullishDescriptions.join("\n"),
24
+ },
25
+ }));
26
+ }));
@@ -1,5 +1,5 @@
1
1
  import { AdvantageDisadvantagePrerequisites, AnimistPowerPrerequisites, ArcaneTraditionPrerequisites, DerivedCharacteristicPrerequisites, EnhancementPrerequisites, GeneralPrerequisites, GeodeRitualPrerequisites, InfluencePrerequisites, LanguagePrerequisites, LiturgyPrerequisites, PersonalityTraitPrerequisites, PlainGeneralPrerequisites, ProfessionPrerequisites, PublicationPrerequisites, SpecialAbilityIdentifier, SpellworkPrerequisites, type ActivatableIdentifier, type RatedIdentifier } from "optolith-database-schema/gen";
2
- import type { GetInstanceById } from "../../../helpers/getTypes.js";
2
+ import type { GetAllChildInstancesForParent, GetInstanceById } from "../../../helpers/getTypes.js";
3
3
  import { LocaleEnvironment } from "../../../helpers/locale.js";
4
4
  import { GetResolvedSelectOptionById } from "./single/activatable.js";
5
5
  /**
@@ -48,7 +48,7 @@ export declare const printLiturgyPrerequisites: (locale: LocaleEnvironment, valu
48
48
  /**
49
49
  * Print influence prerequisites as a string.
50
50
  */
51
- export declare const printInfluencePrerequisites: (getInstanceById: GetInstanceById<"Influence">, locale: LocaleEnvironment, value: InfluencePrerequisites) => string;
51
+ export declare const printInfluencePrerequisites: (getInstanceById: GetInstanceById<"Influence" | "Race" | ActivatableIdentifier["kind"] | "Aspect">, getResolvedSelectOptionById: GetResolvedSelectOptionById, getChildInstancesForInstanceId: GetAllChildInstancesForParent<"ProfessionVersion">, locale: LocaleEnvironment, value: InfluencePrerequisites) => string;
52
52
  /**
53
53
  * Print language prerequisites as a string.
54
54
  */
@@ -64,4 +64,4 @@ export declare const printGeodeRitualPrerequisites: (getInstanceById: GetInstanc
64
64
  /**
65
65
  * Print enhancement prerequisites as a string.
66
66
  */
67
- export declare const printEnhancementPrerequisites: (getInstanceById: GetInstanceById<RatedIdentifier["kind"] | "Enhancement">, locale: LocaleEnvironment, value: EnhancementPrerequisites) => string;
67
+ export declare const printEnhancementPrerequisites: (getInstanceById: GetInstanceById<RatedIdentifier["kind"] | "Enhancement">, locale: Pick<LocaleEnvironment, "translate" | "translateMap" | "compare" | "join">, value: EnhancementPrerequisites) => string;