@optolith/entity-descriptions 0.4.8 → 0.4.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/lib/entities/activatable.js +17 -14
  3. package/lib/entities/curriculum.js +14 -21
  4. package/lib/entities/derivedCharacteristic.d.ts +0 -2
  5. package/lib/entities/derivedCharacteristic.js +10 -17
  6. package/lib/entities/disease.js +5 -1
  7. package/lib/entities/elixir.js +12 -10
  8. package/lib/entities/equipment.d.ts +6 -5
  9. package/lib/entities/equipment.js +19 -6
  10. package/lib/entities/equipmentPackage.js +2 -1
  11. package/lib/entities/liturgicalChant.js +7 -3
  12. package/lib/entities/partial/commonnessRatedAdvantagesAndDisadvantages.js +8 -12
  13. package/lib/entities/partial/herbary.js +5 -8
  14. package/lib/entities/partial/markdown.d.ts +55 -0
  15. package/lib/entities/partial/markdown.js +59 -0
  16. package/lib/entities/partial/prerequisites/index.d.ts +3 -3
  17. package/lib/entities/partial/prerequisites/index.js +3 -3
  18. package/lib/entities/partial/prerequisites/part.js +8 -3
  19. package/lib/entities/partial/prerequisites/prerequisiteGroups.d.ts +1 -1
  20. package/lib/entities/partial/prerequisites/prerequisiteGroups.js +10 -7
  21. package/lib/entities/partial/prerequisites/single/culture.js +2 -1
  22. package/lib/entities/partial/prerequisites/single/personalityTrait.js +2 -1
  23. package/lib/entities/partial/prerequisites/single/profession.js +4 -1
  24. package/lib/entities/partial/prerequisites/single/race.js +2 -1
  25. package/lib/entities/partial/prerequisites/single/rated.js +2 -27
  26. package/lib/entities/partial/prerequisites/single/ratedMinimumNumber.js +2 -1
  27. package/lib/entities/partial/prerequisites/single/ratedSum.js +2 -1
  28. package/lib/entities/partial/prerequisites/single/state.js +2 -1
  29. package/lib/entities/partial/rated/activatable/checkResultBased.js +3 -1
  30. package/lib/entities/partial/reader.d.ts +33 -0
  31. package/lib/entities/partial/reader.js +13 -0
  32. package/lib/entities/personalityTrait.js +9 -2
  33. package/lib/entities/poison.js +20 -9
  34. package/lib/entities/profession.d.ts +2 -2
  35. package/lib/entities/profession.js +133 -38
  36. package/lib/entities/race.d.ts +1 -1
  37. package/lib/entities/race.js +23 -23
  38. package/lib/entities/spell.d.ts +4 -2
  39. package/lib/entities/spell.js +17 -29
  40. package/lib/index.d.ts +64 -64
  41. package/lib/tsconfig.tsbuildinfo +1 -1
  42. package/package.json +5 -5
@@ -7,12 +7,14 @@ import { omitKeys } from "@elyukai/utils/object";
7
7
  import { compareNumber } from "@elyukai/utils/ordering";
8
8
  import { Reader } from "@elyukai/utils/reader";
9
9
  import { assertExhaustive } from "@elyukai/utils/typeSafety";
10
+ import { Case, fromUniformCase } from "tsondb/schema/gen";
10
11
  import { createEntityDescriptionCreator } from "../creator.js";
11
12
  import { combineNameComponents, getNameComponents, renderActivatableNameComponents, renderCombinedActivatableNameComponents, } from "./partial/activatableNameChunks.js";
12
13
  import { renderCommonnessRatedAdvantagesOrDisadvantages, renderValueWithPossibleTranslation, } from "./partial/commonnessRatedAdvantagesAndDisadvantages.js";
14
+ import { attributedCustomName, attributedInstance, attributedName } from "./partial/markdown.js";
13
15
  import { printProfessionPrerequisites } from "./partial/prerequisites/index.js";
14
16
  import { parensIf } from "./partial/rated/activatable/parensIf.js";
15
- import { formatR, getChildInstancesForInstanceIdR, getInstanceByIdR, localeCompareR, localeJoinR, localeSortR, nameR, strictNameR, translateMapFnR, translateR, translationR, } from "./partial/reader.js";
17
+ import { attributedCustomNameR, attributedNameR, customNameR, formatR, getChildInstancesForInstanceIdR, getInstanceByIdR, localeCompareR, localeJoinR, localeSortR, strictNameR, translateMapFnR, translateR, translationR, } from "./partial/reader.js";
16
18
  import { MISSING_VALUE, UNHANDLED_VALUE } from "./partial/unknown.js";
