@optolith/entity-descriptions 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/CHANGELOG.md +58 -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 +613 -0
  26. package/lib/entities/equipmentPackage.d.ts +8 -0
  27. package/lib/entities/equipmentPackage.js +301 -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 +7 -0
  33. package/lib/entities/influence.js +35 -0
  34. package/lib/entities/liturgicalChant.d.ts +10 -23
  35. package/lib/entities/liturgicalChant.js +169 -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/herbary.d.ts +28 -0
  49. package/lib/entities/partial/herbary.js +65 -0
  50. package/lib/entities/partial/map.d.ts +49 -0
  51. package/lib/entities/partial/map.js +43 -0
  52. package/lib/entities/partial/mathOperation.d.ts +36 -0
  53. package/lib/entities/partial/mathOperation.js +107 -0
  54. package/lib/entities/partial/prerequisites/displayOption.d.ts +7 -0
  55. package/lib/entities/partial/prerequisites/displayOption.js +19 -0
  56. package/lib/entities/partial/prerequisites/index.d.ts +67 -0
  57. package/lib/entities/partial/prerequisites/index.js +189 -0
  58. package/lib/entities/partial/prerequisites/part.d.ts +26 -0
  59. package/lib/entities/partial/prerequisites/part.js +88 -0
  60. package/lib/entities/partial/prerequisites/prerequisiteGroups.d.ts +65 -0
  61. package/lib/entities/partial/prerequisites/prerequisiteGroups.js +269 -0
  62. package/lib/entities/partial/prerequisites/single/activatable.d.ts +18 -0
  63. package/lib/entities/partial/prerequisites/single/activatable.js +40 -0
  64. package/lib/entities/partial/prerequisites/single/animistPower.d.ts +8 -0
  65. package/lib/entities/partial/prerequisites/single/animistPower.js +23 -0
  66. package/lib/entities/partial/prerequisites/single/blessedTradition.d.ts +7 -0
  67. package/lib/entities/partial/prerequisites/single/blessedTradition.js +32 -0
  68. package/lib/entities/partial/prerequisites/single/commonSuggestedByRCP.d.ts +6 -0
  69. package/lib/entities/partial/prerequisites/single/commonSuggestedByRCP.js +19 -0
  70. package/lib/entities/partial/prerequisites/single/culture.d.ts +8 -0
  71. package/lib/entities/partial/prerequisites/single/culture.js +20 -0
  72. package/lib/entities/partial/prerequisites/single/enhancement.d.ts +8 -0
  73. package/lib/entities/partial/prerequisites/single/enhancement.js +41 -0
  74. package/lib/entities/partial/prerequisites/single/influence.d.ts +8 -0
  75. package/lib/entities/partial/prerequisites/single/influence.js +17 -0
  76. package/lib/entities/partial/prerequisites/single/magicalTradition.d.ts +7 -0
  77. package/lib/entities/partial/prerequisites/single/magicalTradition.js +27 -0
  78. package/lib/entities/partial/prerequisites/single/noOtherAncestorBloodAdvantage.d.ts +6 -0
  79. package/lib/entities/partial/prerequisites/single/noOtherAncestorBloodAdvantage.js +8 -0
  80. package/lib/entities/partial/prerequisites/single/pact.d.ts +8 -0
  81. package/lib/entities/partial/prerequisites/single/pact.js +30 -0
  82. package/lib/entities/partial/prerequisites/single/personalityTrait.d.ts +8 -0
  83. package/lib/entities/partial/prerequisites/single/personalityTrait.js +28 -0
  84. package/lib/entities/partial/prerequisites/single/primaryAttribute.d.ts +7 -0
  85. package/lib/entities/partial/prerequisites/single/primaryAttribute.js +15 -0
  86. package/lib/entities/partial/prerequisites/single/publication.d.ts +8 -0
  87. package/lib/entities/partial/prerequisites/single/publication.js +19 -0
  88. package/lib/entities/partial/prerequisites/single/race.d.ts +8 -0
  89. package/lib/entities/partial/prerequisites/single/race.js +20 -0
  90. package/lib/entities/partial/prerequisites/single/rated.d.ts +8 -0
  91. package/lib/entities/partial/prerequisites/single/rated.js +41 -0
  92. package/lib/entities/partial/prerequisites/single/ratedMinimumNumber.d.ts +8 -0
  93. package/lib/entities/partial/prerequisites/single/ratedMinimumNumber.js +89 -0
  94. package/lib/entities/partial/prerequisites/single/ratedSum.d.ts +8 -0
  95. package/lib/entities/partial/prerequisites/single/ratedSum.js +21 -0
  96. package/lib/entities/partial/prerequisites/single/rule.d.ts +7 -0
  97. package/lib/entities/partial/prerequisites/single/rule.js +4 -0
  98. package/lib/entities/partial/prerequisites/single/sex.d.ts +7 -0
  99. package/lib/entities/partial/prerequisites/single/sex.js +19 -0
  100. package/lib/entities/partial/prerequisites/single/sexualCharacteristic.d.ts +7 -0
  101. package/lib/entities/partial/prerequisites/single/sexualCharacteristic.js +21 -0
  102. package/lib/entities/partial/prerequisites/single/socialStatus.d.ts +8 -0
  103. package/lib/entities/partial/prerequisites/single/socialStatus.js +21 -0
  104. package/lib/entities/partial/prerequisites/single/state.d.ts +8 -0
  105. package/lib/entities/partial/prerequisites/single/state.js +20 -0
  106. package/lib/entities/partial/prerequisites/single/text.d.ts +7 -0
  107. package/lib/entities/partial/prerequisites/single/text.js +9 -0
  108. package/lib/entities/partial/professions.d.ts +15 -0
  109. package/lib/entities/partial/professions.js +19 -0
  110. package/lib/entities/partial/rated/activatable/castingTime.d.ts +16 -7
  111. package/lib/entities/partial/rated/activatable/castingTime.js +35 -20
  112. package/lib/entities/partial/rated/activatable/checkResultBased.d.ts +18 -3
  113. package/lib/entities/partial/rated/activatable/checkResultBased.js +18 -10
  114. package/lib/entities/partial/rated/activatable/cost.d.ts +88 -8
  115. package/lib/entities/partial/rated/activatable/cost.js +183 -115
  116. package/lib/entities/partial/rated/activatable/duration.d.ts +36 -13
  117. package/lib/entities/partial/rated/activatable/duration.js +72 -61
  118. package/lib/entities/partial/rated/activatable/effect.d.ts +4 -4
  119. package/lib/entities/partial/rated/activatable/effect.js +32 -25
  120. package/lib/entities/partial/rated/activatable/index.d.ts +31 -27
  121. package/lib/entities/partial/rated/activatable/index.js +63 -28
  122. package/lib/entities/partial/rated/activatable/isMinimumMaximum.d.ts +5 -6
  123. package/lib/entities/partial/rated/activatable/isMinimumMaximum.js +8 -5
  124. package/lib/entities/partial/rated/activatable/nonModifiableSuffix.d.ts +2 -4
  125. package/lib/entities/partial/rated/activatable/nonModifiableSuffix.js +7 -42
  126. package/lib/entities/partial/rated/activatable/range.d.ts +22 -14
  127. package/lib/entities/partial/rated/activatable/range.js +54 -54
  128. package/lib/entities/partial/rated/activatable/speed.d.ts +11 -2
  129. package/lib/entities/partial/rated/activatable/speed.js +14 -1
  130. package/lib/entities/partial/rated/activatable/targetCategory.d.ts +4 -5
  131. package/lib/entities/partial/rated/activatable/targetCategory.js +19 -24
  132. package/lib/entities/partial/rated/improvementCost.d.ts +16 -4
  133. package/lib/entities/partial/rated/improvementCost.js +63 -4
  134. package/lib/entities/partial/rated/skillCheck.d.ts +20 -17
  135. package/lib/entities/partial/rated/skillCheck.js +56 -54
  136. package/lib/entities/partial/reader.d.ts +266 -0
  137. package/lib/entities/partial/reader.js +175 -0
  138. package/lib/entities/partial/responsiveText.d.ts +10 -5
  139. package/lib/entities/partial/responsiveText.js +19 -3
  140. package/lib/entities/partial/units/energy.d.ts +5 -8
  141. package/lib/entities/partial/units/energy.js +5 -23
  142. package/lib/entities/partial/units/length.d.ts +20 -2
  143. package/lib/entities/partial/units/length.js +24 -5
  144. package/lib/entities/partial/units/timeSpan.d.ts +25 -4
  145. package/lib/entities/partial/units/timeSpan.js +27 -15
  146. package/lib/entities/partial/unknown.d.ts +5 -1
  147. package/lib/entities/partial/unknown.js +5 -1
  148. package/lib/entities/personalityTrait.d.ts +7 -0
  149. package/lib/entities/personalityTrait.js +56 -0
  150. package/lib/entities/poison.d.ts +12 -0
  151. package/lib/entities/poison.js +356 -0
  152. package/lib/entities/profession.d.ts +12 -0
  153. package/lib/entities/profession.js +585 -0
  154. package/lib/entities/race.d.ts +9 -0
  155. package/lib/entities/race.js +146 -0
  156. package/lib/entities/sexPractice.d.ts +6 -0
  157. package/lib/entities/sexPractice.js +33 -0
  158. package/lib/entities/skill.d.ts +9 -9
  159. package/lib/entities/skill.js +124 -91
  160. package/lib/entities/spell.d.ts +83 -26
  161. package/lib/entities/spell.js +835 -147
  162. package/lib/entities/state.d.ts +6 -0
  163. package/lib/entities/state.js +17 -0
  164. package/lib/helpers/enums.d.ts +11 -0
  165. package/lib/helpers/enums.js +6 -0
  166. package/lib/helpers/getTypes.d.ts +12 -482
  167. package/lib/helpers/getTypes.js +1 -0
  168. package/lib/helpers/identifiers.d.ts +314 -0
  169. package/lib/helpers/identifiers.js +333 -0
  170. package/lib/helpers/locale.d.ts +21 -2
  171. package/lib/helpers/translate.d.ts +47 -5
  172. package/lib/helpers/translate.js +13 -6
  173. package/lib/index.d.ts +848 -21
  174. package/lib/index.js +182 -17
  175. package/lib/references/index.d.ts +6 -3
  176. package/lib/references/index.js +25 -33
  177. package/lib/references/page.d.ts +1 -1
  178. package/lib/references/page.js +14 -14
  179. package/lib/references/pageRange.d.ts +1 -5
  180. package/lib/references/pageRange.js +7 -16
  181. package/lib/tsconfig.tsbuildinfo +1 -0
  182. package/package.json +30 -10
  183. package/.prettierrc.yml +0 -1
  184. package/lib/entities/partial/rated/activatable/entity.d.ts +0 -11
  185. package/lib/entities/partial/rated/activatable/entity.js +0 -12
  186. package/lib/references/occurrence.d.ts +0 -4
  187. package/lib/references/occurrence.js +0 -3
