@optolith/entity-descriptions 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/lib/creator.d.ts +2 -2
  3. package/lib/entities/activatable.js +35 -64
  4. package/lib/entities/condition.js +19 -24
  5. package/lib/entities/culture.js +18 -26
  6. package/lib/entities/curriculum.js +30 -33
  7. package/lib/entities/derivedCharacteristic.js +2 -1
  8. package/lib/entities/disease.js +3 -6
  9. package/lib/entities/elixir.js +6 -4
  10. package/lib/entities/equipment.js +17 -17
  11. package/lib/entities/equipmentPackage.js +4 -3
  12. package/lib/entities/experienceLevel.js +7 -7
  13. package/lib/entities/focusRule.js +2 -2
  14. package/lib/entities/influence.js +2 -2
  15. package/lib/entities/liturgicalChant.d.ts +1 -1
  16. package/lib/entities/liturgicalChant.js +11 -59
  17. package/lib/entities/partial/adventurePointsValue.js +14 -19
  18. package/lib/entities/partial/enhancements.d.ts +1 -1
  19. package/lib/entities/partial/enhancements.js +1 -1
  20. package/lib/entities/partial/herbary.js +13 -9
  21. package/lib/entities/partial/map.d.ts +3 -1
  22. package/lib/entities/partial/map.js +21 -8
  23. package/lib/entities/partial/mathOperation.js +12 -17
  24. package/lib/entities/partial/prerequisites/displayOption.d.ts +2 -2
  25. package/lib/entities/partial/prerequisites/index.d.ts +3 -3
  26. package/lib/entities/partial/prerequisites/index.js +10 -4
  27. package/lib/entities/partial/prerequisites/part.d.ts +1 -1
  28. package/lib/entities/partial/prerequisites/prerequisiteGroups.d.ts +4 -4
  29. package/lib/entities/partial/prerequisites/prerequisiteGroups.js +1 -1
  30. package/lib/entities/partial/prerequisites/single/activatable.d.ts +3 -3
  31. package/lib/entities/partial/prerequisites/single/animistPower.d.ts +2 -2
  32. package/lib/entities/partial/prerequisites/single/animistPower.js +1 -1
  33. package/lib/entities/partial/prerequisites/single/blessedTradition.d.ts +2 -2
  34. package/lib/entities/partial/prerequisites/single/commonSuggestedByRCP.d.ts +2 -2
  35. package/lib/entities/partial/prerequisites/single/culture.d.ts +4 -4
  36. package/lib/entities/partial/prerequisites/single/enhancement.d.ts +4 -4
  37. package/lib/entities/partial/prerequisites/single/influence.d.ts +3 -3
  38. package/lib/entities/partial/prerequisites/single/magicalTradition.d.ts +2 -2
  39. package/lib/entities/partial/prerequisites/single/noOtherAncestorBloodAdvantage.d.ts +2 -2
  40. package/lib/entities/partial/prerequisites/single/pact.d.ts +3 -3
  41. package/lib/entities/partial/prerequisites/single/personalityTrait.d.ts +4 -4
  42. package/lib/entities/partial/prerequisites/single/primaryAttribute.d.ts +3 -3
  43. package/lib/entities/partial/prerequisites/single/primaryAttribute.js +1 -1
  44. package/lib/entities/partial/prerequisites/single/profession.d.ts +4 -4
  45. package/lib/entities/partial/prerequisites/single/publication.d.ts +4 -4
  46. package/lib/entities/partial/prerequisites/single/race.d.ts +4 -4
  47. package/lib/entities/partial/prerequisites/single/rated.d.ts +2 -2
  48. package/lib/entities/partial/prerequisites/single/rated.js +2 -2
  49. package/lib/entities/partial/prerequisites/single/ratedMinimumNumber.d.ts +3 -3
  50. package/lib/entities/partial/prerequisites/single/ratedMinimumNumber.js +1 -1
  51. package/lib/entities/partial/prerequisites/single/ratedSum.d.ts +4 -4
  52. package/lib/entities/partial/prerequisites/single/rule.d.ts +3 -3
  53. package/lib/entities/partial/prerequisites/single/sex.d.ts +1 -1
  54. package/lib/entities/partial/prerequisites/single/sexualCharacteristic.d.ts +3 -3
  55. package/lib/entities/partial/prerequisites/single/socialStatus.d.ts +3 -3
  56. package/lib/entities/partial/prerequisites/single/state.d.ts +4 -4
  57. package/lib/entities/partial/prerequisites/single/text.d.ts +3 -3
  58. package/lib/entities/partial/rated/activatable/castingTime.d.ts +1 -1
  59. package/lib/entities/partial/rated/activatable/castingTime.js +6 -8
  60. package/lib/entities/partial/rated/activatable/cost.d.ts +1 -1
  61. package/lib/entities/partial/rated/activatable/cost.js +17 -14
  62. package/lib/entities/partial/rated/activatable/duration.js +4 -4
  63. package/lib/entities/partial/rated/activatable/effect.js +1 -1
  64. package/lib/entities/partial/rated/activatable/index.d.ts +6 -1
  65. package/lib/entities/partial/rated/activatable/index.js +25 -0
  66. package/lib/entities/partial/rated/activatable/isMinimumMaximum.js +2 -2
  67. package/lib/entities/partial/rated/activatable/range.js +6 -6
  68. package/lib/entities/partial/rated/activatable/speed.d.ts +1 -1
  69. package/lib/entities/partial/rated/activatable/targetCategory.js +2 -2
  70. package/lib/entities/partial/reader.d.ts +4 -2
  71. package/lib/entities/partial/responsiveText.d.ts +5 -1
  72. package/lib/entities/partial/responsiveText.js +3 -5
  73. package/lib/entities/partial/units/length.d.ts +1 -1
  74. package/lib/entities/partial/units/length.js +2 -10
  75. package/lib/entities/partial/units/timeSpan.d.ts +4 -4
  76. package/lib/entities/partial/units/timeSpan.js +21 -17
  77. package/lib/entities/personalityTrait.js +3 -4
  78. package/lib/entities/poison.js +4 -4
  79. package/lib/entities/profession.d.ts +1 -1
  80. package/lib/entities/profession.js +19 -14
  81. package/lib/entities/race.js +11 -13
  82. package/lib/entities/sexPractice.js +1 -3
  83. package/lib/entities/skill.js +2 -3
  84. package/lib/entities/spell.js +2 -14
  85. package/lib/helpers/getTypes.d.ts +2 -2
  86. package/lib/helpers/locale.d.ts +7 -2
  87. package/lib/helpers/translate.d.ts +1 -1
  88. package/lib/index.d.ts +3 -1
  89. package/lib/index.js +1 -0
  90. package/lib/references/index.d.ts +1 -1
  91. package/lib/references/index.js +1 -1
  92. package/lib/references/page.d.ts +3 -3
  93. package/lib/references/page.js +1 -1
  94. package/lib/references/pageRange.d.ts +2 -2
  95. package/lib/tsconfig.tsbuildinfo +1 -1
  96. package/package.json +4 -3
