@optolith/entity-descriptions 0.13.1 → 0.14.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 (42) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/lib/creator.js +1 -1
  3. package/lib/entities/activatable.d.ts +1 -1
  4. package/lib/entities/activatable.js +28 -18
  5. package/lib/entities/culture.js +3 -3
  6. package/lib/entities/curriculum.js +1 -1
  7. package/lib/entities/elixir.js +1 -1
  8. package/lib/entities/equipment.js +1 -1
  9. package/lib/entities/liturgicalChant.d.ts +2 -2
  10. package/lib/entities/liturgicalChant.js +4 -0
  11. package/lib/entities/partial/adventurePointsValue.js +8 -7
  12. package/lib/entities/partial/dice.js +2 -2
  13. package/lib/entities/partial/enhancements.js +2 -2
  14. package/lib/entities/partial/herbary.js +1 -1
  15. package/lib/entities/partial/prerequisites/index.d.ts +6 -1
  16. package/lib/entities/partial/prerequisites/index.js +24 -1
  17. package/lib/entities/partial/prerequisites/single/personalityTrait.js +1 -1
  18. package/lib/entities/partial/prerequisites/single/ratedSum.js +1 -1
  19. package/lib/entities/partial/profession/options/combatTechniquesForAdventurePoints.d.ts +16 -0
  20. package/lib/entities/partial/profession/options/combatTechniquesForAdventurePoints.js +43 -0
  21. package/lib/entities/partial/profession/options/numberOfSpellworksTotalingAdventurePoints.d.ts +16 -0
  22. package/lib/entities/partial/profession/options/numberOfSpellworksTotalingAdventurePoints.js +39 -0
  23. package/lib/entities/partial/profession/packages.d.ts +67 -0
  24. package/lib/entities/partial/profession/packages.js +84 -0
  25. package/lib/entities/partial/profession/variants.d.ts +21 -0
  26. package/lib/entities/partial/profession/variants.js +31 -0
  27. package/lib/entities/partial/rated/activatable/cost.js +11 -9
  28. package/lib/entities/partial/rated/activatable/effect.js +2 -2
  29. package/lib/entities/partial/reader.d.ts +25 -1
  30. package/lib/entities/partial/reader.js +20 -0
  31. package/lib/entities/partial/units/energy.js +2 -1
  32. package/lib/entities/partial/units/length.js +6 -3
  33. package/lib/entities/partial/units/timeSpan.js +11 -7
  34. package/lib/entities/partial/unknown.d.ts +4 -0
  35. package/lib/entities/partial/unknown.js +4 -0
  36. package/lib/entities/poison.js +1 -1
  37. package/lib/entities/profession.js +44 -62
  38. package/lib/entities/spell.d.ts +2 -2
  39. package/lib/entities/spell.js +3 -1
  40. package/lib/index.d.ts +69 -68
  41. package/lib/tsconfig.tsbuildinfo +1 -1
  42. package/package.json +2 -2
@@ -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
  })),
@@ -1,6 +1,7 @@
1
1
  import { Reader } from "@elyukai/utils/reader";
2
2
  import type { ChildEntityMap, EntityMap, FastSkillModificationLevelConfig, ResponsiveText, ResponsiveTextOptional, SkillModificationLevel, SlowSkillModificationLevelConfig } from "@optolith/database-schema/gen";
3
3
  import type { IdArgsVariant } from "tsondb/schema/gen";
4
+ import type { StdReader } from "../../env.js";
4
5
  import type { GetAllChildInstancesForParent, GetAllInstances, GetInstanceById } from "../../helpers/getTypes.js";
5
6
  import type { LocaleCompare, LocaleJoin, LocaleJoinType } from "../../helpers/locale.js";
6
7
  import type { Format, LocaleMap, Translate, TranslateMap, TranslationKeyMatchingParamsOfKey, TranslationParamsInArray, Translations } from "../../helpers/translate.js";
@@ -99,7 +100,7 @@ export declare const attributedNameFromInstanceR: (instance: {
99
100
  translations: LocaleMap<{
100
101
  name: string;
101
102
  }>;
102
- } | undefined, context: string, ...args: IdArgsVariant) => Reader<{
103
+ } | undefined, context: string, ...args: IdArgsVariant<EntityMap, keyof EntityMap>) => Reader<{
103
104
  translateMap: TranslateMap;
104
105
  }, string | undefined>;
