@optolith/entity-descriptions 0.4.8 → 0.4.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/lib/entities/activatable.js +17 -14
- package/lib/entities/curriculum.js +14 -21
- package/lib/entities/derivedCharacteristic.d.ts +0 -2
- package/lib/entities/derivedCharacteristic.js +10 -17
- package/lib/entities/disease.js +5 -1
- package/lib/entities/elixir.js +12 -10
- package/lib/entities/equipment.d.ts +6 -5
- package/lib/entities/equipment.js +19 -6
- package/lib/entities/equipmentPackage.js +2 -1
- package/lib/entities/liturgicalChant.js +7 -3
- package/lib/entities/partial/commonnessRatedAdvantagesAndDisadvantages.js +8 -12
- package/lib/entities/partial/herbary.js +5 -8
- package/lib/entities/partial/markdown.d.ts +55 -0
- package/lib/entities/partial/markdown.js +59 -0
- package/lib/entities/partial/prerequisites/index.d.ts +3 -3
- package/lib/entities/partial/prerequisites/index.js +3 -3
- package/lib/entities/partial/prerequisites/part.js +8 -3
- package/lib/entities/partial/prerequisites/prerequisiteGroups.d.ts +1 -1
- package/lib/entities/partial/prerequisites/prerequisiteGroups.js +10 -7
- package/lib/entities/partial/prerequisites/single/culture.js +2 -1
- package/lib/entities/partial/prerequisites/single/personalityTrait.js +2 -1
- package/lib/entities/partial/prerequisites/single/profession.js +4 -1
- package/lib/entities/partial/prerequisites/single/race.js +2 -1
- package/lib/entities/partial/prerequisites/single/rated.js +2 -27
- package/lib/entities/partial/prerequisites/single/ratedMinimumNumber.js +2 -1
- package/lib/entities/partial/prerequisites/single/ratedSum.js +2 -1
- package/lib/entities/partial/prerequisites/single/state.js +2 -1
- package/lib/entities/partial/rated/activatable/checkResultBased.js +3 -1
- package/lib/entities/partial/reader.d.ts +33 -0
- package/lib/entities/partial/reader.js +13 -0
- package/lib/entities/personalityTrait.js +9 -2
- package/lib/entities/poison.js +20 -9
- package/lib/entities/profession.d.ts +2 -2
- package/lib/entities/profession.js +133 -38
- package/lib/entities/race.d.ts +1 -1
- package/lib/entities/race.js +23 -23
- package/lib/entities/spell.d.ts +4 -2
- package/lib/entities/spell.js +17 -29
- package/lib/index.d.ts +64 -64
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,34 @@
|
|
|
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.4.11](https://github.com/Optolith/entity-descriptions/compare/v0.4.10...v0.4.11) (2026-03-23)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* support schema version 0.42.0 ([80b2eaa](https://github.com/Optolith/entity-descriptions/commit/80b2eaacbcf445cf3222c11b86c1be77b7d973d6))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* activatable cost by level separator ([a0119ff](https://github.com/Optolith/entity-descriptions/commit/a0119ffdafcc6027801c0ff7b502c9c65af107dd))
|
|
16
|
+
* render attribute prerequisite as abbreviation ([6f258b1](https://github.com/Optolith/entity-descriptions/commit/6f258b1f6f4fb1c88b09fe9fb2013b180d91fc4b))
|
|
17
|
+
|
|
18
|
+
## [0.4.10](https://github.com/Optolith/entity-descriptions/compare/v0.4.9...v0.4.10) (2026-03-17)
|
|
19
|
+
|
|
20
|
+
## [0.4.9](https://github.com/Optolith/entity-descriptions/compare/v0.4.8...v0.4.9) (2026-03-17)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
### Features
|
|
24
|
+
|
|
25
|
+
* mark generated instances with attributed strings ([85cce91](https://github.com/Optolith/entity-descriptions/commit/85cce917d16ac10833d4dccf7031c2dca4e75d26))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
### Bug Fixes
|
|
29
|
+
|
|
30
|
+
* always append level to activatable name ([10ca82a](https://github.com/Optolith/entity-descriptions/commit/10ca82a1e57afd2d11ba7c32b55a49172e676398))
|
|
31
|
+
* require previous level and fix function typo ([2b12a01](https://github.com/Optolith/entity-descriptions/commit/2b12a01beaaf786843edbbcf42def3d1b1ecb673))
|
|
32
|
+
|
|
5
33
|
## [0.4.8](https://github.com/Optolith/entity-descriptions/compare/v0.4.7...v0.4.8) (2026-03-15)
|
|
6
34
|
|
|
7
35
|
|
|
@@ -13,6 +13,7 @@ import { createEntityDescriptionCreator } from "../creator.js";
|
|
|
13
13
|
import { renderActivatableNameComponents } from "./partial/activatableNameChunks.js";
|
|
14
14
|
import { renderAdventurePointsValue } from "./partial/adventurePointsValue.js";
|
|
15
15
|
import { renderResponsiveMap } from "./partial/map.js";
|
|
16
|
+
import { attributedName } from "./partial/markdown.js";
|
|
16
17
|
import { additionFormatter } from "./partial/mathOperation.js";
|
|
17
18
|
import { printAdvantageDisadvantagePrerequisites, printGeneralPrerequisites, } from "./partial/prerequisites/index.js";
|
|
18
19
|
import { printActivatableName, } from "./partial/prerequisites/single/activatable.js";
|
|
@@ -142,7 +143,7 @@ const wrapInParens = (args, append = "") => {
|
|
|
142
143
|
return ` (${filteredArgs.join(", ") + append})`;
|
|
143
144
|
};
|
|
144
145
|
const addSpaceIfNoCommaAtStart = (str) => (str.startsWith(",") ? str : ` ${str}`);
|
|
145
|
-
const renderApplicableCombatTechniquesRestriction = (getInstanceById, locale, main, restriction, translation, weapons,
|
|
146
|
+
const renderApplicableCombatTechniquesRestriction = (getInstanceById, locale, main, restriction, translation, weapons, normalizeExcludedId) => {
|
|
146
147
|
switch (restriction.kind) {
|
|
147
148
|
case "Improvised":
|
|
148
149
|
return main + wrapInParens([locale.translate("only improvised weapons"), weapons]);
|
|
@@ -156,13 +157,12 @@ const renderApplicableCombatTechniquesRestriction = (getInstanceById, locale, ma
|
|
|
156
157
|
return main + wrapInParens([weapons], locale.translate("while mounted"));
|
|
157
158
|
}
|
|
158
159
|
case "Race": {
|
|
159
|
-
const
|
|
160
|
-
const raceName = locale.translateMap(race?.translations)?.name ?? MISSING_VALUE;
|
|
160
|
+
const raceName = attributedName(locale.translateMap, getInstanceById, "applicable-combat-technique", "Race", restriction.Race);
|
|
161
161
|
return (main +
|
|
162
162
|
wrapInParens([
|
|
163
163
|
// originally "while {$racial} weapon", but different to parameterize without inflection support
|
|
164
164
|
locale.translate("while weapon of race {$race}", {
|
|
165
|
-
race: raceName,
|
|
165
|
+
race: raceName ?? MISSING_VALUE,
|
|
166
166
|
}),
|
|
167
167
|
weapons,
|
|
168
168
|
]));
|
|
@@ -172,7 +172,12 @@ const renderApplicableCombatTechniquesRestriction = (getInstanceById, locale, ma
|
|
|
172
172
|
wrapInParens([
|
|
173
173
|
locale.translate("except {$list}", {
|
|
174
174
|
list: locale.join(restriction.ExcludeCombatTechniques.list
|
|
175
|
-
.map(id =>
|
|
175
|
+
.map(id => {
|
|
176
|
+
const normalizedId = normalizeExcludedId?.(id);
|
|
177
|
+
return normalizedId !== undefined
|
|
178
|
+
? (attributedName(locale.translateMap, getInstanceById, "applicable-combat-technique", normalizedId) ?? MISSING_VALUE)
|
|
179
|
+
: MISSING_VALUE;
|
|
180
|
+
})
|
|
176
181
|
.toSorted(locale.compare), "conjunction"),
|
|
177
182
|
}),
|
|
178
183
|
weapons,
|
|
@@ -207,28 +212,27 @@ const renderApplicableCombatTechniquesValue = (getInstanceById, locale, translat
|
|
|
207
212
|
const main = locale.translate("All");
|
|
208
213
|
const mainWithRestriction = applicableCombatTechniques.All.restriction === undefined
|
|
209
214
|
? main
|
|
210
|
-
: renderApplicableCombatTechniquesRestriction(getInstanceById, locale, main, applicableCombatTechniques.All.restriction, translation, undefined, id =>
|
|
215
|
+
: renderApplicableCombatTechniquesRestriction(getInstanceById, locale, main, applicableCombatTechniques.All.restriction, translation, undefined, id => id);
|
|
211
216
|
return mainWithRestriction;
|
|
212
217
|
}
|
|
213
218
|
case "AllClose": {
|
|
214
219
|
const main = locale.translate("All Close Combat Techniques");
|
|
215
220
|
const mainWithRestriction = applicableCombatTechniques.AllClose.restriction === undefined
|
|
216
221
|
? main
|
|
217
|
-
: renderApplicableCombatTechniquesRestriction(getInstanceById, locale, main, applicableCombatTechniques.AllClose.restriction, translation, undefined, id =>
|
|
222
|
+
: renderApplicableCombatTechniquesRestriction(getInstanceById, locale, main, applicableCombatTechniques.AllClose.restriction, translation, undefined, id => Case("CloseCombatTechnique", id));
|
|
218
223
|
return mainWithRestriction;
|
|
219
224
|
}
|
|
220
225
|
case "AllRanged": {
|
|
221
226
|
const main = locale.translate("All Ranged Combat Techniques");
|
|
222
227
|
const mainWithRestriction = applicableCombatTechniques.AllRanged.restriction === undefined
|
|
223
228
|
? main
|
|
224
|
-
: renderApplicableCombatTechniquesRestriction(getInstanceById, locale, main, applicableCombatTechniques.AllRanged.restriction, translation, undefined, id =>
|
|
229
|
+
: renderApplicableCombatTechniquesRestriction(getInstanceById, locale, main, applicableCombatTechniques.AllRanged.restriction, translation, undefined, id => Case("RangedCombatTechnique", id));
|
|
225
230
|
return mainWithRestriction;
|
|
226
231
|
}
|
|
227
232
|
case "Specific": {
|
|
228
233
|
return applicableCombatTechniques.Specific.list
|
|
229
234
|
.map(specific => {
|
|
230
|
-
const
|
|
231
|
-
const main = locale.translateMap(entry?.translations)?.name ?? MISSING_VALUE;
|
|
235
|
+
const main = attributedName(locale.translateMap, getInstanceById, "applicable-combat-technique", specific.id) ?? MISSING_VALUE;
|
|
232
236
|
const mainWithRestriction = specific.restriction === undefined
|
|
233
237
|
? main
|
|
234
238
|
: renderApplicableCombatTechniquesRestriction(getInstanceById, locale, main, specific.restriction, translation, specific.weapons === undefined
|
|
@@ -388,7 +392,7 @@ const renderArcaneEnergyCost = (translate, translateMap, localeJoin, responsiveT
|
|
|
388
392
|
cost: translate("{$value} AE", {
|
|
389
393
|
value: cost.ByLevel.levels.map(level => level.value).join("/"),
|
|
390
394
|
}),
|
|
391
|
-
level: Array.from({ length: cost.ByLevel.levels.length }, (_, index) => romanize(index + 1)),
|
|
395
|
+
level: Array.from({ length: cost.ByLevel.levels.length }, (_, index) => romanize(index + 1)).join("/"),
|
|
392
396
|
});
|
|
393
397
|
case "Verbose":
|
|
394
398
|
return cost.ByLevel.levels
|
|
@@ -635,9 +639,8 @@ export const getActivatableEntityDescription = createEntityDescriptionCreator(({
|
|
|
635
639
|
const baseEntry = entry;
|
|
636
640
|
const wrappedId = Case(entityName, id);
|
|
637
641
|
return {
|
|
638
|
-
title: translation.name_in_library ??
|
|
639
|
-
|
|
640
|
-
(baseEntry.levels !== undefined ? ` I–${romanize(baseEntry.levels)}` : ""),
|
|
642
|
+
title: (translation.name_in_library ?? translation.name) +
|
|
643
|
+
(baseEntry.levels !== undefined ? ` I–${romanize(baseEntry.levels)}` : ""),
|
|
641
644
|
subtitle: mapNullable(baseEntry.usage_type, usageType => {
|
|
642
645
|
switch (usageType.kind) {
|
|
643
646
|
case "Passive":
|
|
@@ -5,6 +5,7 @@ import { sign } from "@elyukai/utils/string/number";
|
|
|
5
5
|
import { assertExhaustive } from "@elyukai/utils/typeSafety";
|
|
6
6
|
import { isNotNullish, mapNullable } from "@optolith/helpers/nullable";
|
|
7
7
|
import { createEntityDescriptionCreator } from "../creator.js";
|
|
8
|
+
import { attributedName } from "./partial/markdown.js";
|
|
8
9
|
import { getBaseProfessionPackageForCurriculum } from "./partial/professions.js";
|
|
9
10
|
import { parensIf } from "./partial/rated/activatable/parensIf.js";
|
|
10
11
|
import { MISSING_VALUE } from "./partial/unknown.js";
|
|
@@ -14,13 +15,12 @@ const renderElectiveSpellworks = (translate, translateMap, localeCompare, getIns
|
|
|
14
15
|
return translate("All, at the GM’s discretion");
|
|
15
16
|
case "Specific":
|
|
16
17
|
return electiveSpellworks.Specific.list
|
|
17
|
-
.map(item => mapNullable(translateMap
|
|
18
|
+
.map(item => mapNullable(attributedName(translateMap, getInstanceById, "curriculum", item.id), name => {
|
|
18
19
|
if (item.restriction === undefined) {
|
|
19
20
|
return name;
|
|
20
21
|
}
|
|
21
22
|
return (name +
|
|
22
|
-
parensIf(translateMap
|
|
23
|
-
?.name ?? MISSING_VALUE));
|
|
23
|
+
parensIf(attributedName(translateMap, getInstanceById, "curriculum", "Element", item.restriction.Element)));
|
|
24
24
|
}))
|
|
25
25
|
.filter(isNotNullish)
|
|
26
26
|
.toSorted(localeCompare)
|
|
@@ -42,7 +42,7 @@ const renderRestrictedSpellworks = (translate, translateMap, localeCompare, loca
|
|
|
42
42
|
return "";
|
|
43
43
|
}
|
|
44
44
|
const translatedSpellworks = excludedSpellworks
|
|
45
|
-
.map(excludedSpellwork => translateMap
|
|
45
|
+
.map(excludedSpellwork => attributedName(translateMap, getInstanceById, "curriculum", excludedSpellwork))
|
|
46
46
|
.filter(isNotNullish);
|
|
47
47
|
if (translatedSpellworks.length === 0) {
|
|
48
48
|
return "";
|
|
@@ -53,16 +53,14 @@ const renderRestrictedSpellworks = (translate, translateMap, localeCompare, loca
|
|
|
53
53
|
};
|
|
54
54
|
const restrictedProperties = restrictedGroups.get("restrictedProperty")?.map(restriction => translate(".input {$count :number} {{only {$count} additional spellworks with the Property {$property}}}", {
|
|
55
55
|
count: restriction.Property.maximum,
|
|
56
|
-
property: translateMap
|
|
57
|
-
?.name ?? MISSING_VALUE,
|
|
56
|
+
property: attributedName(translateMap, getInstanceById, "curriculum", "Property", restriction.Property.id) ?? MISSING_VALUE,
|
|
58
57
|
}) + addExclusion(restriction.Property.exclude)) ?? [];
|
|
59
58
|
const propertyGroup = restrictedGroups.get("property");
|
|
60
59
|
const translatedProperties = propertyGroup
|
|
61
60
|
?.map(restriction => {
|
|
62
61
|
switch (restriction.kind) {
|
|
63
62
|
case "Property":
|
|
64
|
-
return translateMap
|
|
65
|
-
?.name;
|
|
63
|
+
return attributedName(translateMap, getInstanceById, "curriculum", "Property", restriction.Property.id);
|
|
66
64
|
case "DemonSummoning":
|
|
67
65
|
return translate("Demon Summoning");
|
|
68
66
|
default:
|
|
@@ -80,7 +78,7 @@ const renderRestrictedSpellworks = (translate, translateMap, localeCompare, loca
|
|
|
80
78
|
: [];
|
|
81
79
|
const spellworkGroup = restrictedGroups.get("spellwork");
|
|
82
80
|
const translatedSpellworks = spellworkGroup
|
|
83
|
-
?.map(restriction => translateMap
|
|
81
|
+
?.map(restriction => attributedName(translateMap, getInstanceById, "curriculum", restriction.Spellwork))
|
|
84
82
|
.filter(isNotNullish)
|
|
85
83
|
.toSorted(localeCompare) ?? [];
|
|
86
84
|
const spellworks = translatedSpellworks.length > 0
|
|
@@ -103,24 +101,20 @@ const renderRestrictedSpellworks = (translate, translateMap, localeCompare, loca
|
|
|
103
101
|
}) ?? [];
|
|
104
102
|
return [...restrictedProperties, ...properties, ...others, ...spellworks].join("; ");
|
|
105
103
|
};
|
|
106
|
-
const renderSpellworkAdjustment = (translateMap, getInstanceById, adjustment) => `${translateMap
|
|
104
|
+
const renderSpellworkAdjustment = (translateMap, getInstanceById, adjustment) => `${attributedName(translateMap, getInstanceById, "curriculum", adjustment.id) ?? MISSING_VALUE} ${adjustment.points.toFixed()}${parensIf(adjustment.tradition === undefined
|
|
107
105
|
? undefined
|
|
108
|
-
: translateMap
|
|
106
|
+
: attributedName(translateMap, getInstanceById, "curriculum", "MagicalTradition", adjustment.tradition))}`;
|
|
109
107
|
const renderAbilityAdjustmentName = (translateMap, getInstanceById, abilityAdjustment) => {
|
|
110
108
|
switch (abilityAdjustment.kind) {
|
|
111
109
|
case "Skill":
|
|
112
|
-
return (translateMap
|
|
113
|
-
MISSING_VALUE);
|
|
110
|
+
return (attributedName(translateMap, getInstanceById, "curriculum", "Skill", abilityAdjustment.Skill.id) ?? MISSING_VALUE);
|
|
114
111
|
case "CombatTechnique":
|
|
115
|
-
return (translateMap
|
|
116
|
-
MISSING_VALUE);
|
|
112
|
+
return (attributedName(translateMap, getInstanceById, "curriculum", abilityAdjustment.CombatTechnique.id) ?? MISSING_VALUE);
|
|
117
113
|
case "Spellwork":
|
|
118
|
-
return ((translateMap
|
|
119
|
-
MISSING_VALUE) +
|
|
114
|
+
return ((attributedName(translateMap, getInstanceById, "curriculum", abilityAdjustment.Spellwork.id) ?? MISSING_VALUE) +
|
|
120
115
|
parensIf(abilityAdjustment.Spellwork.tradition === undefined
|
|
121
116
|
? undefined
|
|
122
|
-
: translateMap
|
|
123
|
-
?.translations)?.name));
|
|
117
|
+
: attributedName(translateMap, getInstanceById, "curriculum", "MagicalTradition", abilityAdjustment.Spellwork.tradition)));
|
|
124
118
|
default:
|
|
125
119
|
return assertExhaustive(abilityAdjustment);
|
|
126
120
|
}
|
|
@@ -195,8 +189,7 @@ export const getCurriculumEntityDescription = createEntityDescriptionCreator(({
|
|
|
195
189
|
items: [
|
|
196
190
|
{
|
|
197
191
|
label: translate("Guideline"),
|
|
198
|
-
value: translateMap
|
|
199
|
-
MISSING_VALUE,
|
|
192
|
+
value: attributedName(translateMap, getInstanceById, "curriculum", "Guideline", entry.guideline) ?? MISSING_VALUE,
|
|
200
193
|
},
|
|
201
194
|
{
|
|
202
195
|
label: translate("Elective Spellworks Package"),
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import type { GetInstanceById } from "../helpers/getTypes.js";
|
|
2
|
-
import type { IdMap } from "../index.js";
|
|
3
2
|
/**
|
|
4
3
|
* Get a JSON representation of the rules text for a derived characteristic.
|
|
5
4
|
*/
|
|
6
5
|
export declare const getDerivedCharacteristicEntityDescription: import("../creator.js").EntityDescriptionCreator<"DerivedCharacteristic", {
|
|
7
6
|
getInstanceById: GetInstanceById<"Publication" | "Attribute" | "DerivedCharacteristic">;
|
|
8
|
-
idMap: IdMap;
|
|
9
7
|
}, import("../index.js").EntityDescription>;
|
|
@@ -1,27 +1,20 @@
|
|
|
1
1
|
import { assertExhaustive } from "@optolith/helpers/typeSafety";
|
|
2
2
|
import { createEntityDescriptionCreator } from "../creator.js";
|
|
3
|
+
import { attributedCustomName } from "./partial/markdown.js";
|
|
3
4
|
import { renderMathOperation } from "./partial/mathOperation.js";
|
|
4
5
|
import { MISSING_VALUE } from "./partial/unknown.js";
|
|
5
|
-
const
|
|
6
|
-
const attribute = getInstanceById("Attribute", attributeId);
|
|
7
|
-
if (attribute === undefined) {
|
|
8
|
-
return MISSING_VALUE;
|
|
9
|
-
}
|
|
6
|
+
const renderAttributeNameForStyle = (style, translation) => {
|
|
10
7
|
switch (style) {
|
|
11
8
|
case "full":
|
|
12
|
-
return
|
|
9
|
+
return translation.name;
|
|
13
10
|
case "compact":
|
|
14
|
-
return
|
|
11
|
+
return translation.abbreviation;
|
|
15
12
|
default:
|
|
16
13
|
return assertExhaustive(style);
|
|
17
14
|
}
|
|
18
15
|
};
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
const translation = translateMap(instance?.translations);
|
|
22
|
-
if (translation === undefined) {
|
|
23
|
-
return MISSING_VALUE;
|
|
24
|
-
}
|
|
16
|
+
const getAttribute = (getInstanceById, translateMap, attributeId, style = "full") => attributedCustomName(translateMap, getInstanceById, "derived-characteristic-calculation", translation => renderAttributeNameForStyle(style, translation), "Attribute", attributeId) ?? MISSING_VALUE;
|
|
17
|
+
const getRaceBaseValue = (translate, translation, style = "full") => {
|
|
25
18
|
switch (style) {
|
|
26
19
|
case "full":
|
|
27
20
|
return translate("Base {$name} from Race", translation);
|
|
@@ -31,14 +24,14 @@ const getRaceBaseValue = (translate, translateMap, getInstanceById, idMap, raceB
|
|
|
31
24
|
return assertExhaustive(style);
|
|
32
25
|
}
|
|
33
26
|
};
|
|
34
|
-
const renderBaseCalculation = (getInstanceById, translate, translateMap,
|
|
27
|
+
const renderBaseCalculation = (getInstanceById, translate, translateMap, translation, calculation, style = "full") => renderMathOperation(calculation, value => {
|
|
35
28
|
switch (value.kind) {
|
|
36
29
|
case "Constant":
|
|
37
30
|
return value.Constant.toFixed();
|
|
38
31
|
case "Attribute":
|
|
39
32
|
return getAttribute(getInstanceById, translateMap, value.Attribute, style);
|
|
40
33
|
case "RaceBaseValue":
|
|
41
|
-
return getRaceBaseValue(translate,
|
|
34
|
+
return getRaceBaseValue(translate, translation, style);
|
|
42
35
|
case "PrimaryAttribute":
|
|
43
36
|
switch (style) {
|
|
44
37
|
case "full":
|
|
@@ -62,7 +55,7 @@ const renderBaseCalculation = (getInstanceById, translate, translateMap, idMap,
|
|
|
62
55
|
/**
|
|
63
56
|
* Get a JSON representation of the rules text for a derived characteristic.
|
|
64
57
|
*/
|
|
65
|
-
export const getDerivedCharacteristicEntityDescription = createEntityDescriptionCreator(({ getInstanceById
|
|
58
|
+
export const getDerivedCharacteristicEntityDescription = createEntityDescriptionCreator(({ getInstanceById }, { translate, translateMap }, { content: entry }) => {
|
|
66
59
|
const translation = translateMap(entry.translations);
|
|
67
60
|
if (translation === undefined) {
|
|
68
61
|
return undefined;
|
|
@@ -82,7 +75,7 @@ export const getDerivedCharacteristicEntityDescription = createEntityDescription
|
|
|
82
75
|
items: [
|
|
83
76
|
{
|
|
84
77
|
label: translate("Base Value"),
|
|
85
|
-
value: renderBaseCalculation(getInstanceById, translate, translateMap,
|
|
78
|
+
value: renderBaseCalculation(getInstanceById, translate, translateMap, translation, entry.calculation.base),
|
|
86
79
|
},
|
|
87
80
|
],
|
|
88
81
|
},
|
package/lib/entities/disease.js
CHANGED
|
@@ -25,7 +25,11 @@ export const getDiseaseEntityDescription = createEntityDescriptionCreator(({ get
|
|
|
25
25
|
return undefined;
|
|
26
26
|
}
|
|
27
27
|
return {
|
|
28
|
-
title: translation.name
|
|
28
|
+
title: translation.name +
|
|
29
|
+
parensIf(ensureNonEmpty([
|
|
30
|
+
baseEntry.isChildhoodDisease === true ? translate("childhood disease") : undefined,
|
|
31
|
+
baseEntry.isMagicalDisease === true ? translate("magical disease") : undefined,
|
|
32
|
+
].filter(isNotNullish))?.join(", ")),
|
|
29
33
|
className: "disease",
|
|
30
34
|
body: [
|
|
31
35
|
{
|
package/lib/entities/elixir.js
CHANGED
|
@@ -58,16 +58,18 @@ export const getElixirEntityDescription = createEntityDescriptionCreator(({ getI
|
|
|
58
58
|
},
|
|
59
59
|
{
|
|
60
60
|
label: translate("Quality Levels"),
|
|
61
|
-
value:
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
61
|
+
value: translation.quality_levels.kind === "Plain"
|
|
62
|
+
? translation.quality_levels.Plain.text
|
|
63
|
+
: [
|
|
64
|
+
{
|
|
65
|
+
type: "definitionList",
|
|
66
|
+
style: "nested",
|
|
67
|
+
items: translation.quality_levels.ForEachQualityLevel.qualityLevels.map((effectForLevel, index) => ({
|
|
68
|
+
label: (index + 1).toFixed(),
|
|
69
|
+
value: effectForLevel,
|
|
70
|
+
})),
|
|
71
|
+
},
|
|
72
|
+
],
|
|
71
73
|
},
|
|
72
74
|
],
|
|
73
75
|
},
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type { EquipmentIdentifier, GenMeleeWeapon, GenRangedWeapon, LocaleMeasurementAdjustments } from "optolith-database-schema/gen";
|
|
1
|
+
import type { AmmunitionishIdentifier, EquipmentIdentifier, GenMeleeWeapon, GenRangedWeapon, LocaleMeasurementAdjustments } from "optolith-database-schema/gen";
|
|
2
2
|
import { type TaggedEntity } from "../creator.js";
|
|
3
3
|
import type { GetInstanceById } from "../helpers/getTypes.js";
|
|
4
|
+
import type { LocaleJoin } from "../helpers/locale.js";
|
|
4
5
|
import type { Translate, TranslateMap } from "../helpers/translate.js";
|
|
5
6
|
import type { IdMap, LabeledEntityDescriptionSection, RawEntityDescriptionSectionContent } from "../index.js";
|
|
6
7
|
/**
|
|
@@ -10,15 +11,15 @@ export declare const getEquipmentName: (translate: Translate, translateMap: Tran
|
|
|
10
11
|
/**
|
|
11
12
|
* Render combat values of a melee weapon.
|
|
12
13
|
*/
|
|
13
|
-
export declare const renderMeleeWeapon: <Damage>(translate: Translate, translateMap: TranslateMap, getInstanceById: GetInstanceById<"Attribute" | "CloseCombatTechnique" | "Reach">, measurements: Required<LocaleMeasurementAdjustments>, renderDamage: (damage: Damage) => string, closeCombatTechniqueId: string, use: GenMeleeWeapon<Damage>) => LabeledEntityDescriptionSection<RawEntityDescriptionSectionContent>;
|
|
14
|
+
export declare const renderMeleeWeapon: <Damage>(translate: Translate, translateMap: TranslateMap, localeJoin: LocaleJoin, getInstanceById: GetInstanceById<"Attribute" | "CloseCombatTechnique" | "Reach">, measurements: Required<LocaleMeasurementAdjustments>, renderDamage: (damage: Damage) => string, closeCombatTechniqueId: string, use: GenMeleeWeapon<Damage>) => LabeledEntityDescriptionSection<RawEntityDescriptionSectionContent>;
|
|
14
15
|
/**
|
|
15
16
|
* Render combat values of a ranged weapon.
|
|
16
17
|
*/
|
|
17
|
-
export declare const renderRangedWeapon: <Damage>(translate: Translate, translateMap: TranslateMap, getInstanceById: GetInstanceById<"RangedCombatTechnique" | "
|
|
18
|
+
export declare const renderRangedWeapon: <Damage>(translate: Translate, translateMap: TranslateMap, getInstanceById: GetInstanceById<"RangedCombatTechnique" | AmmunitionishIdentifier["kind"]>, measurements: Required<LocaleMeasurementAdjustments>, renderDamage: (damage: Damage) => string, rangedCombatTechniqueId: string, use: GenRangedWeapon<Damage>) => LabeledEntityDescriptionSection<RawEntityDescriptionSectionContent>;
|
|
18
19
|
/**
|
|
19
20
|
* Get a JSON representation of the rules text for equipment.
|
|
20
21
|
*/
|
|
21
|
-
export declare const getEquipmentEntityDescription: import("../creator.js").EntityDescriptionCreator<"
|
|
22
|
-
getInstanceById: GetInstanceById<"Publication" | "Attribute" | "Reach" | "SocialStatus" | "CloseCombatTechnique" | "RangedCombatTechnique" | "MagicalTradition" | "BlessedTradition" | "DerivedCharacteristic" | "
|
|
22
|
+
export declare const getEquipmentEntityDescription: import("../creator.js").EntityDescriptionCreator<"Ammunition" | "Weapon" | "Armor" | "Animal" | "AnimalCare" | "BandageOrRemedy" | "Book" | "CeremonialItem" | "Clothes" | "ClothingPackage" | "Container" | "EquipmentOfBlessedOnes" | "GemOrPreciousStone" | "IlluminationLightSource" | "IlluminationRefillOrSupply" | "Jewelry" | "Laboratory" | "Liebesspielzeug" | "LuxuryGood" | "MagicalArtifact" | "MusicalInstrument" | "Newspaper" | "OrienteeringAid" | "RopeOrChain" | "Stationery" | "ThievesTool" | "ToolOfTheTrade" | "TravelGearOrTool" | "Vehicle" | "WeaponAccessory" | "WorkingSupernaturalCreature", {
|
|
23
|
+
getInstanceById: GetInstanceById<"Publication" | "Attribute" | "Reach" | "SocialStatus" | "CloseCombatTechnique" | "RangedCombatTechnique" | "MagicalTradition" | "BlessedTradition" | "DerivedCharacteristic" | AmmunitionishIdentifier["kind"] | "Race" | "Culture" | "Profession" | "Skill">;
|
|
23
24
|
idMap: IdMap;
|
|
24
25
|
}, import("../index.js").EntityDescription>;
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { ensureNonEmpty, isNotEmpty } from "@elyukai/utils/array/nonEmpty";
|
|
2
|
+
import { on } from "@elyukai/utils/function";
|
|
2
3
|
import { isNotNullish } from "@elyukai/utils/nullable";
|
|
4
|
+
import { compareNumber } from "@elyukai/utils/ordering";
|
|
3
5
|
import { romanize } from "@elyukai/utils/roman";
|
|
4
6
|
import { sign } from "@elyukai/utils/string/number";
|
|
5
7
|
import { assertExhaustive } from "@elyukai/utils/typeSafety";
|
|
6
8
|
import { mapNullable } from "@optolith/helpers/nullable";
|
|
7
9
|
import { createEntityDescriptionCreator } from "../creator.js";
|
|
8
10
|
import { renderDice, renderDiceAndFlat } from "./partial/dice.js";
|
|
11
|
+
import { attributedName, attributedNameFromInstance } from "./partial/markdown.js";
|
|
9
12
|
import { additionFormatter, subtractionFormatter } from "./partial/mathOperation.js";
|
|
10
13
|
import { parensIf } from "./partial/rated/activatable/parensIf.js";
|
|
11
14
|
import { ResponsiveTextSize } from "./partial/responsiveText.js";
|
|
@@ -91,9 +94,19 @@ const renderPrimaryAttributeAndDamageThreshold = (translateMap, getInstanceById,
|
|
|
91
94
|
const renderAttackParryModifier = (attackModifier, parryModifier) => attackModifier === undefined && parryModifier === undefined
|
|
92
95
|
? "—"
|
|
93
96
|
: `${attackModifier === undefined ? "—" : sign(attackModifier)}/${parryModifier === undefined ? "—" : sign(parryModifier)}`;
|
|
94
|
-
const renderReach = (translateMap, getInstanceById,
|
|
97
|
+
const renderReach = (translateMap, localeJoin, getInstanceById, reaches) => reaches === undefined
|
|
95
98
|
? "—"
|
|
96
|
-
: (
|
|
99
|
+
: localeJoin(reaches
|
|
100
|
+
.map((reach) => {
|
|
101
|
+
const instance = getInstanceById("Reach", reach);
|
|
102
|
+
return [
|
|
103
|
+
instance?.position ?? 0,
|
|
104
|
+
attributedNameFromInstance(translateMap, instance, "equipment", "Reach", reach) ??
|
|
105
|
+
MISSING_VALUE,
|
|
106
|
+
];
|
|
107
|
+
})
|
|
108
|
+
.toSorted(on(e => e[0], compareNumber))
|
|
109
|
+
.map(e => e[1]), "conjunction");
|
|
97
110
|
const renderLength = (translate, measurements, length) => length === undefined
|
|
98
111
|
? "—"
|
|
99
112
|
: translate(".input {$value :number} {{{$value} inches}}", {
|
|
@@ -102,7 +115,7 @@ const renderLength = (translate, measurements, length) => length === undefined
|
|
|
102
115
|
/**
|
|
103
116
|
* Render combat values of a melee weapon.
|
|
104
117
|
*/
|
|
105
|
-
export const renderMeleeWeapon = (translate, translateMap, getInstanceById, measurements, renderDamage, closeCombatTechniqueId, use) => {
|
|
118
|
+
export const renderMeleeWeapon = (translate, translateMap, localeJoin, getInstanceById, measurements, renderDamage, closeCombatTechniqueId, use) => {
|
|
106
119
|
const combatTechnique = getInstanceById("CloseCombatTechnique", closeCombatTechniqueId);
|
|
107
120
|
const fields = combatTechnique?.special ?? {
|
|
108
121
|
can_parry: { kind: "Prohibited" },
|
|
@@ -137,7 +150,7 @@ export const renderMeleeWeapon = (translate, translateMap, getInstanceById, meas
|
|
|
137
150
|
? undefined
|
|
138
151
|
: {
|
|
139
152
|
label: translate("Reach"),
|
|
140
|
-
value: renderReach(translateMap, getInstanceById, use.reach),
|
|
153
|
+
value: renderReach(translateMap, localeJoin, getInstanceById, use.reach),
|
|
141
154
|
},
|
|
142
155
|
fields.has_length.kind === "Prohibited"
|
|
143
156
|
? undefined
|
|
@@ -159,7 +172,7 @@ const renderReloadTime = (translate, reloadTime) => isNotEmpty(reloadTime)
|
|
|
159
172
|
const renderRangeBrackets = (rangeBrackets) => `${rangeBrackets.close.toFixed()}/${rangeBrackets.medium.toFixed()}/${rangeBrackets.far.toFixed()}`;
|
|
160
173
|
const renderAmmunition = (translateMap, getInstanceById, ammunition) => ammunition === undefined
|
|
161
174
|
? "—"
|
|
162
|
-
: (translateMap
|
|
175
|
+
: (attributedName(translateMap, getInstanceById, "equipment", ammunition) ?? MISSING_VALUE);
|
|
163
176
|
const renderMeleeDamage = (translate) => (damage) => renderDiceAndFlat(translate, damage.dice, damage.flat);
|
|
164
177
|
/**
|
|
165
178
|
* Render combat values of a ranged weapon.
|
|
@@ -713,7 +726,7 @@ export const getEquipmentEntityDescription = createEntityDescriptionCreator(({ g
|
|
|
713
726
|
className: "equipment",
|
|
714
727
|
body: [
|
|
715
728
|
...(combatValues?.type === "Weapon"
|
|
716
|
-
? Object.entries(combatValues.values.melee_uses ?? {}).map(([combatTechniqueId, use]) => renderMeleeWeapon(translate, translateMap, getInstanceById, locale.measurementAdjustments, renderMeleeDamage(translate), combatTechniqueId, use))
|
|
729
|
+
? Object.entries(combatValues.values.melee_uses ?? {}).map(([combatTechniqueId, use]) => renderMeleeWeapon(translate, translateMap, locale.join, getInstanceById, locale.measurementAdjustments, renderMeleeDamage(translate), combatTechniqueId, use))
|
|
717
730
|
: []),
|
|
718
731
|
...(combatValues?.type === "Weapon"
|
|
719
732
|
? Object.entries(combatValues.values.ranged_uses ?? {}).map(([combatTechniqueId, use]) => renderRangedWeapon(translate, translateMap, getInstanceById, locale.measurementAdjustments, renderRangedDamage(translate), combatTechniqueId, use))
|
|
@@ -2,6 +2,7 @@ import { on } from "@elyukai/utils/function";
|
|
|
2
2
|
import { assertExhaustive } from "@elyukai/utils/typeSafety";
|
|
3
3
|
import { createEntityDescriptionCreator } from "../creator.js";
|
|
4
4
|
import { getEquipmentName } from "./equipment.js";
|
|
5
|
+
import { attributedInstance } from "./partial/markdown.js";
|
|
5
6
|
const sumAtomicEquipmentCost = (acc, current) => {
|
|
6
7
|
if (typeof acc === "string") {
|
|
7
8
|
return acc;
|
|
@@ -280,7 +281,7 @@ export const getEquipmentPackageEntityDescription = createEntityDescriptionCreat
|
|
|
280
281
|
header: [translation.name, translate("Weight"), translate("Cost")],
|
|
281
282
|
rows: actualItems
|
|
282
283
|
.map((item) => [
|
|
283
|
-
getEquipmentName(translate, translateMap, getInstanceById, item.content),
|
|
284
|
+
attributedInstance(getEquipmentName(translate, translateMap, getInstanceById, item.content), item.content.entity, item.content.id, { context: '"equipment-package"' }),
|
|
284
285
|
renderAtomicWeight(translate, locale.measurementAdjustments, getAtomicEquipmentWeight(item.content)),
|
|
285
286
|
renderAtomicCost(translate, getAtomicEquipmentCost(item.content)),
|
|
286
287
|
])
|
|
@@ -3,6 +3,7 @@ import { assertExhaustive } from "@optolith/helpers/typeSafety";
|
|
|
3
3
|
import { Case } from "tsondb/schema/gen";
|
|
4
4
|
import { createEntityDescriptionCreator } from "../creator.js";
|
|
5
5
|
import { renderEnhancements } from "./partial/enhancements.js";
|
|
6
|
+
import { attributedInstance, attributedName } from "./partial/markdown.js";
|
|
6
7
|
import { renderOneTimeDuration } from "./partial/rated/activatable/duration.js";
|
|
7
8
|
import { renderEffect } from "./partial/rated/activatable/effect.js";
|
|
8
9
|
import { combineGeneratedTextWithStaticTranslation, renderFastPerformanceParameters, renderSlowPerformanceParameters, } from "./partial/rated/activatable/index.js";
|
|
@@ -13,7 +14,7 @@ import { renderImprovementCost } from "./partial/rated/improvementCost.js";
|
|
|
13
14
|
import { renderSkillCheckWithPenalty } from "./partial/rated/skillCheck.js";
|
|
14
15
|
import { ResponsiveTextSize } from "./partial/responsiveText.js";
|
|
15
16
|
const getTextForTraditions = (deps, values) => {
|
|
16
|
-
const getAspectName = (aspectId) => deps.translateMap
|
|
17
|
+
const getAspectName = (aspectId) => attributedName(deps.translateMap, deps.getInstanceById, "traditions", "Aspect", aspectId);
|
|
17
18
|
const text = values
|
|
18
19
|
.map(trad => {
|
|
19
20
|
switch (trad.kind) {
|
|
@@ -25,14 +26,17 @@ const getTextForTraditions = (deps, values) => {
|
|
|
25
26
|
if (name === undefined) {
|
|
26
27
|
return undefined;
|
|
27
28
|
}
|
|
29
|
+
const attributedStringName = attributedInstance(name, "BlessedTradition", trad.Tradition.tradition, {
|
|
30
|
+
context: '"traditions"',
|
|
31
|
+
});
|
|
28
32
|
const aspects = trad.Tradition.aspects
|
|
29
33
|
?.map(getAspectName)
|
|
30
34
|
.filter(isNotNullish)
|
|
31
35
|
.sort(deps.localeCompare) ?? [];
|
|
32
36
|
if (aspects.length === 0) {
|
|
33
|
-
return
|
|
37
|
+
return attributedStringName;
|
|
34
38
|
}
|
|
35
|
-
return `${
|
|
39
|
+
return `${attributedStringName} (${aspects.join(" and ")})`;
|
|
36
40
|
}
|
|
37
41
|
default:
|
|
38
42
|
return assertExhaustive(trad);
|
|
@@ -2,18 +2,17 @@ import { isNotEmpty } from "@elyukai/utils/array/nonEmpty";
|
|
|
2
2
|
import { mapNullable } from "@elyukai/utils/nullable";
|
|
3
3
|
import { Reader } from "@elyukai/utils/reader";
|
|
4
4
|
import { ensureNonEmpty } from "@optolith/helpers/array";
|
|
5
|
+
import { attributedCustomName } from "./markdown.js";
|
|
5
6
|
import { parensIf } from "./rated/activatable/parensIf.js";
|
|
6
7
|
import { MISSING_VALUE } from "./unknown.js";
|
|
7
8
|
const renderCommonnessRatedAdvantageOrDisadvantageName = (translateMap, getInstanceById, entity, item) => {
|
|
8
|
-
const instance = getInstanceById(entity, item.id);
|
|
9
|
-
const instanceTranslation = translateMap(instance?.translations);
|
|
10
9
|
const customTranslation = translateMap(item.translations);
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
customTranslation?.options !== undefined &&
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
return (attributedCustomName(translateMap, getInstanceById, entity, translation => {
|
|
11
|
+
const name = translation.name_in_library ?? translation.name;
|
|
12
|
+
return customTranslation?.options !== undefined && name.endsWith(")")
|
|
13
|
+
? `${name.slice(0, -1)}; ${customTranslation.options})`
|
|
14
|
+
: name + parensIf(customTranslation?.options);
|
|
15
|
+
}, entity, item.id) ?? MISSING_VALUE);
|
|
17
16
|
};
|
|
18
17
|
/**
|
|
19
18
|
* Render the names of either commonness-rated advantages or commonness-rated disadvantages.
|
|
@@ -27,10 +26,7 @@ export const renderCommonnessRatedAdvantagesOrDisadvantages = (entity, items) =>
|
|
|
27
26
|
/**
|
|
28
27
|
* Render the names of commonness-rated advantages and disadvantages together.
|
|
29
28
|
*/
|
|
30
|
-
export const renderCommonnessRatedAdvantagesAndDisadvantages = (advantages, disadvantages) => Reader.asks(({ translate, translateMap, getInstanceById, localeCompare }) => mapNullable(ensureNonEmpty([
|
|
31
|
-
["Advantage", advantages],
|
|
32
|
-
["Disadvantage", disadvantages],
|
|
33
|
-
].flatMap(([entity, items]) => items?.map(item => renderCommonnessRatedAdvantageOrDisadvantageName(translateMap, getInstanceById, entity, item)) ?? [])), renderedItems => renderedItems.toSorted(localeCompare).join(", ")) ?? translate("none"));
|
|
29
|
+
export const renderCommonnessRatedAdvantagesAndDisadvantages = (advantages, disadvantages) => Reader.asks(({ translate, translateMap, getInstanceById, localeCompare }) => mapNullable(ensureNonEmpty([["Advantage", advantages], ["Disadvantage", disadvantages]].flatMap(([entity, items]) => items?.map(item => renderCommonnessRatedAdvantageOrDisadvantageName(translateMap, getInstanceById, entity, item)) ?? [])), renderedItems => renderedItems.toSorted(localeCompare).join(", ")) ?? translate("none"));
|
|
34
30
|
/**
|
|
35
31
|
* Render a value with a possible translation, falling back to explicity rendering the value if no translation is available.
|
|
36
32
|
*/
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isNotEmpty } from "@elyukai/utils/array/nonEmpty";
|
|
2
2
|
import { assertExhaustive } from "@elyukai/utils/typeSafety";
|
|
3
3
|
import { renderDice } from "./dice.js";
|
|
4
|
+
import { attributedName } from "./markdown.js";
|
|
4
5
|
import { parensIf } from "./rated/activatable/parensIf.js";
|
|
5
6
|
import { MISSING_VALUE } from "./unknown.js";
|
|
6
7
|
/**
|
|
@@ -24,16 +25,12 @@ export const renderLaboratoryLevel = (translate, level) => {
|
|
|
24
25
|
export const renderResistance = (translate, translateMap, getInstanceById, idMap, resistance) => {
|
|
25
26
|
switch (resistance.kind) {
|
|
26
27
|
case "Spirit":
|
|
27
|
-
return (translateMap
|
|
28
|
-
?.translations)?.name ?? MISSING_VALUE);
|
|
28
|
+
return (attributedName(translateMap, getInstanceById, "resistance", "DerivedCharacteristic", idMap.DerivedCharacteristic.Spirit) ?? MISSING_VALUE);
|
|
29
29
|
case "Toughness":
|
|
30
|
-
return (translateMap
|
|
31
|
-
?.translations)?.name ?? MISSING_VALUE);
|
|
30
|
+
return (attributedName(translateMap, getInstanceById, "resistance", "DerivedCharacteristic", idMap.DerivedCharacteristic.Toughness) ?? MISSING_VALUE);
|
|
32
31
|
case "LowerOfSpiritAndToughness": {
|
|
33
|
-
const spiritTranslation = translateMap
|
|
34
|
-
|
|
35
|
-
const toughnessTranslation = translateMap(getInstanceById("DerivedCharacteristic", idMap.DerivedCharacteristic.Toughness)
|
|
36
|
-
?.translations)?.name ?? MISSING_VALUE;
|
|
32
|
+
const spiritTranslation = attributedName(translateMap, getInstanceById, "resistance", "DerivedCharacteristic", idMap.DerivedCharacteristic.Spirit) ?? MISSING_VALUE;
|
|
33
|
+
const toughnessTranslation = attributedName(translateMap, getInstanceById, "resistance", "DerivedCharacteristic", idMap.DerivedCharacteristic.Toughness) ?? MISSING_VALUE;
|
|
37
34
|
return translate("{$first} or {$second}, depending on which value is lower", {
|
|
38
35
|
first: spiritTranslation,
|
|
39
36
|
second: toughnessTranslation,
|