@@ -1,7 +1,7 @@
1
1
  import type { SocialStatusPrerequisite } from "optolith-database-schema/gen";
2
- import { type GetInstanceById } from "../../../../helpers/getTypes.js";
3
- import { LocaleEnvironment } from "../../../../helpers/locale.js";
4
- import { PrerequisitePart } from "../part.js";
2
+ import type { GetInstanceById } from "../../../../helpers/getTypes.js";
3
+ import type { LocaleEnvironment } from "../../../../helpers/locale.js";
4
+ import type { PrerequisitePart } from "../part.js";
5
5
  /**
6
6
  * Get the translation of a social status prerequisite.
7
7
  */
@@ -1,7 +1,7 @@
1
- import { StatePrerequisite } from "optolith-database-schema/gen";
2
- import { type GetInstanceById } from "../../../../helpers/getTypes.js";
3
- import { LocaleEnvironment } from "../../../../helpers/locale.js";
4
- import { PrerequisitePart } from "../part.js";
1
+ import type { StatePrerequisite } from "optolith-database-schema/gen";
2
+ import type { GetInstanceById } from "../../../../helpers/getTypes.js";
3
+ import type { LocaleEnvironment } from "../../../../helpers/locale.js";
4
+ import type { PrerequisitePart } from "../part.js";
5
5
  /**
6
6
  * Get the translation of a state prerequisite.
7
7
  */
@@ -1,6 +1,6 @@
1
- import { TextPrerequisite } from "optolith-database-schema/gen";
2
- import { LocaleEnvironment } from "../../../../helpers/locale.js";
3
- import { PrerequisitePart } from "../part.js";
1
+ import type { TextPrerequisite } from "optolith-database-schema/gen";
2
+ import type { LocaleEnvironment } from "../../../../helpers/locale.js";
3
+ import type { PrerequisitePart } from "../part.js";
4
4
  /**
5
5
  * Get the translation of a text prerequisite.
6
6
  */
@@ -1,4 +1,4 @@
1
- import { CastingTime, FastCastingTime, FastSkillNonModifiableCastingTime, SlowCastingTime, SlowSkillNonModifiableCastingTime } from "optolith-database-schema/gen";
1
+ import type { CastingTime, FastCastingTime, FastSkillNonModifiableCastingTime, SlowCastingTime, SlowSkillNonModifiableCastingTime } from "optolith-database-schema/gen";
2
2
  import { type StdReader } from "../../reader.js";
