refactor: use Reader for activatable skill helpers
Use Reader to inject environment functions and values. This keeps function parameter lists tidy and also functions more readable since far less arguments have to be passed around.
This commit is contained in:
Generated
+4
-4
@@ -9,7 +9,7 @@
|
||||
"version": "0.2.1",
|
||||
"license": "MPL-2.0",
|
||||
"dependencies": {
|
||||
"@elyukai/utils": "^0.2.2",
|
||||
"@elyukai/utils": "^0.2.4",
|
||||
"@optolith/helpers": "^0.2.2",
|
||||
"messageformat": "^4.0.0",
|
||||
"tsondb": "^0.19.8"
|
||||
@@ -69,9 +69,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@elyukai/utils": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@elyukai/utils/-/utils-0.2.2.tgz",
|
||||
"integrity": "sha512-nHzQGG7Oi7qTMIwCaWA9LKQJWRYCRu00V/gaMX22GeQkSgi6eNXaGmT0WA4TOSCEinw7wjdfiVkel55SmdUDqA==",
|
||||
"version": "0.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@elyukai/utils/-/utils-0.2.4.tgz",
|
||||
"integrity": "sha512-E1NnNUCo23Pn8J3arDjWqkJ7yikr1ddPG/zPcuGAE/O0kGAMkJbFnBDbNb+eelqq+/J5zZhNmOZBgACD3ObCFg==",
|
||||
"license": "MPL-2.0"
|
||||
},
|
||||
"node_modules/@es-joy/jsdoccomment": {
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@
|
||||
},
|
||||
"homepage": "https://github.com/Optolith/entity-descriptions#readme",
|
||||
"dependencies": {
|
||||
"@elyukai/utils": "^0.2.2",
|
||||
"@elyukai/utils": "^0.2.4",
|
||||
"@optolith/helpers": "^0.2.2",
|
||||
"messageformat": "^4.0.0",
|
||||
"tsondb": "^0.19.8"
|
||||
|
||||
+26
-24
@@ -33,6 +33,7 @@ import type {
|
||||
PropertyDeclaration,
|
||||
PublicationRefs,
|
||||
RangedCombatTechnique,
|
||||
RatedIdentifier,
|
||||
SelectOptions,
|
||||
SpecialRule,
|
||||
Volume,
|
||||
@@ -544,17 +545,14 @@ const renderVolumeValue = (
|
||||
})
|
||||
case "Map":
|
||||
return renderResponsiveMap(
|
||||
translate,
|
||||
translateMap,
|
||||
responsiveTextSize,
|
||||
volume.Map,
|
||||
option => option.points,
|
||||
values => translate("{$points} points", { points: values }),
|
||||
optionTranslation => optionTranslation.label,
|
||||
translation => translation.list_prepend,
|
||||
translation => translation.list_append,
|
||||
translation => translation.replacement,
|
||||
)
|
||||
).run({
|
||||
translate,
|
||||
translateMap,
|
||||
responsiveTextSize,
|
||||
})
|
||||
case "DerivedFromSelection": {
|
||||
const groups = Map.groupBy(
|
||||
getAllResolvedSelectOptions(),
|
||||
@@ -736,16 +734,9 @@ const renderArcaneEnergyCost = (
|
||||
})
|
||||
case "Map":
|
||||
return renderResponsiveMap(
|
||||
translate,
|
||||
translateMap,
|
||||
responsiveTextSize,
|
||||
cost.Map,
|
||||
option => option.value,
|
||||
values => translate("{$value} AE", { value: values }),
|
||||
optionTranslation => optionTranslation.label,
|
||||
translation => translation.list_prepend,
|
||||
translation => translation.list_append,
|
||||
translation => translation.replacement,
|
||||
cost.Map.options.every(option => option.permanent_value !== undefined)
|
||||
? {
|
||||
surround: values =>
|
||||
@@ -755,7 +746,11 @@ const renderArcaneEnergyCost = (
|
||||
getAdditionalValue: option => option.permanent_value!,
|
||||
}
|
||||
: undefined,
|
||||
)
|
||||
).run({
|
||||
translate,
|
||||
translateMap,
|
||||
responsiveTextSize,
|
||||
})
|
||||
case "Variable":
|
||||
return translate("Variable")
|
||||
case "ByLevel":
|
||||
@@ -809,17 +804,14 @@ const renderBindingCost = (
|
||||
})
|
||||
case "Map":
|
||||
return renderResponsiveMap(
|
||||
translate,
|
||||
translateMap,
|
||||
responsiveTextSize,
|
||||
cost.Map,
|
||||
option => option.permanent_value,
|
||||
values => translate("{$value} permanent AE", { value: values }),
|
||||
optionTranslation => optionTranslation.label,
|
||||
translation => translation.list_prepend,
|
||||
translation => translation.list_append,
|
||||
translation => translation.replacement,
|
||||
)
|
||||
).run({
|
||||
translate,
|
||||
translateMap,
|
||||
responsiveTextSize,
|
||||
})
|
||||
case "DerivedFromSelection": {
|
||||
const groups = Map.groupBy(
|
||||
getAllResolvedSelectOptions(),
|
||||
@@ -937,11 +929,21 @@ export const getActivatableEntityDescription = createEntityDescriptionCreator<
|
||||
ActivatableIdentifier["kind"],
|
||||
{
|
||||
getInstanceById: GetInstanceById<
|
||||
| "Publication"
|
||||
| "Subject"
|
||||
| ActivatableIdentifier["kind"]
|
||||
| RatedIdentifier["kind"]
|
||||
| "TradeSecret"
|
||||
| "Aspect"
|
||||
| "Property"
|
||||
| "Race"
|
||||
| "Culture"
|
||||
| "State"
|
||||
| "Enhancement"
|
||||
| "PactCategory"
|
||||
| "PactDomain"
|
||||
| "SocialStatus"
|
||||
| "Weapon"
|
||||
>
|
||||
getAllInstances: GetAllInstances<"Script">
|
||||
getResolvedSelectOptionById: GetResolvedSelectOptionById
|
||||
|
||||
@@ -8,7 +8,7 @@ import { MISSING_VALUE } from "./partial/unknown.js"
|
||||
export const getAlternativeRuleEntityDescription =
|
||||
createEntityDescriptionCreator<
|
||||
"AlternativeRule",
|
||||
{ getInstanceById: GetInstanceById<"PlayerType"> }
|
||||
{ getInstanceById: GetInstanceById<"Publication" | "PlayerType"> }
|
||||
>(({ getInstanceById }, { translateMap }, { content: entry }) => {
|
||||
const translation = translateMap(entry.translations)
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { mapNullable } from "@optolith/helpers/nullable"
|
||||
import { createEntityDescriptionCreator } from "../creator.js"
|
||||
import type { GetInstanceById } from "../helpers/getTypes.js"
|
||||
import { createImprovementCost } from "./partial/rated/improvementCost.js"
|
||||
import { renderImprovementCost } from "./partial/rated/improvementCost.js"
|
||||
|
||||
/**
|
||||
* Get a JSON representation of the rules text for a close combat technique.
|
||||
@@ -10,7 +10,7 @@ export const getCloseCombatTechniqueEntityDescription =
|
||||
createEntityDescriptionCreator<
|
||||
"CloseCombatTechnique",
|
||||
{
|
||||
getInstanceById: GetInstanceById<"Attribute">
|
||||
getInstanceById: GetInstanceById<"Publication" | "Attribute">
|
||||
}
|
||||
>(({ getInstanceById }, { translate, translateMap }, { content: entry }) => {
|
||||
const translation = translateMap(entry.translations)
|
||||
@@ -37,7 +37,7 @@ export const getCloseCombatTechniqueEntityDescription =
|
||||
)
|
||||
.join("/"),
|
||||
},
|
||||
createImprovementCost(translate, entry.improvement_cost),
|
||||
renderImprovementCost(entry.improvement_cost).run({ translate }),
|
||||
],
|
||||
errata: translation.errata,
|
||||
references: entry.src,
|
||||
@@ -51,7 +51,7 @@ export const getRangedCombatTechniqueEntityDescription =
|
||||
createEntityDescriptionCreator<
|
||||
"RangedCombatTechnique",
|
||||
{
|
||||
getInstanceById: GetInstanceById<"Attribute">
|
||||
getInstanceById: GetInstanceById<"Publication" | "Attribute">
|
||||
}
|
||||
>(({ getInstanceById }, { translate, translateMap }, { content: entry }) => {
|
||||
const translation = translateMap(entry.translations)
|
||||
@@ -78,7 +78,7 @@ export const getRangedCombatTechniqueEntityDescription =
|
||||
)
|
||||
.join("/"),
|
||||
},
|
||||
createImprovementCost(translate, entry.improvement_cost),
|
||||
renderImprovementCost(entry.improvement_cost).run({ translate }),
|
||||
],
|
||||
errata: translation.errata,
|
||||
references: entry.src,
|
||||
|
||||
@@ -464,6 +464,7 @@ export const getCurriculumEntityDescription = createEntityDescriptionCreator<
|
||||
"Curriculum",
|
||||
{
|
||||
getInstanceById: GetInstanceById<
|
||||
| "Publication"
|
||||
| "Skill"
|
||||
| "CloseCombatTechnique"
|
||||
| "RangedCombatTechnique"
|
||||
@@ -471,6 +472,8 @@ export const getCurriculumEntityDescription = createEntityDescriptionCreator<
|
||||
| "Spell"
|
||||
| "Ritual"
|
||||
| "MagicalTradition"
|
||||
| "Property"
|
||||
| "Element"
|
||||
>
|
||||
getAllInstances: GetAllInstances<"Profession">
|
||||
getChildInstancesForInstanceId: GetAllChildInstancesForParent<
|
||||
|
||||
@@ -121,7 +121,9 @@ export const getDerivedCharacteristicEntityDescription =
|
||||
createEntityDescriptionCreator<
|
||||
"DerivedCharacteristic",
|
||||
{
|
||||
getInstanceById: GetInstanceById<"Attribute" | "DerivedCharacteristic">
|
||||
getInstanceById: GetInstanceById<
|
||||
"Publication" | "Attribute" | "DerivedCharacteristic"
|
||||
>
|
||||
idMap: IdMap
|
||||
}
|
||||
>(
|
||||
|
||||
@@ -79,7 +79,9 @@ const renderCauses = (
|
||||
export const getDiseaseEntityDescription = createEntityDescriptionCreator<
|
||||
"AnimalDisease" | "Disease",
|
||||
{
|
||||
getInstanceById: GetInstanceById<"AnimalType" | "DerivedCharacteristic">
|
||||
getInstanceById: GetInstanceById<
|
||||
"Publication" | "AnimalType" | "DerivedCharacteristic"
|
||||
>
|
||||
idMap: IdMap
|
||||
}
|
||||
>(
|
||||
|
||||
+19
-1
@@ -1,5 +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 { createEntityDescriptionCreator } from "../creator.js"
|
||||
import type { GetInstanceById } from "../helpers/getTypes.js"
|
||||
import {
|
||||
@@ -16,7 +20,21 @@ import { parensIf } from "./partial/rated/activatable/parensIf.js"
|
||||
export const getElixirEntityDescription = createEntityDescriptionCreator<
|
||||
"Elixir",
|
||||
{
|
||||
getInstanceById: GetInstanceById<"DerivedCharacteristic">
|
||||
getInstanceById: GetInstanceById<
|
||||
| "Publication"
|
||||
| "DerivedCharacteristic"
|
||||
| ActivatableIdentifier["kind"]
|
||||
| RatedIdentifier["kind"]
|
||||
| "Race"
|
||||
| "Culture"
|
||||
| "State"
|
||||
| "Enhancement"
|
||||
| "PactCategory"
|
||||
| "PactDomain"
|
||||
| "SocialStatus"
|
||||
| "Aspect"
|
||||
| "Property"
|
||||
>
|
||||
getResolvedSelectOptionById: GetResolvedSelectOptionById
|
||||
}
|
||||
>(
|
||||
|
||||
@@ -884,6 +884,7 @@ export const getEquipmentEntityDescription = createEntityDescriptionCreator<
|
||||
Exclude<EquipmentIdentifier["kind"], "Elixir" | "Poison">,
|
||||
{
|
||||
getInstanceById: GetInstanceById<
|
||||
| "Publication"
|
||||
| "Attribute"
|
||||
| "Reach"
|
||||
| "SocialStatus"
|
||||
@@ -892,6 +893,10 @@ export const getEquipmentEntityDescription = createEntityDescriptionCreator<
|
||||
| "MagicalTradition"
|
||||
| "BlessedTradition"
|
||||
| "DerivedCharacteristic"
|
||||
| "Ammunition"
|
||||
| "Race"
|
||||
| "Culture"
|
||||
| "Profession"
|
||||
>
|
||||
idMap: IdMap
|
||||
}
|
||||
|
||||
@@ -316,7 +316,7 @@ export const getEquipmentPackageEntityDescription =
|
||||
"EquipmentPackage",
|
||||
{
|
||||
getInstanceById: GetInstanceById<
|
||||
EquipmentIdentifier["kind"] | "SocialStatus"
|
||||
"Publication" | EquipmentIdentifier["kind"] | "SocialStatus"
|
||||
>
|
||||
}
|
||||
>(({ getInstanceById }, locale, { content }) => {
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { GetInstanceById } from "../helpers/getTypes.js"
|
||||
*/
|
||||
export const getFocusRuleEntityDescription = createEntityDescriptionCreator<
|
||||
"FocusRule",
|
||||
{ getInstanceById: GetInstanceById<"Subject"> }
|
||||
{ getInstanceById: GetInstanceById<"Publication" | "Subject"> }
|
||||
>(({ getInstanceById }, { translate, translateMap }, { content: entry }) => {
|
||||
const translation = translateMap(entry.translations)
|
||||
const topicTranslation = translateMap(
|
||||
|
||||
@@ -4,22 +4,24 @@ import { assertExhaustive } from "@optolith/helpers/typeSafety"
|
||||
import { type LiturgyTradition } from "optolith-database-schema/gen"
|
||||
import { createEntityDescriptionCreator } from "../creator.js"
|
||||
import type { GetInstanceById } from "../helpers/getTypes.js"
|
||||
import { Translate, TranslateMap } from "../helpers/translate.js"
|
||||
import { EntityDescriptionSection, type IdMap } from "../index.js"
|
||||
import { getSlowCastingTimeTranslation } from "./partial/rated/activatable/castingTime.js"
|
||||
import { getDurationTranslationForBlessing } from "./partial/rated/activatable/duration.js"
|
||||
import { getTextForEffect } from "./partial/rated/activatable/effect.js"
|
||||
import { Entity } from "./partial/rated/activatable/entity.js"
|
||||
import {
|
||||
getFastOneTimePerformanceParametersTranslations,
|
||||
getFastSustainedPerformanceParametersTranslations,
|
||||
getSlowOneTimePerformanceParametersTranslations,
|
||||
getSlowSustainedPerformanceParametersTranslations,
|
||||
Translate,
|
||||
TranslateMap,
|
||||
type TranslationKeysWithoutParams,
|
||||
} from "../helpers/translate.js"
|
||||
import { EntityDescriptionSection, type IdMap } from "../index.js"
|
||||
import { renderOneTimeDuration } from "./partial/rated/activatable/duration.js"
|
||||
import { renderEffect } from "./partial/rated/activatable/effect.js"
|
||||
import {
|
||||
renderFastPerformanceParameters,
|
||||
renderSlowPerformanceParameters,
|
||||
} from "./partial/rated/activatable/index.js"
|
||||
import { getTextForBlessingRange } from "./partial/rated/activatable/range.js"
|
||||
import { getTargetCategoryTranslation } from "./partial/rated/activatable/targetCategory.js"
|
||||
import { createImprovementCost } from "./partial/rated/improvementCost.js"
|
||||
import { getTextForCheck } from "./partial/rated/skillCheck.js"
|
||||
import { ModifiableParameter } from "./partial/rated/activatable/nonModifiableSuffix.js"
|
||||
import { renderNonModifiableRange } from "./partial/rated/activatable/range.js"
|
||||
import { renderTargetCategory } from "./partial/rated/activatable/targetCategory.js"
|
||||
import { renderImprovementCost } from "./partial/rated/improvementCost.js"
|
||||
import { renderSkillCheckWithPenalty } from "./partial/rated/skillCheck.js"
|
||||
import type { EnvMap } from "./partial/reader.js"
|
||||
import { ResponsiveTextSize } from "./partial/responsiveText.js"
|
||||
|
||||
const getTextForTraditions = (
|
||||
@@ -83,7 +85,7 @@ const getTextForTraditions = (
|
||||
export const getBlessingEntityDescription = createEntityDescriptionCreator<
|
||||
"Blessing",
|
||||
{
|
||||
getInstanceById: GetInstanceById<"TargetCategory">
|
||||
getInstanceById: GetInstanceById<"Publication" | "TargetCategory">
|
||||
}
|
||||
>(({ getInstanceById }, locale, { content: entry }) => {
|
||||
const { translate, translateMap } = locale
|
||||
@@ -93,18 +95,15 @@ export const getBlessingEntityDescription = createEntityDescriptionCreator<
|
||||
return undefined
|
||||
}
|
||||
|
||||
const range = getTextForBlessingRange(
|
||||
translate,
|
||||
ResponsiveTextSize.Full,
|
||||
entry.parameters.range,
|
||||
)
|
||||
|
||||
const duration = getDurationTranslationForBlessing(
|
||||
const env = {
|
||||
translate,
|
||||
translateMap,
|
||||
ResponsiveTextSize.Full,
|
||||
entry.parameters.duration,
|
||||
)
|
||||
getInstanceById,
|
||||
responsiveTextSize: ResponsiveTextSize.Full,
|
||||
} satisfies Partial<EnvMap>
|
||||
|
||||
const range = renderNonModifiableRange(entry.parameters.range, false).run(env)
|
||||
const duration = renderOneTimeDuration(entry.parameters.duration).run(env)
|
||||
|
||||
return {
|
||||
title: translation.name,
|
||||
@@ -128,7 +127,7 @@ export const getBlessingEntityDescription = createEntityDescriptionCreator<
|
||||
? `***${duration}*** (${translation.duration})`
|
||||
: duration,
|
||||
},
|
||||
getTargetCategoryTranslation(getInstanceById, locale, entry.target),
|
||||
renderTargetCategory(entry.target).run(env),
|
||||
],
|
||||
errata: translation.errata,
|
||||
references: entry.src,
|
||||
@@ -143,6 +142,7 @@ export const getLiturgicalChantEntityDescription =
|
||||
"LiturgicalChant",
|
||||
{
|
||||
getInstanceById: GetInstanceById<
|
||||
| "Publication"
|
||||
| "Attribute"
|
||||
| "SkillModificationLevel"
|
||||
| "TargetCategory"
|
||||
@@ -160,46 +160,42 @@ export const getLiturgicalChantEntityDescription =
|
||||
return undefined
|
||||
}
|
||||
|
||||
const { castingTime, cost, range, duration } = (() => {
|
||||
switch (entry.parameters.kind) {
|
||||
case "OneTime":
|
||||
return getFastOneTimePerformanceParametersTranslations(
|
||||
getInstanceById,
|
||||
locale,
|
||||
Entity.LiturgicalChant,
|
||||
ResponsiveTextSize.Full,
|
||||
entry.parameters.OneTime,
|
||||
)
|
||||
const env = {
|
||||
translate,
|
||||
translateMap,
|
||||
getInstanceById,
|
||||
localeJoin: locale.join,
|
||||
energyUnit: "KarmaPoints",
|
||||
responsiveTextSize: ResponsiveTextSize.Full,
|
||||
nonModifiableSuffix: (
|
||||
param: ModifiableParameter,
|
||||
): TranslationKeysWithoutParams => {
|
||||
switch (param) {
|
||||
case ModifiableParameter.CastingTime:
|
||||
return " (you cannot use a modification on this chant’s liturgical time)"
|
||||
case ModifiableParameter.Cost:
|
||||
return " (you cannot use a modification on this chant’s cost)"
|
||||
case ModifiableParameter.Range:
|
||||
return " (you cannot use a modification on this chant’s range)"
|
||||
default:
|
||||
return assertExhaustive(param)
|
||||
}
|
||||
},
|
||||
} satisfies Partial<EnvMap>
|
||||
|
||||
case "Sustained":
|
||||
return getFastSustainedPerformanceParametersTranslations(
|
||||
getInstanceById,
|
||||
locale,
|
||||
Entity.LiturgicalChant,
|
||||
ResponsiveTextSize.Full,
|
||||
entry.parameters.Sustained,
|
||||
)
|
||||
|
||||
default:
|
||||
return assertExhaustive(entry.parameters)
|
||||
}
|
||||
})()
|
||||
const { castingTime, cost, range, duration } =
|
||||
renderFastPerformanceParameters(entry.parameters).run(env)
|
||||
|
||||
return {
|
||||
title: translation.name,
|
||||
className: "liturgical-chant",
|
||||
body: [
|
||||
getTextForCheck(
|
||||
{ translate, translateMap, getInstanceById },
|
||||
renderSkillCheckWithPenalty(
|
||||
entry.check,
|
||||
{
|
||||
value: entry.check_penalty,
|
||||
responsiveText: ResponsiveTextSize.Full,
|
||||
getInstanceById,
|
||||
idMap,
|
||||
},
|
||||
),
|
||||
...getTextForEffect(locale, translation.effect),
|
||||
entry.check_penalty,
|
||||
idMap,
|
||||
).run(env),
|
||||
...renderEffect(translation.effect).run(env),
|
||||
{
|
||||
label: translate("Liturgical Time"),
|
||||
value:
|
||||
@@ -229,7 +225,7 @@ export const getLiturgicalChantEntityDescription =
|
||||
? `***${duration}*** (${translation.duration.full})`
|
||||
: duration,
|
||||
},
|
||||
getTargetCategoryTranslation(getInstanceById, locale, entry.target),
|
||||
renderTargetCategory(entry.target).run(env),
|
||||
getTextForTraditions(
|
||||
{
|
||||
translate,
|
||||
@@ -239,7 +235,7 @@ export const getLiturgicalChantEntityDescription =
|
||||
},
|
||||
entry.traditions,
|
||||
),
|
||||
createImprovementCost(translate, entry.improvement_cost),
|
||||
renderImprovementCost(entry.improvement_cost).run(env),
|
||||
],
|
||||
errata: translation.errata,
|
||||
references: entry.src,
|
||||
@@ -253,6 +249,7 @@ export const getCeremonyEntityDescription = createEntityDescriptionCreator<
|
||||
"Ceremony",
|
||||
{
|
||||
getInstanceById: GetInstanceById<
|
||||
| "Publication"
|
||||
| "Attribute"
|
||||
| "SkillModificationLevel"
|
||||
| "TargetCategory"
|
||||
@@ -270,47 +267,40 @@ export const getCeremonyEntityDescription = createEntityDescriptionCreator<
|
||||
return undefined
|
||||
}
|
||||
|
||||
const { castingTime, cost, range, duration } = (() => {
|
||||
switch (entry.parameters.kind) {
|
||||
case "OneTime":
|
||||
return getSlowOneTimePerformanceParametersTranslations(
|
||||
getInstanceById,
|
||||
locale,
|
||||
Entity.Ritual,
|
||||
ResponsiveTextSize.Full,
|
||||
entry.parameters.OneTime,
|
||||
getSlowCastingTimeTranslation,
|
||||
)
|
||||
const env = {
|
||||
translate,
|
||||
translateMap,
|
||||
getInstanceById,
|
||||
localeJoin: locale.join,
|
||||
energyUnit: "KarmaPoints",
|
||||
responsiveTextSize: ResponsiveTextSize.Full,
|
||||
nonModifiableSuffix: (
|
||||
param: ModifiableParameter,
|
||||
): TranslationKeysWithoutParams => {
|
||||
switch (param) {
|
||||
case ModifiableParameter.CastingTime:
|
||||
return " (you cannot use a modification on this ceremony’s ceremonial time)"
|
||||
case ModifiableParameter.Cost:
|
||||
return " (you cannot use a modification on this ceremony’s cost)"
|
||||
case ModifiableParameter.Range:
|
||||
return " (you cannot use a modification on this ceremony’s range)"
|
||||
default:
|
||||
return assertExhaustive(param)
|
||||
}
|
||||
},
|
||||
} satisfies Partial<EnvMap>
|
||||
|
||||
case "Sustained":
|
||||
return getSlowSustainedPerformanceParametersTranslations(
|
||||
getInstanceById,
|
||||
locale,
|
||||
Entity.Ceremony,
|
||||
ResponsiveTextSize.Full,
|
||||
entry.parameters.Sustained,
|
||||
)
|
||||
|
||||
default:
|
||||
return assertExhaustive(entry.parameters)
|
||||
}
|
||||
})()
|
||||
const { castingTime, cost, range, duration } =
|
||||
renderSlowPerformanceParameters(entry.parameters).run(env)
|
||||
|
||||
return {
|
||||
title: translation.name,
|
||||
className: "ceremony",
|
||||
body: [
|
||||
getTextForCheck(
|
||||
{ translate, translateMap, getInstanceById },
|
||||
entry.check,
|
||||
{
|
||||
value: entry.check_penalty,
|
||||
responsiveText: ResponsiveTextSize.Full,
|
||||
getInstanceById,
|
||||
idMap,
|
||||
},
|
||||
renderSkillCheckWithPenalty(entry.check, entry.check_penalty, idMap).run(
|
||||
env,
|
||||
),
|
||||
...getTextForEffect(locale, translation.effect),
|
||||
...renderEffect(translation.effect).run(env),
|
||||
{
|
||||
label: translate("Ceremonial Time"),
|
||||
value:
|
||||
@@ -340,7 +330,7 @@ export const getCeremonyEntityDescription = createEntityDescriptionCreator<
|
||||
? `***${duration}*** (${translation.duration.full})`
|
||||
: duration,
|
||||
},
|
||||
getTargetCategoryTranslation(getInstanceById, locale, entry.target),
|
||||
renderTargetCategory(entry.target).run(env),
|
||||
getTextForTraditions(
|
||||
{
|
||||
translate,
|
||||
@@ -350,7 +340,7 @@ export const getCeremonyEntityDescription = createEntityDescriptionCreator<
|
||||
},
|
||||
entry.traditions,
|
||||
),
|
||||
createImprovementCost(translate, entry.improvement_cost),
|
||||
renderImprovementCost(entry.improvement_cost).run(env),
|
||||
],
|
||||
errata: translation.errata,
|
||||
references: entry.src,
|
||||
|
||||
+56
-49
@@ -1,4 +1,5 @@
|
||||
import { mapNullableDefault } from "@elyukai/utils/nullable"
|
||||
import { Reader } from "@elyukai/utils/reader"
|
||||
import { isNotNullish, mapNullable } from "@optolith/helpers/nullable"
|
||||
import type { ResponsiveTextOptional } from "optolith-database-schema/gen"
|
||||
import type {
|
||||
@@ -6,10 +7,8 @@ import type {
|
||||
Translate,
|
||||
TranslateMap,
|
||||
} from "../../helpers/translate.js"
|
||||
import {
|
||||
getResponsiveTextOptional,
|
||||
type ResponsiveTextSize,
|
||||
} from "./responsiveText.js"
|
||||
import { type StdReader } from "./reader.js"
|
||||
import { getResponsiveTextOptional } from "./responsiveText.js"
|
||||
import { MISSING_VALUE } from "./unknown.js"
|
||||
|
||||
/**
|
||||
@@ -95,62 +94,70 @@ export const renderMap = <
|
||||
* Renders a map of options to a string.
|
||||
*/
|
||||
export const renderResponsiveMap = <
|
||||
M extends { options: MO[]; translations?: LocaleMap<MT> },
|
||||
MOT = M extends { options: { translations?: LocaleMap<infer MOT_> }[] }
|
||||
? MOT_
|
||||
: never,
|
||||
MT = M extends { translations?: LocaleMap<infer MT_> } ? MT_ : never,
|
||||
MO extends { translations?: LocaleMap<MOT> } = M extends {
|
||||
options: (infer MO_ & { translations?: LocaleMap<MOT> })[]
|
||||
}
|
||||
? MO_
|
||||
: never,
|
||||
M extends {
|
||||
options: MO[]
|
||||
translations?: LocaleMap<{
|
||||
list_prepend?: ResponsiveTextOptional
|
||||
listPrefix?: ResponsiveTextOptional
|
||||
list_append?: ResponsiveTextOptional
|
||||
listSuffix?: ResponsiveTextOptional
|
||||
replacement?: ResponsiveTextOptional
|
||||
}>
|
||||
},
|
||||
MO extends { translations?: LocaleMap<{ label: ResponsiveTextOptional }> } =
|
||||
M extends {
|
||||
options: (infer MO_)[]
|
||||
}
|
||||
? MO_
|
||||
: never,
|
||||
>(
|
||||
translate: Translate,
|
||||
translateMap: TranslateMap,
|
||||
responsiveTextSize: ResponsiveTextSize,
|
||||
map: M,
|
||||
getValue: (option: MO) => string | number,
|
||||
surroundValues: ((values: string) => string) | undefined,
|
||||
getLabel: (optionTranslation: MOT) => ResponsiveTextOptional,
|
||||
getListPrefix: (translation: MT) => ResponsiveTextOptional | undefined,
|
||||
getListSuffix: (translation: MT) => ResponsiveTextOptional | undefined,
|
||||
getReplacement: (translation: MT) => ResponsiveTextOptional | undefined,
|
||||
...moreToAppend: (
|
||||
| {
|
||||
surround: (values: string) => string
|
||||
getAdditionalValue: (
|
||||
option: MO,
|
||||
optionTranslation: MOT,
|
||||
optionTranslation: { label: ResponsiveTextOptional },
|
||||
) => string | number
|
||||
}
|
||||
| undefined
|
||||
)[]
|
||||
): string =>
|
||||
renderMap<M, MOT, MT, MO>(
|
||||
translate,
|
||||
translateMap,
|
||||
map,
|
||||
getValue,
|
||||
surroundValues,
|
||||
translation => {
|
||||
const responsiveLabel = getLabel(translation)
|
||||
return (
|
||||
getResponsiveTextOptional(responsiveLabel, responsiveTextSize) ??
|
||||
responsiveLabel.full
|
||||
)
|
||||
},
|
||||
translation =>
|
||||
mapNullable(getListPrefix(translation), text =>
|
||||
getResponsiveTextOptional(text, responsiveTextSize),
|
||||
),
|
||||
translation =>
|
||||
mapNullable(getListSuffix(translation), text =>
|
||||
getResponsiveTextOptional(text, responsiveTextSize),
|
||||
),
|
||||
translation =>
|
||||
mapNullable(getReplacement(translation), text =>
|
||||
getResponsiveTextOptional(text, responsiveTextSize),
|
||||
),
|
||||
...moreToAppend,
|
||||
): StdReader<string, "t" | "tm" | "rts"> =>
|
||||
Reader.asks(({ translate, translateMap, responsiveTextSize }) =>
|
||||
renderMap<
|
||||
M,
|
||||
{ label: ResponsiveTextOptional },
|
||||
{
|
||||
list_prepend?: ResponsiveTextOptional
|
||||
listPrefix?: ResponsiveTextOptional
|
||||
list_append?: ResponsiveTextOptional
|
||||
listSuffix?: ResponsiveTextOptional
|
||||
replacement?: ResponsiveTextOptional
|
||||
},
|
||||
MO
|
||||
>(
|
||||
translate,
|
||||
translateMap,
|
||||
map,
|
||||
getValue,
|
||||
surroundValues,
|
||||
translation =>
|
||||
getResponsiveTextOptional(translation.label, responsiveTextSize) ??
|
||||
translation.label.full,
|
||||
translation =>
|
||||
mapNullable(translation.listPrefix ?? translation.list_prepend, text =>
|
||||
getResponsiveTextOptional(text, responsiveTextSize),
|
||||
),
|
||||
translation =>
|
||||
mapNullable(translation.listSuffix ?? translation.list_append, text =>
|
||||
getResponsiveTextOptional(text, responsiveTextSize),
|
||||
),
|
||||
translation =>
|
||||
mapNullable(translation.replacement, text =>
|
||||
getResponsiveTextOptional(text, responsiveTextSize),
|
||||
),
|
||||
...moreToAppend,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -29,7 +29,6 @@ import {
|
||||
type ActivatableIdentifier,
|
||||
type GeneralPrerequisiteGroup,
|
||||
type RatedIdentifier,
|
||||
type SkillWithEnhancementsIdentifier,
|
||||
} from "optolith-database-schema/gen"
|
||||
import type { GetInstanceById } from "../../../helpers/getTypes.js"
|
||||
import { LocaleEnvironment } from "../../../helpers/locale.js"
|
||||
@@ -268,12 +267,14 @@ export const printPlainGeneralPrerequisites = (
|
||||
| "Race"
|
||||
| "Culture"
|
||||
| "PactCategory"
|
||||
| "PactDomain"
|
||||
| "SocialStatus"
|
||||
| "State"
|
||||
| ActivatableIdentifier["kind"]
|
||||
| RatedIdentifier["kind"]
|
||||
| "Property"
|
||||
| "Aspect"
|
||||
| "Enhancement"
|
||||
>,
|
||||
getResolvedSelectOptionById: GetResolvedSelectOptionById,
|
||||
locale: LocaleEnvironment,
|
||||
@@ -299,12 +300,14 @@ export const printGeneralPrerequisites = (
|
||||
| "Race"
|
||||
| "Culture"
|
||||
| "PactCategory"
|
||||
| "PactDomain"
|
||||
| "SocialStatus"
|
||||
| "State"
|
||||
| ActivatableIdentifier["kind"]
|
||||
| RatedIdentifier["kind"]
|
||||
| "Property"
|
||||
| "Aspect"
|
||||
| "Enhancement"
|
||||
>,
|
||||
getResolvedSelectOptionById: GetResolvedSelectOptionById,
|
||||
locale: LocaleEnvironment,
|
||||
@@ -374,12 +377,14 @@ export const printAdvantageDisadvantagePrerequisites = (
|
||||
| "Race"
|
||||
| "Culture"
|
||||
| "PactCategory"
|
||||
| "PactDomain"
|
||||
| "SocialStatus"
|
||||
| "State"
|
||||
| ActivatableIdentifier["kind"]
|
||||
| RatedIdentifier["kind"]
|
||||
| "Property"
|
||||
| "Aspect"
|
||||
| "Enhancement"
|
||||
>,
|
||||
getResolvedSelectOptionById: GetResolvedSelectOptionById,
|
||||
locale: LocaleEnvironment,
|
||||
@@ -424,7 +429,7 @@ export const printArcaneTraditionPrerequisites = (
|
||||
* Print personality trait prerequisites as a string.
|
||||
*/
|
||||
export const printPersonalityTraitPrerequisites = (
|
||||
getInstanceById: GetInstanceById<"Culture">,
|
||||
getInstanceById: GetInstanceById<"Race" | "Culture" | "PersonalityTrait">,
|
||||
locale: LocaleEnvironment,
|
||||
value: PersonalityTraitPrerequisites,
|
||||
): string =>
|
||||
@@ -535,7 +540,7 @@ export const printGeodeRitualPrerequisites = (
|
||||
* Print enhancement prerequisites as a string.
|
||||
*/
|
||||
export const printEnhancementPrerequisites = (
|
||||
getInstanceById: GetInstanceById<SkillWithEnhancementsIdentifier["kind"]>,
|
||||
getInstanceById: GetInstanceById<RatedIdentifier["kind"] | "Enhancement">,
|
||||
locale: LocaleEnvironment,
|
||||
value: EnhancementPrerequisites,
|
||||
): string =>
|
||||
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
SpellworkPrerequisiteGroup,
|
||||
type ActivatableIdentifier,
|
||||
type RatedIdentifier,
|
||||
type SkillWithEnhancementsIdentifier,
|
||||
} from "optolith-database-schema/gen"
|
||||
import type { GetInstanceById } from "../../../helpers/getTypes.js"
|
||||
import { LocaleEnvironment } from "../../../helpers/locale.js"
|
||||
@@ -101,12 +100,14 @@ export const printGeneralPrerequisiteGroup = (
|
||||
| "Race"
|
||||
| "Culture"
|
||||
| "PactCategory"
|
||||
| "PactDomain"
|
||||
| "SocialStatus"
|
||||
| "State"
|
||||
| ActivatableIdentifier["kind"]
|
||||
| RatedIdentifier["kind"]
|
||||
| "Property"
|
||||
| "Aspect"
|
||||
| "Enhancement"
|
||||
>,
|
||||
getResolvedSelectOptionById: GetResolvedSelectOptionById,
|
||||
locale: LocaleEnvironment,
|
||||
@@ -237,12 +238,14 @@ export const printAdvantageDisadvantagePrerequisiteGroup = (
|
||||
| "Race"
|
||||
| "Culture"
|
||||
| "PactCategory"
|
||||
| "PactDomain"
|
||||
| "SocialStatus"
|
||||
| "State"
|
||||
| ActivatableIdentifier["kind"]
|
||||
| RatedIdentifier["kind"]
|
||||
| "Property"
|
||||
| "Aspect"
|
||||
| "Enhancement"
|
||||
>,
|
||||
getResolvedSelectOptionById: GetResolvedSelectOptionById,
|
||||
locale: LocaleEnvironment,
|
||||
@@ -449,7 +452,7 @@ export const printGeodeRitualPrerequisiteGroup = (
|
||||
* Print the translation of an enhancement prerequisite group.
|
||||
*/
|
||||
export const printEnhancementPrerequisiteGroup = (
|
||||
getInstanceById: GetInstanceById<SkillWithEnhancementsIdentifier["kind"]>,
|
||||
getInstanceById: GetInstanceById<RatedIdentifier["kind"] | "Enhancement">,
|
||||
locale: LocaleEnvironment,
|
||||
prerequisite: EnhancementPrerequisiteGroup,
|
||||
): PrerequisitePart | undefined => {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Reader } from "@elyukai/utils/reader"
|
||||
import { isNotNullish, mapNullable } from "@optolith/helpers/nullable"
|
||||
import { assertExhaustive } from "@optolith/helpers/typeSafety"
|
||||
import {
|
||||
@@ -9,192 +10,133 @@ import {
|
||||
ModifiableCastingTime,
|
||||
SlowCastingTime,
|
||||
SlowSkillNonModifiableCastingTime,
|
||||
type DurationUnitValue,
|
||||
type SkillModificationLevel_ID,
|
||||
} from "optolith-database-schema/gen"
|
||||
import { Case } from "../../../../helpers/enums.js"
|
||||
import type { GetInstanceById } from "../../../../helpers/getTypes.js"
|
||||
import type { Translate } from "../../../../helpers/translate.js"
|
||||
import { ResponsiveTextSize } from "../../responsiveText.js"
|
||||
import { formatTimeSpan } from "../../units/timeSpan.js"
|
||||
import { MISSING_VALUE } from "../../unknown.js"
|
||||
import { Entity } from "./entity.js"
|
||||
import {
|
||||
getNonModifiableSuffixTranslation,
|
||||
getInstanceByIdR,
|
||||
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 { getMapModifiableBySpeed, Speed } from "./speed.js"
|
||||
import { Speed } from "./speed.js"
|
||||
|
||||
const getModifiableCastingTimeTranslation = (
|
||||
getInstanceById: GetInstanceById<"SkillModificationLevel">,
|
||||
translate: Translate,
|
||||
speed: Speed,
|
||||
responsiveTextSize: ResponsiveTextSize,
|
||||
const deriveModifiableCastingTime = (
|
||||
modificationLevelId: SkillModificationLevel_ID,
|
||||
): StdReader<
|
||||
DurationUnitValue | undefined,
|
||||
"s" | "ibi",
|
||||
"SkillModificationLevel"
|
||||
> =>
|
||||
getInstanceByIdR<"SkillModificationLevel">().thenW(
|
||||
getInstanceById =>
|
||||
mapNullable(
|
||||
getInstanceById("SkillModificationLevel", modificationLevelId),
|
||||
modificationLevel =>
|
||||
modifiableBySpeedR("casting_time", modificationLevel).map(
|
||||
castingTime =>
|
||||
typeof castingTime === "number"
|
||||
? { value: castingTime, unit: Case("Actions") }
|
||||
: castingTime,
|
||||
),
|
||||
) ?? Reader.of(undefined),
|
||||
)
|
||||
|
||||
const renderModifiableCastingTime = (
|
||||
value: ModifiableCastingTime,
|
||||
): string =>
|
||||
mapNullable(
|
||||
getInstanceById("SkillModificationLevel", value.initial_modification_level),
|
||||
modificationLevel =>
|
||||
getMapModifiableBySpeed(
|
||||
config =>
|
||||
formatTimeSpan(
|
||||
translate,
|
||||
responsiveTextSize,
|
||||
Case("Actions"),
|
||||
config.casting_time,
|
||||
),
|
||||
config =>
|
||||
formatTimeSpan(
|
||||
translate,
|
||||
responsiveTextSize,
|
||||
config.casting_time.unit,
|
||||
config.casting_time.value,
|
||||
),
|
||||
speed,
|
||||
modificationLevel,
|
||||
),
|
||||
) ?? MISSING_VALUE
|
||||
): StdReader<string, "t" | "rts" | "s" | "ibi", "SkillModificationLevel"> =>
|
||||
deriveModifiableCastingTime(value.initial_modification_level).thenW(
|
||||
castingTime =>
|
||||
castingTime === undefined
|
||||
? Reader.of(MISSING_VALUE)
|
||||
: formatCombinedTimeSpanR(castingTime),
|
||||
)
|
||||
|
||||
const getFastSkillNonModifiableCastingTimeTranslation = (
|
||||
translate: Translate,
|
||||
responsiveTextSize: ResponsiveTextSize,
|
||||
const renderCastingTimeDuringLovemaking = (
|
||||
value: CastingTimeDuringLovemaking,
|
||||
): StdReader<string, "t" | "rts"> => formatCombinedTimeSpanR(value)
|
||||
|
||||
/**
|
||||
* Renders the text for a non-modifiable casting time of a fast activatable skill.
|
||||
*/
|
||||
export const renderFastSkillNonModifiableCastingTime = (
|
||||
value: FastSkillNonModifiableCastingTime,
|
||||
): string =>
|
||||
formatTimeSpan(translate, responsiveTextSize, 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.
|
||||
*/
|
||||
export const getSlowSkillNonModifiableCastingTimeTranslation = (
|
||||
translate: Translate,
|
||||
responsiveTextSize: ResponsiveTextSize,
|
||||
export const renderSlowSkillNonModifiableCastingTime = (
|
||||
value: SlowSkillNonModifiableCastingTime,
|
||||
): string =>
|
||||
formatTimeSpan(translate, responsiveTextSize, value.unit, value.value)
|
||||
): StdReader<string, "t" | "rts"> => formatCombinedTimeSpanR(value)
|
||||
|
||||
/**
|
||||
* Translate casting time.
|
||||
*/
|
||||
export const getCastingTimeTranslation = <NonModifiable extends object>(
|
||||
getNonModifiableCastingTimeTranslation: (
|
||||
translate: Translate,
|
||||
responsiveTextSize: ResponsiveTextSize,
|
||||
export const renderCastingTime = <NonModifiable extends object>(
|
||||
renderNonModifiableCastingTime: (
|
||||
value: NonModifiable,
|
||||
) => string,
|
||||
speed: Speed,
|
||||
getInstanceById: GetInstanceById<"SkillModificationLevel">,
|
||||
translate: Translate,
|
||||
entity: Entity,
|
||||
responsiveTextSize: ResponsiveTextSize,
|
||||
) => StdReader<string, "t" | "rts" | "s" | "nms">,
|
||||
value: CastingTime<NonModifiable>,
|
||||
): string => {
|
||||
): StdReader<
|
||||
string,
|
||||
"t" | "rts" | "s" | "nms" | "ibi",
|
||||
"SkillModificationLevel"
|
||||
> => {
|
||||
switch (value.kind) {
|
||||
case "Modifiable":
|
||||
return getModifiableCastingTimeTranslation(
|
||||
getInstanceById,
|
||||
translate,
|
||||
speed,
|
||||
responsiveTextSize,
|
||||
value.Modifiable,
|
||||
)
|
||||
return renderModifiableCastingTime(value.Modifiable)
|
||||
case "NonModifiable":
|
||||
return (
|
||||
getNonModifiableCastingTimeTranslation(
|
||||
translate,
|
||||
responsiveTextSize,
|
||||
value.NonModifiable,
|
||||
) +
|
||||
getNonModifiableSuffixTranslation(
|
||||
translate,
|
||||
entity,
|
||||
ModifiableParameter.CastingTime,
|
||||
responsiveTextSize,
|
||||
)
|
||||
return renderNonModifiableCastingTime(value.NonModifiable).then(base =>
|
||||
appendNonModifiableSuffix(ModifiableParameter.CastingTime, base),
|
||||
)
|
||||
default:
|
||||
return assertExhaustive(value)
|
||||
}
|
||||
}
|
||||
|
||||
const getCastingTimeDuringLovemakingTranslation = (
|
||||
translate: Translate,
|
||||
responsiveTextSize: ResponsiveTextSize,
|
||||
value: CastingTimeDuringLovemaking,
|
||||
): string =>
|
||||
formatTimeSpan(translate, responsiveTextSize, value.unit, value.value)
|
||||
|
||||
const getCastingTimeIncludingLovemakingTranslation = <
|
||||
NonModifiable extends object,
|
||||
>(
|
||||
getNonModifiableCastingTimeTranslation: (
|
||||
translate: Translate,
|
||||
responsiveTextSize: ResponsiveTextSize,
|
||||
const renderCastingTimeIncludingLovemaking = <NonModifiable extends object>(
|
||||
renderNonModifiableCastingTime: (
|
||||
value: NonModifiable,
|
||||
) => string,
|
||||
getInstanceById: GetInstanceById<"SkillModificationLevel">,
|
||||
translate: Translate,
|
||||
speed: Speed,
|
||||
entity: Entity,
|
||||
responsiveTextSize: ResponsiveTextSize,
|
||||
) => StdReader<string, "t" | "rts" | "s" | "nms">,
|
||||
value: CastingTimeIncludingLovemaking<NonModifiable>,
|
||||
) =>
|
||||
[
|
||||
Reader.sequence([
|
||||
mapNullable(value.default, def =>
|
||||
getCastingTimeTranslation(
|
||||
getNonModifiableCastingTimeTranslation,
|
||||
speed,
|
||||
getInstanceById,
|
||||
translate,
|
||||
entity,
|
||||
responsiveTextSize,
|
||||
def,
|
||||
),
|
||||
),
|
||||
mapNullable(value.during_lovemaking, duringLovemaking =>
|
||||
getCastingTimeDuringLovemakingTranslation(
|
||||
translate,
|
||||
responsiveTextSize,
|
||||
duringLovemaking,
|
||||
),
|
||||
),
|
||||
]
|
||||
.filter(isNotNullish)
|
||||
.join(" / ")
|
||||
renderCastingTime(renderNonModifiableCastingTime, def),
|
||||
) ?? Reader.of(undefined),
|
||||
mapNullable(value.during_lovemaking, renderCastingTimeDuringLovemaking) ??
|
||||
Reader.of(undefined),
|
||||
]).map(texts => texts.filter(isNotNullish).join(" / "))
|
||||
|
||||
/**
|
||||
* Get the text for the casting time of a fast activatable skill.
|
||||
*/
|
||||
export const getFastCastingTimeTranslation = (
|
||||
getInstanceById: GetInstanceById<"SkillModificationLevel">,
|
||||
translate: Translate,
|
||||
entity: Entity,
|
||||
responsiveTextSize: ResponsiveTextSize,
|
||||
export const renderFastCastingTime = (
|
||||
value: FastCastingTime,
|
||||
): string =>
|
||||
getCastingTimeIncludingLovemakingTranslation(
|
||||
getFastSkillNonModifiableCastingTimeTranslation,
|
||||
getInstanceById,
|
||||
translate,
|
||||
Speed.Fast,
|
||||
entity,
|
||||
responsiveTextSize,
|
||||
): StdReader<string, "t" | "rts" | "nms" | "ibi", "SkillModificationLevel"> =>
|
||||
renderCastingTimeIncludingLovemaking(
|
||||
renderFastSkillNonModifiableCastingTime,
|
||||
value,
|
||||
)
|
||||
).with(env => ({ ...env, speed: Speed.Fast }))
|
||||
|
||||
/**
|
||||
* Get the text for the casting time of a slow activatable skill.
|
||||
*/
|
||||
export const getSlowCastingTimeTranslation = (
|
||||
getInstanceById: GetInstanceById<"SkillModificationLevel">,
|
||||
translate: Translate,
|
||||
entity: Entity,
|
||||
responsiveTextSize: ResponsiveTextSize,
|
||||
export const renderSlowCastingTime = (
|
||||
value: SlowCastingTime,
|
||||
): string =>
|
||||
getCastingTimeIncludingLovemakingTranslation(
|
||||
getSlowSkillNonModifiableCastingTimeTranslation,
|
||||
getInstanceById,
|
||||
translate,
|
||||
Speed.Slow,
|
||||
entity,
|
||||
responsiveTextSize,
|
||||
): StdReader<string, "t" | "rts" | "nms" | "ibi", "SkillModificationLevel"> =>
|
||||
renderCastingTimeIncludingLovemaking(
|
||||
renderSlowSkillNonModifiableCastingTime,
|
||||
value,
|
||||
)
|
||||
).with(env => ({ ...env, speed: Speed.Slow }))
|
||||
|
||||
@@ -5,21 +5,18 @@ import type {
|
||||
CheckResultBasedModifier,
|
||||
CheckResultValue,
|
||||
} from "optolith-database-schema/gen"
|
||||
import { Translate } from "../../../../helpers/translate.js"
|
||||
import {
|
||||
divisionFormatter,
|
||||
multiplicationFormatter,
|
||||
} from "../../mathOperation.js"
|
||||
import { translateR, type StdReader } from "../../reader.js"
|
||||
|
||||
const getCheckResultBaseValueTranslation = (
|
||||
translate: Translate,
|
||||
baseValue: CheckResultValue,
|
||||
) => {
|
||||
const renderCheckResultBaseValue = (baseValue: CheckResultValue) => {
|
||||
switch (baseValue.kind) {
|
||||
case "QualityLevels":
|
||||
return translate("QL")
|
||||
return translateR("QL")
|
||||
case "SkillPoints":
|
||||
return translate("SP")
|
||||
return translateR("SP")
|
||||
default:
|
||||
return assertExhaustive(baseValue)
|
||||
}
|
||||
@@ -56,21 +53,20 @@ export const appendCheckResultModifier = (
|
||||
modifier: CheckResultBasedModifier,
|
||||
) => {
|
||||
const formatter = getArithmeticFormatter(modifier.arithmetic)
|
||||
return formatter(left, modifier.value.toString())
|
||||
return formatter(left, modifier.value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value text for a check-result-based parameter of an activatable
|
||||
* skill.
|
||||
*/
|
||||
export const getCheckResultBasedValueTranslation = (
|
||||
translate: Translate,
|
||||
export const renderCheckResultBasedValue = (
|
||||
value: CheckResultBased,
|
||||
): string => {
|
||||
const base = getCheckResultBaseValueTranslation(translate, value.base)
|
||||
return mapNullableDefault(
|
||||
value.modifier,
|
||||
modifier => appendCheckResultModifier(base, modifier),
|
||||
base,
|
||||
): StdReader<string, "t"> =>
|
||||
renderCheckResultBaseValue(value.base).map(base =>
|
||||
mapNullableDefault(
|
||||
value.modifier,
|
||||
modifier => appendCheckResultModifier(base, modifier),
|
||||
base,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,204 +1,156 @@
|
||||
import { Reader } from "@elyukai/utils/reader"
|
||||
import { mapNullable } from "@optolith/helpers/nullable"
|
||||
import { assertExhaustive } from "@optolith/helpers/typeSafety"
|
||||
import type {
|
||||
BlessingDuration,
|
||||
CantripDuration,
|
||||
CastingTimeDuringLovemaking,
|
||||
CheckResultBasedDuration,
|
||||
DurationForSustained,
|
||||
FixedDuration,
|
||||
Immediate,
|
||||
IndefiniteBlessingDuration,
|
||||
IndefiniteBlessingDurationTranslation,
|
||||
IndefiniteDuration,
|
||||
IndefiniteDurationTranslation,
|
||||
MusicDuration,
|
||||
PermanentDuration,
|
||||
ResponsiveText,
|
||||
} from "optolith-database-schema/gen"
|
||||
import type { Translate, TranslateMap } from "../../../../helpers/translate.js"
|
||||
import type { LocaleMap } from "../../../../helpers/translate.js"
|
||||
import {
|
||||
getResponsiveText,
|
||||
replaceTextIfRequested,
|
||||
responsive,
|
||||
ResponsiveTextSize,
|
||||
} from "../../responsiveText.js"
|
||||
import { formatTimeSpan } from "../../units/timeSpan.js"
|
||||
import { getCheckResultBasedValueTranslation } from "./checkResultBased.js"
|
||||
responsiveTextR,
|
||||
responsiveTranslateR,
|
||||
translateMapR,
|
||||
translateR,
|
||||
type StdReader,
|
||||
} from "../../reader.js"
|
||||
import { replaceTextIfNeeded } from "../../responsiveText.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 getImmediateDurationTranslation = (
|
||||
translate: Translate,
|
||||
translateMap: TranslateMap,
|
||||
responsiveTextSize: ResponsiveTextSize,
|
||||
const renderImmediateDuration = (
|
||||
value?: Immediate,
|
||||
): string => {
|
||||
const text = appendInParensIfNotEmpty(
|
||||
mapNullable(value?.maximum, max => {
|
||||
const maxText = formatTimeSpan(
|
||||
translate,
|
||||
responsiveTextSize,
|
||||
max.unit,
|
||||
max.value,
|
||||
)
|
||||
): StdReader<string, "t" | "tm" | "rts"> =>
|
||||
translateR("Immediate")
|
||||
.thenW(
|
||||
base =>
|
||||
mapNullable(value?.maximum, max =>
|
||||
formatTimeSpanR(max.unit, max.value)
|
||||
.then(wrapAsMaximum)
|
||||
.map(maxText => appendInParensIfNotEmpty(maxText, base)),
|
||||
) ?? Reader.of(base),
|
||||
)
|
||||
.thenW(text => replaceTextIfNeeded(value?.translations, text))
|
||||
|
||||
return responsive(
|
||||
responsiveTextSize,
|
||||
() => translate("no more than {$value}", { value: maxText }),
|
||||
() => translate("max. {$value}", { value: maxText }),
|
||||
)
|
||||
}),
|
||||
translate("Immediate"),
|
||||
)
|
||||
|
||||
return replaceTextIfRequested(
|
||||
"replacement",
|
||||
value?.translations,
|
||||
translateMap,
|
||||
responsiveTextSize,
|
||||
text,
|
||||
)
|
||||
}
|
||||
|
||||
const getPermanentDurationTranslation = (
|
||||
translate: Translate,
|
||||
translateMap: TranslateMap,
|
||||
responsiveTextSize: ResponsiveTextSize,
|
||||
const renderPermanentDuration = (
|
||||
value: PermanentDuration,
|
||||
): string =>
|
||||
replaceTextIfRequested(
|
||||
"replacement",
|
||||
value.translations,
|
||||
translateMap,
|
||||
responsiveTextSize,
|
||||
translate("Permanent"),
|
||||
): StdReader<string, "t" | "tm" | "rts"> =>
|
||||
translateR("Permanent").thenW(text =>
|
||||
replaceTextIfNeeded(value.translations, text),
|
||||
)
|
||||
|
||||
const getFixedDurationTranslation = (
|
||||
translate: Translate,
|
||||
translateMap: TranslateMap,
|
||||
responsiveTextSize: ResponsiveTextSize,
|
||||
const renderFixedDuration = (
|
||||
value: FixedDuration,
|
||||
): string => {
|
||||
const duration = formatTimeSpan(
|
||||
translate,
|
||||
responsiveTextSize,
|
||||
value.unit,
|
||||
value.value,
|
||||
)
|
||||
): StdReader<string, "t" | "tm" | "rts"> =>
|
||||
formatCombinedTimeSpanR(value)
|
||||
.then(text => wrapIfMaximum(value.is_maximum, text))
|
||||
.thenW(text => replaceTextIfNeeded(value.translations, text))
|
||||
|
||||
const durationWrappedIfMaximum = wrapIfMaximum(
|
||||
translate,
|
||||
responsiveTextSize,
|
||||
value.is_maximum,
|
||||
duration,
|
||||
)
|
||||
|
||||
return replaceTextIfRequested(
|
||||
"replacement",
|
||||
value.translations,
|
||||
translateMap,
|
||||
responsiveTextSize,
|
||||
durationWrappedIfMaximum,
|
||||
)
|
||||
}
|
||||
|
||||
const getCheckResultBasedDurationTranslation = (
|
||||
translate: Translate,
|
||||
responsiveTextSize: ResponsiveTextSize,
|
||||
const renderCheckResultBasedDuration = (
|
||||
value: CheckResultBasedDuration,
|
||||
): string => {
|
||||
const duration = formatTimeSpan(
|
||||
translate,
|
||||
responsiveTextSize,
|
||||
value.unit,
|
||||
getCheckResultBasedValueTranslation(translate, value),
|
||||
)
|
||||
): StdReader<string, "t" | "tm" | "rts"> =>
|
||||
renderCheckResultBasedValue(value)
|
||||
.thenW(text => formatTimeSpanR(value.unit, text))
|
||||
.then(text => wrapIfMaximum(value.is_maximum, text))
|
||||
|
||||
return wrapIfMaximum(
|
||||
translate,
|
||||
responsiveTextSize,
|
||||
value.is_maximum,
|
||||
duration,
|
||||
)
|
||||
const renderIndefiniteDuration = (value: {
|
||||
maximum?: OneTimeDuration
|
||||
translations: LocaleMap<{
|
||||
description: ResponsiveText | string
|
||||
}>
|
||||
}): StdReader<string, "t" | "tm" | "rts"> => {
|
||||
const { maximum, translations } = value
|
||||
return translateMapR<{
|
||||
description: ResponsiveText | string
|
||||
}>(translations)
|
||||
.thenW(translation =>
|
||||
typeof translation?.description === "object"
|
||||
? responsiveTextR(translation.description)
|
||||
: Reader.of(translation?.description ?? MISSING_VALUE),
|
||||
)
|
||||
.thenW(
|
||||
maximum === undefined
|
||||
? Reader.of
|
||||
: 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,
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
const getIndefiniteDurationTranslation = (
|
||||
translateMap: TranslateMap,
|
||||
responsiveTextSize: ResponsiveTextSize,
|
||||
value: IndefiniteDuration | IndefiniteBlessingDuration,
|
||||
) => {
|
||||
const description = translateMap<
|
||||
IndefiniteDurationTranslation | IndefiniteBlessingDurationTranslation
|
||||
>(value.translations)?.description
|
||||
const renderDurationDuringLovemaking = (
|
||||
value: CastingTimeDuringLovemaking,
|
||||
): StdReader<string, "t" | "rts"> => formatCombinedTimeSpanR(value)
|
||||
|
||||
return typeof description === "string"
|
||||
? description
|
||||
: getResponsiveText(description, responsiveTextSize)
|
||||
}
|
||||
type OneTimeDuration =
|
||||
| {
|
||||
kind: "Immediate"
|
||||
Immediate?: Immediate
|
||||
}
|
||||
| {
|
||||
kind: "Permanent"
|
||||
Permanent: PermanentDuration
|
||||
}
|
||||
| {
|
||||
kind: "Fixed"
|
||||
Fixed: FixedDuration
|
||||
}
|
||||
| {
|
||||
kind: "CheckResultBased"
|
||||
CheckResultBased: CheckResultBasedDuration
|
||||
}
|
||||
| {
|
||||
kind: "Indefinite"
|
||||
Indefinite: {
|
||||
maximum?: OneTimeDuration
|
||||
translations: LocaleMap<{
|
||||
description: ResponsiveText | string
|
||||
}>
|
||||
}
|
||||
}
|
||||
// used for cantrips:
|
||||
| {
|
||||
kind: "DuringLovemaking"
|
||||
DuringLovemaking: CastingTimeDuringLovemaking
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the text for the duration of a one-time activatable skill.
|
||||
*/
|
||||
export const getDurationForOneTimeTranslation = (
|
||||
translate: Translate,
|
||||
translateMap: TranslateMap,
|
||||
responsiveTextSize: ResponsiveTextSize,
|
||||
value:
|
||||
| {
|
||||
kind: "Immediate"
|
||||
Immediate?: Immediate
|
||||
}
|
||||
| {
|
||||
kind: "Permanent"
|
||||
Permanent: PermanentDuration
|
||||
}
|
||||
| {
|
||||
kind: "Fixed"
|
||||
Fixed: FixedDuration
|
||||
}
|
||||
| {
|
||||
kind: "CheckResultBased"
|
||||
CheckResultBased: CheckResultBasedDuration
|
||||
}
|
||||
| {
|
||||
kind: "Indefinite"
|
||||
Indefinite: IndefiniteDuration
|
||||
},
|
||||
): string => {
|
||||
export const renderOneTimeDuration = (
|
||||
value: OneTimeDuration,
|
||||
): StdReader<string, "t" | "tm" | "rts"> => {
|
||||
switch (value.kind) {
|
||||
case "Immediate":
|
||||
return getImmediateDurationTranslation(
|
||||
translate,
|
||||
translateMap,
|
||||
responsiveTextSize,
|
||||
value.Immediate,
|
||||
)
|
||||
return renderImmediateDuration(value.Immediate)
|
||||
case "Permanent":
|
||||
return getPermanentDurationTranslation(
|
||||
translate,
|
||||
translateMap,
|
||||
responsiveTextSize,
|
||||
value.Permanent,
|
||||
)
|
||||
return renderPermanentDuration(value.Permanent)
|
||||
case "Fixed":
|
||||
return getFixedDurationTranslation(
|
||||
translate,
|
||||
translateMap,
|
||||
responsiveTextSize,
|
||||
value.Fixed,
|
||||
)
|
||||
return renderFixedDuration(value.Fixed)
|
||||
case "CheckResultBased":
|
||||
return getCheckResultBasedDurationTranslation(
|
||||
translate,
|
||||
responsiveTextSize,
|
||||
value.CheckResultBased,
|
||||
)
|
||||
return renderCheckResultBasedDuration(value.CheckResultBased)
|
||||
case "Indefinite":
|
||||
return getIndefiniteDurationTranslation(
|
||||
translateMap,
|
||||
responsiveTextSize,
|
||||
value.Indefinite,
|
||||
)
|
||||
return renderIndefiniteDuration(value.Indefinite)
|
||||
case "DuringLovemaking":
|
||||
return renderDurationDuringLovemaking(value.DuringLovemaking)
|
||||
default:
|
||||
return assertExhaustive(value)
|
||||
}
|
||||
@@ -207,107 +159,43 @@ export const getDurationForOneTimeTranslation = (
|
||||
/**
|
||||
* Returns the text for the duration of a sustained activatable skill.
|
||||
*/
|
||||
export const getDurationForSustainedTranslation = (
|
||||
translate: Translate,
|
||||
responsiveTextSize: ResponsiveTextSize,
|
||||
export const renderSustainedDuration = (
|
||||
value: DurationForSustained | undefined,
|
||||
): string =>
|
||||
): StdReader<string, "t" | "rts"> =>
|
||||
value === undefined
|
||||
? responsive(
|
||||
responsiveTextSize,
|
||||
() => translate("Sustained"),
|
||||
() => translate("(S)"),
|
||||
? responsiveTranslateR("Sustained", "(S)")
|
||||
: formatCombinedTimeSpanR(value.maximum).then(maxText =>
|
||||
wrapAsMaximum(maxText),
|
||||
)
|
||||
: wrapAsMaximum(
|
||||
translate,
|
||||
responsiveTextSize,
|
||||
formatTimeSpan(
|
||||
translate,
|
||||
responsiveTextSize,
|
||||
value.maximum.unit,
|
||||
value.maximum.value,
|
||||
),
|
||||
)
|
||||
|
||||
const getDurationDuringLovemakingTranslation = (
|
||||
translate: Translate,
|
||||
responsiveTextSize: ResponsiveTextSize,
|
||||
value: CastingTimeDuringLovemaking,
|
||||
): string =>
|
||||
formatTimeSpan(translate, responsiveTextSize, value.unit, value.value)
|
||||
|
||||
/**
|
||||
* Returns the text for the duration of a cantrip.
|
||||
* Returns the text for the duration of a musical activatable skill.
|
||||
*/
|
||||
export const getDurationTranslationForCantrip = (
|
||||
translate: Translate,
|
||||
translateMap: TranslateMap,
|
||||
responsiveTextSize: ResponsiveTextSize,
|
||||
value: CantripDuration,
|
||||
): string => {
|
||||
switch (value.kind) {
|
||||
case "Immediate":
|
||||
return getImmediateDurationTranslation(
|
||||
translate,
|
||||
translateMap,
|
||||
responsiveTextSize,
|
||||
{},
|
||||
)
|
||||
case "Fixed":
|
||||
return getFixedDurationTranslation(
|
||||
translate,
|
||||
translateMap,
|
||||
responsiveTextSize,
|
||||
value.Fixed,
|
||||
)
|
||||
case "Indefinite":
|
||||
return getIndefiniteDurationTranslation(
|
||||
translateMap,
|
||||
responsiveTextSize,
|
||||
value.Indefinite,
|
||||
)
|
||||
case "DuringLovemaking":
|
||||
return getDurationDuringLovemakingTranslation(
|
||||
translate,
|
||||
responsiveTextSize,
|
||||
value.DuringLovemaking,
|
||||
)
|
||||
default:
|
||||
return assertExhaustive(value)
|
||||
}
|
||||
}
|
||||
export const renderMusicDuration = (
|
||||
duration: MusicDuration,
|
||||
): StdReader<string, "t"> =>
|
||||
Reader.asks(({ translate }) => {
|
||||
const length = (() => {
|
||||
switch (duration.length.kind) {
|
||||
case "Long":
|
||||
return translate("long")
|
||||
case "Short":
|
||||
return translate("short")
|
||||
default:
|
||||
return assertExhaustive(duration.length)
|
||||
}
|
||||
})()
|
||||
|
||||
/**
|
||||
* Returns the text for the duration of a blessing.
|
||||
*/
|
||||
export const getDurationTranslationForBlessing = (
|
||||
translate: Translate,
|
||||
translateMap: TranslateMap,
|
||||
responsiveTextSize: ResponsiveTextSize,
|
||||
value: BlessingDuration,
|
||||
): string => {
|
||||
switch (value.kind) {
|
||||
case "Immediate":
|
||||
return getImmediateDurationTranslation(
|
||||
translate,
|
||||
translateMap,
|
||||
responsiveTextSize,
|
||||
{},
|
||||
)
|
||||
case "Fixed":
|
||||
return getFixedDurationTranslation(
|
||||
translate,
|
||||
translateMap,
|
||||
responsiveTextSize,
|
||||
value.Fixed,
|
||||
)
|
||||
case "Indefinite":
|
||||
return getIndefiniteDurationTranslation(
|
||||
translateMap,
|
||||
responsiveTextSize,
|
||||
value.Indefinite,
|
||||
)
|
||||
default:
|
||||
return assertExhaustive(value)
|
||||
}
|
||||
}
|
||||
const reusability = (() => {
|
||||
switch (duration.reusability.kind) {
|
||||
case "OneTime":
|
||||
return translate("one-time")
|
||||
case "Sustainable":
|
||||
return translate("sustainable")
|
||||
default:
|
||||
return assertExhaustive(duration.reusability)
|
||||
}
|
||||
})()
|
||||
|
||||
return `${length}, ${reusability}`
|
||||
})
|
||||
|
||||
@@ -2,60 +2,58 @@ import { filterNonNullable } from "@optolith/helpers/array"
|
||||
import { mapNullable } from "@optolith/helpers/nullable"
|
||||
import { assertExhaustive } from "@optolith/helpers/typeSafety"
|
||||
import type { ActivatableSkillEffect } from "optolith-database-schema/gen"
|
||||
import { LocaleEnvironment } from "../../../../helpers/locale.js"
|
||||
import { EntityDescriptionSection } from "../../../../index.js"
|
||||
import { translateFnR, type StdReader } from "../../reader.js"
|
||||
|
||||
const getContentPartsForQualityLevels = (
|
||||
getQualityLevelString: (index: number) => string | number,
|
||||
locale: LocaleEnvironment,
|
||||
source: {
|
||||
text_before: string
|
||||
quality_levels: string[]
|
||||
text_after?: string
|
||||
},
|
||||
): EntityDescriptionSection[] =>
|
||||
filterNonNullable([
|
||||
{
|
||||
label: locale.translate("Effect"),
|
||||
value: source.text_before,
|
||||
},
|
||||
...source.quality_levels.map((text, index) => ({
|
||||
value: text,
|
||||
label: locale.translate("QL {$value}", {
|
||||
value: getQualityLevelString(index),
|
||||
}),
|
||||
})),
|
||||
mapNullable(source.text_after, textAfter => ({
|
||||
value: textAfter,
|
||||
className: "effect-after",
|
||||
})),
|
||||
])
|
||||
): StdReader<EntityDescriptionSection[], "t"> =>
|
||||
translateFnR.map(translate =>
|
||||
filterNonNullable([
|
||||
{
|
||||
label: translate("Effect"),
|
||||
value: source.text_before,
|
||||
},
|
||||
...source.quality_levels.map((text, index) => ({
|
||||
value: text,
|
||||
label: translate("QL {$value}", {
|
||||
value: getQualityLevelString(index),
|
||||
}),
|
||||
})),
|
||||
mapNullable(source.text_after, textAfter => ({
|
||||
value: textAfter,
|
||||
className: "effect-after",
|
||||
})),
|
||||
]),
|
||||
)
|
||||
|
||||
/**
|
||||
* Gets the text for the effect of an activatable skill.
|
||||
*/
|
||||
export const getTextForEffect = (
|
||||
locale: LocaleEnvironment,
|
||||
export const renderEffect = (
|
||||
effect: ActivatableSkillEffect,
|
||||
): EntityDescriptionSection[] => {
|
||||
): StdReader<EntityDescriptionSection[], "t"> => {
|
||||
switch (effect.kind) {
|
||||
case "Plain":
|
||||
return [
|
||||
return translateFnR.map(translate => [
|
||||
{
|
||||
label: locale.translate("Effect"),
|
||||
label: translate("Effect"),
|
||||
value: effect.Plain.text,
|
||||
},
|
||||
]
|
||||
])
|
||||
case "ForEachQualityLevel":
|
||||
return getContentPartsForQualityLevels(
|
||||
index => index + 1,
|
||||
locale,
|
||||
effect.ForEachQualityLevel,
|
||||
)
|
||||
case "ForEachTwoQualityLevels":
|
||||
return getContentPartsForQualityLevels(
|
||||
index => `${index * 2 + 1}–${index * 2 + 2}`,
|
||||
locale,
|
||||
effect.ForEachTwoQualityLevels,
|
||||
)
|
||||
default:
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
/**
|
||||
* The entity type of an activatable skill.
|
||||
*/
|
||||
export enum Entity {
|
||||
Cantrip,
|
||||
Spell,
|
||||
Ritual,
|
||||
Blessing,
|
||||
LiturgicalChant,
|
||||
Ceremony,
|
||||
}
|
||||
@@ -1,218 +1,171 @@
|
||||
import { assertExhaustive } from "@elyukai/utils/typeSafety"
|
||||
import type {
|
||||
FastOneTimePerformanceParameters,
|
||||
FastSustainedPerformanceParameters,
|
||||
OneTimePerformanceParameters,
|
||||
SlowOneTimePerformanceParameters,
|
||||
SlowSustainedPerformanceParameters,
|
||||
} from "optolith-database-schema/gen"
|
||||
import type { GetInstanceById } from "../../../../helpers/getTypes.js"
|
||||
import { LocaleEnvironment } from "../../../../helpers/locale.js"
|
||||
import type { Translate } from "../../../../helpers/translate.js"
|
||||
import { ResponsiveTextSize } from "../../responsiveText.js"
|
||||
import {
|
||||
getFastCastingTimeTranslation,
|
||||
getSlowCastingTimeTranslation,
|
||||
} from "./castingTime.js"
|
||||
import {
|
||||
getOneTimeCostTranslation,
|
||||
getSustainedCostTranslation,
|
||||
} from "./cost.js"
|
||||
import {
|
||||
getDurationForOneTimeTranslation,
|
||||
getDurationForSustainedTranslation,
|
||||
} from "./duration.js"
|
||||
import { Entity } from "./entity.js"
|
||||
import { getTextForActivatableSkillRange } from "./range.js"
|
||||
import type { StdReader } from "../../reader.js"
|
||||
import { renderFastCastingTime, renderSlowCastingTime } from "./castingTime.js"
|
||||
import { renderOneTimeCost, renderSustainedCost } from "./cost.js"
|
||||
import { renderOneTimeDuration, renderSustainedDuration } from "./duration.js"
|
||||
import { renderRange } from "./range.js"
|
||||
import { Speed } from "./speed.js"
|
||||
|
||||
type RenderedPerformanceParameters = {
|
||||
castingTime: string
|
||||
cost: string
|
||||
range: string
|
||||
duration: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the texts for all fast one-time performance parameters.
|
||||
*/
|
||||
export const getFastOneTimePerformanceParametersTranslations = (
|
||||
getInstanceById: GetInstanceById<"SkillModificationLevel">,
|
||||
locale: LocaleEnvironment,
|
||||
entity: Entity,
|
||||
responsiveTextSize: ResponsiveTextSize,
|
||||
export const renderFastOneTimePerformanceParameters = (
|
||||
value: FastOneTimePerformanceParameters,
|
||||
): {
|
||||
castingTime: string
|
||||
cost: string
|
||||
range: string
|
||||
duration: string
|
||||
} => ({
|
||||
castingTime: getFastCastingTimeTranslation(
|
||||
getInstanceById,
|
||||
locale.translate,
|
||||
entity,
|
||||
responsiveTextSize,
|
||||
value.casting_time,
|
||||
),
|
||||
cost: getOneTimeCostTranslation(
|
||||
getInstanceById,
|
||||
locale,
|
||||
Speed.Fast,
|
||||
entity,
|
||||
responsiveTextSize,
|
||||
value.cost,
|
||||
),
|
||||
range: getTextForActivatableSkillRange(
|
||||
getInstanceById,
|
||||
locale.translate,
|
||||
locale.translateMap,
|
||||
Speed.Fast,
|
||||
responsiveTextSize,
|
||||
entity,
|
||||
value.range,
|
||||
),
|
||||
duration: getDurationForOneTimeTranslation(
|
||||
locale.translate,
|
||||
locale.translateMap,
|
||||
responsiveTextSize,
|
||||
value.duration,
|
||||
),
|
||||
})
|
||||
): StdReader<
|
||||
RenderedPerformanceParameters,
|
||||
"t" | "tm" | "lj" | "eu" | "rts" | "nms" | "ibi",
|
||||
"SkillModificationLevel"
|
||||
> =>
|
||||
renderFastCastingTime(value.casting_time)
|
||||
.thenW(castingTime =>
|
||||
renderOneTimeCost(value.cost).then(cost =>
|
||||
renderRange(value.range).then(range =>
|
||||
renderOneTimeDuration(value.duration).map(duration => ({
|
||||
castingTime,
|
||||
cost,
|
||||
range,
|
||||
duration,
|
||||
})),
|
||||
),
|
||||
),
|
||||
)
|
||||
.with(env => ({ ...env, speed: Speed.Fast }))
|
||||
|
||||
/**
|
||||
* Get the texts for all fast sustained performance parameters.
|
||||
*/
|
||||
export const getFastSustainedPerformanceParametersTranslations = (
|
||||
getInstanceById: GetInstanceById<"SkillModificationLevel">,
|
||||
locale: LocaleEnvironment,
|
||||
entity: Entity,
|
||||
responsiveTextSize: ResponsiveTextSize,
|
||||
export const renderFastSustainedPerformanceParameters = (
|
||||
value: FastSustainedPerformanceParameters,
|
||||
): {
|
||||
castingTime: string
|
||||
cost: string
|
||||
range: string
|
||||
duration: string
|
||||
} => ({
|
||||
castingTime: getFastCastingTimeTranslation(
|
||||
getInstanceById,
|
||||
locale.translate,
|
||||
entity,
|
||||
responsiveTextSize,
|
||||
value.casting_time,
|
||||
),
|
||||
cost: getSustainedCostTranslation(
|
||||
getInstanceById,
|
||||
locale,
|
||||
Speed.Fast,
|
||||
entity,
|
||||
responsiveTextSize,
|
||||
value.cost,
|
||||
),
|
||||
range: getTextForActivatableSkillRange(
|
||||
getInstanceById,
|
||||
locale.translate,
|
||||
locale.translateMap,
|
||||
Speed.Fast,
|
||||
responsiveTextSize,
|
||||
entity,
|
||||
value.range,
|
||||
),
|
||||
duration: getDurationForSustainedTranslation(
|
||||
locale.translate,
|
||||
responsiveTextSize,
|
||||
value.duration,
|
||||
),
|
||||
})
|
||||
): StdReader<
|
||||
RenderedPerformanceParameters,
|
||||
"t" | "tm" | "lj" | "eu" | "rts" | "nms" | "ibi",
|
||||
"SkillModificationLevel"
|
||||
> =>
|
||||
renderFastCastingTime(value.casting_time)
|
||||
.thenW(castingTime =>
|
||||
renderSustainedCost(value.cost).then(cost =>
|
||||
renderRange(value.range).then(range =>
|
||||
renderSustainedDuration(value.duration).map(duration => ({
|
||||
castingTime,
|
||||
cost,
|
||||
range,
|
||||
duration,
|
||||
})),
|
||||
),
|
||||
),
|
||||
)
|
||||
.with(env => ({ ...env, speed: Speed.Fast }))
|
||||
|
||||
/**
|
||||
* Renders the performance parameters for a fast activatable skill.
|
||||
*/
|
||||
export const renderFastPerformanceParameters = (
|
||||
value:
|
||||
| { kind: "OneTime"; OneTime: FastOneTimePerformanceParameters }
|
||||
| { kind: "Sustained"; Sustained: FastSustainedPerformanceParameters },
|
||||
): StdReader<
|
||||
RenderedPerformanceParameters,
|
||||
"t" | "tm" | "lj" | "eu" | "rts" | "nms" | "ibi",
|
||||
"SkillModificationLevel"
|
||||
> => {
|
||||
switch (value.kind) {
|
||||
case "OneTime":
|
||||
return renderFastOneTimePerformanceParameters(value.OneTime)
|
||||
case "Sustained":
|
||||
return renderFastSustainedPerformanceParameters(value.Sustained)
|
||||
default:
|
||||
return assertExhaustive(value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the texts for all slow one-time performance parameters.
|
||||
*/
|
||||
export const getSlowOneTimePerformanceParametersTranslations = <CastingTime>(
|
||||
getInstanceById: GetInstanceById<"SkillModificationLevel">,
|
||||
locale: LocaleEnvironment,
|
||||
entity: Entity,
|
||||
responsiveTextSize: ResponsiveTextSize,
|
||||
value: OneTimePerformanceParameters<CastingTime>,
|
||||
export const renderSlowOneTimePerformanceParameters = <CastingTime>(
|
||||
renderCastingTime: (
|
||||
getInstanceById: GetInstanceById<"SkillModificationLevel">,
|
||||
translate: Translate,
|
||||
entity: Entity,
|
||||
responsiveTextSize: ResponsiveTextSize,
|
||||
value: CastingTime,
|
||||
) => string,
|
||||
): {
|
||||
castingTime: string
|
||||
cost: string
|
||||
range: string
|
||||
duration: string
|
||||
} => ({
|
||||
castingTime: renderCastingTime(
|
||||
getInstanceById,
|
||||
locale.translate,
|
||||
entity,
|
||||
responsiveTextSize,
|
||||
value.casting_time,
|
||||
),
|
||||
cost: getOneTimeCostTranslation(
|
||||
getInstanceById,
|
||||
locale,
|
||||
Speed.Slow,
|
||||
entity,
|
||||
responsiveTextSize,
|
||||
value.cost,
|
||||
),
|
||||
range: getTextForActivatableSkillRange(
|
||||
getInstanceById,
|
||||
locale.translate,
|
||||
locale.translateMap,
|
||||
Speed.Slow,
|
||||
responsiveTextSize,
|
||||
entity,
|
||||
value.range,
|
||||
),
|
||||
duration: getDurationForOneTimeTranslation(
|
||||
locale.translate,
|
||||
locale.translateMap,
|
||||
responsiveTextSize,
|
||||
value.duration,
|
||||
),
|
||||
})
|
||||
) => StdReader<string, "t" | "rts" | "nms" | "ibi", "SkillModificationLevel">,
|
||||
value: OneTimePerformanceParameters<CastingTime>,
|
||||
): StdReader<
|
||||
RenderedPerformanceParameters,
|
||||
"t" | "tm" | "lj" | "eu" | "rts" | "nms" | "ibi",
|
||||
"SkillModificationLevel"
|
||||
> =>
|
||||
renderCastingTime(value.casting_time)
|
||||
.thenW(castingTime =>
|
||||
renderOneTimeCost(value.cost).then(cost =>
|
||||
renderRange(value.range).then(range =>
|
||||
renderOneTimeDuration(value.duration).map(duration => ({
|
||||
castingTime,
|
||||
cost,
|
||||
range,
|
||||
duration,
|
||||
})),
|
||||
),
|
||||
),
|
||||
)
|
||||
.with(env => ({ ...env, speed: Speed.Slow }))
|
||||
|
||||
/**
|
||||
* Get the texts for all slow sustained performance parameters.
|
||||
*/
|
||||
export const getSlowSustainedPerformanceParametersTranslations = (
|
||||
getInstanceById: GetInstanceById<"SkillModificationLevel">,
|
||||
locale: LocaleEnvironment,
|
||||
entity: Entity,
|
||||
responsiveTextSize: ResponsiveTextSize,
|
||||
export const renderSlowSustainedPerformanceParameters = (
|
||||
value: SlowSustainedPerformanceParameters,
|
||||
): {
|
||||
castingTime: string
|
||||
cost: string
|
||||
range: string
|
||||
duration: string
|
||||
} => ({
|
||||
castingTime: getSlowCastingTimeTranslation(
|
||||
getInstanceById,
|
||||
locale.translate,
|
||||
entity,
|
||||
responsiveTextSize,
|
||||
value.casting_time,
|
||||
),
|
||||
cost: getSustainedCostTranslation(
|
||||
getInstanceById,
|
||||
locale,
|
||||
Speed.Slow,
|
||||
entity,
|
||||
responsiveTextSize,
|
||||
value.cost,
|
||||
),
|
||||
range: getTextForActivatableSkillRange(
|
||||
getInstanceById,
|
||||
locale.translate,
|
||||
locale.translateMap,
|
||||
Speed.Slow,
|
||||
responsiveTextSize,
|
||||
entity,
|
||||
value.range,
|
||||
),
|
||||
duration: getDurationForSustainedTranslation(
|
||||
locale.translate,
|
||||
responsiveTextSize,
|
||||
value.duration,
|
||||
),
|
||||
})
|
||||
): StdReader<
|
||||
RenderedPerformanceParameters,
|
||||
"t" | "tm" | "lj" | "eu" | "rts" | "nms" | "ibi",
|
||||
"SkillModificationLevel"
|
||||
> =>
|
||||
renderSlowCastingTime(value.casting_time)
|
||||
.thenW(castingTime =>
|
||||
renderSustainedCost(value.cost).then(cost =>
|
||||
renderRange(value.range).then(range =>
|
||||
renderSustainedDuration(value.duration).map(duration => ({
|
||||
castingTime,
|
||||
cost,
|
||||
range,
|
||||
duration,
|
||||
})),
|
||||
),
|
||||
),
|
||||
)
|
||||
.with(env => ({ ...env, speed: Speed.Slow }))
|
||||
|
||||
/**
|
||||
* Renders the performance parameters for a slow activatable skill.
|
||||
*/
|
||||
export const renderSlowPerformanceParameters = (
|
||||
value:
|
||||
| { kind: "OneTime"; OneTime: SlowOneTimePerformanceParameters }
|
||||
| { kind: "Sustained"; Sustained: SlowSustainedPerformanceParameters },
|
||||
): StdReader<
|
||||
RenderedPerformanceParameters,
|
||||
"t" | "tm" | "lj" | "eu" | "rts" | "nms" | "ibi",
|
||||
"SkillModificationLevel"
|
||||
> => {
|
||||
switch (value.kind) {
|
||||
case "OneTime":
|
||||
return renderSlowOneTimePerformanceParameters(
|
||||
renderSlowCastingTime,
|
||||
value.OneTime,
|
||||
)
|
||||
case "Sustained":
|
||||
return renderSlowSustainedPerformanceParameters(value.Sustained)
|
||||
default:
|
||||
return assertExhaustive(value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,55 +1,36 @@
|
||||
import { LocaleEnvironment } from "../../../../helpers/locale.js"
|
||||
import type { Translate } from "../../../../helpers/translate.js"
|
||||
import { responsive, ResponsiveTextSize } from "../../responsiveText.js"
|
||||
import { Reader } from "@elyukai/utils/reader"
|
||||
import { responsiveTranslateR, type StdReader } from "../../reader.js"
|
||||
|
||||
/**
|
||||
* Wraps the text in a translation that indicates it’s a minimum value.
|
||||
*/
|
||||
export const wrapAsMinimum = (
|
||||
locale: LocaleEnvironment,
|
||||
responsiveTextSize: ResponsiveTextSize,
|
||||
text: string,
|
||||
): string =>
|
||||
responsive(
|
||||
responsiveTextSize,
|
||||
() => locale.translate("at least {$value}", { value: text }),
|
||||
() => locale.translate("min. {$value}", { value: text }),
|
||||
)
|
||||
export const wrapAsMinimum = (text: string): StdReader<string, "t" | "rts"> =>
|
||||
responsiveTranslateR("at least {$value}", "min. {$value}", { value: text })
|
||||
|
||||
/**
|
||||
* Wraps the text in a translation that indicates it’s a minimum value if the
|
||||
* `is_minimum` property says it’s a minimum value.
|
||||
*/
|
||||
export const wrapIfMinimum = (
|
||||
locale: LocaleEnvironment,
|
||||
responsiveTextSize: ResponsiveTextSize,
|
||||
is_minimum: boolean | undefined,
|
||||
text: string,
|
||||
) =>
|
||||
is_minimum === true ? wrapAsMinimum(locale, responsiveTextSize, text) : ""
|
||||
): StdReader<string, "t" | "rts"> =>
|
||||
is_minimum === true ? wrapAsMinimum(text) : Reader.of("")
|
||||
|
||||
/**
|
||||
* Wraps the text in a translation that indicates it’s a maximum value.
|
||||
*/
|
||||
export const wrapAsMaximum = (
|
||||
translate: Translate,
|
||||
responsiveTextSize: ResponsiveTextSize,
|
||||
text: string,
|
||||
): string =>
|
||||
responsive(
|
||||
responsiveTextSize,
|
||||
() => translate("no more than {$value}", { value: text }),
|
||||
() => translate("max. {$value}", { value: text }),
|
||||
)
|
||||
export const wrapAsMaximum = (text: string): StdReader<string, "t" | "rts"> =>
|
||||
responsiveTranslateR("no more than {$value}", "max. {$value}", {
|
||||
value: text,
|
||||
})
|
||||
|
||||
/**
|
||||
* Wraps the text in a translation that indicates it’s a maximum value if the
|
||||
* `is_maximum` property says it’s a maximum value.
|
||||
*/
|
||||
export const wrapIfMaximum = (
|
||||
translate: Translate,
|
||||
responsiveTextSize: ResponsiveTextSize,
|
||||
is_maximum: boolean | undefined,
|
||||
text: string,
|
||||
): string =>
|
||||
is_maximum === true ? wrapAsMaximum(translate, responsiveTextSize, text) : ""
|
||||
): StdReader<string, "t" | "rts"> =>
|
||||
is_maximum === true ? wrapAsMaximum(text) : Reader.of("")
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { mapNullable } from "@optolith/helpers/nullable"
|
||||
import { assertExhaustive } from "@optolith/helpers/typeSafety"
|
||||
import type { Translate, Translations } from "../../../../helpers/translate.js"
|
||||
import { responsive, ResponsiveTextSize } from "../../responsiveText.js"
|
||||
import { Entity } from "./entity.js"
|
||||
import { Reader } from "@elyukai/utils/reader"
|
||||
import {
|
||||
responsiveTranslateR,
|
||||
type StdEnv,
|
||||
type StdReader,
|
||||
} from "../../reader.js"
|
||||
|
||||
/**
|
||||
* A parameter that is designed to be modifiable.
|
||||
@@ -13,77 +14,18 @@ export enum ModifiableParameter {
|
||||
Range,
|
||||
}
|
||||
|
||||
const translationKeyForNonModifiableSuffix = {
|
||||
[Entity.Spell]: {
|
||||
[ModifiableParameter.CastingTime]:
|
||||
" (you cannot use a modification on this spell’s casting time)",
|
||||
[ModifiableParameter.Cost]:
|
||||
" (you cannot use a modification on this spell’s cost)",
|
||||
[ModifiableParameter.Range]:
|
||||
" (you cannot use a modification on this spell’s range)",
|
||||
},
|
||||
[Entity.Ritual]: {
|
||||
[ModifiableParameter.CastingTime]:
|
||||
" (you cannot use a modification on this ritual’s ritual time)",
|
||||
[ModifiableParameter.Cost]:
|
||||
" (you cannot use a modification on this ritual’s cost)",
|
||||
[ModifiableParameter.Range]:
|
||||
" (you cannot use a modification on this ritual’s range)",
|
||||
},
|
||||
[Entity.LiturgicalChant]: {
|
||||
[ModifiableParameter.CastingTime]:
|
||||
" (you cannot use a modification on this chant’s liturgical time)",
|
||||
[ModifiableParameter.Cost]:
|
||||
" (you cannot use a modification on this chant’s cost)",
|
||||
[ModifiableParameter.Range]:
|
||||
" (you cannot use a modification on this chant’s range)",
|
||||
},
|
||||
[Entity.Ceremony]: {
|
||||
[ModifiableParameter.CastingTime]:
|
||||
" (you cannot use a modification on this ceremony’s ceremonial time)",
|
||||
[ModifiableParameter.Cost]:
|
||||
" (you cannot use a modification on this ceremony’s cost)",
|
||||
[ModifiableParameter.Range]:
|
||||
" (you cannot use a modification on this ceremony’s range)",
|
||||
},
|
||||
[Entity.Cantrip]: undefined,
|
||||
[Entity.Blessing]: undefined,
|
||||
} satisfies {
|
||||
[entityKey in Entity]:
|
||||
| {
|
||||
[paramKey in ModifiableParameter]: keyof Translations
|
||||
}
|
||||
| undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the suffix for the text of a non-modifiable parameter that indicates
|
||||
* that the parameter cannot be modified.
|
||||
*/
|
||||
export const getNonModifiableSuffixTranslation = (
|
||||
translate: Translate,
|
||||
entity: Entity,
|
||||
export const appendNonModifiableSuffix = (
|
||||
param: ModifiableParameter,
|
||||
responsiveTextSize: ResponsiveTextSize,
|
||||
): string =>
|
||||
responsive(
|
||||
responsiveTextSize,
|
||||
() =>
|
||||
mapNullable(translationKeyForNonModifiableSuffix[entity]?.[param], key =>
|
||||
translate(key),
|
||||
) ?? "",
|
||||
() => {
|
||||
switch (entity) {
|
||||
case Entity.Spell:
|
||||
case Entity.Ritual:
|
||||
case Entity.LiturgicalChant:
|
||||
case Entity.Ceremony:
|
||||
return translate(" (cannot modify)")
|
||||
case Entity.Cantrip:
|
||||
case Entity.Blessing:
|
||||
return ""
|
||||
default:
|
||||
return assertExhaustive(entity)
|
||||
}
|
||||
},
|
||||
)
|
||||
base: string,
|
||||
): StdReader<string, "t" | "rts" | "nms"> =>
|
||||
Reader.asks((ctx: StdEnv<"nms">) => ctx.nonModifiableSuffix)
|
||||
.thenW(suffix =>
|
||||
suffix === undefined
|
||||
? Reader.of("")
|
||||
: responsiveTranslateR(suffix(param), " (cannot modify)"),
|
||||
)
|
||||
.map(suffix => base + suffix)
|
||||
|
||||
@@ -1,166 +1,140 @@
|
||||
import { Reader } from "@elyukai/utils/reader"
|
||||
import { mapNullable } from "@optolith/helpers/nullable"
|
||||
import { assertExhaustive } from "@optolith/helpers/typeSafety"
|
||||
import type {
|
||||
BlessingRange,
|
||||
CantripRange,
|
||||
CheckResultBasedRange,
|
||||
FixedRange,
|
||||
ModifiableRange,
|
||||
Range,
|
||||
RangeValue,
|
||||
SkillModificationLevel_ID,
|
||||
} from "optolith-database-schema/gen"
|
||||
import { type GetInstanceById } from "../../../../helpers/getTypes.js"
|
||||
import type { Translate, TranslateMap } from "../../../../helpers/translate.js"
|
||||
import {
|
||||
appendNoteIfRequested,
|
||||
replaceTextIfRequested,
|
||||
ResponsiveTextSize,
|
||||
getInstanceByIdR,
|
||||
modifiableBySpeedOptionalR,
|
||||
modifiableBySpeedR,
|
||||
translateFnR,
|
||||
translateMapR,
|
||||
translateR,
|
||||
type StdReader,
|
||||
} from "../../reader.js"
|
||||
import {
|
||||
appendNoteIfNeeded,
|
||||
replaceTextIfNeeded,
|
||||
} from "../../responsiveText.js"
|
||||
import { formatLength } from "../../units/length.js"
|
||||
import { formatCombinedLengthR, formatLengthR } from "../../units/length.js"
|
||||
import { MISSING_VALUE } from "../../unknown.js"
|
||||
import { getCheckResultBasedValueTranslation } from "./checkResultBased.js"
|
||||
import { Entity } from "./entity.js"
|
||||
import { renderCheckResultBasedValue } from "./checkResultBased.js"
|
||||
import { wrapIfMaximum } from "./isMinimumMaximum.js"
|
||||
import {
|
||||
getNonModifiableSuffixTranslation,
|
||||
appendNonModifiableSuffix,
|
||||
ModifiableParameter,
|
||||
} from "./nonModifiableSuffix.js"
|
||||
import { getModifiableBySpeed, Speed } from "./speed.js"
|
||||
|
||||
const getModifiableRangeTranslation = (
|
||||
getInstanceById: GetInstanceById<"SkillModificationLevel">,
|
||||
translate: Translate,
|
||||
speed: Speed,
|
||||
responsiveTextSize: ResponsiveTextSize,
|
||||
value: ModifiableRange,
|
||||
) =>
|
||||
mapNullable(
|
||||
getInstanceById("SkillModificationLevel", value.initial_modification_level),
|
||||
modificationLevel => {
|
||||
const range = getModifiableBySpeed(speed, "range", modificationLevel)
|
||||
|
||||
if (range === 1) {
|
||||
return translate("Touch")
|
||||
}
|
||||
|
||||
return formatLength(translate, responsiveTextSize, "Steps", range)
|
||||
},
|
||||
) ?? MISSING_VALUE
|
||||
|
||||
const getSightTranslation = (translate: Translate) => translate("Sight")
|
||||
|
||||
const getSelfTranslation = (translate: Translate) => translate("Self")
|
||||
|
||||
const getGlobalTranslation = (translate: Translate) => translate("Global")
|
||||
|
||||
const getTouchTranslation = (
|
||||
translate: Translate,
|
||||
entity: Entity,
|
||||
responsiveTextSize: ResponsiveTextSize,
|
||||
) =>
|
||||
translate("Touch") +
|
||||
getNonModifiableSuffixTranslation(
|
||||
translate,
|
||||
entity,
|
||||
ModifiableParameter.Range,
|
||||
responsiveTextSize,
|
||||
const deriveModifiableRange = (
|
||||
modificationLevelId: SkillModificationLevel_ID,
|
||||
): StdReader<
|
||||
{ value: number; translation: string | undefined } | undefined,
|
||||
"s" | "tm" | "ibi",
|
||||
"SkillModificationLevel"
|
||||
> =>
|
||||
getInstanceByIdR<"SkillModificationLevel">().thenW(
|
||||
getInstanceById =>
|
||||
mapNullable(
|
||||
getInstanceById("SkillModificationLevel", modificationLevelId),
|
||||
modificationLevel =>
|
||||
modifiableBySpeedR("range", modificationLevel).thenW(value =>
|
||||
translateMapR(modificationLevel.translations).thenW(translation =>
|
||||
translation === undefined
|
||||
? Reader.of({ value, translation: undefined })
|
||||
: modifiableBySpeedOptionalR("range", translation).map(
|
||||
valueTranslation => ({
|
||||
value,
|
||||
translation: valueTranslation,
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
) ?? Reader.of(undefined),
|
||||
)
|
||||
|
||||
const getFixedRangeTranslation = (
|
||||
translate: Translate,
|
||||
entity: Entity,
|
||||
responsiveTextSize: ResponsiveTextSize,
|
||||
value: FixedRange,
|
||||
) =>
|
||||
formatLength(translate, responsiveTextSize, value.unit.kind, value.value) +
|
||||
getNonModifiableSuffixTranslation(
|
||||
translate,
|
||||
entity,
|
||||
ModifiableParameter.Range,
|
||||
responsiveTextSize,
|
||||
const wrapIfRadius = (is_radius: boolean | undefined, text: string) =>
|
||||
translateFnR.map(translate =>
|
||||
is_radius === true ? `${text} ${translate("Radius")}` : text,
|
||||
)
|
||||
|
||||
const wrapIfRadius = (
|
||||
translate: Translate,
|
||||
is_radius: boolean | undefined,
|
||||
text: string,
|
||||
) => (is_radius === true ? `${text} ${translate("Radius")}` : text)
|
||||
|
||||
const getCheckResultBasedRangeTranslation = (
|
||||
translate: Translate,
|
||||
entity: Entity,
|
||||
responsiveTextSize: ResponsiveTextSize,
|
||||
value: CheckResultBasedRange,
|
||||
) => {
|
||||
const range = formatLength(
|
||||
translate,
|
||||
responsiveTextSize,
|
||||
value.unit.kind,
|
||||
getCheckResultBasedValueTranslation(translate, value),
|
||||
)
|
||||
|
||||
const rangeWrappedIfRadius = wrapIfRadius(translate, value.is_radius, range)
|
||||
|
||||
const rangeWrappedIfRadiusAndIfMaximum = wrapIfMaximum(
|
||||
translate,
|
||||
responsiveTextSize,
|
||||
value.is_maximum,
|
||||
rangeWrappedIfRadius,
|
||||
)
|
||||
|
||||
return (
|
||||
rangeWrappedIfRadiusAndIfMaximum +
|
||||
getNonModifiableSuffixTranslation(
|
||||
translate,
|
||||
entity,
|
||||
ModifiableParameter.Range,
|
||||
responsiveTextSize,
|
||||
const renderModifiableRange = (value: ModifiableRange) =>
|
||||
deriveModifiableRange(value.initial_modification_level)
|
||||
.thenW(range =>
|
||||
range === undefined
|
||||
? Reader.of(MISSING_VALUE)
|
||||
: typeof range === "string"
|
||||
? Reader.of(range)
|
||||
: formatLengthR("Steps", range.value),
|
||||
)
|
||||
)
|
||||
}
|
||||
.then(text => wrapIfRadius(value.is_radius, text))
|
||||
.then(text => wrapIfMaximum(value.is_maximum, text))
|
||||
|
||||
const renderFixedRange = (value: FixedRange) =>
|
||||
formatCombinedLengthR(value)
|
||||
.then(text => wrapIfRadius(value.is_radius, text))
|
||||
.then(text => wrapIfMaximum(value.is_maximum, text))
|
||||
|
||||
const getCheckResultBasedRangeTranslation = (value: CheckResultBasedRange) =>
|
||||
renderCheckResultBasedValue(value)
|
||||
.thenW(text => formatLengthR(value.unit, text))
|
||||
.then(text => wrapIfRadius(value.is_radius, text))
|
||||
.then(text => wrapIfMaximum(value.is_maximum, text))
|
||||
|
||||
/**
|
||||
* Returns the text for the range of an activatable skill.
|
||||
* Returns the text for the non-modifiable range of an activatable skill.
|
||||
*/
|
||||
export const getRangeValueTranslation = (
|
||||
getInstanceById: GetInstanceById<"SkillModificationLevel">,
|
||||
translate: Translate,
|
||||
speed: Speed,
|
||||
responsiveTextSize: ResponsiveTextSize,
|
||||
entity: Entity,
|
||||
value: RangeValue,
|
||||
) => {
|
||||
export const renderNonModifiableRange = (
|
||||
value:
|
||||
| {
|
||||
kind: "Sight"
|
||||
}
|
||||
| {
|
||||
kind: "Self"
|
||||
}
|
||||
| {
|
||||
kind: "Global"
|
||||
}
|
||||
| {
|
||||
kind: "Touch"
|
||||
}
|
||||
| {
|
||||
kind: "Fixed"
|
||||
Fixed: FixedRange
|
||||
}
|
||||
| {
|
||||
kind: "CheckResultBased"
|
||||
CheckResultBased: CheckResultBasedRange
|
||||
},
|
||||
shouldAppendNonModifiableSuffix: boolean,
|
||||
): StdReader<string, "t" | "tm" | "rts" | "nms"> => {
|
||||
const appendNonModifiableSuffixIfNeeded = shouldAppendNonModifiableSuffix
|
||||
? (text: string) =>
|
||||
appendNonModifiableSuffix(ModifiableParameter.Range, text)
|
||||
: Reader.of
|
||||
|
||||
switch (value.kind) {
|
||||
case "Modifiable":
|
||||
return getModifiableRangeTranslation(
|
||||
getInstanceById,
|
||||
translate,
|
||||
speed,
|
||||
responsiveTextSize,
|
||||
value.Modifiable,
|
||||
)
|
||||
case "Sight":
|
||||
return getSightTranslation(translate)
|
||||
return translateR("Sight")
|
||||
case "Self":
|
||||
return getSelfTranslation(translate)
|
||||
return translateR("Self")
|
||||
case "Global":
|
||||
return getGlobalTranslation(translate)
|
||||
return translateR("Global")
|
||||
case "Touch":
|
||||
return getTouchTranslation(translate, entity, responsiveTextSize)
|
||||
return translateR("Touch").thenW(appendNonModifiableSuffixIfNeeded)
|
||||
case "Fixed": {
|
||||
return getFixedRangeTranslation(
|
||||
translate,
|
||||
entity,
|
||||
responsiveTextSize,
|
||||
value.Fixed,
|
||||
return renderFixedRange(value.Fixed).thenW(
|
||||
appendNonModifiableSuffixIfNeeded,
|
||||
)
|
||||
}
|
||||
case "CheckResultBased":
|
||||
return getCheckResultBasedRangeTranslation(
|
||||
translate,
|
||||
entity,
|
||||
responsiveTextSize,
|
||||
value.CheckResultBased,
|
||||
return getCheckResultBasedRangeTranslation(value.CheckResultBased).then(
|
||||
appendNonModifiableSuffixIfNeeded,
|
||||
)
|
||||
default:
|
||||
return assertExhaustive(value)
|
||||
@@ -170,90 +144,39 @@ export const getRangeValueTranslation = (
|
||||
/**
|
||||
* Returns the text for the range of an activatable skill.
|
||||
*/
|
||||
export const getTextForActivatableSkillRange = (
|
||||
getInstanceById: GetInstanceById<"SkillModificationLevel">,
|
||||
translate: Translate,
|
||||
translateMap: TranslateMap,
|
||||
speed: Speed,
|
||||
responsiveTextSize: ResponsiveTextSize,
|
||||
entity: Entity,
|
||||
value: Range,
|
||||
): string => {
|
||||
const rangeValue = getRangeValueTranslation(
|
||||
getInstanceById,
|
||||
translate,
|
||||
speed,
|
||||
responsiveTextSize,
|
||||
entity,
|
||||
value.value,
|
||||
)
|
||||
|
||||
const withReplacement = replaceTextIfRequested(
|
||||
"replacement",
|
||||
value.translations,
|
||||
translateMap,
|
||||
responsiveTextSize,
|
||||
rangeValue,
|
||||
)
|
||||
|
||||
return appendNoteIfRequested(
|
||||
"note",
|
||||
value.translations,
|
||||
translateMap,
|
||||
responsiveTextSize,
|
||||
withReplacement,
|
||||
)
|
||||
}
|
||||
|
||||
const getTextForTinyActivatableRange = (
|
||||
translate: Translate,
|
||||
responsiveTextSize: ResponsiveTextSize,
|
||||
entity: Entity,
|
||||
value: CantripRange | BlessingRange,
|
||||
): string => {
|
||||
export const renderRangeValue = (
|
||||
value: RangeValue,
|
||||
shouldAppendNonModifiableSuffix: boolean,
|
||||
): StdReader<
|
||||
string,
|
||||
"t" | "tm" | "rts" | "s" | "nms" | "ibi",
|
||||
"SkillModificationLevel"
|
||||
> => {
|
||||
switch (value.kind) {
|
||||
case "Modifiable":
|
||||
return renderModifiableRange(value.Modifiable)
|
||||
case "Sight":
|
||||
case "Self":
|
||||
return getSelfTranslation(translate)
|
||||
case "Global":
|
||||
case "Touch":
|
||||
return getTouchTranslation(translate, entity, responsiveTextSize)
|
||||
case "Fixed":
|
||||
return getFixedRangeTranslation(
|
||||
translate,
|
||||
entity,
|
||||
responsiveTextSize,
|
||||
value.Fixed,
|
||||
)
|
||||
case "CheckResultBased":
|
||||
return renderNonModifiableRange(value, shouldAppendNonModifiableSuffix)
|
||||
default:
|
||||
return assertExhaustive(value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the text for the range of a cantrip.
|
||||
* Returns the text for the range of an activatable skill.
|
||||
*/
|
||||
export const getTextForCantripRange = (
|
||||
translate: Translate,
|
||||
responsiveTextSize: ResponsiveTextSize,
|
||||
value: CantripRange,
|
||||
): string =>
|
||||
getTextForTinyActivatableRange(
|
||||
translate,
|
||||
responsiveTextSize,
|
||||
Entity.Cantrip,
|
||||
value,
|
||||
)
|
||||
|
||||
/**
|
||||
* Returns the text for the range of a blessing.
|
||||
*/
|
||||
export const getTextForBlessingRange = (
|
||||
translate: Translate,
|
||||
responsiveTextSize: ResponsiveTextSize,
|
||||
value: BlessingRange,
|
||||
): string =>
|
||||
getTextForTinyActivatableRange(
|
||||
translate,
|
||||
responsiveTextSize,
|
||||
Entity.Blessing,
|
||||
value,
|
||||
)
|
||||
export const renderRange = (
|
||||
value: Range,
|
||||
): 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,59 +1,51 @@
|
||||
import { Reader } from "@elyukai/utils/reader"
|
||||
import { mapNullable } from "@optolith/helpers/nullable"
|
||||
import { assertExhaustive } from "@optolith/helpers/typeSafety"
|
||||
import type {
|
||||
AffectedTargetCategories,
|
||||
SpecificAffectedTargetCategoryIdentifier,
|
||||
TargetCategory_ID,
|
||||
} from "optolith-database-schema/gen"
|
||||
import { type GetInstanceById } from "../../../../helpers/getTypes.js"
|
||||
import { LocaleEnvironment } from "../../../../helpers/locale.js"
|
||||
import { EntityDescriptionSection } from "../../../../index.js"
|
||||
import {
|
||||
getInstanceByIdR,
|
||||
translateMapR,
|
||||
translateR,
|
||||
type StdReader,
|
||||
} from "../../reader.js"
|
||||
import { MISSING_VALUE } from "../../unknown.js"
|
||||
import { appendInParensIfNotEmpty } from "./parensIf.js"
|
||||
|
||||
const getSelfTranslation = (locale: LocaleEnvironment) =>
|
||||
locale.translate("Self")
|
||||
|
||||
const getZoneTranslation = (locale: LocaleEnvironment) =>
|
||||
locale.translate("Zone")
|
||||
|
||||
const getLiturgicalChantsAndCeremoniesTranslation = (
|
||||
locale: LocaleEnvironment,
|
||||
) => locale.translate("Liturgical Chants and Ceremonies")
|
||||
|
||||
const getSpellworksTranslation = (locale: LocaleEnvironment) =>
|
||||
locale.translate("Spellworks")
|
||||
|
||||
const getCantripsTranslation = (locale: LocaleEnvironment) =>
|
||||
locale.translate("Cantrips")
|
||||
|
||||
const getPredefinedTranslation = (
|
||||
getInstanceById: GetInstanceById<"TargetCategory">,
|
||||
locale: LocaleEnvironment,
|
||||
id: string,
|
||||
) =>
|
||||
mapNullable(
|
||||
locale.translateMap(getInstanceById("TargetCategory", id)?.translations),
|
||||
translation => translation.name,
|
||||
) ?? MISSING_VALUE
|
||||
const renderPredefined = (targetCategoryId: TargetCategory_ID) =>
|
||||
getInstanceByIdR<"TargetCategory">()
|
||||
.thenW(
|
||||
getInstanceById =>
|
||||
mapNullable(
|
||||
getInstanceById("TargetCategory", targetCategoryId),
|
||||
targetCategory =>
|
||||
translateMapR(targetCategory.translations).map(
|
||||
translation => translation?.name,
|
||||
),
|
||||
) ?? Reader.of(undefined),
|
||||
)
|
||||
.map(translation => translation ?? MISSING_VALUE)
|
||||
|
||||
const getTargetCategoryTranslationByType = (
|
||||
getInstanceById: GetInstanceById<"TargetCategory">,
|
||||
locale: LocaleEnvironment,
|
||||
id: SpecificAffectedTargetCategoryIdentifier,
|
||||
) => {
|
||||
): StdReader<string, "t" | "tm" | "ibi", "TargetCategory"> => {
|
||||
switch (id.kind) {
|
||||
case "Self":
|
||||
return getSelfTranslation(locale)
|
||||
return translateR("Self")
|
||||
case "Zone":
|
||||
return getZoneTranslation(locale)
|
||||
return translateR("Zone")
|
||||
case "LiturgicalChantsAndCeremonies":
|
||||
return getLiturgicalChantsAndCeremoniesTranslation(locale)
|
||||
return translateR("Liturgical Chants and Ceremonies")
|
||||
case "Spellworks":
|
||||
return getSpellworksTranslation(locale)
|
||||
return translateR("Spellworks")
|
||||
case "Cantrips":
|
||||
return getCantripsTranslation(locale)
|
||||
return translateR("Cantrips")
|
||||
case "Predefined":
|
||||
return getPredefinedTranslation(getInstanceById, locale, id.Predefined)
|
||||
return renderPredefined(id.Predefined)
|
||||
default:
|
||||
return assertExhaustive(id)
|
||||
}
|
||||
@@ -62,21 +54,21 @@ const getTargetCategoryTranslationByType = (
|
||||
/**
|
||||
* Get the text for the target category.
|
||||
*/
|
||||
export const getTargetCategoryTranslation = (
|
||||
getInstanceById: GetInstanceById<"TargetCategory">,
|
||||
locale: LocaleEnvironment,
|
||||
export const renderTargetCategory = (
|
||||
values: AffectedTargetCategories,
|
||||
): EntityDescriptionSection => ({
|
||||
label: locale.translate("Target Category"),
|
||||
value:
|
||||
values.length === 0
|
||||
? locale.translate("all")
|
||||
: values
|
||||
.map(({ id, translations }) =>
|
||||
appendInParensIfNotEmpty(
|
||||
locale.translateMap(translations)?.note,
|
||||
getTargetCategoryTranslationByType(getInstanceById, locale, id),
|
||||
): StdReader<EntityDescriptionSection, "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,
|
||||
),
|
||||
)
|
||||
.join(", "),
|
||||
})
|
||||
),
|
||||
).map(texts => texts.join(", "))
|
||||
).map(value => ({ label, value })),
|
||||
)
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import type { ImprovementCost } from "optolith-database-schema/gen"
|
||||
import { Translate } from "../../../helpers/translate.js"
|
||||
import { EntityDescriptionSection } from "../../../index.js"
|
||||
import { translateR, type StdReader } from "../reader.js"
|
||||
|
||||
/**
|
||||
* Returns the improvement cost as an inline library property.
|
||||
*/
|
||||
export const createImprovementCost = (
|
||||
translate: Translate,
|
||||
export const renderImprovementCost = (
|
||||
improvementCost: ImprovementCost,
|
||||
): EntityDescriptionSection => ({
|
||||
label: translate("Improvement Cost"),
|
||||
value: improvementCost.kind,
|
||||
})
|
||||
): StdReader<EntityDescriptionSection, "t"> =>
|
||||
translateR("Improvement Cost").map(label => ({
|
||||
label,
|
||||
value: improvementCost.kind,
|
||||
}))
|
||||
|
||||
@@ -1,145 +1,135 @@
|
||||
import { Reader } from "@elyukai/utils/reader"
|
||||
import { assertExhaustive } from "@optolith/helpers/typeSafety"
|
||||
import type {
|
||||
SkillCheck,
|
||||
SkillCheckPenalty,
|
||||
} from "optolith-database-schema/gen"
|
||||
import { type GetInstanceById } from "../../../helpers/getTypes.js"
|
||||
import { Translate, TranslateMap } from "../../../helpers/translate.js"
|
||||
import { EntityDescriptionSection, type IdMap } from "../../../index.js"
|
||||
import { responsive, ResponsiveTextSize } from "../responsiveText.js"
|
||||
import { type IdMap } from "../../../index.js"
|
||||
import {
|
||||
getInstanceByIdR,
|
||||
responsiveR,
|
||||
responsiveThenR,
|
||||
responsiveTranslateR,
|
||||
translateMapR,
|
||||
translateR,
|
||||
type StdReader,
|
||||
} from "../reader.js"
|
||||
import { MISSING_VALUE } from "../unknown.js"
|
||||
|
||||
/**
|
||||
* Returns the skill check as an inline library property.
|
||||
* Renders a skill check.
|
||||
*/
|
||||
export const getTextForCheck = (
|
||||
deps: {
|
||||
translate: Translate
|
||||
translateMap: TranslateMap
|
||||
getInstanceById: GetInstanceById<"Attribute">
|
||||
},
|
||||
export const renderSkillCheck = (
|
||||
check: SkillCheck,
|
||||
checkPenalty?: {
|
||||
value: SkillCheckPenalty | undefined
|
||||
responsiveText: ResponsiveTextSize
|
||||
getInstanceById: GetInstanceById<"DerivedCharacteristic">
|
||||
idMap: IdMap
|
||||
},
|
||||
): EntityDescriptionSection => ({
|
||||
label: deps.translate("Check"),
|
||||
value:
|
||||
check
|
||||
): StdReader<
|
||||
{ label: string; value: string },
|
||||
"t" | "tm" | "ibi",
|
||||
"Attribute"
|
||||
> =>
|
||||
Reader.asks(({ translate, translateMap, getInstanceById }) => ({
|
||||
label: translate("Check"),
|
||||
value: check
|
||||
.map(
|
||||
id =>
|
||||
deps.translateMap(deps.getInstanceById("Attribute", id)?.translations)
|
||||
?.abbreviation ?? "??",
|
||||
translateMap(getInstanceById("Attribute", id)?.translations)
|
||||
?.abbreviation ?? MISSING_VALUE,
|
||||
)
|
||||
.join("/") +
|
||||
(() => {
|
||||
if (checkPenalty?.value === undefined) {
|
||||
return ""
|
||||
}
|
||||
.join("/"),
|
||||
}))
|
||||
|
||||
const { responsiveText } = checkPenalty
|
||||
const renderSkillCheckPenalty = (
|
||||
idMap: IdMap,
|
||||
penalty: SkillCheckPenalty,
|
||||
): StdReader<string, "t" | "tm" | "rts" | "ibi", "DerivedCharacteristic"> => {
|
||||
const getDerivedCharacteristicTranslation = (id: string) =>
|
||||
getInstanceByIdR<"DerivedCharacteristic">()
|
||||
.map(getInstanceById => getInstanceById("DerivedCharacteristic", id))
|
||||
.thenW(dc =>
|
||||
dc === undefined
|
||||
? Reader.of(undefined)
|
||||
: translateMapR(dc.translations).thenW(translation =>
|
||||
translation === undefined
|
||||
? Reader.of(undefined)
|
||||
: responsiveR(
|
||||
() => translation.name,
|
||||
() => translation.abbreviation,
|
||||
),
|
||||
),
|
||||
)
|
||||
.map(translation => translation ?? MISSING_VALUE)
|
||||
|
||||
const getDerivedCharacteristicTranslation = (id: string) =>
|
||||
deps.translateMap(
|
||||
checkPenalty.getInstanceById("DerivedCharacteristic", id)
|
||||
?.translations,
|
||||
)
|
||||
const getSpiritTranslation = () =>
|
||||
getDerivedCharacteristicTranslation(idMap.DerivedCharacteristic.Spirit)
|
||||
|
||||
const getSpiritTranslation = () =>
|
||||
getDerivedCharacteristicTranslation(
|
||||
checkPenalty.idMap.DerivedCharacteristic.Spirit,
|
||||
)
|
||||
const getToughnessTranslation = () =>
|
||||
getDerivedCharacteristicTranslation(idMap.DerivedCharacteristic.Toughness)
|
||||
|
||||
const getToughnessTranslation = () =>
|
||||
getDerivedCharacteristicTranslation(
|
||||
checkPenalty.idMap.DerivedCharacteristic.Toughness,
|
||||
)
|
||||
switch (penalty.kind) {
|
||||
case "Spirit":
|
||||
return getSpiritTranslation()
|
||||
|
||||
const penalty = (() => {
|
||||
switch (checkPenalty.value.kind) {
|
||||
case "Spirit": {
|
||||
const translation = getSpiritTranslation()
|
||||
return translation === undefined
|
||||
? ""
|
||||
: responsive(
|
||||
responsiveText,
|
||||
() => translation.name,
|
||||
() => translation.abbreviation,
|
||||
)
|
||||
}
|
||||
case "HalfOfSpirit":
|
||||
return getSpiritTranslation().map(translation => `${translation}/2`)
|
||||
|
||||
case "HalfOfSpirit": {
|
||||
const translation = getSpiritTranslation()
|
||||
return translation === undefined
|
||||
? ""
|
||||
: responsive(
|
||||
responsiveText,
|
||||
() => `${translation.name}/2`,
|
||||
() => `${translation.abbreviation}/2`,
|
||||
)
|
||||
}
|
||||
case "Toughness":
|
||||
return getToughnessTranslation()
|
||||
|
||||
case "Toughness": {
|
||||
const translation = getToughnessTranslation()
|
||||
return translation === undefined
|
||||
? ""
|
||||
: responsive(
|
||||
responsiveText,
|
||||
() => `${translation.name}/2`,
|
||||
() => `${translation.abbreviation}/2`,
|
||||
)
|
||||
}
|
||||
case "HigherOfSpiritAndToughness":
|
||||
return getSpiritTranslation().thenW(spirit =>
|
||||
getToughnessTranslation().thenW(toughness =>
|
||||
responsiveThenR(
|
||||
() =>
|
||||
translateR(
|
||||
"{$first} or {$second}, depending on which value is higher",
|
||||
{
|
||||
first: spirit,
|
||||
second: toughness,
|
||||
},
|
||||
),
|
||||
() => Reader.of(`${spirit}/${toughness}`),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
case "HigherOfSpiritAndToughness": {
|
||||
const spiritTranslation = getSpiritTranslation()
|
||||
const toughnessTranslation = getToughnessTranslation()
|
||||
return spiritTranslation === undefined ||
|
||||
toughnessTranslation === undefined
|
||||
? ""
|
||||
: responsive(
|
||||
responsiveText,
|
||||
() =>
|
||||
deps.translate(
|
||||
"{$first} or {$second}, depending on which value is higher",
|
||||
{
|
||||
first: spiritTranslation.abbreviation,
|
||||
second: toughnessTranslation.abbreviation,
|
||||
},
|
||||
),
|
||||
() =>
|
||||
`${spiritTranslation.abbreviation}/${toughnessTranslation.abbreviation}`,
|
||||
)
|
||||
}
|
||||
case "SummoningDifficulty":
|
||||
return responsiveTranslateR("Invocation Difficulty", "ID")
|
||||
|
||||
case "SummoningDifficulty":
|
||||
return responsive(
|
||||
responsiveText,
|
||||
() => deps.translate("Invocation Difficulty"),
|
||||
() => deps.translate("ID"),
|
||||
)
|
||||
case "CreationDifficulty":
|
||||
return responsiveTranslateR("Creation Difficulty", "CD")
|
||||
|
||||
case "CreationDifficulty":
|
||||
return responsive(
|
||||
responsiveText,
|
||||
() => deps.translate("Creation Difficulty"),
|
||||
() => deps.translate("CD"),
|
||||
)
|
||||
case "Object":
|
||||
return translateR("Object")
|
||||
|
||||
case "Object":
|
||||
return deps.translate("Object")
|
||||
default:
|
||||
return assertExhaustive(penalty)
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return assertExhaustive(checkPenalty.value)
|
||||
}
|
||||
})()
|
||||
|
||||
return responsive(
|
||||
responsiveText,
|
||||
() =>
|
||||
deps.translate(" (modified by {$modifier})", { modifier: penalty }),
|
||||
() => deps.translate(" (−{$modifier})", { modifier: penalty }),
|
||||
/**
|
||||
* Renders a skill check with a possible penalty.
|
||||
*/
|
||||
export const renderSkillCheckWithPenalty = (
|
||||
check: SkillCheck,
|
||||
checkPenalty: SkillCheckPenalty | undefined,
|
||||
idMap: IdMap,
|
||||
): StdReader<
|
||||
{ label: string; value: string },
|
||||
"t" | "tm" | "rts" | "ibi",
|
||||
"Attribute" | "DerivedCharacteristic"
|
||||
> =>
|
||||
checkPenalty === undefined
|
||||
? renderSkillCheck(check)
|
||||
: renderSkillCheck(check).thenW(checkText =>
|
||||
renderSkillCheckPenalty(idMap, checkPenalty)
|
||||
.then(penaltyText =>
|
||||
responsiveTranslateR(
|
||||
" (modified by {$modifier})",
|
||||
" (−{$modifier})",
|
||||
{ modifier: penaltyText },
|
||||
),
|
||||
)
|
||||
.map(penaltyText => ({
|
||||
...checkText,
|
||||
value: checkText.value + penaltyText,
|
||||
})),
|
||||
)
|
||||
})(),
|
||||
})
|
||||
|
||||
@@ -0,0 +1,355 @@
|
||||
import { isNotNullish } from "@elyukai/utils/nullable"
|
||||
import { Reader } from "@elyukai/utils/reader"
|
||||
import { assertExhaustive } from "@elyukai/utils/typeSafety"
|
||||
import type {
|
||||
EntityMap,
|
||||
FastSkillModificationLevelConfig,
|
||||
ResponsiveText,
|
||||
ResponsiveTextOptional,
|
||||
SkillModificationLevel,
|
||||
SlowSkillModificationLevelConfig,
|
||||
} from "optolith-database-schema/gen"
|
||||
import type { GetInstanceById } from "../../helpers/getTypes.js"
|
||||
import type {
|
||||
LocaleCompare,
|
||||
LocaleJoin,
|
||||
LocaleJoinType,
|
||||
} from "../../helpers/locale.js"
|
||||
import type {
|
||||
LocaleMap,
|
||||
Translate,
|
||||
TranslateMap,
|
||||
TranslationKeyMatchingParamsOfKey,
|
||||
TranslationKeysWithoutParams,
|
||||
TranslationParamsInArray,
|
||||
Translations,
|
||||
} from "../../helpers/translate.js"
|
||||
import type { ModifiableParameter } from "./rated/activatable/nonModifiableSuffix.js"
|
||||
import { Speed } from "./rated/activatable/speed.js"
|
||||
import { responsive, ResponsiveTextSize } from "./responsiveText.js"
|
||||
import { formatEnergy, type EnergyUnit } from "./units/energy.js"
|
||||
|
||||
/**
|
||||
* The standard set of environment properties for readers in this project.
|
||||
*/
|
||||
export type EnvMap<E extends keyof EntityMap = never> = {
|
||||
translate: Translate
|
||||
translateMap: TranslateMap
|
||||
localeJoin: LocaleJoin
|
||||
localeCompare: LocaleCompare
|
||||
responsiveTextSize: ResponsiveTextSize
|
||||
speed: Speed
|
||||
energyUnit: EnergyUnit
|
||||
nonModifiableSuffix?: (
|
||||
param: ModifiableParameter,
|
||||
) => TranslationKeysWithoutParams
|
||||
getInstanceById: GetInstanceById<E>
|
||||
}
|
||||
|
||||
type EnvMapAbbr = {
|
||||
t: "translate"
|
||||
tm: "translateMap"
|
||||
lj: "localeJoin"
|
||||
lc: "localeCompare"
|
||||
rts: "responsiveTextSize"
|
||||
s: "speed"
|
||||
eu: "energyUnit"
|
||||
nms: "nonModifiableSuffix"
|
||||
ibi: "getInstanceById"
|
||||
}
|
||||
|
||||
/**
|
||||
* The standard set of environment properties for readers in this project.
|
||||
*
|
||||
* The keys are abbreviated to keep type annotations short.
|
||||
*/
|
||||
export type StdEnv<
|
||||
in K extends keyof EnvMapAbbr = keyof EnvMapAbbr,
|
||||
in E extends keyof EntityMap = never,
|
||||
> = Pick<EnvMap<E>, EnvMapAbbr[K]>
|
||||
|
||||
/**
|
||||
* Shortcut for a reader with common environment properties.
|
||||
*
|
||||
* The keys are abbreviated to keep return type annotations short.
|
||||
*/
|
||||
export type StdReader<
|
||||
T,
|
||||
K extends keyof EnvMapAbbr,
|
||||
E extends keyof EntityMap = never,
|
||||
> = Reader<StdEnv<K, E>, T>
|
||||
|
||||
// Specialized constructors for common contexts
|
||||
|
||||
/**
|
||||
* Creates a value from a translation key.
|
||||
*/
|
||||
export const translateR = <K extends keyof Translations>(
|
||||
key: K,
|
||||
...rest: TranslationParamsInArray<K>
|
||||
): Reader<{ translate: Translate }, string> =>
|
||||
Reader.asks(env => env.translate(key, ...rest))
|
||||
|
||||
/**
|
||||
* Returns the `translate` function from the context.
|
||||
*/
|
||||
export const translateFnR: Reader<{ translate: Translate }, Translate> =
|
||||
Reader.asks(env => env.translate)
|
||||
|
||||
/**
|
||||
* Takes the appropriate translation from a locale map.
|
||||
*/
|
||||
export const translateMapR = <T>(
|
||||
map: LocaleMap<T> | undefined,
|
||||
): Reader<{ translateMap: TranslateMap }, T | undefined> =>
|
||||
Reader.asks(env => env.translateMap(map))
|
||||
|
||||
/**
|
||||
* Returns the `translateMap` function from the context.
|
||||
*/
|
||||
export const translateMapFnR: Reader<
|
||||
{ translateMap: TranslateMap },
|
||||
TranslateMap
|
||||
> = Reader.asks(env => env.translateMap)
|
||||
|
||||
/**
|
||||
* Joins a list of strings according to the locale’s rules for the given type.
|
||||
*/
|
||||
export const localeJoinR = (
|
||||
arr: string[],
|
||||
type: LocaleJoinType,
|
||||
): Reader<{ localeJoin: LocaleJoin }, string> =>
|
||||
Reader.asks(env => env.localeJoin(arr, type))
|
||||
|
||||
/**
|
||||
* Returns a function to retrieve an instance from the database by its entity name and ID.
|
||||
*/
|
||||
export const getInstanceByIdR = <E extends keyof EntityMap = never>(): Reader<
|
||||
{ getInstanceById: GetInstanceById<E> },
|
||||
GetInstanceById<E>
|
||||
> => Reader.asks(env => env.getInstanceById)
|
||||
|
||||
/**
|
||||
* Joins a list of strings according to the locale’s rules for the given type.
|
||||
*/
|
||||
export const responsiveLocaleJoinR = (
|
||||
arr: string[],
|
||||
type: LocaleJoinType,
|
||||
): Reader<
|
||||
{ localeJoin: LocaleJoin; responsiveTextSize: ResponsiveTextSize },
|
||||
string
|
||||
> =>
|
||||
Reader.asks(({ localeJoin, responsiveTextSize }) =>
|
||||
responsive(
|
||||
responsiveTextSize,
|
||||
() => localeJoin(arr, type),
|
||||
() => {
|
||||
switch (type) {
|
||||
case "conjunction":
|
||||
return " + "
|
||||
case "disjunction":
|
||||
return " / "
|
||||
case "unit":
|
||||
return " "
|
||||
default:
|
||||
return assertExhaustive(type)
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
/**
|
||||
* Compares two strings according to the locale’s sorting rules.
|
||||
*/
|
||||
export const localeCompareR: Reader<
|
||||
{ localeCompare: LocaleCompare },
|
||||
LocaleCompare
|
||||
> = Reader.asks(env => env.localeCompare)
|
||||
|
||||
/**
|
||||
* Creates a responsive value from two functions that return the value for the full and compressed version, respectively.
|
||||
*/
|
||||
export const responsiveR = <T>(
|
||||
full: () => T,
|
||||
compressed: () => T,
|
||||
): Reader<{ responsiveTextSize: ResponsiveTextSize }, T> =>
|
||||
Reader.asks(({ responsiveTextSize }) =>
|
||||
responsive(responsiveTextSize, full, compressed),
|
||||
)
|
||||
|
||||
/**
|
||||
* Creates a responsive value from two functions that return the value for the full and compressed version, respectively.
|
||||
*/
|
||||
export const responsiveThenR = <T, RF extends object, RC extends object>(
|
||||
full: () => Reader<RF, T>,
|
||||
compressed: () => Reader<RC, T>,
|
||||
): Reader<{ responsiveTextSize: ResponsiveTextSize } & RF & RC, T> =>
|
||||
Reader.asks(env => {
|
||||
switch (env.responsiveTextSize) {
|
||||
case ResponsiveTextSize.Full:
|
||||
return full().run(env)
|
||||
case ResponsiveTextSize.Compressed:
|
||||
return compressed().run(env)
|
||||
default:
|
||||
return assertExhaustive(env.responsiveTextSize)
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Creates a responsive value from a translation key that has a full and a compressed version.
|
||||
*/
|
||||
export const responsiveTranslateR = <
|
||||
K extends keyof Translations,
|
||||
K2 extends TranslationKeyMatchingParamsOfKey<K>,
|
||||
>(
|
||||
fullKey: K,
|
||||
compressedKey: K2,
|
||||
...rest: TranslationParamsInArray<K> & TranslationParamsInArray<K2>
|
||||
): Reader<
|
||||
{ translate: Translate; responsiveTextSize: ResponsiveTextSize },
|
||||
string
|
||||
> =>
|
||||
Reader.asks(({ translate, responsiveTextSize }) =>
|
||||
responsive(
|
||||
responsiveTextSize,
|
||||
() => translate(fullKey, ...(rest as TranslationParamsInArray<K>)),
|
||||
() => translate(compressedKey, ...(rest as TranslationParamsInArray<K2>)),
|
||||
),
|
||||
)
|
||||
|
||||
/**
|
||||
* Creates a responsive value from a responsive text.
|
||||
*/
|
||||
export const responsiveTextR = (
|
||||
responsiveText: ResponsiveText,
|
||||
): Reader<{ responsiveTextSize: ResponsiveTextSize }, string> =>
|
||||
Reader.asks(({ responsiveTextSize }) =>
|
||||
responsive(
|
||||
responsiveTextSize,
|
||||
() => responsiveText.full,
|
||||
() => responsiveText.compressed,
|
||||
),
|
||||
)
|
||||
|
||||
/**
|
||||
* Creates a responsive value from a responsive text with an optional compressed variant.
|
||||
*/
|
||||
export const responsiveTextOptionalR = (
|
||||
responsiveText: ResponsiveTextOptional,
|
||||
): Reader<{ responsiveTextSize: ResponsiveTextSize }, string | undefined> =>
|
||||
Reader.asks(({ responsiveTextSize }) =>
|
||||
responsive(
|
||||
responsiveTextSize,
|
||||
() => responsiveText.full,
|
||||
() => responsiveText.compressed,
|
||||
),
|
||||
)
|
||||
|
||||
/**
|
||||
* Formats the given energy cost value with the appropriate unit.
|
||||
*/
|
||||
export const formatEnergyR = (
|
||||
value: string | number,
|
||||
): Reader<{ translate: Translate; energyUnit: EnergyUnit }, string> =>
|
||||
Reader.asks(({ translate, energyUnit }) =>
|
||||
formatEnergy(translate, energyUnit, value),
|
||||
)
|
||||
|
||||
/**
|
||||
* Formats the given energy cost value with the appropriate unit based on the entity type.
|
||||
*/
|
||||
export const formatEnergyFnR: Reader<
|
||||
{ translate: Translate; energyUnit: EnergyUnit },
|
||||
(value: string | number) => string
|
||||
> = Reader.asks(
|
||||
({ translate, energyUnit }) =>
|
||||
value =>
|
||||
formatEnergy(translate, energyUnit, value),
|
||||
)
|
||||
|
||||
type SpeedMap = {
|
||||
[Speed.Fast]: FastSkillModificationLevelConfig
|
||||
[Speed.Slow]: SlowSkillModificationLevelConfig
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a common value for a skill modification level depending on the speed.
|
||||
*/
|
||||
export const modifiableBySpeedR = <
|
||||
Fast extends object,
|
||||
Slow extends { [K_ in keyof Fast]: unknown },
|
||||
K extends keyof Fast,
|
||||
>(
|
||||
key: K,
|
||||
level: { fast: Fast; slow: Slow },
|
||||
): Reader<{ speed: Speed }, (Fast | Slow)[K]> =>
|
||||
Reader.asks(({ speed }) => {
|
||||
switch (speed) {
|
||||
case Speed.Fast:
|
||||
return level.fast[key] as (Fast | Slow)[K]
|
||||
case Speed.Slow:
|
||||
return level.slow[key] as (Fast | Slow)[K]
|
||||
default:
|
||||
return assertExhaustive(speed)
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Returns a common value for a skill modification level depending on the speed.
|
||||
*/
|
||||
export const modifiableBySpeedOptionalR = <
|
||||
Fast extends object,
|
||||
Slow extends { [K_ in keyof Fast]: unknown },
|
||||
K extends keyof Fast,
|
||||
>(
|
||||
key: K,
|
||||
level: { fast?: Fast; slow?: Slow },
|
||||
): Reader<{ speed: Speed }, (Fast | Slow)[K] | undefined> =>
|
||||
Reader.asks(({ speed }) => {
|
||||
switch (speed) {
|
||||
case Speed.Fast:
|
||||
return level.fast?.[key] as (Fast | Slow)[K] | undefined
|
||||
case Speed.Slow:
|
||||
return level.slow?.[key] as (Fast | Slow)[K] | undefined
|
||||
default:
|
||||
return assertExhaustive(speed)
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Returns a common value for a skill modification level depending on the speed.
|
||||
*/
|
||||
export const modifyBySpeedR: Reader<
|
||||
{ speed: Speed },
|
||||
<S extends Speed, K extends keyof SpeedMap[S]>(
|
||||
key: K,
|
||||
level: SkillModificationLevel,
|
||||
) => SpeedMap[S][K]
|
||||
> = Reader.asks(
|
||||
({ speed }) =>
|
||||
<S extends Speed, K extends keyof SpeedMap[S]>(
|
||||
key: K,
|
||||
level: SkillModificationLevel,
|
||||
) => {
|
||||
switch (speed) {
|
||||
case Speed.Fast:
|
||||
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]
|
||||
default:
|
||||
return assertExhaustive(speed)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
/**
|
||||
* Returns the default values wrapped in a reader if the value is nullish, otherwise applies the given function to the value and returns the result.
|
||||
*/
|
||||
export const mapNullableR =
|
||||
<T, U, R>(defaultValue: U, fn: (value: NonNullable<T>) => Reader<U, R>) =>
|
||||
(value: T) =>
|
||||
isNotNullish(value) ? fn(value) : Reader.of(defaultValue)
|
||||
@@ -1,12 +1,19 @@
|
||||
import { mapNullable } from "@optolith/helpers/nullable"
|
||||
import { identity } from "@elyukai/utils/function"
|
||||
import { Reader } from "@elyukai/utils/reader"
|
||||
import { assertExhaustive } from "@optolith/helpers/typeSafety"
|
||||
import type {
|
||||
ResponsiveText,
|
||||
ResponsiveTextOptional,
|
||||
ResponsiveTextReplace,
|
||||
} from "optolith-database-schema/gen"
|
||||
import { TranslateMap, type LocaleMap } from "../../helpers/translate.js"
|
||||
import { type LocaleMap } from "../../helpers/translate.js"
|
||||
import { appendInParensIfNotEmpty } from "./rated/activatable/parensIf.js"
|
||||
import {
|
||||
responsiveTextOptionalR,
|
||||
responsiveTextR,
|
||||
translateMapR,
|
||||
type StdEnv,
|
||||
} from "./reader.js"
|
||||
import { MISSING_VALUE } from "./unknown.js"
|
||||
|
||||
/**
|
||||
@@ -78,34 +85,38 @@ export const getResponsiveTextOptional = (
|
||||
* Replaces a text with a given value if a replacement is requested, otherwise
|
||||
* just return the given value.
|
||||
*/
|
||||
export const replaceTextIfRequested = <Key extends string>(
|
||||
key: Key,
|
||||
translation: LocaleMap<{ [K in Key]?: ResponsiveTextReplace }> | undefined,
|
||||
translateMap: TranslateMap,
|
||||
responsiveText: ResponsiveTextSize,
|
||||
export const replaceTextIfNeeded = (
|
||||
translations: LocaleMap<{ replacement?: ResponsiveTextReplace }> | undefined,
|
||||
valueToReplace: string,
|
||||
) =>
|
||||
mapNullable(translateMap(translation)?.[key], replacement =>
|
||||
getResponsiveText(replacement, responsiveText).replace(
|
||||
"$1",
|
||||
valueToReplace,
|
||||
),
|
||||
) ?? valueToReplace
|
||||
translateMapR(translations)
|
||||
.with<StdEnv<"tm" | "rts">>(identity)
|
||||
.then(translation => {
|
||||
if (translation?.replacement === undefined) {
|
||||
return Reader.of(valueToReplace)
|
||||
}
|
||||
|
||||
return responsiveTextR(translation.replacement).map(note =>
|
||||
note.replace("$1", valueToReplace),
|
||||
)
|
||||
})
|
||||
|
||||
/**
|
||||
* Appends a note to a given value if a note is requested, otherwise just return
|
||||
* the given value.
|
||||
*/
|
||||
export const appendNoteIfRequested = <Key extends string>(
|
||||
key: Key,
|
||||
translation: LocaleMap<{ [K in Key]?: ResponsiveTextOptional }> | undefined,
|
||||
translateMap: TranslateMap,
|
||||
responsiveText: ResponsiveTextSize,
|
||||
export const appendNoteIfNeeded = (
|
||||
translations: LocaleMap<{ note?: ResponsiveTextOptional }> | undefined,
|
||||
valueToAppendTo: string,
|
||||
) =>
|
||||
mapNullable(translateMap(translation)?.[key], note =>
|
||||
appendInParensIfNotEmpty(
|
||||
getResponsiveTextOptional(note, responsiveText),
|
||||
valueToAppendTo,
|
||||
),
|
||||
) ?? valueToAppendTo
|
||||
translateMapR(translations)
|
||||
.with<StdEnv<"tm" | "rts">>(identity)
|
||||
.then(translation => {
|
||||
if (translation?.note === undefined) {
|
||||
return Reader.of(valueToAppendTo)
|
||||
}
|
||||
|
||||
return responsiveTextOptionalR(translation.note).map(note =>
|
||||
appendInParensIfNotEmpty(note, valueToAppendTo),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { assertExhaustive } from "@optolith/helpers/typeSafety"
|
||||
import type { Translate, Translations } from "../../../helpers/translate.js"
|
||||
import { Entity } from "../rated/activatable/entity.js"
|
||||
|
||||
type EnergyUnit = "ArcaneEnergy" | "KarmaPoints"
|
||||
/**
|
||||
* The unit of an energy cost.
|
||||
*/
|
||||
export type EnergyUnit = "ArcaneEnergy" | "KarmaPoints"
|
||||
|
||||
const lengthUnitTranslationKeys = {
|
||||
ArcaneEnergy: "{$value} AE",
|
||||
@@ -23,25 +24,3 @@ export const formatEnergy = (
|
||||
|
||||
return translate(key, { value })
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the text for an energy unit that is based on the entity type.
|
||||
*/
|
||||
export const formatEnergyByEntity = (
|
||||
translate: Translate,
|
||||
entity: Entity,
|
||||
value: number | string,
|
||||
) => {
|
||||
switch (entity) {
|
||||
case Entity.Cantrip:
|
||||
case Entity.Spell:
|
||||
case Entity.Ritual:
|
||||
return formatEnergy(translate, "ArcaneEnergy", value)
|
||||
case Entity.Blessing:
|
||||
case Entity.LiturgicalChant:
|
||||
case Entity.Ceremony:
|
||||
return formatEnergy(translate, "KarmaPoints", value)
|
||||
default:
|
||||
return assertExhaustive(entity)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
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"
|
||||
|
||||
type LengthUnit = "Steps" | "Miles"
|
||||
@@ -28,11 +30,11 @@ const lengthUnitTranslationKeys = {
|
||||
export const formatLength = (
|
||||
translate: Translate,
|
||||
responsiveTextSize: ResponsiveTextSize,
|
||||
unit: LengthUnit,
|
||||
unit: LengthUnit | { kind: LengthUnit },
|
||||
value: number | string,
|
||||
) => {
|
||||
const [fullNumberKey, fullKey, compressedKey] =
|
||||
lengthUnitTranslationKeys[unit]
|
||||
lengthUnitTranslationKeys[typeof unit === "string" ? unit : unit.kind]
|
||||
|
||||
return responsive(
|
||||
responsiveTextSize,
|
||||
@@ -43,3 +45,30 @@ export const formatLength = (
|
||||
() => translate(compressedKey, { value }),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the text for a length unit.
|
||||
*/
|
||||
export const formatLengthR = (
|
||||
unit: LengthUnit | { kind: LengthUnit },
|
||||
value: number | string,
|
||||
): StdReader<string, "t" | "rts"> =>
|
||||
Reader.asks(env =>
|
||||
formatLength(env.translate, env.responsiveTextSize, unit, value),
|
||||
)
|
||||
|
||||
/**
|
||||
* Returns the text for a length unit.
|
||||
*/
|
||||
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,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import type { Translate, Translations } from "../../../helpers/translate.js"
|
||||
import { Reader } from "@elyukai/utils/reader"
|
||||
import type {
|
||||
Translate,
|
||||
TranslationKeyMatchingParams,
|
||||
} from "../../../helpers/translate.js"
|
||||
import type { StdEnv } from "../reader.js"
|
||||
import { ResponsiveTextSize, responsive } from "../responsiveText.js"
|
||||
|
||||
type TimeSpanUnit =
|
||||
@@ -31,9 +36,9 @@ const timeSpanUnitTranslationKeys = {
|
||||
Rounds: [".input {$value :number} {{{$value} rounds}}", "{$value} rounds", "{$value} rnds"],
|
||||
} as const satisfies {
|
||||
[key in TimeSpanUnit]: [
|
||||
fullNumber: keyof Translations,
|
||||
full: keyof Translations,
|
||||
compressed: keyof Translations,
|
||||
fullNumber: TranslationKeyMatchingParams<{ value: number }>,
|
||||
full: TranslationKeyMatchingParams<{ value: number }>,
|
||||
compressed: TranslationKeyMatchingParams<{ value: number }>,
|
||||
]
|
||||
}
|
||||
|
||||
@@ -58,3 +63,22 @@ export const formatTimeSpan = (
|
||||
() => translate(compressedKey, { value }),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the text for a time span unit.
|
||||
*/
|
||||
export const formatTimeSpanR = (
|
||||
unit: { kind: TimeSpanUnit },
|
||||
value: number | string,
|
||||
): Reader<StdEnv<"t" | "rts">, string> =>
|
||||
Reader.asks(({ translate, responsiveTextSize }) =>
|
||||
formatTimeSpan(translate, responsiveTextSize, unit, value),
|
||||
)
|
||||
/**
|
||||
* Returns the text for a time span unit.
|
||||
*/
|
||||
export const formatCombinedTimeSpanR = (object: {
|
||||
unit: { kind: TimeSpanUnit }
|
||||
value: number | string
|
||||
}): Reader<StdEnv<"t" | "rts">, string> =>
|
||||
formatTimeSpanR(object.unit, object.value)
|
||||
|
||||
@@ -13,7 +13,11 @@ import { MISSING_VALUE } from "./partial/unknown.js"
|
||||
export const getPersonalityTraitEntityDescription =
|
||||
createEntityDescriptionCreator<
|
||||
"PersonalityTrait",
|
||||
{ getInstanceById: GetInstanceById<"Culture" | "PersonalityTrait"> }
|
||||
{
|
||||
getInstanceById: GetInstanceById<
|
||||
"Publication" | "Race" | "Culture" | "PersonalityTrait"
|
||||
>
|
||||
}
|
||||
>(({ getInstanceById }, locale, { content: entry }) => {
|
||||
const { translate, translateMap, join: localeJoin } = locale
|
||||
const translation = translateMap(entry.translations)
|
||||
|
||||
+18
-1
@@ -3,6 +3,7 @@ import { isNotNullish, mapNullable } from "@elyukai/utils/nullable"
|
||||
import { sign } from "@elyukai/utils/string/number"
|
||||
import { assertExhaustive } from "@elyukai/utils/typeSafety"
|
||||
import type {
|
||||
ActivatableIdentifier,
|
||||
AnimalVenomLevel,
|
||||
DemonicPoisonLevel,
|
||||
Intoxicant,
|
||||
@@ -13,6 +14,7 @@ import type {
|
||||
PoisonDuration,
|
||||
PoisonSourceType,
|
||||
PoisonStart,
|
||||
RatedIdentifier,
|
||||
} from "optolith-database-schema/gen"
|
||||
import { createEntityDescriptionCreator } from "../creator.js"
|
||||
import type { GetInstanceById } from "../helpers/getTypes.js"
|
||||
@@ -423,7 +425,22 @@ const renderValueCost = (
|
||||
export const getPoisonEntityDescription = createEntityDescriptionCreator<
|
||||
"Poison",
|
||||
{
|
||||
getInstanceById: GetInstanceById<"DerivedCharacteristic" | "Disease">
|
||||
getInstanceById: GetInstanceById<
|
||||
| "Publication"
|
||||
| "DerivedCharacteristic"
|
||||
| "Disease"
|
||||
| ActivatableIdentifier["kind"]
|
||||
| RatedIdentifier["kind"]
|
||||
| "Race"
|
||||
| "Culture"
|
||||
| "State"
|
||||
| "Enhancement"
|
||||
| "PactCategory"
|
||||
| "PactDomain"
|
||||
| "SocialStatus"
|
||||
| "Aspect"
|
||||
| "Property"
|
||||
>
|
||||
getResolvedSelectOptionById: GetResolvedSelectOptionById
|
||||
idMap: IdMap
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@ import type {
|
||||
GetAllResolvedSkillUses,
|
||||
} from "../index.js"
|
||||
import { BaseActivatableTranslation } from "./activatable.js"
|
||||
import { createImprovementCost } from "./partial/rated/improvementCost.js"
|
||||
import { getTextForCheck } from "./partial/rated/skillCheck.js"
|
||||
import { renderImprovementCost } from "./partial/rated/improvementCost.js"
|
||||
import { renderSkillCheck } from "./partial/rated/skillCheck.js"
|
||||
|
||||
const getUsesOrNewApplications = <
|
||||
T extends ResolvedNewSkillApplication | ResolvedSkillUse,
|
||||
@@ -69,7 +69,7 @@ export const getSkillEntityDescription = createEntityDescriptionCreator<
|
||||
"Skill",
|
||||
{
|
||||
getInstanceById: GetInstanceById<
|
||||
"Attribute" | ActivatableIdentifier["kind"]
|
||||
"Publication" | "Attribute" | ActivatableIdentifier["kind"] | "Aspect"
|
||||
>
|
||||
getAllInstances: GetAllInstances<
|
||||
| "BlessedTradition"
|
||||
@@ -159,10 +159,11 @@ export const getSkillEntityDescription = createEntityDescriptionCreator<
|
||||
label: translate("Uses"),
|
||||
value: uses.join(", "),
|
||||
},
|
||||
getTextForCheck(
|
||||
{ translate, translateMap, getInstanceById },
|
||||
entry.check,
|
||||
),
|
||||
renderSkillCheck(entry.check).run({
|
||||
translate,
|
||||
translateMap,
|
||||
getInstanceById,
|
||||
}),
|
||||
{
|
||||
label: translate("Applications"),
|
||||
value: applications.join(", "),
|
||||
@@ -198,7 +199,7 @@ export const getSkillEntityDescription = createEntityDescriptionCreator<
|
||||
label: translate("Botch"),
|
||||
value: translation.botch,
|
||||
},
|
||||
createImprovementCost(translate, entry.improvement_cost),
|
||||
renderImprovementCost(entry.improvement_cost).run({ translate }),
|
||||
],
|
||||
}
|
||||
},
|
||||
|
||||
+330
-662
File diff suppressed because it is too large
Load Diff
+13
-1
@@ -3,10 +3,22 @@
|
||||
import * as Database from "optolith-database-schema/gen"
|
||||
import { IdArgsVariant } from "tsondb/schema/gen"
|
||||
|
||||
export type GetInstanceById<T extends keyof Database.EntityMap> = <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
|
||||
|
||||
// export type GetInstanceById<T extends Extract<keyof Database.EntityMap, string>> = {
|
||||
// <U extends T>(entity: U, id: string): Database.EntityMap[U] | undefined
|
||||
// <U extends T>(enumCase: Case<U, string>): Database.EntityMap[U] | undefined
|
||||
// }
|
||||
|
||||
// const test = (() =>
|
||||
// null) as unknown as GetInstanceById<"AdvancedCombatSpecialAbility">
|
||||
|
||||
// const test2: GetInstanceById<"Advantage"> = test
|
||||
|
||||
export type GetAllInstances<T extends keyof Database.EntityMap> = <U extends T>(
|
||||
entity: U,
|
||||
) => { id: string; content: Database.EntityMap[U] }[]
|
||||
|
||||
@@ -2,6 +2,11 @@ import { Compare } from "@optolith/helpers/compare"
|
||||
import type { LocaleMeasurementAdjustments } from "optolith-database-schema/gen"
|
||||
import { Translate, TranslateMap } from "./translate.js"
|
||||
|
||||
/**
|
||||
* The type of list to join in a locale-aware way.
|
||||
*/
|
||||
export type LocaleJoinType = "conjunction" | "disjunction" | "unit"
|
||||
|
||||
/**
|
||||
* The environment for a locale.
|
||||
*/
|
||||
@@ -10,7 +15,7 @@ export type LocaleEnvironment = {
|
||||
translate: Translate
|
||||
translateMap: TranslateMap
|
||||
compare: LocaleCompare
|
||||
join: (list: string[], type: "conjunction" | "disjunction" | "unit") => string
|
||||
join: (list: string[], type: LocaleJoinType) => string
|
||||
measurementAdjustments: Required<LocaleMeasurementAdjustments>
|
||||
}
|
||||
|
||||
@@ -22,7 +27,4 @@ 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: "conjunction" | "disjunction" | "unit",
|
||||
) => string
|
||||
export type LocaleJoin = (list: string[], type: LocaleJoinType) => string
|
||||
|
||||
+31
-17
@@ -23,7 +23,10 @@ export type TranslationParams<K extends keyof Translations> =
|
||||
? Params
|
||||
: undefined
|
||||
|
||||
type TranslationParamsInArray<K extends keyof Translations> =
|
||||
/**
|
||||
* 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]
|
||||
: []
|
||||
@@ -38,7 +41,10 @@ export type LocaleMap<T> = Record<string, T>
|
||||
*/
|
||||
export type TranslateMap = <T>(map: LocaleMap<T> | undefined) => T | undefined
|
||||
|
||||
type TranslationKeysWithoutParams = {
|
||||
/**
|
||||
* The set of translation keys that do not have parameters.
|
||||
*/
|
||||
export type TranslationKeysWithoutParams = {
|
||||
[K in keyof Translations]-?: Translations[K] extends string & {
|
||||
__params: object
|
||||
}
|
||||
@@ -46,37 +52,45 @@ type TranslationKeysWithoutParams = {
|
||||
: K
|
||||
}[keyof Translations]
|
||||
|
||||
type TranslationKeyMatchingParams<Params> = Params extends object
|
||||
/**
|
||||
* Extracts the translation keys that match the parameters.
|
||||
*/
|
||||
export type TranslationKeyMatchingParams<Params> = Params extends object
|
||||
? {
|
||||
[K in keyof Translations]-?: Translations[K] extends string & {
|
||||
__params: Params
|
||||
}
|
||||
? K
|
||||
[K in keyof Translations]-?: keyof Params extends keyof TranslationParams<K>
|
||||
? keyof TranslationParams<K> extends keyof Params
|
||||
? K
|
||||
: never
|
||||
: never
|
||||
}[keyof Translations]
|
||||
: TranslationKeysWithoutParams
|
||||
|
||||
type TranslationKeyMatchingParamsOfKey<K extends keyof Translations> =
|
||||
TranslationKeyMatchingParams<TranslationParams<K>>
|
||||
/**
|
||||
* Extracts the translation keys that match the parameters of the given key.
|
||||
*/
|
||||
export type TranslationKeyMatchingParamsOfKey<T extends keyof Translations> =
|
||||
Translations[T] extends object
|
||||
? TranslationKeyMatchingParams<TranslationParams<T>>
|
||||
: TranslationKeysWithoutParams
|
||||
|
||||
/**
|
||||
* Translates a key that has different translations based on the responsive text size, e.g. a full and a compressed version of the same translation.
|
||||
*/
|
||||
export const responsiveTranslate = <K extends keyof Translations>(
|
||||
export const responsiveTranslate = <
|
||||
K extends keyof Translations,
|
||||
K2 extends TranslationKeyMatchingParamsOfKey<K>,
|
||||
>(
|
||||
translate: Translate,
|
||||
responsiveTextSize: ResponsiveTextSize,
|
||||
fullKey: K,
|
||||
compressedKey: TranslationKeyMatchingParamsOfKey<K>,
|
||||
...rest: TranslationParamsInArray<K>
|
||||
compressedKey: K2,
|
||||
...rest: TranslationParamsInArray<K> & TranslationParamsInArray<K2>
|
||||
): string => {
|
||||
switch (responsiveTextSize) {
|
||||
case ResponsiveTextSize.Full:
|
||||
return translate(fullKey, ...rest)
|
||||
return translate(fullKey, ...(rest as TranslationParamsInArray<K>))
|
||||
case ResponsiveTextSize.Compressed:
|
||||
return translate(
|
||||
compressedKey,
|
||||
...(rest as unknown as TranslationParamsInArray<typeof compressedKey>),
|
||||
)
|
||||
return translate(compressedKey, ...(rest as TranslationParamsInArray<K2>))
|
||||
default:
|
||||
return assertExhaustive(responsiveTextSize)
|
||||
}
|
||||
|
||||
@@ -1,36 +1,36 @@
|
||||
import assert from "node:assert/strict"
|
||||
import { describe, it } from "node:test"
|
||||
import { getCheckResultBasedValueTranslation } from "../../../../../src/entities/partial/rated/activatable/checkResultBased.js"
|
||||
import { renderCheckResultBasedValue } from "../../../../../src/entities/partial/rated/activatable/checkResultBased.js"
|
||||
import { Case } from "../../../../../src/helpers/enums.js"
|
||||
import { translateMock } from "../../../../helpers/translate.js"
|
||||
|
||||
describe("getTextForCheckResultBased", () => {
|
||||
it("should return the value text for a check-result-based parameter of an activatable skill", () => {
|
||||
assert.equal(
|
||||
getCheckResultBasedValueTranslation(translateMock, {
|
||||
renderCheckResultBasedValue({
|
||||
base: Case("QualityLevels"),
|
||||
}),
|
||||
}).run({ translate: translateMock }),
|
||||
"QL",
|
||||
)
|
||||
assert.equal(
|
||||
getCheckResultBasedValueTranslation(translateMock, {
|
||||
renderCheckResultBasedValue({
|
||||
base: Case("SkillPoints"),
|
||||
}),
|
||||
}).run({ translate: translateMock }),
|
||||
"SP",
|
||||
)
|
||||
assert.equal(
|
||||
getCheckResultBasedValueTranslation(translateMock, {
|
||||
renderCheckResultBasedValue({
|
||||
base: Case("QualityLevels"),
|
||||
modifier: { arithmetic: Case("Divide"), value: 2 },
|
||||
}),
|
||||
"QL / 2",
|
||||
}).run({ translate: translateMock }),
|
||||
"QL / 2",
|
||||
)
|
||||
assert.equal(
|
||||
getCheckResultBasedValueTranslation(translateMock, {
|
||||
renderCheckResultBasedValue({
|
||||
base: Case("SkillPoints"),
|
||||
modifier: { arithmetic: Case("Multiply"), value: 3 },
|
||||
}),
|
||||
"SP × 3",
|
||||
}).run({ translate: translateMock }),
|
||||
"SP × 3",
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user