@optolith/entity-descriptions 0.4.10 → 0.4.11

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,19 @@
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.11](https://github.com/Optolith/entity-descriptions/compare/v0.4.10...v0.4.11) (2026-03-23)
6
+
7
+
8
+ ### Features
9
+
10
+ * support schema version 0.42.0 ([80b2eaa](https://github.com/Optolith/entity-descriptions/commit/80b2eaacbcf445cf3222c11b86c1be77b7d973d6))
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * activatable cost by level separator ([a0119ff](https://github.com/Optolith/entity-descriptions/commit/a0119ffdafcc6027801c0ff7b502c9c65af107dd))
16
+ * render attribute prerequisite as abbreviation ([6f258b1](https://github.com/Optolith/entity-descriptions/commit/6f258b1f6f4fb1c88b09fe9fb2013b180d91fc4b))
17
+
5
18
  ## [0.4.10](https://github.com/Optolith/entity-descriptions/compare/v0.4.9...v0.4.10) (2026-03-17)
6
19
 
7
20
  ## [0.4.9](https://github.com/Optolith/entity-descriptions/compare/v0.4.8...v0.4.9) (2026-03-17)
@@ -392,7 +392,7 @@ const renderArcaneEnergyCost = (translate, translateMap, localeJoin, responsiveT
392
392
  cost: translate("{$value} AE", {
393
393
  value: cost.ByLevel.levels.map(level => level.value).join("/"),
394
394
  }),
395
- level: Array.from({ length: cost.ByLevel.levels.length }, (_, index) => romanize(index + 1)),
395
+ level: Array.from({ length: cost.ByLevel.levels.length }, (_, index) => romanize(index + 1)).join("/"),
396
396
  });
397
397
  case "Verbose":
398
398
  return cost.ByLevel.levels
@@ -1,9 +1,7 @@
1
1
  import type { GetInstanceById } from "../helpers/getTypes.js";
2
- import type { IdMap } from "../index.js";
3
2
  /**
4
3
  * Get a JSON representation of the rules text for a derived characteristic.
5
4
  */
6
5
  export declare const getDerivedCharacteristicEntityDescription: import("../creator.js").EntityDescriptionCreator<"DerivedCharacteristic", {
7
6
  getInstanceById: GetInstanceById<"Publication" | "Attribute" | "DerivedCharacteristic">;
8
- idMap: IdMap;
9
7
  }, import("../index.js").EntityDescription>;
@@ -14,12 +14,7 @@ const renderAttributeNameForStyle = (style, translation) => {
14
14
  }
15
15
  };
16
16
  const getAttribute = (getInstanceById, translateMap, attributeId, style = "full") => attributedCustomName(translateMap, getInstanceById, "derived-characteristic-calculation", translation => renderAttributeNameForStyle(style, translation), "Attribute", attributeId) ?? MISSING_VALUE;
17
- const getRaceBaseValue = (translate, translateMap, getInstanceById, idMap, raceBaseValue, style = "full") => {
18
- const instance = getInstanceById("DerivedCharacteristic", idMap.DerivedCharacteristic[raceBaseValue.kind]);
19
- const translation = translateMap(instance?.translations);
20
- if (translation === undefined) {
21
- return MISSING_VALUE;
22
- }
17
+ const getRaceBaseValue = (translate, translation, style = "full") => {
23
18
  switch (style) {
24
19
  case "full":
25
20
  return translate("Base {$name} from Race", translation);
@@ -29,14 +24,14 @@ const getRaceBaseValue = (translate, translateMap, getInstanceById, idMap, raceB
29
24
  return assertExhaustive(style);
30
25
  }
31
26
  };
32
- const renderBaseCalculation = (getInstanceById, translate, translateMap, idMap, calculation, style = "full") => renderMathOperation(calculation, value => {
27
+ const renderBaseCalculation = (getInstanceById, translate, translateMap, translation, calculation, style = "full") => renderMathOperation(calculation, value => {
33
28
  switch (value.kind) {
34
29
  case "Constant":
35
30
  return value.Constant.toFixed();
36
31
  case "Attribute":
37
32
  return getAttribute(getInstanceById, translateMap, value.Attribute, style);
38
33
  case "RaceBaseValue":
39
- return getRaceBaseValue(translate, translateMap, getInstanceById, idMap, value.RaceBaseValue, style);
34
+ return getRaceBaseValue(translate, translation, style);
40
35
  case "PrimaryAttribute":
41
36
  switch (style) {
42
37
  case "full":
@@ -60,7 +55,7 @@ const renderBaseCalculation = (getInstanceById, translate, translateMap, idMap,
60
55
  /**
61
56
  * Get a JSON representation of the rules text for a derived characteristic.
62
57
  */
63
- export const getDerivedCharacteristicEntityDescription = createEntityDescriptionCreator(({ getInstanceById, idMap }, { translate, translateMap }, { content: entry }) => {
58
+ export const getDerivedCharacteristicEntityDescription = createEntityDescriptionCreator(({ getInstanceById }, { translate, translateMap }, { content: entry }) => {
64
59
  const translation = translateMap(entry.translations);
65
60
  if (translation === undefined) {
66
61
  return undefined;
@@ -80,7 +75,7 @@ export const getDerivedCharacteristicEntityDescription = createEntityDescription
80
75
  items: [
81
76
  {
82
77
  label: translate("Base Value"),
83
- value: renderBaseCalculation(getInstanceById, translate, translateMap, idMap, entry.calculation.base),
78
+ value: renderBaseCalculation(getInstanceById, translate, translateMap, translation, entry.calculation.base),
84
79
  },
85
80
  ],
86
81
  },
@@ -25,7 +25,11 @@ export const getDiseaseEntityDescription = createEntityDescriptionCreator(({ get
25
25
  return undefined;
26
26
  }
27
27
  return {
28
- title: translation.name,
28
+ title: translation.name +
29
+ parensIf(ensureNonEmpty([
30
+ baseEntry.isChildhoodDisease === true ? translate("childhood disease") : undefined,
31
+ baseEntry.isMagicalDisease === true ? translate("magical disease") : undefined,
32
+ ].filter(isNotNullish))?.join(", ")),
29
33
  className: "disease",
30
34
  body: [
31
35
  {
@@ -58,16 +58,18 @@ export const getElixirEntityDescription = createEntityDescriptionCreator(({ getI
58
58
  },
59
59
  {
60
60
  label: translate("Quality Levels"),
61
- value: [
62
- {
63
- type: "definitionList",
64
- style: "nested",
65
- items: translation.quality_levels.map((effectForLevel, index) => ({
66
- label: (index + 1).toFixed(),
67
- value: effectForLevel,
68
- })),
69
- },
70
- ],
61
+ value: translation.quality_levels.kind === "Plain"
62
+ ? translation.quality_levels.Plain.text
63
+ : [
64
+ {
65
+ type: "definitionList",
66
+ style: "nested",
67
+ items: translation.quality_levels.ForEachQualityLevel.qualityLevels.map((effectForLevel, index) => ({
68
+ label: (index + 1).toFixed(),
69
+ value: effectForLevel,
70
+ })),
71
+ },
72
+ ],
71
73
  },
72
74
  ],
73
75
  },
@@ -1,6 +1,7 @@
1
- import type { EquipmentIdentifier, GenMeleeWeapon, GenRangedWeapon, LocaleMeasurementAdjustments } from "optolith-database-schema/gen";
1
+ import type { AmmunitionishIdentifier, EquipmentIdentifier, GenMeleeWeapon, GenRangedWeapon, LocaleMeasurementAdjustments } from "optolith-database-schema/gen";
2
2
  import { type TaggedEntity } from "../creator.js";
3
3
  import type { GetInstanceById } from "../helpers/getTypes.js";
4
+ import type { LocaleJoin } from "../helpers/locale.js";
4
5
  import type { Translate, TranslateMap } from "../helpers/translate.js";
5
6
  import type { IdMap, LabeledEntityDescriptionSection, RawEntityDescriptionSectionContent } from "../index.js";
6
7
  /**
@@ -10,15 +11,15 @@ export declare const getEquipmentName: (translate: Translate, translateMap: Tran
10
11
  /**
11
12
  * Render combat values of a melee weapon.
12
13
  */
13
- export declare const renderMeleeWeapon: <Damage>(translate: Translate, translateMap: TranslateMap, getInstanceById: GetInstanceById<"Attribute" | "CloseCombatTechnique" | "Reach">, measurements: Required<LocaleMeasurementAdjustments>, renderDamage: (damage: Damage) => string, closeCombatTechniqueId: string, use: GenMeleeWeapon<Damage>) => LabeledEntityDescriptionSection<RawEntityDescriptionSectionContent>;
14
+ export declare const renderMeleeWeapon: <Damage>(translate: Translate, translateMap: TranslateMap, localeJoin: LocaleJoin, getInstanceById: GetInstanceById<"Attribute" | "CloseCombatTechnique" | "Reach">, measurements: Required<LocaleMeasurementAdjustments>, renderDamage: (damage: Damage) => string, closeCombatTechniqueId: string, use: GenMeleeWeapon<Damage>) => LabeledEntityDescriptionSection<RawEntityDescriptionSectionContent>;
14
15
  /**
15
16
  * Render combat values of a ranged weapon.
16
17
  */
17
- export declare const renderRangedWeapon: <Damage>(translate: Translate, translateMap: TranslateMap, getInstanceById: GetInstanceById<"RangedCombatTechnique" | "Ammunition">, measurements: Required<LocaleMeasurementAdjustments>, renderDamage: (damage: Damage) => string, rangedCombatTechniqueId: string, use: GenRangedWeapon<Damage>) => LabeledEntityDescriptionSection<RawEntityDescriptionSectionContent>;
18
+ export declare const renderRangedWeapon: <Damage>(translate: Translate, translateMap: TranslateMap, getInstanceById: GetInstanceById<"RangedCombatTechnique" | AmmunitionishIdentifier["kind"]>, measurements: Required<LocaleMeasurementAdjustments>, renderDamage: (damage: Damage) => string, rangedCombatTechniqueId: string, use: GenRangedWeapon<Damage>) => LabeledEntityDescriptionSection<RawEntityDescriptionSectionContent>;
18
19
  /**
19
20
  * Get a JSON representation of the rules text for equipment.
20
21
  */
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
- getInstanceById: GetInstanceById<"Publication" | "Attribute" | "Reach" | "SocialStatus" | "CloseCombatTechnique" | "RangedCombatTechnique" | "MagicalTradition" | "BlessedTradition" | "DerivedCharacteristic" | "Ammunition" | "Race" | "Culture" | "Profession" | "Skill">;
22
+ export declare const getEquipmentEntityDescription: import("../creator.js").EntityDescriptionCreator<"Ammunition" | "Weapon" | "Armor" | "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", {
23
+ getInstanceById: GetInstanceById<"Publication" | "Attribute" | "Reach" | "SocialStatus" | "CloseCombatTechnique" | "RangedCombatTechnique" | "MagicalTradition" | "BlessedTradition" | "DerivedCharacteristic" | AmmunitionishIdentifier["kind"] | "Race" | "Culture" | "Profession" | "Skill">;
23
24
  idMap: IdMap;
24
25
  }, import("../index.js").EntityDescription>;
@@ -1,11 +1,14 @@
1
1
  import { ensureNonEmpty, isNotEmpty } from "@elyukai/utils/array/nonEmpty";
2
+ import { on } from "@elyukai/utils/function";
2
3
  import { isNotNullish } from "@elyukai/utils/nullable";
4
+ import { compareNumber } from "@elyukai/utils/ordering";
3
5
  import { romanize } from "@elyukai/utils/roman";
4
6
  import { sign } from "@elyukai/utils/string/number";
5
7
  import { assertExhaustive } from "@elyukai/utils/typeSafety";
6
8
  import { mapNullable } from "@optolith/helpers/nullable";
7
9
  import { createEntityDescriptionCreator } from "../creator.js";
8
10
  import { renderDice, renderDiceAndFlat } from "./partial/dice.js";
11
+ import { attributedName, attributedNameFromInstance } from "./partial/markdown.js";
9
12
  import { additionFormatter, subtractionFormatter } from "./partial/mathOperation.js";
10
13
  import { parensIf } from "./partial/rated/activatable/parensIf.js";
11
14
  import { ResponsiveTextSize } from "./partial/responsiveText.js";
@@ -91,9 +94,19 @@ const renderPrimaryAttributeAndDamageThreshold = (translateMap, getInstanceById,
91
94
  const renderAttackParryModifier = (attackModifier, parryModifier) => attackModifier === undefined && parryModifier === undefined
92
95
  ? "—"
93
96
  : `${attackModifier === undefined ? "—" : sign(attackModifier)}/${parryModifier === undefined ? "—" : sign(parryModifier)}`;
94
- const renderReach = (translateMap, getInstanceById, reach) => reach === undefined
97
+ const renderReach = (translateMap, localeJoin, getInstanceById, reaches) => reaches === undefined
95
98
  ? "—"
96
- : (translateMap(getInstanceById("Reach", reach)?.translations)?.name ?? MISSING_VALUE);
99
+ : localeJoin(reaches
100
+ .map((reach) => {
101
+ const instance = getInstanceById("Reach", reach);
102
+ return [
103
+ instance?.position ?? 0,
104
+ attributedNameFromInstance(translateMap, instance, "equipment", "Reach", reach) ??
105
+ MISSING_VALUE,
106
+ ];
107
+ })
108
+ .toSorted(on(e => e[0], compareNumber))
109
+ .map(e => e[1]), "conjunction");
97
110
  const renderLength = (translate, measurements, length) => length === undefined
98
111
  ? "—"
99
112
  : translate(".input {$value :number} {{{$value} inches}}", {
@@ -102,7 +115,7 @@ const renderLength = (translate, measurements, length) => length === undefined
102
115
  /**
103
116
  * Render combat values of a melee weapon.
104
117
  */
105
- export const renderMeleeWeapon = (translate, translateMap, getInstanceById, measurements, renderDamage, closeCombatTechniqueId, use) => {
118
+ export const renderMeleeWeapon = (translate, translateMap, localeJoin, getInstanceById, measurements, renderDamage, closeCombatTechniqueId, use) => {
106
119
  const combatTechnique = getInstanceById("CloseCombatTechnique", closeCombatTechniqueId);
107
120
  const fields = combatTechnique?.special ?? {
108
121
  can_parry: { kind: "Prohibited" },
@@ -137,7 +150,7 @@ export const renderMeleeWeapon = (translate, translateMap, getInstanceById, meas
137
150
  ? undefined
138
151
  : {
139
152
  label: translate("Reach"),
140
- value: renderReach(translateMap, getInstanceById, use.reach),
153
+ value: renderReach(translateMap, localeJoin, getInstanceById, use.reach),
141
154
  },
142
155
  fields.has_length.kind === "Prohibited"
143
156
  ? undefined
@@ -159,7 +172,7 @@ const renderReloadTime = (translate, reloadTime) => isNotEmpty(reloadTime)
159
172
  const renderRangeBrackets = (rangeBrackets) => `${rangeBrackets.close.toFixed()}/${rangeBrackets.medium.toFixed()}/${rangeBrackets.far.toFixed()}`;
160
173
  const renderAmmunition = (translateMap, getInstanceById, ammunition) => ammunition === undefined
161
174
  ? "—"
162
- : (translateMap(getInstanceById("Ammunition", ammunition)?.translations)?.name ?? MISSING_VALUE);
175
+ : (attributedName(translateMap, getInstanceById, "equipment", ammunition) ?? MISSING_VALUE);
163
176
  const renderMeleeDamage = (translate) => (damage) => renderDiceAndFlat(translate, damage.dice, damage.flat);
164
177
  /**
165
178
  * Render combat values of a ranged weapon.
@@ -713,7 +726,7 @@ export const getEquipmentEntityDescription = createEntityDescriptionCreator(({ g
713
726
  className: "equipment",
714
727
  body: [
715
728
  ...(combatValues?.type === "Weapon"
716
- ? Object.entries(combatValues.values.melee_uses ?? {}).map(([combatTechniqueId, use]) => renderMeleeWeapon(translate, translateMap, getInstanceById, locale.measurementAdjustments, renderMeleeDamage(translate), combatTechniqueId, use))
729
+ ? Object.entries(combatValues.values.melee_uses ?? {}).map(([combatTechniqueId, use]) => renderMeleeWeapon(translate, translateMap, locale.join, getInstanceById, locale.measurementAdjustments, renderMeleeDamage(translate), combatTechniqueId, use))
717
730
  : []),
718
731
  ...(combatValues?.type === "Weapon"
719
732
  ? Object.entries(combatValues.values.ranged_uses ?? {}).map(([combatTechniqueId, use]) => renderRangedWeapon(translate, translateMap, getInstanceById, locale.measurementAdjustments, renderRangedDamage(translate), combatTechniqueId, use))
@@ -1,4 +1,4 @@
1
- import type { ActivatableIdentifier, AdvantageDisadvantagePrerequisites, AnimistPowerPrerequisites, ArcaneTraditionPrerequisites, DerivedCharacteristicPrerequisites, EnhancementPrerequisites, GeneralPrerequisites, GeodeRitualPrerequisites, InfluencePrerequisites, LanguagePrerequisites, LiturgyPrerequisites, PersonalityTraitPrerequisites, PlainGeneralPrerequisites, ProfessionPrerequisites, PublicationPrerequisites, RatedIdentifier, SpecialAbilityIdentifier, SpellworkPrerequisites } from "optolith-database-schema/gen";
1
+ import type { ActivatableIdentifier, AdvantageDisadvantagePrerequisites, AnimistPowerPrerequisites, ArcaneTraditionPrerequisites, DerivedCharacteristicPrerequisites, EnhancementPrerequisites, GeneralPrerequisites, GeodeRitualPrerequisites, InfluencePrerequisites, LanguagePrerequisites, LiturgyPrerequisites, PersonalityTraitPrerequisites, PlainGeneralPrerequisites, ProfessionPrerequisites, PublicationPrerequisites, RatedIdentifier, SpellworkPrerequisites } from "optolith-database-schema/gen";
2
2
  import type { GetAllChildInstancesForParent, GetInstanceById } from "../../../helpers/getTypes.js";
3
3
  import type { LocaleEnvironment } from "../../../helpers/locale.js";
4
4
  import type { GetResolvedSelectOptionById } from "./single/activatable.js";
@@ -18,7 +18,7 @@ export declare const printPlainGeneralPrerequisites: (getInstanceById: GetInstan
18
18
  * Print general prerequisites as a string.
19
19
  */
20
20
  export declare const printGeneralPrerequisites: (getInstanceById: GetInstanceById<"Race" | "Culture" | "PactCategory" | "PactDomain" | "SocialStatus" | "State" | ActivatableIdentifier["kind"] | RatedIdentifier["kind"] | "Property" | "Aspect" | "Enhancement">, getResolvedSelectOptionById: GetResolvedSelectOptionById, locale: LocaleEnvironment, value: GeneralPrerequisites, printPreviousLevelPrerequisites?: {
21
- id: SpecialAbilityIdentifier;
21
+ id: ActivatableIdentifier;
22
22
  levels: number;
23
23
  }, trailingText?: string) => string;
24
24
  /**
@@ -60,7 +60,7 @@ export declare const printAnimistPowerPrerequisites: (getInstanceById: GetInstan
60
60
  /**
61
61
  * Print geode ritual prerequisites as a string.
62
62
  */
63
- export declare const printGeodeRitualPrerequisites: (getInstanceById: GetInstanceById<"Influence">, locale: LocaleEnvironment, value: GeodeRitualPrerequisites) => string;
63
+ export declare const printGeodeRitualPrerequisites: (getInstanceById: GetInstanceById<"Influence" | ActivatableIdentifier["kind"] | "Aspect">, getResolvedSelectOptionById: GetResolvedSelectOptionById, locale: LocaleEnvironment, value: GeodeRitualPrerequisites) => string;
64
64
  /**
65
65
  * Print enhancement prerequisites as a string.
66
66
  */
@@ -201,7 +201,7 @@ export const printAnimistPowerPrerequisites = (getInstanceById, locale, value) =
201
201
  /**
202
202
  * Print geode ritual prerequisites as a string.
203
203
  */
204
- export const printGeodeRitualPrerequisites = (getInstanceById, locale, value) => printPlainPrerequisites(prerequisite => printGeodeRitualPrerequisiteGroup(getInstanceById, locale, prerequisite), locale, value);
204
+ export const printGeodeRitualPrerequisites = (getInstanceById, getResolvedSelectOptionById, locale, value) => printPlainPrerequisites(prerequisite => printGeodeRitualPrerequisiteGroup(getInstanceById, getResolvedSelectOptionById, locale, prerequisite), locale, value);
205
205
  /**
206
206
  * Print enhancement prerequisites as a string.
207
207
  */
@@ -54,7 +54,7 @@ export declare const printAnimistPowerPrerequisiteGroup: (getInstanceById: GetIn
54
54
  /**
55
55
  * Print the translation of a geode ritual prerequisite group.
56
56
  */
57
- export declare const printGeodeRitualPrerequisiteGroup: (getInstanceById: GetInstanceById<"Influence">, locale: LocaleEnvironment, prerequisite: GeodeRitualPrerequisiteGroup) => PrerequisitePart | undefined;
57
+ export declare const printGeodeRitualPrerequisiteGroup: (getInstanceById: GetInstanceById<"Influence" | ActivatableIdentifier["kind"] | "Aspect">, getResolvedSelectOptionById: GetResolvedSelectOptionById, locale: LocaleEnvironment, prerequisite: GeodeRitualPrerequisiteGroup) => PrerequisitePart | undefined;
58
58
  /**
59
59
  * Print the translation of an enhancement prerequisite group.
60
60
  */
@@ -241,13 +241,16 @@ printAnimistPowerPrerequisite(getInstanceById, locale, prerequisite.AnimistPower
241
241
  /**
242
242
  * Print the translation of a geode ritual prerequisite group.
243
243
  */
244
- export const printGeodeRitualPrerequisiteGroup = (getInstanceById, locale, prerequisite) =>
245
- // switch (prerequisite.tag) {
246
- // case "Influence":
247
- // default:
248
- // return assertExhaustive(prerequisite)
249
- // }
250
- printInfluencePrerequisite(getInstanceById, locale, prerequisite.Influence);
244
+ export const printGeodeRitualPrerequisiteGroup = (getInstanceById, getResolvedSelectOptionById, locale, prerequisite) => {
245
+ switch (prerequisite.kind) {
246
+ case "Activatable":
247
+ return printActivatablePrerequisite(getInstanceById, getResolvedSelectOptionById, locale, prerequisite.Activatable, false);
248
+ case "Influence":
249
+ return printInfluencePrerequisite(getInstanceById, locale, prerequisite.Influence);
250
+ default:
251
+ return assertExhaustive(prerequisite);
252
+ }
253
+ };
251
254
  /**
252
255
  * Print the translation of an enhancement prerequisite group.
253
256
  */
@@ -1,4 +1,4 @@
1
- import { attributedName } from "../../markdown.js";
1
+ import { attributedCustomName } from "../../markdown.js";
2
2
  import { MISSING_VALUE } from "../../unknown.js";
3
3
  import { printDisplayOption } from "../displayOption.js";
4
4
  /**
@@ -9,7 +9,7 @@ export const printRatedPrerequisite = (getInstanceById, locale, prerequisite) =>
9
9
  return printDisplayOption(locale.translateMap, prerequisite.display_option);
10
10
  }
11
11
  return {
12
- value: `${attributedName(locale.translateMap, getInstanceById, "prerequisite", prerequisite.id) ?? MISSING_VALUE} ${prerequisite.value.toFixed()}`,
12
+ value: `${attributedCustomName(locale.translateMap, getInstanceById, "prerequisite", (t) => t.abbreviation ?? t.name, prerequisite.id) ?? MISSING_VALUE} ${prerequisite.value.toFixed()}`,
13
13
  sentenceType: undefined,
14
14
  isMeta: false,
15
15
  };
@@ -1,6 +1,6 @@
1
1
  import { mapNullableDefault } from "@optolith/helpers/nullable";
2
2
  import { assertExhaustive } from "@optolith/helpers/typeSafety";
3
- import { divisionFormatter, multiplicationFormatter, } from "../../mathOperation.js";
3
+ import { divisionFormatter, multiplicationFormatter } from "../../mathOperation.js";
4
4
  import { translateR } from "../../reader.js";
5
5
  const renderCheckResultBaseValue = (baseValue) => {
6
6
  switch (baseValue.kind) {
@@ -8,6 +8,8 @@ const renderCheckResultBaseValue = (baseValue) => {
8
8
  return translateR("QL");
9
9
  case "SkillPoints":
10
10
  return translateR("SP");
11
+ case "SkillRating":
12
+ return translateR("SR");
11
13
  default:
12
14
  return assertExhaustive(baseValue);
13
15
  }
@@ -3,7 +3,7 @@ import { isNotNullish, mapNullable } from "@elyukai/utils/nullable";
3
3
  import { sign } from "@elyukai/utils/string/number";
4
4
  import { assertExhaustive } from "@elyukai/utils/typeSafety";
5
5
  import { createEntityDescriptionCreator } from "../creator.js";
6
- import { renderDice, renderDiceAndFlat } from "./partial/dice.js";
6
+ import { renderDice } from "./partial/dice.js";
7
7
  import { renderAlternativeNames, renderChance, renderLaboratoryLevel, renderResistance, } from "./partial/herbary.js";
8
8
  import { renderMathOperation } from "./partial/mathOperation.js";
9
9
  import { printPlainGeneralPrerequisites } from "./partial/prerequisites/index.js";
@@ -93,6 +93,12 @@ const renderSourceTypeBasedValues = (translate, translateMap, getInstanceById, s
93
93
  return {
94
94
  level: renderLevel(translate, translateMap, sourceType.AnimalVenom.level),
95
95
  sourceType: translate("animal venom"),
96
+ tradeSecret: mapNullable(sourceType.AnimalVenom.complexity, complexity => complexity.kind === "Complex"
97
+ ? {
98
+ apValue: complexity.Complex.ap_value,
99
+ prerequisites: complexity.Complex.prerequisites,
100
+ }
101
+ : undefined),
96
102
  };
97
103
  case "AlchemicalPoison": {
98
104
  const translation = translateMap(sourceType.AlchemicalPoison.translations);
@@ -156,10 +162,19 @@ const renderStart = (translate, translateMap, start) => {
156
162
  switch (start.kind) {
157
163
  case "Immediate":
158
164
  return translate("immediate");
159
- case "Constant":
160
- return formatTimeSpan(translate, ResponsiveTextSize.Full, start.Constant.unit, start.Constant.value);
161
- case "DiceBased":
162
- return formatTimeSpan(translate, ResponsiveTextSize.Full, start.DiceBased.unit, renderDiceAndFlat(translate, start.DiceBased.dice, start.DiceBased.flat));
165
+ case "ExpressionBased":
166
+ return renderMathOperation(start.ExpressionBased.value, value => {
167
+ switch (value.kind) {
168
+ case "Constant":
169
+ return value.Constant.toFixed();
170
+ case "Dice":
171
+ return renderDice(translate, value.Dice);
172
+ case "CircleOfDamnation":
173
+ return translate("CoD");
174
+ default:
175
+ return assertExhaustive(value);
176
+ }
177
+ });
163
178
  case "Indefinite":
164
179
  return translateMap(start.Indefinite.translations)?.description ?? MISSING_VALUE;
165
180
  default:
@@ -170,10 +185,6 @@ const renderDuration = (translate, translateMap, duration) => {
170
185
  switch (duration.kind) {
171
186
  case "Instant":
172
187
  return translate("instant");
173
- case "Constant":
174
- return formatTimeSpan(translate, ResponsiveTextSize.Full, duration.Constant.unit, duration.Constant.value);
175
- case "DiceBased":
176
- return formatTimeSpan(translate, ResponsiveTextSize.Full, duration.DiceBased.unit, renderDiceAndFlat(translate, duration.DiceBased.dice, duration.DiceBased.flat));
177
188
  case "ExpressionBased":
178
189
  return renderMathOperation(duration.ExpressionBased.value, value => {
179
190
  switch (value.kind) {
@@ -1,11 +1,11 @@
1
- import type { MagicalActionIdentifier, RatedIdentifier, SpecialAbilityIdentifier } from "optolith-database-schema/gen";
1
+ import type { MagicalActionIdentifier, ProfessionSpecialAbilityIdentifier, RatedIdentifier } from "optolith-database-schema/gen";
2
2
  import type { GetAllChildInstancesForParent, GetAllInstances, GetInstanceById } from "../helpers/getTypes.js";
3
3
  import { type GetResolvedSelectOptionById } from "./partial/prerequisites/single/activatable.js";
4
4
  /**
5
5
  * Get a JSON representation of the rules text for a profession version.
6
6
  */
7
7
  export declare const getProfessionVersionEntityDescription: import("../creator.js").EntityDescriptionCreator<"ProfessionVersion", {
8
- getInstanceById: GetInstanceById<"Publication" | "ExperienceLevel" | "Advantage" | "Disadvantage" | SpecialAbilityIdentifier["kind"] | "Aspect" | RatedIdentifier["kind"] | "Race" | "Culture" | MagicalActionIdentifier["kind"] | "SkillGroup" | "Cantrip" | "Blessing">;
8
+ getInstanceById: GetInstanceById<"Publication" | "ExperienceLevel" | "Advantage" | "Disadvantage" | ProfessionSpecialAbilityIdentifier["kind"] | "Aspect" | RatedIdentifier["kind"] | "Race" | "Culture" | MagicalActionIdentifier["kind"] | "SkillGroup" | "Cantrip" | "Blessing">;
9
9
  getAllInstances: GetAllInstances<"SkillGroup">;
10
10
  getChildInstancesForInstanceId: GetAllChildInstancesForParent<"ProfessionPackage" | "ProfessionVariant">;
11
11
  getResolvedSelectOptionById: GetResolvedSelectOptionById;
@@ -7,7 +7,7 @@ import { omitKeys } from "@elyukai/utils/object";
7
7
  import { compareNumber } from "@elyukai/utils/ordering";
8
8
  import { Reader } from "@elyukai/utils/reader";
9
9
  import { assertExhaustive } from "@elyukai/utils/typeSafety";
10
- import { fromUniformCase } from "tsondb/schema/gen";
10
+ import { Case, fromUniformCase } from "tsondb/schema/gen";
11
11
  import { createEntityDescriptionCreator } from "../creator.js";
12
12
  import { combineNameComponents, getNameComponents, renderActivatableNameComponents, renderCombinedActivatableNameComponents, } from "./partial/activatableNameChunks.js";
13
13
  import { renderCommonnessRatedAdvantagesOrDisadvantages, renderValueWithPossibleTranslation, } from "./partial/commonnessRatedAdvantagesAndDisadvantages.js";
@@ -103,7 +103,7 @@ const renderCombatTechniquesOption = (option) => isNotEmpty(option.fixed)
103
103
  })), firstTextR);
104
104
  const completeTextR = fixedTextR.then(fixedText => option.rest_rating_modifier === undefined
105
105
  ? Reader.of(fixedText)
106
- : translateR("{$previous}, all others {$rating}", {
106
+ : translateR("{$previous}, the others {$rating}", {
107
107
  previous: fixedText,
108
108
  rating: option.rest_rating_modifier + 6,
109
109
  }));
@@ -123,10 +123,36 @@ const getTotalingAPValues = (professionPackages, selector) => {
123
123
  options[0].ap_value
124
124
  : options.map(option => option?.ap_value ?? 0).join("/");
125
125
  };
126
- const renderSkillsOption = (professionPackages, skillGroup) => mapNullable(getTotalingAPValues(professionPackages, pkg => pkg.content.options?.skills?.group === skillGroup.id ? pkg.content.options.skills : undefined), apValue => translateR("{$apValue} AP to improve other {$skillsOfGroup}", {
127
- apValue,
128
- skillsOfGroup: skillGroup.nameOfSkillsOfGroup,
129
- })) ?? Reader.of(undefined);
126
+ const renderSkillsOption = (professionPackages, skillGroup) => allSame(professionPackages.filter(pkg => pkg.content.options?.skills?.group === skillGroup.id), on(pkg => pkg.content.options?.skills?.specific, deepEqual))
127
+ ? (mapNullable(getTotalingAPValues(professionPackages, pkg => pkg.content.options?.skills?.group === skillGroup.id
128
+ ? pkg.content.options.skills
129
+ : undefined), apValue => professionPackages[0].content.options?.skills?.specific === undefined
130
+ ? translateR("{$apValue} AP to improve other {$skillsOfGroup}", {
131
+ apValue,
132
+ skillsOfGroup: skillGroup.nameOfSkillsOfGroup,
133
+ })
134
+ : Reader.traverse(professionPackages[0].content.options.skills.specific, id => attributedNameR("profession", "Skill", id).map(name => name ?? MISSING_VALUE))
135
+ .thenW(localeSortR)
136
+ .thenW(list => translateR("{$apValue} AP to distribute among the following skills: {$list}", {
137
+ apValue,
138
+ list: list.join(", "),
139
+ }))) ?? Reader.of(undefined))
140
+ : Reader.traverse(professionPackages, pkg => {
141
+ const skillsOption = pkg.content.options?.skills;
142
+ return skillsOption === undefined
143
+ ? Reader.of("—")
144
+ : skillsOption.specific === undefined
145
+ ? translateR("{$apValue} AP to improve other {$skillsOfGroup}", {
146
+ apValue: skillsOption.ap_value,
147
+ skillsOfGroup: skillGroup.nameOfSkillsOfGroup,
148
+ })
149
+ : Reader.traverse(skillsOption.specific, id => attributedNameR("profession", "Skill", id).map(name => name ?? MISSING_VALUE))
150
+ .thenW(localeSortR)
151
+ .thenW(list => translateR("{$apValue} AP to distribute among the following skills: {$list}", {
152
+ apValue: skillsOption.ap_value,
153
+ list: list.join(", "),
154
+ }));
155
+ }).map(list => list.join(" / "));
130
156
  const renderCantripList = (cantripIds) => Reader.traverse(cantripIds, id => attributedNameR("profession", "Cantrip", id).map(name => name ?? MISSING_VALUE))
131
157
  .thenW(localeSortR)
132
158
  .map(list => list.join(", "));
@@ -256,7 +282,69 @@ const renderVariantLiturgiesOption = (baseOptions, variantOptions) => renderVari
256
282
  apValue,
257
283
  })),
258
284
  });
259
- const getSpecialAbilityNameComponents = (getInstanceById, getResolvedSelectOptionById, translate, option) => {
285
+ const getEnhancementBaseName = (translate, enhancement, parentName) => {
286
+ switch (enhancement.parent.kind) {
287
+ case "Spell":
288
+ return translate("Spell Enhancement {$spell}", { spell: parentName });
289
+ case "Ritual":
290
+ return translate("Ritual Enhancement {$ritual}", { ritual: parentName });
291
+ case "LiturgicalChant":
292
+ case "Ceremony":
293
+ return translate("Liturgical Enhancement {$liturgicalChantOrCeremony}", {
294
+ liturgicalChantOrCeremony: parentName,
295
+ });
296
+ default:
297
+ return assertExhaustive(enhancement.parent);
298
+ }
299
+ };
300
+ const isEnhancementNameComponents = (components) => components.id.kind === "Enhancement";
301
+ const isActivatableNameComponents = (components) => !isEnhancementNameComponents(components);
302
+ const getEnhancementNameComponents = (getInstanceById, translate, translateMap, id) => {
303
+ const instance = getInstanceById("Enhancement", id);
304
+ if (instance === undefined) {
305
+ return undefined;
306
+ }
307
+ return {
308
+ id: Case("Enhancement", id),
309
+ parent: instance.parent,
310
+ base: getEnhancementBaseName(translate, instance, attributedName(translateMap, getInstanceById, "profession", instance.parent) ?? MISSING_VALUE),
311
+ options: translateMap(instance.translations)?.name ?? MISSING_VALUE,
312
+ };
313
+ };
314
+ const renderEnhancementNameComponents = (chunk) => [chunk.base, chunk.options].join(" ");
315
+ const isCombinedEnhancementNameComponents = (components) => Array.isArray(components.id);
316
+ const combineEnhancementNameComponents = (chunks) => {
317
+ if (allSame(chunks, (a, b) => deepEqual(a.parent, b.parent)) && isNotEmpty(chunks)) {
318
+ const [first, ...rest] = chunks;
319
+ return {
320
+ id: [first.id, ...rest.map(chunk => chunk.id)],
321
+ parent: first.parent,
322
+ base: first.base,
323
+ options: [
324
+ [first.id, first.options],
325
+ ...rest.map((chunk) => [
326
+ chunk.id,
327
+ chunk.options,
328
+ ]),
329
+ ],
330
+ };
331
+ }
332
+ else {
333
+ return undefined;
334
+ }
335
+ };
336
+ const renderCombinedEnhancementNameComponents = (chunk) => [
337
+ chunk.base,
338
+ chunk.options
339
+ .map(([optionId, optionName]) => attributedInstance(optionName, optionId.kind, optionId.Enhancement, {
340
+ context: `"profession"`,
341
+ }))
342
+ .join(" + "),
343
+ ].join(" ");
344
+ const getSpecialAbilityNameComponents = (getInstanceById, getResolvedSelectOptionById, translate, translateMap, option) => {
345
+ if (option.id.kind === "Enhancement") {
346
+ return getEnhancementNameComponents(getInstanceById, translate, translateMap, option.id.Enhancement);
347
+ }
260
348
  const instance = getInstanceById(option.id);
261
349
  if (instance === undefined) {
262
350
  return undefined;
@@ -264,26 +352,33 @@ const getSpecialAbilityNameComponents = (getInstanceById, getResolvedSelectOptio
264
352
  return getNameComponents(translate, option.id, option.options, option.level, "nameBuilderRules" in instance ? instance.nameBuilderRules : undefined, instance.translations, t => t.name, getResolvedSelectOptionById, true);
265
353
  };
266
354
  const wrapActivatableInAttributedString = (text, id) => attributedInstance(text, id.kind, fromUniformCase(id), { context: '"prerequisite"' });
355
+ const renderSingleActivatableNameChunk = (translateMap, chunk) => chunk === undefined
356
+ ? MISSING_VALUE
357
+ : wrapActivatableInAttributedString(isEnhancementNameComponents(chunk)
358
+ ? renderEnhancementNameComponents(chunk)
359
+ : renderActivatableNameComponents(translateMap, chunk, false), chunk.id);
267
360
  const renderSpecialAbilityName = (specialAbility) => Reader.asks(({ translate, translateMap, localeJoin, getInstanceById, getResolvedSelectOptionById, }) => {
268
361
  switch (specialAbility.kind) {
269
362
  case "Constant": {
270
- const chunk = getSpecialAbilityNameComponents(getInstanceById, getResolvedSelectOptionById, translate, specialAbility.Constant);
271
- return chunk === undefined
272
- ? MISSING_VALUE
273
- : wrapActivatableInAttributedString(renderActivatableNameComponents(translateMap, chunk, false), chunk.id);
363
+ const chunk = getSpecialAbilityNameComponents(getInstanceById, getResolvedSelectOptionById, translate, translateMap, specialAbility.Constant);
364
+ return renderSingleActivatableNameChunk(translateMap, chunk);
274
365
  }
275
366
  case "Selection": {
276
- const chunks = specialAbility.Selection.options.map(option => getSpecialAbilityNameComponents(getInstanceById, getResolvedSelectOptionById, translate, option));
367
+ const chunks = specialAbility.Selection.options.map(option => getSpecialAbilityNameComponents(getInstanceById, getResolvedSelectOptionById, translate, translateMap, option));
277
368
  const possiblyCombined = isNotEmpty(specialAbility.Selection.options) && chunks.every(isNotNullish)
278
- ? combineNameComponents(chunks)
369
+ ? chunks.every(isEnhancementNameComponents)
370
+ ? combineEnhancementNameComponents(chunks)
371
+ : chunks.every(isActivatableNameComponents)
372
+ ? combineNameComponents(chunks)
373
+ : undefined
279
374
  : undefined;
280
375
  if (possiblyCombined === undefined) {
281
- return localeJoin(chunks.map(chunk => chunk === undefined
282
- ? MISSING_VALUE
283
- : wrapActivatableInAttributedString(renderActivatableNameComponents(translateMap, chunk, false), chunk.id)), "disjunction");
376
+ return localeJoin(chunks.map(chunk => renderSingleActivatableNameChunk(translateMap, chunk)), "disjunction");
284
377
  }
285
378
  else {
286
- return wrapActivatableInAttributedString(renderCombinedActivatableNameComponents(translateMap, possiblyCombined, false, list => localeJoin(list, "disjunction")), possiblyCombined.id);
379
+ return isCombinedEnhancementNameComponents(possiblyCombined)
380
+ ? renderCombinedEnhancementNameComponents(possiblyCombined)
381
+ : wrapActivatableInAttributedString(renderCombinedActivatableNameComponents(translateMap, possiblyCombined, false, list => localeJoin(list, "disjunction")), possiblyCombined.id);
287
382
  }
288
383
  }
289
384
  default:
@@ -3,7 +3,7 @@ import type { CountInstances, GetAllChildInstancesForParent, GetInstanceById } f
3
3
  * Get a JSON representation of the rules text for a race.
4
4
  */
5
5
  export declare const getRaceEntityDescription: import("../creator.js").EntityDescriptionCreator<"Race", {
6
- getInstanceById: GetInstanceById<"Publication" | "Attribute" | "Advantage" | "Disadvantage" | "Culture">;
6
+ getInstanceById: GetInstanceById<"Publication" | "Attribute" | "Advantage" | "Disadvantage" | "Culture" | "DerivedCharacteristic">;
7
7
  countInstances: CountInstances<"Attribute">;
8
8
  getChildInstancesForInstanceId: GetAllChildInstancesForParent<"RaceVariant">;
9
9
  }, import("../index.js").EntityDescription>;