3
3
  /**
4
4
  * Renders the text for a non-modifiable casting time of a fast activatable skill.
@@ -2,17 +2,15 @@ import { Reader } from "@elyukai/utils/reader";
2
2
  import { isNotNullish, mapNullable } from "@optolith/helpers/nullable";
3
3
  import { assertExhaustive } from "@optolith/helpers/typeSafety";
4
4
  import { Case } from "../../../../helpers/enums.js";
5
- import { getInstanceByIdFnR, modifiableBySpeedR, } from "../../reader.js";
6
- import { formatCombinedTimeSpanR, formatTimeSpanR, } from "../../units/timeSpan.js";
5
+ import { getInstanceByIdFnR, modifiableBySpeedR } from "../../reader.js";
6
+ import { formatCombinedTimeSpanR, formatTimeSpanR } from "../../units/timeSpan.js";
7
7
  import { MISSING_VALUE } from "../../unknown.js";
8
- import { appendNonModifiableSuffix, ModifiableParameter, } from "./nonModifiableSuffix.js";
8
+ import { appendNonModifiableSuffix, ModifiableParameter } from "./nonModifiableSuffix.js";
9
9
  import { Speed } from "./speed.js";
10
10
  const deriveModifiableCastingTime = (modificationLevelId) => getInstanceByIdFnR().thenW(getInstanceById => mapNullable(getInstanceById("SkillModificationLevel", modificationLevelId), modificationLevel => modifiableBySpeedR("casting_time", modificationLevel).map(castingTime => typeof castingTime === "number"
11
11
  ? { value: castingTime, unit: Case("Actions") }
12
12
  : castingTime)) ?? Reader.of(undefined));
13
- const renderModifiableCastingTime = (value) => deriveModifiableCastingTime(value.initial_modification_level).thenW(castingTime => castingTime === undefined
14
- ? Reader.of(MISSING_VALUE)
15
- : formatCombinedTimeSpanR(castingTime));
13
+ const renderModifiableCastingTime = (value) => deriveModifiableCastingTime(value.initial_modification_level).thenW(castingTime => castingTime === undefined ? Reader.of(MISSING_VALUE) : formatCombinedTimeSpanR(castingTime));
16
14
  const renderCastingTimeDuringLovemaking = (value) => formatCombinedTimeSpanR(value);
17
15
  /**
18
16
  * Renders the text for a non-modifiable casting time of a fast activatable skill.
@@ -36,9 +34,9 @@ export const renderCastingTime = (renderNonModifiableCastingTime, value) => {
36
34
  }
37
35
  };
38
36
  const renderCastingTimeIncludingLovemaking = (renderNonModifiableCastingTime, value) => Reader.sequence([
39
- mapNullable(value.default, def => renderCastingTime(renderNonModifiableCastingTime, def)) ?? Reader.of(undefined),
40
- mapNullable(value.during_lovemaking, renderCastingTimeDuringLovemaking) ??
37
+ mapNullable(value.default, def => renderCastingTime(renderNonModifiableCastingTime, def)) ??
41
38
  Reader.of(undefined),
39
+ mapNullable(value.during_lovemaking, renderCastingTimeDuringLovemaking) ?? Reader.of(undefined),
42
40
  ]).map(texts => texts.filter(isNotNullish).join(" / "));
43
41
  /**
44
42
  * Get the text for the casting time of a fast activatable skill.
@@ -1,5 +1,5 @@
1
1
  import { Reader } from "@elyukai/utils/reader";
2
- import { NonModifiableSustainedCost, OneTimeCost, SustainedCost, type CheckResultBasedModifier, type DurationUnitValue, type ElvenMagicalSongPermanentCost, type FirstPersonMagicalMelodyCost, type NonModifiableOneTimeCostPerCountable, type OneTimeCostMap, type ResponsiveText, type ResponsiveTextOptional, type ResponsiveTextReplace, type SkillModificationLevel_ID } from "optolith-database-schema/gen";
2
+ import type { CheckResultBasedModifier, DurationUnitValue, ElvenMagicalSongPermanentCost, FirstPersonMagicalMelodyCost, NonModifiableOneTimeCostPerCountable, NonModifiableSustainedCost, OneTimeCost, OneTimeCostMap, ResponsiveText, ResponsiveTextOptional, ResponsiveTextReplace, SkillModificationLevel_ID, SustainedCost } from "optolith-database-schema/gen";
3
3
  import { type LocaleMap } from "../../../../helpers/translate.js";
4
4
  import { type StdEnv, type StdReader } from "../../reader.js";
5
5
  /**
@@ -3,14 +3,13 @@ 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 { renderResponsiveMap } from "../../map.js";
6
- import { additionFormatter } from "../../mathOperation.js";
7
6
  import { formatEnergyFnR, formatEnergyR, getInstanceByIdFnR, modifiableBySpeedR, responsiveLocaleJoinR, responsiveR, responsiveTextR, responsiveTranslateR, translateMapR, translateR, } from "../../reader.js";
8
- import { appendNoteIfNeeded, replaceTextIfNeeded, } from "../../responsiveText.js";
7
+ import { appendNoteIfNeeded, replaceTextIfNeeded } from "../../responsiveText.js";
9
8
  import { formatCombinedTimeSpanR } from "../../units/timeSpan.js";
10
9
  import { MISSING_VALUE } from "../../unknown.js";
11
10
  import { appendCheckResultModifier } from "./checkResultBased.js";
12
11
  import { wrapIfMinimum } from "./isMinimumMaximum.js";
13
- import { appendNonModifiableSuffix, ModifiableParameter, } from "./nonModifiableSuffix.js";
12
+ import { appendNonModifiableSuffix, ModifiableParameter } from "./nonModifiableSuffix.js";
14
13
  const deriveModifiableCost = (modificationLevelId) => getInstanceByIdFnR().thenW(getInstanceById => mapNullable(getInstanceById("SkillModificationLevel", modificationLevelId), modificationLevel => modifiableBySpeedR("cost", modificationLevel)) ?? Reader.of(undefined));
15
14
  const appendPerCountableToCostIfNeeded = (value, baseCost) => {
16
15
  if (value === undefined) {
@@ -22,7 +21,10 @@ const appendPerCountableToCostIfNeeded = (value, baseCost) => {
22
21
  return Reader.of(baseCost);
23
22
  }
24
23
  return responsiveTextR(translation.countable)
25
- .thenW(countable => responsiveTranslateR("{$cost} per {$countable}", "{$cost}/{$countable}", { cost: baseCost, countable }))
24
+ .thenW(countable => responsiveTranslateR("{$cost} per {$countable}", "{$cost}/{$countable}", {
25
+ cost: baseCost,
26
+ countable,
27
+ }))
26
28
  .thenW(text => minimum_total === undefined
27
29
  ? Reader.of(text)
28
30
  : formatEnergyR(minimum_total).then(cost => translateR("{$baseCost}, minimum of {$value}", {
@@ -32,7 +34,9 @@ const appendPerCountableToCostIfNeeded = (value, baseCost) => {
32
34
  });
33
35
  };
34
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);
35
- const appendElvenPermanentCostIfNeeded = (permanent, baseCost) => mapNullable(permanent, value => responsiveTranslateR(".input {$value :number} {{{$value} permanent AE}}", "{$value} pAE", { value: value.value })
37
+ const appendElvenPermanentCostIfNeeded = (permanent, baseCost) => mapNullable(permanent, value => responsiveTranslateR(".input {$value :number} {{{$value} permanent AE}}", "{$value} pAE", {
38
+ value: value.value,
39
+ })
36
40
  .thenW(text => replaceTextIfNeeded(value.translations, text))
37
41
  .map(text => baseCost + text)) ?? Reader.of(baseCost);
38
42
  const appendFamiliarsTrickLPCostIfNeeded = (lpValue, baseCost) => lpValue === undefined
@@ -51,11 +55,11 @@ export const renderModifiableOneTimeCost = (value) => deriveModifiableCost(value
51
55
  if (translation?.additional === undefined) {
52
56
  return Reader.of(text);
53
57
  }
54
- return responsiveTextR(translation.additional).map(additional => additionFormatter(text, additional));
58
+ return responsiveTextR(translation.additional).map(additional => `${text} + ${additional}`);
55
59
  })));
56
60
  const appendIntervalToCost = (interval, baseCost) => interval === undefined
57
61
  ? Reader.of(baseCost)
58
- : formatCombinedTimeSpanR(interval).then(formattedInterval => responsiveTranslateR("{$cost} per {$interval}", "{$cost}/{$interval}", {
62
+ : formatCombinedTimeSpanR(interval, true).then(formattedInterval => responsiveTranslateR("{$cost} per {$interval}", "{$cost}/{$interval}", {
59
63
  cost: baseCost,
60
64
  interval: formattedInterval,
61
65
  }));
@@ -72,7 +76,7 @@ export const renderNonModifiableOneTimeCost = (value, shouldAppendNonModifiableS
72
76
  .then(base => appendNoteIfNeeded(value.translations, base))
73
77
  .then(shouldAppendNonModifiableSuffix
74
78
  ? base => appendNonModifiableSuffix(ModifiableParameter.Cost, base)
75
- : Reader.of);
79
+ : base => Reader.of(base));
76
80
  /**
77
81
  * Returns the text for the indefinite cost of an activatable skill.
78
82
  */
@@ -82,12 +86,10 @@ export const renderIndefiniteCost = (value, shouldAppendNonModifiableSuffix) =>
82
86
  .thenW(translation => translation === undefined
83
87
  ? Reader.of(MISSING_VALUE)
84
88
  : responsiveTextR(translation.description))
