@optolith/entity-descriptions 0.13.2 → 0.15.0

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 (74) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/lib/creator.d.ts +4 -11
  3. package/lib/creator.js +58 -2
  4. package/lib/entities/activatable.d.ts +5 -2
  5. package/lib/entities/activatable.js +29 -15
  6. package/lib/entities/alternativeRule.d.ts +1 -1
  7. package/lib/entities/attribute.d.ts +1 -3
  8. package/lib/entities/combatTechnique.d.ts +2 -2
  9. package/lib/entities/condition.d.ts +2 -6
  10. package/lib/entities/culture.d.ts +2 -1
  11. package/lib/entities/culture.js +3 -3
  12. package/lib/entities/curriculum.d.ts +1 -1
  13. package/lib/entities/curriculum.js +1 -1
  14. package/lib/entities/derivedCharacteristic.d.ts +1 -1
  15. package/lib/entities/disease.d.ts +1 -1
  16. package/lib/entities/elixir.d.ts +1 -1
  17. package/lib/entities/elixir.js +1 -1
  18. package/lib/entities/equipment.d.ts +4 -6
  19. package/lib/entities/equipment.js +517 -213
  20. package/lib/entities/equipmentPackage.d.ts +1 -1
  21. package/lib/entities/experienceLevel.d.ts +1 -3
  22. package/lib/entities/focusRule.d.ts +1 -1
  23. package/lib/entities/influence.d.ts +1 -1
  24. package/lib/entities/liturgicalChant.d.ts +3 -3
  25. package/lib/entities/optionalRule.d.ts +1 -3
  26. package/lib/entities/partial/adventurePointsValue.js +8 -7
  27. package/lib/entities/partial/dice.js +2 -2
  28. package/lib/entities/partial/enhancements.js +2 -2
  29. package/lib/entities/partial/herbary.js +1 -1
  30. package/lib/entities/partial/prerequisites/single/personalityTrait.js +1 -1
  31. package/lib/entities/partial/prerequisites/single/ratedSum.js +1 -1
  32. package/lib/entities/partial/profession/options/combatTechniquesForAdventurePoints.d.ts +16 -0
  33. package/lib/entities/partial/profession/options/combatTechniquesForAdventurePoints.js +43 -0
  34. package/lib/entities/partial/profession/options/numberOfSpellworksTotalingAdventurePoints.d.ts +16 -0
  35. package/lib/entities/partial/profession/options/numberOfSpellworksTotalingAdventurePoints.js +39 -0
  36. package/lib/entities/partial/profession/packages.d.ts +67 -0
  37. package/lib/entities/partial/profession/packages.js +84 -0
  38. package/lib/entities/partial/profession/variants.d.ts +21 -0
  39. package/lib/entities/partial/profession/variants.js +31 -0
  40. package/lib/entities/partial/rated/activatable/cost.js +11 -9
  41. package/lib/entities/partial/rated/activatable/effect.js +2 -2
  42. package/lib/entities/partial/reader.d.ts +25 -1
  43. package/lib/entities/partial/reader.js +20 -0
  44. package/lib/entities/partial/units/energy.js +2 -1
  45. package/lib/entities/partial/units/length.js +6 -3
  46. package/lib/entities/partial/units/simple.d.ts +55 -0
  47. package/lib/entities/partial/units/simple.js +56 -0
  48. package/lib/entities/partial/units/timeSpan.js +11 -7
  49. package/lib/entities/partial/unknown.d.ts +4 -0
  50. package/lib/entities/partial/unknown.js +4 -0
  51. package/lib/entities/personalityTrait.d.ts +1 -1
  52. package/lib/entities/poison.d.ts +1 -1
  53. package/lib/entities/poison.js +1 -1
  54. package/lib/entities/profession.d.ts +2 -1
  55. package/lib/entities/profession.js +44 -62
  56. package/lib/entities/race.d.ts +2 -2
  57. package/lib/entities/sexPractice.d.ts +1 -3
  58. package/lib/entities/skill.d.ts +1 -1
  59. package/lib/entities/spell.d.ts +17 -16
  60. package/lib/entities/state.d.ts +1 -3
  61. package/lib/entities/tradeSecret.d.ts +4 -0
  62. package/lib/entities/tradeSecret.js +77 -0
  63. package/lib/env.d.ts +9 -2
  64. package/lib/helpers/locale.d.ts +4 -0
  65. package/lib/index.d.ts +320 -293
  66. package/lib/index.js +9 -1
  67. package/lib/references/index.d.ts +2 -3
  68. package/lib/references/index.js +11 -16
  69. package/lib/references/page.d.ts +4 -2
  70. package/lib/references/page.js +6 -4
  71. package/lib/references/pageRange.d.ts +3 -3
  72. package/lib/references/pageRange.js +6 -4
  73. package/lib/tsconfig.tsbuildinfo +1 -1
  74. package/package.json +2 -2
@@ -5,4 +5,4 @@ import type { GetInstanceById } from "../helpers/getTypes.js";
5
5
  */
6
6
  export declare const getEquipmentPackageEntityDescription: import("../creator.js").EntityDescriptionCreator<"EquipmentPackage", {
7
7
  getInstanceById: GetInstanceById<"Publication" | EquipmentIdentifier["kind"] | "SocialStatus">;
8
- }, import("../index.js").EntityDescription>;
8
+ } & import("../env.js").StdEnv<"fd" | "t" | "tm" | "ibi", "Publication">, import("../index.js").EntityDescription<string>>;
@@ -1,6 +1,4 @@
1
1
  /**
2
2
  * Get a JSON representation of the rules text for an experience level.
3
3
  */
