@optolith/entity-descriptions 0.4.8 → 0.4.10
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 +15 -0
- package/lib/entities/activatable.js +16 -13
- package/lib/entities/curriculum.js +14 -21
- package/lib/entities/derivedCharacteristic.js +5 -7
- 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.js +2 -2
- package/lib/entities/partial/prerequisites/part.js +8 -3
- 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/reader.d.ts +33 -0
- package/lib/entities/partial/reader.js +13 -0
- package/lib/entities/personalityTrait.js +9 -2
- package/lib/entities/profession.js +25 -25
- package/lib/entities/race.js +4 -6
- package/lib/entities/spell.js +13 -25
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
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.10](https://github.com/Optolith/entity-descriptions/compare/v0.4.9...v0.4.10) (2026-03-17)
|
|
6
|
+
|
|
7
|
+
## [0.4.9](https://github.com/Optolith/entity-descriptions/compare/v0.4.8...v0.4.9) (2026-03-17)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Features
|
|
11
|
+
|
|
12
|
+
* mark generated instances with attributed strings ([85cce91](https://github.com/Optolith/entity-descriptions/commit/85cce917d16ac10833d4dccf7031c2dca4e75d26))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* always append level to activatable name ([10ca82a](https://github.com/Optolith/entity-descriptions/commit/10ca82a1e57afd2d11ba7c32b55a49172e676398))
|
|
18
|
+
* require previous level and fix function typo ([2b12a01](https://github.com/Optolith/entity-descriptions/commit/2b12a01beaaf786843edbbcf42def3d1b1ecb673))
|
|
19
|
+
|
|
5
20
|
## [0.4.8](https://github.com/Optolith/entity-descriptions/compare/v0.4.7...v0.4.8) (2026-03-15)
|
|
6
21
|
|
|
7
22
|
|
|
@@ -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
|
|
@@ -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,21 +1,19 @@
|
|
|
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
|
};
|
|
16
|
+
const getAttribute = (getInstanceById, translateMap, attributeId, style = "full") => attributedCustomName(translateMap, getInstanceById, "derived-characteristic-calculation", translation => renderAttributeNameForStyle(style, translation), "Attribute", attributeId) ?? MISSING_VALUE;
|
|
19
17
|
const getRaceBaseValue = (translate, translateMap, getInstanceById, idMap, raceBaseValue, style = "full") => {
|
|
20
18
|
const instance = getInstanceById("DerivedCharacteristic", idMap.DerivedCharacteristic[raceBaseValue.kind]);
|
|
21
19
|
const translation = translateMap(instance?.translations);
|
|
@@ -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,
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Some helpers to create Markdown content.
|
|
3
|
+
*/
|
|
4
|
+
import type { EntityMap } from "optolith-database-schema/gen";
|
|
5
|
+
import { type IdArgsVariant } from "tsondb/schema/gen";
|
|
6
|
+
import type { GetInstanceById } from "../../helpers/getTypes.js";
|
|
7
|
+
import type { LocaleMap, TranslateMap } from "../../helpers/translate.js";
|
|
8
|
+
/**
|
|
9
|
+
* Creates an attributed string in Markdown format.
|
|
10
|
+
*/
|
|
11
|
+
export declare const attributedString: (text: string, rawAttributes: Partial<Record<string, string>>) => string;
|
|
12
|
+
/**
|
|
13
|
+
* Display an instance of an entity in Markdown format.
|
|
14
|
+
*/
|
|
15
|
+
export declare const attributedInstance: (name: string, entity: string, id?: string, other?: Record<string, string>) => string;
|
|
16
|
+
/**
|
|
17
|
+
* Renders the name of an instance in an attributed string.
|
|
18
|
+
*/
|
|
19
|
+
export declare const attributedName: <E extends { [K in keyof EntityMap]: EntityMap[K] extends {
|
|
20
|
+
translations: LocaleMap<{
|
|
21
|
+
name: string;
|
|
22
|
+
}>;
|
|
23
|
+
} ? K : never; }[keyof EntityMap]>(translateMap: TranslateMap, getInstanceById: GetInstanceById<E>, context: string, ...args: IdArgsVariant<EntityMap, E>) => string | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* Renders the name of an instance according to a custom function.
|
|
26
|
+
*/
|
|
27
|
+
export declare const customName: <E extends { [K in keyof EntityMap]: EntityMap[K] extends {
|
|
28
|
+
translations: LocaleMap<object>;
|
|
29
|
+
} ? K : never; }[keyof EntityMap]>(translateMap: TranslateMap, getInstanceById: GetInstanceById<E>, fn: (translation: EntityMap[E]["translations"][string]) => string, ...args: IdArgsVariant<EntityMap, E>) => string | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* Renders the name of an instance according to a custom function in an attributed string.
|
|
32
|
+
*/
|
|
33
|
+
export declare const attributedCustomName: <E extends { [K in keyof EntityMap]: EntityMap[K] extends {
|
|
34
|
+
translations: LocaleMap<object>;
|
|
35
|
+
} ? K : never; }[keyof EntityMap]>(translateMap: TranslateMap, getInstanceById: GetInstanceById<E>, context: string, fn: (translation: EntityMap[E]["translations"][string]) => string, ...args: IdArgsVariant<EntityMap, E>) => string | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* Renders the name of an instance in an attributed string.
|
|
38
|
+
*/
|
|
39
|
+
export declare const attributedNameFromInstance: (translateMap: TranslateMap, instance: {
|
|
40
|
+
translations: LocaleMap<{
|
|
41
|
+
name: string;
|
|
42
|
+
}>;
|
|
43
|
+
} | undefined, context: string, ...args: IdArgsVariant<EntityMap, keyof EntityMap>) => string | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* Renders the name of an instance in an attributed string.
|
|
46
|
+
*/
|
|
47
|
+
export declare const attributedNameFromTranslation: (translation: {
|
|
48
|
+
name: string;
|
|
49
|
+
} | undefined, context: string, ...args: IdArgsVariant<EntityMap, keyof EntityMap>) => string | undefined;
|
|
50
|
+
/**
|
|
51
|
+
* Renders the name of an instance in an attributed string.
|
|
52
|
+
*/
|
|
53
|
+
export declare const attributedNameFromSafeTranslation: (translation: {
|
|
54
|
+
name: string;
|
|
55
|
+
}, context: string, ...args: IdArgsVariant<EntityMap, keyof EntityMap>) => string;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Some helpers to create Markdown content.
|
|
3
|
+
*/
|
|
4
|
+
import { mapNullable } from "@elyukai/utils/nullable";
|
|
5
|
+
import { normalizedIdArgs } from "tsondb/schema/gen";
|
|
6
|
+
/**
|
|
7
|
+
* Creates an attributed string in Markdown format.
|
|
8
|
+
*/
|
|
9
|
+
export const attributedString = (text, rawAttributes) => `^[${text}](${Object.entries(rawAttributes)
|
|
10
|
+
.filter((pair) => pair[1] !== undefined)
|
|
11
|
+
.map(([key, value]) => `${key}: ${value}`)
|
|
12
|
+
.join(", ")})`;
|
|
13
|
+
/**
|
|
14
|
+
* Display an instance of an entity in Markdown format.
|
|
15
|
+
*/
|
|
16
|
+
export const attributedInstance = (name, entity, id, other) => attributedString(name, {
|
|
17
|
+
...other,
|
|
18
|
+
entity: `"${entity}"`,
|
|
19
|
+
instance: id === undefined ? undefined : `"${id}"`,
|
|
20
|
+
});
|
|
21
|
+
/**
|
|
22
|
+
* Renders the name of an instance in an attributed string.
|
|
23
|
+
*/
|
|
24
|
+
export const attributedName = (translateMap, getInstanceById, context, ...args) => {
|
|
25
|
+
const id = normalizedIdArgs(args);
|
|
26
|
+
return mapNullable(translateMap(getInstanceById(...args)?.translations)?.name, name => attributedInstance(name, id.entityName, id.id, { context: `"${context}"` }));
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Renders the name of an instance according to a custom function.
|
|
30
|
+
*/
|
|
31
|
+
export const customName = (translateMap, getInstanceById, fn, ...args) => mapNullable(translateMap(getInstanceById(...args)?.translations), translation => fn(translation));
|
|
32
|
+
/**
|
|
33
|
+
* Renders the name of an instance according to a custom function in an attributed string.
|
|
34
|
+
*/
|
|
35
|
+
export const attributedCustomName = (translateMap, getInstanceById, context, fn, ...args) => {
|
|
36
|
+
const id = normalizedIdArgs(args);
|
|
37
|
+
return mapNullable(translateMap(getInstanceById(...args)?.translations), translation => attributedInstance(fn(translation), id.entityName, id.id, { context: `"${context}"` }));
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Renders the name of an instance in an attributed string.
|
|
41
|
+
*/
|
|
42
|
+
export const attributedNameFromInstance = (translateMap, instance, context, ...args) => {
|
|
43
|
+
const id = normalizedIdArgs(args);
|
|
44
|
+
return mapNullable(translateMap(instance?.translations)?.name, name => attributedInstance(name, id.entityName, id.id, { context: `"${context}"` }));
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Renders the name of an instance in an attributed string.
|
|
48
|
+
*/
|
|
49
|
+
export const attributedNameFromTranslation = (translation, context, ...args) => {
|
|
50
|
+
const id = normalizedIdArgs(args);
|
|
51
|
+
return mapNullable(translation?.name, name => attributedInstance(name, id.entityName, id.id, { context: `"${context}"` }));
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Renders the name of an instance in an attributed string.
|
|
55
|
+
*/
|
|
56
|
+
export const attributedNameFromSafeTranslation = (translation, context, ...args) => {
|
|
57
|
+
const id = normalizedIdArgs(args);
|
|
58
|
+
return attributedInstance(translation.name, id.entityName, id.id, { context: `"${context}"` });
|
|
59
|
+
};
|
|
@@ -85,7 +85,7 @@ const printPrerequisitesForLevels = (printPrerequisite, locale, value, printPrev
|
|
|
85
85
|
level: i + 2,
|
|
86
86
|
prerequisite: {
|
|
87
87
|
kind: "Single",
|
|
88
|
-
Single: printPreviousLevelPrerequisites.
|
|
88
|
+
Single: printPreviousLevelPrerequisites.createPrerequisite(i + 1),
|
|
89
89
|
},
|
|
90
90
|
}));
|
|
91
91
|
const groupedByLevel = Map.groupBy([...value, ...previousLevelPrerequisites], prerequisite => prerequisite.level);
|
|
@@ -153,7 +153,7 @@ export const printGeneralPrerequisites = (getInstanceById, getResolvedSelectOpti
|
|
|
153
153
|
? undefined
|
|
154
154
|
: {
|
|
155
155
|
levels: printPreviousLevelPrerequisites.levels,
|
|
156
|
-
|
|
156
|
+
createPrerequisite: (level) => ({
|
|
157
157
|
kind: "Activatable",
|
|
158
158
|
Activatable: {
|
|
159
159
|
id: printPreviousLevelPrerequisites.id,
|
|
@@ -5,6 +5,7 @@ import { compareNumber, reduceCompare } from "@elyukai/utils/ordering";
|
|
|
5
5
|
import { assertExhaustive } from "@optolith/helpers/typeSafety";
|
|
6
6
|
import { fromUniformCase } from "tsondb/schema/gen";
|
|
7
7
|
import { renderActivatableNameComponents, renderActivatableNameComponentsCombinedIfPossible, } from "../activatableNameChunks.js";
|
|
8
|
+
import { attributedInstance } from "../markdown.js";
|
|
8
9
|
/**
|
|
9
10
|
* Type guard for prerequisite parts with a value object.
|
|
10
11
|
*/
|
|
@@ -18,15 +19,19 @@ const countFurtherToCombined = (part, remainingParts) => {
|
|
|
18
19
|
remainingParts.length
|
|
19
20
|
: indexHasDifferentBaseValues;
|
|
20
21
|
};
|
|
22
|
+
const wrapActivatableInAttributedString = (text, id) => attributedInstance(text, id.kind, fromUniformCase(id), { context: '"prerequisite"' });
|
|
21
23
|
const joinAdjacentParts = (translateMap, localeCompare, part, remainingParts) => {
|
|
22
24
|
const valuesWithSameBase = remainingParts.slice(0, countFurtherToCombined(part, remainingParts));
|
|
23
25
|
if (valuesWithSameBase.length > 0) {
|
|
24
26
|
return [
|
|
25
|
-
renderActivatableNameComponentsCombinedIfPossible(translateMap, [part.value, ...valuesWithSameBase.map(p => p.value)], true, list => list.toSorted(localeCompare).join(", ")),
|
|
27
|
+
wrapActivatableInAttributedString(renderActivatableNameComponentsCombinedIfPossible(translateMap, [part.value, ...valuesWithSameBase.map(p => p.value)], true, list => list.toSorted(localeCompare).join(", ")), part.value.id),
|
|
26
28
|
valuesWithSameBase.length,
|
|
27
29
|
];
|
|
28
30
|
}
|
|
29
|
-
return [
|
|
31
|
+
return [
|
|
32
|
+
wrapActivatableInAttributedString(renderActivatableNameComponents(translateMap, part.value, true), part.value.id),
|
|
33
|
+
0,
|
|
34
|
+
];
|
|
30
35
|
};
|
|
31
36
|
const appendBySentenceType = (previouslyRendered, currentRendered, sentenceType, isLast) => {
|
|
32
37
|
switch (sentenceType?.kind) {
|
|
@@ -76,7 +81,7 @@ const appendPrerequisitePartGroup = (previous, translateMap, localeCompare, part
|
|
|
76
81
|
return parts.reduce((acc, current, i, arr) => appendBySentenceType(acc, (current.label ?? "") +
|
|
77
82
|
(typeof current.value === "string"
|
|
78
83
|
? current.value
|
|
79
|
-
: renderActivatableNameComponents(translateMap, current.value, true)), current.sentenceType, isLast && i === arr.length - 1), previous);
|
|
84
|
+
: wrapActivatableInAttributedString(renderActivatableNameComponents(translateMap, current.value, true), current.value.id)), current.sentenceType, isLast && i === arr.length - 1), previous);
|
|
80
85
|
}
|
|
81
86
|
};
|
|
82
87
|
/**
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { attributedNameFromSafeTranslation } from "../../markdown.js";
|
|
1
2
|
import { printDisplayOption } from "../displayOption.js";
|
|
2
3
|
/**
|
|
3
4
|
* Get the translation of a culture prerequisite.
|
|
@@ -13,7 +14,7 @@ export const printCulturePrerequisite = (getInstanceById, locale, prerequisite)
|
|
|
13
14
|
}
|
|
14
15
|
return {
|
|
15
16
|
label: `${locale.translate("Culture")} `,
|
|
16
|
-
value: cultureTranslation.
|
|
17
|
+
value: attributedNameFromSafeTranslation(cultureTranslation, "prerequisite", "Culture", prerequisite.id),
|
|
17
18
|
sentenceType: undefined,
|
|
18
19
|
isMeta: false,
|
|
19
20
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { attributedNameFromSafeTranslation } from "../../markdown.js";
|
|
1
2
|
import { printDisplayOption } from "../displayOption.js";
|
|
2
3
|
/**
|
|
3
4
|
* Get the translation of a personality trait prerequisite.
|
|
@@ -11,7 +12,7 @@ export const printPersonalityTraitPrerequisite = (getInstanceById, locale, prere
|
|
|
11
12
|
if (personalityTrait === undefined || personalityTraitTranslation === undefined) {
|
|
12
13
|
return undefined;
|
|
13
14
|
}
|
|
14
|
-
const name = `${personalityTraitTranslation.
|
|
15
|
+
const name = `${attributedNameFromSafeTranslation(personalityTraitTranslation, "prerequisite", "PersonalityTrait", prerequisite.id)} (${locale.translate("Level {$level}", {
|
|
15
16
|
level: personalityTrait.level,
|
|
16
17
|
})})`;
|
|
17
18
|
return {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { attributedInstance } from "../../markdown.js";
|
|
1
2
|
import { getProfessionName } from "../../professions.js";
|
|
2
3
|
import { printDisplayOption } from "../displayOption.js";
|
|
3
4
|
/**
|
|
@@ -12,7 +13,9 @@ export const printProfessionPrerequisite = (getChildInstancesForInstanceId, loca
|
|
|
12
13
|
return undefined;
|
|
13
14
|
}
|
|
14
15
|
return {
|
|
15
|
-
value: professionName,
|
|
16
|
+
value: attributedInstance(professionName, "Profession", prerequisite.id, {
|
|
17
|
+
context: '"prerequisite"',
|
|
18
|
+
}),
|
|
16
19
|
sentenceType: undefined,
|
|
17
20
|
isMeta: false,
|
|
18
21
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { attributedNameFromSafeTranslation } from "../../markdown.js";
|
|
1
2
|
import { printDisplayOption } from "../displayOption.js";
|
|
2
3
|
/**
|
|
3
4
|
* Get the translation of a race prerequisite.
|
|
@@ -13,7 +14,7 @@ export const printRacePrerequisite = (getInstanceById, locale, prerequisite) =>
|
|
|
13
14
|
}
|
|
14
15
|
return {
|
|
15
16
|
label: `${locale.translate("Race")} `,
|
|
16
|
-
value: raceTranslation.
|
|
17
|
+
value: attributedNameFromSafeTranslation(raceTranslation, "prerequisite", "Race", prerequisite.id),
|
|
17
18
|
sentenceType: undefined,
|
|
18
19
|
isMeta: false,
|
|
19
20
|
};
|