@optolith/entity-descriptions 0.13.2 → 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.
- package/CHANGELOG.md +13 -0
- package/lib/creator.js +1 -1
- package/lib/entities/activatable.js +22 -14
- package/lib/entities/culture.js +3 -3
- package/lib/entities/curriculum.js +1 -1
- package/lib/entities/elixir.js +1 -1
- package/lib/entities/equipment.js +1 -1
- package/lib/entities/partial/adventurePointsValue.js +8 -7
- package/lib/entities/partial/dice.js +2 -2
- package/lib/entities/partial/enhancements.js +2 -2
- package/lib/entities/partial/herbary.js +1 -1
- package/lib/entities/partial/prerequisites/single/personalityTrait.js +1 -1
- package/lib/entities/partial/prerequisites/single/ratedSum.js +1 -1
- package/lib/entities/partial/profession/options/combatTechniquesForAdventurePoints.d.ts +16 -0
- package/lib/entities/partial/profession/options/combatTechniquesForAdventurePoints.js +43 -0
- package/lib/entities/partial/profession/options/numberOfSpellworksTotalingAdventurePoints.d.ts +16 -0
- package/lib/entities/partial/profession/options/numberOfSpellworksTotalingAdventurePoints.js +39 -0
- package/lib/entities/partial/profession/packages.d.ts +67 -0
- package/lib/entities/partial/profession/packages.js +84 -0
- package/lib/entities/partial/profession/variants.d.ts +21 -0
- package/lib/entities/partial/profession/variants.js +31 -0
- package/lib/entities/partial/rated/activatable/cost.js +11 -9
- package/lib/entities/partial/rated/activatable/effect.js +2 -2
- package/lib/entities/partial/reader.d.ts +25 -1
- package/lib/entities/partial/reader.js +20 -0
- package/lib/entities/partial/units/energy.js +2 -1
- package/lib/entities/partial/units/length.js +6 -3
- package/lib/entities/partial/units/timeSpan.js +11 -7
- package/lib/entities/partial/unknown.d.ts +4 -0
- package/lib/entities/partial/unknown.js +4 -0
- package/lib/entities/poison.js +1 -1
- package/lib/entities/profession.js +44 -62
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
## [0.14.0](https://github.com/Optolith/entity-descriptions/compare/v0.13.2...v0.14.0) (2026-09-24)
|
|
6
|
+
|
|
7
|
+
### ⚠ BREAKING CHANGES
|
|
8
|
+
|
|
9
|
+
* **deps:** upgrade to schema 0.65.0
|
|
10
|
+
|
|
11
|
+
### build
|
|
12
|
+
|
|
13
|
+
* **deps:** upgrade to schema 0.65.0 ([cb94c96](https://github.com/Optolith/entity-descriptions/commit/cb94c96df8dc3f1d4f5476db729df2ed85ce6b1d))
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* missing adventure points label for AP value by level ([9aabd43](https://github.com/Optolith/entity-descriptions/commit/9aabd437d587e7b559c18a730249ea3c66bb8387))
|
|
5
18
|
## [0.13.2](https://github.com/Optolith/entity-descriptions/compare/v0.13.1...v0.13.2) (2026-09-16)
|
|
6
19
|
|
|
7
20
|
### Features
|
package/lib/creator.js
CHANGED
|
@@ -64,7 +64,7 @@ export const createEntityDescriptionCreator = (fn) => (databaseAccessors, locale
|
|
|
64
64
|
...rawEntry,
|
|
65
65
|
body: rawEntry.body.filter(isNotNullish).map(mapRawSection),
|
|
66
66
|
errata: rawEntry.errata?.map(({ date, description }) => ({
|
|
67
|
-
date: locale.formatDate(date),
|
|
67
|
+
date: locale.formatDate(new Date(date)),
|
|
68
68
|
description: description.trim(),
|
|
69
69
|
})),
|
|
70
70
|
references: rawEntry.references === undefined
|
|
@@ -44,6 +44,10 @@ const renderAdvantageDisadvantageSubtype = (subtype) => {
|
|
|
44
44
|
return translateR("Magical Rank");
|
|
45
45
|
case "MagicalTitle":
|
|
46
46
|
return translateR("Magical Title");
|
|
47
|
+
case "BlessedRank":
|
|
48
|
+
return translateR("Blessed Title");
|
|
49
|
+
case "BlessedTitle":
|
|
50
|
+
return translateR("Blessed Title");
|
|
47
51
|
default:
|
|
48
52
|
return assertExhaustive(subtype);
|
|
49
53
|
}
|
|
@@ -311,7 +315,9 @@ const renderVolumeValue = (translate, translateMap, getAllResolvedSelectOptions,
|
|
|
311
315
|
levels: volume.ByLevel.list.map((_, index) => romanize(index + 1)).join("/"),
|
|
312
316
|
});
|
|
313
317
|
case "Map":
|
|
314
|
-
return renderParameterMap(volume.Map, option => Reader.of(option.points), values => translateR("{$points} points", {
|
|
318
|
+
return renderParameterMap(volume.Map, option => Reader.of(option.points), values => translateR("{$points} points", {
|
|
319
|
+
points: typeof values === "number" ? values.toFixed() : values,
|
|
320
|
+
})).run({
|
|
315
321
|
translate,
|
|
316
322
|
translateMap,
|
|
317
323
|
responsiveTextSize,
|
|
@@ -342,7 +348,7 @@ const renderArcaneEnergyCost = (translate, translateMap, format, localeJoin, res
|
|
|
342
348
|
? value => translate(".input {$value :number} {{{$value} permanent AE}}", {
|
|
343
349
|
value,
|
|
344
350
|
})
|
|
345
|
-
: value => translate("{$value} AE", { value });
|
|
351
|
+
: value => translate("{$value} AE", { value: value.toFixed() });
|
|
346
352
|
const { interval } = cost.Fixed;
|
|
347
353
|
const wrapInInterval = interval === undefined
|
|
348
354
|
? str => str
|
|
@@ -370,40 +376,40 @@ const renderArcaneEnergyCost = (translate, translateMap, format, localeJoin, res
|
|
|
370
376
|
return wrapInPerLevel(value => wrapInInterval(translationWrapper(value))) + noteInParens;
|
|
371
377
|
}
|
|
372
378
|
case "Constant":
|
|
373
|
-
return (translate("{$value} AE", { value: cost.Constant.value }) +
|
|
379
|
+
return (translate("{$value} AE", { value: cost.Constant.value.toFixed() }) +
|
|
374
380
|
(cost.Constant.permanent_value === undefined
|
|
375
381
|
? ""
|
|
376
382
|
: translate(", {$value} of which are permanent", {
|
|
377
|
-
value: cost.Constant.permanent_value,
|
|
383
|
+
value: cost.Constant.permanent_value.toFixed(),
|
|
378
384
|
})));
|
|
379
385
|
case "PerCountable": {
|
|
380
386
|
const translation = translateMap(cost.PerCountable.translations);
|
|
381
387
|
return ((cost.PerCountable.base_value === undefined
|
|
382
388
|
? ""
|
|
383
|
-
: `${translate("{$value} AE", { value: cost.PerCountable.base_value })} + `) +
|
|
389
|
+
: `${translate("{$value} AE", { value: cost.PerCountable.base_value.toFixed() })} + `) +
|
|
384
390
|
translate("{$cost} per {$countable}", {
|
|
385
|
-
cost: translate("{$value} AE", { value: cost.PerCountable.value }),
|
|
391
|
+
cost: translate("{$value} AE", { value: cost.PerCountable.value.toFixed() }),
|
|
386
392
|
countable: getResponsiveText(translation?.per, responsiveTextSize),
|
|
387
393
|
}) +
|
|
388
394
|
parensIf(mapNullable(translation?.note, note => getResponsiveTextOptional(note, responsiveTextSize))));
|
|
389
395
|
}
|
|
390
396
|
case "Interval":
|
|
391
397
|
return translate("{$cost} per {$interval}", {
|
|
392
|
-
cost: translate("{$value} AE", { value: cost.Interval.value }),
|
|
398
|
+
cost: translate("{$value} AE", { value: cost.Interval.value.toFixed() }),
|
|
393
399
|
interval: formatTimeSpan(translate, translateMap, format, responsiveTextSize, cost.Interval.interval.unit, cost.Interval.interval.value, true),
|
|
394
400
|
});
|
|
395
401
|
case "ActivationAndHalfInterval":
|
|
396
402
|
return additionFormatter(translate("{$value} AE", {
|
|
397
|
-
value: cost.ActivationAndHalfInterval.value,
|
|
403
|
+
value: cost.ActivationAndHalfInterval.value.toFixed(),
|
|
398
404
|
}) + parensIf(translate("activation")), translate("{$cost} per {$interval}", {
|
|
399
405
|
cost: translate("{$value} AE", {
|
|
400
|
-
value: Math.round(cost.ActivationAndHalfInterval.value / 2),
|
|
406
|
+
value: Math.round(cost.ActivationAndHalfInterval.value / 2).toFixed(),
|
|
401
407
|
}),
|
|
402
408
|
interval: formatTimeSpan(translate, translateMap, format, responsiveTextSize, cost.ActivationAndHalfInterval.interval.unit, cost.ActivationAndHalfInterval.interval.value, true),
|
|
403
409
|
}));
|
|
404
410
|
case "Indefinite":
|
|
405
411
|
return ((mapNullable(translateMap(cost.Indefinite.translations)?.description, description => getResponsiveText(description, responsiveTextSize)) ?? MISSING_VALUE) +
|
|
406
|
-
mapNullableDefault(cost.Indefinite.modifier, modifier => ` + ${translate("{$value} AE", { value: modifier.value })}`, ""));
|
|
412
|
+
mapNullableDefault(cost.Indefinite.modifier, modifier => ` + ${translate("{$value} AE", { value: modifier.value.toFixed() })}`, ""));
|
|
407
413
|
case "Disjunction":
|
|
408
414
|
return translate("{$value} AE", {
|
|
409
415
|
value: localeJoin(cost.Disjunction.options.map(option => option.value.toFixed() +
|
|
@@ -430,7 +436,7 @@ const renderArcaneEnergyCost = (translate, translateMap, format, localeJoin, res
|
|
|
430
436
|
case "Verbose":
|
|
431
437
|
return cost.ByLevel.levels
|
|
432
438
|
.map((level, index) => translate("{$cost} for level {$level}", {
|
|
433
|
-
cost: translate("{$value} AE", { value: level.value }),
|
|
439
|
+
cost: translate("{$value} AE", { value: level.value.toFixed() }),
|
|
434
440
|
level: romanize(index + 1),
|
|
435
441
|
}))
|
|
436
442
|
.join("; ");
|
|
@@ -454,7 +460,9 @@ const renderBindingCost = (translate, translateMap, localeCompare, getAllResolve
|
|
|
454
460
|
}),
|
|
455
461
|
});
|
|
456
462
|
case "Map":
|
|
457
|
-
return renderParameterMap(cost.Map, option => Reader.of(option.permanentValue), values => translateR("{$value} permanent AE", {
|
|
463
|
+
return renderParameterMap(cost.Map, option => Reader.of(option.permanentValue), values => translateR("{$value} permanent AE", {
|
|
464
|
+
value: typeof values === "number" ? values.toFixed() : values,
|
|
465
|
+
})).run({
|
|
458
466
|
translate,
|
|
459
467
|
translateMap,
|
|
460
468
|
responsiveTextSize,
|
|
@@ -481,12 +489,12 @@ const renderBindingCost = (translate, translateMap, localeCompare, getAllResolve
|
|
|
481
489
|
const renderLifePointsCost = (translate, cost) => cost === undefined
|
|
482
490
|
? ""
|
|
483
491
|
: // eslint-disable-next-line no-irregular-whitespace
|
|
484
|
-
` (+ ${translate("{$value} LP", { value: cost.Fixed.value })})`;
|
|
492
|
+
` (+ ${translate("{$value} LP", { value: cost.Fixed.value.toFixed() })})`;
|
|
485
493
|
const renderCost = (translate, translateMap, format, localeJoin, localeCompare, getAllResolvedSelectOptions, responsiveTextSize, levels, cost) => {
|
|
486
494
|
if (typeof cost === "number") {
|
|
487
495
|
return {
|
|
488
496
|
label: translate("AE Cost"),
|
|
489
|
-
value: translate("{$value} AE", { value: cost }),
|
|
497
|
+
value: translate("{$value} AE", { value: cost.toFixed() }),
|
|
490
498
|
};
|
|
491
499
|
}
|
|
492
500
|
switch (cost.kind) {
|
package/lib/entities/culture.js
CHANGED
|
@@ -248,14 +248,14 @@ export const getCultureEntityDescription = createEntityDescriptionCreator(({ get
|
|
|
248
248
|
if (allSame(processedScripts, on(([, apValue]) => apValue, equal))) {
|
|
249
249
|
return (localeJoin(processedScripts.map(([name]) => name).toSorted(localeCompare), "disjunction") +
|
|
250
250
|
parensIf(translate("{$value} AP", {
|
|
251
|
-
value: processedScripts[0][1],
|
|
251
|
+
value: processedScripts[0][1].toFixed(),
|
|
252
252
|
})));
|
|
253
253
|
}
|
|
254
254
|
else {
|
|
255
255
|
return localeJoin(processedScripts
|
|
256
256
|
.map(([name, apValue]) => name +
|
|
257
257
|
parensIf(translate("{$value} AP", {
|
|
258
|
-
value: apValue,
|
|
258
|
+
value: apValue.toFixed(),
|
|
259
259
|
})))
|
|
260
260
|
.toSorted(localeCompare), "disjunction");
|
|
261
261
|
}
|
|
@@ -306,7 +306,7 @@ export const getCultureEntityDescription = createEntityDescriptionCreator(({ get
|
|
|
306
306
|
type: "labeled",
|
|
307
307
|
label: translate("Cultural Package {$cultureName}", {
|
|
308
308
|
cultureName: translation.name,
|
|
309
|
-
}) + parensIf(translate("{$value} AP", { value: culturalPackageApValue })),
|
|
309
|
+
}) + parensIf(translate("{$value} AP", { value: culturalPackageApValue.toFixed() })),
|
|
310
310
|
value: {
|
|
311
311
|
type: "plain",
|
|
312
312
|
text: culturePackageText,
|
|
@@ -183,7 +183,7 @@ const renderAbilityAdjustment = (translate, translateMap, getInstanceById, baseP
|
|
|
183
183
|
const printedBaseValue = mapNullable(renderPrintedAbilityAdjustmentBaseValue(abilityAdjustment), value => value + defaultValue);
|
|
184
184
|
return translate("{$replacement} instead of {$base}", {
|
|
185
185
|
base: printedBaseValue === basePoints
|
|
186
|
-
? basePoints
|
|
186
|
+
? basePoints.toFixed()
|
|
187
187
|
: `<ins>${basePoints.toFixed()}</ins><del>${printedBaseValue?.toFixed() ?? "n/a"}</del>`,
|
|
188
188
|
replacement: `${renderAbilityAdjustmentName(translateMap, getInstanceById, abilityAdjustment)} ${(basePoints + renderAbilityAdjustmentModifierValue(abilityAdjustment)).toFixed()}`,
|
|
189
189
|
});
|
package/lib/entities/elixir.js
CHANGED
|
@@ -54,7 +54,7 @@ export const getElixirEntityDescription = createEntityDescriptionCreator(({ getI
|
|
|
54
54
|
{
|
|
55
55
|
label: `${translate("AP Value")} (${translate("Trade Secret")})`,
|
|
56
56
|
value: translate("{$value} AP", {
|
|
57
|
-
value: entry.trade_secret.ap_value,
|
|
57
|
+
value: entry.trade_secret.ap_value.toFixed(),
|
|
58
58
|
}) +
|
|
59
59
|
parensIf(mapNullable(entry.trade_secret.prerequisites, prerequisites => `${translate("Prerequisites")}: ${printPlainGeneralPrerequisites(prerequisites).run(env)}`)),
|
|
60
60
|
},
|
|
@@ -239,7 +239,7 @@ const renderComplexity = (translate, complexity) => mapNullable(complexity, c =>
|
|
|
239
239
|
return translate("Simple");
|
|
240
240
|
case "Complex":
|
|
241
241
|
return `${translate("Complex")} (${translate("{$value} AP", {
|
|
242
|
-
value: c.Complex.ap_value,
|
|
242
|
+
value: c.Complex.ap_value.toFixed(),
|
|
243
243
|
})})`;
|
|
244
244
|
case "Various":
|
|
245
245
|
return translate("Various");
|
|
@@ -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
|
|
91
|
+
const levelsPrefix = translate("Level {$level}", {
|
|
92
92
|
level: Array.from({ length: entry.levels }, (_, index) => romanize(index + 1)).join("/"),
|
|
93
|
-
})
|
|
93
|
+
});
|
|
94
|
+
const mainValues = value.ByLevel.list.map(applyNegative).join("/");
|
|
94
95
|
const { additionalBySizeCategory } = value.ByLevel;
|
|
95
96
|
if (additionalBySizeCategory === undefined) {
|
|
96
|
-
return
|
|
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 `${
|
|
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);
|
package/lib/entities/partial/profession/options/numberOfSpellworksTotalingAdventurePoints.d.ts
ADDED
|
@@ -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>;
|