4
- export declare const getExperienceLevelEntityDescription: import("../creator.js").EntityDescriptionCreator<"ExperienceLevel", {
5
- getInstanceById: import("../helpers/getTypes.js").GetInstanceById<"Publication">;
6
- }, import("../index.js").EntityDescription>;
4
+ export declare const getExperienceLevelEntityDescription: import("../creator.js").EntityDescriptionCreator<"ExperienceLevel", import("@elyukai/utils/nullable").AnyNonNullish & import("../env.js").StdEnv<"fd" | "t" | "tm" | "ibi", "Publication">, import("../index.js").EntityDescription<string>>;
@@ -4,4 +4,4 @@ import type { GetInstanceById } from "../helpers/getTypes.js";
4
4
  */
5
5
  export declare const getFocusRuleEntityDescription: import("../creator.js").EntityDescriptionCreator<"FocusRule", {
6
6
  getInstanceById: GetInstanceById<"Publication" | "Subject">;
7
- }, import("../index.js").EntityDescription>;
7
+ } & import("../env.js").StdEnv<"fd" | "t" | "tm" | "ibi", "Publication">, import("../index.js").EntityDescription<string>>;
@@ -8,4 +8,4 @@ export declare const getInfluenceEntityDescription: import("../creator.js").Enti
8
8
  getInstanceById: GetInstanceById<"Publication" | "Influence" | "Race" | ActivatableIdentifier["kind"] | "Aspect">;
9
9
  getResolvedSelectOptionById: GetResolvedSelectOptionById;
10
10
  getChildInstancesForInstanceId: GetAllChildInstancesForParent<"ProfessionVersion">;
11
- }, import("../index.js").EntityDescription>;
11
+ } & import("../env.js").StdEnv<"fd" | "t" | "tm" | "ibi", "Publication">, import("../index.js").EntityDescription<string>>;
@@ -7,7 +7,7 @@ import type { IdMap } from "../index.js";
7
7
  export declare const getBlessingEntityDescription: import("../creator.js").EntityDescriptionCreator<"Blessing", {
8
8
  getInstanceById: GetInstanceById<"Publication" | "TargetCategory">;
9
9
  getAllInstances: GetAllInstances<"BlessedTradition">;
10
- }, import("../index.js").EntityDescription>;
10
+ } & import("../env.js").StdEnv<"fd" | "t" | "tm" | "ibi", "Publication">, import("../index.js").EntityDescription<string>>;
11
11
  /**
12
12
  * Get a JSON representation of the rules text for a liturgical chant.
13
13
  */
@@ -15,7 +15,7 @@ export declare const getLiturgicalChantEntityDescription: import("../creator.js"
15
15
  getInstanceById: GetInstanceById<"Publication" | "Attribute" | "SkillModificationLevel" | "TargetCategory" | "Aspect" | "BlessedTradition" | "DerivedCharacteristic" | RatedIdentifier["kind"] | "Enhancement" | "FocusRule">;
16
16
  getChildInstancesForInstanceId: GetAllChildInstancesForParent<"Enhancement">;
17
17
  idMap: IdMap;
18
- }, import("../index.js").EntityDescription>;
18
+ } & import("../env.js").StdEnv<"fd" | "t" | "tm" | "ibi", "Publication">, import("../index.js").EntityDescription<string>>;
19
19
  /**
20
20
  * Get a JSON representation of the rules text for a ceremony.
21
21
  */
@@ -23,4 +23,4 @@ export declare const getCeremonyEntityDescription: import("../creator.js").Entit
23
23
  getInstanceById: GetInstanceById<"Publication" | "Attribute" | "SkillModificationLevel" | "TargetCategory" | "Aspect" | "BlessedTradition" | "DerivedCharacteristic" | RatedIdentifier["kind"] | "Enhancement" | "FocusRule">;
24
24
  getChildInstancesForInstanceId: GetAllChildInstancesForParent<"Enhancement">;
25
25
  idMap: IdMap;
26
- }, import("../index.js").EntityDescription>;
26
+ } & import("../env.js").StdEnv<"fd" | "t" | "tm" | "ibi", "Publication">, import("../index.js").EntityDescription<string>>;
@@ -1,6 +1,4 @@
1
1
  /**
2
2
  * Get a JSON representation of the rules text for an optional rule.
3
3
  */
4
- export declare const getOptionalRuleEntityDescription: import("../creator.js").EntityDescriptionCreator<"OptionalRule", {
5
- getInstanceById: import("../helpers/getTypes.js").GetInstanceById<"Publication">;
6
- }, import("../index.js").EntityDescription>;
4
+ export declare const getOptionalRuleEntityDescription: import("../creator.js").EntityDescriptionCreator<"OptionalRule", import("@elyukai/utils/nullable").AnyNonNullish & import("../env.js").StdEnv<"fd" | "t" | "tm" | "ibi", "Publication">, import("../index.js").EntityDescription<string>>;
@@ -35,7 +35,7 @@ const renderFixedSelectOptionsAdventurePointsValue = (locale, getAllSelectOption
35
35
  .toSorted(locale.compare), "conjunction"),
36
36
  ])
37
37
  .toSorted(on(item => item[1], locale.compare))
38
- .map(([apValue, itemNames]) => `${itemNames}: ${locale.translate("{$value} Adventure Points", { value: typeof apValue === "number" ? applyNegative(apValue) : apValue })}`)
38
+ .map(([apValue, itemNames]) => `${itemNames}: ${locale.translate("{$value} Adventure Points", { value: typeof apValue === "number" ? applyNegative(apValue).toFixed() : apValue })}`)
39
39
  .join("; ");