17
19
  const prepareProfessionPackages = (professionId) => Reader.asks(({ getInstanceById, getChildInstancesForInstanceId }) => getChildInstancesForInstanceId("ProfessionPackage", professionId)
18
20
  .map((professionPackage) => ({
@@ -79,12 +81,12 @@ const renderSkillSpecializationOption = (option) => Reader.asks(({ format, trans
79
81
  switch (option.kind) {
80
82
  case "Specific":
81
83
  return translate("Skill Specialization {$possibleSkills}", {
82
- possibleSkills: localeJoin(option.Specific.options.map(id => translateMap(getInstanceById("Skill", id)?.translations)?.name ?? MISSING_VALUE), "disjunction"),
84
+ possibleSkills: localeJoin(option.Specific.options.map(id => attributedName(translateMap, getInstanceById, "profession", "Skill", id) ??
85
+ MISSING_VALUE), "disjunction"),
83
86
  });
84
87
  case "Group":
85
88
  return translate("Skill Specialization for a {$skillOfGroup}", {
86
- skillOfGroup: format(translateMap(getInstanceById("SkillGroup", option.Group)?.translations)?.longName ??
87
- MISSING_VALUE, { hiddenCount: 1 }),
89
+ skillOfGroup: format(attributedCustomName(translateMap, getInstanceById, "profession", t => t.longName, "SkillGroup", option.Group) ?? MISSING_VALUE, { hiddenCount: 1 }),
88
90
  });
89
91
  default:
90
92
  return assertExhaustive(option);
@@ -101,11 +103,11 @@ const renderCombatTechniquesOption = (option) => isNotEmpty(option.fixed)
101
103
  })), firstTextR);
102
104
  const completeTextR = fixedTextR.then(fixedText => option.rest_rating_modifier === undefined
103
105
  ? Reader.of(fixedText)
104
- : translateR("{$previous}, all others {$rating}", {
106
+ : translateR("{$previous}, the others {$rating}", {
105
107
  previous: fixedText,
106
108
  rating: option.rest_rating_modifier + 6,
107
109
  }));
108
- const listR = Reader.traverse(option.options, strictNameR)
110
+ const listR = Reader.traverse(option.options, id => attributedNameR("profession", id).map(name => name ?? MISSING_VALUE))
109
111
  .thenW(list => localeSortR(list))
110
112
  .map(list => list.join(", "));
111
113
  return completeTextR.thenW(completeText => listR.map(list => `${completeText}: ${list}`));
@@ -121,11 +123,37 @@ const getTotalingAPValues = (professionPackages, selector) => {
121
123
  options[0].ap_value
122
124
  : options.map(option => option?.ap_value ?? 0).join("/");
123
125
  };
124
- const renderSkillsOption = (professionPackages, skillGroup) => mapNullable(getTotalingAPValues(professionPackages, pkg => pkg.content.options?.skills?.group === skillGroup.id ? pkg.content.options.skills : undefined), apValue => translateR("{$apValue} AP to improve other {$skillsOfGroup}", {
125
- apValue,
126
- skillsOfGroup: skillGroup.nameOfSkillsOfGroup,
127
- })) ?? Reader.of(undefined);
128
- const renderCantripList = (cantripIds) => Reader.traverse(cantripIds, id => strictNameR("Cantrip", id))
126
+ const renderSkillsOption = (professionPackages, skillGroup) => allSame(professionPackages.filter(pkg => pkg.content.options?.skills?.group === skillGroup.id), on(pkg => pkg.content.options?.skills?.specific, deepEqual))
127
+ ? (mapNullable(getTotalingAPValues(professionPackages, pkg => pkg.content.options?.skills?.group === skillGroup.id
128
+ ? pkg.content.options.skills
129
+ : undefined), apValue => professionPackages[0].content.options?.skills?.specific === undefined
130
+ ? translateR("{$apValue} AP to improve other {$skillsOfGroup}", {
131
+ apValue,
132
+ skillsOfGroup: skillGroup.nameOfSkillsOfGroup,
133
+ })
134
+ : Reader.traverse(professionPackages[0].content.options.skills.specific, id => attributedNameR("profession", "Skill", id).map(name => name ?? MISSING_VALUE))
135
+ .thenW(localeSortR)
136
+ .thenW(list => translateR("{$apValue} AP to distribute among the following skills: {$list}", {
137
+ apValue,
138
+ list: list.join(", "),
139
+ }))) ?? Reader.of(undefined))
140
+ : Reader.traverse(professionPackages, pkg => {
141
+ const skillsOption = pkg.content.options?.skills;
142
+ return skillsOption === undefined
143
+ ? Reader.of("—")
144
+ : skillsOption.specific === undefined
145
+ ? translateR("{$apValue} AP to improve other {$skillsOfGroup}", {
146
+ apValue: skillsOption.ap_value,
147
+ skillsOfGroup: skillGroup.nameOfSkillsOfGroup,
148
+ })
149
+ : Reader.traverse(skillsOption.specific, id => attributedNameR("profession", "Skill", id).map(name => name ?? MISSING_VALUE))
150
+ .thenW(localeSortR)
151
+ .thenW(list => translateR("{$apValue} AP to distribute among the following skills: {$list}", {
152
+ apValue: skillsOption.ap_value,
153
+ list: list.join(", "),
154
+ }));
155
+ }).map(list => list.join(" / "));
156
+ const renderCantripList = (cantripIds) => Reader.traverse(cantripIds, id => attributedNameR("profession", "Cantrip", id).map(name => name ?? MISSING_VALUE))
129
157
  .thenW(localeSortR)
130
158
  .map(list => list.join(", "));
131
159
  const renderVariantCantripsOption = (baseOptions, variantOptions) => renderVariantOptionPaths(baseOptions, variantOptions, {
@@ -184,16 +212,15 @@ const renderVariantSkillSpecializationOption = (baseOptions, variantOptions) =>
184
212
  remove: (base) => {
185
213
  switch (base.kind) {
186
214
  case "Specific":
187
- return Reader.traverse(base.Specific.options, id => strictNameR("Skill", id))
215
+ return Reader.traverse(base.Specific.options, id => attributedNameR("profession", "Skill", id).map(name => name ?? MISSING_VALUE))
188
216
  .thenW(localeSortR)
189
217
  .thenW(list => localeJoinR(list, "disjunction"))
190
218
  .thenW(skillsText => translateR("no Skill Specialization {$possibleSkills}", {
191
219
  possibleSkills: skillsText,
192
220
  }));
193
221
  case "Group":
194
- return getInstanceByIdR("SkillGroup", base.Group)
195
- .thenW(translationR)
196
- .map(t => t?.longName ?? MISSING_VALUE)
222
+ return attributedCustomNameR("profession", t => t.longName, "SkillGroup", base.Group)
223
+ .map(name => name ?? MISSING_VALUE)
197
224
  .thenW(longName => formatR(longName, { hiddenCount: 1 }))
198
225
  .thenW(skillOfGroup => translateR("no Skill Specialization for a {$skillOfGroup}", {
199
226
  skillOfGroup,
@@ -205,8 +232,8 @@ const renderVariantSkillSpecializationOption = (baseOptions, variantOptions) =>
205
232
  add: override => renderSkillSpecializationOption(override),
206
233
  update: (base, override) => {
207
234
  if (base.kind === "Specific" && override.kind === "Specific") {
208
- const overrideSkills = Reader.traverse(override.Specific.options, id => strictNameR("Skill", id)).thenW(list => localeJoinR(list, "disjunction"));
209
- const baseSkills = Reader.traverse(base.Specific.options, id => strictNameR("Skill", id)).thenW(list => localeJoinR(list, "disjunction"));
235
+ const overrideSkills = Reader.traverse(override.Specific.options, id => attributedNameR("profession", "Skill", id).map(name => name ?? MISSING_VALUE)).thenW(list => localeJoinR(list, "disjunction"));
236
+ const baseSkills = Reader.traverse(base.Specific.options, id => attributedNameR("profession", "Skill", id).map(name => name ?? MISSING_VALUE)).thenW(list => localeJoinR(list, "disjunction"));
210
237
  return overrideSkills
211
238
  .thenW(overrideSkillsText => baseSkills.thenW(baseSkillsText => insteadOfR(overrideSkillsText, baseSkillsText)))
212
239
  .then(skillsText => translateR("Skill Specialization {$possibleSkills}", {
@@ -234,9 +261,7 @@ const renderVariantCursesOption = (baseOptions, variantOptions) => renderVariant
234
261
  })),
235
262
  });
236
263
  const renderCursesOption = (option) => renderVariantCursesOption(undefined, { kind: "Override", Override: option });
237
- const nameOfSkillsOfGroupR = (skillGroupId) => getInstanceByIdR("SkillGroup", skillGroupId)
238
- .thenW(translationR)
239
- .map(t => t?.longName ?? MISSING_VALUE);
264
+ const nameOfSkillsOfGroupR = (skillGroupId) => customNameR(t => t.longName, "SkillGroup", skillGroupId).map(name => name ?? MISSING_VALUE);
240
265
  const renderVariantSkillsOption = (baseOptions, variantOptions) => renderVariantOptionPaths(baseOptions, variantOptions, {
241
266
  remove: base => nameOfSkillsOfGroupR(base.group).thenW(nameOfSkillsOfGroup => translateR("no AP to improve other {$skillsOfGroup}", {
242
267
  skillsOfGroup: nameOfSkillsOfGroup,
@@ -257,33 +282,103 @@ const renderVariantLiturgiesOption = (baseOptions, variantOptions) => renderVari
257
282
  apValue,
258
283
  })),
259
284
  });
260
- const getSpecialAbilityNameComponents = (getInstanceById, getResolvedSelectOptionById, translate, option) => {
285
+ const getEnhancementBaseName = (translate, enhancement, parentName) => {
286
+ switch (enhancement.parent.kind) {
287
+ case "Spell":
288
+ return translate("Spell Enhancement {$spell}", { spell: parentName });
289
+ case "Ritual":
290
+ return translate("Ritual Enhancement {$ritual}", { ritual: parentName });
291
+ case "LiturgicalChant":
292
+ case "Ceremony":
293
+ return translate("Liturgical Enhancement {$liturgicalChantOrCeremony}", {
294
+ liturgicalChantOrCeremony: parentName,
295
+ });
296
+ default:
297
+ return assertExhaustive(enhancement.parent);
298
+ }
299
+ };
300
+ const isEnhancementNameComponents = (components) => components.id.kind === "Enhancement";
301
+ const isActivatableNameComponents = (components) => !isEnhancementNameComponents(components);
302
+ const getEnhancementNameComponents = (getInstanceById, translate, translateMap, id) => {
303
+ const instance = getInstanceById("Enhancement", id);
304
+ if (instance === undefined) {
305
+ return undefined;
306
+ }
307
+ return {
308
+ id: Case("Enhancement", id),
309
+ parent: instance.parent,
310
+ base: getEnhancementBaseName(translate, instance, attributedName(translateMap, getInstanceById, "profession", instance.parent) ?? MISSING_VALUE),
311
+ options: translateMap(instance.translations)?.name ?? MISSING_VALUE,
312
+ };
313
+ };
314
+ const renderEnhancementNameComponents = (chunk) => [chunk.base, chunk.options].join(" ");
315
+ const isCombinedEnhancementNameComponents = (components) => Array.isArray(components.id);
316
+ const combineEnhancementNameComponents = (chunks) => {
317
+ if (allSame(chunks, (a, b) => deepEqual(a.parent, b.parent)) && isNotEmpty(chunks)) {
318
+ const [first, ...rest] = chunks;
319
+ return {
320
+ id: [first.id, ...rest.map(chunk => chunk.id)],
321
+ parent: first.parent,
322
+ base: first.base,
323
+ options: [
324
+ [first.id, first.options],
325
+ ...rest.map((chunk) => [
326
+ chunk.id,
327
+ chunk.options,
328
+ ]),
329
+ ],
330
+ };
331
+ }
332
+ else {
333
+ return undefined;
334
+ }
335
+ };
336
+ const renderCombinedEnhancementNameComponents = (chunk) => [
337
+ chunk.base,
338
+ chunk.options
339
+ .map(([optionId, optionName]) => attributedInstance(optionName, optionId.kind, optionId.Enhancement, {
340
+ context: `"profession"`,
341
+ }))
342
+ .join(" + "),
343
+ ].join(" ");
344
+ const getSpecialAbilityNameComponents = (getInstanceById, getResolvedSelectOptionById, translate, translateMap, option) => {
345
+ if (option.id.kind === "Enhancement") {
346
+ return getEnhancementNameComponents(getInstanceById, translate, translateMap, option.id.Enhancement);
347
+ }
261
348
  const instance = getInstanceById(option.id);
262
349
  if (instance === undefined) {
263
350
  return undefined;
264
351
  }
265
352
  return getNameComponents(translate, option.id, option.options, option.level, "nameBuilderRules" in instance ? instance.nameBuilderRules : undefined, instance.translations, t => t.name, getResolvedSelectOptionById, true);
266
353
  };
354
+ const wrapActivatableInAttributedString = (text, id) => attributedInstance(text, id.kind, fromUniformCase(id), { context: '"prerequisite"' });
355
+ const renderSingleActivatableNameChunk = (translateMap, chunk) => chunk === undefined
356
+ ? MISSING_VALUE
357
+ : wrapActivatableInAttributedString(isEnhancementNameComponents(chunk)
358
+ ? renderEnhancementNameComponents(chunk)
359
+ : renderActivatableNameComponents(translateMap, chunk, false), chunk.id);
267
360
  const renderSpecialAbilityName = (specialAbility) => Reader.asks(({ translate, translateMap, localeJoin, getInstanceById, getResolvedSelectOptionById, }) => {
268
361
  switch (specialAbility.kind) {
269
362
  case "Constant": {
270
- const chunk = getSpecialAbilityNameComponents(getInstanceById, getResolvedSelectOptionById, translate, specialAbility.Constant);
271
- return chunk === undefined
272
- ? MISSING_VALUE
273
- : renderActivatableNameComponents(translateMap, chunk, false);
363
+ const chunk = getSpecialAbilityNameComponents(getInstanceById, getResolvedSelectOptionById, translate, translateMap, specialAbility.Constant);
364
+ return renderSingleActivatableNameChunk(translateMap, chunk);
274
365
  }
275
366
  case "Selection": {
276
- const chunks = specialAbility.Selection.options.map(option => getSpecialAbilityNameComponents(getInstanceById, getResolvedSelectOptionById, translate, option));
367
+ const chunks = specialAbility.Selection.options.map(option => getSpecialAbilityNameComponents(getInstanceById, getResolvedSelectOptionById, translate, translateMap, option));
277
368
  const possiblyCombined = isNotEmpty(specialAbility.Selection.options) && chunks.every(isNotNullish)
278
- ? combineNameComponents(chunks)
369
+ ? chunks.every(isEnhancementNameComponents)
370
+ ? combineEnhancementNameComponents(chunks)
371
+ : chunks.every(isActivatableNameComponents)
372
+ ? combineNameComponents(chunks)
373
+ : undefined
279
374
  : undefined;
280
375
  if (possiblyCombined === undefined) {
281
- return localeJoin(chunks.map(chunk => chunk === undefined
282
- ? MISSING_VALUE
283
- : renderActivatableNameComponents(translateMap, chunk, false)), "disjunction");
376
+ return localeJoin(chunks.map(chunk => renderSingleActivatableNameChunk(translateMap, chunk)), "disjunction");
284
377
  }
285
378
  else {
286
- return renderCombinedActivatableNameComponents(translateMap, possiblyCombined, false, list => localeJoin(list, "disjunction"));
379
+ return isCombinedEnhancementNameComponents(possiblyCombined)
380
+ ? renderCombinedEnhancementNameComponents(possiblyCombined)
381
+ : wrapActivatableInAttributedString(renderCombinedActivatableNameComponents(translateMap, possiblyCombined, false, list => localeJoin(list, "disjunction")), possiblyCombined.id);
287
382
  }
288
383
  }
289
384
  default:
@@ -310,7 +405,7 @@ const renderSpecialAbilities = (specialAbilities) => Reader.sequence([
310
405
  return result === undefined ? translateR("none") : Reader.of(result);
311
406
  });
312
407
  const renderCombatTechniques = (professionPackages) => Reader.sequence([
313
- renderNumericListAcrossPackages(professionPackages, pkg => Reader.of(pkg.content.combat_techniques?.map(ct => [ct.id, ct.rating_modifier]) ?? []), deepEqual, (ctId) => strictNameR(ctId), 6),
408
+ renderNumericListAcrossPackages(professionPackages, pkg => Reader.of(pkg.content.combat_techniques?.map(ct => [ct.id, ct.rating_modifier]) ?? []), deepEqual, (ctId) => attributedNameR("profession", ctId).map(name => name ?? MISSING_VALUE), 6),
314
409
  professionPackages.some(pkg => pkg.content.options?.combat_techniques !== undefined)
315
410
  ? renderSinglePackageValue(professionPackages, pkg => Reader.of(pkg.content.options?.combat_techniques), deepEqual, options => Reader.traverse(options, option => option === undefined ? Reader.of("—") : renderCombatTechniquesOption(option)).map(renderedOptions => renderedOptions.join(" / ")))
316
411
  : Reader.of(undefined),
@@ -318,7 +413,7 @@ const renderCombatTechniques = (professionPackages) => Reader.sequence([
318
413
  const renderSkills = (professionPackages) => Reader.asks(({ getAllInstances }) => getAllInstances("SkillGroup")).thenW(skillGroups => Reader.traverse(skillGroups.toSorted(on(group => group.content.position, compareNumber)), ({ id: groupId, content: group }) => translationR(group).thenW(groupTranslation => Reader.sequence([
319
414
  renderNumericListAcrossPackages(professionPackages, pkg => Reader.asks(({ getInstanceById }) => pkg.content.skills
320
415
  ?.filter(skill => getInstanceById("Skill", skill.id)?.group === groupId)
321
- .map(skill => [skill.id, skill.rating_modifier]) ?? []), equal, skillId => strictNameR("Skill", skillId), 0),
416
+ .map(skill => [skill.id, skill.rating_modifier]) ?? []), equal, skillId => attributedNameR("profession", "Skill", skillId).map(name => name ?? MISSING_VALUE), 0),
322
417
  renderSkillsOption(professionPackages, {
323
418
  id: groupId,
324
419
  nameOfSkillsOfGroup: groupTranslation?.longName ?? MISSING_VALUE,
@@ -330,11 +425,11 @@ const renderSkills = (professionPackages) => Reader.asks(({ getAllInstances }) =
330
425
  const renderSpellworkName = (spellworkIds) => Reader.traverse(spellworkIds, spellworkId => {
331
426
  switch (spellworkId.kind) {
332
427
  case "Spellwork":
333
- return strictNameR(spellworkId.Spellwork.id).thenW(baseName => (spellworkId.Spellwork.tradition === undefined
428
+ return attributedNameR("profession", spellworkId.Spellwork.id).thenW(baseName => (spellworkId.Spellwork.tradition === undefined
334
429
  ? Reader.of(undefined)
335
- : nameR("MagicalTradition", spellworkId.Spellwork.tradition)).map(traditionName => baseName + parensIf(traditionName)));
430
+ : attributedNameR("profession", "MagicalTradition", spellworkId.Spellwork.tradition)).map(traditionName => (baseName ?? MISSING_VALUE) + parensIf(traditionName)));
336
431
  case "MagicalAction":
337
- return strictNameR(spellworkId.MagicalAction.id);
432
+ return attributedNameR("profession", spellworkId.MagicalAction.id).map(name => name ?? MISSING_VALUE);
338
433
  default:
339
434
  return spellworkId;
340
435
  }
@@ -358,7 +453,7 @@ const retrieveBlessedTraditionIdentifierFromPrerequisites = (prerequisites) => p
358
453
  return assertExhaustive(part);
359
454
  }
360
455
  }) ?? [];
361
- const renderBlessingList = (blessings) => Reader.traverse(blessings, id => strictNameR("Blessing", id)).thenW(localeSortR);
456
+ const renderBlessingList = (blessings) => Reader.traverse(blessings, id => attributedNameR("profession", "Blessing", id).map(name => name ?? MISSING_VALUE)).thenW(localeSortR);
362
457
  const renderRestrictedBlessings = (restrictedBlessings) => {
363
458
  switch (restrictedBlessings.kind) {
364
459
  case "Three":
@@ -406,7 +501,7 @@ const renderBlessingsForPackages = (professionPackages) => renderBlessings(profe
406
501
  const renderBlessingsForVariant = (professionVariant) => renderBlessings([
407
502
  retrieveBlessedTraditionIdentifierFromPrerequisites(professionVariant.prerequisites),
408
503
  ]).map(blessingsText => (blessingsText !== undefined ? [blessingsText] : []));
409
- const renderLiturgicalChantName = (liturgyIds) => Reader.traverse(liturgyIds, strictNameR).thenW(list => localeJoinR(list, "disjunction"));
504
+ const renderLiturgicalChantName = (liturgyIds) => Reader.traverse(liturgyIds, id => attributedNameR("profession", id).map(name => name ?? MISSING_VALUE)).thenW(list => localeJoinR(list, "disjunction"));
410
505
  const renderLiturgicalChants = (professionPackages) => Reader.sequence([
411
506
  renderBlessingsForPackages(professionPackages),
412
507
  renderNumericListAcrossPackages(professionPackages, (pkg) => Reader.of(pkg.content.liturgical_chants?.map(ct => [ct.id, ct.rating_modifier]) ?? []), deepEqual, renderLiturgicalChantName, 0),
@@ -3,7 +3,7 @@ import type { CountInstances, GetAllChildInstancesForParent, GetInstanceById } f
3
3
  * Get a JSON representation of the rules text for a race.
4
4
  */
5
5
  export declare const getRaceEntityDescription: import("../creator.js").EntityDescriptionCreator<"Race", {
6
- getInstanceById: GetInstanceById<"Publication" | "Attribute" | "Advantage" | "Disadvantage" | "Culture">;
6
+ getInstanceById: GetInstanceById<"Publication" | "Attribute" | "Advantage" | "Disadvantage" | "Culture" | "DerivedCharacteristic">;
7
7
  countInstances: CountInstances<"Attribute">;
8
8
  getChildInstancesForInstanceId: GetAllChildInstancesForParent<"RaceVariant">;
9
9
  }, import("../index.js").EntityDescription>;
@@ -3,24 +3,30 @@ import { isNotEmpty } from "@elyukai/utils/array/nonEmpty";
3
3
  import { deepEqual } from "@elyukai/utils/equality";
4
4
  import { on } from "@elyukai/utils/function";
5
5
  import { isNotNullish } from "@elyukai/utils/nullable";
6
+ import { compareNumber } from "@elyukai/utils/ordering";
6
7
  import { Reader } from "@elyukai/utils/reader";
7
8
  import { sign } from "@elyukai/utils/string/number";
8
9
  import { createEntityDescriptionCreator } from "../creator.js";
9
10
  import { renderCommonnessRatedAdvantagesAndDisadvantages, renderCommonnessRatedAdvantagesOrDisadvantages, renderValueWithPossibleTranslation, } from "./partial/commonnessRatedAdvantagesAndDisadvantages.js";
11
+ import { attributedCustomName, attributedName } from "./partial/markdown.js";
12
+ import { getInstanceByIdR, translateMapR, } from "./partial/reader.js";
10
13
  import { MISSING_VALUE } from "./partial/unknown.js";
11
- const renderBaseValueItem = (label, value) => Reader.asks(({ translate }) => ({
12
- label: translate(label),
13
- value: value < 0 ? sign(value) : value.toFixed(),
14
- }));
15
- const renderAttributeAdjustmentsItem = (totalAttributesCount, adjustments) => Reader.asks(({ translate, translateMap, localeJoin, getInstanceById, }) => {
16
- const getAttributeAbbreviation = (id) => translateMap(getInstanceById("Attribute", id)?.translations)?.abbreviation ?? MISSING_VALUE;
17
- return {
18
- label: translate("Attribute Adjustments"),
19
- value: [
20
- ...(adjustments.fixed?.map(adj => `${getAttributeAbbreviation(adj.id)} ${sign(adj.value)}`) ?? []),
21
- ...(adjustments.selectable?.map(adj => `${adj.list.length === totalAttributesCount ? translate("one attribute of your choice") : localeJoin(adj.list.map(getAttributeAbbreviation), "disjunction")} ${sign(adj.value)}`) ?? []),
22
- ].join("; "),
23
- };
14
+ const renderBaseValues = (values) => Reader.traverse(Object.entries(values), ([id, { value }]) => getInstanceByIdR("DerivedCharacteristic", id).thenW(derivedCharacteristic => translateMapR(derivedCharacteristic?.translations).thenW(translation => Reader.asks(({ translate }) => [
15
+ derivedCharacteristic?.position ?? 0,
16
+ {
17
+ label: translate("{$derivedCharacteristic} Base Value", {
18
+ derivedCharacteristic: translation?.name ?? MISSING_VALUE,
19
+ }),
20
+ value: value < 0 ? sign(value) : value.toFixed(),
21
+ },
22
+ ])))).map(items => items.sort(on(item => item[0], compareNumber)).map(item => item[1]));
23
+ const renderAttributeAdjustmentsItem = (totalAttributesCount, adjustments) => Reader.asks(({ translate, translateMap, localeJoin, getInstanceById }) => {
24
+ const getAttributeAbbreviation = (id) => attributedCustomName(translateMap, getInstanceById, "race", t => t.abbreviation, "Attribute", id) ?? MISSING_VALUE;
25
+ return [
26
+ ...(adjustments.fixed?.map(adj => `${getAttributeAbbreviation(adj.id)} ${sign(adj.value)}`) ??
27
+ []),
28
+ ...(adjustments.selectable?.map(adj => `${adj.list.length === totalAttributesCount ? translate("one attribute of your choice") : localeJoin(adj.list.map(getAttributeAbbreviation), "disjunction")} ${sign(adj.value)}`) ?? []),
29
+ ].join("; ");
24
30
  });
25
31
  const renderVariantValues = (label, variants, selector, renderValue, translationSelector) => Reader.asks(({ translate, translateMap, localeCompare, localeJoin, }) => {
26
32
  const values = variants.map(variant => {
@@ -69,16 +75,13 @@ const renderVariantValues = (label, variants, selector, renderValue, translation
69
75
  const renderAutomaticAdvantagesOrDisadvantages = (entity, items) => Reader.asks(({ translate, translateMap, getInstanceById, localeCompare }) => items === undefined || !isNotEmpty(items)
70
76
  ? translate("none")
71
77
  : items
72
- .map(item => {
73
- const instanceTranslation = translateMap(getInstanceById(entity, item.id)?.translations);
74
- return (instanceTranslation?.name_in_library ?? instanceTranslation?.name ?? MISSING_VALUE);
75
- })
78
+ .map(item => attributedCustomName(translateMap, getInstanceById, "race", t => t.name_in_library ?? t.name, entity, item.id) ?? MISSING_VALUE)
76
79
  .toSorted(localeCompare)
77
80
  .join(", "));
78
81
  const renderCommonCultures = (items) => Reader.asks(({ translate, translateMap, getInstanceById, localeCompare }) => items === undefined || !isNotEmpty(items)
79
82
  ? translate("none")
80
83
  : items
81
- .map(itemId => translateMap(getInstanceById("Culture", itemId)?.translations)?.name)
84
+ .map(itemId => attributedName(translateMap, getInstanceById, "race", "Culture", itemId))
82
85
  .filter(isNotNullish)
83
86
  .toSorted(localeCompare)
84
87
  .join(", "));
@@ -112,11 +115,8 @@ export const getRaceEntityDescription = createEntityDescriptionCreator(({ getIns
112
115
  value: entry.ap_value,
113
116
  }),
114
117
  },
115
- renderBaseValueItem("Life Point Base Value", entry.base_values.life_points).run(env),
116
- renderBaseValueItem("Spirit Base Value", entry.base_values.spirit).run(env),
117
- renderBaseValueItem("Toughness Base Value", entry.base_values.toughness).run(env),
118
- renderBaseValueItem("Movement Base Value", entry.base_values.movement).run(env),
119
- renderAttributeAdjustmentsItem(totalAttributesCount, entry.attribute_adjustments).run(env),
118
+ ...renderBaseValues(entry.base_values).run(env),
119
+ renderVariantValues("Attribute Adjustments", raceVariants, v => v.attribute_adjustments, attrs => renderAttributeAdjustmentsItem(totalAttributesCount, attrs).run(env)).run(env),
120
120
  renderVariantValues("Common Cultures", raceVariants, v => v.common_cultures, cultures => renderCommonCultures(cultures).run(env)).run(env),
121
121
  entry.automatic_advantages === undefined
122
122
  ? undefined
@@ -1,6 +1,7 @@
1
- import { type RatedIdentifier } from "optolith-database-schema/gen";
1
+ import { type ActivatableIdentifier, type RatedIdentifier } from "optolith-database-schema/gen";
2
2
  import type { GetAllChildInstancesForParent, GetInstanceById } from "../helpers/getTypes.js";
3
3
  import { type IdMap } from "../index.js";
4
+ import type { GetResolvedSelectOptionById } from "./partial/prerequisites/single/activatable.js";
4
5
  /**
5
6
  * Get a JSON representation of the rules text for a cantrip.
6
7
  */
@@ -72,7 +73,8 @@ export declare const getAnimistPowerEntityDescription: import("../creator.js").E
72
73
  * Get a JSON representation of the rules text for a Goede ritual.
73
74
  */
74
75
  export declare const getGeodeRitualEntityDescription: import("../creator.js").EntityDescriptionCreator<"GeodeRitual", {
75
- getInstanceById: GetInstanceById<"Publication" | "Attribute" | "SkillModificationLevel" | "TargetCategory" | "Property" | "MagicalTradition" | "DerivedCharacteristic" | "Influence">;
76
+ getInstanceById: GetInstanceById<"Publication" | "Attribute" | "SkillModificationLevel" | "TargetCategory" | "Property" | "MagicalTradition" | "DerivedCharacteristic" | "Influence" | ActivatableIdentifier["kind"] | "Aspect">;
77
+ getResolvedSelectOptionById: GetResolvedSelectOptionById;
76
78
  }, import("../index.js").EntityDescription>;
77
79
  /**
78
80
  * Get a JSON representation of the rules text for a jester trick.
@@ -11,6 +11,7 @@ import { Case } from "tsondb/schema/gen";
11
11
  import { createEntityDescriptionCreator } from "../creator.js";
12
12
  import { renderAnimalTypesSection } from "./partial/animalTypes.js";
13
13
  import { renderEnhancements } from "./partial/enhancements.js";
14
+ import { attributedCustomName, attributedName } from "./partial/markdown.js";
14
15
  import { printAnimistPowerPrerequisites, printGeodeRitualPrerequisites, } from "./partial/prerequisites/index.js";
15
16
  import { renderCastingTime, renderFastSkillNonModifiableCastingTime, renderSlowSkillNonModifiableCastingTime, } from "./partial/rated/activatable/castingTime.js";
16
17
  import { renderMagicalActionCost, renderModifiableOneTimeCost, renderNonModifiableOneTimeCost, } from "./partial/rated/activatable/cost.js";
@@ -28,29 +29,18 @@ import { formatEnergyR, localeJoinR, responsiveR, responsiveTextR, responsiveTra
28
29
  import { appendNoteIfNeeded, getResponsiveText, ResponsiveTextSize, } from "./partial/responsiveText.js";
29
30
  import { formatTimeSpanR } from "./partial/units/timeSpan.js";
30
31
  import { MISSING_VALUE } from "./partial/unknown.js";
31
- const renderProperty = (id) => Reader.asks(({ translate, translateMap, getInstanceById }) => {
32
- const text = (() => {
33
- const staticEntry = getInstanceById("Property", id);
34
- const staticEntryTranslation = translateMap(staticEntry?.translations);
35
- if (staticEntryTranslation === undefined) {
36
- return "";
37
- }
38
- return staticEntryTranslation.name;
39
- })();
40
- return {
41
- label: translate("Property"),
42
- value: text,
43
- };
44
- });
32
+ const renderProperty = (id) => Reader.asks(({ translate, translateMap, getInstanceById }) => ({
33
+ label: translate("Property"),
34
+ value: attributedName(translateMap, getInstanceById, "property", "Property", id) ?? "",
35
+ }));
45
36
  const getTextForTraditions = (deps, value) => {
46
37
  const text = (() => {
47
38
  switch (value.kind) {
48
39
  case "General":
49
40
  return deps.translate("General");
50
41
  case "Specific":
51
- return value.Specific.map(trad => deps.translateMap(deps.getInstanceById("MagicalTradition", trad)?.translations))
42
+ return value.Specific.map(trad => attributedCustomName(deps.translateMap, deps.getInstanceById, "traditions", t => t.name_for_arcane_spellworks ?? t.name, "MagicalTradition", trad))
52
43
  .filter(isNotNullish)
53
- .map(trad => trad.name_for_arcane_spellworks ?? trad.name)
54
44
  .sort(deps.localeCompare)
55
45
  .join(", ");
56
46
  default:
@@ -62,10 +52,7 @@ const getTextForTraditions = (deps, value) => {
62
52
  value: text,
63
53
  };
64
54
  };
65
- const getTraditionNameForArcaneSpellworksById = (id, getInstanceById, translateMap) => {
66
- const translation = translateMap(getInstanceById("MagicalTradition", id)?.translations);
67
- return translation?.name_for_arcane_spellworks ?? translation?.name;
68
- };
55
+ const getTraditionNameForArcaneSpellworksById = (id, getInstanceById, translateMap) => attributedCustomName(translateMap, getInstanceById, "cantrip-note", t => t.name_for_arcane_spellworks ?? t.name, "MagicalTradition", id);
69
56
  /**
70
57
  * Get a JSON representation of the rules text for a cantrip.
71
58
  */
@@ -107,7 +94,7 @@ export const getCantripEntityDescription = createEntityDescriptionCreator(({ get
107
94
  .map(academyOrTradition => {
108
95
  switch (academyOrTradition.kind) {
109
96
  case "Academy":
110
- return translateMap(getInstanceById("Curriculum", academyOrTradition.Academy)?.translations)?.name;
97
+ return attributedName(translateMap, getInstanceById, "cantrip-note", "Curriculum", academyOrTradition.Academy);
111
98
  case "Tradition": {
112
99
  return mapNullable(getTraditionNameForArcaneSpellworksById(academyOrTradition.Tradition.id, getInstanceById, translateMap), name => name +
113
100
  parensIf(translateMap(academyOrTradition.Tradition.translations)?.note));
@@ -296,7 +283,8 @@ export const getCurseEntityDescription = createEntityDescriptionCreator(({ getIn
296
283
  const renderMagicalActionSkill = (skill) => Reader.asks(({ translate, translateMap, localeCompare, localeJoin, getInstanceById }) => ({
297
284
  label: translate("Skill"),
298
285
  value: localeJoin(skill
299
- .map(id => translateMap(getInstanceById("Skill", id)?.translations)?.name ?? MISSING_VALUE)
286
+ .map(id => attributedName(translateMap, getInstanceById, "magical-action-skill", "Skill", id) ??
287
+ MISSING_VALUE)
300
288
  .toSorted(localeCompare), "disjunction"),
301
289
  }));
302
290
  /**
@@ -383,7 +371,7 @@ export const getDominationRitualEntityDescription = createEntityDescriptionCreat
383
371
  const renderMusicTradition = (translate, translateMap, localeCompare, getInstanceById, entity, musicTraditions) => ({
384
372
  label: translate("Music Tradition"),
385
373
  value: ensureNonEmpty(musicTraditions
386
- .map(trad => translateMap(getInstanceById(entity, trad.id)?.translations)?.name)
374
+ .map(trad => attributedName(translateMap, getInstanceById, "music-tradition", entity, trad.id))
387
375
  .filter(isNotNullish)
388
376
  .toSorted(localeCompare))?.join(", ") ?? MISSING_VALUE,
389
377
  });
@@ -472,7 +460,7 @@ export const getMagicalMelodyEntityDescription = createEntityDescriptionCreator(
472
460
  const renderFamiliarsTrickProperty = (translateMap, getInstanceById, responsiveTextSize, property) => {
473
461
  switch (property.kind) {
474
462
  case "Fixed":
475
- return (translateMap(getInstanceById("Property", property.Fixed)?.translations)?.name ??
463
+ return (attributedName(translateMap, getInstanceById, "property", "Property", property.Fixed) ??
476
464
  MISSING_VALUE);
477
465
  case "Indefinite":
478
466
  return getResponsiveText(translateMap(property.Indefinite.translations)?.description, responsiveTextSize);
@@ -573,7 +561,7 @@ const renderAnimistPowerTribeTradition = (translate, translateMap, localeCompare
573
561
  return translate("General");
574
562
  }
575
563
  return (ensureNonEmpty(tribeTradition
576
- .map(id => translateMap(getInstanceById("Tribe", id)?.translations)?.name)
564
+ .map(id => attributedName(translateMap, getInstanceById, "tribe", "Tribe", id))
577
565
  .filter(isNotNullish)
578
566
  .toSorted(localeCompare))?.join(", ") ?? MISSING_VALUE);
579
567
  };
@@ -678,7 +666,7 @@ export const getAnimistPowerEntityDescription = createEntityDescriptionCreator((
678
666
  /**
679
667
  * Get a JSON representation of the rules text for a Goede ritual.
680
668
  */
681
- export const getGeodeRitualEntityDescription = createEntityDescriptionCreator(({ getInstanceById }, locale, { content: entry }) => {
669
+ export const getGeodeRitualEntityDescription = createEntityDescriptionCreator(({ getInstanceById, getResolvedSelectOptionById }, locale, { content: entry }) => {
682
670
  const { translate, translateMap } = locale;
683
671
  const translation = translateMap(entry.translations);
684
672
  if (translation === undefined) {
@@ -719,7 +707,7 @@ export const getGeodeRitualEntityDescription = createEntityDescriptionCreator(({
719
707
  ? undefined
720
708
  : {
721
709
  label: translate("Prerequisites"),
722
- value: printGeodeRitualPrerequisites(getInstanceById, locale, entry.prerequisites),
710
+ value: printGeodeRitualPrerequisites(getInstanceById, getResolvedSelectOptionById, locale, entry.prerequisites),
723
711
  },
724
712
  renderProperty(entry.property).run(env),
725
713
  {
@@ -848,7 +836,7 @@ const renderMagicalRuneCost = (options, cost) => {
848
836
  }
849
837
  };
850
838
  const renderSplitMagicalRuneParameterTranslation = (parameter) => responsiveR(() => parameter.fast.full, () => parameter.fast.abbr).map2(responsiveR(() => parameter.slow.full, () => parameter.slow.abbr), (fast, slow) => `${slow} / ${fast}`);
851
- const renderMagicalRunCraftingTimePart = (craftingTime, unit) => formatTimeSpanR(unit, craftingTime.value).thenW(text => {
839
+ const renderMagicalRuneCraftingTimePart = (craftingTime, unit) => formatTimeSpanR(unit, craftingTime.value).thenW(text => {
852
840
  if (craftingTime.per === undefined) {
853
841
  return Reader.of(text);
854
842
  }
@@ -863,7 +851,7 @@ const renderMagicalRunCraftingTimePart = (craftingTime, unit) => formatTimeSpanR
863
851
  }));
864
852
  });
865
853
  });
866
- const renderMagicalRuneCraftingTime = (craftingTime) => renderMagicalRunCraftingTimePart(craftingTime, "Actions").map2(renderMagicalRunCraftingTimePart(craftingTime, "Days"), (fast, slow) => `${slow} / ${fast}`);
854
+ const renderMagicalRuneCraftingTime = (craftingTime) => renderMagicalRuneCraftingTimePart(craftingTime, "Actions").map2(renderMagicalRuneCraftingTimePart(craftingTime, "Days"), (fast, slow) => `${slow} / ${fast}`);
867
855
  const renderMagicalRuneDuration = (duration) => renderCheckResultBasedDuration(duration.fast).map2(renderCheckResultBasedDuration(duration.slow), (fast, slow) => `${slow} / ${fast}`);
868
856
  const renderMagicalRuneImprovementCost = (options, improvementCost) => {
869
857
  switch (improvementCost.kind) {