@optolith/entity-descriptions 0.2.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.
Files changed (191) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/lib/creator.d.ts +32 -0
  3. package/lib/creator.js +72 -0
  4. package/lib/entities/activatable.d.ts +52 -0
  5. package/lib/entities/activatable.js +609 -0
  6. package/lib/entities/alternativeRule.d.ts +7 -0
  7. package/lib/entities/alternativeRule.js +21 -0
  8. package/lib/entities/attribute.d.ts +6 -0
  9. package/lib/entities/attribute.js +15 -0
  10. package/lib/entities/combatTechnique.d.ts +5 -7
  11. package/lib/entities/combatTechnique.js +34 -24
  12. package/lib/entities/condition.d.ts +12 -0
  13. package/lib/entities/condition.js +63 -0
  14. package/lib/entities/culture.d.ts +8 -0
  15. package/lib/entities/culture.js +309 -0
  16. package/lib/entities/curriculum.d.ts +11 -0
  17. package/lib/entities/curriculum.js +266 -0
  18. package/lib/entities/derivedCharacteristic.d.ts +9 -0
  19. package/lib/entities/derivedCharacteristic.js +91 -0
  20. package/lib/entities/disease.d.ts +9 -0
  21. package/lib/entities/disease.js +88 -0
  22. package/lib/entities/elixir.d.ts +10 -0
  23. package/lib/entities/elixir.js +76 -0
  24. package/lib/entities/equipment.d.ts +24 -0
  25. package/lib/entities/equipment.js +605 -0
  26. package/lib/entities/equipmentPackage.d.ts +8 -0
  27. package/lib/entities/equipmentPackage.js +300 -0
  28. package/lib/entities/experienceLevel.d.ts +3 -2
  29. package/lib/entities/experienceLevel.js +33 -28
  30. package/lib/entities/focusRule.d.ts +3 -4
  31. package/lib/entities/focusRule.js +13 -5
  32. package/lib/entities/influence.d.ts +11 -0
  33. package/lib/entities/influence.js +43 -0
  34. package/lib/entities/liturgicalChant.d.ts +13 -23
  35. package/lib/entities/liturgicalChant.js +171 -131
  36. package/lib/entities/optionalRule.d.ts +3 -2
  37. package/lib/entities/optionalRule.js +4 -3
  38. package/lib/entities/partial/activatableNameChunks.d.ts +58 -0
  39. package/lib/entities/partial/activatableNameChunks.js +356 -0
  40. package/lib/entities/partial/adventurePointsValue.d.ts +9 -0
  41. package/lib/entities/partial/adventurePointsValue.js +243 -0
  42. package/lib/entities/partial/animalTypes.d.ts +11 -0
  43. package/lib/entities/partial/animalTypes.js +13 -0
  44. package/lib/entities/partial/commonnessRatedAdvantagesAndDisadvantages.d.ts +16 -0
  45. package/lib/entities/partial/commonnessRatedAdvantagesAndDisadvantages.js +40 -0
  46. package/lib/entities/partial/dice.d.ts +10 -0
  47. package/lib/entities/partial/dice.js +13 -0
  48. package/lib/entities/partial/enhancements.d.ts +7 -0
  49. package/lib/entities/partial/enhancements.js +26 -0
  50. package/lib/entities/partial/herbary.d.ts +28 -0
  51. package/lib/entities/partial/herbary.js +65 -0
  52. package/lib/entities/partial/map.d.ts +49 -0
  53. package/lib/entities/partial/map.js +43 -0
  54. package/lib/entities/partial/mathOperation.d.ts +36 -0
  55. package/lib/entities/partial/mathOperation.js +107 -0
  56. package/lib/entities/partial/prerequisites/displayOption.d.ts +7 -0
  57. package/lib/entities/partial/prerequisites/displayOption.js +19 -0
  58. package/lib/entities/partial/prerequisites/index.d.ts +67 -0
  59. package/lib/entities/partial/prerequisites/index.js +189 -0
  60. package/lib/entities/partial/prerequisites/part.d.ts +26 -0
  61. package/lib/entities/partial/prerequisites/part.js +88 -0
  62. package/lib/entities/partial/prerequisites/prerequisiteGroups.d.ts +65 -0
  63. package/lib/entities/partial/prerequisites/prerequisiteGroups.js +278 -0
  64. package/lib/entities/partial/prerequisites/single/activatable.d.ts +18 -0
  65. package/lib/entities/partial/prerequisites/single/activatable.js +40 -0
  66. package/lib/entities/partial/prerequisites/single/animistPower.d.ts +8 -0
  67. package/lib/entities/partial/prerequisites/single/animistPower.js +23 -0
  68. package/lib/entities/partial/prerequisites/single/blessedTradition.d.ts +7 -0
  69. package/lib/entities/partial/prerequisites/single/blessedTradition.js +32 -0
  70. package/lib/entities/partial/prerequisites/single/commonSuggestedByRCP.d.ts +6 -0
  71. package/lib/entities/partial/prerequisites/single/commonSuggestedByRCP.js +19 -0
  72. package/lib/entities/partial/prerequisites/single/culture.d.ts +8 -0
  73. package/lib/entities/partial/prerequisites/single/culture.js +20 -0
  74. package/lib/entities/partial/prerequisites/single/enhancement.d.ts +8 -0
  75. package/lib/entities/partial/prerequisites/single/enhancement.js +41 -0
  76. package/lib/entities/partial/prerequisites/single/influence.d.ts +8 -0
  77. package/lib/entities/partial/prerequisites/single/influence.js +17 -0
  78. package/lib/entities/partial/prerequisites/single/magicalTradition.d.ts +7 -0
  79. package/lib/entities/partial/prerequisites/single/magicalTradition.js +27 -0
  80. package/lib/entities/partial/prerequisites/single/noOtherAncestorBloodAdvantage.d.ts +6 -0
  81. package/lib/entities/partial/prerequisites/single/noOtherAncestorBloodAdvantage.js +8 -0
  82. package/lib/entities/partial/prerequisites/single/pact.d.ts +8 -0
  83. package/lib/entities/partial/prerequisites/single/pact.js +30 -0
  84. package/lib/entities/partial/prerequisites/single/personalityTrait.d.ts +8 -0
  85. package/lib/entities/partial/prerequisites/single/personalityTrait.js +28 -0
  86. package/lib/entities/partial/prerequisites/single/primaryAttribute.d.ts +7 -0
  87. package/lib/entities/partial/prerequisites/single/primaryAttribute.js +15 -0
  88. package/lib/entities/partial/prerequisites/single/profession.d.ts +8 -0
  89. package/lib/entities/partial/prerequisites/single/profession.js +19 -0
  90. package/lib/entities/partial/prerequisites/single/publication.d.ts +8 -0
  91. package/lib/entities/partial/prerequisites/single/publication.js +19 -0
  92. package/lib/entities/partial/prerequisites/single/race.d.ts +8 -0
  93. package/lib/entities/partial/prerequisites/single/race.js +20 -0
  94. package/lib/entities/partial/prerequisites/single/rated.d.ts +8 -0
  95. package/lib/entities/partial/prerequisites/single/rated.js +41 -0
  96. package/lib/entities/partial/prerequisites/single/ratedMinimumNumber.d.ts +8 -0
  97. package/lib/entities/partial/prerequisites/single/ratedMinimumNumber.js +89 -0
  98. package/lib/entities/partial/prerequisites/single/ratedSum.d.ts +8 -0
  99. package/lib/entities/partial/prerequisites/single/ratedSum.js +21 -0
  100. package/lib/entities/partial/prerequisites/single/rule.d.ts +7 -0
  101. package/lib/entities/partial/prerequisites/single/rule.js +4 -0
  102. package/lib/entities/partial/prerequisites/single/sex.d.ts +7 -0
  103. package/lib/entities/partial/prerequisites/single/sex.js +19 -0
  104. package/lib/entities/partial/prerequisites/single/sexualCharacteristic.d.ts +7 -0
  105. package/lib/entities/partial/prerequisites/single/sexualCharacteristic.js +21 -0
  106. package/lib/entities/partial/prerequisites/single/socialStatus.d.ts +8 -0
  107. package/lib/entities/partial/prerequisites/single/socialStatus.js +21 -0
  108. package/lib/entities/partial/prerequisites/single/state.d.ts +8 -0
  109. package/lib/entities/partial/prerequisites/single/state.js +20 -0
  110. package/lib/entities/partial/prerequisites/single/text.d.ts +7 -0
  111. package/lib/entities/partial/prerequisites/single/text.js +9 -0
  112. package/lib/entities/partial/professions.d.ts +15 -0
  113. package/lib/entities/partial/professions.js +19 -0
  114. package/lib/entities/partial/rated/activatable/castingTime.d.ts +16 -7
  115. package/lib/entities/partial/rated/activatable/castingTime.js +35 -20
  116. package/lib/entities/partial/rated/activatable/checkResultBased.d.ts +18 -3
  117. package/lib/entities/partial/rated/activatable/checkResultBased.js +18 -10
  118. package/lib/entities/partial/rated/activatable/cost.d.ts +88 -8
  119. package/lib/entities/partial/rated/activatable/cost.js +183 -115
  120. package/lib/entities/partial/rated/activatable/duration.d.ts +36 -13
  121. package/lib/entities/partial/rated/activatable/duration.js +72 -61
  122. package/lib/entities/partial/rated/activatable/effect.d.ts +4 -4
  123. package/lib/entities/partial/rated/activatable/effect.js +32 -25
  124. package/lib/entities/partial/rated/activatable/index.d.ts +31 -27
  125. package/lib/entities/partial/rated/activatable/index.js +63 -28
  126. package/lib/entities/partial/rated/activatable/isMinimumMaximum.d.ts +5 -6
  127. package/lib/entities/partial/rated/activatable/isMinimumMaximum.js +8 -5
  128. package/lib/entities/partial/rated/activatable/nonModifiableSuffix.d.ts +2 -4
  129. package/lib/entities/partial/rated/activatable/nonModifiableSuffix.js +7 -42
  130. package/lib/entities/partial/rated/activatable/range.d.ts +22 -14
  131. package/lib/entities/partial/rated/activatable/range.js +54 -54
  132. package/lib/entities/partial/rated/activatable/speed.d.ts +11 -2
  133. package/lib/entities/partial/rated/activatable/speed.js +14 -1
  134. package/lib/entities/partial/rated/activatable/targetCategory.d.ts +4 -5
  135. package/lib/entities/partial/rated/activatable/targetCategory.js +19 -24
  136. package/lib/entities/partial/rated/improvementCost.d.ts +8 -4
  137. package/lib/entities/partial/rated/improvementCost.js +9 -4
  138. package/lib/entities/partial/rated/skillCheck.d.ts +20 -17
  139. package/lib/entities/partial/rated/skillCheck.js +56 -54
  140. package/lib/entities/partial/reader.d.ts +266 -0
  141. package/lib/entities/partial/reader.js +175 -0
  142. package/lib/entities/partial/responsiveText.d.ts +10 -5
  143. package/lib/entities/partial/responsiveText.js +19 -3
  144. package/lib/entities/partial/units/energy.d.ts +5 -8
  145. package/lib/entities/partial/units/energy.js +5 -23
  146. package/lib/entities/partial/units/length.d.ts +20 -2
  147. package/lib/entities/partial/units/length.js +24 -5
  148. package/lib/entities/partial/units/timeSpan.d.ts +25 -4
  149. package/lib/entities/partial/units/timeSpan.js +27 -15
  150. package/lib/entities/partial/unknown.d.ts +5 -1
  151. package/lib/entities/partial/unknown.js +5 -1
  152. package/lib/entities/personalityTrait.d.ts +7 -0
  153. package/lib/entities/personalityTrait.js +56 -0
  154. package/lib/entities/poison.d.ts +12 -0
  155. package/lib/entities/poison.js +368 -0
  156. package/lib/entities/profession.d.ts +12 -0
  157. package/lib/entities/profession.js +585 -0
  158. package/lib/entities/race.d.ts +9 -0
  159. package/lib/entities/race.js +146 -0
  160. package/lib/entities/sexPractice.d.ts +6 -0
  161. package/lib/entities/sexPractice.js +33 -0
  162. package/lib/entities/skill.d.ts +9 -9
  163. package/lib/entities/skill.js +124 -91
  164. package/lib/entities/spell.d.ts +86 -26
  165. package/lib/entities/spell.js +842 -147
  166. package/lib/entities/state.d.ts +6 -0
  167. package/lib/entities/state.js +17 -0
  168. package/lib/helpers/enums.d.ts +11 -0
  169. package/lib/helpers/enums.js +6 -0
  170. package/lib/helpers/getTypes.d.ts +12 -482
  171. package/lib/helpers/getTypes.js +1 -0
  172. package/lib/helpers/identifiers.d.ts +314 -0
  173. package/lib/helpers/identifiers.js +333 -0
  174. package/lib/helpers/locale.d.ts +21 -2
  175. package/lib/helpers/translate.d.ts +47 -5
  176. package/lib/helpers/translate.js +13 -6
  177. package/lib/index.d.ts +854 -21
  178. package/lib/index.js +182 -17
  179. package/lib/references/index.d.ts +6 -3
  180. package/lib/references/index.js +25 -33
  181. package/lib/references/page.d.ts +1 -1
  182. package/lib/references/page.js +14 -14
  183. package/lib/references/pageRange.d.ts +1 -5
  184. package/lib/references/pageRange.js +7 -16
  185. package/lib/tsconfig.tsbuildinfo +1 -0
  186. package/package.json +31 -10
  187. package/.prettierrc.yml +0 -1
  188. package/lib/entities/partial/rated/activatable/entity.d.ts +0 -11
  189. package/lib/entities/partial/rated/activatable/entity.js +0 -12
  190. package/lib/references/occurrence.d.ts +0 -4
  191. package/lib/references/occurrence.js +0 -3