105
106
  /**
@@ -204,6 +205,13 @@ export declare const responsiveTranslateR: <K extends keyof Translations, K2 ext
204
205
  translate: Translate;
205
206
  responsiveTextSize: ResponsiveTextSize;
206
207
  }, string>;
208
+ /**
209
+ * Creates a responsive value from a translation key that has a full and a compressed version.
210
+ */
211
+ export declare const responsiveTranslateSplitR: <K extends keyof Translations, K2 extends TranslationKeyMatchingParamsOfKey<K>>(fullKey: K, compressedKey: K2, ...rest: [...TranslationParamsInArray<K>, ...TranslationParamsInArray<K2>]) => Reader<{
212
+ translate: Translate;
213
+ responsiveTextSize: ResponsiveTextSize;
214
+ }, string>;
207
215
  /**
208
216
  * Creates a responsive value from a responsive text.
209
217
  */
@@ -266,4 +274,20 @@ export declare const mapNullableR: <T, U, R>(defaultValue: U, fn: (value: NonNul
266
274
  * Use `Reader` instances directly in a template string.
267
275
  */
268
276
  export declare const sequence: <E>(string: TemplateStringsArray, ...values: (Reader<E, string> | string)[]) => Reader<E, string>;
277
+ /**
278
+ * Formats a number according to the locale’s rules, returns a string as-is.
279
+ */
280
+ export declare const formatNumber: (value: number) => StdReader<string, "fn">;
281
+ /**
282
+ * Formats an AP value in an appreviated form.
283
+ */
284
+ export declare const formatAPValue: (value: number) => StdReader<string, "t" | "fn">;
285
+ /**
286
+ * Formats a number according to the locale’s rules, returns a string as-is.
287
+ */
288
+ export declare const formatNumberOrString: (value: number | string) => StdReader<string, "fn">;
289
+ /**
290
+ * Formats a number in fixed-point notation, returns a string as-is.
291
+ */
292
+ export declare const fixedNumberOrString: (value: number | string, fractionDigits?: number) => string;
269
293
  export {};
@@ -135,6 +135,10 @@ export const responsiveThenR = (full, compressed) => Reader.asks(env => {
135
135
  * Creates a responsive value from a translation key that has a full and a compressed version.
136
136
  */
137
137
  export const responsiveTranslateR = (fullKey, compressedKey, ...rest) => Reader.asks(({ translate, responsiveTextSize }) => responsive(responsiveTextSize, () => translate(fullKey, ...rest), () => translate(compressedKey, ...rest)));
138
+ /**
139
+ * Creates a responsive value from a translation key that has a full and a compressed version.
140
+ */
141
+ export const responsiveTranslateSplitR = (fullKey, compressedKey, ...rest) => Reader.asks(({ translate, responsiveTextSize }) => responsive(responsiveTextSize, () => translate(fullKey, ...rest.slice(0, 1)), () => translate(compressedKey, ...rest.slice(1))));
138
142
  /**
139
143
  * Creates a responsive value from a responsive text.
140
144
  */
@@ -198,3 +202,19 @@ export const mapNullableR = (defaultValue, fn) => (value) => isNotNullish(value)
198
202
  * Use `Reader` instances directly in a template string.
199
203
  */
200
204
  export const sequence = (string, ...values) => Reader.traverse(values, value => (value instanceof Reader ? value : Reader.of(value))).map(resolvedValues => string.reduce((acc, str, i) => acc + str + (resolvedValues[i] ?? ""), ""));
205
+ /**
206
+ * Formats a number according to the locale’s rules, returns a string as-is.
207
+ */
208
+ export const formatNumber = (value) => Reader.asks(env => env.formatNumber(value));
209
+ /**
210
+ * Formats an AP value in an appreviated form.
211
+ */
212
+ export const formatAPValue = (value) => formatNumber(value).thenW(formattedValue => translateR("{$value} AP", { value: formattedValue }));
213
+ /**
214
+ * Formats a number according to the locale’s rules, returns a string as-is.
215
+ */
216
+ export const formatNumberOrString = (value) => Reader.asks(env => (typeof value === "number" ? env.formatNumber(value) : value));
217
+ /**
218
+ * Formats a number in fixed-point notation, returns a string as-is.
219
+ */
220
+ export const fixedNumberOrString = (value, fractionDigits) => typeof value === "number" ? value.toFixed(fractionDigits) : value;
@@ -1,3 +1,4 @@
1
+ import { fixedNumberOrString } from "../reader.js";
1
2
  const lengthUnitTranslationKeys = {
2
3
  LifePoints: "{$value} LP",
3
4
  ArcaneEnergy: "{$value} AE",
@@ -8,5 +9,5 @@ const lengthUnitTranslationKeys = {
8
9
  */
9
10
  export const formatEnergy = (translate, unit, value) => {
10
11
  const key = lengthUnitTranslationKeys[unit];
11
- return translate(key, { value });
12
+ return translate(key, { value: fixedNumberOrString(value) });
12
13
  };
@@ -1,4 +1,5 @@
1
1
  import { Reader } from "@elyukai/utils/reader";
2
+ import { fixedNumberOrString } from "../reader.js";
2
3
  import { responsive } from "../responsiveText.js";
3
4
  const lengthUnitTranslationKeys = {
4
5
  Steps: [".input {$value :number} {{{$value} yards}}", "{$value} yards", "{$value} yd"],
@@ -9,9 +10,11 @@ const lengthUnitTranslationKeys = {
9
10
  */
10
11
  export const formatLength = (translate, responsiveTextSize, unit, value) => {
11
12
  const [fullNumberKey, fullKey, compressedKey] = lengthUnitTranslationKeys[typeof unit === "string" ? unit : unit.kind];
12
- return responsive(responsiveTextSize, () => translate(typeof value === "number" ? fullNumberKey : fullKey, {
13
- value,
14
- }), () => translate(compressedKey, { value }));
13
+ return responsive(responsiveTextSize, () => translate(typeof value === "number" ? fullNumberKey : fullKey, typeof value === "number"
14
+ ? {
15
+ value,
16
+ }
17
+ : { value }), () => translate(compressedKey, { value: fixedNumberOrString(value) }));
15
18
  };
16
19
  /**
17
20
  * Returns the text for a length unit.
@@ -35,13 +35,17 @@ export const formatTimeSpan = (translate, translateMap, format, responsiveTextSi
35
35
  }));
36
36
  }
37
37
  const [fullNumberKey, fullKey, compressedNumberKey, compressedKey] = timeSpanUnitTranslationKeys[typeof unit === "string" ? unit : unit.kind];
38
- return responsive(responsiveTextSize, () => translate(typeof value === "number" ? fullNumberKey : fullKey, {
39
- value,
40
- style: interval ? "interval" : "default",
41
- }), () => translate(typeof value === "number" ? compressedNumberKey : compressedKey, {
42
- value,
43
- style: interval ? "interval" : "default",
44
- }));
38
+ return responsive(responsiveTextSize, () => translate(typeof value === "number" ? fullNumberKey : fullKey, typeof value === "number"
39
+ ? {
40
+ value,
41
+ style: interval ? "interval" : "default",
42
+ }
43
+ : { value }), () => translate(typeof value === "number" ? compressedNumberKey : compressedKey, typeof value === "number"
44
+ ? {
45
+ value,
46
+ style: interval ? "interval" : "default",
47
+ }
48
+ : { value }));
45
49
  };
46
50
  /**
47
51
  * Returns the text for a time span unit.
@@ -6,3 +6,7 @@ export declare const MISSING_VALUE = "###MISSING###";
6
6
  * String to display when a translation that should be present produces an error.
7
7
  */
8
8
  export declare const UNHANDLED_VALUE = "###UNHANDLEDERROR###";
9
+ /**
10
+ * String to display when there is a logic error in text generation.
11
+ */
12
+ export declare const LOGIC_ERROR = "###LOGIC_ERROR###";
@@ -6,3 +6,7 @@ export const MISSING_VALUE = "###MISSING###";
6
6
  * String to display when a translation that should be present produces an error.
7
7
  */
8
8
  export const UNHANDLED_VALUE = "###UNHANDLEDERROR###";
9
+ /**
10
+ * String to display when there is a logic error in text generation.
11
+ */
12
+ export const LOGIC_ERROR = "###LOGIC_ERROR###";
@@ -379,7 +379,7 @@ export const getPoisonEntityDescription = createEntityDescriptionCreator(({ getI
379
379
  ? undefined
380
380
  : {
381
381
  label: `${translate("AP Value")} (${translate("Trade Secret")})`,
382
- value: translate("{$value} AP", { value: tradeSecret.apValue }) +
382
+ value: translate("{$value} AP", { value: tradeSecret.apValue.toFixed() }) +
383
383
  parensIf(mapNullable(tradeSecret.prerequisites, prerequisites => `${translate("Prerequisites")}: ${printPlainGeneralPrerequisites(prerequisites).run(env)}`)),
384
384
  },
385
385
  entry.source_type.kind === "AlchemicalPoison" ||