@@ -0,0 +1,609 @@
1
+ import { on } from "@elyukai/utils/function";
2
+ import { isNotNullish } from "@elyukai/utils/nullable";
3
+ import { numAsc } from "@optolith/helpers/compare";
4
+ import { sign } from "@optolith/helpers/math";
5
+ import { mapNullable, mapNullableDefault } from "@optolith/helpers/nullable";
6
+ import { romanize } from "@optolith/helpers/roman";
7
+ import { assertExhaustive } from "@optolith/helpers/typeSafety";
8
+ import { Case, fromUniformCase } from "tsondb/schema/gen";
9
+ import { createEntityDescriptionCreator } from "../creator.js";
10
+ import { renderAdventurePointsValue } from "./partial/adventurePointsValue.js";
11
+ import { renderResponsiveMap } from "./partial/map.js";
12
+ import { additionFormatter } from "./partial/mathOperation.js";
13
+ import { printAdvantageDisadvantagePrerequisites, printGeneralPrerequisites, } from "./partial/prerequisites/index.js";
14
+ import { parensIf } from "./partial/rated/activatable/parensIf.js";
15
+ import { getResponsiveText, getResponsiveTextOptional, ResponsiveTextSize, } from "./partial/responsiveText.js";
16
+ import { formatTimeSpan } from "./partial/units/timeSpan.js";
17
+ import { MISSING_VALUE } from "./partial/unknown.js";
18
+ const renderPropertyValue = (getInstanceById, translate, translateMap, propertyDecl) => {
19
+ switch (propertyDecl.kind) {
20
+ case "DependingOnSelection":
21
+ return translate("As chosen");
22
+ case "Fixed":
23
+ return (translateMap(getInstanceById("Property", propertyDecl.Fixed)?.translations)?.name ?? MISSING_VALUE);
24
+ default:
25
+ return assertExhaustive(propertyDecl);
26
+ }
27
+ };
28
+ const renderPenaltyByAttackLabel = (translate, penaltyByAttackReplacement, ord) => {
29
+ if (penaltyByAttackReplacement === undefined) {
30
+ return translate(".input {$ord :number} {{{$ord}. attack}}", { ord });
31
+ }
32
+ // switch (penaltyByAttackReplacement.kind) {
33
+ // case "Throw":
34
+ return translate(".input {$ord :number} {{{$ord}. throw}}", { ord });
35
+ // default:
36
+ // return assertExhaustive(penaltyByAttackReplacement)
37
+ // }
38
+ };
39
+ const renderPenaltyValue = (getInstanceById, translate, translateMap, name, penalty) => {
40
+ switch (penalty.kind) {
41
+ case "Single":
42
+ return (sign(penalty.Single.value) +
43
+ (penalty.Single.applies_to_parry === true
44
+ ? ` (${translate("for parry")})`
45
+ : ""));
46
+ case "ByHandedness": {
47
+ const appendParry = penalty.ByHandedness.applies_to_parry === true
48
+ ? `; ${translate("for parry")}`
49
+ : "";
50
+ return `${sign(penalty.ByHandedness.one_handed)} (${translate("one-handed weapon") + appendParry}); ${sign(penalty.ByHandedness.two_handed)} (${translate("two-handed weapon") + appendParry})`;
51
+ }
52
+ case "ByActivation": {
53
+ return `${sign(penalty.ByActivation.active)}/${sign(penalty.ByActivation.inactive)} (${[
54
+ penalty.ByActivation.applies_to_parry === true
55
+ ? translate("for parry")
56
+ : undefined,
57
+ translate("for secondary fighters with/without special ability {$name}", { name }),
58
+ ]
59
+ .filter(isNotNullish)
60
+ .join("; ")})`;
61
+ }
62
+ case "Selection":
63
+ switch (penalty.Selection.options.kind) {
64
+ case "Specific":
65
+ return penalty.Selection.options.Specific.list
66
+ .map(option => sign(option.value))
67
+ .join("/");
68
+ case "Range":
69
+ return translate("{$start} to {$end}", {
70
+ start: sign(penalty.Selection.options.Range.minimum),
71
+ end: sign(penalty.Selection.options.Range.maximum),
72
+ });
73
+ default:
74
+ return assertExhaustive(penalty.Selection.options);
75
+ }
76
+ case "ByLevel": {
77
+ const external = penalty.ByLevel.external?.id;
78
+ const main = penalty.ByLevel.levels
79
+ .map(penaltyByLevel => sign(penaltyByLevel.value))
80
+ .join("/");
81
+ if (external === undefined) {
82
+ return main;
83
+ }
84
+ const externalName = translateMap(getInstanceById(external.kind, fromUniformCase(external))
85
+ ?.translations)?.name ?? MISSING_VALUE;
86
+ return `${main} (${translate("depending on the level of the special ability {$name}", {
87
+ name: externalName,
88
+ })})`;
89
+ }
90
+ case "ByAttack": {
91
+ const offset = penalty.ByAttack.initial_order ?? 1;
92
+ return penalty.ByAttack.list
93
+ .map((penaltyByAttack, index) => `${penaltyByAttack.value} (${renderPenaltyByAttackLabel(translate, penalty.ByAttack.attack_replacement, index + offset)})`)
94
+ .join("; ");
95
+ }
96
+ case "DependsOnHitZone":
97
+ return translate("Depends on zone");
98
+ default:
99
+ return assertExhaustive(penalty);
100
+ }
101
+ };
102
+ // type RestrictionStyle = "Enclosed" | "Phrase" | "Subclause"
103
+ // const renderApplicableCombatTechniquesRestrictions = (
104
+ // items: [string, style: RestrictionStyle][],
105
+ // ): string => {
106
+ // const dict = Dictionary.groupBy(items, item => item[1])
107
+ // return ` ${[
108
+ // dict
109
+ // .get("Phrase")
110
+ // ?.map(([phrase]) => phrase)
111
+ // .join(" "),
112
+ // dict
113
+ // .get("Subclause")
114
+ // ?.map(
115
+ // ([subclause], index, arr) =>
116
+ // subclause.trim() +
117
+ // (subclause.startsWith(",") && index < arr.length - 1 ? ", " : " "),
118
+ // )
119
+ // .join(""),
120
+ // mapNullable(
121
+ // dict
122
+ // .get("Enclosed")
123
+ // ?.map(([enclosed]) => enclosed)
124
+ // .join(", "),
125
+ // enclosed => `(${enclosed})`,
126
+ // ),
127
+ // ]
128
+ // .filter(isNotNullish)
129
+ // .join(" ")}`
130
+ // }
131
+ const wrapInParens = (args, append = "") => {
132
+ const filteredArgs = args.filter(isNotNullish);
133
+ if (filteredArgs.length === 0) {
134
+ return "";
135
+ }
136
+ return ` (${filteredArgs.join(", ") + append})`;
137
+ };
138
+ const addSpaceIfNoCommaAtStart = (str) => str.startsWith(",") ? str : ` ${str}`;
139
+ const renderApplicableCombatTechniquesRestriction = (getInstanceById, locale, main, restriction, translation, weapons, getExcludedInstance) => {
140
+ switch (restriction.kind) {
141
+ case "Improvised":
142
+ return (main +
143
+ wrapInParens([locale.translate("only improvised weapons"), weapons]));
144
+ case "PointedBlade":
145
+ return (main +
146
+ wrapInParens([
147
+ locale.translate("weapon must have a pointed blade"),
148
+ weapons,
149
+ ]));
150
+ case "Mount":
151
+ if (weapons === undefined) {
152
+ return `${main} ${locale.translate("while mounted")}`;
153
+ }
154
+ else {
155
+ return main + wrapInParens([weapons], locale.translate("while mounted"));
156
+ }
157
+ case "Race": {
158
+ const race = getInstanceById("Race", restriction.Race);
159
+ const raceName = locale.translateMap(race?.translations)?.name ?? MISSING_VALUE;
160
+ return (main +
161
+ wrapInParens([
162
+ // originally "while {$racial} weapon", but different to parameterize without inflection support
163
+ locale.translate("while weapon of race {$race}", {
164
+ race: raceName,
165
+ }),
166
+ weapons,
167
+ ]));
168
+ }
169
+ case "ExcludeCombatTechniques":
170
+ return (main +
171
+ wrapInParens([
172
+ locale.translate("except {$list}", {
173
+ list: locale.join(restriction.ExcludeCombatTechniques.list
174
+ .map(id => locale.translateMap(getExcludedInstance?.(id)?.translations)?.name ?? MISSING_VALUE)
175
+ .toSorted(locale.compare), "conjunction"),
176
+ }),
177
+ weapons,
178
+ ]));
179
+ case "HasParry":
180
+ return `${main} ${locale.translate("with parry") + wrapInParens([weapons])}`;
181
+ case "OneHanded":
182
+ return (main +
183
+ addSpaceIfNoCommaAtStart(locale.translate("that may be performed with one-handed weapons")) +
184
+ wrapInParens([weapons]));
185
+ case "TwoHanded":
186
+ return (locale.translate("All Two-Handed Weapons") + wrapInParens([weapons]));
187
+ case "ParryingWeapon":
188
+ return locale.translate("All Parrying Weapons") + wrapInParens([weapons]);
189
+ case "Level": {
190
+ const nameWithLevel = `${translation} ${romanize(restriction.Level.level)}`;
191
+ return (main +
192
+ wrapInParens([
193
+ locale.translate("only {$nameWithLevel}", { nameWithLevel }),
194
+ weapons,
195
+ ]));
196
+ }
197
+ case "OneBluntSide":
198
+ return (main +
199
+ wrapInParens([
200
+ locale.translate("only those with at least one blunt side"),
201
+ weapons,
202
+ ]));
203
+ default:
204
+ return assertExhaustive(restriction);
205
+ }
206
+ };
207
+ const renderApplicableCombatTechniquesValue = (getInstanceById, locale, translation, applicableCombatTechniques) => {
208
+ switch (applicableCombatTechniques.kind) {
209
+ case "None":
210
+ return "—";
211
+ case "DependingOnCombatStyle":
212
+ return locale.translate("Depends on combat style; both combat styles can be used only for their corresponding combat techniques");
213
+ case "All": {
214
+ const main = locale.translate("All");
215
+ const mainWithRestriction = applicableCombatTechniques.All.restriction === undefined
216
+ ? main
217
+ : renderApplicableCombatTechniquesRestriction(getInstanceById, locale, main, applicableCombatTechniques.All.restriction, translation, undefined, id => getInstanceById(id.kind, fromUniformCase(id)));
218
+ return mainWithRestriction;
219
+ }
220
+ case "AllClose": {
221
+ const main = locale.translate("All Close Combat Techniques");
222
+ const mainWithRestriction = applicableCombatTechniques.AllClose.restriction === undefined
223
+ ? main
224
+ : renderApplicableCombatTechniquesRestriction(getInstanceById, locale, main, applicableCombatTechniques.AllClose.restriction, translation, undefined, id => getInstanceById("CloseCombatTechnique", id));
225
+ return mainWithRestriction;
226
+ }
227
+ case "AllRanged": {
228
+ const main = locale.translate("All Ranged Combat Techniques");
229
+ const mainWithRestriction = applicableCombatTechniques.AllRanged.restriction === undefined
230
+ ? main
231
+ : renderApplicableCombatTechniquesRestriction(getInstanceById, locale, main, applicableCombatTechniques.AllRanged.restriction, translation, undefined, id => getInstanceById("RangedCombatTechnique", id));
232
+ return mainWithRestriction;
233
+ }
234
+ case "Specific": {
235
+ return applicableCombatTechniques.Specific.list
236
+ .map(specific => {
237
+ const entry = getInstanceById(specific.id.kind, fromUniformCase(specific.id));
238
+ const main = locale.translateMap(entry?.translations)?.name ?? MISSING_VALUE;
239
+ const mainWithRestriction = specific.restriction === undefined
240
+ ? main
241
+ : renderApplicableCombatTechniquesRestriction(getInstanceById, locale, main, specific.restriction, translation, specific.weapons === undefined
242
+ ? undefined
243
+ : locale.translate("only {$weapons}", {
244
+ weapons: locale.join(specific.weapons
245
+ .map(weapon => locale.translateMap(getInstanceById("Weapon", weapon)
246
+ ?.translations)?.name ?? MISSING_VALUE)
247
+ .toSorted(locale.compare), "conjunction"),
248
+ }), undefined);
249
+ return mainWithRestriction;
250
+ })
251
+ .toSorted(locale.compare)
252
+ .join(", ");
253
+ }
254
+ default:
255
+ return assertExhaustive(applicableCombatTechniques);
256
+ }
257
+ };
258
+ const renderVolumeValue = (translate, translateMap, getAllResolvedSelectOptions, responsiveTextSize, volume) => {
259
+ switch (volume.kind) {
260
+ case "Fixed":
261
+ return translate(".input {$points :number} {{{$points} points}}", {
262
+ points: volume.Fixed.points,
263
+ });
264
+ case "PerLevel":
265
+ return volume.PerLevel.points === 0
266
+ ? translate(".input {$points :number} {{{$points} points}}", {
267
+ points: 0,
268
+ })
269
+ : translate(".input {$points :number} {{{$points} points per level}}", {
270
+ points: volume.PerLevel.points,
271
+ });
272
+ case "ByLevel":
273
+ return translate("{$points} points for levels {$levels}", {
274
+ points: volume.ByLevel.list.map(item => item.points).join("/"),
275
+ levels: volume.ByLevel.list
276
+ .map((_, index) => romanize(index + 1))
277
+ .join("/"),
278
+ });
279
+ case "Map":
280
+ return renderResponsiveMap(volume.Map, option => option.points, values => translate("{$points} points", { points: values })).run({
281
+ translate,
282
+ translateMap,
283
+ responsiveTextSize,
284
+ });
285
+ case "DerivedFromSelection": {
286
+ const groups = Map.groupBy(getAllResolvedSelectOptions(), option => option.content.volume ?? volume.DerivedFromSelection.fallback)
287
+ .entries()
288
+ .toArray()
289
+ .toSorted(on(group => group[0], numAsc));
290
+ const separator = groups.some(group => group[1].length > 1) ? " / " : "/";
291
+ return `${translate("{$points} points", {
292
+ points: groups.map(group => group[0]).join("/"),
293
+ })} ${translate("for")} ${groups
294
+ .map(group => group[1]
295
+ .map(groupItem => translateMap(groupItem.content.translations)?.name ??
296
+ MISSING_VALUE)
297
+ .join(", "))
298
+ .join(separator)}`;
299
+ }
300
+ default:
301
+ return assertExhaustive(volume);
302
+ }
303
+ };
304
+ const renderArcaneEnergyCost = (translate, translateMap, localeJoin, responsiveTextSize, levels, cost) => {
305
+ switch (cost.kind) {
306
+ case "Fixed": {
307
+ const translationWrapper = cost.Fixed.is_permanent === true
308
+ ? value => translate(".input {$value :number} {{{$value} permanent AE}}", {
309
+ value,
310
+ })
311
+ : value => translate("{$value} AE", { value });
312
+ const { interval } = cost.Fixed;
313
+ const wrapInInterval = interval === undefined
314
+ ? str => str
315
+ : str => translate("{$cost} per {$interval}", {
316
+ cost: str,
317
+ interval: formatTimeSpan(translate, responsiveTextSize, interval.unit, interval.value),
318
+ });
319
+ const wrapInPerLevel = (() => {
320
+ if (cost.Fixed.per_level === undefined) {
321
+ return prev => prev(cost.Fixed.value);
322
+ }
323
+ switch (cost.Fixed.per_level?.kind) {
324
+ case "Compressed":
325
+ return prev => translate("{$cost} per level", { cost: prev(cost.Fixed.value) });
326
+ case "Verbose":
327
+ return prev => Array.from({ length: levels ?? 1 }, (_, index) => translate("{$cost} for level {$level}", {
328
+ cost: prev(cost.Fixed.value),
329
+ level: romanize(index + 1),
330
+ })).join("; ");
331
+ default:
332
+ return assertExhaustive(cost.Fixed.per_level);
333
+ }
334
+ })();
335
+ const noteInParens = parensIf(mapNullable(translateMap(cost.Fixed.translations)?.note, note => getResponsiveTextOptional(note, responsiveTextSize)));
336
+ return (wrapInPerLevel(value => wrapInInterval(translationWrapper(value))) +
337
+ noteInParens);
338
+ }
339
+ case "Constant":
340
+ return (translate("{$value} AE", { value: cost.Constant.value }) +
341
+ (cost.Constant.permanent_value === undefined
342
+ ? ""
343
+ : translate(", {$value} of which are permanent", {
344
+ value: cost.Constant.permanent_value,
345
+ })));
346
+ case "PerCountable": {
347
+ const translation = translateMap(cost.PerCountable.translations);
348
+ return ((cost.PerCountable.base_value === undefined
349
+ ? ""
350
+ : `${translate("{$value} AE", { value: cost.PerCountable.base_value })} + `) +
351
+ translate("{$cost} per {$countable}", {
352
+ cost: translate("{$value} AE", { value: cost.PerCountable.value }),
353
+ countable: getResponsiveText(translation?.per, responsiveTextSize),
354
+ }) +
355
+ parensIf(mapNullable(translation?.note, note => getResponsiveTextOptional(note, responsiveTextSize))));
356
+ }
357
+ case "Interval":
358
+ return translate("{$cost} per {$interval}", {
359
+ cost: translate("{$value} AE", { value: cost.Interval.value }),
360
+ interval: formatTimeSpan(translate, responsiveTextSize, cost.Interval.interval.unit, cost.Interval.interval.value),
361
+ });
362
+ case "ActivationAndHalfInterval":
363
+ return additionFormatter(translate("{$value} AE", {
364
+ value: cost.ActivationAndHalfInterval.value,
365
+ }) + parensIf(translate("activation")), translate("{$cost} per {$interval}", {
366
+ cost: translate("{$value} AE", {
367
+ value: Math.round(cost.ActivationAndHalfInterval.value / 2),
368
+ }),
369
+ interval: formatTimeSpan(translate, responsiveTextSize, cost.ActivationAndHalfInterval.interval.unit, cost.ActivationAndHalfInterval.interval.value),
370
+ }));
371
+ case "Indefinite":
372
+ return (mapNullable(translateMap(cost.Indefinite.translations)?.description, description => getResponsiveText(description, responsiveTextSize)) +
373
+ mapNullableDefault(cost.Indefinite.modifier, modifier => ` + ${translate("{$value} AE", { value: modifier.value })}`, ""));
374
+ case "Disjunction":
375
+ return translate("{$value} AE", {
376
+ value: localeJoin(cost.Disjunction.options.map(option => option.value +
377
+ mapNullableDefault(translateMap(option.translations)?.note, note => parensIf(getResponsiveTextOptional(note, responsiveTextSize)), "")), "disjunction"),
378
+ });
379
+ case "Map":
380
+ return renderResponsiveMap(cost.Map, option => option.value, values => translate("{$value} AE", { value: values }), cost.Map.options.every(option => option.permanent_value !== undefined)
381
+ ? {
382
+ surround: values => translate(", {$value} of which are permanent", {
383
+ value: values,
384
+ }),
385
+ getAdditionalValue: option => option.permanent_value,
386
+ }
387
+ : undefined).run({
388
+ translate,
389
+ translateMap,
390
+ responsiveTextSize,
391
+ });
392
+ case "Variable":
393
+ return translate("Variable");
394
+ case "ByLevel":
395
+ switch (cost.ByLevel.style.kind) {
396
+ case "Compressed":
397
+ return translate("{$cost} for level {$level}", {
398
+ cost: translate("{$value} AE", {
399
+ value: cost.ByLevel.levels.map(level => level.value).join("/"),
400
+ }),
401
+ level: Array.from({ length: cost.ByLevel.levels.length }, (_, index) => romanize(index + 1)),
402
+ });
403
+ case "Verbose":
404
+ return cost.ByLevel.levels
405
+ .map((level, index) => translate("{$cost} for level {$level}", {
406
+ cost: translate("{$value} AE", { value: level.value }),
407
+ level: romanize(index + 1),
408
+ }))
409
+ .join("; ");
410
+ default:
411
+ return assertExhaustive(cost.ByLevel.style);
412
+ }
413
+ default:
414
+ return assertExhaustive(cost);
415
+ }
416
+ };
417
+ const renderBindingCost = (translate, translateMap, localeCompare, getAllResolvedSelectOptions, responsiveTextSize, cost) => {
418
+ switch (cost.kind) {
419
+ case "Fixed":
420
+ return translate(".input {$value :number} {{{$value} permanent AE}}", {
421
+ value: cost.Fixed.permanent_value,
422
+ });
423
+ case "PerLevel":
424
+ return translate("{$cost} per level", {
425
+ cost: translate(".input {$value :number} {{{$value} permanent AE}}", {
426
+ value: cost.PerLevel.permanent_value,
427
+ }),
428
+ });
429
+ case "Map":
430
+ return renderResponsiveMap(cost.Map, option => option.permanent_value, values => translate("{$value} permanent AE", { value: values })).run({
431
+ translate,
432
+ translateMap,
433
+ responsiveTextSize,
434
+ });
435
+ case "DerivedFromSelection": {
436
+ const groups = Map.groupBy(getAllResolvedSelectOptions(), option => option.content.binding_cost ?? cost.DerivedFromSelection.fallback)
437
+ .entries()
438
+ .toArray()
439
+ .toSorted(on(group => group[0], numAsc));
440
+ const separator = groups.some(group => group[1].length > 1) ? " / " : "/";
441
+ return `${translate("{$value} permanent AE", {
442
+ value: groups.map(group => group[0]).join("/"),
443
+ })} ${translate("for")} ${groups
444
+ .map(group => group[1]
445
+ .map(groupItem => translateMap(groupItem.content.translations)?.name ??
446
+ MISSING_VALUE)
447
+ .toSorted(localeCompare)
448
+ .join(", "))
449
+ .join(separator)}`;
450
+ }
451
+ default:
452
+ return assertExhaustive(cost);
453
+ }
454
+ };
455
+ const renderLifePointsCost = (translate, cost) => cost === undefined
456
+ ? ""
457
+ : // eslint-disable-next-line no-irregular-whitespace
458
+ ` (+ ${translate("{$value} LP", { value: cost.Fixed.value })})`;
459
+ const renderCost = (translate, translateMap, localeJoin, localeCompare, getAllResolvedSelectOptions, responsiveTextSize, levels, cost) => {
460
+ if (typeof cost === "number") {
461
+ return {
462
+ label: translate("AE Cost"),
463
+ value: translate("{$value} AE", { value: cost }),
464
+ };
465
+ }
466
+ switch (cost.kind) {
467
+ case "ArcaneEnergyCost":
468
+ case "Constant":
469
+ case "Map":
470
+ return {
471
+ label: translate("AE Cost"),
472
+ value: cost.kind === "ArcaneEnergyCost"
473
+ ? "ae_cost" in cost.ArcaneEnergyCost
474
+ ? renderArcaneEnergyCost(translate, translateMap, localeJoin, responsiveTextSize, levels, cost.ArcaneEnergyCost.ae_cost) +
475
+ renderLifePointsCost(translate, cost.ArcaneEnergyCost.lp_cost)
476
+ : renderArcaneEnergyCost(translate, translateMap, localeJoin, responsiveTextSize, levels, cost.ArcaneEnergyCost)
477
+ : renderArcaneEnergyCost(translate, translateMap, localeJoin, responsiveTextSize, levels, cost),
478
+ };
479
+ case "BindingCost":
480
+ return {
481
+ label: translate("Binding Cost"),
482
+ value: renderBindingCost(translate, translateMap, localeCompare, getAllResolvedSelectOptions, responsiveTextSize, cost.BindingCost),
483
+ };
484
+ default:
485
+ return assertExhaustive(cost);
486
+ }
487
+ };
488
+ /**
489
+ * Get a JSON representation of the rules text for a special ability.
490
+ */
491
+ export const getActivatableEntityDescription = createEntityDescriptionCreator(({ getInstanceById, getAllInstances, getResolvedSelectOptionById, getAllResolvedSelectOptions, }, locale, { entity: entityName, content: entry, id }) => {
492
+ const { translate, translateMap } = locale;
493
+ const translation = translateMap(entry.translations);
494
+ const responsiveTextSize = ResponsiveTextSize.Full;
495
+ if (translation === undefined) {
496
+ return undefined;
497
+ }
498
+ const baseEntry = entry;
499
+ const wrappedId = Case(entityName, id);
500
+ return {
501
+ title: translation.name_in_library ??
502
+ translation.name +
503
+ (baseEntry.levels !== undefined
504
+ ? ` I–${romanize(baseEntry.levels)}`
505
+ : ""),
506
+ subtitle: mapNullable(baseEntry.usage_type, usageType => {
507
+ switch (usageType.kind) {
508
+ case "Passive":
509
+ return translate("Passive");
510
+ case "BasicManeuver":
511
+ return translate("Basic Maneuver");
512
+ case "SpecialManeuver":
513
+ return translate("Special Maneuver");
514
+ default:
515
+ return assertExhaustive(usageType);
516
+ }
517
+ }),
518
+ className: "special-ability",
519
+ body: [
520
+ mapNullable(translation.special_rules, specialRules => ({
521
+ type: "plain",
522
+ text: specialRules
523
+ .map(specialRule => specialRule.label === undefined
524
+ ? `- ${specialRule.text}`
525
+ : `- *${specialRule.label}*: ${specialRule.text}`)
526
+ .join("\n"),
527
+ })),
528
+ {
529
+ type: "definitionList",
530
+ items: [
531
+ mapNullable(translation.rules, rules => ({
532
+ label: translate("Rules"),
533
+ value: rules,
534
+ })),
535
+ mapNullable(translation.effect, effect => ({
536
+ label: translate("Effect"),
537
+ value: effect,
538
+ })),
539
+ mapNullable(translation.protective_circle, protectiveCircle => ({
540
+ label: translate("Protective Circle"),
541
+ value: protectiveCircle,
542
+ })),
543
+ mapNullable(translation.warding_circle, wardingCircle => ({
544
+ label: translate("Warding Circle"),
545
+ value: wardingCircle,
546
+ })),
547
+ mapNullable(baseEntry.aspect, aspect => ({
548
+ label: translate("Aspect"),
549
+ value: translateMap(getInstanceById("Aspect", aspect)?.translations)
550
+ ?.name ?? MISSING_VALUE,
551
+ })),
552
+ mapNullable(translation.range, range => ({
553
+ label: translate("Range"),
554
+ value: range,
555
+ })),
556
+ mapNullable(baseEntry.penalty, penalty => ({
557
+ label: translate("Penalty"),
558
+ value: renderPenaltyValue(getInstanceById, translate, translateMap, translation.name, penalty),
559
+ })),
560
+ mapNullable(entry.prerequisites, prerequisites => ({
561
+ label: translate("Prerequisites"),
562
+ value: wrappedId.kind === "Advantage" ||
563
+ wrappedId.kind === "Disadvantage"
564
+ ? printAdvantageDisadvantagePrerequisites(getInstanceById, getResolvedSelectOptionById, locale, prerequisites, translation.name, wrappedId.kind)
565
+ : printGeneralPrerequisites(getInstanceById, getResolvedSelectOptionById, locale, prerequisites, mapNullable(baseEntry.levels, levels => ({
566
+ id: wrappedId,
567
+ levels,
568
+ }))),
569
+ })),
570
+ mapNullable(baseEntry.combat_techniques, combatTechniques => ({
571
+ label: translate("Combat Techniques"),
572
+ value: renderApplicableCombatTechniquesValue(getInstanceById, locale, translation, combatTechniques),
573
+ })),
574
+ mapNullable(baseEntry.volume, volume => ({
575
+ label: translate("Volume"),
576
+ value: renderVolumeValue(translate, translateMap, () => getAllResolvedSelectOptions(wrappedId), responsiveTextSize, volume),
577
+ })),
578
+ mapNullable(baseEntry.cost, cost => renderCost(translate, translateMap, locale.join, locale.compare, () => getAllResolvedSelectOptions(wrappedId), responsiveTextSize, baseEntry.levels, cost)),
579
+ mapNullable(baseEntry.property, property => ({
580
+ label: translate("Property"),
581
+ value: renderPropertyValue(getInstanceById, translate, translateMap, property),
582
+ })),
583
+ mapNullable(entry.ap_value, apValue => {
584
+ const append = translation.ap_value_append !== undefined
585
+ ? ` ${translation.ap_value_append}`
586
+ : "";
587
+ return {
588
+ label: translate("AP Value"),
589
+ value: renderAdventurePointsValue(locale, activatableId => locale.translateMap(getInstanceById(activatableId.kind, fromUniformCase(activatableId))?.translations)?.name, selectOptionId => {
590
+ const selectOption = getResolvedSelectOptionById(wrappedId, selectOptionId);
591
+ if (selectOption === undefined) {
592
+ return undefined;
593
+ }
594
+ const name = locale.translateMap(selectOption.content.translations)?.name;
595
+ if (name !== undefined) {
596
+ return name;
597
+ }
598
+ const { parent: parentId } = selectOption.content;
599
+ return locale.translateMap(getInstanceById(parentId.kind, fromUniformCase(parentId))?.translations)?.name;
600
+ }, () => getAllResolvedSelectOptions(wrappedId), getAllInstances, apValue, entry, translation) + append,
601
+ };
602
+ }),
603
+ ],
604
+ },
605
+ ],
606
+ errata: translation.errata,
607
+ references: entry.src,
608
+ };
609
+ });
@@ -0,0 +1,7 @@
1
+ import type { GetInstanceById } from "../helpers/getTypes.js";
2
+ /**
3
+ * Get a JSON representation of the rules text for an alternative rule.
4
+ */
5
+ export declare const getAlternativeRuleEntityDescription: import("../creator.js").EntityDescriptionCreator<"AlternativeRule", {
6
+ getInstanceById: GetInstanceById<"Publication" | "PlayerType">;
7
+ }, import("../index.js").EntityDescription>;
@@ -0,0 +1,21 @@
1
+ import { createEntityDescriptionCreator } from "../creator.js";
2
+ import { MISSING_VALUE } from "./partial/unknown.js";
3
+ /**
4
+ * Get a JSON representation of the rules text for an alternative rule.
5
+ */
6
+ export const getAlternativeRuleEntityDescription = createEntityDescriptionCreator(({ getInstanceById }, { translateMap }, { content: entry }) => {
7
+ const translation = translateMap(entry.translations);
8
+ if (translation === undefined) {
9
+ return undefined;
10
+ }
11
+ return {
12
+ title: translation.name,
13
+ subtitle: entry.playerTypes
14
+ .map(playerType => translateMap(getInstanceById("PlayerType", playerType)?.translations)?.name ?? MISSING_VALUE)
15
+ .join(", "),
16
+ className: "alternative-rule",
17
+ body: [{ type: "plain", text: translation.description }],
18
+ errata: translation.errata,
19
+ references: entry.src,
20
+ };
21
+ });
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Get a JSON representation of the rules text for an attribute.
3
+ */
4
+ export declare const getAttributeEntityDescription: import("../creator.js").EntityDescriptionCreator<"Attribute", {
5
+ getInstanceById: import("../helpers/getTypes.js").GetInstanceById<"Publication">;
6
+ }, import("../index.js").EntityDescription>;
@@ -0,0 +1,15 @@
1
+ import { createEntityDescriptionCreator } from "../creator.js";
2
+ /**
3
+ * Get a JSON representation of the rules text for an attribute.
4
+ */
5
+ export const getAttributeEntityDescription = createEntityDescriptionCreator((_, { translateMap }, { content: entry }) => {
6
+ const translation = translateMap(entry.translations);
7
+ if (translation === undefined) {
8
+ return undefined;
9
+ }
10
+ return {
11
+ title: `${translation.name} (${translation.abbreviation})`,
12
+ className: "attribute",
13
+ body: [{ type: "plain", text: translation.description }],
14
+ };
15
+ });