@@ -0,0 +1,605 @@
1
+ import { ensureNonEmpty, isNotEmpty } from "@elyukai/utils/array/nonEmpty";
2
+ import { isNotNullish } from "@elyukai/utils/nullable";
3
+ import { sign } from "@elyukai/utils/string/number";
4
+ import { assertExhaustive } from "@elyukai/utils/typeSafety";
5
+ import { mapNullable } from "@optolith/helpers/nullable";
6
+ import { createEntityDescriptionCreator } from "../creator.js";
7
+ import { renderDice, renderDiceAndFlat } from "./partial/dice.js";
8
+ import { additionFormatter, subtractionFormatter } from "./partial/mathOperation.js";
9
+ import { parensIf } from "./partial/rated/activatable/parensIf.js";
10
+ import { ResponsiveTextSize } from "./partial/responsiveText.js";
11
+ import { formatTimeSpan } from "./partial/units/timeSpan.js";
12
+ import { MISSING_VALUE, UNHANDLED_VALUE } from "./partial/unknown.js";
13
+ /**
14
+ * Get the name of an equipment item.
15
+ */
16
+ export const getEquipmentName = (translate, translateMap, getInstanceById, entry) => {
17
+ switch (entry.entity) {
18
+ case "ClothingPackage": {
19
+ const socialStatusName = translateMap(getInstanceById("SocialStatus", entry.content.socialStatus)?.translations)
20
+ ?.name ?? MISSING_VALUE;
21
+ return translate("Clothing Package {$socialStatus}", {
22
+ socialStatus: socialStatusName,
23
+ });
24
+ }
25
+ case "Ammunition":
26
+ case "Animal":
27
+ case "AnimalCare":
28
+ case "Armor":
29
+ case "BandageOrRemedy":
30
+ case "Book":
31
+ case "CeremonialItem":
32
+ case "Clothes":
33
+ case "Container":
34
+ case "Elixir":
35
+ case "EquipmentOfBlessedOnes":
36
+ case "GemOrPreciousStone":
37
+ case "IlluminationLightSource":
38
+ case "IlluminationRefillOrSupply":
39
+ case "Jewelry":
40
+ case "Laboratory":
41
+ case "Liebesspielzeug":
42
+ case "LuxuryGood":
43
+ case "MagicalArtifact":
44
+ case "MusicalInstrument":
45
+ case "Newspaper":
46
+ case "OrienteeringAid":
47
+ case "Poison":
48
+ case "RopeOrChain":
49
+ case "Stationery":
50
+ case "ThievesTool":
51
+ case "ToolOfTheTrade":
52
+ case "TravelGearOrTool":
53
+ case "Vehicle":
54
+ case "Weapon":
55
+ case "WeaponAccessory":
56
+ case "WorkingSupernaturalCreature":
57
+ return translateMap(entry.content.translations)?.name ?? MISSING_VALUE;
58
+ default:
59
+ return assertExhaustive(entry);
60
+ }
61
+ };
62
+ const renderPrimaryAttributeAndDamageThreshold = (translateMap, getInstanceById, closeCombatTechnique, damageThreshold) => {
63
+ if (damageThreshold === undefined) {
64
+ return "—";
65
+ }
66
+ const getAttrAbbrv = (attrId) => translateMap(getInstanceById("Attribute", attrId)?.translations)?.abbreviation ?? MISSING_VALUE;
67
+ switch (damageThreshold.kind) {
68
+ case "Default":
69
+ return `${closeCombatTechnique?.primary_attribute.map(getAttrAbbrv).join("/") ?? MISSING_VALUE} ${damageThreshold.Default.threshold}`;
70
+ case "List":
71
+ if (isNotEmpty(damageThreshold.List.list)) {
72
+ const { list } = damageThreshold.List;
73
+ if (list.some(item => item.threshold !== list[0].threshold)) {
74
+ return list.map(item => `${getAttrAbbrv(item.attribute)} ${item.threshold}`).join("/");
75
+ }
76
+ else {
77
+ return `${list.map(item => getAttrAbbrv(item.attribute)).join("/")} ${list[0].threshold}`;
78
+ }
79
+ }
80
+ else {
81
+ return MISSING_VALUE;
82
+ }
83
+ default:
84
+ return assertExhaustive(damageThreshold);
85
+ }
86
+ };
87
+ const renderAttackParryModifier = (attackModifier, parryModifier) => attackModifier === undefined && parryModifier === undefined
88
+ ? "—"
89
+ : `${attackModifier === undefined ? "—" : sign(attackModifier)}/${parryModifier === undefined ? "—" : sign(parryModifier)}`;
90
+ const renderReach = (translateMap, getInstanceById, reach) => reach === undefined
91
+ ? "—"
92
+ : (translateMap(getInstanceById("Reach", reach)?.translations)?.name ?? MISSING_VALUE);
93
+ const renderLength = (translate, measurements, length) => length === undefined
94
+ ? "—"
95
+ : translate(".input {$value :number} {{{$value} inches}}", {
96
+ value: length * measurements.halffingersMultiplier,
97
+ });
98
+ /**
99
+ * Render combat values of a melee weapon.
100
+ */
101
+ export const renderMeleeWeapon = (translate, translateMap, getInstanceById, measurements, renderDamage, closeCombatTechniqueId, use) => {
102
+ const combatTechnique = getInstanceById("CloseCombatTechnique", closeCombatTechniqueId);
103
+ const fields = combatTechnique?.special ?? {
104
+ can_parry: { kind: "Prohibited" },
105
+ has_damage_threshold: { kind: "Prohibited" },
106
+ has_reach: { kind: "Prohibited" },
107
+ has_length: { kind: "Prohibited" },
108
+ has_shield_size: { kind: "Prohibited" },
109
+ };
110
+ return {
111
+ type: "labeled",
112
+ label: translate("Combat Technique {$name}", {
113
+ name: translateMap(combatTechnique?.translations)?.name ?? MISSING_VALUE,
114
+ }),
115
+ value: {
116
+ type: "definitionList",
117
+ items: [
118
+ {
119
+ label: translate("Damage Points"),
120
+ value: renderDamage(use.damage),
121
+ },
122
+ fields.has_damage_threshold.kind === "Prohibited"
123
+ ? undefined
124
+ : {
125
+ label: translate("Primary Attribute + Damage Threshold"),
126
+ value: renderPrimaryAttributeAndDamageThreshold(translateMap, getInstanceById, combatTechnique, use.damage_threshold),
127
+ },
128
+ {
129
+ label: translate("Attack/Parry Modifier"),
130
+ value: renderAttackParryModifier(use.attackModifier, use.parryModifier),
131
+ },
132
+ fields.has_reach.kind === "Prohibited"
133
+ ? undefined
134
+ : {
135
+ label: translate("Reach"),
136
+ value: renderReach(translateMap, getInstanceById, use.reach),
137
+ },
138
+ fields.has_length.kind === "Prohibited"
139
+ ? undefined
140
+ : {
141
+ label: translate("Length"),
142
+ value: renderLength(translate, measurements, use.length),
143
+ },
144
+ ],
145
+ },
146
+ };
147
+ };
148
+ const renderReloadTime = (translate, reloadTime) => isNotEmpty(reloadTime)
149
+ ? reloadTime.length > 1
150
+ ? translate("{$value} actions", {
151
+ value: reloadTime.map(time => time.value).join("/"),
152
+ })
153
+ : translate(".input {$value :number} {{{$value} actions}}", {
154
+ value: reloadTime[0].value,
155
+ })
156
+ : MISSING_VALUE;
157
+ const renderRangeBrackets = (rangeBrackets) => `${rangeBrackets.close}/${rangeBrackets.medium}/${rangeBrackets.far}`;
158
+ const renderAmmunition = (translateMap, getInstanceById, ammunition) => ammunition === undefined
159
+ ? "—"
160
+ : (translateMap(getInstanceById("Ammunition", ammunition)?.translations)?.name ?? MISSING_VALUE);
161
+ const renderMeleeDamage = (translate) => (damage) => renderDiceAndFlat(translate, damage.dice, damage.flat);
162
+ /**
163
+ * Render combat values of a ranged weapon.
164
+ */
165
+ export const renderRangedWeapon = (translate, translateMap, getInstanceById, measurements, renderDamage, rangedCombatTechniqueId, use) => {
166
+ const combatTechnique = getInstanceById("RangedCombatTechnique", rangedCombatTechniqueId);
167
+ return {
168
+ type: "labeled",
169
+ label: translate("Combat Technique {$name}", {
170
+ name: translateMap(combatTechnique?.translations)?.name ?? MISSING_VALUE,
171
+ }),
172
+ value: {
173
+ type: "definitionList",
174
+ items: [
175
+ {
176
+ label: translate("Damage Points"),
177
+ value: renderDamage(use.damage),
178
+ },
179
+ {
180
+ label: translate("Reload Time"),
181
+ value: renderReloadTime(translate, use.reload_time),
182
+ },
183
+ {
184
+ label: translate("Range Brackets"),
185
+ value: renderRangeBrackets(use.range),
186
+ },
187
+ {
188
+ label: translate("Ammunition"),
189
+ value: renderAmmunition(translateMap, getInstanceById, use.ammunition),
190
+ },
191
+ {
192
+ label: translate("Length"),
193
+ value: renderLength(translate, measurements, use.length),
194
+ },
195
+ ],
196
+ },
197
+ };
198
+ };
199
+ const renderRangedDamage = (translate) => (damage) => {
200
+ switch (damage.kind) {
201
+ case "Default": {
202
+ const renderedDice = renderDice(translate, damage.Default.dice);
203
+ return damage.Default.flat === undefined || damage.Default.flat === 0
204
+ ? renderedDice
205
+ : damage.Default.flat > 0
206
+ ? additionFormatter(renderedDice, damage.Default.flat)
207
+ : subtractionFormatter(renderedDice, damage.Default.flat);
208
+ }
209
+ case "NotApplicable":
210
+ return "—";
211
+ case "Special":
212
+ return translate("Special");
213
+ default:
214
+ return assertExhaustive(damage);
215
+ }
216
+ };
217
+ const renderComplexity = (translate, complexity) => mapNullable(complexity, c => ({
218
+ label: translate("Complexity"),
219
+ value: (() => {
220
+ switch (c.kind) {
221
+ case "Primitive":
222
+ return translate("Primitive");
223
+ case "Simple":
224
+ return translate("Simple");
225
+ case "Complex":
226
+ return `${translate("Complex")} (${translate("{$value} AP", {
227
+ value: c.Complex.ap_value,
228
+ })})`;
229
+ case "Various":
230
+ return translate("Various");
231
+ default:
232
+ return assertExhaustive(c);
233
+ }
234
+ })(),
235
+ }));
236
+ const renderBlessedTraditionRestriction = (translate, translateMap, localeJoin, getInstanceById, name, restriction) => {
237
+ const getName = (traditionId) => translateMap(getInstanceById("BlessedTradition", traditionId)?.translations)?.name;
238
+ if (restriction.isSanctifiedBy) {
239
+ switch (restriction.scope.kind) {
240
+ case "Specific":
241
+ if (!isNotEmpty(restriction.scope.Specific)) {
242
+ return translate("Sanctified ({$tradition}); only Blessed Ones of {$tradition} may purchase weapons sanctified by {$tradition}.", { tradition: MISSING_VALUE });
243
+ }
244
+ else if (restriction.scope.Specific.length > 1) {
245
+ const list = restriction.scope.Specific.map(getName).filter(isNotNullish);
246
+ return translate("Sanctified ({$sanctifiedTraditions}); only Blessed Ones of {$traditions} may purchase weapons sanctified by {$traditions}, respectively.", {
247
+ sanctifiedTraditions: localeJoin(list, "unit"),
248
+ traditions: localeJoin(list, "disjunction"),
249
+ });
250
+ }
251
+ else {
252
+ return translate("Sanctified ({$tradition}); only Blessed Ones of {$tradition} may purchase weapons sanctified by {$tradition}.", {
253
+ tradition: getName(restriction.scope.Specific[0]) ?? MISSING_VALUE,
254
+ });
255
+ }
256
+ case "Church":
257
+ return UNHANDLED_VALUE;
258
+ case "Shamanistic":
259
+ return UNHANDLED_VALUE;
260
+ default:
261
+ return assertExhaustive(restriction.scope);
262
+ }
263
+ }
264
+ else {
265
+ switch (restriction.scope.kind) {
266
+ case "Specific":
267
+ return translate("To buy a {$itemName} during hero creation, the character must have Tradition ({$traditions}).", {
268
+ itemName: name,
269
+ traditions: localeJoin(restriction.scope.Specific.map(traditionId => translateMap(getInstanceById("BlessedTradition", traditionId)?.translations)
270
+ ?.name).filter(isNotNullish), "disjunction"),
271
+ });
272
+ case "Church":
273
+ return UNHANDLED_VALUE;
274
+ case "Shamanistic":
275
+ return translate("To buy a {$itemName} during hero creation, the character must have a shamanistic tradition.", {
276
+ itemName: name,
277
+ });
278
+ default:
279
+ return assertExhaustive(restriction.scope);
280
+ }
281
+ }
282
+ };
283
+ const renderMagicalTraditionRestriction = (translate, translateMap, getInstanceById, localeJoin, name, restriction) => translate("To buy a {$itemName} during hero creation, the character must have Tradition ({$traditions}).", {
284
+ itemName: name,
285
+ traditions: localeJoin(restriction.scope
286
+ .map(traditionId => translateMap(getInstanceById("MagicalTradition", traditionId)?.translations)?.name)
287
+ .filter(isNotNullish), "disjunction"),
288
+ });
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}.", {
290
+ name,
291
+ races: localeJoin(restriction.scope
292
+ .map(id => translateMap(getInstanceById("Race", id)?.translations)?.name ?? MISSING_VALUE)
293
+ .filter(isNotNullish), "disjunction"),
294
+ });
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}.", {
296
+ name,
297
+ cultures: localeJoin(restriction.scope
298
+ .map(id => translateMap(getInstanceById("Culture", id)?.translations)?.name ?? MISSING_VALUE)
299
+ .filter(isNotNullish), "disjunction"),
300
+ });
301
+ const renderProfessionRestriction = (_translate, _translateMap, _getInstanceById, _localeJoin, _name, _restriction) => UNHANDLED_VALUE;
302
+ const renderNote = (translate, translateMap, getInstanceById, localeJoin, melee_uses, restrictedTo, name, note) => ensureNonEmpty([
303
+ Object.values(melee_uses ?? {}).some(use => use.is_parrying_weapon)
304
+ ? translate("Parrying weapon (PA bonus +1 for the main weapon)")
305
+ : undefined,
306
+ restrictedTo?.blessedTraditions === undefined
307
+ ? undefined
308
+ : renderBlessedTraditionRestriction(translate, translateMap, localeJoin, getInstanceById, name, restrictedTo.blessedTraditions),
309
+ restrictedTo?.races === undefined
310
+ ? undefined
311
+ : renderRaceRestriction(translate, translateMap, getInstanceById, localeJoin, name, restrictedTo.races),
312
+ restrictedTo?.cultures === undefined
313
+ ? undefined
314
+ : renderCultureRestriction(translate, translateMap, getInstanceById, localeJoin, name, restrictedTo.cultures),
315
+ restrictedTo?.professions === undefined
316
+ ? undefined
317
+ : renderProfessionRestriction(translate, translateMap, getInstanceById, localeJoin, name, restrictedTo.professions),
318
+ restrictedTo?.magicalTraditions === undefined
319
+ ? undefined
320
+ : renderMagicalTraditionRestriction(translate, translateMap, getInstanceById, localeJoin, name, restrictedTo.magicalTraditions),
321
+ note,
322
+ ].filter(isNotNullish))?.join("; ");
323
+ const renderWeight = (translate, measurements, weight) => {
324
+ if (typeof weight === "number") {
325
+ return {
326
+ label: translate("Weight"),
327
+ value: translate(".input {$value :number} {{{$value} pounds}}", {
328
+ value: weight * measurements.stonesMultiplier,
329
+ }),
330
+ };
331
+ }
332
+ else {
333
+ return {
334
+ label: translate("Weight (Bronze/Silver/Gold)"),
335
+ value: translate("{$value} pounds", {
336
+ value: [weight.bronze, weight.silver, weight.gold]
337
+ .map(value => value * measurements.stonesMultiplier)
338
+ .join("/"),
339
+ }),
340
+ };
341
+ }
342
+ };
343
+ const renderCost = (translate, translateMap, cost) => {
344
+ const renderBookCostVariant = (bookCostVariant) => {
345
+ switch (bookCostVariant.kind) {
346
+ case "Definite": {
347
+ const translation = translateMap(bookCostVariant.Definite.translations);
348
+ return (renderCost(translate, translateMap, bookCostVariant.Definite.cost) +
349
+ parensIf(translation?.label));
350
+ }
351
+ case "Indefinite": {
352
+ const translation = translateMap(bookCostVariant.Indefinite.translations);
353
+ return (translation?.description ?? MISSING_VALUE) + parensIf(translation?.label);
354
+ }
355
+ default:
356
+ return assertExhaustive(bookCostVariant);
357
+ }
358
+ };
359
+ if ("bronze" in cost) {
360
+ return {
361
+ label: translate("Cost (Bronze/Silver/Gold)"),
362
+ value: translate("{$value} silverthalers", {
363
+ value: [cost.bronze, cost.silver, cost.gold].join("/"),
364
+ }),
365
+ };
366
+ }
367
+ else {
368
+ return {
369
+ label: translate("Cost"),
370
+ value: (() => {
371
+ switch (cost.kind) {
372
+ case "Free":
373
+ return translate("free");
374
+ case "Various":
375
+ return translate("various");
376
+ case "Invaluable":
377
+ return translate("invaluable");
378
+ case "Fixed": {
379
+ const translation = translateMap(cost.Fixed.translations);
380
+ const main = translate(".input {$value :number} {{{$value} silverthalers}}", {
381
+ value: cost.Fixed.value,
382
+ });
383
+ if (translation?.wrap_in_text === undefined) {
384
+ return main;
385
+ }
386
+ return `${UNHANDLED_VALUE} ${translate(".input {$value :number} {{{$value} silverthalers}}", {
387
+ value: cost.Fixed.value,
388
+ })}`;
389
+ }
390
+ case "Range":
391
+ return translate(".input {$from :number} .input {$to :number} {{{$from}–{$to} silverthalers}}", {
392
+ from: cost.Range.from,
393
+ to: cost.Range.to,
394
+ });
395
+ case "Single":
396
+ return renderBookCostVariant(cost.Single);
397
+ case "Multiple":
398
+ return cost.Multiple.map(renderBookCostVariant).join(", ");
399
+ default:
400
+ return assertExhaustive(cost);
401
+ }
402
+ })(),
403
+ };
404
+ }
405
+ };
406
+ const renderArmorValues = (translate, translateMap, localeCompare, getInstanceById, idMap, values) => [
407
+ { label: translate("Protection"), value: values.protection.toString() },
408
+ { label: translate("Encumbrance"), value: values.encumbrance.toString() },
409
+ {
410
+ label: translate("Additional Penalties"),
411
+ value: values.has_additional_penalties
412
+ ? [idMap.DerivedCharacteristic.Movement, idMap.DerivedCharacteristic.Initiative]
413
+ .map(dcId => `${sign(-1)} ${translateMap(getInstanceById("DerivedCharacteristic", dcId)?.translations)
414
+ ?.abbreviation ?? MISSING_VALUE}`)
415
+ .toSorted(localeCompare)
416
+ .join(", ")
417
+ : "—",
418
+ },
419
+ ];
420
+ const normalizeCombatValues = (entity) => {
421
+ switch (entity.entity) {
422
+ case "Weapon":
423
+ return {
424
+ type: "Weapon",
425
+ values: entity.content,
426
+ };
427
+ case "Armor":
428
+ return {
429
+ type: "Armor",
430
+ values: entity.content,
431
+ };
432
+ case "Ammunition":
433
+ case "Animal":
434
+ case "AnimalCare":
435
+ case "BandageOrRemedy":
436
+ case "Book":
437
+ case "CeremonialItem":
438
+ case "Clothes":
439
+ case "ClothingPackage":
440
+ case "Container":
441
+ case "EquipmentOfBlessedOnes":
442
+ case "GemOrPreciousStone":
443
+ case "IlluminationLightSource":
444
+ case "IlluminationRefillOrSupply":
445
+ case "Jewelry":
446
+ case "Laboratory":
447
+ case "Liebesspielzeug":
448
+ case "LuxuryGood":
449
+ case "MagicalArtifact":
450
+ case "MusicalInstrument":
451
+ case "Newspaper":
452
+ case "OrienteeringAid":
453
+ case "RopeOrChain":
454
+ case "Stationery":
455
+ case "ThievesTool":
456
+ case "ToolOfTheTrade":
457
+ case "TravelGearOrTool":
458
+ case "Vehicle":
459
+ case "WeaponAccessory":
460
+ case "WorkingSupernaturalCreature": {
461
+ const baseItem = entity.content;
462
+ if (baseItem.combat_use === undefined) {
463
+ return undefined;
464
+ }
465
+ switch (baseItem.combat_use.kind) {
466
+ case "Weapon":
467
+ return {
468
+ type: "Weapon",
469
+ values: baseItem.combat_use.Weapon,
470
+ };
471
+ case "Armor":
472
+ return {
473
+ type: "Armor",
474
+ values: baseItem.combat_use.Armor,
475
+ };
476
+ default:
477
+ return assertExhaustive(baseItem.combat_use);
478
+ }
479
+ }
480
+ default:
481
+ return assertExhaustive(entity);
482
+ }
483
+ };
484
+ /**
485
+ * Get a JSON representation of the rules text for equipment.
486
+ */
487
+ export const getEquipmentEntityDescription = createEntityDescriptionCreator(({ getInstanceById, idMap }, locale, entry) => {
488
+ const { translate, translateMap } = locale;
489
+ const translation = entry.entity === "ClothingPackage" ? undefined : translateMap(entry.content.translations);
490
+ if (entry.entity !== "ClothingPackage" && translation === undefined) {
491
+ return undefined;
492
+ }
493
+ const baseItem = entry.content;
494
+ const baseItemTranslation = translation;
495
+ const name = getEquipmentName(translate, translateMap, getInstanceById, entry);
496
+ const combatValues = normalizeCombatValues(entry);
497
+ const combatTranslation = translateMap(combatValues?.values.translations);
498
+ return {
499
+ title: name,
500
+ className: "equipment",
501
+ body: [
502
+ ...(combatValues?.type === "Weapon"
503
+ ? Object.entries(combatValues.values.melee_uses ?? {}).map(([combatTechniqueId, use]) => renderMeleeWeapon(translate, translateMap, getInstanceById, locale.measurementAdjustments, renderMeleeDamage(translate), combatTechniqueId, use))
504
+ : []),
505
+ ...(combatValues?.type === "Weapon"
506
+ ? Object.entries(combatValues.values.ranged_uses ?? {}).map(([combatTechniqueId, use]) => renderRangedWeapon(translate, translateMap, getInstanceById, locale.measurementAdjustments, renderRangedDamage(translate), combatTechniqueId, use))
507
+ : []),
508
+ {
509
+ type: "definitionList",
510
+ items: [
511
+ renderComplexity(translate, baseItem.complexity),
512
+ ...(combatValues?.type === "Armor"
513
+ ? renderArmorValues(translate, translateMap, locale.compare, getInstanceById, idMap, combatValues.values)
514
+ : []),
515
+ mapNullable(baseItem.burning_time, burningTime => ({
516
+ label: translate("Burning Time"),
517
+ value: burningTime.kind === "Unlimited"
518
+ ? translate("unlimited")
519
+ : formatTimeSpan(translate, ResponsiveTextSize.Full, burningTime.Limited.unit, burningTime.Limited.value),
520
+ })),
521
+ mapNullable(baseItemTranslation?.color, color => ({
522
+ label: translate("Color"),
523
+ value: color,
524
+ })),
525
+ baseItemTranslation?.language !== undefined || baseItemTranslation?.script !== undefined
526
+ ? {
527
+ label: translate("Language/Script"),
528
+ value: [baseItemTranslation?.language, baseItemTranslation?.script]
529
+ .map(value => value ?? "—")
530
+ .join(" / "),
531
+ }
532
+ : undefined,
533
+ baseItem.structure_points !== undefined && isNotEmpty(baseItem.structure_points)
534
+ ? {
535
+ label: translate("Structure Points"),
536
+ value: baseItem.structure_points.length === 1
537
+ ? translate(".input {$value :number} {{{$value} Structure Points}}", {
538
+ value: baseItem.structure_points[0].points,
539
+ })
540
+ : translate("{$value} Structure Points", {
541
+ value: baseItem.structure_points.map(elem => elem.points).join("/"),
542
+ }),
543
+ }
544
+ : undefined,
545
+ mapNullable(baseItem.weight, weight => renderWeight(translate, locale.measurementAdjustments, weight)),
546
+ mapNullable(baseItem.cost, cost => renderCost(translate, translateMap, cost)),
547
+ mapNullable(renderNote(translate, translateMap, getInstanceById, locale.join, combatValues?.type === "Weapon" ? combatValues.values.melee_uses : undefined, combatValues?.values.restrictedTo, name, baseItemTranslation?.note), note => ({
548
+ label: translate("Note"),
549
+ value: note,
550
+ })),
551
+ baseItemTranslation?.rules !== undefined
552
+ ? {
553
+ label: translate("Rules"),
554
+ value: baseItemTranslation.rules,
555
+ }
556
+ : undefined,
557
+ combatValues?.type === "Weapon" && combatTranslation?.advantage !== undefined
558
+ ? {
559
+ label: translate("Weapon Advantage"),
560
+ value: combatTranslation.advantage,
561
+ }
562
+ : undefined,
563
+ combatValues?.type === "Weapon" && combatTranslation?.disadvantage !== undefined
564
+ ? {
565
+ label: translate("Weapon Disadvantage"),
566
+ value: combatTranslation.disadvantage,
567
+ }
568
+ : undefined,
569
+ combatValues?.type === "Armor" && combatTranslation?.advantage !== undefined
570
+ ? {
571
+ label: translate("Armor Advantage"),
572
+ value: combatTranslation.advantage,
573
+ }
574
+ : undefined,
575
+ combatValues?.type === "Armor" && combatTranslation?.disadvantage !== undefined
576
+ ? {
577
+ label: translate("Armor Disadvantage"),
578
+ value: combatTranslation.disadvantage,
579
+ }
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,
599
+ ],
600
+ },
601
+ ],
602
+ errata: baseItemTranslation?.errata,
603
+ references: entry.content.src,
604
+ };
605
+ });
@@ -0,0 +1,8 @@
1
+ import type { EquipmentIdentifier } from "optolith-database-schema/gen";
2
+ import type { GetInstanceById } from "../helpers/getTypes.js";
3
+ /**
4
+ * Get a JSON representation of the rules text for an equipment package.
5
+ */
6
+ export declare const getEquipmentPackageEntityDescription: import("../creator.js").EntityDescriptionCreator<"EquipmentPackage", {
7
+ getInstanceById: GetInstanceById<"Publication" | EquipmentIdentifier["kind"] | "SocialStatus">;
8
+ }, import("../index.js").EntityDescription>;