style: fix potential bugs reported by eslint

This commit is contained in:
Lukas Obermann
2026-03-13 18:15:39 +01:00
parent 1b2d21ba68
commit 5f34eaafe5
76 changed files with 1037 additions and 1616 deletions
+58 -42
View File
@@ -2,11 +2,12 @@
import js from "@eslint/js"
import eslintConfigPrettier from "eslint-config-prettier"
import jsdoc from "eslint-plugin-jsdoc"
import { defineConfig, globalIgnores } from "eslint/config"
import globals from "globals"
import ts from "typescript-eslint"
/** @type {import('eslint').Linter.Config[]} */
export default [
export default defineConfig([
globalIgnores(["lib/*", "scripts/*", "*.config.js"]),
js.configs.recommended,
...ts.configs.recommended,
jsdoc.configs["flat/recommended-typescript-error"],
@@ -101,7 +102,54 @@ export default [
"symbol-description": "error",
yoda: "error",
// TypeScript
// JSDoc
"jsdoc/check-tag-names": [
"error",
{
definedTags: ["main", "integer", "minItems"],
},
],
"jsdoc/require-jsdoc": [
"error",
{
contexts: [
"TSInterfaceDeclaration",
"TSMethodSignature",
"TSEnumDeclaration",
"TSTypeAliasDeclaration",
"ExportNamedDeclaration > VariableDeclaration",
],
publicOnly: true,
require: {
ArrowFunctionExpression: true,
ClassDeclaration: true,
ClassExpression: true,
FunctionDeclaration: true,
FunctionExpression: true,
MethodDefinition: true,
},
},
],
"jsdoc/require-param": "off",
"jsdoc/require-returns": "off",
"jsdoc/require-description": [
"error",
{
contexts: ["any"],
},
],
},
},
{
ignores: ["eslint.config.js"],
extends: ts.configs.strictTypeChecked,
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
rules: {
"@typescript-eslint/adjacent-overload-signatures": "error",
"@typescript-eslint/array-type": "error",
"@typescript-eslint/consistent-type-assertions": "error",
@@ -146,47 +194,15 @@ export default [
"@typescript-eslint/require-array-sort-compare": "error",
"@typescript-eslint/strict-boolean-expressions": "error",
"@typescript-eslint/switch-exhaustiveness-check": "error",
// JSDoc
"jsdoc/check-tag-names": [
"error",
{
definedTags: ["main", "integer", "minItems"],
},
],
"jsdoc/require-jsdoc": [
"error",
{
contexts: [
"TSInterfaceDeclaration",
"TSMethodSignature",
"TSEnumDeclaration",
"TSTypeAliasDeclaration",
"ExportNamedDeclaration > VariableDeclaration",
],
publicOnly: true,
require: {
ArrowFunctionExpression: true,
ClassDeclaration: true,
ClassExpression: true,
FunctionDeclaration: true,
FunctionExpression: true,
MethodDefinition: true,
},
},
],
"jsdoc/require-param": "off",
"jsdoc/require-returns": "off",
"jsdoc/require-description": [
"error",
{
contexts: ["any"],
},
],
"@typescript-eslint/consistent-type-exports": "error",
"@typescript-eslint/consistent-type-imports": "error",
},
},
eslintConfigPrettier,
{
ignores: ["lib/*", "scripts/*", "*.config.js"],
files: ["test/**/*.{js,mjs,cjs,ts,mts,cts}"],
rules: {
"@typescript-eslint/no-floating-promises": "off",
},
},
]
])
+8 -8
View File
@@ -9,7 +9,7 @@
"version": "0.4.0",
"license": "MPL-2.0",
"dependencies": {
"@elyukai/utils": "^0.3.0",
"@elyukai/utils": "^0.3.1",
"@optolith/adventure-points": "^0.1.1",
"@optolith/helpers": "^0.2.2",
"messageformat": "^4.0.0",
@@ -22,7 +22,7 @@
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-jsdoc": "^62.8.0",
"globals": "^17.4.0",
"optolith-database-schema": "^0.34.17",
"optolith-database-schema": "^0.35.0",
"prettier": "^3.8.1",
"tsx": "^4.21.0",
"typescript": "^5.9.3",
@@ -82,9 +82,9 @@
}
},
"node_modules/@elyukai/utils": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@elyukai/utils/-/utils-0.3.0.tgz",
"integrity": "sha512-NLzeqPjO27qUE1OrGKdEHIs8p++Lg+Zl0xyFb/jiMNtxAjofvx6MBPNSpej+9Ngc9c38I3AUhvpXiXaFmyIg2Q==",
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@elyukai/utils/-/utils-0.3.1.tgz",
"integrity": "sha512-OzfEkxVu8uYbPfrrUPaYLMVMh27dyx0VqK9gwULjDnZdMb9egtFSPeMQandGXDjEXGfb+USa4iWP8zSudh0QwQ==",
"license": "MPL-2.0"
},
"node_modules/@es-joy/jsdoccomment": {
@@ -3862,9 +3862,9 @@
}
},
"node_modules/optolith-database-schema": {
"version": "0.34.17",
"resolved": "https://registry.npmjs.org/optolith-database-schema/-/optolith-database-schema-0.34.17.tgz",
"integrity": "sha512-RhsLdWLcWKbDrmCEfim5iovj1piwk+uU0zkriRJkTnTCcpkLfAlXZ2YlepC2OcoVQjzWViM9EaRYlZgOoN8/OQ==",
"version": "0.35.0",
"resolved": "https://registry.npmjs.org/optolith-database-schema/-/optolith-database-schema-0.35.0.tgz",
"integrity": "sha512-36VAqVSY4PXmz+IUiekldYVhz9CTaN166elqpgYnJaNd8UP+R0ew1gM8vjiwande1gHutFXuiYEF4/09rEpbEw==",
"dev": true,
"license": "MPL-2.0",
"dependencies": {
+2 -2
View File
@@ -33,7 +33,7 @@
},
"homepage": "https://github.com/Optolith/entity-descriptions#readme",
"dependencies": {
"@elyukai/utils": "^0.3.0",
"@elyukai/utils": "^0.3.1",
"@optolith/adventure-points": "^0.1.1",
"@optolith/helpers": "^0.2.2",
"messageformat": "^4.0.0",
@@ -46,7 +46,7 @@
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-jsdoc": "^62.8.0",
"globals": "^17.4.0",
"optolith-database-schema": "^0.34.17",
"optolith-database-schema": "^0.35.0",
"prettier": "^3.8.1",
"tsx": "^4.21.0",
"typescript": "^5.9.3",
+3 -1
View File
@@ -68,12 +68,14 @@ const unitListFormat = new Intl.ListFormat(localeId, {
const collator = new Intl.Collator(localeId, { usage: "sort" })
const dateFormatter = new Intl.DateTimeFormat(localeId)
const numberFormatter = new Intl.NumberFormat(localeId)
const localeEnv: LocaleEnvironment = {
id: localeId,
format: (text, args) => new MessageFormat(localeId, text, { bidiIsolation: "none" }).format(args),
compare: collator.compare.bind(collator),
formatDate: dateFormatter.format.bind(dateFormatter),
formatNumber: numberFormatter.format.bind(numberFormatter),
formatDate: date => dateFormatter.format(new Date(date)),
translate: (key, ...rest) =>
new MessageFormat(localeId, localeInstance.translations?.[key] ?? key, {
bidiIsolation: "none",
+10 -10
View File
@@ -2,18 +2,18 @@ import { isNotNullish } from "@elyukai/utils/nullable"
import { assertExhaustive } from "@elyukai/utils/typeSafety"
import type { EntityMap } from "optolith-database-schema/gen"
import type { GetInstanceById } from "./helpers/getTypes.js"
import { LocaleEnvironment } from "./helpers/locale.js"
import {
import type { LocaleEnvironment } from "./helpers/locale.js"
import type {
DefinitionListEntityDescriptionSection,
EntityDescription,
EntityDescriptionSection,
EntityDescriptionSectionContent,
NestedDefinitionListEntityDescriptionSection,
RawDefinitionListEntityDescriptionSection,
RawEntityDescription,
type DefinitionListEntityDescriptionSection,
type EntityDescriptionSection,
type EntityDescriptionSectionContent,
type NestedDefinitionListEntityDescriptionSection,
type RawDefinitionListEntityDescriptionSection,
type RawEntityDescriptionSection,
type RawEntityDescriptionSectionContent,
type RawNestedDefinitionListEntityDescriptionSection,
RawEntityDescriptionSection,
RawEntityDescriptionSectionContent,
RawNestedDefinitionListEntityDescriptionSection,
} from "./index.js"
import { getReferencesTranslation } from "./references/index.js"
+77 -181
View File
@@ -41,11 +41,7 @@ import type {
import { Case, fromUniformCase } from "tsondb/schema/gen"
import { createEntityDescriptionCreator } from "../creator.js"
import type { GetAllInstances, GetInstanceById } from "../helpers/getTypes.js"
import type {
LocaleCompare,
LocaleEnvironment,
LocaleJoin,
} from "../helpers/locale.js"
import type { LocaleCompare, LocaleEnvironment, LocaleJoin } from "../helpers/locale.js"
import type { Translate, TranslateMap } from "../helpers/translate.js"
import type {
GetAllResolvedNewSkillApplications,
@@ -122,9 +118,8 @@ const renderPropertyValue = (
return translate("As chosen")
case "Fixed":
return (
translateMap(
getInstanceById("Property", propertyDecl.Fixed)?.translations,
)?.name ?? MISSING_VALUE
translateMap(getInstanceById("Property", propertyDecl.Fixed)?.translations)?.name ??
MISSING_VALUE
)
default:
return assertExhaustive(propertyDecl)
@@ -149,9 +144,7 @@ const renderPenaltyByAttackLabel = (
}
const renderPenaltyValue = (
getInstanceById: GetInstanceById<
CombatRelatedSpecialAbilityIdentifier["kind"]
>,
getInstanceById: GetInstanceById<CombatRelatedSpecialAbilityIdentifier["kind"]>,
translate: Translate,
translateMap: TranslateMap,
name: string,
@@ -161,15 +154,11 @@ const renderPenaltyValue = (
case "Single":
return (
sign(penalty.Single.value) +
(penalty.Single.applies_to_parry === true
? ` (${translate("for parry")})`
: "")
(penalty.Single.applies_to_parry === true ? ` (${translate("for parry")})` : "")
)
case "ByHandedness": {
const appendParry =
penalty.ByHandedness.applies_to_parry === true
? `; ${translate("for parry")}`
: ""
penalty.ByHandedness.applies_to_parry === true ? `; ${translate("for parry")}` : ""
return `${sign(penalty.ByHandedness.one_handed)} (${
translate("one-handed weapon") + appendParry
@@ -179,13 +168,8 @@ const renderPenaltyValue = (
}
case "ByActivation": {
return `${sign(penalty.ByActivation.active)}/${sign(penalty.ByActivation.inactive)} (${[
penalty.ByActivation.applies_to_parry === true
? translate("for parry")
: undefined,
translate(
"for secondary fighters with/without special ability {$name}",
{ name },
),
penalty.ByActivation.applies_to_parry === true ? translate("for parry") : undefined,
translate("for secondary fighters with/without special ability {$name}", { name }),
]
.filter(isNotNullish)
.join("; ")})`
@@ -193,9 +177,7 @@ const renderPenaltyValue = (
case "Selection":
switch (penalty.Selection.options.kind) {
case "Specific":
return penalty.Selection.options.Specific.list
.map(option => sign(option.value))
.join("/")
return penalty.Selection.options.Specific.list.map(option => sign(option.value)).join("/")
case "Range":
return translate("{$start} to {$end}", {
start: sign(penalty.Selection.options.Range.minimum),
@@ -216,24 +198,19 @@ const renderPenaltyValue = (
}
const externalName =
translateMap(
getInstanceById(external.kind, fromUniformCase(external))
?.translations,
)?.name ?? MISSING_VALUE
translateMap(getInstanceById(external.kind, fromUniformCase(external))?.translations)
?.name ?? MISSING_VALUE
return `${main} (${translate(
"depending on the level of the special ability {$name}",
{
name: externalName,
},
)})`
return `${main} (${translate("depending on the level of the special ability {$name}", {
name: externalName,
})})`
}
case "ByAttack": {
const offset = penalty.ByAttack.initial_order ?? 1
return penalty.ByAttack.list
.map(
(penaltyByAttack, index) =>
`${penaltyByAttack.value} (${renderPenaltyByAttackLabel(
`${penaltyByAttack.value.toFixed()} (${renderPenaltyByAttackLabel(
translate,
penalty.ByAttack.attack_replacement,
index + offset,
@@ -287,8 +264,7 @@ const wrapInParens = (args: (string | undefined)[], append = ""): string => {
return ` (${filteredArgs.join(", ") + append})`
}
const addSpaceIfNoCommaAtStart = (str: string): string =>
str.startsWith(",") ? str : ` ${str}`
const addSpaceIfNoCommaAtStart = (str: string): string => (str.startsWith(",") ? str : ` ${str}`)
const renderApplicableCombatTechniquesRestriction = <
T extends
@@ -297,9 +273,7 @@ const renderApplicableCombatTechniquesRestriction = <
| ApplicableRangedCombatTechniquesRestriction
| ApplicableSpecificCombatTechniquesRestriction,
>(
getInstanceById: GetInstanceById<
"CloseCombatTechnique" | "RangedCombatTechnique" | "Race"
>,
getInstanceById: GetInstanceById<"CloseCombatTechnique" | "RangedCombatTechnique" | "Race">,
locale: LocaleEnvironment,
main: string,
restriction: T,
@@ -313,18 +287,9 @@ const renderApplicableCombatTechniquesRestriction = <
): string => {
switch (restriction.kind) {
case "Improvised":
return (
main +
wrapInParens([locale.translate("only improvised weapons"), weapons])
)
return main + wrapInParens([locale.translate("only improvised weapons"), weapons])
case "PointedBlade":
return (
main +
wrapInParens([
locale.translate("weapon must have a pointed blade"),
weapons,
])
)
return main + wrapInParens([locale.translate("weapon must have a pointed blade"), weapons])
case "Mount":
if (weapons === undefined) {
return `${main} ${locale.translate("while mounted")}`
@@ -333,8 +298,7 @@ const renderApplicableCombatTechniquesRestriction = <
}
case "Race": {
const race = getInstanceById("Race", restriction.Race)
const raceName =
locale.translateMap(race?.translations)?.name ?? MISSING_VALUE
const raceName = locale.translateMap(race?.translations)?.name ?? MISSING_VALUE
return (
main +
@@ -357,9 +321,7 @@ const renderApplicableCombatTechniquesRestriction = <
.map(
id =>
locale.translateMap(
getExcludedInstance?.(
id as (CombatTechniqueIdentifier & string) & string,
)?.translations,
getExcludedInstance?.(id as CombatTechniqueIdentifier & string)?.translations,
)?.name ?? MISSING_VALUE,
)
.toSorted(locale.compare),
@@ -380,28 +342,18 @@ const renderApplicableCombatTechniquesRestriction = <
wrapInParens([weapons])
)
case "TwoHanded":
return (
locale.translate("All Two-Handed Weapons") + wrapInParens([weapons])
)
return locale.translate("All Two-Handed Weapons") + wrapInParens([weapons])
case "ParryingWeapon":
return locale.translate("All Parrying Weapons") + wrapInParens([weapons])
case "Level": {
const nameWithLevel = `${translation} ${romanize(restriction.Level.level)}`
const nameWithLevel = `${translation.name_in_library ?? translation.name} ${romanize(restriction.Level.level)}`
return (
main +
wrapInParens([
locale.translate("only {$nameWithLevel}", { nameWithLevel }),
weapons,
])
main + wrapInParens([locale.translate("only {$nameWithLevel}", { nameWithLevel }), weapons])
)
}
case "OneBluntSide":
return (
main +
wrapInParens([
locale.translate("only those with at least one blunt side"),
weapons,
])
main + wrapInParens([locale.translate("only those with at least one blunt side"), weapons])
)
default:
return assertExhaustive(restriction)
@@ -474,12 +426,8 @@ const renderApplicableCombatTechniquesValue = (
case "Specific": {
return applicableCombatTechniques.Specific.list
.map(specific => {
const entry = getInstanceById(
specific.id.kind,
fromUniformCase(specific.id),
)
const main =
locale.translateMap(entry?.translations)?.name ?? MISSING_VALUE
const entry = getInstanceById(specific.id.kind, fromUniformCase(specific.id))
const main = locale.translateMap(entry?.translations)?.name ?? MISSING_VALUE
const mainWithRestriction =
specific.restriction === undefined
? main
@@ -496,10 +444,8 @@ const renderApplicableCombatTechniquesValue = (
specific.weapons
.map(
weapon =>
locale.translateMap(
getInstanceById("Weapon", weapon)
?.translations,
)?.name ?? MISSING_VALUE,
locale.translateMap(getInstanceById("Weapon", weapon)?.translations)
?.name ?? MISSING_VALUE,
)
.toSorted(locale.compare),
"conjunction",
@@ -540,9 +486,7 @@ const renderVolumeValue = (
case "ByLevel":
return translate("{$points} points for levels {$levels}", {
points: volume.ByLevel.list.map(item => item.points).join("/"),
levels: volume.ByLevel.list
.map((_, index) => romanize(index + 1))
.join("/"),
levels: volume.ByLevel.list.map((_, index) => romanize(index + 1)).join("/"),
})
case "Map":
return renderResponsiveMap(
@@ -570,11 +514,7 @@ const renderVolumeValue = (
})} ${translate("for")} ${groups
.map(group =>
group[1]
.map(
groupItem =>
translateMap(groupItem.content.translations)?.name ??
MISSING_VALUE,
)
.map(groupItem => translateMap(groupItem.content.translations)?.name ?? MISSING_VALUE)
.join(", "),
)
.join(separator)}`
@@ -618,28 +558,26 @@ const renderArcaneEnergyCost = (
),
})
const wrapInPerLevel: (prev: (value: number) => string) => string =
(() => {
if (cost.Fixed.per_level === undefined) {
return prev => prev(cost.Fixed.value)
}
const wrapInPerLevel: (prev: (value: number) => string) => string = (() => {
if (cost.Fixed.per_level === undefined) {
return prev => prev(cost.Fixed.value)
}
switch (cost.Fixed.per_level?.kind) {
case "Compressed":
return prev =>
translate("{$cost} per level", { cost: prev(cost.Fixed.value) })
case "Verbose":
return prev =>
Array.from({ length: levels ?? 1 }, (_, index) =>
translate("{$cost} for level {$level}", {
cost: prev(cost.Fixed.value),
level: romanize(index + 1),
}),
).join("; ")
default:
return assertExhaustive(cost.Fixed.per_level)
}
})()
switch (cost.Fixed.per_level.kind) {
case "Compressed":
return prev => translate("{$cost} per level", { cost: prev(cost.Fixed.value) })
case "Verbose":
return prev =>
Array.from({ length: levels ?? 1 }, (_, index) =>
translate("{$cost} for level {$level}", {
cost: prev(cost.Fixed.value),
level: romanize(index + 1),
}),
).join("; ")
default:
return assertExhaustive(cost.Fixed.per_level)
}
})()
const noteInParens = parensIf(
mapNullable(translateMap(cost.Fixed.translations)?.note, note =>
@@ -647,10 +585,7 @@ const renderArcaneEnergyCost = (
),
)
return (
wrapInPerLevel(value => wrapInInterval(translationWrapper(value))) +
noteInParens
)
return wrapInPerLevel(value => wrapInInterval(translationWrapper(value))) + noteInParens
}
case "Constant":
return (
@@ -708,14 +643,12 @@ const renderArcaneEnergyCost = (
)
case "Indefinite":
return (
mapNullable(
translateMap(cost.Indefinite.translations)?.description,
description => getResponsiveText(description, responsiveTextSize),
) +
(mapNullable(translateMap(cost.Indefinite.translations)?.description, description =>
getResponsiveText(description, responsiveTextSize),
) ?? MISSING_VALUE) +
mapNullableDefault(
cost.Indefinite.modifier,
modifier =>
` + ${translate("{$value} AE", { value: modifier.value })}`,
modifier => ` + ${translate("{$value} AE", { value: modifier.value })}`,
"",
)
)
@@ -724,11 +657,10 @@ const renderArcaneEnergyCost = (
value: localeJoin(
cost.Disjunction.options.map(
option =>
option.value +
option.value.toFixed() +
mapNullableDefault(
translateMap(option.translations)?.note,
note =>
parensIf(getResponsiveTextOptional(note, responsiveTextSize)),
note => parensIf(getResponsiveTextOptional(note, responsiveTextSize)),
"",
),
),
@@ -746,6 +678,7 @@ const renderArcaneEnergyCost = (
translate(", {$value} of which are permanent", {
value: values,
}),
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- is checked beforehand
getAdditionalValue: option => option.permanent_value!,
}
: undefined,
@@ -763,9 +696,8 @@ const renderArcaneEnergyCost = (
cost: translate("{$value} AE", {
value: cost.ByLevel.levels.map(level => level.value).join("/"),
}),
level: Array.from(
{ length: cost.ByLevel.levels.length },
(_, index) => romanize(index + 1),
level: Array.from({ length: cost.ByLevel.levels.length }, (_, index) =>
romanize(index + 1),
),
})
case "Verbose":
@@ -818,8 +750,7 @@ const renderBindingCost = (
case "DerivedFromSelection": {
const groups = Map.groupBy(
getAllResolvedSelectOptions(),
option =>
option.content.binding_cost ?? cost.DerivedFromSelection.fallback,
option => option.content.binding_cost ?? cost.DerivedFromSelection.fallback,
)
.entries()
.toArray()
@@ -832,11 +763,7 @@ const renderBindingCost = (
})} ${translate("for")} ${groups
.map(group =>
group[1]
.map(
groupItem =>
translateMap(groupItem.content.translations)?.name ??
MISSING_VALUE,
)
.map(groupItem => translateMap(groupItem.content.translations)?.name ?? MISSING_VALUE)
.toSorted(localeCompare)
.join(", "),
)
@@ -847,10 +774,7 @@ const renderBindingCost = (
}
}
const renderLifePointsCost = (
translate: Translate,
cost: LifePointsCost | undefined,
): string =>
const renderLifePointsCost = (translate: Translate, cost: LifePointsCost | undefined): string =>
cost === undefined
? ""
: // eslint-disable-next-line no-irregular-whitespace
@@ -889,8 +813,7 @@ const renderCost = (
responsiveTextSize,
levels,
cost.ArcaneEnergyCost.ae_cost,
) +
renderLifePointsCost(translate, cost.ArcaneEnergyCost.lp_cost)
) + renderLifePointsCost(translate, cost.ArcaneEnergyCost.lp_cost)
: renderArcaneEnergyCost(
translate,
translateMap,
@@ -956,19 +879,12 @@ export const getActivatableEntityDescription = createEntityDescriptionCreator<
}
>(
(
{
getInstanceById,
getAllInstances,
getResolvedSelectOptionById,
getAllResolvedSelectOptions,
},
{ getInstanceById, getAllInstances, getResolvedSelectOptionById, getAllResolvedSelectOptions },
locale,
{ entity: entityName, content: entry, id },
) => {
const { translate, translateMap } = locale
const translation = translateMap<BaseActivatableTranslation>(
entry.translations,
)
const translation = translateMap<BaseActivatableTranslation>(entry.translations)
const responsiveTextSize: ResponsiveTextSize = ResponsiveTextSize.Full
if (translation === undefined) {
@@ -983,9 +899,7 @@ export const getActivatableEntityDescription = createEntityDescriptionCreator<
title:
translation.name_in_library ??
translation.name +
(baseEntry.levels !== undefined
? ` I${romanize(baseEntry.levels)}`
: ""),
(baseEntry.levels !== undefined ? ` I${romanize(baseEntry.levels)}` : ""),
subtitle: mapNullable(baseEntry.usage_type, usageType => {
switch (usageType.kind) {
case "Passive":
@@ -1032,8 +946,8 @@ export const getActivatableEntityDescription = createEntityDescriptionCreator<
mapNullable(baseEntry.aspect, aspect => ({
label: translate("Aspect"),
value:
translateMap(getInstanceById("Aspect", aspect)?.translations)
?.name ?? MISSING_VALUE,
translateMap(getInstanceById("Aspect", aspect)?.translations)?.name ??
MISSING_VALUE,
})),
mapNullable(translation.range, range => ({
label: translate("Range"),
@@ -1052,8 +966,7 @@ export const getActivatableEntityDescription = createEntityDescriptionCreator<
mapNullable(entry.prerequisites, prerequisites => ({
label: translate("Prerequisites"),
value:
wrappedId.kind === "Advantage" ||
wrappedId.kind === "Disadvantage"
wrappedId.kind === "Advantage" || wrappedId.kind === "Disadvantage"
? printAdvantageDisadvantagePrerequisites(
getInstanceById,
getResolvedSelectOptionById,
@@ -1106,18 +1019,11 @@ export const getActivatableEntityDescription = createEntityDescriptionCreator<
),
mapNullable(baseEntry.property, property => ({
label: translate("Property"),
value: renderPropertyValue(
getInstanceById,
translate,
translateMap,
property,
),
value: renderPropertyValue(getInstanceById, translate, translateMap, property),
})),
mapNullable(entry.ap_value, apValue => {
const append =
translation.ap_value_append !== undefined
? ` ${translation.ap_value_append}`
: ""
translation.ap_value_append !== undefined ? ` ${translation.ap_value_append}` : ""
return {
label: translate("AP Value"),
value:
@@ -1125,31 +1031,21 @@ export const getActivatableEntityDescription = createEntityDescriptionCreator<
locale,
activatableId =>
locale.translateMap<BaseActivatableTranslation>(
getInstanceById(
activatableId.kind,
fromUniformCase(activatableId),
)?.translations,
getInstanceById(activatableId.kind, fromUniformCase(activatableId))
?.translations,
)?.name,
selectOptionId => {
const selectOption = getResolvedSelectOptionById(
wrappedId,
selectOptionId,
)
const selectOption = getResolvedSelectOptionById(wrappedId, selectOptionId)
if (selectOption === undefined) {
return undefined
}
const name = locale.translateMap(
selectOption.content.translations,
)?.name
const name = locale.translateMap(selectOption.content.translations)?.name
if (name !== undefined) {
return name
}
const { parent: parentId } = selectOption.content
return locale.translateMap<BaseActivatableTranslation>(
getInstanceById(
parentId.kind,
fromUniformCase(parentId),
)?.translations,
getInstanceById(parentId.kind, fromUniformCase(parentId))?.translations,
)?.name
},
() => getAllResolvedSelectOptions(wrappedId),
+73 -166
View File
@@ -7,6 +7,7 @@ import { isNotNullish, mapNullable } from "@elyukai/utils/nullable"
import { Reader } from "@elyukai/utils/reader"
import { sign } from "@elyukai/utils/string/number"
import { assertExhaustive } from "@elyukai/utils/typeSafety"
import { getAdventurePointsForRatingRange } from "@optolith/adventure-points/improvement-cost"
import type {
BlessedTraditionConstraint,
CommonNames,
@@ -21,10 +22,7 @@ import type {
Weighted,
} from "optolith-database-schema/gen"
import { createEntityDescriptionCreator } from "../creator.js"
import type {
GetAllChildInstancesForParent,
GetInstanceById,
} from "../helpers/getTypes.js"
import type { GetAllChildInstancesForParent, GetInstanceById } from "../helpers/getTypes.js"
import type { TranslationKeysWithoutParams } from "../helpers/translate.js"
import type {
RawDefinitionListEntityDescriptionSectionItem,
@@ -38,14 +36,8 @@ import {
} from "./partial/commonnessRatedAdvantagesAndDisadvantages.js"
import { getProfessionName } from "./partial/professions.js"
import { parensIf } from "./partial/rated/activatable/parensIf.js"
import {
translateR,
type EnvMap,
type StdEnv,
type StdReader,
} from "./partial/reader.js"
import { translateR, type EnvMap, type StdEnv, type StdReader } from "./partial/reader.js"
import { MISSING_VALUE } from "./partial/unknown.js"
import { getAdventurePointsForRatingRange } from "@optolith/adventure-points/improvement-cost"
const renderListOperation = (
operation: CommonProfessionConstraintsOperation,
@@ -55,10 +47,9 @@ const renderListOperation = (
case "Intersection":
return Reader.of(list.join(", "))
case "Difference":
return translateR(
"all but {$excludedProfessions :list type=conjunction}",
{ excludedProfessions: list },
)
return translateR("all but {$excludedProfessions :list type=conjunction}", {
excludedProfessions: list,
})
default:
return assertExhaustive(operation)
}
@@ -79,12 +70,11 @@ const renderCommonProfessionConstraints = <T>(
"ProfessionVariant" | "BlessedTradition" | "MagicalTradition"
> =>
Reader.asks(({ localeCompare }: StdEnv<"lc">) =>
Reader.sequence(constraints.constraints.map(renderConstraint)).thenW(
constraintValues =>
renderListOperation(
constraints.operation,
constraintValues.filter(isNotNullish).toSorted(localeCompare),
),
Reader.sequence(constraints.constraints.map(renderConstraint)).thenW(constraintValues =>
renderListOperation(
constraints.operation,
constraintValues.filter(isNotNullish).toSorted(localeCompare),
),
),
).thenW(identity)
@@ -109,9 +99,7 @@ const renderCommonProfessionGroup = <T>(
})),
)
const renderRarity = (
rarity: Rarity | undefined,
): StdReader<string | undefined, "t"> => {
const renderRarity = (rarity: Rarity | undefined): StdReader<string | undefined, "t"> => {
if (rarity === undefined) {
return Reader.of(undefined)
}
@@ -136,10 +124,7 @@ const renderWeighted = <ID extends string>(
return Reader.asks(({ translate, localeCompare }) => {
const variants = ensureNonEmpty(
weightedVariants.elements
.map(getName)
.filter(isNotNullish)
.toSorted(localeCompare),
weightedVariants.elements.map(getName).filter(isNotNullish).toSorted(localeCompare),
)
if (variants === undefined) {
@@ -166,43 +151,27 @@ const renderProfessionConstraint = (
constraint: ProfessionConstraint,
) =>
Reader.asks(({ translateMap }: StdEnv<"tm">) =>
getProfessionName(
translateMap,
getChildInstancesForInstanceId,
constraint.id,
),
getProfessionName(translateMap, getChildInstancesForInstanceId, constraint.id),
).thenW(baseName =>
baseName === undefined
? Reader.of(undefined)
: Reader.sequence<
StdEnv<"t" | "tm" | "lc" | "ibi", "ProfessionVariant">,
string | undefined
>([
renderRarity(constraint.rarity),
Reader.asks(
({
translateMap,
getInstanceById,
}: StdEnv<"tm" | "ibi", "ProfessionVariant">) =>
renderWeighted(
constraint.weighted_variants,
variantId =>
translateMap(
getInstanceById("ProfessionVariant", variantId)
?.translations,
)?.name.default,
),
).thenW(identity),
]).map(
notes =>
baseName +
parensIf(ensureNonEmpty(notes.filter(isNotNullish))?.join("; ")),
),
: Reader.sequence<StdEnv<"t" | "tm" | "lc" | "ibi", "ProfessionVariant">, string | undefined>(
[
renderRarity(constraint.rarity),
Reader.asks(
({ translateMap, getInstanceById }: StdEnv<"tm" | "ibi", "ProfessionVariant">) =>
renderWeighted(
constraint.weighted_variants,
variantId =>
translateMap(getInstanceById("ProfessionVariant", variantId)?.translations)
?.name.default,
),
).thenW(identity),
],
).map(notes => baseName + parensIf(ensureNonEmpty(notes.filter(isNotNullish))?.join("; "))),
)
const renderTraditionConstraint = <
E extends "MagicalTradition" | "BlessedTradition",
>(
const renderTraditionConstraint = <E extends "MagicalTradition" | "BlessedTradition">(
getChildInstancesForInstanceId: GetAllChildInstancesForParent<"ProfessionVersion">,
entity: E,
constraint: MagicalTraditionConstraint | BlessedTraditionConstraint,
@@ -221,17 +190,11 @@ const renderTraditionConstraint = <
renderRarity(constraint.rarity),
Reader.asks(({ translateMap }: StdEnv<"tm">) =>
renderWeighted(constraint.weighted_professions, profId =>
getProfessionName(
translateMap,
getChildInstancesForInstanceId,
profId,
),
getProfessionName(translateMap, getChildInstancesForInstanceId, profId),
),
).thenW(identity),
]).map(
notes =>
baseName +
parensIf(ensureNonEmpty(notes.filter(isNotNullish))?.join("; ")),
notes => baseName + parensIf(ensureNonEmpty(notes.filter(isNotNullish))?.join("; ")),
),
)
@@ -239,25 +202,16 @@ const renderCommonProfessions = (
getChildInstancesForInstanceId: GetAllChildInstancesForParent<"ProfessionVersion">,
commonProfessions: CommonProfessions,
): StdReader<
| string
| [
RawEntityDescriptionSectionContent<RawNestedDefinitionListEntityDescriptionSection>,
],
string | [RawEntityDescriptionSectionContent<RawNestedDefinitionListEntityDescriptionSection>],
"t" | "tm" | "lc" | "ibi",
"ProfessionVariant" | "BlessedTradition" | "MagicalTradition"
> => {
switch (commonProfessions.kind) {
case "Plain":
return renderCommonProfessionConstraints(
commonProfessions.Plain,
profId =>
Reader.asks(({ translateMap }) =>
getProfessionName(
translateMap,
getChildInstancesForInstanceId,
profId,
),
),
return renderCommonProfessionConstraints(commonProfessions.Plain, profId =>
Reader.asks(({ translateMap }) =>
getProfessionName(translateMap, getChildInstancesForInstanceId, profId),
),
)
case "Grouped":
return renderCommonProfessionGroup(
@@ -266,10 +220,7 @@ const renderCommonProfessions = (
constraint =>
// switch (constraint.kind) {
// case "Profession":
renderProfessionConstraint(
getChildInstancesForInstanceId,
constraint.Profession,
),
renderProfessionConstraint(getChildInstancesForInstanceId, constraint.Profession),
// case "ProfessionSubgroup":
// switch (constraint.ProfessionSubgroup.kind) {
// case "Profane":
@@ -336,11 +287,7 @@ const renderCommonSkills = (
items === undefined || !isNotEmpty(items)
? translate("none")
: items
.map(
itemId =>
translateMap(getInstanceById("Skill", itemId)?.translations)
?.name,
)
.map(itemId => translateMap(getInstanceById("Skill", itemId)?.translations)?.name)
.filter(isNotNullish)
.toSorted(localeCompare)
.join(", "),
@@ -386,31 +333,25 @@ const renderCommonNames = (
const renderCulturalPackage = (
items: CulturalPackageItem[],
): StdReader<
{ text: string; apValue: number },
"t" | "tm" | "lc" | "ibi",
"Skill"
> =>
): StdReader<{ text: string; apValue: number }, "t" | "tm" | "lc" | "ibi", "Skill"> =>
Reader.asks(({ translate, translateMap, getInstanceById, localeCompare }) => {
if (!isNotEmpty(items)) {
return { text: translate("none"), apValue: 0 }
}
const processedItems = items.map(
(item): [text: string, apValue: number] => {
const instance = getInstanceById("Skill", item.id)
const instanceTranslation = translateMap(instance?.translations)
const processedItems = items.map((item): [text: string, apValue: number] => {
const instance = getInstanceById("Skill", item.id)
const instanceTranslation = translateMap(instance?.translations)
if (instance === undefined || instanceTranslation === undefined) {
return [MISSING_VALUE, 0]
}
if (instance === undefined || instanceTranslation === undefined) {
return [MISSING_VALUE, 0]
}
return [
`${instanceTranslation.name ?? MISSING_VALUE} ${sign(item.points)}`,
getAdventurePointsForRatingRange(instance.improvement_cost.kind, 0, item.points),
]
},
)
return [
`${instanceTranslation.name} ${sign(item.points)}`,
getAdventurePointsForRatingRange(instance.improvement_cost.kind, 0, item.points),
]
})
return {
text: processedItems
@@ -461,8 +402,9 @@ export const getCultureEntityDescription = createEntityDescriptionCreator<
getInstanceById,
} satisfies Partial<EnvMap>
const { text: culturePackageText, apValue: culturalPackageApValue } =
renderCulturalPackage(entry.cultural_package).run(env)
const { text: culturePackageText, apValue: culturalPackageApValue } = renderCulturalPackage(
entry.cultural_package,
).run(env)
return {
title: translation.name,
@@ -477,14 +419,9 @@ export const getCultureEntityDescription = createEntityDescriptionCreator<
entry.language
.map(lang => {
const language = getInstanceById("Language", lang.id)
const languageTranslation = translateMap(
language?.translations,
)
const languageTranslation = translateMap(language?.translations)
if (
language === undefined ||
languageTranslation === undefined
) {
if (language === undefined || languageTranslation === undefined) {
return MISSING_VALUE
}
@@ -492,16 +429,11 @@ export const getCultureEntityDescription = createEntityDescriptionCreator<
languageTranslation.name +
parensIf(
[
translateMap(
language.customSpecializations?.translations,
)?.description,
translateMap(language.customSpecializations?.translations)?.description,
...(lang.specializations?.map(
specId =>
translateMap(
getInstanceById(
"LanguageSpecialization",
specId,
)?.translations,
getInstanceById("LanguageSpecialization", specId)?.translations,
)?.name,
) ?? []),
]
@@ -521,23 +453,16 @@ export const getCultureEntityDescription = createEntityDescriptionCreator<
entry.script === undefined
? translate("none")
: (() => {
const processedScripts = entry.script.map(
(scriptId): [string, number] => {
const script = getInstanceById("Script", scriptId)
const scriptTranslation = translateMap(
script?.translations,
)
const processedScripts = entry.script.map((scriptId): [string, number] => {
const script = getInstanceById("Script", scriptId)
const scriptTranslation = translateMap(script?.translations)
if (
script === undefined ||
scriptTranslation === undefined
) {
return [MISSING_VALUE, 0]
}
if (script === undefined || scriptTranslation === undefined) {
return [MISSING_VALUE, 0]
}
return [scriptTranslation.name, script.ap_value ?? 0]
},
)
return [scriptTranslation.name, script.ap_value ?? 0]
})
if (!isNotEmpty(processedScripts)) {
return translate("none")
@@ -551,9 +476,7 @@ export const getCultureEntityDescription = createEntityDescriptionCreator<
) {
return (
localeJoin(
processedScripts
.map(([name]) => name)
.toSorted(localeCompare),
processedScripts.map(([name]) => name).toSorted(localeCompare),
"disjunction",
) +
parensIf(
@@ -599,9 +522,8 @@ export const getCultureEntityDescription = createEntityDescriptionCreator<
value: entry.social_status
.map(
status =>
translateMap(
getInstanceById("SocialStatus", status)?.translations,
)?.name ?? MISSING_VALUE,
translateMap(getInstanceById("SocialStatus", status)?.translations)?.name ??
MISSING_VALUE,
)
.toSorted(localeCompare)
.join(", "),
@@ -617,40 +539,28 @@ export const getCultureEntityDescription = createEntityDescriptionCreator<
"Common Advantages",
entry.common_advantages,
values =>
renderCommonnessRatedAdvantagesOrDisadvantages(
"Advantage",
values,
).run(env),
renderCommonnessRatedAdvantagesOrDisadvantages("Advantage", values).run(env),
translation.common_advantages,
).run(env),
renderValueWithPossibleTranslation(
"Common Disadvantages",
entry.common_disadvantages,
values =>
renderCommonnessRatedAdvantagesOrDisadvantages(
"Disadvantage",
values,
).run(env),
renderCommonnessRatedAdvantagesOrDisadvantages("Disadvantage", values).run(env),
translation.common_disadvantages,
).run(env),
renderValueWithPossibleTranslation(
"Uncommon Advantages",
entry.uncommon_advantages,
values =>
renderCommonnessRatedAdvantagesOrDisadvantages(
"Advantage",
values,
).run(env),
renderCommonnessRatedAdvantagesOrDisadvantages("Advantage", values).run(env),
translation.uncommon_advantages,
).run(env),
renderValueWithPossibleTranslation(
"Uncommon Disadvantages",
entry.uncommon_disadvantages,
values =>
renderCommonnessRatedAdvantagesOrDisadvantages(
"Disadvantage",
values,
).run(env),
renderCommonnessRatedAdvantagesOrDisadvantages("Disadvantage", values).run(env),
translation.uncommon_disadvantages,
).run(env),
{
@@ -672,10 +582,7 @@ export const getCultureEntityDescription = createEntityDescriptionCreator<
label:
translate("Cultural Package {$cultureName}", {
cultureName: translation.name,
}) +
parensIf(
translate("{$value} AP", { value: culturalPackageApValue }),
),
}) + parensIf(translate("{$value} AP", { value: culturalPackageApValue })),
value: {
type: "plain",
text: culturePackageText,
+65 -123
View File
@@ -44,24 +44,19 @@ const renderElectiveSpellworks = (
case "Specific":
return electiveSpellworks.Specific.list
.map(item =>
mapNullable(
translateMap(getInstanceById(item.id)?.translations)?.name,
name => {
if (item.restriction === undefined) {
return name
}
mapNullable(translateMap(getInstanceById(item.id)?.translations)?.name, name => {
if (item.restriction === undefined) {
return name
}
return (
name +
parensIf(
translateMap(
getInstanceById("Element", item.restriction.Element)
?.translations,
)?.name ?? MISSING_VALUE,
)
return (
name +
parensIf(
translateMap(getInstanceById("Element", item.restriction.Element)?.translations)
?.name ?? MISSING_VALUE,
)
},
),
)
}),
)
.filter(isNotNullish)
.toSorted(localeCompare)
@@ -76,36 +71,27 @@ const renderRestrictedSpellworks = (
translateMap: TranslateMap,
localeCompare: LocaleCompare,
localeJoin: LocaleJoin,
getInstanceById: GetInstanceById<
SpellworkIdentifier["kind"] | "Element" | "Property"
>,
getInstanceById: GetInstanceById<SpellworkIdentifier["kind"] | "Element" | "Property">,
restrictedSpellworks: RestrictedSpellworks,
): string => {
const restrictedGroups = Dictionary.groupBy(
restrictedSpellworks,
restriction =>
restriction.kind === "Spellwork"
? "spellwork"
: restriction.kind === "Property" &&
restriction.Property.maximum !== undefined
? "restrictedProperty"
: restriction.kind === "Property" ||
restriction.kind === "DemonSummoning"
? "property"
: "other",
const restrictedGroups = Dictionary.groupBy(restrictedSpellworks, restriction =>
restriction.kind === "Spellwork"
? "spellwork"
: restriction.kind === "Property" && restriction.Property.maximum !== undefined
? "restrictedProperty"
: restriction.kind === "Property" || restriction.kind === "DemonSummoning"
? "property"
: "other",
)
const addExclusion = (
excludedSpellworks: SpellworkIdentifier[] | undefined,
) => {
const addExclusion = (excludedSpellworks: SpellworkIdentifier[] | undefined) => {
if (excludedSpellworks === undefined) {
return ""
}
const translatedSpellworks = excludedSpellworks
.map(
excludedSpellwork =>
translateMap(getInstanceById(excludedSpellwork)?.translations)?.name,
excludedSpellwork => translateMap(getInstanceById(excludedSpellwork)?.translations)?.name,
)
.filter(isNotNullish)
@@ -133,10 +119,8 @@ const renderRestrictedSpellworks = (
{
count: restriction.Property.maximum,
property:
translateMap(
getInstanceById("Property", restriction.Property.id)
?.translations,
)?.name ?? MISSING_VALUE,
translateMap(getInstanceById("Property", restriction.Property.id)?.translations)
?.name ?? MISSING_VALUE,
},
) + addExclusion(restriction.Property.exclude),
) ?? []
@@ -158,10 +142,8 @@ const renderRestrictedSpellworks = (
?.map(restriction => {
switch (restriction.kind) {
case "Property":
return translateMap(
getInstanceById("Property", restriction.Property.id)
?.translations,
)?.name
return translateMap(getInstanceById("Property", restriction.Property.id)?.translations)
?.name
case "DemonSummoning":
return translate("Demon Summoning")
default:
@@ -189,11 +171,7 @@ const renderRestrictedSpellworks = (
const translatedSpellworks =
spellworkGroup
?.map(
restriction =>
translateMap(getInstanceById(restriction.Spellwork)?.translations)
?.name,
)
?.map(restriction => translateMap(getInstanceById(restriction.Spellwork)?.translations)?.name)
.filter(isNotNullish)
.toSorted(localeCompare) ?? []
@@ -229,39 +207,26 @@ const renderRestrictedSpellworks = (
case "Borbaradian":
return translate("no Borbaradian spellworks")
case "DamageIntelligent":
return translate(
"no spellworks that inflict DP or sDP on intelligent creatures",
)
return translate("no spellworks that inflict DP or sDP on intelligent creatures")
default:
return assertExhaustive(restriction)
}
}) ?? []
return [
...restrictedProperties,
...properties,
...others,
...spellworks,
].join("; ")
return [...restrictedProperties, ...properties, ...others, ...spellworks].join("; ")
}
const renderSpellworkAdjustment = (
translateMap: TranslateMap,
getInstanceById: GetInstanceById<
SpellworkIdentifier["kind"] | "MagicalTradition"
>,
getInstanceById: GetInstanceById<SpellworkIdentifier["kind"] | "MagicalTradition">,
adjustment: SpellworkAdjustment,
): string =>
`${
translateMap(getInstanceById(adjustment.id)?.translations)?.name ??
MISSING_VALUE
} ${adjustment.points}${parensIf(
translateMap(getInstanceById(adjustment.id)?.translations)?.name ?? MISSING_VALUE
} ${adjustment.points.toFixed()}${parensIf(
adjustment.tradition === undefined
? undefined
: translateMap(
getInstanceById("MagicalTradition", adjustment.tradition)
?.translations,
)?.name,
: translateMap(getInstanceById("MagicalTradition", adjustment.tradition)?.translations)?.name,
)}`
const renderAbilityAdjustmentName = (
@@ -279,29 +244,24 @@ const renderAbilityAdjustmentName = (
switch (abilityAdjustment.kind) {
case "Skill":
return (
translateMap(
getInstanceById("Skill", abilityAdjustment.Skill.id)?.translations,
)?.name ?? MISSING_VALUE
translateMap(getInstanceById("Skill", abilityAdjustment.Skill.id)?.translations)?.name ??
MISSING_VALUE
)
case "CombatTechnique":
return (
translateMap(
getInstanceById(abilityAdjustment.CombatTechnique.id)?.translations,
)?.name ?? MISSING_VALUE
translateMap(getInstanceById(abilityAdjustment.CombatTechnique.id)?.translations)?.name ??
MISSING_VALUE
)
case "Spellwork":
return (
(translateMap(
getInstanceById(abilityAdjustment.Spellwork.id)?.translations,
)?.name ?? MISSING_VALUE) +
(translateMap(getInstanceById(abilityAdjustment.Spellwork.id)?.translations)?.name ??
MISSING_VALUE) +
parensIf(
abilityAdjustment.Spellwork.tradition === undefined
? undefined
: translateMap(
getInstanceById(
"MagicalTradition",
abilityAdjustment.Spellwork.tradition,
)?.translations,
getInstanceById("MagicalTradition", abilityAdjustment.Spellwork.tradition)
?.translations,
)?.name,
)
)
@@ -316,9 +276,8 @@ const renderAbilityAdjustmentBaseValue = (
) => {
switch (abilityAdjustment.kind) {
case "Skill":
return baseProfessionPackage.skills?.find(
skill => skill.id === abilityAdjustment.Skill.id,
)?.rating_modifier
return baseProfessionPackage.skills?.find(skill => skill.id === abilityAdjustment.Skill.id)
?.rating_modifier
case "CombatTechnique":
return baseProfessionPackage.combat_techniques?.find(skill =>
deepEqual(skill.id, abilityAdjustment.CombatTechnique.id),
@@ -336,9 +295,7 @@ const renderAbilityAdjustmentBaseValue = (
}
}
const renderAbilityAdjustmentModifierValue = (
abilityAdjustment: AbilityAdjustment,
) => {
const renderAbilityAdjustmentModifierValue = (abilityAdjustment: AbilityAdjustment) => {
switch (abilityAdjustment.kind) {
case "Skill":
return abilityAdjustment.Skill.points
@@ -351,9 +308,7 @@ const renderAbilityAdjustmentModifierValue = (
}
}
const renderAbilityAdjustmentDefaultValue = (
abilityAdjustment: AbilityAdjustment,
) => {
const renderAbilityAdjustmentDefaultValue = (abilityAdjustment: AbilityAdjustment) => {
switch (abilityAdjustment.kind) {
case "Skill":
return 0
@@ -388,17 +343,15 @@ const renderAbilityAdjustment = (
)} ${sign(renderAbilityAdjustmentModifierValue(abilityAdjustment))}`
: abilityAdjustment => {
const basePoints =
(renderAbilityAdjustmentBaseValue(
baseProfessionPackage,
abilityAdjustment,
) ?? 0) + renderAbilityAdjustmentDefaultValue(abilityAdjustment)
(renderAbilityAdjustmentBaseValue(baseProfessionPackage, abilityAdjustment) ?? 0) +
renderAbilityAdjustmentDefaultValue(abilityAdjustment)
return translate("{$replacement} instead of {$base}", {
base: basePoints,
replacement: `${renderAbilityAdjustmentName(
translateMap,
getInstanceById,
abilityAdjustment,
)} ${basePoints + renderAbilityAdjustmentModifierValue(abilityAdjustment)}`,
)} ${(basePoints + renderAbilityAdjustmentModifierValue(abilityAdjustment)).toFixed()}`,
})
}
@@ -418,16 +371,9 @@ const renderAbilityAdjustments = (
list: AbilityAdjustment[],
) =>
list
?.map(
renderAbilityAdjustment(
translate,
translateMap,
getInstanceById,
baseProfessionPackage,
),
)
.map(renderAbilityAdjustment(translate, translateMap, getInstanceById, baseProfessionPackage))
.toSorted(localeCompare)
.join(", ") ?? translate("none")
.join(", ")
/**
* Get a JSON representation of the rules text for a curriculum.
@@ -482,9 +428,8 @@ export const getCurriculumEntityDescription = createEntityDescriptionCreator<
{
label: translate("Guideline"),
value:
translateMap(
getInstanceById("Guideline", entry.guideline)?.translations,
)?.name ?? MISSING_VALUE,
translateMap(getInstanceById("Guideline", entry.guideline)?.translations)?.name ??
MISSING_VALUE,
},
{
label: translate("Elective Spellworks Package"),
@@ -522,21 +467,18 @@ export const getCurriculumEntityDescription = createEntityDescriptionCreator<
(
lessonPackageTranslation,
): LabeledEntityDescriptionSection<RawEntityDescriptionSectionContent> => {
const [boni, mali] = partition(
lessonPackage.content.skills ?? [],
adjustment => {
switch (adjustment.kind) {
case "Skill":
return adjustment.Skill.points > 0
case "CombatTechnique":
return adjustment.CombatTechnique.points > 0
case "Spellwork":
return adjustment.Spellwork.points > 0
default:
return assertExhaustive(adjustment)
}
},
)
const [boni, mali] = partition(lessonPackage.content.skills ?? [], adjustment => {
switch (adjustment.kind) {
case "Skill":
return adjustment.Skill.points > 0
case "CombatTechnique":
return adjustment.CombatTechnique.points > 0
case "Spellwork":
return adjustment.Spellwork.points > 0
default:
return assertExhaustive(adjustment)
}
})
return {
type: "labeled",
@@ -547,7 +489,7 @@ export const getCurriculumEntityDescription = createEntityDescriptionCreator<
{
label: translate("Spellwork Changes"),
value:
lessonPackageTranslation?.spellwork_changes ??
lessonPackageTranslation.spellwork_changes ??
lessonPackage.content.spellwork_changes
?.map(change =>
translate("{$replacement} instead of {$base}", {
+40 -58
View File
@@ -72,14 +72,9 @@ const renderBaseCalculation = (
renderMathOperation(calculation, value => {
switch (value.kind) {
case "Constant":
return value.Constant.toString(10)
return value.Constant.toFixed()
case "Attribute":
return getAttribute(
getInstanceById,
translateMap,
value.Attribute,
style,
)
return getAttribute(getInstanceById, translateMap, value.Attribute, style)
case "RaceBaseValue":
return getRaceBaseValue(
translate,
@@ -94,13 +89,9 @@ const renderBaseCalculation = (
case "full":
switch (value.PrimaryAttribute.kind) {
case "Magical":
return translate(
"Primary attribute for the magic users Tradition",
)
return translate("Primary attribute for the magic users Tradition")
case "Blessed":
return translate(
"Primary attribute for the Blessed Ones Tradition",
)
return translate("Primary attribute for the Blessed Ones Tradition")
default:
return assertExhaustive(value.PrimaryAttribute)
}
@@ -117,53 +108,44 @@ const renderBaseCalculation = (
/**
* Get a JSON representation of the rules text for a derived characteristic.
*/
export const getDerivedCharacteristicEntityDescription =
createEntityDescriptionCreator<
"DerivedCharacteristic",
{
getInstanceById: GetInstanceById<
"Publication" | "Attribute" | "DerivedCharacteristic"
>
idMap: IdMap
}
>(
(
{ getInstanceById, idMap },
{ translate, translateMap },
{ content: entry },
) => {
const translation = translateMap(entry.translations)
export const getDerivedCharacteristicEntityDescription = createEntityDescriptionCreator<
"DerivedCharacteristic",
{
getInstanceById: GetInstanceById<"Publication" | "Attribute" | "DerivedCharacteristic">
idMap: IdMap
}
>(({ getInstanceById, idMap }, { translate, translateMap }, { content: entry }) => {
const translation = translateMap(entry.translations)
if (translation === undefined) {
return undefined
}
if (translation === undefined) {
return undefined
}
return {
title: `${translation.name} (${translation.abbreviation})`,
className: "derived-characteristic",
body: [
translation.description === undefined
? undefined
: {
type: "plain",
text: translation.description,
},
return {
title: `${translation.name} (${translation.abbreviation})`,
className: "derived-characteristic",
body: [
translation.description === undefined
? undefined
: {
type: "plain",
text: translation.description,
},
{
type: "definitionList",
items: [
{
type: "definitionList",
items: [
{
label: translate("Base Value"),
value: renderBaseCalculation(
getInstanceById,
translate,
translateMap,
idMap,
entry.calculation.base,
),
},
],
label: translate("Base Value"),
value: renderBaseCalculation(
getInstanceById,
translate,
translateMap,
idMap,
entry.calculation.base,
),
},
],
}
},
)
},
],
}
})
+8 -23
View File
@@ -10,18 +10,10 @@ import type {
} from "optolith-database-schema/gen"
import { createEntityDescriptionCreator } from "../creator.js"
import type { GetInstanceById } from "../helpers/getTypes.js"
import type {
LocaleMap,
Translate,
TranslateMap,
} from "../helpers/translate.js"
import type { LocaleMap, Translate, TranslateMap } from "../helpers/translate.js"
import type { IdMap } from "../index.js"
import { renderAnimalTypesSection } from "./partial/animalTypes.js"
import {
renderAlternativeNames,
renderChance,
renderResistance,
} from "./partial/herbary.js"
import { renderAlternativeNames, renderChance, renderResistance } from "./partial/herbary.js"
import { parensIf } from "./partial/rated/activatable/parensIf.js"
import { MISSING_VALUE } from "./partial/unknown.js"
@@ -46,11 +38,7 @@ type BaseDiseaseTranslation = {
errata?: Errata
}
const renderCauses = (
translate: Translate,
translateMap: TranslateMap,
causes: Cause[],
) =>
const renderCauses = (translate: Translate, translateMap: TranslateMap, causes: Cause[]) =>
causes
.map(cause => {
const causeTranslation = translateMap(cause.translations)
@@ -63,10 +51,9 @@ const renderCauses = (
causeTranslation.name +
parensIf(
ensureNonEmpty(
[
renderChance(translate, translateMap, cause, true),
causeTranslation.note,
].filter(isNotNullish),
[renderChance(translate, translateMap, cause, true), causeTranslation.note].filter(
isNotNullish,
),
)?.join("; "),
)
)
@@ -79,9 +66,7 @@ const renderCauses = (
export const getDiseaseEntityDescription = createEntityDescriptionCreator<
"AnimalDisease" | "Disease",
{
getInstanceById: GetInstanceById<
"Publication" | "AnimalType" | "DerivedCharacteristic"
>
getInstanceById: GetInstanceById<"Publication" | "AnimalType" | "DerivedCharacteristic">
idMap: IdMap
}
>(
@@ -105,7 +90,7 @@ export const getDiseaseEntityDescription = createEntityDescriptionCreator<
type: "definitionList",
items: [
renderAlternativeNames(translate, translation.alternative_names),
{ label: translate("Level"), value: baseEntry.level.toString() },
{ label: translate("Level"), value: baseEntry.level.toFixed() },
{ label: translate("Progress"), value: translation.progress },
{
label: translate("Resistance"),
+82 -102
View File
@@ -1,15 +1,9 @@
import { mapNullable } from "@elyukai/utils/nullable"
import { sign } from "@elyukai/utils/string/number"
import type {
ActivatableIdentifier,
RatedIdentifier,
} from "optolith-database-schema/gen"
import type { ActivatableIdentifier, RatedIdentifier } from "optolith-database-schema/gen"
import { createEntityDescriptionCreator } from "../creator.js"
import type { GetInstanceById } from "../helpers/getTypes.js"
import {
renderAlternativeNames,
renderLaboratoryLevel,
} from "./partial/herbary.js"
import { renderAlternativeNames, renderLaboratoryLevel } from "./partial/herbary.js"
import { printPlainGeneralPrerequisites } from "./partial/prerequisites/index.js"
import type { GetResolvedSelectOptionById } from "./partial/prerequisites/single/activatable.js"
import { parensIf } from "./partial/rated/activatable/parensIf.js"
@@ -37,100 +31,86 @@ export const getElixirEntityDescription = createEntityDescriptionCreator<
>
getResolvedSelectOptionById: GetResolvedSelectOptionById
}
>(
(
{ getInstanceById, getResolvedSelectOptionById },
locale,
{ content: entry },
) => {
const { translate, translateMap } = locale
const translation = translateMap(entry.translations)
>(({ getInstanceById, getResolvedSelectOptionById }, locale, { content: entry }) => {
const { translate, translateMap } = locale
const translation = translateMap(entry.translations)
if (translation === undefined) {
return undefined
}
if (translation === undefined) {
return undefined
}
return {
title: translation.name,
className: "elixir",
body: [
{
type: "definitionList",
items: [
renderAlternativeNames(translate, translation.alternative_names),
{
label: translate("Typical Ingredients"),
value: translation.typical_ingredients.join(", "),
},
{
label: translate("Price of Ingredients/Level"),
value: translate(
".input {$value :number} {{{$value} silverthalers}}",
{ value: entry.cost_per_ingredient_level },
),
},
{
label: translate("Laboratory"),
value: renderLaboratoryLevel(translate, entry.laboratory),
},
{
label: translate("Brewing Difficulty"),
value: sign(entry.brewing_difficulty),
},
{
label: `${translate("Prerequisites")} (${translate(
"Brewing Process",
)})`,
value:
translation?.brewing_process_prerequisites ?? translate("none"),
},
{
label: `${translate("AP Value")} (${translate("Trade Secret")})`,
value:
translate("{$value} AP", {
value: entry.trade_secret.ap_value,
}) +
parensIf(
mapNullable(
entry.trade_secret.prerequisites,
prerequisites =>
`${translate(
"Prerequisites",
)}: ${printPlainGeneralPrerequisites(
getInstanceById,
getResolvedSelectOptionById,
locale,
prerequisites,
)}`,
),
return {
title: translation.name,
className: "elixir",
body: [
{
type: "definitionList",
items: [
renderAlternativeNames(translate, translation.alternative_names),
{
label: translate("Typical Ingredients"),
value: translation.typical_ingredients.join(", "),
},
{
label: translate("Price of Ingredients/Level"),
value: translate(".input {$value :number} {{{$value} silverthalers}}", {
value: entry.cost_per_ingredient_level,
}),
},
{
label: translate("Laboratory"),
value: renderLaboratoryLevel(translate, entry.laboratory),
},
{
label: translate("Brewing Difficulty"),
value: sign(entry.brewing_difficulty),
},
{
label: `${translate("Prerequisites")} (${translate("Brewing Process")})`,
value: translation.brewing_process_prerequisites ?? translate("none"),
},
{
label: `${translate("AP Value")} (${translate("Trade Secret")})`,
value:
translate("{$value} AP", {
value: entry.trade_secret.ap_value,
}) +
parensIf(
mapNullable(
entry.trade_secret.prerequisites,
prerequisites =>
`${translate("Prerequisites")}: ${printPlainGeneralPrerequisites(
getInstanceById,
getResolvedSelectOptionById,
locale,
prerequisites,
)}`,
),
},
translation.special === undefined
? undefined
: {
label: translate("Special"),
value: translation.special,
},
{
label: translate("Quality Levels"),
value: [
{
type: "definitionList",
style: "nested",
items: translation.quality_levels.map(
(effectForLevel, index) => ({
label: (index + 1).toString(),
value: effectForLevel,
}),
),
},
],
},
],
},
],
errata: translation.errata,
references: entry.src,
}
},
)
),
},
translation.special === undefined
? undefined
: {
label: translate("Special"),
value: translation.special,
},
{
label: translate("Quality Levels"),
value: [
{
type: "definitionList",
style: "nested",
items: translation.quality_levels.map((effectForLevel, index) => ({
label: (index + 1).toFixed(),
value: effectForLevel,
})),
},
],
},
],
},
],
errata: translation.errata,
references: entry.src,
}
})
+22 -16
View File
@@ -50,7 +50,7 @@ import type {
} from "optolith-database-schema/gen"
import { createEntityDescriptionCreator, type TaggedEntity } from "../creator.js"
import type { GetInstanceById } from "../helpers/getTypes.js"
import type { LocaleCompare, LocaleJoin } from "../helpers/locale.js"
import type { FormatNumber, LocaleCompare, LocaleJoin } from "../helpers/locale.js"
import type { LocaleMap, Translate, TranslateMap } from "../helpers/translate.js"
import type {
IdMap,
@@ -140,14 +140,16 @@ const renderPrimaryAttributeAndDamageThreshold = (
case "Default":
return `${
closeCombatTechnique?.primary_attribute.map(getAttrAbbrv).join("/") ?? MISSING_VALUE
} ${damageThreshold.Default.threshold}`
} ${damageThreshold.Default.threshold.toFixed()}`
case "List":
if (isNotEmpty(damageThreshold.List.list)) {
const { list } = damageThreshold.List
if (list.some(item => item.threshold !== list[0].threshold)) {
return list.map(item => `${getAttrAbbrv(item.attribute)} ${item.threshold}`).join("/")
return list
.map(item => `${getAttrAbbrv(item.attribute)} ${item.threshold.toFixed()}`)
.join("/")
} else {
return `${list.map(item => getAttrAbbrv(item.attribute)).join("/")} ${list[0].threshold}`
return `${list.map(item => getAttrAbbrv(item.attribute)).join("/")} ${list[0].threshold.toFixed()}`
}
} else {
return MISSING_VALUE
@@ -263,7 +265,7 @@ const renderReloadTime = (translate: Translate, reloadTime: ReloadTime[]) =>
: MISSING_VALUE
const renderRangeBrackets = (rangeBrackets: RangeBrackets) =>
`${rangeBrackets.close}/${rangeBrackets.medium}/${rangeBrackets.far}`
`${rangeBrackets.close.toFixed()}/${rangeBrackets.medium.toFixed()}/${rangeBrackets.far.toFixed()}`
const renderAmmunition = (
translateMap: TranslateMap,
@@ -596,6 +598,7 @@ const renderNote = (
const renderWeight = (
translate: Translate,
formatNumber: FormatNumber,
measurements: Required<LocaleMeasurementAdjustments>,
weight: Weight | JewelryMaterialDifference<Weight>,
): RawDefinitionListEntityDescriptionSectionItem => {
@@ -611,7 +614,7 @@ const renderWeight = (
label: translate("Weight (Bronze/Silver/Gold)"),
value: translate("{$value} pounds", {
value: [weight.bronze, weight.silver, weight.gold]
.map(value => value * measurements.stonesMultiplier)
.map(value => formatNumber(value * measurements.stonesMultiplier))
.join("/"),
}),
}
@@ -621,14 +624,15 @@ const renderWeight = (
const renderCost = (
translate: Translate,
translateMap: TranslateMap,
cost: Cost | BookCost | JewelryMaterialDifference<Cost>,
): RawDefinitionListEntityDescriptionSectionItem => {
formatNumber: FormatNumber,
cost: Cost | BookCost | JewelryMaterialDifference<number>,
): { label: string; value: string } => {
const renderBookCostVariant = (bookCostVariant: BookCostVariant) => {
switch (bookCostVariant.kind) {
case "Definite": {
const translation = translateMap(bookCostVariant.Definite.translations)
return (
renderCost(translate, translateMap, bookCostVariant.Definite.cost) +
renderCost(translate, translateMap, formatNumber, bookCostVariant.Definite.cost).value +
parensIf(translation?.label)
)
}
@@ -645,7 +649,7 @@ const renderCost = (
return {
label: translate("Cost (Bronze/Silver/Gold)"),
value: translate("{$value} silverthalers", {
value: [cost.bronze, cost.silver, cost.gold].join("/"),
value: [cost.bronze, cost.silver, cost.gold].map(formatNumber).join("/"),
}),
}
} else {
@@ -704,8 +708,8 @@ const renderArmorValues = (
idMap: IdMap,
values: NormalizedArmorValues,
): RawDefinitionListEntityDescriptionSectionItem[] => [
{ label: translate("Protection"), value: values.protection.toString() },
{ label: translate("Encumbrance"), value: values.encumbrance.toString() },
{ label: translate("Protection"), value: values.protection.toFixed() },
{ label: translate("Encumbrance"), value: values.encumbrance.toFixed() },
{
label: translate("Additional Penalties"),
value: values.has_additional_penalties
@@ -724,7 +728,7 @@ const renderArmorValues = (
]
type BaseItem = {
cost?: Cost | BookCost | JewelryMaterialDifference<Cost>
cost?: Cost | BookCost | JewelryMaterialDifference<number>
weight?: Weight | JewelryMaterialDifference<Weight>
complexity?: ArmorComplexity | Complexity
structure_points?: StructurePoints
@@ -960,7 +964,7 @@ export const getEquipmentEntityDescription = createEntityDescriptionCreator<
baseItemTranslation?.language !== undefined || baseItemTranslation?.script !== undefined
? {
label: translate("Language/Script"),
value: [baseItemTranslation?.language, baseItemTranslation?.script]
value: [baseItemTranslation.language, baseItemTranslation.script]
.map(value => value ?? "—")
.join(" / "),
}
@@ -979,9 +983,11 @@ export const getEquipmentEntityDescription = createEntityDescriptionCreator<
}
: undefined,
mapNullable(baseItem.weight, weight =>
renderWeight(translate, locale.measurementAdjustments, weight),
renderWeight(translate, locale.formatNumber, locale.measurementAdjustments, weight),
),
mapNullable(baseItem.cost, cost =>
renderCost(translate, translateMap, locale.formatNumber, cost),
),
mapNullable(baseItem.cost, cost => renderCost(translate, translateMap, cost)),
mapNullable(
renderNote(
translate,
+5 -4
View File
@@ -129,12 +129,12 @@ const getAtomicEquipmentCost = (entry: TaggedEntity<EquipmentIdentifier["kind"]>
}
}
case "Jewelry": {
const costs: Cost[] = [
const costs: AtomicCost[] = [
entry.content.cost.bronze,
entry.content.cost.silver,
entry.content.cost.gold,
]
return costs.map(getAtomicCost).reduce(rangeAtomicEquipmentCost)
return costs.reduce(rangeAtomicEquipmentCost)
}
case "Ammunition":
case "Animal":
@@ -212,7 +212,8 @@ const getAtomicEquipmentWeight = (
return assertExhaustive(entry.content.type)
}
case "Jewelry": {
const values = Object.values(entry.content.weight)
const { bronze, silver, gold } = entry.content.weight
const values = [bronze, silver, gold]
return [Math.min(...values), Math.max(...values)]
}
case "Armor":
@@ -313,7 +314,7 @@ export const getEquipmentPackageEntityDescription = createEntityDescriptionCreat
item,
): item is EquipmentPackageItem & {
content: TaggedEntity<EquipmentIdentifier["kind"]>
} => item.content !== undefined,
} => item.content.content !== undefined,
) ?? []
return {
+7 -7
View File
@@ -21,31 +21,31 @@ export const getExperienceLevelEntityDescription =
items: [
{
label: translate("Adventure Points"),
value: entry.adventure_points.toString(),
value: entry.adventure_points.toFixed(),
},
{
label: translate("Maximum Attribute Value"),
value: entry.max_attribute_value.toString(),
value: entry.max_attribute_value.toFixed(),
},
{
label: translate("Maximum Skill Value"),
value: entry.max_skill_rating.toString(),
value: entry.max_skill_rating.toFixed(),
},
{
label: translate("Maximum Combat Technique"),
value: entry.max_combat_technique_rating.toString(),
value: entry.max_combat_technique_rating.toFixed(),
},
{
label: translate("Maximum Attribute Total"),
value: entry.max_attribute_total.toString(),
value: entry.max_attribute_total.toFixed(),
},
{
label: translate("Number of Spells/Liturgical Chants"),
value: entry.max_number_of_spells_liturgical_chants.toString(),
value: entry.max_number_of_spells_liturgical_chants.toFixed(),
},
{
label: translate("Number from other Traditions"),
value: entry.max_number_of_unfamiliar_spells.toString(),
value: entry.max_number_of_unfamiliar_spells.toFixed(),
},
],
},
+2 -2
View File
@@ -44,10 +44,10 @@ export const getInfluenceEntityDescription = createEntityDescriptionCreator<
: {
type: "definitionList",
items: [
...(translation.effects?.map(effect => ({
...translation.effects.map(effect => ({
label: effect.label,
value: effect.text,
})) ?? []),
})),
entry.prerequisites === undefined
? undefined
: {
+3 -3
View File
@@ -1,12 +1,12 @@
import { Compare } from "@optolith/helpers/compare"
import type { Compare } from "@optolith/helpers/compare"
import { isNotNullish } from "@optolith/helpers/nullable"
import { assertExhaustive } from "@optolith/helpers/typeSafety"
import { type LiturgyTradition, type RatedIdentifier } from "optolith-database-schema/gen"
import { Case } from "tsondb/schema/gen"
import { createEntityDescriptionCreator } from "../creator.js"
import type { GetAllChildInstancesForParent, GetInstanceById } from "../helpers/getTypes.js"
import { Translate, TranslateMap, type TranslationKeysWithoutParams } from "../helpers/translate.js"
import { type IdMap, type RawDefinitionListEntityDescriptionSectionItem } from "../index.js"
import type { Translate, TranslateMap, TranslationKeysWithoutParams } from "../helpers/translate.js"
import type { IdMap, RawDefinitionListEntityDescriptionSectionItem } from "../index.js"
import { renderEnhancements } from "./partial/enhancements.js"
import { renderOneTimeDuration } from "./partial/rated/activatable/duration.js"
import { renderEffect } from "./partial/rated/activatable/effect.js"
+56 -109
View File
@@ -14,22 +14,15 @@ import type {
} from "optolith-database-schema/gen"
import type { GetAllInstances } from "../../helpers/getTypes.js"
import type { LocaleEnvironment } from "../../helpers/locale.js"
import type {
BaseActivatable,
BaseActivatableTranslation,
} from "../activatable.js"
import type { BaseActivatable, BaseActivatableTranslation } from "../activatable.js"
import { evaluateMathOperation } from "./mathOperation.js"
import { MISSING_VALUE } from "./unknown.js"
const renderSelectOptionsAdventurePointsValue = <
T extends ResolvedSelectOptionIdentifier,
>(
const renderSelectOptionsAdventurePointsValue = <T extends ResolvedSelectOptionIdentifier>(
locale: LocaleEnvironment,
derivedLabel: () => string,
getAllSelectOptions: () => ResolvedSelectOption[],
getNameForSelectOptionId: (
id: ResolvedSelectOptionIdentifier,
) => string | undefined,
getNameForSelectOptionId: (id: ResolvedSelectOptionIdentifier) => string | undefined,
config: SelectOptionsAdventurePointsValue<T> | undefined,
) => {
if (config === undefined) {
@@ -49,13 +42,11 @@ const renderSelectOptionsAdventurePointsValue = <
}
case "Fixed":
return Map.groupBy(
getAllSelectOptions().map(
(item): [apValue: number, item: ResolvedSelectOption] => [
config.Fixed.map.find(mapItem => deepEqual(mapItem.id, item.id))
?.ap_value ?? config.Fixed.default,
item,
],
),
getAllSelectOptions().map((item): [apValue: number, item: ResolvedSelectOption] => [
config.Fixed.map.find(mapItem => deepEqual(mapItem.id, item.id))?.ap_value ??
config.Fixed.default,
item,
]),
item => item[0],
)
.entries()
@@ -87,9 +78,7 @@ const renderSelectOptionsAdventurePointsValue = <
export const renderAdventurePointsValue = (
locale: LocaleEnvironment,
getNameForId: (id: ActivatableIdentifier) => string | undefined,
getNameForSelectOptionId: (
id: ResolvedSelectOptionIdentifier,
) => string | undefined,
getNameForSelectOptionId: (id: ResolvedSelectOptionIdentifier) => string | undefined,
getAllSelectOptions: () => ResolvedSelectOption[],
getAllInstances: GetAllInstances<"Script" | "AnimalShapeSize">,
value: AdventurePointsValue | number,
@@ -106,15 +95,13 @@ export const renderAdventurePointsValue = (
switch (value.kind) {
case "Fixed": {
if (entry.levels === undefined) {
return translate(
".input {$value :number} {{{$value} Adventure Points}}",
{ value: value.Fixed },
)
return translate(".input {$value :number} {{{$value} Adventure Points}}", {
value: value.Fixed,
})
} else {
return translate(
".input {$value :number} {{{$value} Adventure Points per level}}",
{ value: value.Fixed },
)
return translate(".input {$value :number} {{{$value} Adventure Points per level}}", {
value: value.Fixed,
})
}
}
case "ByLevel": {
@@ -123,28 +110,18 @@ export const renderAdventurePointsValue = (
}
const mainValue = `${translate("Level {$level}", {
level: Array.from({ length: entry.levels }, (_, index) =>
romanize(index + 1),
).join("/"),
level: Array.from({ length: entry.levels }, (_, index) => romanize(index + 1)).join("/"),
})}: ${value.ByLevel.list.join("/")}`
const { additionalBySizeCategory } = value.ByLevel
if (additionalBySizeCategory === undefined) {
return mainValue
} else {
const sizeCategories = [
"tiny",
"small",
"medium",
"large",
"huge",
] as const
const sizeCategories = ["tiny", "small", "medium", "large", "huge"] as const
const values = sizeCategories
.map(sizeCategory => additionalBySizeCategory[sizeCategory])
.join("/")
const labels = sizeCategories
.map(sizeCategory => translate(sizeCategory))
.join("/")
const labels = sizeCategories.map(sizeCategory => translate(sizeCategory)).join("/")
return `${mainValue} + ${translate("{$values} AP for size category {$labels} (per level)", { values, labels })}`
}
@@ -175,19 +152,12 @@ export const renderAdventurePointsValue = (
const sizes = getAllInstances("AnimalShapeSize").toSorted(
on(x => x.content.ap_value, numAsc),
)
return translate(
"{$values} adventure points for a {$sized} animal shape",
{
values: sizes.map(size => size.content.ap_value).join("/"),
sized: sizes
.map(
size =>
translateMap(size.content.translations)?.name ??
MISSING_VALUE,
)
.join("/"),
},
)
return translate("{$values} adventure points for a {$sized} animal shape", {
values: sizes.map(size => size.content.ap_value).join("/"),
sized: sizes
.map(size => translateMap(size.content.translations)?.name ?? MISSING_VALUE)
.join("/"),
})
}
case "ArcaneBardTraditions":
return MISSING_VALUE
@@ -212,22 +182,14 @@ export const renderAdventurePointsValue = (
case "Aspects":
return MISSING_VALUE
case "Diseases":
if (
derivedSelectOptions.Diseases.use_half_level_as_ap_value === true
) {
return translate(
"Half the chosen diseases level in adventure points",
)
if (derivedSelectOptions.Diseases.use_half_level_as_ap_value === true) {
return translate("Half the chosen diseases level in adventure points")
} else {
return translate("The chosen diseases level in adventure points")
}
case "Poisons":
if (
derivedSelectOptions.Poisons.use_half_level_as_ap_value === true
) {
return translate(
"Half the chosen poisons level in adventure points",
)
if (derivedSelectOptions.Poisons.use_half_level_as_ap_value === true) {
return translate("Half the chosen poisons level in adventure points")
} else {
return translate("The chosen poisons level in adventure points")
}
@@ -239,6 +201,7 @@ export const renderAdventurePointsValue = (
() => {
switch (derivedSelectOptions.Skills.categories.length) {
case 1: {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- ensured by the schema
const first = derivedSelectOptions.Skills.categories[0]!
switch (first.kind) {
case "Skills":
@@ -257,14 +220,12 @@ export const renderAdventurePointsValue = (
}
case 2:
return derivedSelectOptions.Skills.categories.every(
category =>
category.kind === "Spells" || category.kind === "Rituals",
category => category.kind === "Spells" || category.kind === "Rituals",
)
? translate("A/B/C/D spellwork")
: derivedSelectOptions.Skills.categories.every(
category =>
category.kind === "LiturgicalChants" ||
category.kind === "Ceremonies",
category.kind === "LiturgicalChants" || category.kind === "Ceremonies",
)
? translate("A/B/C/D liturgical chant or ceremony")
: translate("A/B/C/D ability")
@@ -292,56 +253,42 @@ export const renderAdventurePointsValue = (
}
}
case "DependingOnActive":
return `${translate(
".input {$value :number} {{{$value} Adventure Points}}",
{
value: value.DependingOnActive.inactive,
},
)} (${translate(
".input {$value :number} {{{$value} Adventure Points with {$name}}}",
{
value: value.DependingOnActive.active,
name: getNameForId(value.DependingOnActive.id) ?? MISSING_VALUE,
},
)})`
return `${translate(".input {$value :number} {{{$value} Adventure Points}}", {
value: value.DependingOnActive.inactive,
})} (${translate(".input {$value :number} {{{$value} Adventure Points with {$name}}}", {
value: value.DependingOnActive.active,
name: getNameForId(value.DependingOnActive.id) ?? MISSING_VALUE,
})})`
case "DependingOnActiveInstances":
switch (value.DependingOnActiveInstances.kind) {
case "Threshold":
return translate(
".input {$value :number} {{{$value} Adventure Points}}",
{
value: value.DependingOnActiveInstances.Threshold.normal,
},
)
return translate(".input {$value :number} {{{$value} Adventure Points}}", {
value: value.DependingOnActiveInstances.Threshold.normal,
})
case "Expression": {
const expression = value.DependingOnActiveInstances.Expression
const values = Array.from(
{ length: entry.maximum ?? 3 },
(_, index) =>
evaluateMathOperation(expression, exprValue => {
switch (exprValue.kind) {
case "Constant":
return exprValue.Constant
case "Active":
return index
default:
return assertExhaustive(exprValue)
}
}),
const values = Array.from({ length: entry.maximum ?? 3 }, (_, index) =>
evaluateMathOperation(expression, exprValue => {
switch (exprValue.kind) {
case "Constant":
return exprValue.Constant
case "Active":
return index
default:
return assertExhaustive(exprValue)
}
}),
).join("/")
const labels = Array.from(
{ length: entry.maximum ?? 3 },
(_, index) => `${index + 1}.`,
(_, index) => `${(index + 1).toFixed()}.`,
).join("/")
if (entry.maximum !== undefined) {
return translate(
"{$values} Adventure Points for the {$labels} purchase",
{
values,
labels,
},
)
return translate("{$values} Adventure Points for the {$labels} purchase", {
values,
labels,
})
} else {
return translate(
"{$values}/and so on Adventure Points for the {$labels}/and so on purchase",
+4 -4
View File
@@ -3,10 +3,10 @@ import { isNotNullish } from "@elyukai/utils/nullable"
import { compareNumber } from "@elyukai/utils/ordering"
import { Reader } from "@elyukai/utils/reader"
import { getAdventurePointsForActivation } from "@optolith/adventure-points/improvement-cost"
import {
import type {
ImprovementCost,
RatedIdentifier,
type ImprovementCost,
type SkillWithEnhancementsIdentifier,
SkillWithEnhancementsIdentifier,
} from "optolith-database-schema/gen"
import type { RawEntityDescriptionSection } from "../../index.js"
import { printEnhancementPrerequisites } from "./prerequisites/index.js"
@@ -40,7 +40,7 @@ export const renderEnhancements = (
>().map(env =>
translation === undefined
? undefined
: `- ^[${translation.name}](entity: "Enhancement") (${env.translate("SR {$value}", { value: enhancement.content.skill_rating })}, ${env.translate("{$value} AP", { value: enhancement.content.adventure_points_modifier * getAdventurePointsForActivation(parentImprovementCost.kind) })}): ${translation.effect ?? ""}${enhancement.content.prerequisites === undefined ? "" : ` ${env.translate(".input {$hiddenCount :number} {{Prerequisites}}", { hiddenCount: enhancement.content.prerequisites.length })}: ${printEnhancementPrerequisites(env.getInstanceById, { translate: env.translate, translateMap: env.translateMap, compare: env.localeCompare, join: env.localeJoin }, enhancement.content.prerequisites)}`}`,
: `- ^[${translation.name}](entity: "Enhancement") (${env.translate("SR {$value}", { value: enhancement.content.skill_rating })}, ${env.translate("{$value} AP", { value: enhancement.content.adventure_points_modifier * getAdventurePointsForActivation(parentImprovementCost.kind) })}): ${translation.effect}${enhancement.content.prerequisites === undefined ? "" : ` ${env.translate(".input {$hiddenCount :number} {{Prerequisites}}", { hiddenCount: enhancement.content.prerequisites.length })}: ${printEnhancementPrerequisites(env.getInstanceById, { translate: env.translate, translateMap: env.translateMap, compare: env.localeCompare, join: env.localeJoin }, enhancement.content.prerequisites)}`}`,
),
),
).thenW(enhancementDescriptions => {
+20 -45
View File
@@ -1,16 +1,8 @@
import { isNotEmpty } from "@elyukai/utils/array/nonEmpty"
import { assertExhaustive } from "@elyukai/utils/typeSafety"
import type {
AlternativeName,
LaboratoryLevel,
Resistance,
} from "optolith-database-schema/gen"
import type { AlternativeName, LaboratoryLevel, Resistance } from "optolith-database-schema/gen"
import type { GetInstanceById } from "../../helpers/getTypes.js"
import type {
LocaleMap,
Translate,
TranslateMap,
} from "../../helpers/translate.js"
import type { LocaleMap, Translate, TranslateMap } from "../../helpers/translate.js"
import type { IdMap } from "../../index.js"
import { renderDice } from "./dice.js"
import { parensIf } from "./rated/activatable/parensIf.js"
@@ -19,10 +11,7 @@ import { MISSING_VALUE } from "./unknown.js"
/**
* Renders a laboratory level into a localized string.
*/
export const renderLaboratoryLevel = (
translate: Translate,
level: LaboratoryLevel,
) => {
export const renderLaboratoryLevel = (translate: Translate, level: LaboratoryLevel) => {
switch (level.kind) {
case "ArchaicLaboratory":
return translate("Archaic laboratory")
@@ -49,45 +38,34 @@ export const renderResistance = (
case "Spirit":
return (
translateMap(
getInstanceById(
"DerivedCharacteristic",
idMap.DerivedCharacteristic.Spirit,
)?.translations,
getInstanceById("DerivedCharacteristic", idMap.DerivedCharacteristic.Spirit)
?.translations,
)?.name ?? MISSING_VALUE
)
case "Toughness":
return (
translateMap(
getInstanceById(
"DerivedCharacteristic",
idMap.DerivedCharacteristic.Toughness,
)?.translations,
getInstanceById("DerivedCharacteristic", idMap.DerivedCharacteristic.Toughness)
?.translations,
)?.name ?? MISSING_VALUE
)
case "LowerOfSpiritAndToughness": {
const spiritTranslation =
translateMap(
getInstanceById(
"DerivedCharacteristic",
idMap.DerivedCharacteristic.Spirit,
)?.translations,
getInstanceById("DerivedCharacteristic", idMap.DerivedCharacteristic.Spirit)
?.translations,
)?.name ?? MISSING_VALUE
const toughnessTranslation =
translateMap(
getInstanceById(
"DerivedCharacteristic",
idMap.DerivedCharacteristic.Toughness,
)?.translations,
getInstanceById("DerivedCharacteristic", idMap.DerivedCharacteristic.Toughness)
?.translations,
)?.name ?? MISSING_VALUE
return translate(
"{$first} or {$second}, depending on which value is lower",
{
first: spiritTranslation,
second: toughnessTranslation,
},
)
return translate("{$first} or {$second}, depending on which value is lower", {
first: spiritTranslation,
second: toughnessTranslation,
})
}
default:
@@ -108,7 +86,7 @@ export const renderChance = (
(item.chance === undefined
? undefined
: translate("{$valueRange} on {$dice}", {
valueRange: item.chance === 5 ? 1 : `1${item.chance / 5}`,
valueRange: item.chance === 5 ? 1 : `1${(item.chance / 5).toFixed()}`,
dice: renderDice(translate, { number: 1, sides: 20 }),
}) +
(includePercentage
@@ -125,11 +103,8 @@ export const renderAlternativeNames = (
alternativeNames === undefined || !isNotEmpty(alternativeNames)
? undefined
: {
label: translate(
".input {$hiddenCount :number} {{Alternative Names}}",
{ hiddenCount: alternativeNames.length },
),
value: alternativeNames
.map(name => name.name + parensIf(name.region))
.join(", "),
label: translate(".input {$hiddenCount :number} {{Alternative Names}}", {
hiddenCount: alternativeNames.length,
}),
value: alternativeNames.map(name => name.name + parensIf(name.region)).join(", "),
}
+15 -21
View File
@@ -4,43 +4,46 @@ import type { MathOperation } from "optolith-database-schema/gen"
type UnaryFormatter = (value: string | number) => string
type BinaryFormatter = (left: string | number, right: string | number) => string
const printOperand = (operand: string | number): string =>
typeof operand === "number" ? operand.toFixed() : operand
/**
* Typographic formatter for addition.
*/
export const additionFormatter: BinaryFormatter = (left, right) =>
// eslint-disable-next-line no-irregular-whitespace
`${left}+${right}`
`${printOperand(left)}+${printOperand(right)}`
/**
* Typographic formatter for subtraction.
*/
export const subtractionFormatter: BinaryFormatter = (left, right) =>
// eslint-disable-next-line no-irregular-whitespace
`${left}${right}`
`${printOperand(left)}${printOperand(right)}`
/**
* Typographic formatter for multiplication.
*/
export const multiplicationFormatter: BinaryFormatter = (left, right) =>
// eslint-disable-next-line no-irregular-whitespace
`${left}×${right}`
`${printOperand(left)}×${printOperand(right)}`
/**
* Typographic formatter for division.
*/
export const divisionFormatter: BinaryFormatter = (left, right) =>
// eslint-disable-next-line no-irregular-whitespace
`${left}/${right}`
`${printOperand(left)}/${printOperand(right)}`
/**
* Typographic formatter for exponentiation. Uses Markdown syntax.
*/
export const exponentiationFormatter: BinaryFormatter = (left, right) =>
`${left}^${right}^`
`${printOperand(left)}^${printOperand(right)}^`
/**
* Typographic formatter for grouping (parentheses).
*/
export const groupFormatter: UnaryFormatter = value => `(${value})`
export const groupFormatter: UnaryFormatter = value => `(${printOperand(value)})`
/**
* Render a math operation as a string, using the provided function to render the values.
@@ -54,9 +57,7 @@ export const renderMathOperation = <T>(
addParenthesisTo: MathOperation<T>["kind"][] = [],
): string => {
const rendered = renderMathOperation(op, renderValue)
return addParenthesisTo.includes(op.kind)
? groupFormatter(rendered)
: rendered
return addParenthesisTo.includes(op.kind) ? groupFormatter(rendered) : rendered
}
const renderBinary = (
@@ -69,10 +70,7 @@ export const renderMathOperation = <T>(
): string =>
options.formatter(
renderWithParenthesis(left, options.addParenthesisTo),
renderWithParenthesis(
right,
options.addParenthesisToRight ?? options.addParenthesisTo,
),
renderWithParenthesis(right, options.addParenthesisToRight ?? options.addParenthesisTo),
)
switch (operation.kind) {
@@ -117,29 +115,25 @@ export const evaluateMathOperation = <T>(
case "Addition": {
const [left, right] = operation.Addition
return (
evaluateMathOperation(left, evaluateValue) +
evaluateMathOperation(right, evaluateValue)
evaluateMathOperation(left, evaluateValue) + evaluateMathOperation(right, evaluateValue)
)
}
case "Subtraction": {
const [left, right] = operation.Subtraction
return (
evaluateMathOperation(left, evaluateValue) -
evaluateMathOperation(right, evaluateValue)
evaluateMathOperation(left, evaluateValue) - evaluateMathOperation(right, evaluateValue)
)
}
case "Multiplication": {
const [left, right] = operation.Multiplication
return (
evaluateMathOperation(left, evaluateValue) *
evaluateMathOperation(right, evaluateValue)
evaluateMathOperation(left, evaluateValue) * evaluateMathOperation(right, evaluateValue)
)
}
case "Division": {
const [left, right] = operation.Division
return (
evaluateMathOperation(left, evaluateValue) /
evaluateMathOperation(right, evaluateValue)
evaluateMathOperation(left, evaluateValue) / evaluateMathOperation(right, evaluateValue)
)
}
case "Exponentiation": {
@@ -1,8 +1,8 @@
import { assertExhaustive } from "@optolith/helpers/typeSafety"
import { DisplayOption } from "optolith-database-schema/gen"
import type { DisplayOption } from "optolith-database-schema/gen"
import type { TranslateMap } from "../../../helpers/translate.js"
import { MISSING_VALUE } from "../unknown.js"
import { PrerequisitePart } from "./part.js"
import type { PrerequisitePart } from "./part.js"
/**
* Get the translation of a display option.
+20 -11
View File
@@ -1,15 +1,18 @@
import { isNotEmpty } from "@elyukai/utils/array/nonEmpty"
import { on } from "@elyukai/utils/function"
import { mapNullable } from "@elyukai/utils/nullable"
import { numAsc } from "@optolith/helpers/compare"
import { isNotNullish } from "@optolith/helpers/nullable"
import { romanize } from "@optolith/helpers/roman"
import { assertExhaustive } from "@optolith/helpers/typeSafety"
import {
import type {
ActivatableIdentifier,
AdvantageDisadvantagePrerequisites,
AnimistPowerPrerequisites,
ArcaneTraditionPrerequisites,
DerivedCharacteristicPrerequisites,
EnhancementPrerequisites,
GeneralPrerequisiteGroup,
GeneralPrerequisites,
GeodeRitualPrerequisites,
InfluencePrerequisites,
@@ -25,14 +28,12 @@ import {
PrerequisitesForLevels,
ProfessionPrerequisites,
PublicationPrerequisites,
RatedIdentifier,
SpecialAbilityIdentifier,
SpellworkPrerequisites,
type ActivatableIdentifier,
type GeneralPrerequisiteGroup,
type RatedIdentifier,
} from "optolith-database-schema/gen"
import type { GetAllChildInstancesForParent, GetInstanceById } from "../../../helpers/getTypes.js"
import { LocaleEnvironment } from "../../../helpers/locale.js"
import type { LocaleEnvironment } from "../../../helpers/locale.js"
import type { TranslateMap } from "../../../helpers/translate.js"
import {
renderActivatableNameComponents,
@@ -40,7 +41,7 @@ import {
} from "../activatableNameChunks.js"
import { MISSING_VALUE } from "../unknown.js"
import { printDisplayOption } from "./displayOption.js"
import { hasPartValueObject, joinPrerequisiteParts, PrerequisitePart } from "./part.js"
import { hasPartValueObject, joinPrerequisiteParts, type PrerequisitePart } from "./part.js"
import {
printAdvantageDisadvantagePrerequisiteGroup,
printAnimistPowerPrerequisiteGroup,
@@ -57,7 +58,7 @@ import {
printPublicationPrerequisiteGroup,
printSpellworkPrerequisiteGroup,
} from "./prerequisiteGroups.js"
import { GetResolvedSelectOptionById } from "./single/activatable.js"
import type { GetResolvedSelectOptionById } from "./single/activatable.js"
type Prerequisite = { kind: string }
@@ -79,15 +80,17 @@ const printPrerequisitesDisjunction = <T extends Prerequisite>(
return printDisplayOption(locale.translateMap, disjunction.display_option)
}
const [first, ...other] = disjunction.list.map(getPrerequisiteTranslation).filter(isNotNullish)
const { list: disjunctionList } = disjunction
const [first, ...other] = disjunctionList.map(getPrerequisiteTranslation).filter(isNotNullish)
if (first === undefined) {
return undefined
}
if (
disjunction.list.length < 2 ||
disjunction.list.slice(1).every(part => part.kind === disjunction.list[0]!.kind)
disjunctionList.length < 2 ||
(isNotEmpty(disjunctionList) &&
disjunctionList.slice(1).every(part => part.kind === disjunctionList[0].kind))
) {
return {
label: first.label,
@@ -114,7 +117,13 @@ const printPrerequisitesDisjunction = <T extends Prerequisite>(
return {
value: locale.join(
[first, ...other].map(part => (part.label ?? "") + part.value),
[first, ...other].map(
part =>
(part.label ?? "") +
(typeof part.value === "string"
? part.value
: renderActivatableNameComponents(locale.translateMap, part.value, true)),
),
"disjunction",
),
sentenceType: undefined,
+2 -2
View File
@@ -3,7 +3,7 @@ import { deepEqual, equal } from "@elyukai/utils/equality"
import { on } from "@elyukai/utils/function"
import { compareNumber, reduceCompare, type Compare } from "@elyukai/utils/ordering"
import { assertExhaustive } from "@optolith/helpers/typeSafety"
import { SentenceType, type ActivatableIdentifier } from "optolith-database-schema/gen"
import type { ActivatableIdentifier, SentenceType } from "optolith-database-schema/gen"
import type { LocaleCompare } from "../../../helpers/locale.js"
import type { Translate, TranslateMap } from "../../../helpers/translate.js"
import {
@@ -192,7 +192,7 @@ export const joinPrerequisiteParts = (
on(part => part.type, equal),
)
.map(group => ({
type: group[0]!.type,
type: group[0].type,
parts: group.map(groupItem => groupItem.part),
}))
.reduce(
@@ -1,5 +1,6 @@
import { assertExhaustive } from "@optolith/helpers/typeSafety"
import {
import type {
ActivatableIdentifier,
AdvantageDisadvantagePrerequisiteGroup,
AnimistPowerPrerequisiteGroup,
ArcaneTraditionPrerequisiteGroup,
@@ -14,14 +15,16 @@ import {
PreconditionGroup,
ProfessionPrerequisiteGroup,
PublicationPrerequisiteGroup,
RatedIdentifier,
SpellworkPrerequisiteGroup,
type ActivatableIdentifier,
type RatedIdentifier,
} from "optolith-database-schema/gen"
import type { GetAllChildInstancesForParent, GetInstanceById } from "../../../helpers/getTypes.js"
import { LocaleEnvironment } from "../../../helpers/locale.js"
import { PrerequisitePart } from "./part.js"
import { GetResolvedSelectOptionById, printActivatablePrerequisite } from "./single/activatable.js"
import type { LocaleEnvironment } from "../../../helpers/locale.js"
import type { PrerequisitePart } from "./part.js"
import {
type GetResolvedSelectOptionById,
printActivatablePrerequisite,
} from "./single/activatable.js"
import { printAnimistPowerPrerequisite } from "./single/animistPower.js"
import { printBlessedTraditionPrerequisite } from "./single/blessedTradition.js"
import { printCommonSuggestedByRCPPrerequisite } from "./single/commonSuggestedByRCP.js"
@@ -8,12 +8,12 @@ import type {
ActivatablePrerequisite,
RequirableSelectOptionIdentifier,
} from "optolith-database-schema/gen"
import { type GetInstanceById } from "../../../../helpers/getTypes.js"
import { LocaleEnvironment } from "../../../../helpers/locale.js"
import type { GetInstanceById } from "../../../../helpers/getTypes.js"
import type { LocaleEnvironment } from "../../../../helpers/locale.js"
import type { LocaleMap, Translate } from "../../../../helpers/translate.js"
import { getNameComponents } from "../../activatableNameChunks.js"
import { printDisplayOption } from "../displayOption.js"
import { PrerequisitePart } from "../part.js"
import type { PrerequisitePart } from "../part.js"
/**
* Gets a resolved select option by its identifier.
@@ -2,9 +2,9 @@ import { isNotNullish } from "@optolith/helpers/nullable"
import { romanize } from "@optolith/helpers/roman"
import type { AnimistPowerPrerequisite } from "optolith-database-schema/gen"
import { type GetInstanceById } from "../../../../helpers/getTypes.js"
import { LocaleEnvironment } from "../../../../helpers/locale.js"
import type { LocaleEnvironment } from "../../../../helpers/locale.js"
import { printDisplayOption } from "../displayOption.js"
import { PrerequisitePart } from "../part.js"
import type { PrerequisitePart } from "../part.js"
/**
* Get the translation of a culture prerequisite.
@@ -24,7 +24,7 @@ export const printAnimistPowerPrerequisite = (
value: [
locale.translateMap(animistPower?.translations)?.name ?? "MISSING_VALUE",
prerequisite.level === undefined ? undefined : romanize(prerequisite.level),
prerequisite.value.toString(),
prerequisite.value.toFixed(),
]
.filter(isNotNullish)
.join(" "),
@@ -3,9 +3,9 @@ import type {
BlessedTraditionPrerequisite,
BlessedTraditionPrerequisiteRestriction,
} from "optolith-database-schema/gen"
import { LocaleEnvironment } from "../../../../helpers/locale.js"
import type { LocaleEnvironment } from "../../../../helpers/locale.js"
import { printDisplayOption } from "../displayOption.js"
import { PrerequisitePart } from "../part.js"
import type { PrerequisitePart } from "../part.js"
const printValue = (
locale: LocaleEnvironment,
@@ -1,11 +1,8 @@
import { assertExhaustive } from "@optolith/helpers/typeSafety"
import { LocaleEnvironment } from "../../../../helpers/locale.js"
import { PrerequisitePart } from "../part.js"
import type { LocaleEnvironment } from "../../../../helpers/locale.js"
import type { PrerequisitePart } from "../part.js"
const printType = (
locale: LocaleEnvironment,
type: "Advantage" | "Disadvantage",
): string => {
const printType = (locale: LocaleEnvironment, type: "Advantage" | "Disadvantage"): string => {
switch (type) {
case "Advantage":
return locale.translate("advantage")
@@ -1,8 +1,8 @@
import { CulturePrerequisite } from "optolith-database-schema/gen"
import { type GetInstanceById } from "../../../../helpers/getTypes.js"
import { LocaleEnvironment } from "../../../../helpers/locale.js"
import type { CulturePrerequisite } from "optolith-database-schema/gen"
import type { GetInstanceById } from "../../../../helpers/getTypes.js"
import type { LocaleEnvironment } from "../../../../helpers/locale.js"
import { printDisplayOption } from "../displayOption.js"
import { PrerequisitePart } from "../part.js"
import type { PrerequisitePart } from "../part.js"
/**
* Get the translation of a culture prerequisite.
@@ -1,13 +1,13 @@
import { assertExhaustive } from "@optolith/helpers/typeSafety"
import {
type EnhancementPrerequisite,
type SkillWithEnhancementsIdentifier,
import type {
EnhancementPrerequisite,
SkillWithEnhancementsIdentifier,
} from "optolith-database-schema/gen"
import { type GetInstanceById } from "../../../../helpers/getTypes.js"
import { LocaleEnvironment } from "../../../../helpers/locale.js"
import type { GetInstanceById } from "../../../../helpers/getTypes.js"
import type { LocaleEnvironment } from "../../../../helpers/locale.js"
import type { LocaleMap } from "../../../../helpers/translate.js"
import { MISSING_VALUE } from "../../unknown.js"
import { PrerequisitePart } from "../part.js"
import type { PrerequisitePart } from "../part.js"
const printLabel = (
locale: Pick<LocaleEnvironment, "translate">,
@@ -1,9 +1,9 @@
import { InfluencePrerequisite } from "optolith-database-schema/gen"
import type { InfluencePrerequisite } from "optolith-database-schema/gen"
import type { GetInstanceById } from "../../../../helpers/getTypes.js"
import { LocaleEnvironment } from "../../../../helpers/locale.js"
import type { LocaleEnvironment } from "../../../../helpers/locale.js"
import { MISSING_VALUE } from "../../unknown.js"
import { printDisplayOption } from "../displayOption.js"
import { PrerequisitePart } from "../part.js"
import type { PrerequisitePart } from "../part.js"
/**
* Get the translation of a culture prerequisite.
@@ -3,9 +3,9 @@ import type {
MagicalTraditionPrerequisite,
MagicalTraditionPrerequisiteRestriction,
} from "optolith-database-schema/gen"
import { LocaleEnvironment } from "../../../../helpers/locale.js"
import type { LocaleEnvironment } from "../../../../helpers/locale.js"
import { printDisplayOption } from "../displayOption.js"
import { PrerequisitePart } from "../part.js"
import type { PrerequisitePart } from "../part.js"
const printValue = (
locale: LocaleEnvironment,
@@ -1,5 +1,5 @@
import { LocaleEnvironment } from "../../../../helpers/locale.js"
import { PrerequisitePart } from "../part.js"
import type { LocaleEnvironment } from "../../../../helpers/locale.js"
import type { PrerequisitePart } from "../part.js"
/**
* Get the translation of a culture prerequisite.
@@ -1,11 +1,11 @@
import { isNotNullish } from "@optolith/helpers/nullable"
import { romanize } from "@optolith/helpers/roman"
import type { PactPrerequisite } from "optolith-database-schema/gen"
import { type GetInstanceById } from "../../../../helpers/getTypes.js"
import { LocaleEnvironment } from "../../../../helpers/locale.js"
import type { GetInstanceById } from "../../../../helpers/getTypes.js"
import type { LocaleEnvironment } from "../../../../helpers/locale.js"
import { MISSING_VALUE } from "../../unknown.js"
import { printDisplayOption } from "../displayOption.js"
import { PrerequisitePart } from "../part.js"
import type { PrerequisitePart } from "../part.js"
/**
* Get the translation of a culture prerequisite.
@@ -1,8 +1,8 @@
import { type PersonalityTraitPrerequisite } from "optolith-database-schema/gen"
import { type GetInstanceById } from "../../../../helpers/getTypes.js"
import { LocaleEnvironment } from "../../../../helpers/locale.js"
import type { PersonalityTraitPrerequisite } from "optolith-database-schema/gen"
import type { GetInstanceById } from "../../../../helpers/getTypes.js"
import type { LocaleEnvironment } from "../../../../helpers/locale.js"
import { printDisplayOption } from "../displayOption.js"
import { PrerequisitePart } from "../part.js"
import type { PrerequisitePart } from "../part.js"
/**
* Get the translation of a personality trait prerequisite.
@@ -1,7 +1,7 @@
import { PrimaryAttributePrerequisite } from "optolith-database-schema/gen"
import { LocaleEnvironment } from "../../../../helpers/locale.js"
import type { PrimaryAttributePrerequisite } from "optolith-database-schema/gen"
import type { LocaleEnvironment } from "../../../../helpers/locale.js"
import { printDisplayOption } from "../displayOption.js"
import { PrerequisitePart } from "../part.js"
import type { PrerequisitePart } from "../part.js"
/**
* Get the translation of a state prerequisite.
@@ -16,7 +16,7 @@ export const printPrimaryAttributePrerequisite = (
return {
label: `${locale.translate("Primary Attribute")} `,
value: prerequisite.value.toString(),
value: prerequisite.value.toFixed(),
sentenceType: undefined,
isMeta: false,
}
@@ -1,9 +1,9 @@
import { type ProfessionPrerequisite } from "optolith-database-schema/gen"
import { type GetAllChildInstancesForParent } from "../../../../helpers/getTypes.js"
import { LocaleEnvironment } from "../../../../helpers/locale.js"
import type { ProfessionPrerequisite } from "optolith-database-schema/gen"
import type { GetAllChildInstancesForParent } from "../../../../helpers/getTypes.js"
import type { LocaleEnvironment } from "../../../../helpers/locale.js"
import { getProfessionName } from "../../professions.js"
import { printDisplayOption } from "../displayOption.js"
import { PrerequisitePart } from "../part.js"
import type { PrerequisitePart } from "../part.js"
/**
* Get the translation of a profession prerequisite.
@@ -1,8 +1,8 @@
import { PublicationPrerequisite } from "optolith-database-schema/gen"
import { type GetInstanceById } from "../../../../helpers/getTypes.js"
import { LocaleEnvironment } from "../../../../helpers/locale.js"
import type { PublicationPrerequisite } from "optolith-database-schema/gen"
import type { GetInstanceById } from "../../../../helpers/getTypes.js"
import type { LocaleEnvironment } from "../../../../helpers/locale.js"
import { printDisplayOption } from "../displayOption.js"
import { PrerequisitePart } from "../part.js"
import type { PrerequisitePart } from "../part.js"
/**
* Get the translation of a state prerequisite.
@@ -1,8 +1,8 @@
import { RacePrerequisite } from "optolith-database-schema/gen"
import { type GetInstanceById } from "../../../../helpers/getTypes.js"
import { LocaleEnvironment } from "../../../../helpers/locale.js"
import type { RacePrerequisite } from "optolith-database-schema/gen"
import type { GetInstanceById } from "../../../../helpers/getTypes.js"
import type { LocaleEnvironment } from "../../../../helpers/locale.js"
import { printDisplayOption } from "../displayOption.js"
import { PrerequisitePart } from "../part.js"
import type { PrerequisitePart } from "../part.js"
/**
* Get the translation of a race prerequisite.
@@ -1,11 +1,11 @@
import { assertExhaustive } from "@optolith/helpers/typeSafety"
import type { RatedIdentifier, RatedPrerequisite } from "optolith-database-schema/gen"
import { type GetInstanceById } from "../../../../helpers/getTypes.js"
import { LocaleEnvironment } from "../../../../helpers/locale.js"
import type { LocaleEnvironment } from "../../../../helpers/locale.js"
import type { TranslateMap } from "../../../../helpers/translate.js"
import { MISSING_VALUE } from "../../unknown.js"
import { printDisplayOption } from "../displayOption.js"
import { PrerequisitePart } from "../part.js"
import type { PrerequisitePart } from "../part.js"
const printRatedName = (
getInstanceById: GetInstanceById<
@@ -80,7 +80,7 @@ export const printRatedPrerequisite = (
}
return {
value: `${printRatedName(getInstanceById, locale.translateMap, prerequisite.id)} ${prerequisite.value}`,
value: `${printRatedName(getInstanceById, locale.translateMap, prerequisite.id)} ${prerequisite.value.toFixed()}`,
sentenceType: undefined,
isMeta: false,
}
@@ -4,11 +4,11 @@ import type {
RatedMinimumNumberPrerequisite,
RatedMinimumNumberPrerequisiteCombatTechniquesTargetGroup,
} from "optolith-database-schema/gen"
import { type GetInstanceById } from "../../../../helpers/getTypes.js"
import { LocaleEnvironment } from "../../../../helpers/locale.js"
import type { GetInstanceById } from "../../../../helpers/getTypes.js"
import type { LocaleEnvironment } from "../../../../helpers/locale.js"
import { MISSING_VALUE } from "../../unknown.js"
import { printDisplayOption } from "../displayOption.js"
import { PrerequisitePart } from "../part.js"
import type { PrerequisitePart } from "../part.js"
const printNumberOfTheFollowingSkills = (locale: LocaleEnvironment, number: number): string =>
locale.translate(".input {$count :number} {{{$count} of the following skills}}", {
@@ -91,7 +91,7 @@ export const printRatedMinimumNumberPrerequisite = (
locale,
prerequisite.targets.CombatTechniques.group,
prerequisite.number,
)} ${prerequisite.value}`,
)} ${prerequisite.value.toFixed()}`,
sentenceType: undefined,
isMeta: false,
}
@@ -1,9 +1,9 @@
import { isNotNullish } from "@optolith/helpers/nullable"
import { RatedSumPrerequisite } from "optolith-database-schema/gen"
import { type GetInstanceById } from "../../../../helpers/getTypes.js"
import { LocaleEnvironment } from "../../../../helpers/locale.js"
import type { RatedSumPrerequisite } from "optolith-database-schema/gen"
import type { GetInstanceById } from "../../../../helpers/getTypes.js"
import type { LocaleEnvironment } from "../../../../helpers/locale.js"
import { printDisplayOption } from "../displayOption.js"
import { PrerequisitePart } from "../part.js"
import type { PrerequisitePart } from "../part.js"
/**
* Get the translation of a rated sum prerequisite.
@@ -1,6 +1,6 @@
import { RulePrerequisite } from "optolith-database-schema/gen"
import { LocaleEnvironment } from "../../../../helpers/locale.js"
import { PrerequisitePart } from "../part.js"
import type { RulePrerequisite } from "optolith-database-schema/gen"
import type { LocaleEnvironment } from "../../../../helpers/locale.js"
import type { PrerequisitePart } from "../part.js"
/**
* Get the translation of a rule prerequisite.
@@ -1,7 +1,7 @@
import { assertExhaustive } from "@optolith/helpers/typeSafety"
import type { BinarySex, SexPrerequisite } from "optolith-database-schema/gen"
import type { Translate } from "../../../../helpers/translate.js"
import { PrerequisitePart } from "../part.js"
import type { PrerequisitePart } from "../part.js"
const printId = (translate: Translate, id: BinarySex): string => {
switch (id.kind) {
@@ -1,15 +1,12 @@
import { assertExhaustive } from "@optolith/helpers/typeSafety"
import {
import type {
SexualCharacteristic,
SexualCharacteristicPrerequisite,
} from "optolith-database-schema/gen"
import { LocaleEnvironment } from "../../../../helpers/locale.js"
import { PrerequisitePart } from "../part.js"
import type { LocaleEnvironment } from "../../../../helpers/locale.js"
import type { PrerequisitePart } from "../part.js"
const printId = (
locale: LocaleEnvironment,
id: SexualCharacteristic,
): string => {
const printId = (locale: LocaleEnvironment, id: SexualCharacteristic): string => {
switch (id.kind) {
case "Penis":
return locale.translate("Penis")
@@ -1,8 +1,8 @@
import type { SocialStatusPrerequisite } from "optolith-database-schema/gen"
import { type GetInstanceById } from "../../../../helpers/getTypes.js"
import { LocaleEnvironment } from "../../../../helpers/locale.js"
import type { GetInstanceById } from "../../../../helpers/getTypes.js"
import type { LocaleEnvironment } from "../../../../helpers/locale.js"
import { printDisplayOption } from "../displayOption.js"
import { PrerequisitePart } from "../part.js"
import type { PrerequisitePart } from "../part.js"
/**
* Get the translation of a social status prerequisite.
@@ -1,8 +1,8 @@
import { StatePrerequisite } from "optolith-database-schema/gen"
import { type GetInstanceById } from "../../../../helpers/getTypes.js"
import { LocaleEnvironment } from "../../../../helpers/locale.js"
import type { StatePrerequisite } from "optolith-database-schema/gen"
import type { GetInstanceById } from "../../../../helpers/getTypes.js"
import type { LocaleEnvironment } from "../../../../helpers/locale.js"
import { printDisplayOption } from "../displayOption.js"
import { PrerequisitePart } from "../part.js"
import type { PrerequisitePart } from "../part.js"
/**
* Get the translation of a state prerequisite.
@@ -1,7 +1,7 @@
import { TextPrerequisite } from "optolith-database-schema/gen"
import { LocaleEnvironment } from "../../../../helpers/locale.js"
import type { TextPrerequisite } from "optolith-database-schema/gen"
import type { LocaleEnvironment } from "../../../../helpers/locale.js"
import { MISSING_VALUE } from "../../unknown.js"
import { PrerequisitePart } from "../part.js"
import type { PrerequisitePart } from "../part.js"
/**
* Get the translation of a text prerequisite.
@@ -1,52 +1,37 @@
import { Reader } from "@elyukai/utils/reader"
import { isNotNullish, mapNullable } from "@optolith/helpers/nullable"
import { assertExhaustive } from "@optolith/helpers/typeSafety"
import {
import type {
CastingTime,
CastingTimeDuringLovemaking,
CastingTimeIncludingLovemaking,
DurationUnitValue,
FastCastingTime,
FastSkillNonModifiableCastingTime,
ModifiableCastingTime,
SkillModificationLevel_ID,
SlowCastingTime,
SlowSkillNonModifiableCastingTime,
type DurationUnitValue,
type SkillModificationLevel_ID,
} from "optolith-database-schema/gen"
import { Case } from "../../../../helpers/enums.js"
import {
getInstanceByIdFnR,
modifiableBySpeedR,
type StdReader,
} from "../../reader.js"
import {
formatCombinedTimeSpanR,
formatTimeSpanR,
} from "../../units/timeSpan.js"
import { getInstanceByIdFnR, modifiableBySpeedR, type StdReader } from "../../reader.js"
import { formatCombinedTimeSpanR, formatTimeSpanR } from "../../units/timeSpan.js"
import { MISSING_VALUE } from "../../unknown.js"
import {
appendNonModifiableSuffix,
ModifiableParameter,
} from "./nonModifiableSuffix.js"
import { appendNonModifiableSuffix, ModifiableParameter } from "./nonModifiableSuffix.js"
import { Speed } from "./speed.js"
const deriveModifiableCastingTime = (
modificationLevelId: SkillModificationLevel_ID,
): StdReader<
DurationUnitValue | undefined,
"s" | "ibi",
"SkillModificationLevel"
> =>
): StdReader<DurationUnitValue | undefined, "s" | "ibi", "SkillModificationLevel"> =>
getInstanceByIdFnR<"SkillModificationLevel">().thenW(
getInstanceById =>
mapNullable(
getInstanceById("SkillModificationLevel", modificationLevelId),
modificationLevel =>
modifiableBySpeedR("casting_time", modificationLevel).map(
castingTime =>
typeof castingTime === "number"
? { value: castingTime, unit: Case("Actions") }
: castingTime,
modifiableBySpeedR("casting_time", modificationLevel).map(castingTime =>
typeof castingTime === "number"
? { value: castingTime, unit: Case("Actions") }
: castingTime,
),
) ?? Reader.of(undefined),
)
@@ -54,11 +39,8 @@ const deriveModifiableCastingTime = (
const renderModifiableCastingTime = (
value: ModifiableCastingTime,
): StdReader<string, "t" | "rts" | "s" | "ibi", "SkillModificationLevel"> =>
deriveModifiableCastingTime(value.initial_modification_level).thenW(
castingTime =>
castingTime === undefined
? Reader.of(MISSING_VALUE)
: formatCombinedTimeSpanR(castingTime),
deriveModifiableCastingTime(value.initial_modification_level).thenW(castingTime =>
castingTime === undefined ? Reader.of(MISSING_VALUE) : formatCombinedTimeSpanR(castingTime),
)
const renderCastingTimeDuringLovemaking = (
@@ -70,8 +52,7 @@ const renderCastingTimeDuringLovemaking = (
*/
export const renderFastSkillNonModifiableCastingTime = (
value: FastSkillNonModifiableCastingTime,
): StdReader<string, "t" | "rts"> =>
formatTimeSpanR(Case("Actions"), value.actions)
): StdReader<string, "t" | "rts"> => formatTimeSpanR(Case("Actions"), value.actions)
/**
* Get the text for a non-modifiable casting time of a slow activatable skill.
@@ -88,11 +69,7 @@ export const renderCastingTime = <NonModifiable extends object>(
value: NonModifiable,
) => StdReader<string, "t" | "rts" | "s" | "nms">,
value: CastingTime<NonModifiable>,
): StdReader<
string,
"t" | "rts" | "s" | "nms" | "ibi",
"SkillModificationLevel"
> => {
): StdReader<string, "t" | "rts" | "s" | "nms" | "ibi", "SkillModificationLevel"> => {
switch (value.kind) {
case "Modifiable":
return renderModifiableCastingTime(value.Modifiable)
@@ -112,11 +89,9 @@ const renderCastingTimeIncludingLovemaking = <NonModifiable extends object>(
value: CastingTimeIncludingLovemaking<NonModifiable>,
) =>
Reader.sequence([
mapNullable(value.default, def =>
renderCastingTime(renderNonModifiableCastingTime, def),
) ?? Reader.of(undefined),
mapNullable(value.during_lovemaking, renderCastingTimeDuringLovemaking) ??
mapNullable(value.default, def => renderCastingTime(renderNonModifiableCastingTime, def)) ??
Reader.of(undefined),
mapNullable(value.during_lovemaking, renderCastingTimeDuringLovemaking) ?? Reader.of(undefined),
]).map(texts => texts.filter(isNotNullish).join(" / "))
/**
@@ -125,10 +100,9 @@ const renderCastingTimeIncludingLovemaking = <NonModifiable extends object>(
export const renderFastCastingTime = (
value: FastCastingTime,
): StdReader<string, "t" | "rts" | "nms" | "ibi", "SkillModificationLevel"> =>
renderCastingTimeIncludingLovemaking(
renderFastSkillNonModifiableCastingTime,
value,
).with(env => ({ ...env, speed: Speed.Fast }))
renderCastingTimeIncludingLovemaking(renderFastSkillNonModifiableCastingTime, value).with(
env => ({ ...env, speed: Speed.Fast }),
)
/**
* Get the text for the casting time of a slow activatable skill.
@@ -136,7 +110,6 @@ export const renderFastCastingTime = (
export const renderSlowCastingTime = (
value: SlowCastingTime,
): StdReader<string, "t" | "rts" | "nms" | "ibi", "SkillModificationLevel"> =>
renderCastingTimeIncludingLovemaking(
renderSlowSkillNonModifiableCastingTime,
value,
).with(env => ({ ...env, speed: Speed.Slow }))
renderCastingTimeIncludingLovemaking(renderSlowSkillNonModifiableCastingTime, value).with(
env => ({ ...env, speed: Speed.Slow }),
)
+35 -71
View File
@@ -2,23 +2,23 @@ import { identity } from "@elyukai/utils/function"
import { Reader } from "@elyukai/utils/reader"
import { mapNullable } from "@optolith/helpers/nullable"
import { assertExhaustive } from "@optolith/helpers/typeSafety"
import {
import type {
CheckResultBasedModifier,
DurationUnitValue,
ElvenMagicalSongPermanentCost,
FirstPersonMagicalMelodyCost,
MultipleOneTimeCosts,
NonModifiableOneTimeCostPerCountable,
NonModifiableSustainedCost,
OneTimeCost,
OneTimeCostMap,
ResponsiveText,
ResponsiveTextOptional,
ResponsiveTextReplace,
SingleOneTimeCost,
SkillModificationLevel_ID,
SustainedCost,
type CheckResultBasedModifier,
type DurationUnitValue,
type ElvenMagicalSongPermanentCost,
type FirstPersonMagicalMelodyCost,
type NonModifiableOneTimeCostPerCountable,
type OneTimeCostMap,
type ResponsiveText,
type ResponsiveTextOptional,
type ResponsiveTextReplace,
type SkillModificationLevel_ID,
type SustainedCostMap,
SustainedCostMap,
} from "optolith-database-schema/gen"
import { type LocaleMap } from "../../../../helpers/translate.js"
import { renderResponsiveMap } from "../../map.js"
@@ -37,18 +37,12 @@ import {
type StdEnv,
type StdReader,
} from "../../reader.js"
import {
appendNoteIfNeeded,
replaceTextIfNeeded,
} from "../../responsiveText.js"
import { appendNoteIfNeeded, replaceTextIfNeeded } from "../../responsiveText.js"
import { formatCombinedTimeSpanR } from "../../units/timeSpan.js"
import { MISSING_VALUE } from "../../unknown.js"
import { appendCheckResultModifier } from "./checkResultBased.js"
import { wrapIfMinimum } from "./isMinimumMaximum.js"
import {
appendNonModifiableSuffix,
ModifiableParameter,
} from "./nonModifiableSuffix.js"
import { appendNonModifiableSuffix, ModifiableParameter } from "./nonModifiableSuffix.js"
const deriveModifiableCost = (
modificationLevelId: SkillModificationLevel_ID,
@@ -85,11 +79,10 @@ const appendPerCountableToCostIfNeeded = (
return responsiveTextR(translation.countable)
.thenW(countable =>
responsiveTranslateR(
"{$cost} per {$countable}",
"{$cost}/{$countable}",
{ cost: baseCost, countable },
),
responsiveTranslateR("{$cost} per {$countable}", "{$cost}/{$countable}", {
cost: baseCost,
countable,
}),
)
.thenW(text =>
minimum_total === undefined
@@ -121,11 +114,9 @@ const appendElvenPermanentCostIfNeeded = (
baseCost: string,
): StdReader<string, "t" | "tm" | "rts"> =>
mapNullable(permanent, value =>
responsiveTranslateR(
".input {$value :number} {{{$value} permanent AE}}",
"{$value} pAE",
{ value: value.value },
)
responsiveTranslateR(".input {$value :number} {{{$value} permanent AE}}", "{$value} pAE", {
value: value.value,
})
.thenW(text => replaceTextIfNeeded(value.translations, text))
.map(text => baseCost + text),
) ?? Reader.of(baseCost)
@@ -138,9 +129,7 @@ const appendFamiliarsTrickLPCostIfNeeded = (
? Reader.of(baseCost)
: formatEnergyR(lpValue)
.with((env: StdEnv<"t">) => ({ ...env, energyUnit: "LifePoints" }))
.thenW(formattedLpCost =>
responsiveLocaleJoinR([baseCost, formattedLpCost], "conjunction"),
)
.thenW(formattedLpCost => responsiveLocaleJoinR([baseCost, formattedLpCost], "conjunction"))
/**
* Returns the text for the modifiable one-time cost of an activatable skill.
@@ -152,11 +141,7 @@ export const renderModifiableOneTimeCost = (value: {
replacement?: ResponsiveTextReplace
additional?: ResponsiveText
}>
}): StdReader<
string,
"t" | "tm" | "rts" | "eu" | "s" | "ibi",
"SkillModificationLevel"
> =>
}): StdReader<string, "t" | "tm" | "rts" | "eu" | "s" | "ibi", "SkillModificationLevel"> =>
deriveModifiableCost(value.initial_modification_level).thenW(cost =>
cost === undefined
? Reader.of(MISSING_VALUE)
@@ -175,10 +160,7 @@ export const renderModifiableOneTimeCost = (value: {
),
)
const appendIntervalToCost = (
interval: DurationUnitValue | undefined,
baseCost: string,
) =>
const appendIntervalToCost = (interval: DurationUnitValue | undefined, baseCost: string) =>
interval === undefined
? Reader.of(baseCost)
: formatCombinedTimeSpanR(interval).then(formattedInterval =>
@@ -219,7 +201,7 @@ export const renderNonModifiableOneTimeCost = (
.then(
shouldAppendNonModifiableSuffix
? base => appendNonModifiableSuffix(ModifiableParameter.Cost, base)
: Reader.of,
: base => Reader.of(base),
)
type IndefiniteCost = {
@@ -243,15 +225,11 @@ export const renderIndefiniteCost = (
? Reader.of(MISSING_VALUE)
: responsiveTextR(translation.description),
)
.map(
modifier === undefined
? identity
: base => appendCheckResultModifier(base, modifier),
)
.map(modifier === undefined ? identity : base => appendCheckResultModifier(base, modifier))
.thenW(
shouldAppendNonModifiableSuffix
? base => appendNonModifiableSuffix(ModifiableParameter.Cost, base)
: Reader.of,
: base => Reader.of(base),
)
}
@@ -282,12 +260,9 @@ const renderMultipleOneTimeCosts = (
"t" | "tm" | "lj" | "rts" | "eu" | "s" | "nms" | "ibi",
"SkillModificationLevel"
> => {
const appendNonModifiableIfRequested = !value.every(
part => part.kind === "Modifiable",
)
? (text: string) =>
appendNonModifiableSuffix(ModifiableParameter.Cost, text)
: Reader.of
const appendNonModifiableIfRequested = !value.every(part => part.kind === "Modifiable")
? (text: string) => appendNonModifiableSuffix(ModifiableParameter.Cost, text)
: (text: string) => Reader.of(text)
return Reader.sequence(value.map(renderSingleOneTimeCost))
.thenW(list => responsiveLocaleJoinR(list, type))
@@ -313,6 +288,7 @@ export const renderOneTimeCostMap = (
translate(", {$value} of which are permanent", {
value: values,
}),
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- is checked beforehand
getAdditionalValue: option => option.permanent_value!,
}
: undefined,
@@ -323,9 +299,7 @@ export const renderOneTimeCostMap = (
const renderSustainedCostMap = (value: SustainedCostMap) =>
formatEnergyFnR
.thenW(formatEnergy =>
renderResponsiveMap(value, option => option.value, formatEnergy),
)
.thenW(formatEnergy => renderResponsiveMap(value, option => option.value, formatEnergy))
.then(text => appendNonModifiableSuffix(ModifiableParameter.Cost, text))
/**
@@ -378,20 +352,14 @@ const renderModifiableSustainedCost = (value: {
const activationCost = formatEnergy(cost)
const intervalCost = formatEnergy(cost / 2)
return buildSustainedCost(
activationCost,
intervalCost,
value.interval,
)
return buildSustainedCost(activationCost, intervalCost, value.interval)
}),
)
/**
* Returns the text for the non-modifiable cost of a sustained activatable skill.
*/
export const renderNonModifiableSustainedCost = (
value: NonModifiableSustainedCost,
) =>
export const renderNonModifiableSustainedCost = (value: NonModifiableSustainedCost) =>
formatEnergyFnR
.thenW(formatEnergy => {
const activationCost = formatEnergy(value.value)
@@ -413,11 +381,7 @@ export const renderNonModifiableSustainedCost = (
*/
export const renderSustainedCost = (
value: SustainedCost,
): StdReader<
string,
"t" | "tm" | "rts" | "eu" | "s" | "nms" | "ibi",
"SkillModificationLevel"
> => {
): StdReader<string, "t" | "tm" | "rts" | "eu" | "s" | "nms" | "ibi", "SkillModificationLevel"> => {
switch (value.kind) {
case "Modifiable":
return renderModifiableSustainedCost(value.Modifiable)
@@ -20,18 +20,13 @@ import {
type StdReader,
} from "../../reader.js"
import { replaceTextIfNeeded } from "../../responsiveText.js"
import {
formatCombinedTimeSpanR,
formatTimeSpanR,
} from "../../units/timeSpan.js"
import { formatCombinedTimeSpanR, formatTimeSpanR } from "../../units/timeSpan.js"
import { MISSING_VALUE } from "../../unknown.js"
import { renderCheckResultBasedValue } from "./checkResultBased.js"
import { wrapAsMaximum, wrapIfMaximum } from "./isMinimumMaximum.js"
import { appendInParensIfNotEmpty } from "./parensIf.js"
const renderImmediateDuration = (
value?: Immediate,
): StdReader<string, "t" | "tm" | "rts"> =>
const renderImmediateDuration = (value?: Immediate): StdReader<string, "t" | "tm" | "rts"> =>
translateR("Immediate")
.thenW(
base =>
@@ -43,16 +38,10 @@ const renderImmediateDuration = (
)
.thenW(text => replaceTextIfNeeded(value?.translations, text))
const renderPermanentDuration = (
value: PermanentDuration,
): StdReader<string, "t" | "tm" | "rts"> =>
translateR("Permanent").thenW(text =>
replaceTextIfNeeded(value.translations, text),
)
const renderPermanentDuration = (value: PermanentDuration): StdReader<string, "t" | "tm" | "rts"> =>
translateR("Permanent").thenW(text => replaceTextIfNeeded(value.translations, text))
const renderFixedDuration = (
value: FixedDuration,
): StdReader<string, "t" | "tm" | "rts"> =>
const renderFixedDuration = (value: FixedDuration): StdReader<string, "t" | "tm" | "rts"> =>
formatCombinedTimeSpanR(value)
.then(text => wrapIfMaximum(value.is_maximum, text))
.thenW(text => replaceTextIfNeeded(value.translations, text))
@@ -84,17 +73,14 @@ const renderIndefiniteDuration = (value: {
)
.thenW(
maximum === undefined
? Reader.of
? text => Reader.of(text)
: text =>
// eslint-disable-next-line @typescript-eslint/no-use-before-define
renderOneTimeDuration(maximum).then(maximumText =>
translateR(
"{$defaultDuration}, but no more than {$maximumDuration}",
{
defaultDuration: text,
maximumDuration: maximumText,
},
),
translateR("{$defaultDuration}, but no more than {$maximumDuration}", {
defaultDuration: text,
maximumDuration: maximumText,
}),
),
)
}
@@ -167,16 +153,12 @@ export const renderSustainedDuration = (
): StdReader<string, "t" | "rts"> =>
value === undefined
? responsiveTranslateR("Sustained", "(S)")
: formatCombinedTimeSpanR(value.maximum).then(maxText =>
wrapAsMaximum(maxText),
)
: formatCombinedTimeSpanR(value.maximum).then(maxText => wrapAsMaximum(maxText))
/**
* Returns the text for the duration of a musical activatable skill.
*/
export const renderMusicDuration = (
duration: MusicDuration,
): StdReader<string, "t"> =>
export const renderMusicDuration = (duration: MusicDuration): StdReader<string, "t"> =>
Reader.asks(({ translate }) => {
const length = (() => {
switch (duration.length.kind) {
@@ -51,13 +51,10 @@ export const renderEffect = (
value: effect.Plain.text,
}))
case "ForEachQualityLevel":
return getContentPartsForQualityLevels(
index => index + 1,
effect.ForEachQualityLevel,
)
return getContentPartsForQualityLevels(index => index + 1, effect.ForEachQualityLevel)
case "ForEachTwoQualityLevels":
return getContentPartsForQualityLevels(
index => `${index * 2 + 1}${index * 2 + 2}`,
index => `${(index * 2 + 1).toFixed()}${(index * 2 + 2).toFixed()}`,
effect.ForEachTwoQualityLevels,
)
default:
+12 -33
View File
@@ -18,18 +18,12 @@ import {
translateR,
type StdReader,
} from "../../reader.js"
import {
appendNoteIfNeeded,
replaceTextIfNeeded,
} from "../../responsiveText.js"
import { appendNoteIfNeeded, replaceTextIfNeeded } from "../../responsiveText.js"
import { formatCombinedLengthR, formatLengthR } from "../../units/length.js"
import { MISSING_VALUE } from "../../unknown.js"
import { renderCheckResultBasedValue } from "./checkResultBased.js"
import { wrapIfMaximum } from "./isMinimumMaximum.js"
import {
appendNonModifiableSuffix,
ModifiableParameter,
} from "./nonModifiableSuffix.js"
import { appendNonModifiableSuffix, ModifiableParameter } from "./nonModifiableSuffix.js"
const deriveModifiableRange = (
modificationLevelId: SkillModificationLevel_ID,
@@ -47,21 +41,17 @@ const deriveModifiableRange = (
translateMapR(modificationLevel.translations).thenW(translation =>
translation === undefined
? Reader.of({ value, translation: undefined })
: modifiableBySpeedOptionalR("range", translation).map(
valueTranslation => ({
value,
translation: valueTranslation,
}),
),
: modifiableBySpeedOptionalR("range", translation).map(valueTranslation => ({
value,
translation: valueTranslation,
})),
),
),
) ?? Reader.of(undefined),
)
const wrapIfRadius = (is_radius: boolean | undefined, text: string) =>
translateFnR.map(translate =>
is_radius === true ? `${text} ${translate("Radius")}` : text,
)
translateFnR.map(translate => (is_radius === true ? `${text} ${translate("Radius")}` : text))
const renderModifiableRange = (value: ModifiableRange) =>
deriveModifiableRange(value.initial_modification_level)
@@ -114,9 +104,8 @@ export const renderNonModifiableRange = (
shouldAppendNonModifiableSuffix: boolean,
): StdReader<string, "t" | "tm" | "rts" | "nms"> => {
const appendNonModifiableSuffixIfNeeded = shouldAppendNonModifiableSuffix
? (text: string) =>
appendNonModifiableSuffix(ModifiableParameter.Range, text)
: Reader.of
? (text: string) => appendNonModifiableSuffix(ModifiableParameter.Range, text)
: (text: string) => Reader.of(text)
switch (value.kind) {
case "Sight":
@@ -128,9 +117,7 @@ export const renderNonModifiableRange = (
case "Touch":
return translateR("Touch").thenW(appendNonModifiableSuffixIfNeeded)
case "Fixed": {
return renderFixedRange(value.Fixed).thenW(
appendNonModifiableSuffixIfNeeded,
)
return renderFixedRange(value.Fixed).thenW(appendNonModifiableSuffixIfNeeded)
}
case "CheckResultBased":
return getCheckResultBasedRangeTranslation(value.CheckResultBased).then(
@@ -147,11 +134,7 @@ export const renderNonModifiableRange = (
export const renderRangeValue = (
value: RangeValue,
shouldAppendNonModifiableSuffix: boolean,
): StdReader<
string,
"t" | "tm" | "rts" | "s" | "nms" | "ibi",
"SkillModificationLevel"
> => {
): StdReader<string, "t" | "tm" | "rts" | "s" | "nms" | "ibi", "SkillModificationLevel"> => {
switch (value.kind) {
case "Modifiable":
return renderModifiableRange(value.Modifiable)
@@ -172,11 +155,7 @@ export const renderRangeValue = (
*/
export const renderRange = (
value: Range,
): StdReader<
string,
"t" | "tm" | "rts" | "s" | "nms" | "ibi",
"SkillModificationLevel"
> =>
): StdReader<string, "t" | "tm" | "rts" | "s" | "nms" | "ibi", "SkillModificationLevel"> =>
renderRangeValue(value.value, true)
.then(text => replaceTextIfNeeded(value.translations, text))
.then(text => appendNoteIfNeeded(value.translations, text))
@@ -1,5 +1,5 @@
import { assertExhaustive } from "@optolith/helpers/typeSafety"
import {
import type {
FastSkillModificationLevelConfig,
SkillModificationLevel,
SlowSkillModificationLevelConfig,
@@ -21,23 +21,16 @@ type SpeedMap = {
/**
* Returns a common value for a skill modification level depending on the speed.
*/
export const getModifiableBySpeed = <
S extends Speed,
K extends keyof SpeedMap[S],
>(
export const getModifiableBySpeed = <S extends Speed, K extends keyof SpeedMap[S]>(
speed: S,
key: K,
level: SkillModificationLevel,
): SpeedMap[S][K] => {
switch (speed) {
case Speed.Fast:
return level.fast[
key as keyof FastSkillModificationLevelConfig
] as SpeedMap[S][K]
return level.fast[key as keyof FastSkillModificationLevelConfig] as SpeedMap[S][K]
case Speed.Slow:
return level.slow[
key as keyof SlowSkillModificationLevelConfig
] as SpeedMap[S][K]
return level.slow[key as keyof SlowSkillModificationLevelConfig] as SpeedMap[S][K]
default:
return assertExhaustive(speed)
}
@@ -7,12 +7,7 @@ import type {
TargetCategory_ID,
} from "optolith-database-schema/gen"
import { type RawDefinitionListEntityDescriptionSectionItem } from "../../../../index.js"
import {
getInstanceByIdFnR,
translateMapR,
translateR,
type StdReader,
} from "../../reader.js"
import { getInstanceByIdFnR, translateMapR, translateR, type StdReader } from "../../reader.js"
import { MISSING_VALUE } from "../../unknown.js"
import { appendInParensIfNotEmpty } from "./parensIf.js"
@@ -20,12 +15,8 @@ const renderPredefined = (targetCategoryId: TargetCategory_ID) =>
getInstanceByIdFnR<"TargetCategory">()
.thenW(
getInstanceById =>
mapNullable(
getInstanceById("TargetCategory", targetCategoryId),
targetCategory =>
translateMapR(targetCategory.translations).map(
translation => translation?.name,
),
mapNullable(getInstanceById("TargetCategory", targetCategoryId), targetCategory =>
translateMapR(targetCategory.translations).map(translation => translation?.name),
) ?? Reader.of(undefined),
)
.map(translation => translation ?? MISSING_VALUE)
@@ -56,21 +47,16 @@ const getTargetCategoryTranslationByType = (
*/
export const renderTargetCategory = (
values: AffectedTargetCategories,
): StdReader<
RawDefinitionListEntityDescriptionSectionItem,
"t" | "tm" | "ibi",
"TargetCategory"
> =>
): StdReader<RawDefinitionListEntityDescriptionSectionItem, "t" | "tm" | "ibi", "TargetCategory"> =>
translateR("Target Category").thenW(label =>
(values.length === 0
? translateR("all")
: Reader.sequence(
values.map(({ id, translations }) =>
getTargetCategoryTranslationByType(id).then(
text =>
translateMapR(translations)
.map(translation => translation?.note)
.map(note => appendInParensIfNotEmpty(note, text)) ?? text,
getTargetCategoryTranslationByType(id).then(text =>
translateMapR(translations)
.map(translation => translation?.note)
.map(note => appendInParensIfNotEmpty(note, text)),
),
),
).map(texts => texts.join(", "))
+9 -2
View File
@@ -16,7 +16,12 @@ import type {
GetAllInstances,
GetInstanceById,
} from "../../helpers/getTypes.js"
import type { LocaleCompare, LocaleJoin, LocaleJoinType } from "../../helpers/locale.js"
import type {
FormatNumber,
LocaleCompare,
LocaleJoin,
LocaleJoinType,
} from "../../helpers/locale.js"
import type {
Format,
LocaleMap,
@@ -43,6 +48,7 @@ export type EnvMap<
CE extends keyof ChildEntityMap = never,
> = {
format: Format
formatNumber: FormatNumber
translate: Translate
translateMap: TranslateMap
localeJoin: LocaleJoin
@@ -62,6 +68,7 @@ export type EnvMap<
*/
export type EnvMapAbbr = {
f: "format"
fn: "formatNumber"
t: "translate"
tm: "translateMap"
lj: "localeJoin"
@@ -108,7 +115,7 @@ export type StdReader<
*/
export const formatR = (
text: string,
args?: Record<string, unknown> | undefined,
args?: Record<string, unknown>,
): Reader<{ format: Format }, string> => Reader.asks(env => env.format(text, args))
/**
+5 -22
View File
@@ -1,21 +1,13 @@
import { Reader } from "@elyukai/utils/reader"
import type { Translate, Translations } from "../../../helpers/translate.js"
import type { StdReader } from "../reader.js"
import { responsive, ResponsiveTextSize } from "../responsiveText.js"
import { responsive, type ResponsiveTextSize } from "../responsiveText.js"
type LengthUnit = "Steps" | "Miles"
const lengthUnitTranslationKeys = {
Steps: [
".input {$value :number} {{{$value} yards}}",
"{$value} yards",
"{$value} yd",
],
Miles: [
".input {$value :number} {{{$value} miles}}",
"{$value} miles",
"{$value} mi.",
],
Steps: [".input {$value :number} {{{$value} yards}}", "{$value} yards", "{$value} yd"],
Miles: [".input {$value :number} {{{$value} miles}}", "{$value} miles", "{$value} mi."],
} as const satisfies {
[key in LengthUnit]: [
fullNumber: keyof Translations,
@@ -53,9 +45,7 @@ export const formatLengthR = (
unit: LengthUnit | { kind: LengthUnit },
value: number | string,
): StdReader<string, "t" | "rts"> =>
Reader.asks(env =>
formatLength(env.translate, env.responsiveTextSize, unit, value),
)
Reader.asks(env => formatLength(env.translate, env.responsiveTextSize, unit, value))
/**
* Returns the text for a length unit.
@@ -64,11 +54,4 @@ export const formatCombinedLengthR = (object: {
unit: LengthUnit | { kind: LengthUnit }
value: number | string
}): StdReader<string, "t" | "rts"> =>
Reader.asks(env =>
formatLength(
env.translate,
env.responsiveTextSize,
object.unit,
object.value,
),
)
Reader.asks(env => formatLength(env.translate, env.responsiveTextSize, object.unit, object.value))
+3 -7
View File
@@ -1,10 +1,7 @@
import { Reader } from "@elyukai/utils/reader"
import type {
Translate,
TranslationKeyMatchingParams,
} from "../../../helpers/translate.js"
import type { Translate, TranslationKeyMatchingParams } from "../../../helpers/translate.js"
import type { StdEnv } from "../reader.js"
import { ResponsiveTextSize, responsive } from "../responsiveText.js"
import { type ResponsiveTextSize, responsive } from "../responsiveText.js"
/**
* Possible units to use for time spans.
@@ -83,5 +80,4 @@ export const formatTimeSpanR = (
export const formatCombinedTimeSpanR = (object: {
unit: { kind: TimeSpanUnit }
value: number | string
}): Reader<StdEnv<"t" | "rts">, string> =>
formatTimeSpanR(object.unit, object.value)
}): Reader<StdEnv<"t" | "rts">, string> => formatTimeSpanR(object.unit, object.value)
+65 -70
View File
@@ -10,76 +10,71 @@ import { MISSING_VALUE } from "./partial/unknown.js"
/**
* Get a JSON representation of the rules text for a .
*/
export const getPersonalityTraitEntityDescription =
createEntityDescriptionCreator<
"PersonalityTrait",
{
getInstanceById: GetInstanceById<
"Publication" | "Race" | "Culture" | "PersonalityTrait"
>
}
>(({ getInstanceById }, locale, { content: entry }) => {
const { translate, translateMap, join: localeJoin } = locale
const translation = translateMap(entry.translations)
export const getPersonalityTraitEntityDescription = createEntityDescriptionCreator<
"PersonalityTrait",
{
getInstanceById: GetInstanceById<"Publication" | "Race" | "Culture" | "PersonalityTrait">
}
>(({ getInstanceById }, locale, { content: entry }) => {
const { translate, translateMap, join: localeJoin } = locale
const translation = translateMap(entry.translations)
if (translation === undefined) {
return undefined
}
if (translation === undefined) {
return undefined
}
return {
title: `${translation.name} (${translate("Level {$level}", {
level: romanize(entry.level),
})})`,
className: "personality-trait",
body: [
{
type: "definitionList",
items: [
...(translation.effects.map(effect => ({
label: effect.label,
value: effect.text,
})) ?? []),
entry.combination_options === undefined
? undefined
: {
label: translate("Can be combined with"),
value: Map.groupBy(
entry.combination_options.map(optionId =>
getInstanceById("PersonalityTrait", optionId),
),
option => option?.level ?? null,
)
.entries()
.toArray()
.toSorted(on(group => group[0], compareNullish(numAsc)))
.map(([level, options]) =>
level === null
? MISSING_VALUE
: `${translate("Level {$level}", { level: romanize(level) })} ${localeJoin(
options.map(
option =>
translateMap(option?.translations)?.name ??
MISSING_VALUE,
),
"disjunction",
)}`,
)
.join(", "),
},
entry.prerequisites === undefined
? undefined
: {
label: translate("Prerequisites"),
value: printPersonalityTraitPrerequisites(
getInstanceById,
locale,
entry.prerequisites,
return {
title: `${translation.name} (${translate("Level {$level}", {
level: romanize(entry.level),
})})`,
className: "personality-trait",
body: [
{
type: "definitionList",
items: [
...translation.effects.map(effect => ({
label: effect.label,
value: effect.text,
})),
entry.combination_options === undefined
? undefined
: {
label: translate("Can be combined with"),
value: Map.groupBy(
entry.combination_options.map(optionId =>
getInstanceById("PersonalityTrait", optionId),
),
},
],
},
],
errata: translation.errata,
references: entry.src,
}
})
option => option?.level ?? null,
)
.entries()
.toArray()
.toSorted(on(group => group[0], compareNullish(numAsc)))
.map(([level, options]) =>
level === null
? MISSING_VALUE
: `${translate("Level {$level}", { level: romanize(level) })} ${localeJoin(
options.map(
option => translateMap(option?.translations)?.name ?? MISSING_VALUE,
),
"disjunction",
)}`,
)
.join(", "),
},
entry.prerequisites === undefined
? undefined
: {
label: translate("Prerequisites"),
value: printPersonalityTraitPrerequisites(
getInstanceById,
locale,
entry.prerequisites,
),
},
],
},
],
errata: translation.errata,
references: entry.src,
}
})
+4 -4
View File
@@ -80,7 +80,7 @@ const renderLevel = (
case "BySubtype":
return level.BySubtype.map(
subtype =>
`${subtype.value} (${translateMap(subtype.translations)?.name ?? MISSING_VALUE})`,
`${subtype.value.toFixed()} (${translateMap(subtype.translations)?.name ?? MISSING_VALUE})`,
).join(", ")
default:
return assertExhaustive(level)
@@ -161,7 +161,7 @@ const renderIntoxicantValues = (
legality: intoxicant.legality.is_legal ? translate("legal") : translate("illegal"),
special: translation?.special,
addiction:
intoxicant?.addiction === undefined
intoxicant.addiction === undefined
? undefined
: renderAddiction(translate, translateMap, getInstanceById, intoxicant.addiction),
}
@@ -319,7 +319,7 @@ const renderDuration = (
return renderMathOperation(duration.ExpressionBased.value, value => {
switch (value.kind) {
case "Constant":
return value.Constant.toString()
return value.Constant.toFixed()
case "Dice":
return renderDice(translate, value.Dice)
case "CircleOfDamnation":
@@ -447,7 +447,7 @@ export const getPoisonEntityDescription = createEntityDescriptionCreator<
renderAlternativeNames(translate, translation.alternative_names),
{
label: translate("Level"),
value: level.toString(),
value: typeof level === "number" ? level.toFixed() : level,
},
{
label: translate("Type"),
+50 -42
View File
@@ -17,38 +17,38 @@ import {
import { compareNumber } from "@elyukai/utils/ordering"
import { Reader } from "@elyukai/utils/reader"
import { assertExhaustive } from "@elyukai/utils/typeSafety"
import {
import type {
ActivatableIdentifier,
ActivatableNameBuilderRules,
BlessedTradition_ID,
Blessing_ID,
Cantrip_ID,
CantripsOptions,
CombatTechniqueIdentifier,
type ActivatableIdentifier,
type ActivatableNameBuilderRules,
type BlessedTradition_ID,
type Blessing_ID,
type Cantrip_ID,
type CantripsOptions,
type CombatTechniquesOptions,
type ConstantProfessionSpecialAbility,
type CursesOptions,
type ExperienceLevel,
type LanguagesScriptsOptions,
type LiturgiesOptions,
type LiturgyIdentifier,
type MagicalActionIdentifier,
type Profession_ID,
type ProfessionMagicalSkillIdentifier,
type ProfessionPackage,
type ProfessionPackageOptions,
type ProfessionPrerequisiteGroup,
type ProfessionPrerequisites,
type ProfessionSpecialAbility,
type ProfessionVariant,
type ProfessionVariantPackageOptions,
type ProfessionVariantTranslation,
type RatedIdentifier,
type RestrictedBlessings,
type SkillsOptions,
type SkillSpecializationOptions,
type SpecialAbilityIdentifier,
type VariantOptionAction,
CombatTechniquesOptions,
ConstantProfessionSpecialAbility,
CursesOptions,
ExperienceLevel,
LanguagesScriptsOptions,
LiturgiesOptions,
LiturgyIdentifier,
MagicalActionIdentifier,
Profession_ID,
ProfessionMagicalSkillIdentifier,
ProfessionPackage,
ProfessionPackageOptions,
ProfessionPrerequisiteGroup,
ProfessionPrerequisites,
ProfessionSpecialAbility,
ProfessionVariant,
ProfessionVariantPackageOptions,
ProfessionVariantTranslation,
RatedIdentifier,
RestrictedBlessings,
SkillsOptions,
SkillSpecializationOptions,
SpecialAbilityIdentifier,
VariantOptionAction,
} from "optolith-database-schema/gen"
import { createEntityDescriptionCreator } from "../creator.js"
import type {
@@ -146,6 +146,7 @@ const renderNumericListAcrossPackages = <T, SelectorEnv, RenderEnv>(
(filledAcc, [values, number]) => {
const existing = filledAcc.findIndex(([value]) => equalityFn(value, values))
if (existing >= 0) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- existing is checked to be >= 0
filledAcc[existing]![1][pkgIndex] = number + defaultValue
return filledAcc
} else {
@@ -153,9 +154,9 @@ const renderNumericListAcrossPackages = <T, SelectorEnv, RenderEnv>(
...filledAcc,
[
values,
Array(pkgIndex)
.fill(defaultValue)
.concat(number + defaultValue),
Array.from({ length: pkgIndex }, () => defaultValue).concat(
number + defaultValue,
),
],
]
}
@@ -338,7 +339,8 @@ const getTotalingAPValues = (
}
return allSame(options, deepEqual)
? options[0]!.ap_value
? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- not all are nullish and because all are the same, none is nullish
options[0]!.ap_value
: options.map(option => option?.ap_value ?? 0).join("/")
}
@@ -348,9 +350,7 @@ const renderSkillsOption = (
): StdReader<string | undefined, "t"> =>
mapNullable(
getTotalingAPValues(professionPackages, pkg =>
pkg.content.options?.skills?.group === skillGroup.id
? pkg.content.options?.skills
: undefined,
pkg.content.options?.skills?.group === skillGroup.id ? pkg.content.options.skills : undefined,
),
apValue =>
translateR("{$apValue} AP to improve other {$skillsOfGroup}", {
@@ -442,6 +442,7 @@ const renderCantripsOption = (
}
if (allSame(cantripsOptions, deepEqual)) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- not all are nullish and because all are the same, none is nullish
return renderSingleCantripsOption(cantripsOptions[0]!)
} else {
return Reader.traverse(cantripsOptions, option =>
@@ -586,7 +587,13 @@ const renderVariantSkillsOption = (
}),
),
update: (base, override) =>
plainOrInsteadOfR(base, override, b => b.ap_value, equal, Reader.of).thenW(apValue =>
plainOrInsteadOfR(
base,
override,
b => b.ap_value,
equal,
b => Reader.of(b),
).thenW(apValue =>
plainOrInsteadOfR(base, override, b => b.group, equal, nameOfSkillsOfGroupR).thenW(
nameOfSkillsOfGroup =>
translateR("{$apValue} AP to improve other {$skillsOfGroup}", {
@@ -907,10 +914,11 @@ const renderBlessings = (professionPackages: NonEmptyArray<PreparedProfessionPac
if (traditions.every(isEmpty)) {
return Reader.of(undefined)
} else if (allSame(traditions, deepEqual)) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- not all are empty and because all are the same, none is empty
return renderSingleBlessings(traditions[0]!)
} else {
return Reader.traverse(traditions, option =>
option === undefined ? Reader.of("—") : renderSingleBlessings(option),
option.length === 0 ? Reader.of("—") : renderSingleBlessings(option),
).map(list => list.join(" / "))
}
}
@@ -947,7 +955,7 @@ const renderRatedVariantChanges = <ID, Env>(
const baseValue = baseList?.find(item => deepEqual(item.id, id))?.rating_modifier ?? 0
return renderInstance(id).thenW(name =>
translateR("{$replacement} instead of {$base}", {
replacement: `${name} ${baseValue + rating_modifier}`,
replacement: `${name} ${(baseValue + rating_modifier).toFixed()}`,
base: baseValue,
}),
)
@@ -1240,7 +1248,7 @@ export const getProfessionVersionEntityDescription = createEntityDescriptionCrea
Reader.of(
specialAbilityLists
.map(specialAbilities =>
specialAbilities === undefined
Object.values(specialAbilities).every(list => list === undefined)
? translate("none")
: renderSpecialAbilities(specialAbilities).run(env),
)
+33 -85
View File
@@ -35,7 +35,7 @@ const renderBaseValueItem = (
Reader.asks(
({ translate }): RawDefinitionListEntityDescriptionSectionItem => ({
label: translate(label),
value: value < 0 ? sign(value) : value.toString(),
value: value < 0 ? sign(value) : value.toFixed(),
}),
)
@@ -55,8 +55,7 @@ const renderAttributeAdjustmentsItem = (
getInstanceById,
}): RawDefinitionListEntityDescriptionSectionItem => {
const getAttributeAbbreviation = (id: string) =>
translateMap(getInstanceById("Attribute", id)?.translations)
?.abbreviation ?? MISSING_VALUE
translateMap(getInstanceById("Attribute", id)?.translations)?.abbreviation ?? MISSING_VALUE
return {
label: translate("Attribute Adjustments"),
value: [
@@ -77,13 +76,8 @@ const renderVariantValues = <T>(
variants: RaceVariant[],
selector: (variant: RaceVariant) => T,
renderValue: (value: T) => string,
translationSelector?: (
variantTranslation: RaceVariantTranslation,
) => string | undefined,
): StdReader<
RawDefinitionListEntityDescriptionSectionItem,
"t" | "tm" | "lc" | "lj"
> =>
translationSelector?: (variantTranslation: RaceVariantTranslation) => string | undefined,
): StdReader<RawDefinitionListEntityDescriptionSectionItem, "t" | "tm" | "lc" | "lj"> =>
Reader.asks(
({
translate,
@@ -108,7 +102,11 @@ const renderVariantValues = <T>(
on(value => [value.value, value.valueTranslation], deepEqual),
)
if (sameValues.length === 1 && sameValues[0]!.length === values.length) {
if (
isNotEmpty(sameValues) &&
sameValues.length === 1 &&
sameValues[0].length === values.length
) {
return {
label: translate(label),
value:
@@ -124,12 +122,10 @@ const renderVariantValues = <T>(
type: "definitionList",
style: "nested",
items: Map.groupBy(
values
.toSorted(on(item => item.name, localeCompare))
.map(value => ({
label: value.name,
value: value.valueTranslation ?? renderValue(value.value),
})),
values.toSorted(on(item => item.name, localeCompare)).map(value => ({
label: value.name,
value: value.valueTranslation ?? renderValue(value.value),
})),
item => item.value,
)
.entries()
@@ -160,13 +156,9 @@ const renderAutomaticAdvantagesOrDisadvantages = <
? translate("none")
: items
.map(item => {
const instanceTranslation = translateMap(
getInstanceById(entity, item.id)?.translations,
)
const instanceTranslation = translateMap(getInstanceById(entity, item.id)?.translations)
return (
instanceTranslation?.name_in_library ??
instanceTranslation?.name ??
MISSING_VALUE
instanceTranslation?.name_in_library ?? instanceTranslation?.name ?? MISSING_VALUE
)
})
.toSorted(localeCompare)
@@ -180,11 +172,7 @@ const renderCommonCultures = (
items === undefined || !isNotEmpty(items)
? translate("none")
: items
.map(
itemId =>
translateMap(getInstanceById("Culture", itemId)?.translations)
?.name,
)
.map(itemId => translateMap(getInstanceById("Culture", itemId)?.translations)?.name)
.filter(isNotNullish)
.toSorted(localeCompare)
.join(", "),
@@ -237,31 +225,17 @@ export const getRaceEntityDescription = createEntityDescriptionCreator<
items: [
{
label: translate("AP Value"),
value: translate(
".input {$value :number} {{{$value} Adventure Points}}",
{ value: entry.ap_value },
),
value: translate(".input {$value :number} {{{$value} Adventure Points}}", {
value: entry.ap_value,
}),
},
renderBaseValueItem(
"Life Point Base Value",
entry.base_values.life_points,
).run(env),
renderBaseValueItem(
"Spirit Base Value",
entry.base_values.spirit,
).run(env),
renderBaseValueItem(
"Toughness Base Value",
entry.base_values.toughness,
).run(env),
renderBaseValueItem(
"Movement Base Value",
entry.base_values.movement,
).run(env),
renderAttributeAdjustmentsItem(
totalAttributesCount,
entry.attribute_adjustments,
).run(env),
renderBaseValueItem("Life Point Base Value", entry.base_values.life_points).run(env),
renderBaseValueItem("Spirit Base Value", entry.base_values.spirit).run(env),
renderBaseValueItem("Toughness Base Value", entry.base_values.toughness).run(env),
renderBaseValueItem("Movement Base Value", entry.base_values.movement).run(env),
renderAttributeAdjustmentsItem(totalAttributesCount, entry.attribute_adjustments).run(
env,
),
renderVariantValues(
"Common Cultures",
raceVariants,
@@ -273,11 +247,7 @@ export const getRaceEntityDescription = createEntityDescriptionCreator<
: renderValueWithPossibleTranslation(
"Automatic Advantages",
entry.automatic_advantages,
v =>
renderAutomaticAdvantagesOrDisadvantages(
"Advantage",
v,
).run(env),
v => renderAutomaticAdvantagesOrDisadvantages("Advantage", v).run(env),
translation.automatic_advantages,
).run(env),
entry.automatic_disadvantages === undefined
@@ -285,20 +255,14 @@ export const getRaceEntityDescription = createEntityDescriptionCreator<
: renderValueWithPossibleTranslation(
"Automatic Disadvantages",
entry.automatic_disadvantages,
v =>
renderAutomaticAdvantagesOrDisadvantages(
"Disadvantage",
v,
).run(env),
v => renderAutomaticAdvantagesOrDisadvantages("Disadvantage", v).run(env),
translation.automatic_disadvantages,
).run(env),
entry.strongly_recommended_advantages === undefined &&
entry.strongly_recommended_disadvantages === undefined
? undefined
: {
label: translate(
"Strongly recommended Advantages and Disadvantages",
),
label: translate("Strongly recommended Advantages and Disadvantages"),
value: `${translate("The following advantages and disadvantages distinguish Aventurian {$race}. You should choose these advantages and disadvantages. If you dont want to take them, talk to your GM.", { race: translation.name })} ${renderCommonnessRatedAdvantagesAndDisadvantages(
entry.strongly_recommended_advantages,
entry.strongly_recommended_disadvantages,
@@ -308,44 +272,28 @@ export const getRaceEntityDescription = createEntityDescriptionCreator<
"Common Advantages",
raceVariants,
v => v.common_advantages,
advs =>
renderCommonnessRatedAdvantagesOrDisadvantages(
"Advantage",
advs,
).run(env),
advs => renderCommonnessRatedAdvantagesOrDisadvantages("Advantage", advs).run(env),
vt => vt.common_advantages,
).run(env),
renderVariantValues(
"Common Disadvantages",
raceVariants,
v => v.common_disadvantages,
advs =>
renderCommonnessRatedAdvantagesOrDisadvantages(
"Disadvantage",
advs,
).run(env),
advs => renderCommonnessRatedAdvantagesOrDisadvantages("Disadvantage", advs).run(env),
vt => vt.common_disadvantages,
).run(env),
renderVariantValues(
"Uncommon Advantages",
raceVariants,
v => v.uncommon_advantages,
advs =>
renderCommonnessRatedAdvantagesOrDisadvantages(
"Advantage",
advs,
).run(env),
advs => renderCommonnessRatedAdvantagesOrDisadvantages("Advantage", advs).run(env),
vt => vt.uncommon_advantages,
).run(env),
renderVariantValues(
"Uncommon Disadvantages",
raceVariants,
v => v.uncommon_disadvantages,
advs =>
renderCommonnessRatedAdvantagesOrDisadvantages(
"Disadvantage",
advs,
).run(env),
advs => renderCommonnessRatedAdvantagesOrDisadvantages("Disadvantage", advs).run(env),
vt => vt.uncommon_disadvantages,
).run(env),
],
+29 -32
View File
@@ -3,37 +3,34 @@ import { createEntityDescriptionCreator } from "../creator.js"
/**
* Get a JSON representation of the rules text for a sex practice.
*/
export const getSexPracticeEntityDescription =
createEntityDescriptionCreator<"SexPractice">(
(_, { translate, translateMap }, { content: entry }) => {
const translation = translateMap(entry.translations)
export const getSexPracticeEntityDescription = createEntityDescriptionCreator<"SexPractice">(
(_, { translate, translateMap }, { content: entry }) => {
const translation = translateMap(entry.translations)
if (translation === undefined) {
return undefined
}
if (translation === undefined) {
return undefined
}
return {
title: translation.name,
className: "sex-practice",
body: [
{
type: "definitionList",
items: [
{ label: translate("Rules"), value: translation.rules },
{ label: translate("Duration"), value: translation.duration },
translation.prerequisites === undefined
? undefined
: {
label: translate("Prerequisites"),
value: translation.prerequisites,
},
translation.failed === undefined
? undefined
: { label: translate("Failed"), value: translation.failed },
],
},
],
references: entry.src,
}
},
)
return {
title: translation.name,
className: "sex-practice",
body: [
{
type: "definitionList",
items: [
{ label: translate("Rules"), value: translation.rules },
{ label: translate("Duration"), value: translation.duration },
translation.prerequisites === undefined
? undefined
: {
label: translate("Prerequisites"),
value: translation.prerequisites,
},
{ label: translate("Failed"), value: translation.failed },
],
},
],
references: entry.src,
}
},
)
+9 -26
View File
@@ -1,9 +1,6 @@
import { isNotNullish } from "@optolith/helpers/nullable"
import { assertExhaustive } from "@optolith/helpers/typeSafety"
import type {
ResolvedNewSkillApplication,
ResolvedSkillUse,
} from "optolith-database-schema/cache"
import type { ResolvedNewSkillApplication, ResolvedSkillUse } from "optolith-database-schema/cache"
import type { ActivatableIdentifier } from "optolith-database-schema/gen"
import { fromUniformCase } from "tsondb/schema/gen"
import { createEntityDescriptionCreator } from "../creator.js"
@@ -13,20 +10,15 @@ import type {
GetInstanceById,
} from "../helpers/getTypes.js"
import type { LocaleEnvironment } from "../helpers/locale.js"
import type {
GetAllResolvedNewSkillApplications,
GetAllResolvedSkillUses,
} from "../index.js"
import { BaseActivatableTranslation } from "./activatable.js"
import type { GetAllResolvedNewSkillApplications, GetAllResolvedSkillUses } from "../index.js"
import type { BaseActivatableTranslation } from "./activatable.js"
import { renderImprovementCost } from "./partial/rated/improvementCost.js"
import { renderSkillCheck } from "./partial/rated/skillCheck.js"
const getUsesOrNewApplications = <
T extends ResolvedNewSkillApplication | ResolvedSkillUse,
>(
const getUsesOrNewApplications = (
getInstanceById: GetInstanceById<"Aspect" | ActivatableIdentifier["kind"]>,
locale: LocaleEnvironment,
items: T[],
items: (ResolvedNewSkillApplication | ResolvedSkillUse)[],
) =>
items
.map(x => {
@@ -72,11 +64,7 @@ export const getSkillEntityDescription = createEntityDescriptionCreator<
"Publication" | "Attribute" | ActivatableIdentifier["kind"] | "Aspect"
>
getAllInstances: GetAllInstances<
| "BlessedTradition"
| "Disease"
| "Region"
| "SkillUse"
| "NewSkillApplication"
"BlessedTradition" | "Disease" | "Region" | "SkillUse" | "NewSkillApplication"
>
getChildInstancesForInstanceId: GetAllChildInstancesForParent<"SkillApplication">
getAllResolvedNewSkillApplications: GetAllResolvedNewSkillApplications
@@ -107,11 +95,7 @@ export const getSkillEntityDescription = createEntityDescriptionCreator<
getAllResolvedNewSkillApplications(id),
)
const uses = getUsesOrNewApplications(
getInstanceById,
locale,
getAllResolvedSkillUses(id),
)
const uses = getUsesOrNewApplications(getInstanceById, locale, getAllResolvedSkillUses(id))
const applications = [
...getChildInstancesForInstanceId("SkillApplication", id)
@@ -178,10 +162,9 @@ export const getSkillEntityDescription = createEntityDescriptionCreator<
? translate("Yes")
: entry.encumbrance.kind === "No"
? translate("No")
: (translation.encumbrance_description ??
translate("Maybe")),
: (translation.encumbrance_description ?? translate("Maybe")),
},
translation?.tools === undefined
translation.tools === undefined
? undefined
: {
label: translate("Tools"),
+3 -3
View File
@@ -4,7 +4,7 @@ import { Lazy } from "@elyukai/utils/lazy"
import { compareNullish } from "@elyukai/utils/ordering"
import { Reader } from "@elyukai/utils/reader"
import { romanize } from "@elyukai/utils/roman"
import { Compare, numAsc } from "@optolith/helpers/compare"
import { numAsc, type Compare } from "@optolith/helpers/compare"
import { isNotNullish, mapNullable } from "@optolith/helpers/nullable"
import { assertExhaustive } from "@optolith/helpers/typeSafety"
import {
@@ -32,7 +32,7 @@ import { Case } from "tsondb/schema/gen"
import { createEntityDescriptionCreator } from "../creator.js"
import type { GetAllChildInstancesForParent, GetInstanceById } from "../helpers/getTypes.js"
import type { LocaleCompare } from "../helpers/locale.js"
import { Translate, TranslateMap, type TranslationKeysWithoutParams } from "../helpers/translate.js"
import type { Translate, TranslateMap, TranslationKeysWithoutParams } from "../helpers/translate.js"
import { type IdMap, type RawDefinitionListEntityDescriptionSectionItem } from "../index.js"
import { renderAnimalTypesSection } from "./partial/animalTypes.js"
import { renderEnhancements } from "./partial/enhancements.js"
@@ -1449,7 +1449,7 @@ const renderMagicalRuneCost = (options: Lazy<MagicalRuneOption[]>, cost: Magical
options,
option => option.cost?.value,
compareNullish(numAsc),
num => num?.toString() ?? MISSING_VALUE,
num => num?.toFixed() ?? MISSING_VALUE,
).thenW(formatEnergyR)
default:
return assertExhaustive(cost)
+5 -11
View File
@@ -1,11 +1,9 @@
/* eslint-disable jsdoc/require-jsdoc */
import * as Database from "optolith-database-schema/gen"
import { IdArgsVariant } from "tsondb/schema/gen"
import type * as Database from "optolith-database-schema/gen"
import type { IdArgsVariant } from "tsondb/schema/gen"
export type GetInstanceById<
in T extends Extract<keyof Database.EntityMap, string>,
> = <U extends T>(
export type GetInstanceById<in T extends Extract<keyof Database.EntityMap, string>> = <U extends T>(
...args: IdArgsVariant<Database.EntityMap, U>
) => Database.EntityMap[U] | undefined
@@ -23,13 +21,9 @@ export type GetAllInstances<T extends keyof Database.EntityMap> = <U extends T>(
entity: U,
) => { id: string; content: Database.EntityMap[U] }[]
export type CountInstances<T extends keyof Database.EntityMap> = <U extends T>(
entity: U,
) => number
export type CountInstances<T extends keyof Database.EntityMap> = <U extends T>(entity: U) => number
export type GetAllChildInstancesForParent<
T extends keyof Database.ChildEntityMap,
> = <U extends T>(
export type GetAllChildInstancesForParent<T extends keyof Database.ChildEntityMap> = <U extends T>(
entity: U,
parentId: Database.ChildEntityMap[U][2],
) => { id: string; content: Database.ChildEntityMap[U][0] }[]
+8 -2
View File
@@ -1,6 +1,6 @@
import { Compare } from "@optolith/helpers/compare"
import type { Compare } from "@optolith/helpers/compare"
import type { LocaleMeasurementAdjustments } from "optolith-database-schema/gen"
import { Translate, TranslateMap, type Format } from "./translate.js"
import type { Format, Translate, TranslateMap } from "./translate.js"
/**
* The type of list to join in a locale-aware way.
@@ -14,6 +14,7 @@ export type LocaleEnvironment = {
id: string
format: Format
formatDate: (date: Date) => string
formatNumber: FormatNumber
translate: Translate
translateMap: TranslateMap
compare: LocaleCompare
@@ -30,3 +31,8 @@ export type LocaleCompare = Compare<string>
* A function that joins a list of strings according to the locale's rules for the given type.
*/
export type LocaleJoin = (list: string[], type: LocaleJoinType) => string
/**
* A function that formats a number according to the locales rules.
*/
export type FormatNumber = (value: number) => string
+7 -8
View File
@@ -1,5 +1,5 @@
import { assertExhaustive } from "@elyukai/utils/typeSafety"
import { Locale } from "optolith-database-schema/gen"
import type { Locale } from "optolith-database-schema/gen"
import { ResponsiveTextSize } from "../entities/partial/responsiveText.js"
/**
@@ -23,18 +23,17 @@ export type Translate = <K extends keyof Translations>(
/**
* Extracts the parameters for a given translation key, or never if the key does not have parameters.
*/
export type TranslationParams<K extends keyof Translations> =
Translations[K] extends string & { __params: infer Params }
? Params
: undefined
export type TranslationParams<K extends keyof Translations> = Translations[K] extends string & {
__params: infer Params
}
? Params
: undefined
/**
* Extracts the parameters for a given translation key as an array, or an empty array if the key does not have parameters.
*/
export type TranslationParamsInArray<K extends keyof Translations> =
Translations[K] extends string & { __params: infer Params }
? [params: Params]
: []
Translations[K] extends string & { __params: infer Params } ? [params: Params] : []
/**
* A dictionary of locale identifiers to other values.
+2 -1
View File
@@ -71,7 +71,7 @@ import type {
} from "./helpers/getTypes.js"
import type { LocaleEnvironment } from "./helpers/locale.js"
export { LocaleEnvironment }
export type { LocaleEnvironment }
/**
* A JSON representation of the rules text for a library entry.
@@ -434,6 +434,7 @@ export type GetAllResolvedSkillUses = (id: Skill_ID) => ResolvedSkillUse[]
/**
* Get a JSON representation of the rules text for an entry in the database.
*/
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- false positive
export const getEntityDescription = <E extends AvailableCreatorEntity>(
database: TSONDB<TSONDBTypes>,
localeEnv: LocaleEnvironment,
+7 -23
View File
@@ -2,12 +2,8 @@ import { isNotNullish } from "@optolith/helpers/nullable"
import { assertExhaustive } from "@optolith/helpers/typeSafety"
import type { PublicationRefs } from "optolith-database-schema/gen"
import type { GetInstanceById } from "../helpers/getTypes.js"
import { LocaleEnvironment } from "../helpers/locale.js"
import {
fromRawPageRange,
normalizePageRanges,
printPageRanges,
} from "./pageRange.js"
import type { LocaleEnvironment } from "../helpers/locale.js"
import { fromRawPageRange, normalizePageRanges, printPageRanges } from "./pageRange.js"
/**
* Returns the translation of the references.
@@ -20,9 +16,7 @@ export const getReferencesTranslation = (
references
.map(ref => {
const publication = getInstanceById("Publication", ref.id)
const publicationTranslations = locale.translateMap(
publication?.translations,
)
const publicationTranslations = locale.translateMap(publication?.translations)
const occurrences = locale.translateMap(ref.occurrences)
if (
@@ -33,17 +27,12 @@ export const getReferencesTranslation = (
return undefined
}
const initialPageRanges = normalizePageRanges(
occurrences.initial.pages.map(fromRawPageRange),
)
const initialPageRanges = normalizePageRanges(occurrences.initial.pages.map(fromRawPageRange))
const initial =
occurrences.initial.printing === undefined
? printPageRanges(locale.translate, initialPageRanges)
: `${printPageRanges(
locale.translate,
initialPageRanges,
)} (${locale.translate(
: `${printPageRanges(locale.translate, initialPageRanges)} (${locale.translate(
".input {$printing :number} {{since the {$printing}. printing}}",
{ printing: occurrences.initial.printing },
)})`
@@ -52,13 +41,8 @@ export const getReferencesTranslation = (
occurrences.revisions?.map(rev => {
switch (rev.kind) {
case "Since": {
const pageRanges = normalizePageRanges(
rev.Since.pages.map(fromRawPageRange),
)
return `${printPageRanges(
locale.translate,
pageRanges,
)} (${locale.translate(
const pageRanges = normalizePageRanges(rev.Since.pages.map(fromRawPageRange))
return `${printPageRanges(locale.translate, pageRanges)} (${locale.translate(
".input {$printing :number} {{since the {$printing}. printing}}",
{ printing: rev.Since.printing },
)})`
+6 -6
View File
@@ -1,7 +1,7 @@
import { Compare } from "@optolith/helpers/compare"
import type { Compare } from "@optolith/helpers/compare"
import { assertExhaustive } from "@optolith/helpers/typeSafety"
import { Page } from "optolith-database-schema/gen"
import { Translate } from "../helpers/translate.js"
import type { Page } from "optolith-database-schema/gen"
import type { Translate } from "../helpers/translate.js"
/**
* A comparison function for two pages.
@@ -16,8 +16,8 @@ export const comparePage: Compare<Page> = (a, b) => {
return b.kind === "Numbered"
? a.Numbered - b.Numbered
: b.kind === "InsideCoverFront"
? 1
: -1
? 1
: -1
default:
return assertExhaustive(a)
}
@@ -62,7 +62,7 @@ export const printPage = (translate: Translate, page: Page) => {
case "InsideCoverBack":
return translate("Back Cover Inside")
case "Numbered":
return page.Numbered.toString()
return page.Numbered.toFixed()
default:
return assertExhaustive(page)
}
+4 -10
View File
@@ -1,6 +1,6 @@
import { range } from "@optolith/helpers/array"
import { Page, PageRange as RawPageRange } from "optolith-database-schema/gen"
import { Translate } from "../helpers/translate.js"
import type { Page, PageRange as RawPageRange } from "optolith-database-schema/gen"
import type { Translate } from "../helpers/translate.js"
import { comparePage, equalsPage, printPage, succ } from "./page.js"
/**
@@ -62,16 +62,10 @@ export const normalizePageRanges = (ranges: PageRange[]): PageRange[] =>
export const printPageRange = (translate: Translate, pageRange: PageRange) =>
pageRange.lastPage === undefined
? printPage(translate, pageRange.firstPage)
: `${printPage(translate, pageRange.firstPage)}${printPage(
translate,
pageRange.lastPage,
)}`
: `${printPage(translate, pageRange.firstPage)}${printPage(translate, pageRange.lastPage)}`
/**
* Returns a string representation of a list of page ranges.
*/
export const printPageRanges = (
translate: Translate,
pageRanges: PageRange[],
) =>
export const printPageRanges = (translate: Translate, pageRanges: PageRange[]) =>
pageRanges.map(pageRange => printPageRange(translate, pageRange)).join(", ")
+1 -1
View File
@@ -2,7 +2,7 @@ import assert from "node:assert/strict"
import { describe, it } from "node:test"
import {
joinPrerequisiteParts,
PrerequisitePart,
type PrerequisitePart,
} from "../../../../src/entities/partial/prerequisites/part.js"
import { Case } from "../../../../src/helpers/enums.js"
import { defaultLocaleEnvironment } from "../../../helpers/locale.js"
+9 -2
View File
@@ -1,6 +1,12 @@
import { assertExhaustive } from "@elyukai/utils/typeSafety"
import { LocaleEnvironment } from "../../src/helpers/locale.js"
import { formatDateMock, formatMock, translateMapMock, translateMock } from "./translate.js"
import type { LocaleEnvironment } from "../../src/helpers/locale.js"
import {
formatDateMock,
formatMock,
formatNumberMock,
translateMapMock,
translateMock,
} from "./translate.js"
const localeId = "en-US"
@@ -23,6 +29,7 @@ export const defaultLocaleEnvironment: LocaleEnvironment = {
id: "en-US",
format: formatMock,
formatDate: formatDateMock,
formatNumber: formatNumberMock,
translate: translateMock,
translateMap: translateMapMock,
compare: (x, y) => collator.compare(x, y),
+7 -1
View File
@@ -1,5 +1,6 @@
import { MessageFormat } from "messageformat"
import { Translate, TranslateMap, type Format } from "../../src/helpers/translate.js"
import type { FormatNumber } from "../../src/helpers/locale.js"
import type { Format, Translate, TranslateMap } from "../../src/helpers/translate.js"
/**
* A mocked format function.
@@ -11,6 +12,11 @@ export const formatMock: Format = (text, args) => new MessageFormat("en", text).
*/
export const formatDateMock = (date: Date): string => date.toISOString().split("T")[0] ?? ""
/**
* A mocked number format function.
*/
export const formatNumberMock: FormatNumber = value => value.toFixed()
/**
* A mocked translate function.
*/
+2 -2
View File
@@ -1,10 +1,10 @@
import assert from "assert/strict"
import { describe, it } from "node:test"
import { Page } from "optolith-database-schema/gen"
import type { Page } from "optolith-database-schema/gen"
import {
fromRawPageRange,
normalizePageRanges,
PageRange,
type PageRange,
printPageRange,
printPageRanges,
} from "../../src/references/pageRange.js"