40
40
  }
41
41
  };
@@ -88,12 +88,13 @@ export const renderAdventurePointsValue = (locale, getNameForId, getNameForSelec
88
88
  if (entry.levels !== value.ByLevel.list.length) {
89
89
  return MISSING_VALUE;
90
90
  }
91
- const mainValue = `${translate("Level {$level}", {
91
+ const levelsPrefix = translate("Level {$level}", {
92
92
  level: Array.from({ length: entry.levels }, (_, index) => romanize(index + 1)).join("/"),
93
- })}: ${value.ByLevel.list.map(applyNegative).join("/")}`;
93
+ });
94
+ const mainValues = value.ByLevel.list.map(applyNegative).join("/");
94
95
  const { additionalBySizeCategory } = value.ByLevel;
95
96
  if (additionalBySizeCategory === undefined) {
96
- return mainValue;
97
+ return translate("{$value} Adventure Points", { value: mainValues });
97
98
  }
98
99
  else {
99
100
  const sizeCategories = ["tiny", "small", "medium", "large", "huge"];
@@ -101,7 +102,7 @@ export const renderAdventurePointsValue = (locale, getNameForId, getNameForSelec
101
102
  .map(sizeCategory => applyNegative(additionalBySizeCategory[sizeCategory]))
102
103
  .join("/");
103
104
  const labels = sizeCategories.map(sizeCategory => translate(sizeCategory)).join("/");
104
- return `${mainValue} + ${translate("{$values} AP for size category {$labels} (per level)", { values, labels })}`;
105
+ return `${levelsPrefix}: ${mainValues} + ${translate("{$values} AP for size category {$labels} (per level)", { values, labels })}`;
105
106
  }
106
107
  }