85
- .map(modifier === undefined
86
- ? identity
87
- : base => appendCheckResultModifier(base, modifier))
89
+ .map(modifier === undefined ? identity : base => appendCheckResultModifier(base, modifier))
88
90
  .thenW(shouldAppendNonModifiableSuffix
89
91
  ? base => appendNonModifiableSuffix(ModifiableParameter.Cost, base)
90
- : Reader.of);
92
+ : base => Reader.of(base));
91
93
  };
92
94
  const renderSingleOneTimeCost = (value) => {
93
95
  switch (value.kind) {
@@ -104,7 +106,7 @@ const renderSingleOneTimeCost = (value) => {
104
106
  const renderMultipleOneTimeCosts = (type, value) => {
105
107
  const appendNonModifiableIfRequested = !value.every(part => part.kind === "Modifiable")
106
108
  ? (text) => appendNonModifiableSuffix(ModifiableParameter.Cost, text)
107
- : Reader.of;
109
+ : (text) => Reader.of(text);
108
110
  return Reader.sequence(value.map(renderSingleOneTimeCost))
109
111
  .thenW(list => responsiveLocaleJoinR(list, type))
110
112
  .then(text => appendNonModifiableIfRequested(text));
@@ -118,6 +120,7 @@ export const renderOneTimeCostMap = (value) => Reader.asks(({ translate }) => tr
118
120
  surround: values => translate(", {$value} of which are permanent", {
119
121
  value: values,
120
122
  }),
123
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- is checked beforehand
121
124
  getAdditionalValue: option => option.permanent_value,
122
125
  }
123
126
  : undefined))
@@ -145,7 +148,7 @@ export const renderOneTimeCost = (value) => {
145
148
  const buildSustainedCost = (activationCost, intervalCost, interval) => {
146
149
  const activationCostWithLabel = responsiveR(() => translateR("activation").map(label => `${activationCost} (${label})`), () => Reader.of(activationCost)).thenW(identity);
147
150
  const intervalCostWithLabel = appendIntervalToCost(interval, intervalCost);
148
- return activationCostWithLabel.map2(intervalCostWithLabel, additionFormatter);
151
+ return activationCostWithLabel.map2(intervalCostWithLabel, (activationStr, intervalStr) => `${activationStr} + ${intervalStr}`);
149
152
  };
150
153
  const renderModifiableSustainedCost = (value) => deriveModifiableCost(value.initial_modification_level).thenW(cost => cost === undefined
151
154
  ? Reader.of(MISSING_VALUE)
@@ -3,7 +3,7 @@ import { mapNullable } from "@optolith/helpers/nullable";
3
3
  import { assertExhaustive } from "@optolith/helpers/typeSafety";
4
4
  import { responsiveTextR, responsiveTranslateR, translateMapR, translateR, } from "../../reader.js";
5
5
  import { replaceTextIfNeeded } from "../../responsiveText.js";
6
- import { formatCombinedTimeSpanR, formatTimeSpanR, } from "../../units/timeSpan.js";
6
+ import { formatCombinedTimeSpanR, formatTimeSpanR } from "../../units/timeSpan.js";
7
7
  import { MISSING_VALUE } from "../../unknown.js";
8
8
  import { renderCheckResultBasedValue } from "./checkResultBased.js";
9
9
  import { wrapAsMaximum, wrapIfMaximum } from "./isMinimumMaximum.js";
@@ -15,8 +15,8 @@ const renderImmediateDuration = (value) => translateR("Immediate")
15
15
  .thenW(text => replaceTextIfNeeded(value?.translations, text));
16
16
  const renderPermanentDuration = (value) => translateR("Permanent").thenW(text => replaceTextIfNeeded(value.translations, text));
17
17
  const renderFixedDuration = (value) => formatCombinedTimeSpanR(value)
18
- .then(text => wrapIfMaximum(value.is_maximum, text))
19
- .thenW(text => replaceTextIfNeeded(value.translations, text));
18
+ .then(text => wrapIfMaximum(value.is_maximum, text).map(wrapped => [wrapped, text]))
19
+ .thenW(([wrapped, text]) => replaceTextIfNeeded(value.translations, wrapped, text));
20
20
  /**
21
21
  * Renders a duration that is based on the result of the skill check.
22
22
  */
@@ -30,7 +30,7 @@ const renderIndefiniteDuration = (value) => {
30
30
  ? responsiveTextR(translation.description)
31
31
  : Reader.of(translation?.description ?? MISSING_VALUE))
32
32
  .thenW(maximum === undefined
33
- ? Reader.of
33
+ ? text => Reader.of(text)
34
34
  : text =>
35
35
  // eslint-disable-next-line @typescript-eslint/no-use-before-define
36
36
  renderOneTimeDuration(maximum).then(maximumText => translateR("{$defaultDuration}, but no more than {$maximumDuration}", {
@@ -37,7 +37,7 @@ export const renderEffect = (effect) => {
37
37
  case "ForEachQualityLevel":
38
38
  return getContentPartsForQualityLevels(index => index + 1, effect.ForEachQualityLevel);
39
39
  case "ForEachTwoQualityLevels":
40
- return getContentPartsForQualityLevels(index => `${index * 2 + 1}–${index * 2 + 2}`, effect.ForEachTwoQualityLevels);
40
+ return getContentPartsForQualityLevels(index => `${(index * 2 + 1).toFixed()}–${(index * 2 + 2).toFixed()}`, effect.ForEachTwoQualityLevels);
41
41
  default:
42
42
  return assertExhaustive(effect);
43
43
  }
@@ -1,4 +1,5 @@
1
- import type { FastOneTimePerformanceParameters, FastSustainedPerformanceParameters, OneTimePerformanceParameters, SlowOneTimePerformanceParameters, SlowSustainedPerformanceParameters } from "optolith-database-schema/gen";
1
+ import type { FastOneTimePerformanceParameters, FastSustainedPerformanceParameters, OneTimePerformanceParameters, ResponsiveTextOptional, SlowOneTimePerformanceParameters, SlowSustainedPerformanceParameters } from "optolith-database-schema/gen";
2
+ import type { RawDefinitionListEntityDescriptionSectionItem } from "../../../../index.js";
2
3
  import type { StdReader } from "../../reader.js";
3
4
  type RenderedPerformanceParameters = {
4
5
  castingTime: string;
@@ -42,4 +43,8 @@ export declare const renderSlowPerformanceParameters: (value: {
42
43
  kind: "Sustained";
43
44
  Sustained: SlowSustainedPerformanceParameters;
44
45
  }) => StdReader<RenderedPerformanceParameters, "t" | "tm" | "lj" | "eu" | "rts" | "nms" | "ibi", "SkillModificationLevel">;
46
+ /**
47
+ * Diff the generated text with the static translation and combine them into a single text, highlighting the differences.
48
+ */
49
+ export declare const combineGeneratedTextWithStaticTranslation: (label: string, generatedText: string | undefined, staticText: ResponsiveTextOptional | string | undefined) => RawDefinitionListEntityDescriptionSectionItem | undefined;
45
50
  export {};
@@ -1,4 +1,5 @@
1
1
  import { assertExhaustive } from "@elyukai/utils/typeSafety";
2
+ import { diffWordsWithSpace } from "diff";
2
3
  import { renderFastCastingTime, renderSlowCastingTime } from "./castingTime.js";
3
4
  import { renderOneTimeCost, renderSustainedCost } from "./cost.js";
4
5
  import { renderOneTimeDuration, renderSustainedDuration } from "./duration.js";
@@ -74,3 +75,27 @@ export const renderSlowPerformanceParameters = (value) => {
74
75
  return assertExhaustive(value);
75
76
  }
76
77
  };
78
+ /**
79
+ * Diff the generated text with the static translation and combine them into a single text, highlighting the differences.
80
+ */
81
+ export const combineGeneratedTextWithStaticTranslation = (label, generatedText, staticText) => {
82
+ if (generatedText === undefined) {
83
+ return undefined;
84
+ }
85
+ const normalizedStaticText = typeof staticText === "string" ? staticText : staticText?.full;
86
+ const diff = normalizedStaticText === undefined
87
+ ? undefined
88
+ : diffWordsWithSpace(normalizedStaticText, generatedText);
89
+ return {
90
+ label,
91
+ value: diff === undefined
92
+ ? generatedText
93
+ : diff
94
+ .map(part => part.added
95
+ ? `<ins>${part.value}</ins>`
96
+ : part.removed
97
+ ? `<del>${part.value}</del>`
98
+ : part.value)
99
+ .join(""),
100
+ };
101
+ };
@@ -8,7 +8,7 @@ export const wrapAsMinimum = (text) => responsiveTranslateR("at least {$value}",
8
8
  * Wraps the text in a translation that indicates it’s a minimum value if the
9
9
  * `is_minimum` property says it’s a minimum value.
10
10
  */
11
- export const wrapIfMinimum = (is_minimum, text) => is_minimum === true ? wrapAsMinimum(text) : Reader.of("");
11
+ export const wrapIfMinimum = (is_minimum, text) => (is_minimum === true ? wrapAsMinimum(text) : Reader.of(text));
12
12
  /**
13
13
  * Wraps the text in a translation that indicates it’s a maximum value.
14
14
  */
@@ -19,4 +19,4 @@ export const wrapAsMaximum = (text) => responsiveTranslateR("no more than {$valu
19
19
  * Wraps the text in a translation that indicates it’s a maximum value if the
20
20
  * `is_maximum` property says it’s a maximum value.
21
21
  */
22
- export const wrapIfMaximum = (is_maximum, text) => is_maximum === true ? wrapAsMaximum(text) : Reader.of("");
22
+ export const wrapIfMaximum = (is_maximum, text) => (is_maximum === true ? wrapAsMaximum(text) : Reader.of(text));
@@ -2,24 +2,24 @@ import { Reader } from "@elyukai/utils/reader";
2
2
  import { mapNullable } from "@optolith/helpers/nullable";
3
3
  import { assertExhaustive } from "@optolith/helpers/typeSafety";
4
4
  import { getInstanceByIdFnR, modifiableBySpeedOptionalR, modifiableBySpeedR, translateFnR, translateMapR, translateR, } from "../../reader.js";
5
- import { appendNoteIfNeeded, replaceTextIfNeeded, } from "../../responsiveText.js";
5
+ import { appendNoteIfNeeded, replaceTextIfNeeded } from "../../responsiveText.js";
6
6
  import { formatCombinedLengthR, formatLengthR } from "../../units/length.js";
7
7
  import { MISSING_VALUE } from "../../unknown.js";
8
8
  import { renderCheckResultBasedValue } from "./checkResultBased.js";
9
9
  import { wrapIfMaximum } from "./isMinimumMaximum.js";
10
- import { appendNonModifiableSuffix, ModifiableParameter, } from "./nonModifiableSuffix.js";
10
+ import { appendNonModifiableSuffix, ModifiableParameter } from "./nonModifiableSuffix.js";
11
11
  const deriveModifiableRange = (modificationLevelId) => getInstanceByIdFnR().thenW(getInstanceById => mapNullable(getInstanceById("SkillModificationLevel", modificationLevelId), modificationLevel => modifiableBySpeedR("range", modificationLevel).thenW(value => translateMapR(modificationLevel.translations).thenW(translation => translation === undefined
12
12
  ? Reader.of({ value, translation: undefined })
13
13
  : modifiableBySpeedOptionalR("range", translation).map(valueTranslation => ({
14
14
  value,
15
15
  translation: valueTranslation,
16
16
  }))))) ?? Reader.of(undefined));
17
- const wrapIfRadius = (is_radius, text) => translateFnR.map(translate => is_radius === true ? `${text} ${translate("Radius")}` : text);
17
+ const wrapIfRadius = (is_radius, text) => translateFnR.map(translate => (is_radius === true ? `${text} ${translate("Radius")}` : text));
18
18
  const renderModifiableRange = (value) => deriveModifiableRange(value.initial_modification_level)
19
19
  .thenW(range => range === undefined
20
20
  ? Reader.of(MISSING_VALUE)
21
- : typeof range === "string"
22
- ? Reader.of(range)
21
+ : range.translation !== undefined
22
+ ? Reader.of(range.translation)
23
23
  : formatLengthR("Steps", range.value))
24
24
  .then(text => wrapIfRadius(value.is_radius, text))
25
25
  .then(text => wrapIfMaximum(value.is_maximum, text));
@@ -36,7 +36,7 @@ const getCheckResultBasedRangeTranslation = (value) => renderCheckResultBasedVal
36
36
  export const renderNonModifiableRange = (value, shouldAppendNonModifiableSuffix) => {
37
37
  const appendNonModifiableSuffixIfNeeded = shouldAppendNonModifiableSuffix
38
38
  ? (text) => appendNonModifiableSuffix(ModifiableParameter.Range, text)
39
- : Reader.of;
39
+ : (text) => Reader.of(text);
40
40
  switch (value.kind) {
41
41
  case "Sight":
42
42
  return translateR("Sight");
@@ -1,4 +1,4 @@
1
- import { FastSkillModificationLevelConfig, SkillModificationLevel, SlowSkillModificationLevelConfig } from "optolith-database-schema/gen";
1
+ import type { FastSkillModificationLevelConfig, SkillModificationLevel, SlowSkillModificationLevelConfig } from "optolith-database-schema/gen";
2
2
  /**
3
3
  * The speed of an activatable skill.
4
4
  */
@@ -1,7 +1,7 @@
1
1
  import { Reader } from "@elyukai/utils/reader";
2
2
  import { mapNullable } from "@optolith/helpers/nullable";
3
3
  import { assertExhaustive } from "@optolith/helpers/typeSafety";
4
- import { getInstanceByIdFnR, translateMapR, translateR, } from "../../reader.js";
4
+ import { getInstanceByIdFnR, translateMapR, translateR } from "../../reader.js";
5
5
  import { MISSING_VALUE } from "../../unknown.js";
6
6
  import { appendInParensIfNotEmpty } from "./parensIf.js";
7
7
  const renderPredefined = (targetCategoryId) => getInstanceByIdFnR()
@@ -32,4 +32,4 @@ export const renderTargetCategory = (values) => translateR("Target Category").th
32
32
  ? translateR("all")
33
33
  : Reader.sequence(values.map(({ id, translations }) => getTargetCategoryTranslationByType(id).then(text => translateMapR(translations)
34
34
  .map(translation => translation?.note)
35
- .map(note => appendInParensIfNotEmpty(note, text)) ?? text))).map(texts => texts.join(", "))).map(value => ({ label, value })));
35
+ .map(note => appendInParensIfNotEmpty(note, text))))).map(texts => texts.join(", "))).map(value => ({ label, value })));
@@ -2,7 +2,7 @@ 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
4
  import type { GetAllChildInstancesForParent, GetAllInstances, GetInstanceById } from "../../helpers/getTypes.js";
5
- import type { LocaleCompare, LocaleJoin, LocaleJoinType } from "../../helpers/locale.js";
5
+ import type { FormatNumber, LocaleCompare, LocaleJoin, LocaleJoinType } from "../../helpers/locale.js";
6
6
  import type { Format, LocaleMap, Translate, TranslateMap, TranslationKeyMatchingParamsOfKey, TranslationKeysWithoutParams, TranslationParamsInArray, Translations } from "../../helpers/translate.js";
7
7
  import type { GetResolvedSelectOptionById } from "./prerequisites/single/activatable.js";
8
8
  import type { ModifiableParameter } from "./rated/activatable/nonModifiableSuffix.js";
@@ -14,6 +14,7 @@ import { type EnergyUnit } from "./units/energy.js";
14
14
  */
15
15
  export type EnvMap<E extends keyof EntityMap = never, AE extends keyof EntityMap = never, CE extends keyof ChildEntityMap = never> = {
16
16
  format: Format;
17
+ formatNumber: FormatNumber;
17
18
  translate: Translate;
18
19
  translateMap: TranslateMap;
19
20
  localeJoin: LocaleJoin;
@@ -32,6 +33,7 @@ export type EnvMap<E extends keyof EntityMap = never, AE extends keyof EntityMap
32
33
  */
33
34
  export type EnvMapAbbr = {
34
35
  f: "format";
36
+ fn: "formatNumber";
35
37
  t: "translate";
36
38
  tm: "translateMap";
37
39
  lj: "localeJoin";
@@ -60,7 +62,7 @@ export type StdReader<T, K extends keyof EnvMapAbbr, E extends keyof EntityMap =
60
62
  /**
61
63
  * Treats the text as a translation string and applies supplied arguments.
62
64
  */
63
- export declare const formatR: (text: string, args?: Record<string, unknown> | undefined) => Reader<{
65
+ export declare const formatR: (text: string, args?: Record<string, unknown>) => Reader<{
64
66
  format: Format;
65
67
  }, string>;
66
68
  /**
@@ -29,7 +29,11 @@ export declare const getResponsiveTextOptional: (value: ResponsiveTextOptional |
29
29
  */
30
30
  export declare const replaceTextIfNeeded: (translations: LocaleMap<{
31
31
  replacement?: ResponsiveTextReplace;
32
- }> | undefined, valueToReplace: string) => Reader<StdEnv<"rts" | "tm">, string>;
32
+ }> | undefined, valueToReplace: string, differentValueToInsert?: string) => Reader<{
33
+ translateMap: import("../../helpers/translate.js").TranslateMap;
34
+ } & {
35
+ responsiveTextSize: ResponsiveTextSize;
36
+ }, string>;
33
37
  /**
34
38
  * Appends a note to a given value if a note is requested, otherwise just return
35
39
  * the given value.
@@ -2,7 +2,7 @@ import { identity } from "@elyukai/utils/function";
2
2
  import { Reader } from "@elyukai/utils/reader";
3
3
  import { assertExhaustive } from "@optolith/helpers/typeSafety";
4
4
  import { appendInParensIfNotEmpty } from "./rated/activatable/parensIf.js";
5
- import { responsiveTextOptionalR, responsiveTextR, translateMapR, } from "./reader.js";
5
+ import { responsiveTextOptionalR, responsiveTextR, translateMapR } from "./reader.js";
6
6
  import { MISSING_VALUE } from "./unknown.js";
7
7
  /**
8
8
  * Whether the entry is displayed in a normal or compressed setting. Normal/full
@@ -49,13 +49,11 @@ export const getResponsiveTextOptional = (value, size) => {
49
49
  * Replaces a text with a given value if a replacement is requested, otherwise
50
50
  * just return the given value.
51
51
  */
52
- export const replaceTextIfNeeded = (translations, valueToReplace) => translateMapR(translations)
53
- .with(identity)
54
- .then(translation => {
52
+ export const replaceTextIfNeeded = (translations, valueToReplace, differentValueToInsert = valueToReplace) => translateMapR(translations).thenW(translation => {
55
53
  if (translation?.replacement === undefined) {
56
54
  return Reader.of(valueToReplace);
57
55
  }
58
- return responsiveTextR(translation.replacement).map(note => note.replace("$1", valueToReplace));
56
+ return responsiveTextR(translation.replacement).map(note => note.replace("$1", differentValueToInsert));
59
57
  });
60
58
  /**
61
59
  * Appends a note to a given value if a note is requested, otherwise just return
@@ -1,6 +1,6 @@
1
1
  import type { Translate } from "../../../helpers/translate.js";
2
2
  import type { StdReader } from "../reader.js";
3
- import { ResponsiveTextSize } from "../responsiveText.js";
3
+ import { type ResponsiveTextSize } from "../responsiveText.js";
4
4
  type LengthUnit = "Steps" | "Miles";
5
5
  /**
6
6
  * Returns the text for a length unit.
@@ -1,16 +1,8 @@
1
1
  import { Reader } from "@elyukai/utils/reader";
2
2
  import { responsive } from "../responsiveText.js";
3
3
  const lengthUnitTranslationKeys = {
4
- Steps: [
5
- ".input {$value :number} {{{$value} yards}}",
6
- "{$value} yards",
7
- "{$value} yd",
8
- ],
9
- Miles: [
10
- ".input {$value :number} {{{$value} miles}}",
11
- "{$value} miles",
12
- "{$value} mi.",
13
- ],
4
+ Steps: [".input {$value :number} {{{$value} yards}}", "{$value} yards", "{$value} yd"],
5
+ Miles: [".input {$value :number} {{{$value} miles}}", "{$value} miles", "{$value} mi."],
14
6
  };
15
7
  /**
16
8
  * Returns the text for a length unit.
@@ -1,7 +1,7 @@
1
1
  import { Reader } from "@elyukai/utils/reader";
2
2
  import type { Translate } from "../../../helpers/translate.js";
3
3
  import type { StdEnv } from "../reader.js";
4
- import { ResponsiveTextSize } from "../responsiveText.js";
4
+ import { type ResponsiveTextSize } from "../responsiveText.js";
5
5
  /**
6
6
  * Possible units to use for time spans.
7
7
  */
@@ -11,13 +11,13 @@ export type TimeSpanUnit = "Seconds" | "Minutes" | "Hours" | "Days" | "Weeks" |
11
11
  */
12
12
  export declare const formatTimeSpan: (translate: Translate, responsiveTextSize: ResponsiveTextSize, unit: {
13
13
  kind: TimeSpanUnit;
14
- } | TimeSpanUnit, value: number | string) => string;
14
+ } | TimeSpanUnit, value: number | string, interval?: boolean) => string;
15
15
  /**
16
16
  * Returns the text for a time span unit.
17
17
  */
18
18
  export declare const formatTimeSpanR: (unit: {
19
19
  kind: TimeSpanUnit;
20
- } | TimeSpanUnit, value: number | string) => Reader<StdEnv<"t" | "rts">, string>;
20
+ } | TimeSpanUnit, value: number | string, interval?: boolean) => Reader<StdEnv<"t" | "rts">, string>;
21
21
  /**
22
22
  * Returns the text for a time span unit.
23
23
  */
@@ -26,4 +26,4 @@ export declare const formatCombinedTimeSpanR: (object: {
26
26
  kind: TimeSpanUnit;
27
27
  };
28
28
  value: number | string;
29
- }) => Reader<StdEnv<"t" | "rts">, string>;
29
+ }, interval?: boolean) => Reader<StdEnv<"t" | "rts">, string>;
@@ -2,33 +2,37 @@ import { Reader } from "@elyukai/utils/reader";
2
2
  import { responsive } from "../responsiveText.js";
3
3
  // prettier-ignore
4
4
  const timeSpanUnitTranslationKeys = {
5
- Seconds: [".input {$value :number} {{{$value} seconds}}", "{$value} seconds", "{$value} s"],
6
- Minutes: [".input {$value :number} {{{$value} minutes}}", "{$value} minutes", "{$value} min"],
7
- Hours: [".input {$value :number} {{{$value} hours}}", "{$value} hours", "{$value} h"],
8
- Days: [".input {$value :number} {{{$value} days}}", "{$value} days", "{$value} d"],
9
- Weeks: [".input {$value :number} {{{$value} weeks}}", "{$value} weeks", ".input {$value :number} {{{$value} wks.}}"],
10
- Months: [".input {$value :number} {{{$value} months}}", "{$value} months", ".input {$value :number} {{{$value} mos.}}"],
11
- Years: [".input {$value :number} {{{$value} years}}", "{$value} years", ".input {$value :number} {{{$value} yrs.}}"],
12
- Centuries: [".input {$value :number} {{{$value} centuries}}", "{$value} centuries", "{$value} cent."],
13
- Actions: [".input {$value :number} {{{$value} actions}}", "{$value} actions", "{$value} act"],
14
- CombatRounds: [".input {$value :number} {{{$value} combat rounds}}", "{$value} combat rounds", "{$value} CR"],
15
- SeductionActions: [".input {$value :number} {{{$value} seduction actions}}", "{$value} seduction actions", "{$value} SA"],
16
- Rounds: [".input {$value :number} {{{$value} rounds}}", "{$value} rounds", "{$value} rnds"],
5
+ Seconds: [".input {$value :number} .input {$style :string} {{{$value} seconds}}", "{$value} seconds", ".input {$value :number} .input {$style :string} {{{$value} s}}", "{$value} s"],
6
+ Minutes: [".input {$value :number} .input {$style :string} {{{$value} minutes}}", "{$value} minutes", ".input {$value :number} .input {$style :string} {{{$value} min}}", "{$value} min"],
7
+ Hours: [".input {$value :number} .input {$style :string} {{{$value} hours}}", "{$value} hours", ".input {$value :number} .input {$style :string} {{{$value} h}}", "{$value} h"],
8
+ Days: [".input {$value :number} .input {$style :string} {{{$value} days}}", "{$value} days", ".input {$value :number} .input {$style :string} {{{$value} d}}", "{$value} d"],
9
+ Weeks: [".input {$value :number} .input {$style :string} {{{$value} weeks}}", "{$value} weeks", ".input {$value :number} .input {$style :string} {{{$value} wks.}}", "{$value} wks."],
10
+ Months: [".input {$value :number} .input {$style :string} {{{$value} months}}", "{$value} months", ".input {$value :number} .input {$style :string} {{{$value} mos.}}", "{$value} mos."],
11
+ Years: [".input {$value :number} .input {$style :string} {{{$value} years}}", "{$value} years", ".input {$value :number} .input {$style :string} {{{$value} yrs.}}", "{$value} yrs."],
12
+ Centuries: [".input {$value :number} .input {$style :string} {{{$value} centuries}}", "{$value} centuries", ".input {$value :number} .input {$style :string} {{{$value} cent.}}", "{$value} cent."],
13
+ Actions: [".input {$value :number} .input {$style :string} {{{$value} actions}}", "{$value} actions", ".input {$value :number} .input {$style :string} {{{$value} act}}", "{$value} act"],
14
+ CombatRounds: [".input {$value :number} .input {$style :string} {{{$value} combat rounds}}", "{$value} combat rounds", ".input {$value :number} .input {$style :string} {{{$value} CR}}", "{$value} CR"],
15
+ SeductionActions: [".input {$value :number} .input {$style :string} {{{$value} seduction actions}}", "{$value} seduction actions", ".input {$value :number} .input {$style :string} {{{$value} SA}}", "{$value} SA"],
16
+ Rounds: [".input {$value :number} .input {$style :string} {{{$value} rounds}}", "{$value} rounds", ".input {$value :number} .input {$style :string} {{{$value} rnds}}", "{$value} rnds"],
17
17
  };
18
18
  /**
19
19
  * Returns the text for a time span unit.
20
20
  */
21
- export const formatTimeSpan = (translate, responsiveTextSize, unit, value) => {
22
- const [fullNumberKey, fullKey, compressedKey] = timeSpanUnitTranslationKeys[typeof unit === "string" ? unit : unit.kind];
21
+ export const formatTimeSpan = (translate, responsiveTextSize, unit, value, interval = false) => {
22
+ const [fullNumberKey, fullKey, compressedNumberKey, compressedKey] = timeSpanUnitTranslationKeys[typeof unit === "string" ? unit : unit.kind];
23
23
  return responsive(responsiveTextSize, () => translate(typeof value === "number" ? fullNumberKey : fullKey, {
24
24
  value,
25
- }), () => translate(compressedKey, { value }));
25
+ style: interval ? "interval" : "default",
26
+ }), () => translate(typeof value === "number" ? compressedNumberKey : compressedKey, {
27
+ value,
28
+ style: interval ? "interval" : "default",
29
+ }));
26
30
  };
