@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
@@ -1,40 +1,67 @@
1
+ import { ensureNonEmpty } from "@elyukai/utils/array/nonEmpty";
2
+ import { on } from "@elyukai/utils/function";
3
+ import { Lazy } from "@elyukai/utils/lazy";
4
+ import { compareNullish } from "@elyukai/utils/ordering";
5
+ import { Reader } from "@elyukai/utils/reader";
6
+ import { romanize } from "@elyukai/utils/roman";
7
+ import { numAsc } from "@optolith/helpers/compare";
1
8
  import { isNotNullish, mapNullable } from "@optolith/helpers/nullable";
2
9
  import { assertExhaustive } from "@optolith/helpers/typeSafety";
3
- import { createEntityDescriptionCreator, } from "../index.js";
4
- import { getDurationTranslationForCantrip } from "./partial/rated/activatable/duration.js";
5
- import { getTextForEffect } from "./partial/rated/activatable/effect.js";
6
- import { Entity } from "./partial/rated/activatable/entity.js";
7
- import { getFastOneTimePerformanceParametersTranslations, getFastSustainedPerformanceParametersTranslations, getSlowOneTimePerformanceParametersTranslations, getSlowSustainedPerformanceParametersTranslations, } from "./partial/rated/activatable/index.js";
10
+ import { Case } from "tsondb/schema/gen";
11
+ import { createEntityDescriptionCreator } from "../creator.js";
12
+ import { renderAnimalTypesSection } from "./partial/animalTypes.js";
13
+ import { printAnimistPowerPrerequisites, printGeodeRitualPrerequisites, } from "./partial/prerequisites/index.js";
14
+ import { renderCastingTime, renderFastSkillNonModifiableCastingTime, renderSlowSkillNonModifiableCastingTime, } from "./partial/rated/activatable/castingTime.js";
15
+ import { renderMagicalActionCost, renderModifiableOneTimeCost, renderNonModifiableOneTimeCost, } from "./partial/rated/activatable/cost.js";
16
+ import { renderCheckResultBasedDuration, renderMusicDuration, renderOneTimeDuration, renderSustainedDuration, } from "./partial/rated/activatable/duration.js";
17
+ import { renderEffect } from "./partial/rated/activatable/effect.js";
18
+ import { renderFastPerformanceParameters, renderSlowOneTimePerformanceParameters, renderSlowPerformanceParameters, } from "./partial/rated/activatable/index.js";
19
+ import { ModifiableParameter } from "./partial/rated/activatable/nonModifiableSuffix.js";
8
20
  import { parensIf } from "./partial/rated/activatable/parensIf.js";