107
108
  case "DerivedFromSelection": {
@@ -281,13 +282,13 @@ export const renderAdventurePointsValue = (locale, getNameForId, getNameForSelec
281
282
  const labels = Array.from({ length: entry.maximum ?? 3 }, (_, index) => `${(index + 1).toFixed()}.`).join("/");
282
283
  if (entry.maximum !== undefined) {
283
284
  return translate("{$values} Adventure Points for the {$labels} purchase", {
284
- values,
285
+ values: values.join("/"),
285
286
  labels,
286
287
  });
287
288
  }
288
289
  else {
289
290
  return translate("{$values}/and so on Adventure Points for the {$labels}/and so on purchase", {
290
- values,
291
+ values: values.join("/"),
291
292
  labels,
292
293
  });
293
294
  }
@@ -3,11 +3,11 @@ import { translateR } from "./reader.js";
3
3
  /**
4
4
  * Renders a dice expression like "2D6" into a localized string.
5
5
  */
6
- export const renderDice = (translate, dice) => translate("{$count}D{$sides}", { count: dice.number, sides: dice.sides });
6
+ export const renderDice = (translate, dice) => translate("{$count}D{$sides}", { count: dice.number.toFixed(), sides: dice.sides.toFixed() });
7
7
  /**
8
8
  * Renders a dice expression like "2D6" into a localized string.
9
9
  */
10
- export const renderDiceR = (dice) => translateR("{$count}D{$sides}", { count: dice.number, sides: dice.sides });
10
+ export const renderDiceR = (dice) => translateR("{$count}D{$sides}", { count: dice.number.toFixed(), sides: dice.sides.toFixed() });
11
11
  /**
12
12
  * Renders a dice expression with an additional flat modifier, like "2D6+3" or "2D6-1".
13
13
  */
@@ -22,8 +22,8 @@ const renderEnhancement = (enhancement, parentImprovementCost) => {
22
22
  const { prerequisites } = enhancement.content;
23
23
  return translateMapR(enhancement.content.translations).thenW(translation => translation === undefined
24
24
  ? Reader.of(undefined)
25
- : translateR("SR {$value}", { value: enhancement.content.skill_rating }).thenW(sr => translateR("{$value} AP", {
26
- value: getEnhancementAPValue(enhancement.content.adventurePoints, parentImprovementCost),
25
+ : translateR("SR {$value}", { value: enhancement.content.skill_rating.toFixed() }).thenW(sr => translateR("{$value} AP", {
26
+ value: getEnhancementAPValue(enhancement.content.adventurePoints, parentImprovementCost).toFixed(),
27
27
  }).thenW(ap => (prerequisites === undefined
28
28
  ? Reader.of("")
29
29
  : translateR(".input {$hiddenCount :number} {{Prerequisites}}", {
@@ -47,7 +47,7 @@ export const renderChance = (translate, translateMap, item, includePercentage =
47
47
  (item.chance === undefined
48
48
  ? undefined
49
49
  : translate("{$valueRange} on {$dice}", {
50
- valueRange: item.chance === 5 ? 1 : `1–${(item.chance / 5).toFixed()}`,
50
+ valueRange: item.chance === 5 ? "1" : `1–${(item.chance / 5).toFixed()}`,
51
51
  dice: renderDice(translate, { number: 1, sides: 20 }),
52
52
  }) +
53
53
  (includePercentage
@@ -12,7 +12,7 @@ export const printPersonalityTraitPrerequisite = (prerequisite) => prerequisite.
12
12
  : attributedNameFromInstanceR(personalityTrait, "prerequisite", "PersonalityTrait", prerequisite.id)
13
13
  .map(name => name ?? MISSING_VALUE)
14
14
  .thenW(name => translateR("Level {$level}", {
15
- level: personalityTrait.level,
15
+ level: personalityTrait.level.toFixed(),
16
16
  }).map(level => `${name} (${level})`))
17
17
  .then(name => prerequisite.active
18
18
  ? translateR("must have {$trait}", {
@@ -13,7 +13,7 @@ export const printRatedSumPrerequisite = (prerequisite) => prerequisite.display_
13
13
  .thenW(skills => localeJoinR(skills, "conjunction"))
14
14
  .thenW(skills => translateR("the SR for {$skill} combined must add up to at least {$minRating}", {
15
15
  skill: skills,
16
- minRating: prerequisite.sum,
16
+ minRating: prerequisite.sum.toFixed(),
17
17
  }))
18
18
  .map(value => ({
19
19
  value,
@@ -0,0 +1,16 @@
1
+ import type { NonEmptyArray } from "@elyukai/utils/array/nonEmpty";
2
+ import { Reader } from "@elyukai/utils/reader";
3
+ import type { CombatTechniquesForAdventurePointsOption, VariantOptionAction } from "@optolith/database-schema/gen";
4
+ import type { StdEnv } from "../../../../env.js";
5
+ import { type PreparedProfessionPackage } from "../packages.js";
6
+ type Option = CombatTechniquesForAdventurePointsOption;
7
+ type OptionEnv = StdEnv<"t" | "tm" | "ibi", "CloseCombatTechnique" | "RangedCombatTechnique">;
8
+ /**
9
+ * Renders the combat techniques for adventure points option for a list of profession packages.
10
+ */
11
+ export declare const renderBaseCombatTechniquesForAdventurePointsOption: (professionPackages: NonEmptyArray<PreparedProfessionPackage>) => Reader<OptionEnv, string | undefined>;
12
+ /**
13
+ * Renders the combat techniques for adventure points option for a variant of a profession package.
14
+ */
15
+ export declare const renderVariantCombatTechniquesForAdventurePointsOption: (baseOptions: Option | undefined, variantOptions: VariantOptionAction<Option>) => Reader<OptionEnv, string>;
16
+ export {};
@@ -0,0 +1,43 @@
1
+ import { deepEqual, equal } from "@elyukai/utils/equality";
2
+ import { Reader } from "@elyukai/utils/reader";
3
+ import { attributedNameR, fixedNumberOrString, translateR } from "../../reader.js";
4
+ import { MISSING_VALUE } from "../../unknown.js";
5
+ import { renderParameterizedOptionForBaseAndVariant, renderParameterizedOptionForMultiplePackages, } from "../packages.js";
6
+ const renderCombatTechniquesForAdventurePointsOptionList = (options) => Reader.traverse(options, id => attributedNameR("profession", id).map(name => name ?? MISSING_VALUE)).map(list => list.join(", "));
7
+ const renderCombatTechniquesForAdventurePointsOptionText = (apValue, list) => translateR("{$apValue} AP to distribute among the following combat techniques: {$list}", {
8
+ apValue: fixedNumberOrString(apValue),
9
+ list,
10
+ });
11
+ const renderStandaloneOption = (option) => renderCombatTechniquesForAdventurePointsOptionList(option.options).thenW(list => renderCombatTechniquesForAdventurePointsOptionText(option.apValue.toFixed(), list));
12
+ const configs = [
13
+ {
14
+ selector: option => option.apValue,
15
+ equality: equal,
16
+ render: apValue => Reader.of(apValue),
17
+ renderAsString: apValue => Reader.of(apValue.toFixed()),
18
+ emptyText: "—",
19
+ joiner: "/",
20
+ },
21
+ {
22
+ selector: option => option.options,
23
+ equality: deepEqual,
24
+ render: renderCombatTechniquesForAdventurePointsOptionList,
25
+ renderAsString: renderCombatTechniquesForAdventurePointsOptionList,
26
+ emptyText: "—",
27
+ joiner: " / ",
28
+ },
29
+ ];
30
+ const render = (apValue, list) => translateR("{$apValue} AP to distribute among the following combat techniques: {$list}", {
31
+ apValue: fixedNumberOrString(apValue),
32
+ list,
33
+ });
34
+ /**
35
+ * Renders the combat techniques for adventure points option for a list of profession packages.
36
+ */
37
+ export const renderBaseCombatTechniquesForAdventurePointsOption = (professionPackages) => renderParameterizedOptionForMultiplePackages(professionPackages, pkg => pkg.content.options?.combatTechniquesForAdventurePoints, { type: "separate", emptyText: "—", joiner: " / " }, render, ...configs);
38
+ /**
39
+ * Renders the combat techniques for adventure points option for a variant of a profession package.
40
+ */
41
+ export const renderVariantCombatTechniquesForAdventurePointsOption = (baseOptions, variantOptions) => renderParameterizedOptionForBaseAndVariant(baseOptions, variantOptions, { type: "separate", emptyText: "—", joiner: " / " }, base => renderCombatTechniquesForAdventurePointsOptionList(base.options).thenW(list => translateR("no AP to distribute among the following combat techniques: {$list}", {
42
+ list,
43
+ })), renderStandaloneOption, render, ...configs);
@@ -0,0 +1,16 @@
1
+ import type { NonEmptyArray } from "@elyukai/utils/array/nonEmpty";
2
+ import { Reader } from "@elyukai/utils/reader";
3
+ import type { SpellworksOptions, VariantOptionAction } from "@optolith/database-schema/gen";
4
+ import type { StdEnv } from "../../../../env.js";
5
+ import { type PreparedProfessionPackage } from "../packages.js";
6
+ type Option = SpellworksOptions;
7
+ type OptionEnv = StdEnv<"t">;
8
+ /**
9
+ * Renders the combat techniques for adventure points option for a list of profession packages.
10
+ */
11
+ export declare const renderBaseNumberOfSpellworksTotalingAdventurePoints: (professionPackages: NonEmptyArray<PreparedProfessionPackage>) => Reader<OptionEnv, string | undefined>;
12
+ /**
13
+ * Renders the combat techniques for adventure points option for a variant of a profession package.
14
+ */
15
+ export declare const renderVariantNumberOfSpellworksTotalingAdventurePoints: (baseOptions: Option | undefined, variantOptions: VariantOptionAction<Option>) => Reader<OptionEnv, string>;
16
+ export {};
@@ -0,0 +1,39 @@
1
+ import { equal } from "@elyukai/utils/equality";
2
+ import { Reader } from "@elyukai/utils/reader";
3
+ import { fixedNumberOrString, translateR } from "../../reader.js";
4
+ import { renderParameterizedOptionForBaseAndVariant, renderParameterizedOptionForMultiplePackages, } from "../packages.js";
5
+ const configs = [
6
+ {
7
+ selector: option => option.number,
8
+ equality: equal,
9
+ render: apValue => Reader.of(apValue),
10
+ renderAsString: apValue => Reader.of(apValue.toFixed()),
11
+ emptyText: "—",
12
+ joiner: "/",
13
+ },
14
+ {
15
+ selector: option => option.apValue,
16
+ equality: equal,
17
+ render: apValue => Reader.of(apValue),
18
+ renderAsString: apValue => Reader.of(apValue.toFixed()),
19
+ emptyText: "—",
20
+ joiner: "/",
21
+ },
22
+ ];
23
+ const render = (count, apValue) => typeof count === "number"
24
+ ? translateR(".input {$count :number} {{{$count} more spellworks totaling {$apValue} AP}}", {
25
+ count,
26
+ apValue: fixedNumberOrString(apValue),
27
+ })
28
+ : translateR("{$count} more spellworks totaling {$apValue} AP", {
29
+ count,
30
+ apValue: fixedNumberOrString(apValue),
31
+ });
32
+ /**
33
+ * Renders the combat techniques for adventure points option for a list of profession packages.
34
+ */
35
+ export const renderBaseNumberOfSpellworksTotalingAdventurePoints = (professionPackages) => renderParameterizedOptionForMultiplePackages(professionPackages, pkg => pkg.content.options?.spellworks, { type: "separate", emptyText: "—", joiner: " / " }, render, ...configs);
36
+ /**
37
+ * Renders the combat techniques for adventure points option for a variant of a profession package.
38
+ */
39
+ export const renderVariantNumberOfSpellworksTotalingAdventurePoints = (baseOptions, variantOptions) => renderParameterizedOptionForBaseAndVariant(baseOptions, variantOptions, { type: "separate", emptyText: "—", joiner: " / " }, base => render(0, base.apValue), base => render(base.number, base.apValue), render, ...configs);
@@ -0,0 +1,67 @@
1
+ import type { NonEmptyArray } from "@elyukai/utils/array/nonEmpty";
2
+ import type { Equality } from "@elyukai/utils/equality";
3
+ import type { AnyNonNullish } from "@elyukai/utils/nullable";
4
+ import { Reader } from "@elyukai/utils/reader";
5
+ import type { ExperienceLevel, Profession_ID, ProfessionPackage, VariantOptionAction } from "@optolith/database-schema/gen";
6
+ import type { StdEnv, StdReader } from "../../../env.js";
7
+ /**
8
+ * Profession Package with its associated experience level.
9
+ */
10
+ export type PreparedProfessionPackage = {
11
+ id: string;
12
+ content: ProfessionPackage;
13
+ experienceLevel: ExperienceLevel;
14
+ };
15
+ /**
16
+ * Collects all profession packages for a given profession and returns them sorted by their associated experience level.
17
+ */
18
+ export declare const prepareProfessionPackages: (professionId: Profession_ID) => StdReader<PreparedProfessionPackage[], "ibi" | "acibp", "ExperienceLevel", never, "ProfessionPackage">;
19
+ /**
20
+ * Configuration for retrieving, rendering and combining an option parameter.
21
+ */
22
+ export type OptionParameterConfig<T, P, R, E> = {
23
+ selector: (option: T) => P;
24
+ equality: Equality<P | undefined>;
25
+ render: (option: P) => Reader<E, R>;
26
+ renderAsString: (option: P) => Reader<E, string>;
27
+ emptyText: string;
28
+ joiner: string;
29
+ };
30
+ type OptionParameterValueMap = [value: unknown, result: unknown][];
31
+ /**
32
+ * Option parameter configurations for multiple parameters.
33
+ */
34
+ export type OptionParameterConfigs<T, P extends OptionParameterValueMap, E> = {
35
+ [K in keyof P]: OptionParameterConfig<T, P[K][0], P[K][1], E>;
36
+ };
37
+ /**
38
+ * Extract parameters for final text render.
39
+ */
40
+ type OptionParameterTextRenderParameters<P extends OptionParameterValueMap> = {
41
+ [K in keyof P]: P[K][1] | string;
42
+ };
43
+ /**
44
+ * Renders the text for multiple parameters.
45
+ */
46
+ export type OptionParameterTextRender<P extends OptionParameterValueMap, E> = (...args: OptionParameterTextRenderParameters<P>) => Reader<E, string>;
47
+ /**
48
+ * Renders a two-parameter option for a list of profession packages.
49
+ */
50
+ export declare const renderParameterizedOptionForMultiplePackages: <T, P extends OptionParameterValueMap, E>(professionPackages: NonEmptyArray<PreparedProfessionPackage>, optionSelector: (pkg: PreparedProfessionPackage) => T | undefined, allDifferent: {
51
+ type: "separate";
52
+ emptyText: string;
53
+ joiner: string;
54
+ } | {
55
+ type: "combined";
56
+ }, renderText: OptionParameterTextRender<P, E>, ...configs: OptionParameterConfigs<T, P, E>) => Reader<E, string | undefined>;
57
+ /**
58
+ * Renders a two-parameter option for a list of profession packages.
59
+ */
60
+ export declare const renderParameterizedOptionForBaseAndVariant: <T extends AnyNonNullish, P extends OptionParameterValueMap, E>(baseOptions: T | undefined, variantOptions: VariantOptionAction<T>, allDifferent: {
61
+ type: "separate";
62
+ emptyText: string;
63
+ joiner: string;
64
+ } | {
65
+ type: "combined";
66
+ }, renderRemove: (base: T) => Reader<E, string>, renderAdd: (override: T) => Reader<E, string>, renderText: OptionParameterTextRender<P, E>, ...configs: OptionParameterConfigs<T, P, E>) => Reader<E & StdEnv<"t">, string>;
67
+ export {};
@@ -0,0 +1,84 @@
1
+ import { allSame } from "@elyukai/utils/array/filters";
2
+ import { on } from "@elyukai/utils/function";
3
+ import { compareNumber } from "@elyukai/utils/ordering";
4
+ import { Reader } from "@elyukai/utils/reader";
5
+ import { assertExhaustive } from "@elyukai/utils/typeSafety";
6
+ import { LOGIC_ERROR } from "../unknown.js";
7
+ import { insteadOfR, renderVariantOptionPaths } from "./variants.js";
8
+ /**
9
+ * Collects all profession packages for a given profession and returns them sorted by their associated experience level.
10
+ */
11
+ export const prepareProfessionPackages = (professionId) => Reader.asks(({ getInstanceById, getChildInstancesForInstanceId }) => getChildInstancesForInstanceId("ProfessionPackage", professionId)
12
+ .map((professionPackage) => ({
13
+ ...professionPackage,
14
+ experienceLevel: getInstanceById("ExperienceLevel", professionPackage.content.experience_level),
15
+ }))
16
+ .filter((professionPackage) => professionPackage.experienceLevel !== undefined)
17
+ .toSorted(on(item => item.experienceLevel.adventure_points, compareNumber)));
18
+ const renderSeparatedOptionList = (options, render, emptyText, joiner) => Reader.traverse(options, option => option === undefined ? Reader.of(emptyText) : render(option)).map(list => list.join(joiner));
19
+ /**
20
+ * Renders a two-parameter option for a list of profession packages.
21
+ */
22
+ export const renderParameterizedOptionForMultiplePackages = (professionPackages, optionSelector, allDifferent, renderText, ...configs) => {
23
+ const options = professionPackages.map(optionSelector);
24
+ const [firstOption] = options;
25
+ const sames = configs.map(config => allSame(options, on(option => (option === undefined ? undefined : config.selector(option)), config.equality)));
26
+ if (sames.some(same => same)) {
27
+ if (firstOption === undefined) {
28
+ return Reader.of(undefined);
29
+ }
30
+ return Reader.traverse(configs, (config, configIndex) => sames[configIndex] === true
31
+ ? config.render(config.selector(firstOption))
32
+ : renderSeparatedOptionList(options, option => config.renderAsString(config.selector(option)), config.emptyText, config.joiner)).thenW(parameterValues => renderText(...parameterValues));
33
+ }
34
+ else {
35
+ switch (allDifferent.type) {
36
+ case "combined":
37
+ return Reader.traverse(configs, config => renderSeparatedOptionList(options, option => config.renderAsString(config.selector(option)), config.emptyText, config.joiner)).thenW(parameterValues => renderText(...parameterValues));
38
+ case "separate":
39
+ return renderSeparatedOptionList(options, option => Reader.traverse(configs, config => config.render(config.selector(option))).thenW(parameterValues => renderText(...parameterValues)), allDifferent.emptyText, allDifferent.joiner);
40
+ default:
41
+ return assertExhaustive(allDifferent);
42
+ }
43
+ }
44
+ };
45
+ /**
46
+ * Renders a two-parameter option for a list of profession packages.
47
+ */
48
+ export const renderParameterizedOptionForBaseAndVariant = (baseOptions, variantOptions, allDifferent, renderRemove, renderAdd, renderText, ...configs) => renderVariantOptionPaths(baseOptions, variantOptions, {
49
+ remove: renderRemove,
50
+ add: renderAdd,
51
+ update: (base, override) => {
52
+ const sames = configs.map(config => config.equality(config.selector(base), config.selector(override)));
53
+ if (sames.every(same => same)) {
54
+ return Reader.of(LOGIC_ERROR); // Base and variant would be equal then, so the variant option should be removed or changed
55
+ }
56
+ if (sames.some(same => same)) {
57
+ return Reader.traverse(configs, (config, configIndex) => sames[configIndex] === true
58
+ ? config.render(config.selector(base))
59
+ : config
60
+ .renderAsString(config.selector(base))
61
+ .thenW(baseValue => config
62
+ .renderAsString(config.selector(override))
63
+ .thenW(overrideValue => insteadOfR(overrideValue, baseValue)))).thenW(parameterValues => renderText(...parameterValues));
64
+ }
65
+ else {
66
+ switch (allDifferent.type) {
67
+ case "combined":
68
+ return Reader.traverse(configs, config => config
69
+ .renderAsString(config.selector(base))
70
+ .thenW(baseValue => config
71
+ .renderAsString(config.selector(override))
72
+ .thenW(overrideValue => insteadOfR(overrideValue, baseValue)))).thenW(parameterValues => renderText(...parameterValues));
73
+ case "separate":
74
+ return Reader.traverse(configs, config => config.render(config.selector(base)))
75
+ .thenW(parameterValues => renderText(...parameterValues))
76
+ .thenW(baseValue => Reader.traverse(configs, config => config.render(config.selector(override)))
77
+ .thenW(parameterValues => renderText(...parameterValues))
78
+ .thenW(overrideValue => insteadOfR(overrideValue, baseValue)));
79
+ default:
80
+ return assertExhaustive(allDifferent);
81
+ }
82
+ }
83
+ },
84
+ });
@@ -0,0 +1,21 @@
1
+ import { type AnyNonNullish } from "@elyukai/utils/nullable";
2
+ import { Reader } from "@elyukai/utils/reader";
3
+ import type { ProfessionPackage, ProfessionPackageOptions, ProfessionVariant, ProfessionVariantPackageOptions, VariantOptionAction } from "@optolith/database-schema/gen";
4
+ /**
5
+ * Renders an option for a profession variant, using the base profession package and the variant's options.
6
+ */
7
+ export declare const renderVariantOption: <Env, K extends keyof ProfessionVariantPackageOptions>(base: ProfessionPackage, variant: ProfessionVariant, key: K, render: (base: ProfessionPackageOptions[K] | undefined, variant: NonNullable<ProfessionVariantPackageOptions[K]>) => Reader<Env, string>) => Reader<Env, string[]>;
8
+ /**
9
+ * Render a variant option based on whether it is a removal, addition, or override of the base option.
10
+ */
11
+ export declare const renderVariantOptionPaths: <T extends AnyNonNullish, Env1, Env2, Env3>(base: T | undefined, variant: VariantOptionAction<T>, paths: {
12
+ remove: (base: T) => Reader<Env1, string>;
13
+ add: (variant: T) => Reader<Env2, string>;
14
+ update: (base: T, variant: T) => Reader<Env3, string>;
15
+ }) => Reader<Env1 & Env2 & Env3, string>;
16
+ /**
17
+ * Helper for "X instead of Y" text.
18
+ */
19
+ export declare const insteadOfR: (replacement: string | number, base: string | number) => Reader<{
20
+ translate: import("../../../helpers/translate.js").Translate;
21
+ }, string>;
@@ -0,0 +1,31 @@
1
+ import { nullableToArray } from "@elyukai/utils/nullable";
2
+ import { Reader } from "@elyukai/utils/reader";
3
+ import { assertExhaustive } from "@elyukai/utils/typeSafety";
4
+ import { fixedNumberOrString, translateR } from "../reader.js";
5
+ import { MISSING_VALUE } from "../unknown.js";
6
+ /**
7
+ * Renders an option for a profession variant, using the base profession package and the variant's options.
8
+ */
9
+ export const renderVariantOption = (base, variant, key, render) => (variant.options?.[key] === undefined
10
+ ? Reader.of(undefined)
11
+ : render(base.options?.[key], variant.options[key])).map(text => nullableToArray(text));
12
+ /**
13
+ * Render a variant option based on whether it is a removal, addition, or override of the base option.
14
+ */
15
+ export const renderVariantOptionPaths = (base, variant, paths) => {
16
+ switch (variant.kind) {
17
+ case "Remove":
18
+ return base === undefined ? Reader.of(MISSING_VALUE) : paths.remove(base);
19
+ case "Override":
20
+ return base === undefined ? paths.add(variant.Override) : paths.update(base, variant.Override);
21
+ default:
22
+ return assertExhaustive(variant);
23
+ }
24
+ };
25
+ /**
26
+ * Helper for "X instead of Y" text.
27
+ */
28
+ export const insteadOfR = (replacement, base) => translateR("{$replacement} instead of {$base}", {
29
+ replacement: fixedNumberOrString(replacement),
30
+ base: fixedNumberOrString(base),
31
+ });
@@ -3,7 +3,7 @@ import { Reader } from "@elyukai/utils/reader";
3
3
  import { mapNullable } from "@optolith/helpers/nullable";
4
4
  import { assertExhaustive } from "@optolith/helpers/typeSafety";
5
5
  import { renderParameterMap } from "../../map.js";
6
- import { formatEnergyFnR, formatEnergyR, getInstanceByIdFnR, modifiableBySpeedR, responsiveLocaleJoinR, responsiveR, responsiveTextR, responsiveTranslateR, translateMapR, translateR, } from "../../reader.js";
6
+ import { fixedNumberOrString, formatEnergyFnR, formatEnergyR, getInstanceByIdFnR, modifiableBySpeedR, responsiveLocaleJoinR, responsiveR, responsiveTextR, responsiveTranslateR, responsiveTranslateSplitR, translateMapR, translateR, } from "../../reader.js";
7
7
  import { appendNoteIfNeeded, replaceTextIfNeeded } from "../../responsiveText.js";
8
8
  import { formatCombinedTimeSpanR } from "../../units/timeSpan.js";
9
9
  import { MISSING_VALUE } from "../../unknown.js";
@@ -33,9 +33,11 @@ const appendPerCountableToCostIfNeeded = (value, baseCost) => {
33
33
  })));
34
34
  });
35
35
  };
36
- const appendPermanentCostIfNeeded = (permanentValue, baseCost) => mapNullable(permanentValue, value => responsiveTranslateR(".input {$value :number} {{, {$value} of which are permanent}}", " ({$value} perm.)", { value }).map(text => baseCost + text)) ?? Reader.of(baseCost);
37
- const appendElvenPermanentCostIfNeeded = (permanent, baseCost) => mapNullable(permanent, value => responsiveTranslateR(".input {$value :number} {{{$value} permanent AE}}", "{$value} pAE", {
36
+ const appendPermanentCostIfNeeded = (permanentValue, baseCost) => mapNullable(permanentValue, value => responsiveTranslateSplitR(".input {$value :number} {{, {$value} of which are permanent}}", " ({$value} perm.)", { value }, { value: value.toFixed() }).map(text => baseCost + text)) ?? Reader.of(baseCost);
37
+ const appendElvenPermanentCostIfNeeded = (permanent, baseCost) => mapNullable(permanent, value => responsiveTranslateSplitR(".input {$value :number} {{{$value} permanent AE}}", "{$value} pAE", {
38
38
  value: value.value,
39
+ }, {
40
+ value: value.value.toFixed(),
39
41
  })
40
42
  .thenW(text => replaceTextIfNeeded(value.translations, text))
41
43
  .map(text => baseCost + text)) ?? Reader.of(baseCost);
@@ -117,7 +119,7 @@ const renderMultipleOneTimeCosts = (type, value) => {
117
119
  export const renderStandaloneCostMap = (value) => renderParameterMap(value, option => Reader.of(option.value), formatEnergyR, value.options.every(option => option.value.permanentValue !== undefined)
118
120
  ? {
119
121
  format: values => translateR(", {$value} of which are permanent", {
120
- value: values,
122
+ value: fixedNumberOrString(values),
121
123
  }),
122
124
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- is checked beforehand
123
125
  render: option => Reader.of(option.value.permanentValue),
@@ -132,7 +134,7 @@ const renderOneTimeCostMap = (value) => {
132
134
  return renderParameterMap(value.Modifiable.map, option => deriveModifiableCost(option.initialModificationLevel).map(cost => cost ?? MISSING_VALUE), singleValue => formatEnergyR(singleValue), value.Modifiable.map.options.every(option => option.value.permanentValue !== undefined)
133
135
  ? {
134
136
  format: values => translateR(", {$value} of which are permanent", {
135
- value: values,
137
+ value: fixedNumberOrString(values),
136
138
  }),
137
139
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- is checked beforehand
138
140
  render: option => Reader.of(option.value.permanentValue),
@@ -142,7 +144,7 @@ const renderOneTimeCostMap = (value) => {
142
144
  return renderParameterMap(value.NonModifiable.map, option => Reader.of(option.value), formatEnergyR, value.NonModifiable.map.options.every(option => option.value.permanentValue !== undefined)
143
145
  ? {
144
146
  format: values => translateR(", {$value} of which are permanent", {
145
- value: values,
147
+ value: fixedNumberOrString(values),
146
148
  }),
147
149
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- is checked beforehand
148
150
  render: option => Reader.of(option.value.permanentValue),
@@ -240,10 +242,10 @@ export const renderMagicalActionCost = (cost) => {
240
242
  case "FirstPerson":
241
243
  return Reader.asks(({ translate }) => translate("{$firstPersonValue} for the first person; {$additionalPersonValue} for each additional person", {
242
244
  firstPersonValue: translate("{$value} AE", {
243
- value: cost.FirstPerson.value,
245
+ value: cost.FirstPerson.value.toFixed(),
244
246
  }),
245
247
  additionalPersonValue: translate("{$value} AE", {
246
- value: cost.FirstPerson.value / 2,
248
+ value: (cost.FirstPerson.value / 2).toFixed(),
247
249
  }),
248
250
  }));
249
251
  case "ByPrimaryPatron":
@@ -252,7 +254,7 @@ export const renderMagicalActionCost = (cost) => {
252
254
  return Reader.asks(({ translate }) => cost.All.minimum === undefined
253
255
  ? translate("All AE")
254
256
  : translate("All AE, at least {$value} AE", {
255
- value: cost.All.minimum,
257
+ value: cost.All.minimum.toFixed(),
256
258
  }));
257
259
  case "Map":
258
260
  return renderStandaloneCostMap(cost.Map);
@@ -1,6 +1,6 @@
1
1
  import { mapNullable } from "@optolith/helpers/nullable";
2
2
  import { assertExhaustive } from "@optolith/helpers/typeSafety";
3
- import { translateFnR } from "../../reader.js";
3
+ import { fixedNumberOrString, translateFnR } from "../../reader.js";
4
4
  const getContentPartsForQualityLevels = (getQualityLevelString, source) => translateFnR.map((translate) => ({
5
5
  label: translate("Effect"),
6
6
  value: [
@@ -13,7 +13,7 @@ const getContentPartsForQualityLevels = (getQualityLevelString, source) => trans
13
13
  style: "hidden",
14
14
  items: source.quality_levels.map((text, index) => ({
15
15
  label: translate("QL {$value}", {
16
- value: getQualityLevelString(index),
16
+ value: fixedNumberOrString(getQualityLevelString(index)),
17
17
  }),
18
18
  value: text,
19
19
  })),