@optolith/entity-descriptions 0.2.1 → 0.3.2

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