27
31
  /**
28
32
  * Returns the text for a time span unit.
29
33
  */
30
- export const formatTimeSpanR = (unit, value) => Reader.asks(({ translate, responsiveTextSize }) => formatTimeSpan(translate, responsiveTextSize, unit, value));
34
+ export const formatTimeSpanR = (unit, value, interval) => Reader.asks(({ translate, responsiveTextSize }) => formatTimeSpan(translate, responsiveTextSize, unit, value, interval));
31
35
  /**
32
36
  * Returns the text for a time span unit.
33
37
  */
34
- export const formatCombinedTimeSpanR = (object) => formatTimeSpanR(object.unit, object.value);
38
+ export const formatCombinedTimeSpanR = (object, interval) => formatTimeSpanR(object.unit, object.value, interval);
@@ -23,10 +23,10 @@ export const getPersonalityTraitEntityDescription = createEntityDescriptionCreat
23
23
  {
24
24
  type: "definitionList",
25
25
  items: [
26
- ...(translation.effects.map(effect => ({
26
+ ...translation.effects.map(effect => ({
27
27
  label: effect.label,
28
28
  value: effect.text,
29
- })) ?? []),
29
+ })),
30
30
  entry.combination_options === undefined
31
31
  ? undefined
32
32
  : {
@@ -37,8 +37,7 @@ export const getPersonalityTraitEntityDescription = createEntityDescriptionCreat
37
37
  .toSorted(on(group => group[0], compareNullish(numAsc)))
38
38
  .map(([level, options]) => level === null
39
39
  ? MISSING_VALUE
40
- : `${translate("Level {$level}", { level: romanize(level) })} ${localeJoin(options.map(option => translateMap(option?.translations)?.name ??
41
- MISSING_VALUE), "disjunction")}`)
40
+ : `${translate("Level {$level}", { level: romanize(level) })} ${localeJoin(options.map(option => translateMap(option?.translations)?.name ?? MISSING_VALUE), "disjunction")}`)
42
41
  .join(", "),
43
42
  },
44
43
  entry.prerequisites === undefined
@@ -37,7 +37,7 @@ const renderLevel = (translate, translateMap, level) => {
37
37
  case "Constant":
38
38
  return typeof level.Constant === "number" ? level.Constant : level.Constant.value;
39
39
  case "BySubtype":
40
- return level.BySubtype.map(subtype => `${subtype.value} (${translateMap(subtype.translations)?.name ?? MISSING_VALUE})`).join(", ");
40
+ return level.BySubtype.map(subtype => `${subtype.value.toFixed()} (${translateMap(subtype.translations)?.name ?? MISSING_VALUE})`).join(", ");
41
41
  default:
42
42
  return assertExhaustive(level);
43
43
  }
@@ -82,7 +82,7 @@ const renderIntoxicantValues = (translate, translateMap, getInstanceById, intoxi
82
82
  overdose: translation?.overdose ?? MISSING_VALUE,
83
83
  legality: intoxicant.legality.is_legal ? translate("legal") : translate("illegal"),
84
84
  special: translation?.special,
85
- addiction: intoxicant?.addiction === undefined
85
+ addiction: intoxicant.addiction === undefined
86
86
  ? undefined
87
87
  : renderAddiction(translate, translateMap, getInstanceById, intoxicant.addiction),
88
88
  };
@@ -178,7 +178,7 @@ const renderDuration = (translate, translateMap, duration) => {
178
178
  return renderMathOperation(duration.ExpressionBased.value, value => {
179
179
  switch (value.kind) {
180
180
  case "Constant":
181
- return value.Constant.toString();
181
+ return value.Constant.toFixed();
182
182
  case "Dice":
183
183
  return renderDice(translate, value.Dice);
184
184
  case "CircleOfDamnation":
@@ -247,7 +247,7 @@ export const getPoisonEntityDescription = createEntityDescriptionCreator(({ getI
247
247
  renderAlternativeNames(translate, translation.alternative_names),
248
248
  {
249
249
  label: translate("Level"),
250
- value: level.toString(),
250
+ value: typeof level === "number" ? level.toFixed() : level,
251
251
  },
252
252
  {
253
253
  label: translate("Type"),