9
- import { getTextForCantripRange } from "./partial/rated/activatable/range.js";
10
- import { getTargetCategoryTranslation } from "./partial/rated/activatable/targetCategory.js";
11
- import { createImprovementCost } from "./partial/rated/improvementCost.js";
12
- import { getTextForCheck } from "./partial/rated/skillCheck.js";
13
- import { ResponsiveTextSize } from "./partial/responsiveText.js";
14
- const getTextForProperty = (deps, value) => {
21
+ import { renderNonModifiableRange } from "./partial/rated/activatable/range.js";
22
+ import { Speed } from "./partial/rated/activatable/speed.js";
23
+ import { renderTargetCategory } from "./partial/rated/activatable/targetCategory.js";
24
+ import { renderImprovementCost, renderImprovementCostValue, } from "./partial/rated/improvementCost.js";
25
+ import { renderSkillCheck, renderSkillCheckWithPenalty } from "./partial/rated/skillCheck.js";
26
+ import { formatEnergyR, localeJoinR, responsiveR, responsiveTextR, responsiveTranslateR, translateMapR, translateR, } from "./partial/reader.js";
27
+ import { appendNoteIfNeeded, getResponsiveText, ResponsiveTextSize, } from "./partial/responsiveText.js";
28
+ import { formatTimeSpanR } from "./partial/units/timeSpan.js";
29
+ import { MISSING_VALUE } from "./partial/unknown.js";
30
+ const combineGeneratedTextWithStaticTranslation = (label, generatedText, staticText) => {
31
+ if (generatedText === undefined) {
32
+ return undefined;
33
+ }
34
+ const normalizedStaticText = typeof staticText === "string" ? staticText : staticText?.full;
35
+ return {
36
+ label,
37
+ value: normalizedStaticText !== undefined && generatedText !== normalizedStaticText
38
+ ? `***${generatedText}*** (${normalizedStaticText})`
39
+ : generatedText,
40
+ };
41
+ };
42
+ const renderProperty = (id) => Reader.asks(({ translate, translateMap, getInstanceById }) => {
15
43
  const text = (() => {
16
- const staticEntry = deps.getPropertyById(value.id.property);
17
- const staticEntryTranslation = deps.translateMap(staticEntry?.translations);
44
+ const staticEntry = getInstanceById("Property", id);
45
+ const staticEntryTranslation = translateMap(staticEntry?.translations);
18
46
  if (staticEntryTranslation === undefined) {
19
47
  return "";
20
48
  }
21
49
  return staticEntryTranslation.name;
22
50
  })();
23
51
  return {
24
- label: deps.translate("Property"),
52
+ label: translate("Property"),
25
53
  value: text,
26
54
  };
27
- };
55
+ });
28
56
  const getTextForTraditions = (deps, value) => {
29
57
  const text = (() => {
30
- switch (value.tag) {
58
+ switch (value.kind) {
31
59
  case "General":
32
60
  return deps.translate("General");
33
61
  case "Specific":
34
- return value.specific
35
- .map((trad) => deps.translateMap(deps.getMagicalTraditionById(trad.magical_tradition)?.translations))
62
+ return value.Specific.map(trad => deps.translateMap(deps.getInstanceById("MagicalTradition", trad)?.translations))
36
63
  .filter(isNotNullish)
37
- .map((trad) => trad.name_for_arcane_spellworks ?? trad.name)
64
+ .map(trad => trad.name_for_arcane_spellworks ?? trad.name)
38
65
  .sort(deps.localeCompare)
39
66
  .join(", ");
40
67
  default:
@@ -46,200 +73,861 @@ const getTextForTraditions = (deps, value) => {
46
73
  value: text,
47
74
  };
48
75
  };
49
- const getTraditionNameForArcaneSpellworksById = (ref, getMagicalTraditionById, translateMap) => {
50
- const translation = translateMap(getMagicalTraditionById(ref.id.magical_tradition)?.translations);
76
+ const getTraditionNameForArcaneSpellworksById = (id, getInstanceById, translateMap) => {
77
+ const translation = translateMap(getInstanceById("MagicalTradition", id)?.translations);
51
78
  return translation?.name_for_arcane_spellworks ?? translation?.name;
52
79
  };
53
80
  /**
54
81
  * Get a JSON representation of the rules text for a cantrip.
55
82
  */
56
- export const getCantripEntityDescription = createEntityDescriptionCreator(({ getTargetCategoryById, getPropertyById, getMagicalTraditionById, getCurriculumById, }, locale, entry) => {
83
+ export const getCantripEntityDescription = createEntityDescriptionCreator(({ getInstanceById }, locale, { content: entry }) => {
57
84
  const { translate, translateMap, compare: localeCompare } = locale;
58
85
  const translation = translateMap(entry.translations);
59
86
  if (translation === undefined) {
60
87
  return undefined;
61
88
  }
62
- const range = getTextForCantripRange(locale, ResponsiveTextSize.Full, entry.parameters.range);
63
- const duration = getDurationTranslationForCantrip(locale, ResponsiveTextSize.Full, entry.parameters.duration);
89
+ const env = {
90
+ translate,
91
+ translateMap,
92
+ getInstanceById,
93
+ responsiveTextSize: ResponsiveTextSize.Full,
94
+ };
95
+ const range = renderNonModifiableRange(entry.parameters.range, false).run(env);
96
+ const duration = renderOneTimeDuration(entry.parameters.duration).run(env);
64
97
  return {
65
98
  title: translation.name,
66
99
  className: "cantrip",
67
100
  body: [
68
101
  {
69
- label: translate("Effect"),
70
- value: translation.effect,
102
+ type: "definitionList",
103
+ items: [
104
+ {
105
+ label: translate("Effect"),
106
+ value: translation.effect,
107
+ },
108
+ combineGeneratedTextWithStaticTranslation(translate("Range"), range, translation.range),
109
+ combineGeneratedTextWithStaticTranslation(translate("Duration"), duration, translation.duration),
110
+ renderTargetCategory(entry.target).run(env),
111
+ renderProperty(entry.property).run(env),
112
+ mapNullable(entry.note, note => ({
113
+ label: translate("Note"),
114
+ value: (() => {
115
+ switch (note.kind) {
116
+ case "Common":
117
+ return note.Common.list
118
+ .map(academyOrTradition => {
119
+ switch (academyOrTradition.kind) {
120
+ case "Academy":
121
+ return translateMap(getInstanceById("Curriculum", academyOrTradition.Academy)?.translations)?.name;
122
+ case "Tradition": {
123
+ return mapNullable(getTraditionNameForArcaneSpellworksById(academyOrTradition.Tradition.id, getInstanceById, translateMap), name => name +
124
+ parensIf(translateMap(academyOrTradition.Tradition.translations)?.note));
125
+ }
126
+ default:
127
+ return assertExhaustive(academyOrTradition);
128
+ }
129
+ })
130
+ .filter(isNotNullish)
131
+ .sort(localeCompare)
132
+ .join(", ");
133
+ case "Exclusive":
134
+ return note.Exclusive.traditions
135
+ .map(tradition => getTraditionNameForArcaneSpellworksById(tradition, getInstanceById, translateMap))
136
+ .filter(isNotNullish)
137
+ .sort(localeCompare)
138
+ .join(", ");
139
+ default:
140
+ return assertExhaustive(note);
141
+ }
142
+ })(),
143
+ })),
144
+ ],
71
145
  },
146
+ ],
147
+ errata: translation.errata,
148
+ references: entry.src,
149
+ };
150
+ });
151
+ /**
152
+ * Get a JSON representation of the rules text for a skill.
153
+ */
154
+ export const getSpellEntityDescription = createEntityDescriptionCreator(({ getInstanceById, idMap }, locale, { content: entry }) => {
155
+ const { translate, translateMap, compare: localeCompare } = locale;
156
+ const translation = translateMap(entry.translations);
157
+ if (translation === undefined) {
158
+ return undefined;
159
+ }
160
+ const env = {
161
+ translate,
162
+ translateMap,
163
+ getInstanceById,
164
+ localeJoin: locale.join,
165
+ energyUnit: "ArcaneEnergy",
166
+ responsiveTextSize: ResponsiveTextSize.Full,
167
+ nonModifiableSuffix: (param) => {
168
+ switch (param) {
169
+ case ModifiableParameter.CastingTime:
170
+ return " (you cannot use a modification on this spell’s casting time)";
171
+ case ModifiableParameter.Cost:
172
+ return " (you cannot use a modification on this spell’s cost)";
173
+ case ModifiableParameter.Range:
174
+ return " (you cannot use a modification on this spell’s range)";
175
+ default:
176
+ return assertExhaustive(param);
177
+ }
178
+ },
179
+ };
180
+ const { castingTime, cost, range, duration } = renderFastPerformanceParameters(entry.parameters).run(env);
181
+ return {
182
+ title: translation.name,
183
+ className: "spell",
184
+ body: [
72
185
  {
73
- label: translate("Range"),
74
- value: range !== translation.range
75
- ? `***${range}*** (${translation.range})`
76
- : range,
186
+ type: "definitionList",
187
+ items: [
188
+ renderSkillCheckWithPenalty(entry.check, entry.check_penalty, idMap).run(env),
189
+ renderEffect(translation.effect).run(env),
190
+ combineGeneratedTextWithStaticTranslation(translate("Casting Time"), castingTime, translation.casting_time),
191
+ combineGeneratedTextWithStaticTranslation(translate("AE Cost"), cost, translation.cost),
192
+ combineGeneratedTextWithStaticTranslation(translate("Range"), range, translation.range),
193
+ combineGeneratedTextWithStaticTranslation(translate("Duration"), duration, translation.duration),
194
+ renderTargetCategory(entry.target).run(env),
195
+ renderProperty(entry.property).run(env),
196
+ getTextForTraditions({ translate, translateMap, localeCompare, getInstanceById }, entry.traditions),
197
+ renderImprovementCost(entry.improvement_cost).run(env),
198
+ ],
77
199
  },
200
+ ],
201
+ errata: translation.errata,
202
+ references: entry.src,
203
+ };
204
+ });
205
+ /**
206
+ * Get a JSON representation of the rules text for a ritual.
207
+ */
208
+ export const getRitualEntityDescription = createEntityDescriptionCreator(({ getInstanceById, idMap }, locale, { content: entry }) => {
209
+ const { translate, translateMap, compare: localeCompare } = locale;
210
+ const translation = translateMap(entry.translations);
211
+ if (translation === undefined) {
212
+ return undefined;
213
+ }
214
+ const env = {
215
+ translate,
216
+ translateMap,
217
+ getInstanceById,
218
+ localeJoin: locale.join,
219
+ energyUnit: "ArcaneEnergy",
220
+ responsiveTextSize: ResponsiveTextSize.Full,
221
+ nonModifiableSuffix: (param) => {
222
+ switch (param) {
223
+ case ModifiableParameter.CastingTime:
224
+ return " (you cannot use a modification on this ritual’s ritual time)";
225
+ case ModifiableParameter.Cost:
226
+ return " (you cannot use a modification on this ritual’s cost)";
227
+ case ModifiableParameter.Range:
228
+ return " (you cannot use a modification on this ritual’s range)";
229
+ default:
230
+ return assertExhaustive(param);
231
+ }
232
+ },
233
+ };
234
+ const { castingTime, cost, range, duration } = renderSlowPerformanceParameters(entry.parameters).run(env);
235
+ return {
236
+ title: translation.name,
237
+ className: "ritual",
238
+ body: [
78
239
  {
79
- label: translate("Duration"),
80
- value: duration !== translation.duration
81
- ? `***${duration}*** (${translation.duration})`
82
- : duration,
240
+ type: "definitionList",
241
+ items: [
242
+ renderSkillCheckWithPenalty(entry.check, entry.check_penalty, idMap).run(env),
243
+ renderEffect(translation.effect).run(env),
244
+ combineGeneratedTextWithStaticTranslation(translate("Ritual Time"), castingTime, translation.casting_time),
245
+ combineGeneratedTextWithStaticTranslation(translate("AE Cost"), cost, translation.cost),
246
+ combineGeneratedTextWithStaticTranslation(translate("Range"), range, translation.range),
247
+ combineGeneratedTextWithStaticTranslation(translate("Duration"), duration, translation.duration),
248
+ renderTargetCategory(entry.target).run(env),
249
+ renderProperty(entry.property).run(env),
250
+ getTextForTraditions({ translate, translateMap, localeCompare, getInstanceById }, entry.traditions),
251
+ renderImprovementCost(entry.improvement_cost).run(env),
252
+ ],
83
253
  },
84
- getTargetCategoryTranslation(getTargetCategoryById, locale, entry.target),
85
- getTextForProperty({ translate, translateMap, getPropertyById }, entry.property),
86
- mapNullable(entry.note, (note) => ({
87
- label: translate("Note"),
88
- value: (() => {
89
- switch (note.tag) {
90
- case "Common":
91
- return note.common.list
92
- .map((academyOrTradition) => {
93
- switch (academyOrTradition.tag) {
94
- case "Academy":
95
- return translateMap(getCurriculumById(academyOrTradition.academy.id.curriculum)?.translations)?.name;
96
- case "Tradition": {
97
- return mapNullable(getTraditionNameForArcaneSpellworksById(academyOrTradition.tradition, getMagicalTraditionById, translateMap), (name) => name +
98
- parensIf(translateMap(academyOrTradition.tradition.translations)?.note));
99
- }
100
- default:
101
- return assertExhaustive(academyOrTradition);
102
- }
103
- })
104
- .filter(isNotNullish)
105
- .sort(localeCompare)
106
- .join(", ");
107
- case "Exclusive":
108
- return note.exclusive.traditions
109
- .map((tradition) => getTraditionNameForArcaneSpellworksById(tradition, getMagicalTraditionById, translateMap))
110
- .filter(isNotNullish)
111
- .sort(localeCompare)
112
- .join(", ");
113
- default:
114
- return assertExhaustive(note);
115
- }
116
- })(),
117
- })),
118
254
  ],
255
+ errata: translation.errata,
119
256
  references: entry.src,
120
257
  };
121
258
  });
122
259
  /**
123
- * Get a JSON representation of the rules text for a skill.
260
+ * Get a JSON representation of the rules text for a curse.
124
261
  */
125
- export const getSpellEntityDescription = createEntityDescriptionCreator(({ getAttributeById, getSpirit, getToughness, getSkillModificationLevelById, getTargetCategoryById, getPropertyById, getMagicalTraditionById, }, locale, entry) => {
126
- const { translate, translateMap, compare: localeCompare } = locale;
262
+ export const getCurseEntityDescription = createEntityDescriptionCreator(({ getInstanceById, idMap }, locale, { content: entry }) => {
263
+ const { translate, translateMap } = locale;
127
264
  const translation = translateMap(entry.translations);
128
265
  if (translation === undefined) {
129
266
  return undefined;
130
267
  }
131
- const { castingTime, cost, range, duration } = (() => {
132
- switch (entry.parameters.tag) {
133
- case "OneTime":
134
- return getFastOneTimePerformanceParametersTranslations(getSkillModificationLevelById, locale, Entity.Spell, ResponsiveTextSize.Full, entry.parameters.one_time);
135
- case "Sustained":
136
- return getFastSustainedPerformanceParametersTranslations(getSkillModificationLevelById, locale, Entity.Spell, ResponsiveTextSize.Full, entry.parameters.sustained);
137
- default:
138
- return assertExhaustive(entry.parameters);
139
- }
140
- })();
268
+ const env = {
269
+ translate,
270
+ translateMap,
271
+ getInstanceById,
272
+ localeJoin: locale.join,
273
+ energyUnit: "ArcaneEnergy",
274
+ responsiveTextSize: ResponsiveTextSize.Full,
275
+ };
276
+ const cost = renderMagicalActionCost(entry.parameters.cost).run(env);
277
+ const duration = renderOneTimeDuration(entry.parameters.duration).run(env);
141
278
  return {
142
279
  title: translation.name,
143
- className: "spell",
280
+ className: "curse",
144
281
  body: [
145
- getTextForCheck({ translate, translateMap, getAttributeById }, entry.check, {
146
- value: entry.check_penalty,
147
- responsiveText: ResponsiveTextSize.Full,
148
- getSpirit,
149
- getToughness,
150
- }),
151
- ...getTextForEffect(locale, translation.effect),
152
282
  {
153
- label: translate("Casting Time"),
154
- value: castingTime !== translation.casting_time.full
155
- ? `***${castingTime}*** (${translation.casting_time.full})`
156
- : castingTime,
283
+ type: "definitionList",
284
+ items: [
285
+ renderSkillCheckWithPenalty(entry.check, entry.check_penalty, idMap).run(env),
286
+ renderEffect(translation.effect).run(env),
287
+ combineGeneratedTextWithStaticTranslation(translate("AE Cost"), cost, translation.cost),
288
+ combineGeneratedTextWithStaticTranslation(translate("Duration"), duration, translation.duration),
289
+ renderProperty(entry.property).run(env),
290
+ {
291
+ label: translate("Improvement Cost"),
292
+ value: "B",
293
+ },
294
+ ],
157
295
  },
296
+ ],
297
+ errata: translation.errata,
298
+ references: entry.src,
299
+ };
300
+ });
301
+ const renderMagicalActionSkill = (skill) => Reader.asks(({ translate, translateMap, localeCompare, localeJoin, getInstanceById }) => ({
302
+ label: translate("Skill"),
303
+ value: localeJoin(skill
304
+ .map(id => translateMap(getInstanceById("Skill", id)?.translations)?.name ?? MISSING_VALUE)
305
+ .toSorted(localeCompare), "disjunction"),
306
+ }));
307
+ /**
308
+ * Get a JSON representation of the rules text for an Elven magical song.
309
+ */
310
+ export const getElvenMagicalSongEntityDescription = createEntityDescriptionCreator(({ getInstanceById, idMap }, locale, { content: entry }) => {
311
+ const { translate, translateMap } = locale;
312
+ const translation = translateMap(entry.translations);
313
+ if (translation === undefined) {
314
+ return undefined;
315
+ }
316
+ const env = {
317
+ translate,
318
+ translateMap,
319
+ getInstanceById,
320
+ localeJoin: locale.join,
321
+ localeCompare: locale.compare,
322
+ energyUnit: "ArcaneEnergy",
323
+ responsiveTextSize: ResponsiveTextSize.Full,
324
+ };
325
+ const cost = renderNonModifiableOneTimeCost(entry.parameters.cost, false).run(env);
326
+ return {
327
+ title: translation.name,
328
+ className: "elven-magical-song",
329
+ body: [
158
330
  {
159
- label: translate("AE Cost"),
160
- value: cost !== translation.cost.full
161
- ? `***${cost}*** (${translation.cost.full})`
162
- : cost,
331
+ type: "definitionList",
332
+ items: [
333
+ renderSkillCheckWithPenalty(entry.check, entry.check_penalty, idMap).run(env),
334
+ renderEffect(translation.effect).run(env),
335
+ renderMagicalActionSkill(entry.skill).run(env),
336
+ combineGeneratedTextWithStaticTranslation(translate("AE Cost"), cost, translation.cost),
337
+ renderProperty(entry.property).run(env),
338
+ renderImprovementCost(entry.improvement_cost).run(env),
339
+ ],
163
340
  },
341
+ ],
342
+ errata: translation.errata,
343
+ references: entry.src,
344
+ };
345
+ });
346
+ /**
347
+ * Get a JSON representation of the rules text for a domination ritual.
348
+ */
349
+ export const getDominationRitualEntityDescription = createEntityDescriptionCreator(({ getInstanceById, idMap }, locale, { content: entry }) => {
350
+ const { translate, translateMap } = locale;
351
+ const translation = translateMap(entry.translations);
352
+ if (translation === undefined) {
353
+ return undefined;
354
+ }
355
+ const env = {
356
+ translate,
357
+ translateMap,
358
+ getInstanceById,
359
+ speed: Speed.Slow,
360
+ energyUnit: "ArcaneEnergy",
361
+ responsiveTextSize: ResponsiveTextSize.Full,
362
+ };
363
+ const cost = renderModifiableOneTimeCost(entry.parameters.cost).run(env);
364
+ const duration = renderOneTimeDuration(entry.parameters.duration).run(env);
365
+ return {
366
+ title: translation.name,
367
+ className: "domination-ritual",
368
+ body: [
164
369
  {
165
- label: translate("Range"),
166
- value: range !== translation.range.full
167
- ? `***${range}*** (${translation.range.full})`
168
- : range,
370
+ type: "definitionList",
371
+ items: [
372
+ renderSkillCheckWithPenalty(entry.check, entry.check_penalty, idMap).run(env),
373
+ renderEffect(translation.effect).run(env),
374
+ combineGeneratedTextWithStaticTranslation(translate("AE Cost"), cost, translation.cost),
375
+ combineGeneratedTextWithStaticTranslation(translate("Duration"), duration, translation.duration),
376
+ renderProperty(entry.property).run(env),
377
+ {
378
+ label: translate("Improvement Cost"),
379
+ value: "B",
380
+ },
381
+ ],
169
382
  },
383
+ ],
384
+ errata: translation.errata,
385
+ references: entry.src,
386
+ };
387
+ });
388
+ const renderMusicTradition = (translate, translateMap, localeCompare, getInstanceById, entity, musicTraditions) => ({
389
+ label: translate("Music Tradition"),
390
+ value: ensureNonEmpty(musicTraditions
391
+ .map(trad => translateMap(getInstanceById(entity, trad.id)?.translations)?.name)
392
+ .filter(isNotNullish)
393
+ .toSorted(localeCompare))?.join(", ") ?? MISSING_VALUE,
394
+ });
395
+ /**
396
+ * Get a JSON representation of the rules text for a magical dance.
397
+ */
398
+ export const getMagicalDanceEntityDescription = createEntityDescriptionCreator(({ getInstanceById }, locale, { content: entry }) => {
399
+ const { translate, translateMap } = locale;
400
+ const translation = translateMap(entry.translations);
401
+ if (translation === undefined) {
402
+ return undefined;
403
+ }
404
+ const env = {
405
+ translate,
406
+ translateMap,
407
+ getInstanceById,
408
+ localeJoin: locale.join,
409
+ energyUnit: "ArcaneEnergy",
410
+ responsiveTextSize: ResponsiveTextSize.Full,
411
+ };
412
+ const duration = renderMusicDuration(entry.parameters.duration).run(env);
413
+ const cost = renderMagicalActionCost(entry.parameters.cost).run(env);
414
+ return {
415
+ title: translation.name,
416
+ className: "magical-dance",
417
+ body: [
170
418
  {
171
- label: translate("Duration"),
172
- value: duration !== translation.duration.full
173
- ? `***${duration}*** (${translation.duration.full})`
174
- : duration,
419
+ type: "definitionList",
420
+ items: [
421
+ renderSkillCheck(entry.check).run(env),
422
+ renderEffect(translation.effect).run(env),
423
+ combineGeneratedTextWithStaticTranslation(translate("Duration"), duration, translation.duration),
424
+ combineGeneratedTextWithStaticTranslation(translate("AE Cost"), cost, translation.cost),
425
+ renderProperty(entry.property).run(env),
426
+ renderMusicTradition(translate, translateMap, locale.compare, getInstanceById, "ArcaneDancerTradition", entry.music_tradition),
427
+ renderImprovementCost(entry.improvement_cost).run(env),
428
+ ],
175
429
  },
176
- getTargetCategoryTranslation(getTargetCategoryById, locale, entry.target),
177
- getTextForProperty({ translate, translateMap, getPropertyById }, entry.property),
178
- getTextForTraditions({ translate, translateMap, localeCompare, getMagicalTraditionById }, entry.traditions),
179
- createImprovementCost(translate, entry.improvement_cost),
180
430
  ],
431
+ errata: translation.errata,
181
432
  references: entry.src,
182
433
  };
183
434
  });
184
435
  /**
185
- * Get a JSON representation of the rules text for a ritual.
436
+ * Get a JSON representation of the rules text for a magical melody.
186
437
  */
187
- export const getRitualEntityDescription = createEntityDescriptionCreator(({ getAttributeById, getSpirit, getToughness, getSkillModificationLevelById, getTargetCategoryById, getPropertyById, getMagicalTraditionById, }, locale, entry) => {
188
- const { translate, translateMap, compare: localeCompare } = locale;
438
+ export const getMagicalMelodyEntityDescription = createEntityDescriptionCreator(({ getInstanceById }, locale, { content: entry }) => {
439
+ const { translate, translateMap } = locale;
189
440
  const translation = translateMap(entry.translations);
190
441
  if (translation === undefined) {
191
442
  return undefined;
192
443
  }
193
- const { castingTime, cost, range, duration } = (() => {
194
- switch (entry.parameters.tag) {
195
- case "OneTime":
196
- return getSlowOneTimePerformanceParametersTranslations(getSkillModificationLevelById, locale, Entity.Ritual, ResponsiveTextSize.Full, entry.parameters.one_time);
197
- case "Sustained":
198
- return getSlowSustainedPerformanceParametersTranslations(getSkillModificationLevelById, locale, Entity.Ritual, ResponsiveTextSize.Full, entry.parameters.sustained);
444
+ const env = {
445
+ translate,
446
+ translateMap,
447
+ getInstanceById,
448
+ localeCompare: locale.compare,
449
+ localeJoin: locale.join,
450
+ energyUnit: "ArcaneEnergy",
451
+ responsiveTextSize: ResponsiveTextSize.Full,
452
+ };
453
+ const duration = renderMusicDuration(entry.parameters.duration).run(env);
454
+ const cost = renderMagicalActionCost(entry.parameters.cost).run(env);
455
+ return {
456
+ title: translation.name,
457
+ className: "magical-dance",
458
+ body: [
459
+ {
460
+ type: "definitionList",
461
+ items: [
462
+ renderSkillCheck(entry.check).run(env),
463
+ renderEffect(translation.effect).run(env),
464
+ combineGeneratedTextWithStaticTranslation(translate("Duration"), duration, translation.duration),
465
+ renderMagicalActionSkill(entry.skill).run(env),
466
+ combineGeneratedTextWithStaticTranslation(translate("AE Cost"), cost, translation.cost),
467
+ renderProperty(entry.property).run(env),
468
+ renderMusicTradition(translate, translateMap, locale.compare, getInstanceById, "ArcaneBardTradition", entry.music_tradition),
469
+ renderImprovementCost(entry.improvement_cost).run(env),
470
+ ],
471
+ },
472
+ ],
473
+ errata: translation.errata,
474
+ references: entry.src,
475
+ };
476
+ });
477
+ const renderFamiliarsTrickProperty = (translateMap, getInstanceById, responsiveTextSize, property) => {
478
+ switch (property.kind) {
479
+ case "Fixed":
480
+ return (translateMap(getInstanceById("Property", property.Fixed)?.translations)?.name ??
481
+ MISSING_VALUE);
482
+ case "Indefinite":
483
+ return getResponsiveText(translateMap(property.Indefinite.translations)?.description, responsiveTextSize);
484
+ default:
485
+ return assertExhaustive(property);
486
+ }
487
+ };
488
+ const renderFamiliarsTrickPerformanceParameters = (params) => {
489
+ switch (params.kind) {
490
+ case "OneTime":
491
+ return renderMagicalActionCost(params.OneTime.cost).then(cost => renderOneTimeDuration(params.OneTime.duration).map(duration => ({
492
+ cost,
493
+ duration,
494
+ })));
495
+ case "OneTimeInterval":
496
+ return renderNonModifiableOneTimeCost(params.OneTimeInterval.cost, false).then(cost => translateR("depends on spent AE").map(duration => ({
497
+ cost,
498
+ duration,
499
+ })));
500
+ case "Sustained":
501
+ return renderNonModifiableOneTimeCost(params.Sustained.cost, false).then(cost => renderSustainedDuration(undefined).map(duration => ({
502
+ cost,
503
+ duration,
504
+ })));
505
+ default:
506
+ return assertExhaustive(params);
507
+ }
508
+ };
509
+ /**
510
+ * Get a JSON representation of the rules text for a familiar’s trick.
511
+ */
512
+ export const getFamiliarsTrickEntityDescription = createEntityDescriptionCreator(({ getInstanceById }, locale, { content: entry }) => {
513
+ const { translate, translateMap } = locale;
514
+ const translation = translateMap(entry.translations);
515
+ if (translation === undefined) {
516
+ return undefined;
517
+ }
518
+ const env = {
519
+ translate,
520
+ translateMap,
521
+ getInstanceById,
522
+ localeJoin: locale.join,
523
+ energyUnit: "ArcaneEnergy",
524
+ responsiveTextSize: ResponsiveTextSize.Full,
525
+ };
526
+ const { cost, duration } = renderFamiliarsTrickPerformanceParameters(entry.parameters).run(env);
527
+ return {
528
+ title: translation.name,
529
+ className: "magical-dance",
530
+ body: [
531
+ {
532
+ type: "definitionList",
533
+ items: [
534
+ {
535
+ label: locale.translate("Effect"),
536
+ value: translation.effect,
537
+ },
538
+ renderAnimalTypesSection(translate, translateMap, locale.compare, getInstanceById, entry.animal_types),
539
+ combineGeneratedTextWithStaticTranslation(translate("AE Cost"), cost, translation.cost),
540
+ combineGeneratedTextWithStaticTranslation(translate("Duration"), duration, translation.duration),
541
+ {
542
+ label: translate("Property"),
543
+ value: renderFamiliarsTrickProperty(translateMap, getInstanceById, ResponsiveTextSize.Full, entry.property),
544
+ },
545
+ {
546
+ label: translate("AP Value"),
547
+ value: entry.ap_value === undefined
548
+ ? translate("All familiars know this trick by default.")
549
+ : translate(".input {$value :number} {{{$value} Adventure Points}}", {
550
+ value: entry.ap_value,
551
+ }),
552
+ },
553
+ ],
554
+ },
555
+ ],
556
+ errata: translation.errata,
557
+ references: entry.src,
558
+ };
559
+ });
560
+ const renderAnimistPowerPerformanceParameters = (params) => {
561
+ switch (params.kind) {
562
+ case "OneTime":
563
+ return renderMagicalActionCost(params.OneTime.cost).then(cost => renderOneTimeDuration(params.OneTime.duration).map(duration => ({
564
+ cost,
565
+ duration,
566
+ })));
567
+ case "Sustained":
568
+ return renderMagicalActionCost(params.Sustained.cost).then(cost => renderSustainedDuration(undefined).map(duration => ({
569
+ cost,
570
+ duration,
571
+ })));
572
+ default:
573
+ return assertExhaustive(params);
574
+ }
575
+ };
576
+ const renderAnimistPowerTribeTradition = (translate, translateMap, localeCompare, getInstanceById, tribeTradition) => {
577
+ if (tribeTradition.length === 0) {
578
+ return translate("General");
579
+ }
580
+ return (ensureNonEmpty(tribeTradition
581
+ .map(id => translateMap(getInstanceById("Tribe", id)?.translations)?.name)
582
+ .filter(isNotNullish)
583
+ .toSorted(localeCompare))?.join(", ") ?? MISSING_VALUE);
584
+ };
585
+ const renderAnimistPowerImprovementCost = (improvementCost) => {
586
+ switch (improvementCost.kind) {
587
+ case "Fixed":
588
+ return renderImprovementCost(improvementCost.Fixed);
589
+ case "ByPrimaryPatron":
590
+ return Reader.asks(({ translate }) => ({
591
+ label: translate("Improvement Cost"),
592
+ value: translate("Depends on animal type"),
593
+ }));
594
+ default:
595
+ return assertExhaustive(improvementCost);
596
+ }
597
+ };
598
+ /**
599
+ * Get a JSON representation of the rules text for a animist power.
600
+ */
601
+ export const getAnimistPowerEntityDescription = createEntityDescriptionCreator(({ getInstanceById }, locale, { content: entry }) => {
602
+ const { translate, translateMap } = locale;
603
+ const translation = translateMap(entry.translations);
604
+ if (translation === undefined) {
605
+ return undefined;
606
+ }
607
+ const env = {
608
+ translate,
609
+ translateMap,
610
+ getInstanceById,
611
+ localeJoin: locale.join,
612
+ energyUnit: "ArcaneEnergy",
613
+ responsiveTextSize: ResponsiveTextSize.Full,
614
+ };
615
+ const { cost, duration } = renderAnimistPowerPerformanceParameters(entry.parameters).run(env);
616
+ const levels = entry.levels?.length ?? 1;
617
+ const prerequisites = entry.prerequisites === undefined
618
+ ? undefined
619
+ : printAnimistPowerPrerequisites(getInstanceById, locale, entry.prerequisites);
620
+ const additionalEffectsFromLevels = entry.levels
621
+ ?.map(level => translateMap(level.translations)?.effect)
622
+ .filter(isNotNullish)
623
+ .map(text => text)
624
+ .join("\n\n") ?? "";
625
+ const mergedEffect = (() => {
626
+ switch (translation.effect.kind) {
627
+ case "Plain":
628
+ return Case("Plain", {
629
+ text: `${translation.effect.Plain.text}\n\n${additionalEffectsFromLevels}`,
630
+ });
631
+ case "ForEachQualityLevel":
632
+ return Case("ForEachQualityLevel", {
633
+ ...translation.effect.ForEachQualityLevel,
634
+ text_after: [
635
+ translation.effect.ForEachQualityLevel.text_after,
636
+ additionalEffectsFromLevels,
637
+ ]
638
+ .filter(isNotNullish)
639
+ .join("\n\n"),
640
+ });
641
+ case "ForEachTwoQualityLevels":
642
+ return Case("ForEachTwoQualityLevels", {
643
+ ...translation.effect.ForEachTwoQualityLevels,
644
+ text_after: [
645
+ translation.effect.ForEachTwoQualityLevels.text_after,
646
+ additionalEffectsFromLevels,
647
+ ]
648
+ .filter(isNotNullish)
649
+ .join("\n\n"),
650
+ });
199
651
  default:
200
- return assertExhaustive(entry.parameters);
652
+ return assertExhaustive(translation.effect);
201
653
  }
202
654
  })();
655
+ return {
656
+ title: (translation.name_in_library ?? translation.name) +
657
+ parensIf(levels > 2
658
+ ? Array.from({ length: levels }, (_, i) => romanize(i + 1)).join("/")
659
+ : undefined),
660
+ className: "animist-power",
661
+ body: [
662
+ {
663
+ type: "definitionList",
664
+ items: [
665
+ renderSkillCheck(entry.check).run(env),
666
+ renderEffect(mergedEffect).run(env),
667
+ combineGeneratedTextWithStaticTranslation(translate("AE Cost"), cost, translation.cost),
668
+ combineGeneratedTextWithStaticTranslation(translate("Duration"), duration, translation.duration),
669
+ renderProperty(entry.property).run(env),
670
+ {
671
+ label: translate("Tribe Tradition"),
672
+ value: renderAnimistPowerTribeTradition(translate, translateMap, locale.compare, getInstanceById, entry.tribe_tradition),
673
+ },
674
+ renderAnimistPowerImprovementCost(entry.improvement_cost).run(env),
675
+ combineGeneratedTextWithStaticTranslation(translate("Prerequisites"), prerequisites, translation.prerequisites),
676
+ ],
677
+ },
678
+ ],
679
+ errata: translation.errata,
680
+ references: entry.src,
681
+ };
682
+ });
683
+ /**
684
+ * Get a JSON representation of the rules text for a Goede ritual.
685
+ */
686
+ export const getGeodeRitualEntityDescription = createEntityDescriptionCreator(({ getInstanceById }, locale, { content: entry }) => {
687
+ const { translate, translateMap } = locale;
688
+ const translation = translateMap(entry.translations);
689
+ if (translation === undefined) {
690
+ return undefined;
691
+ }
692
+ const env = {
693
+ translate,
694
+ translateMap,
695
+ getInstanceById,
696
+ localeJoin: locale.join,
697
+ energyUnit: "ArcaneEnergy",
698
+ responsiveTextSize: ResponsiveTextSize.Full,
699
+ };
700
+ const castingTime = renderSlowSkillNonModifiableCastingTime(entry.parameters.casting_time).run(env);
701
+ const cost = renderMagicalActionCost(entry.parameters.cost).run(env);
702
+ const range = renderNonModifiableRange(entry.parameters.range.kind === "Fixed"
703
+ ? {
704
+ kind: "Fixed",
705
+ Fixed: { ...entry.parameters.range.Fixed, unit: { kind: "Steps" } },
706
+ }
707
+ : entry.parameters.range, false).run(env);
708
+ const duration = renderOneTimeDuration(entry.parameters.duration).run(env);
203
709
  return {
204
710
  title: translation.name,
205
- className: "ritual",
711
+ className: "geode-ritual",
206
712
  body: [
207
- getTextForCheck({ translate, translateMap, getAttributeById }, entry.check, {
208
- value: entry.check_penalty,
209
- responsiveText: ResponsiveTextSize.Full,
210
- getSpirit,
211
- getToughness,
212
- }),
213
- ...getTextForEffect(locale, translation.effect),
214
713
  {
215
- label: translate("Ritual Time"),
216
- value: castingTime !== translation.casting_time.full
217
- ? `***${castingTime}*** (${translation.casting_time.full})`
218
- : castingTime,
714
+ type: "definitionList",
715
+ items: [
716
+ renderSkillCheck(entry.check).run(env),
717
+ renderEffect(translation.effect).run(env),
718
+ combineGeneratedTextWithStaticTranslation(translate("Ritual Time"), castingTime, translation.casting_time),
719
+ combineGeneratedTextWithStaticTranslation(translate("AE Cost"), cost, translation.cost),
720
+ combineGeneratedTextWithStaticTranslation(translate("Range"), range, translation.range),
721
+ combineGeneratedTextWithStaticTranslation(translate("Duration"), duration, translation.duration),
722
+ renderTargetCategory(entry.target).run(env),
723
+ entry.prerequisites === undefined
724
+ ? undefined
725
+ : {
726
+ label: translate("Prerequisites"),
727
+ value: printGeodeRitualPrerequisites(getInstanceById, locale, entry.prerequisites),
728
+ },
729
+ renderProperty(entry.property).run(env),
730
+ {
731
+ label: translate("Improvement Cost"),
732
+ value: "B",
733
+ },
734
+ ],
219
735
  },
736
+ ],
737
+ errata: translation.errata,
738
+ references: entry.src,
739
+ };
740
+ });
741
+ /**
742
+ * Get a JSON representation of the rules text for a jester trick.
743
+ */
744
+ export const getJesterTrickEntityDescription = createEntityDescriptionCreator(({ getInstanceById, idMap }, locale, { content: entry }) => {
745
+ const { translate, translateMap } = locale;
746
+ const translation = translateMap(entry.translations);
747
+ if (translation === undefined) {
748
+ return undefined;
749
+ }
750
+ const env = {
751
+ translate,
752
+ translateMap,
753
+ getInstanceById,
754
+ localeJoin: locale.join,
755
+ energyUnit: "ArcaneEnergy",
756
+ responsiveTextSize: ResponsiveTextSize.Full,
757
+ };
758
+ const { value: actions, ...castingTimeRest } = entry.parameters.casting_time;
759
+ const castingTime = renderFastSkillNonModifiableCastingTime({
760
+ ...castingTimeRest,
761
+ actions,
762
+ }).run(env);
763
+ const cost = renderNonModifiableOneTimeCost(entry.parameters.cost, false).run(env);
764
+ const range = renderNonModifiableRange(entry.parameters.range.kind === "Fixed"
765
+ ? {
766
+ kind: "Fixed",
767
+ Fixed: { ...entry.parameters.range.Fixed, unit: { kind: "Steps" } },
768
+ }
769
+ : entry.parameters.range, false).run(env);
770
+ const duration = renderOneTimeDuration(entry.parameters.duration).run(env);
771
+ return {
772
+ title: translation.name,
773
+ className: "jester-trick",
774
+ body: [
220
775
  {
221
- label: translate("AE Cost"),
222
- value: cost !== translation.cost.full
223
- ? `***${cost}*** (${translation.cost.full})`
224
- : cost,
776
+ type: "definitionList",
777
+ items: [
778
+ renderSkillCheckWithPenalty(entry.check, entry.check_penalty, idMap).run(env),
779
+ renderEffect(translation.effect).run(env),
780
+ combineGeneratedTextWithStaticTranslation(translate("Casting Time"), castingTime, translation.casting_time),
781
+ combineGeneratedTextWithStaticTranslation(translate("AE Cost"), cost, translation.cost),
782
+ combineGeneratedTextWithStaticTranslation(translate("Range"), range, translation.range),
783
+ combineGeneratedTextWithStaticTranslation(translate("Duration"), duration, translation.duration),
784
+ renderTargetCategory(entry.target).run(env),
785
+ renderProperty(entry.property).run(env),
786
+ renderImprovementCost(entry.improvement_cost).run(env),
787
+ ],
225
788
  },
789
+ ],
790
+ errata: translation.errata,
791
+ references: entry.src,
792
+ };
793
+ });
794
+ /**
795
+ * Get a JSON representation of the rules text for a Zibilja ritual.
796
+ */
797
+ export const getZibiljaRitualEntityDescription = createEntityDescriptionCreator(({ getInstanceById, idMap }, locale, { content: entry }) => {
798
+ const { translate, translateMap } = locale;
799
+ const translation = translateMap(entry.translations);
800
+ if (translation === undefined) {
801
+ return undefined;
802
+ }
803
+ const env = {
804
+ translate,
805
+ translateMap,
806
+ getInstanceById,
807
+ localeCompare: locale.compare,
808
+ localeJoin: locale.join,
809
+ energyUnit: "ArcaneEnergy",
810
+ responsiveTextSize: ResponsiveTextSize.Full,
811
+ };
812
+ const { castingTime, cost, range, duration } = renderSlowOneTimePerformanceParameters(nestedCastingTime => renderCastingTime(renderSlowSkillNonModifiableCastingTime, nestedCastingTime).with(nestedEnv => ({ ...nestedEnv, speed: Speed.Slow })), entry.parameters).run(env);
813
+ return {
814
+ title: translation.name,
815
+ className: "zibilja-ritual",
816
+ body: [
226
817
  {
227
- label: translate("Range"),
228
- value: range !== translation.range.full
229
- ? `***${range}*** (${translation.range.full})`
230
- : range,
818
+ type: "definitionList",
819
+ items: [
820
+ renderSkillCheckWithPenalty(entry.check, entry.check_penalty, idMap).run(env),
821
+ renderEffect(translation.effect).run(env),
822
+ combineGeneratedTextWithStaticTranslation(translate("Ritual Time"), castingTime, translation.casting_time),
823
+ combineGeneratedTextWithStaticTranslation(translate("AE Cost"), cost, translation.cost),
824
+ combineGeneratedTextWithStaticTranslation(translate("Range"), range, translation.range),
825
+ combineGeneratedTextWithStaticTranslation(translate("Duration"), duration, translation.duration),
826
+ renderTargetCategory(entry.target).run(env),
827
+ renderProperty(entry.property).run(env),
828
+ renderImprovementCost(entry.improvement_cost).run(env),
829
+ ],
231
830
  },
831
+ ],
832
+ errata: translation.errata,
833
+ references: entry.src,
834
+ };
835
+ });
836
+ const deriveValueGroupsFromMagicalRuneOptions = (options, grouper, comparator, printValue) => Reader.sequence(Map.groupBy(options.value, grouper)
837
+ .entries()
838
+ .toArray()
839
+ .toSorted(on(item => item[0], comparator))
840
+ .map(([value, matchingOptions]) => Reader.asks(({ translateMap, localeJoin, localeCompare }) => `${printValue(value)} (${localeJoin(matchingOptions
841
+ .map(option => translateMap(option.translations)?.name ?? MISSING_VALUE)
842
+ .toSorted(localeCompare), "conjunction")})`))).then(formattedOptions => localeJoinR(formattedOptions, "disjunction"));
843
+ const renderMagicalRuneCost = (options, cost) => {
844
+ switch (cost.kind) {
845
+ case "Single":
846
+ return formatEnergyR(cost.Single.value).thenW(text => appendNoteIfNeeded(cost.Single.translations, text));
847
+ case "Disjunction":
848
+ return Reader.sequence(cost.Disjunction.list.map(costItem => formatEnergyR(costItem.value).thenW(text => appendNoteIfNeeded(costItem.translations, text)))).thenW(text => localeJoinR(text, "disjunction"));
849
+ case "DerivedFromOption":
850
+ return deriveValueGroupsFromMagicalRuneOptions(options, option => option.cost?.value, compareNullish(numAsc), num => num?.toString() ?? MISSING_VALUE).thenW(formatEnergyR);
851
+ default:
852
+ return assertExhaustive(cost);
853
+ }
854
+ };
855
+ const renderSplitMagicalRuneParameterTranslation = (parameter) => responsiveR(() => parameter.fast.full, () => parameter.fast.abbr).map2(responsiveR(() => parameter.slow.full, () => parameter.slow.abbr), (fast, slow) => `${slow} / ${fast}`);
856
+ const renderMagicalRunCraftingTimePart = (craftingTime, unit) => formatTimeSpanR(unit, craftingTime.value).thenW(text => {
857
+ if (craftingTime.per === undefined) {
858
+ return Reader.of(text);
859
+ }
860
+ const { translations } = craftingTime.per;
861
+ return translateMapR(translations).thenW(translation => {
862
+ if (translation === undefined) {
863
+ return Reader.of(text);
864
+ }
865
+ return responsiveTextR(translation.countable).thenW(countable => responsiveTranslateR("{$cost} per {$countable}", "{$cost}/{$countable}", {
866
+ cost: text,
867
+ countable,
868
+ }));
869
+ });
870
+ });
871
+ const renderMagicalRuneCraftingTime = (craftingTime) => renderMagicalRunCraftingTimePart(craftingTime, "Actions").map2(renderMagicalRunCraftingTimePart(craftingTime, "Days"), (fast, slow) => `${slow} / ${fast}`);
872
+ const renderMagicalRuneDuration = (duration) => renderCheckResultBasedDuration(duration.fast).map2(renderCheckResultBasedDuration(duration.slow), (fast, slow) => `${slow} / ${fast}`);
873
+ const renderMagicalRuneImprovementCost = (options, improvementCost) => {
874
+ switch (improvementCost.kind) {
875
+ case "Constant":
876
+ return renderImprovementCost(improvementCost.Constant);
877
+ case "DerivedFromOption":
878
+ return deriveValueGroupsFromMagicalRuneOptions(options, option => option.improvement_cost === undefined
879
+ ? undefined
880
+ : renderImprovementCostValue(option.improvement_cost), compareNullish((a, b) => a.localeCompare(b)), selectedImprovementCost => selectedImprovementCost ?? MISSING_VALUE)
881
+ .thenW(formatEnergyR)
882
+ .then(value => translateR("Improvement Cost").map(label => ({ label, value })));
883
+ default:
884
+ return assertExhaustive(improvementCost);
885
+ }
886
+ };
887
+ /**
888
+ * Get a JSON representation of the rules text for a magical rune.
889
+ */
890
+ export const getMagicalRuneEntityDescription = createEntityDescriptionCreator(({ getInstanceById, getChildInstancesForInstanceId, idMap }, locale, { id, content: entry }) => {
891
+ const { translate, translateMap } = locale;
892
+ const translation = translateMap(entry.translations);
893
+ if (translation === undefined) {
894
+ return undefined;
895
+ }
896
+ const env = {
897
+ translate,
898
+ translateMap,
899
+ getInstanceById,
900
+ localeCompare: locale.compare,
901
+ localeJoin: locale.join,
902
+ energyUnit: "ArcaneEnergy",
903
+ responsiveTextSize: ResponsiveTextSize.Full,
904
+ };
905
+ const options = Lazy.of(() => getChildInstancesForInstanceId("MagicalRuneOption", id).map(item => item.content));
906
+ const cost = renderMagicalRuneCost(options, entry.parameters.cost).run(env);
907
+ const craftingTime = renderMagicalRuneCraftingTime(entry.parameters.crafting_time).run(env);
908
+ const duration = renderMagicalRuneDuration(entry.parameters.duration).run(env);
909
+ return {
910
+ title: (translation.name_in_library ?? translation.name) + parensIf(translation.native_name),
911
+ className: "magical-rune",
912
+ body: [
232
913
  {
233
- label: translate("Duration"),
234
- value: duration !== translation.duration.full
235
- ? `***${duration}*** (${translation.duration.full})`
236
- : duration,
914
+ type: "definitionList",
915
+ items: [
916
+ renderSkillCheckWithPenalty(entry.check, entry.check_penalty, idMap).run(env),
917
+ renderEffect(translation.effect).run(env),
918
+ combineGeneratedTextWithStaticTranslation(translate("AE Cost"), cost, translation.cost),
919
+ combineGeneratedTextWithStaticTranslation(translate("Crafting Time (slow / fast)"), craftingTime, translation.crafting_time === undefined
920
+ ? undefined
921
+ : renderSplitMagicalRuneParameterTranslation(translation.crafting_time).run(env)),
922
+ combineGeneratedTextWithStaticTranslation(translate("Duration (slow / fast)"), duration, translation.duration === undefined
923
+ ? undefined
924
+ : renderSplitMagicalRuneParameterTranslation(translation.duration).run(env)),
925
+ renderProperty(entry.property).run(env),
926
+ renderMagicalRuneImprovementCost(options, entry.improvement_cost).run(env),
927
+ ],
237
928
  },
238
- getTargetCategoryTranslation(getTargetCategoryById, locale, entry.target),
239
- getTextForProperty({ translate, translateMap, getPropertyById }, entry.property),
240
- getTextForTraditions({ translate, translateMap, localeCompare, getMagicalTraditionById }, entry.traditions),
241
- createImprovementCost(translate, entry.improvement_cost),
242
929
  ],
930
+ errata: translation.errata,
243
931
  references: entry.src,
244
932
  };
245
933
  });