refactor: outsource entity description generation to @optolith/entity-descriptions
This commit is contained in:
Generated
+29
@@ -9,6 +9,7 @@
|
||||
"version": "2.0.0-alpha.1",
|
||||
"license": "MPL-2.0",
|
||||
"dependencies": {
|
||||
"@optolith/entity-descriptions": "^0.2.1",
|
||||
"@reduxjs/toolkit": "^2.2.7",
|
||||
"adm-zip": "^0.5.16",
|
||||
"debug": "^4.3.7",
|
||||
@@ -1658,6 +1659,21 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@optolith/entity-descriptions": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@optolith/entity-descriptions/-/entity-descriptions-0.2.1.tgz",
|
||||
"integrity": "sha512-mtUCQXd2OfXUo0NT1mIFdcwioq/2d9l9S9alC918x8rzamWOB8RFAbVJqknVbZCKmLUNBbSOpOs34QlXNaHsng==",
|
||||
"license": "MPL-2.0",
|
||||
"dependencies": {
|
||||
"@optolith/helpers": "^0.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@optolith/helpers": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@optolith/helpers/-/helpers-0.2.1.tgz",
|
||||
"integrity": "sha512-i2mg5ro2XaY0sFT3vwW+BBuMYVuJpHRMt27X9/sBfXmgY6L16aKG3UXEcP5jjX+/gjH2AM7Uiz6xgtV31/rFLw==",
|
||||
"license": "MPL-2.0"
|
||||
},
|
||||
"node_modules/@pkgjs/parseargs": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
||||
@@ -17974,6 +17990,19 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@optolith/entity-descriptions": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@optolith/entity-descriptions/-/entity-descriptions-0.2.1.tgz",
|
||||
"integrity": "sha512-mtUCQXd2OfXUo0NT1mIFdcwioq/2d9l9S9alC918x8rzamWOB8RFAbVJqknVbZCKmLUNBbSOpOs34QlXNaHsng==",
|
||||
"requires": {
|
||||
"@optolith/helpers": "^0.2.0"
|
||||
}
|
||||
},
|
||||
"@optolith/helpers": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@optolith/helpers/-/helpers-0.2.1.tgz",
|
||||
"integrity": "sha512-i2mg5ro2XaY0sFT3vwW+BBuMYVuJpHRMt27X9/sBfXmgY6L16aKG3UXEcP5jjX+/gjH2AM7Uiz6xgtV31/rFLw=="
|
||||
},
|
||||
"@pkgjs/parseargs": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"license": "MPL-2.0",
|
||||
"main": ".webpack/main.cjs",
|
||||
"dependencies": {
|
||||
"@optolith/entity-descriptions": "^0.2.1",
|
||||
"@reduxjs/toolkit": "^2.2.7",
|
||||
"adm-zip": "^0.5.16",
|
||||
"debug": "^4.3.7",
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { FC } from "react"
|
||||
import { LibraryEntry } from "../../../shared/components/libraryEntry/LibraryEntry.tsx"
|
||||
import { getBlessingLibraryEntry } from "../../../shared/domain/rated/liturgicalChant.ts"
|
||||
import { getBlessingEntityDescription } from "@optolith/entity-descriptions/entities/liturgicalChant"
|
||||
import { FC, useCallback } from "react"
|
||||
import {
|
||||
LibraryEntry,
|
||||
PartialEntityDescriptionCreator,
|
||||
} from "../../../shared/components/libraryEntry/LibraryEntry.tsx"
|
||||
import { useAppSelector } from "../../hooks/redux.ts"
|
||||
import { SelectGetById } from "../../selectors/basicCapabilitySelectors.ts"
|
||||
|
||||
@@ -15,11 +18,18 @@ export const InlineLibraryBlessing: FC<Props> = ({ id }) => {
|
||||
const getTargetCategoryById = useAppSelector(SelectGetById.Static.TargetCategory)
|
||||
const entry = useAppSelector(SelectGetById.Static.Blessing)(id)
|
||||
|
||||
return (
|
||||
<LibraryEntry
|
||||
createLibraryEntry={getBlessingLibraryEntry(entry, {
|
||||
getTargetCategoryById,
|
||||
})}
|
||||
/>
|
||||
const createEntityDescription: PartialEntityDescriptionCreator = useCallback(
|
||||
(defaultDatabaseAccessors, locale) =>
|
||||
getBlessingEntityDescription(
|
||||
{
|
||||
...defaultDatabaseAccessors,
|
||||
getTargetCategoryById,
|
||||
},
|
||||
locale,
|
||||
entry,
|
||||
),
|
||||
[entry, getTargetCategoryById],
|
||||
)
|
||||
|
||||
return <LibraryEntry createEntityDescription={createEntityDescription} />
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { FC } from "react"
|
||||
import { LibraryEntry } from "../../../shared/components/libraryEntry/LibraryEntry.tsx"
|
||||
import { getCantripLibraryEntry } from "../../../shared/domain/rated/spell.ts"
|
||||
import { getCantripEntityDescription } from "@optolith/entity-descriptions/entities/spell"
|
||||
import { FC, useCallback } from "react"
|
||||
import {
|
||||
LibraryEntry,
|
||||
PartialEntityDescriptionCreator,
|
||||
} from "../../../shared/components/libraryEntry/LibraryEntry.tsx"
|
||||
import { useAppSelector } from "../../hooks/redux.ts"
|
||||
import { SelectGetById } from "../../selectors/basicCapabilitySelectors.ts"
|
||||
|
||||
@@ -18,14 +21,21 @@ export const InlineLibraryCantrip: FC<Props> = ({ id }) => {
|
||||
const getMagicalTraditionById = useAppSelector(SelectGetById.Static.MagicalTradition)
|
||||
const entry = useAppSelector(SelectGetById.Static.Cantrip)(id)
|
||||
|
||||
return (
|
||||
<LibraryEntry
|
||||
createLibraryEntry={getCantripLibraryEntry(entry, {
|
||||
getTargetCategoryById,
|
||||
getPropertyById,
|
||||
getMagicalTraditionById,
|
||||
getCurriculumById,
|
||||
})}
|
||||
/>
|
||||
const createEntityDescription: PartialEntityDescriptionCreator = useCallback(
|
||||
(defaultDatabaseAccessors, locale) =>
|
||||
getCantripEntityDescription(
|
||||
{
|
||||
...defaultDatabaseAccessors,
|
||||
getTargetCategoryById,
|
||||
getPropertyById,
|
||||
getCurriculumById,
|
||||
getMagicalTraditionById,
|
||||
},
|
||||
locale,
|
||||
entry,
|
||||
),
|
||||
[entry, getCurriculumById, getMagicalTraditionById, getPropertyById, getTargetCategoryById],
|
||||
)
|
||||
|
||||
return <LibraryEntry createEntityDescription={createEntityDescription} />
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { FC } from "react"
|
||||
import { LibraryEntry } from "../../../shared/components/libraryEntry/LibraryEntry.tsx"
|
||||
import { getCeremonyLibraryEntry } from "../../../shared/domain/rated/liturgicalChant.ts"
|
||||
import { getCeremonyEntityDescription } from "@optolith/entity-descriptions/entities/liturgicalChant"
|
||||
import { FC, useCallback } from "react"
|
||||
import {
|
||||
LibraryEntry,
|
||||
PartialEntityDescriptionCreator,
|
||||
} from "../../../shared/components/libraryEntry/LibraryEntry.tsx"
|
||||
import { DerivedCharacteristicIdentifier } from "../../../shared/domain/identifier.ts"
|
||||
import { useAppSelector } from "../../hooks/redux.ts"
|
||||
import { SelectGetById } from "../../selectors/basicCapabilitySelectors.ts"
|
||||
|
||||
@@ -14,22 +18,41 @@ type Props = {
|
||||
export const InlineLibraryCeremony: FC<Props> = ({ id }) => {
|
||||
const getAttributeById = useAppSelector(SelectGetById.Static.Attribute)
|
||||
const getDerivedCharacteristicById = useAppSelector(SelectGetById.Static.DerivedCharacteristic)
|
||||
const spirit = getDerivedCharacteristicById(DerivedCharacteristicIdentifier.Spirit)
|
||||
const toughness = getDerivedCharacteristicById(DerivedCharacteristicIdentifier.Toughness)
|
||||
const getSkillModificationLevelById = useAppSelector(SelectGetById.Static.SkillModificationLevel)
|
||||
const getTargetCategoryById = useAppSelector(SelectGetById.Static.TargetCategory)
|
||||
const getBlessedTraditionById = useAppSelector(SelectGetById.Static.BlessedTradition)
|
||||
const getAspectById = useAppSelector(SelectGetById.Static.Aspect)
|
||||
const entry = useAppSelector(SelectGetById.Static.Ceremony)(id)
|
||||
|
||||
return (
|
||||
<LibraryEntry
|
||||
createLibraryEntry={getCeremonyLibraryEntry(entry, {
|
||||
getAttributeById,
|
||||
getDerivedCharacteristicById,
|
||||
getSkillModificationLevelById,
|
||||
getTargetCategoryById,
|
||||
getBlessedTraditionById,
|
||||
getAspectById,
|
||||
})}
|
||||
/>
|
||||
const createEntityDescription: PartialEntityDescriptionCreator = useCallback(
|
||||
(defaultDatabaseAccessors, locale) =>
|
||||
getCeremonyEntityDescription(
|
||||
{
|
||||
...defaultDatabaseAccessors,
|
||||
getAttributeById,
|
||||
getSpirit: () => spirit,
|
||||
getToughness: () => toughness,
|
||||
getSkillModificationLevelById,
|
||||
getTargetCategoryById,
|
||||
getBlessedTraditionById,
|
||||
getAspectById,
|
||||
},
|
||||
locale,
|
||||
entry,
|
||||
),
|
||||
[
|
||||
entry,
|
||||
getAspectById,
|
||||
getAttributeById,
|
||||
getBlessedTraditionById,
|
||||
getSkillModificationLevelById,
|
||||
getTargetCategoryById,
|
||||
spirit,
|
||||
toughness,
|
||||
],
|
||||
)
|
||||
|
||||
return <LibraryEntry createEntityDescription={createEntityDescription} />
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { FC } from "react"
|
||||
import { LibraryEntry } from "../../../shared/components/libraryEntry/LibraryEntry.tsx"
|
||||
import { getCloseCombatTechniqueLibraryEntry } from "../../../shared/domain/rated/combatTechnique.ts"
|
||||
import { getCloseCombatTechniqueEntityDescription } from "@optolith/entity-descriptions/entities/combatTechnique"
|
||||
import { FC, useCallback } from "react"
|
||||
import {
|
||||
LibraryEntry,
|
||||
PartialEntityDescriptionCreator,
|
||||
} from "../../../shared/components/libraryEntry/LibraryEntry.tsx"
|
||||
import { useAppSelector } from "../../hooks/redux.ts"
|
||||
import { SelectGetById } from "../../selectors/basicCapabilitySelectors.ts"
|
||||
|
||||
@@ -15,11 +18,18 @@ export const InlineLibraryCloseCombatTechnique: FC<Props> = ({ id }) => {
|
||||
const getAttributeById = useAppSelector(SelectGetById.Static.Attribute)
|
||||
const entry = useAppSelector(SelectGetById.Static.CloseCombatTechnique)(id)
|
||||
|
||||
return (
|
||||
<LibraryEntry
|
||||
createLibraryEntry={getCloseCombatTechniqueLibraryEntry(entry, {
|
||||
getAttributeById,
|
||||
})}
|
||||
/>
|
||||
const createEntityDescription: PartialEntityDescriptionCreator = useCallback(
|
||||
(defaultDatabaseAccessors, locale) =>
|
||||
getCloseCombatTechniqueEntityDescription(
|
||||
{
|
||||
...defaultDatabaseAccessors,
|
||||
getAttributeById,
|
||||
},
|
||||
locale,
|
||||
entry,
|
||||
),
|
||||
[entry, getAttributeById],
|
||||
)
|
||||
|
||||
return <LibraryEntry createEntityDescription={createEntityDescription} />
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { FC } from "react"
|
||||
import { LibraryEntry } from "../../../shared/components/libraryEntry/LibraryEntry.tsx"
|
||||
import { getExperienceLevelLibraryEntry } from "../../../shared/domain/experienceLevel.ts"
|
||||
import { getExperienceLevelEntityDescription } from "@optolith/entity-descriptions/entities/experienceLevel"
|
||||
import { FC, useCallback } from "react"
|
||||
import {
|
||||
LibraryEntry,
|
||||
PartialEntityDescriptionCreator,
|
||||
} from "../../../shared/components/libraryEntry/LibraryEntry.tsx"
|
||||
import { useAppSelector } from "../../hooks/redux.ts"
|
||||
import { selectStaticExperienceLevels } from "../../slices/databaseSlice.ts"
|
||||
|
||||
@@ -14,5 +17,11 @@ type Props = {
|
||||
export const InlineLibraryExperienceLevel: FC<Props> = ({ id }) => {
|
||||
const entry = useAppSelector(selectStaticExperienceLevels)[id]
|
||||
|
||||
return <LibraryEntry createLibraryEntry={getExperienceLevelLibraryEntry(entry)} />
|
||||
const createEntityDescription: PartialEntityDescriptionCreator = useCallback(
|
||||
(defaultDatabaseAccessors, locale) =>
|
||||
getExperienceLevelEntityDescription(defaultDatabaseAccessors, locale, entry),
|
||||
[entry],
|
||||
)
|
||||
|
||||
return <LibraryEntry createEntityDescription={createEntityDescription} />
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { FC } from "react"
|
||||
import { LibraryEntry } from "../../../shared/components/libraryEntry/LibraryEntry.tsx"
|
||||
import { getFocusRuleLibraryEntry } from "../../../shared/domain/rules/focusRule.ts"
|
||||
import { getFocusRuleEntityDescription } from "@optolith/entity-descriptions/entities/focusRule"
|
||||
import { FC, useCallback } from "react"
|
||||
import {
|
||||
LibraryEntry,
|
||||
PartialEntityDescriptionCreator,
|
||||
} from "../../../shared/components/libraryEntry/LibraryEntry.tsx"
|
||||
import { useAppSelector } from "../../hooks/redux.ts"
|
||||
import { SelectGetById } from "../../selectors/basicCapabilitySelectors.ts"
|
||||
import { selectStaticFocusRules } from "../../slices/databaseSlice.ts"
|
||||
@@ -16,5 +19,18 @@ export const InlineLibraryFocusRule: FC<Props> = ({ id }) => {
|
||||
const entry = useAppSelector(selectStaticFocusRules)[id]
|
||||
const getSubjectById = useAppSelector(SelectGetById.Static.Subject)
|
||||
|
||||
return <LibraryEntry createLibraryEntry={getFocusRuleLibraryEntry(entry, { getSubjectById })} />
|
||||
const createEntityDescription: PartialEntityDescriptionCreator = useCallback(
|
||||
(defaultDatabaseAccessors, locale) =>
|
||||
getFocusRuleEntityDescription(
|
||||
{
|
||||
...defaultDatabaseAccessors,
|
||||
getSubjectById,
|
||||
},
|
||||
locale,
|
||||
entry,
|
||||
),
|
||||
[entry, getSubjectById],
|
||||
)
|
||||
|
||||
return <LibraryEntry createEntityDescription={createEntityDescription} />
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { FC } from "react"
|
||||
import { LibraryEntry } from "../../../shared/components/libraryEntry/LibraryEntry.tsx"
|
||||
import { getLiturgicalChantLibraryEntry } from "../../../shared/domain/rated/liturgicalChant.ts"
|
||||
import { getLiturgicalChantEntityDescription } from "@optolith/entity-descriptions/entities/liturgicalChant"
|
||||
import { FC, useCallback } from "react"
|
||||
import {
|
||||
LibraryEntry,
|
||||
PartialEntityDescriptionCreator,
|
||||
} from "../../../shared/components/libraryEntry/LibraryEntry.tsx"
|
||||
import { DerivedCharacteristicIdentifier } from "../../../shared/domain/identifier.ts"
|
||||
import { useAppSelector } from "../../hooks/redux.ts"
|
||||
import { SelectGetById } from "../../selectors/basicCapabilitySelectors.ts"
|
||||
|
||||
@@ -14,22 +18,41 @@ type Props = {
|
||||
export const InlineLibraryLiturgicalChant: FC<Props> = ({ id }) => {
|
||||
const getAttributeById = useAppSelector(SelectGetById.Static.Attribute)
|
||||
const getDerivedCharacteristicById = useAppSelector(SelectGetById.Static.DerivedCharacteristic)
|
||||
const spirit = getDerivedCharacteristicById(DerivedCharacteristicIdentifier.Spirit)
|
||||
const toughness = getDerivedCharacteristicById(DerivedCharacteristicIdentifier.Toughness)
|
||||
const getSkillModificationLevelById = useAppSelector(SelectGetById.Static.SkillModificationLevel)
|
||||
const getTargetCategoryById = useAppSelector(SelectGetById.Static.TargetCategory)
|
||||
const getBlessedTraditionById = useAppSelector(SelectGetById.Static.BlessedTradition)
|
||||
const getAspectById = useAppSelector(SelectGetById.Static.Aspect)
|
||||
const entry = useAppSelector(SelectGetById.Static.LiturgicalChant)(id)
|
||||
|
||||
return (
|
||||
<LibraryEntry
|
||||
createLibraryEntry={getLiturgicalChantLibraryEntry(entry, {
|
||||
getAttributeById,
|
||||
getDerivedCharacteristicById,
|
||||
getSkillModificationLevelById,
|
||||
getTargetCategoryById,
|
||||
getBlessedTraditionById,
|
||||
getAspectById,
|
||||
})}
|
||||
/>
|
||||
const createEntityDescription: PartialEntityDescriptionCreator = useCallback(
|
||||
(defaultDatabaseAccessors, locale) =>
|
||||
getLiturgicalChantEntityDescription(
|
||||
{
|
||||
...defaultDatabaseAccessors,
|
||||
getAttributeById,
|
||||
getSpirit: () => spirit,
|
||||
getToughness: () => toughness,
|
||||
getSkillModificationLevelById,
|
||||
getTargetCategoryById,
|
||||
getBlessedTraditionById,
|
||||
getAspectById,
|
||||
},
|
||||
locale,
|
||||
entry,
|
||||
),
|
||||
[
|
||||
entry,
|
||||
getAspectById,
|
||||
getAttributeById,
|
||||
getBlessedTraditionById,
|
||||
getSkillModificationLevelById,
|
||||
getTargetCategoryById,
|
||||
spirit,
|
||||
toughness,
|
||||
],
|
||||
)
|
||||
|
||||
return <LibraryEntry createEntityDescription={createEntityDescription} />
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { FC } from "react"
|
||||
import { LibraryEntry } from "../../../shared/components/libraryEntry/LibraryEntry.tsx"
|
||||
import { getOptionalRuleLibraryEntry } from "../../../shared/domain/rules/optionalRule.ts"
|
||||
import { getOptionalRuleEntityDescription } from "@optolith/entity-descriptions/entities/optionalRule"
|
||||
import { FC, useCallback } from "react"
|
||||
import {
|
||||
LibraryEntry,
|
||||
PartialEntityDescriptionCreator,
|
||||
} from "../../../shared/components/libraryEntry/LibraryEntry.tsx"
|
||||
import { useAppSelector } from "../../hooks/redux.ts"
|
||||
import { selectStaticOptionalRules } from "../../slices/databaseSlice.ts"
|
||||
|
||||
@@ -14,5 +17,11 @@ type Props = {
|
||||
export const InlineLibraryOptionalRule: FC<Props> = ({ id }) => {
|
||||
const entry = useAppSelector(selectStaticOptionalRules)[id]
|
||||
|
||||
return <LibraryEntry createLibraryEntry={getOptionalRuleLibraryEntry(entry)} />
|
||||
const createEntityDescription: PartialEntityDescriptionCreator = useCallback(
|
||||
(defaultDatabaseAccessors, locale) =>
|
||||
getOptionalRuleEntityDescription(defaultDatabaseAccessors, locale, entry),
|
||||
[entry],
|
||||
)
|
||||
|
||||
return <LibraryEntry createEntityDescription={createEntityDescription} />
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { FC } from "react"
|
||||
import { LibraryEntry } from "../../../shared/components/libraryEntry/LibraryEntry.tsx"
|
||||
import { getRangedCombatTechniqueLibraryEntry } from "../../../shared/domain/rated/combatTechnique.ts"
|
||||
import { getRangedCombatTechniqueEntityDescription } from "@optolith/entity-descriptions/entities/combatTechnique"
|
||||
import { FC, useCallback } from "react"
|
||||
import {
|
||||
LibraryEntry,
|
||||
PartialEntityDescriptionCreator,
|
||||
} from "../../../shared/components/libraryEntry/LibraryEntry.tsx"
|
||||
import { useAppSelector } from "../../hooks/redux.ts"
|
||||
import { SelectGetById } from "../../selectors/basicCapabilitySelectors.ts"
|
||||
|
||||
@@ -15,11 +18,18 @@ export const InlineLibraryRangedCombatTechnique: FC<Props> = ({ id }) => {
|
||||
const getAttributeById = useAppSelector(SelectGetById.Static.Attribute)
|
||||
const entry = useAppSelector(SelectGetById.Static.RangedCombatTechnique)(id)
|
||||
|
||||
return (
|
||||
<LibraryEntry
|
||||
createLibraryEntry={getRangedCombatTechniqueLibraryEntry(entry, {
|
||||
getAttributeById,
|
||||
})}
|
||||
/>
|
||||
const createEntityDescription: PartialEntityDescriptionCreator = useCallback(
|
||||
(defaultDatabaseAccessors, locale) =>
|
||||
getRangedCombatTechniqueEntityDescription(
|
||||
{
|
||||
...defaultDatabaseAccessors,
|
||||
getAttributeById,
|
||||
},
|
||||
locale,
|
||||
entry,
|
||||
),
|
||||
[entry, getAttributeById],
|
||||
)
|
||||
|
||||
return <LibraryEntry createEntityDescription={createEntityDescription} />
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { FC } from "react"
|
||||
import { LibraryEntry } from "../../../shared/components/libraryEntry/LibraryEntry.tsx"
|
||||
import { getRitualLibraryEntry } from "../../../shared/domain/rated/spell.ts"
|
||||
import { getRitualEntityDescription } from "@optolith/entity-descriptions/entities/spell"
|
||||
import { FC, useCallback } from "react"
|
||||
import {
|
||||
LibraryEntry,
|
||||
PartialEntityDescriptionCreator,
|
||||
} from "../../../shared/components/libraryEntry/LibraryEntry.tsx"
|
||||
import { DerivedCharacteristicIdentifier } from "../../../shared/domain/identifier.ts"
|
||||
import { useAppSelector } from "../../hooks/redux.ts"
|
||||
import { SelectGetById } from "../../selectors/basicCapabilitySelectors.ts"
|
||||
|
||||
@@ -14,22 +18,41 @@ type Props = {
|
||||
export const InlineLibraryRitual: FC<Props> = ({ id }) => {
|
||||
const getAttributeById = useAppSelector(SelectGetById.Static.Attribute)
|
||||
const getDerivedCharacteristicById = useAppSelector(SelectGetById.Static.DerivedCharacteristic)
|
||||
const spirit = getDerivedCharacteristicById(DerivedCharacteristicIdentifier.Spirit)
|
||||
const toughness = getDerivedCharacteristicById(DerivedCharacteristicIdentifier.Toughness)
|
||||
const getSkillModificationLevelById = useAppSelector(SelectGetById.Static.SkillModificationLevel)
|
||||
const getTargetCategoryById = useAppSelector(SelectGetById.Static.TargetCategory)
|
||||
const getPropertyById = useAppSelector(SelectGetById.Static.Property)
|
||||
const getMagicalTraditionById = useAppSelector(SelectGetById.Static.MagicalTradition)
|
||||
const entry = useAppSelector(SelectGetById.Static.Ritual)(id)
|
||||
|
||||
return (
|
||||
<LibraryEntry
|
||||
createLibraryEntry={getRitualLibraryEntry(entry, {
|
||||
getAttributeById,
|
||||
getDerivedCharacteristicById,
|
||||
getSkillModificationLevelById,
|
||||
getTargetCategoryById,
|
||||
getPropertyById,
|
||||
getMagicalTraditionById,
|
||||
})}
|
||||
/>
|
||||
const createEntityDescription: PartialEntityDescriptionCreator = useCallback(
|
||||
(defaultDatabaseAccessors, locale) =>
|
||||
getRitualEntityDescription(
|
||||
{
|
||||
...defaultDatabaseAccessors,
|
||||
getAttributeById,
|
||||
getSpirit: () => spirit,
|
||||
getToughness: () => toughness,
|
||||
getSkillModificationLevelById,
|
||||
getTargetCategoryById,
|
||||
getPropertyById,
|
||||
getMagicalTraditionById,
|
||||
},
|
||||
locale,
|
||||
entry,
|
||||
),
|
||||
[
|
||||
entry,
|
||||
getAttributeById,
|
||||
getMagicalTraditionById,
|
||||
getPropertyById,
|
||||
getSkillModificationLevelById,
|
||||
getTargetCategoryById,
|
||||
spirit,
|
||||
toughness,
|
||||
],
|
||||
)
|
||||
|
||||
return <LibraryEntry createEntityDescription={createEntityDescription} />
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { FC } from "react"
|
||||
import { LibraryEntry } from "../../../shared/components/libraryEntry/LibraryEntry.tsx"
|
||||
import { getSkillLibraryEntry } from "../../../shared/domain/rated/skill.ts"
|
||||
import { getSkillEntityDescription } from "@optolith/entity-descriptions/entities/skill"
|
||||
import { FC, useCallback } from "react"
|
||||
import {
|
||||
LibraryEntry,
|
||||
PartialEntityDescriptionCreator,
|
||||
} from "../../../shared/components/libraryEntry/LibraryEntry.tsx"
|
||||
import { useAppSelector } from "../../hooks/redux.ts"
|
||||
import { SelectAll, SelectGetById } from "../../selectors/basicCapabilitySelectors.ts"
|
||||
import { selectNewApplicationsAndUsesCache } from "../../slices/databaseSlice.ts"
|
||||
@@ -20,15 +23,22 @@ export const InlineLibrarySkill: FC<Props> = ({ id }) => {
|
||||
const entry = useAppSelector(SelectGetById.Static.Skill)(id)
|
||||
const cache = useAppSelector(selectNewApplicationsAndUsesCache)
|
||||
|
||||
return (
|
||||
<LibraryEntry
|
||||
createLibraryEntry={getSkillLibraryEntry(entry, {
|
||||
getAttributeById,
|
||||
blessedTraditions,
|
||||
diseases,
|
||||
regions,
|
||||
cache,
|
||||
})}
|
||||
/>
|
||||
const createEntityDescription: PartialEntityDescriptionCreator = useCallback(
|
||||
(defaultDatabaseAccessors, locale) =>
|
||||
getSkillEntityDescription(
|
||||
{
|
||||
...defaultDatabaseAccessors,
|
||||
getAttributeById,
|
||||
blessedTraditions,
|
||||
diseases,
|
||||
regions,
|
||||
cache,
|
||||
},
|
||||
locale,
|
||||
entry,
|
||||
),
|
||||
[blessedTraditions, cache, diseases, entry, getAttributeById, regions],
|
||||
)
|
||||
|
||||
return <LibraryEntry createEntityDescription={createEntityDescription} />
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { FC } from "react"
|
||||
import { LibraryEntry } from "../../../shared/components/libraryEntry/LibraryEntry.tsx"
|
||||
import { getSpellLibraryEntry } from "../../../shared/domain/rated/spell.ts"
|
||||
import { getSpellEntityDescription } from "@optolith/entity-descriptions/entities/spell"
|
||||
import { FC, useCallback } from "react"
|
||||
import {
|
||||
LibraryEntry,
|
||||
PartialEntityDescriptionCreator,
|
||||
} from "../../../shared/components/libraryEntry/LibraryEntry.tsx"
|
||||
import { DerivedCharacteristicIdentifier } from "../../../shared/domain/identifier.ts"
|
||||
import { useAppSelector } from "../../hooks/redux.ts"
|
||||
import { SelectGetById } from "../../selectors/basicCapabilitySelectors.ts"
|
||||
|
||||
@@ -14,22 +18,41 @@ type Props = {
|
||||
export const InlineLibrarySpell: FC<Props> = ({ id }) => {
|
||||
const getAttributeById = useAppSelector(SelectGetById.Static.Attribute)
|
||||
const getDerivedCharacteristicById = useAppSelector(SelectGetById.Static.DerivedCharacteristic)
|
||||
const spirit = getDerivedCharacteristicById(DerivedCharacteristicIdentifier.Spirit)
|
||||
const toughness = getDerivedCharacteristicById(DerivedCharacteristicIdentifier.Toughness)
|
||||
const getSkillModificationLevelById = useAppSelector(SelectGetById.Static.SkillModificationLevel)
|
||||
const getTargetCategoryById = useAppSelector(SelectGetById.Static.TargetCategory)
|
||||
const getPropertyById = useAppSelector(SelectGetById.Static.Property)
|
||||
const getMagicalTraditionById = useAppSelector(SelectGetById.Static.MagicalTradition)
|
||||
const entry = useAppSelector(SelectGetById.Static.Spell)(id)
|
||||
|
||||
return (
|
||||
<LibraryEntry
|
||||
createLibraryEntry={getSpellLibraryEntry(entry, {
|
||||
getAttributeById,
|
||||
getDerivedCharacteristicById,
|
||||
getSkillModificationLevelById,
|
||||
getTargetCategoryById,
|
||||
getPropertyById,
|
||||
getMagicalTraditionById,
|
||||
})}
|
||||
/>
|
||||
const createEntityDescription: PartialEntityDescriptionCreator = useCallback(
|
||||
(defaultDatabaseAccessors, locale) =>
|
||||
getSpellEntityDescription(
|
||||
{
|
||||
...defaultDatabaseAccessors,
|
||||
getAttributeById,
|
||||
getSpirit: () => spirit,
|
||||
getToughness: () => toughness,
|
||||
getSkillModificationLevelById,
|
||||
getTargetCategoryById,
|
||||
getPropertyById,
|
||||
getMagicalTraditionById,
|
||||
},
|
||||
locale,
|
||||
entry,
|
||||
),
|
||||
[
|
||||
entry,
|
||||
getAttributeById,
|
||||
getMagicalTraditionById,
|
||||
getPropertyById,
|
||||
getSkillModificationLevelById,
|
||||
getTargetCategoryById,
|
||||
spirit,
|
||||
toughness,
|
||||
],
|
||||
)
|
||||
|
||||
return <LibraryEntry createEntityDescription={createEntityDescription} />
|
||||
}
|
||||
|
||||
@@ -1,32 +1,57 @@
|
||||
import { EntityDescription } from "@optolith/entity-descriptions"
|
||||
import { FC } from "react"
|
||||
import { useAppSelector } from "../../../main_window/hooks/redux.ts"
|
||||
import { InlineLibraryPlaceholder } from "../../../main_window/inlineLibrary/InlineLibraryPlaceholder.tsx"
|
||||
import { LibraryEntryConfiguredCreator } from "../../domain/libraryEntry.ts"
|
||||
import { SelectGetById } from "../../../main_window/selectors/basicCapabilitySelectors.ts"
|
||||
import { selectLocale } from "../../../main_window/slices/settingsSlice.ts"
|
||||
import { GetById } from "../../domain/getTypes.ts"
|
||||
import { useLocaleCompare } from "../../hooks/localeCompare.ts"
|
||||
import { useTranslate } from "../../hooks/translate.ts"
|
||||
import { useTranslateMap } from "../../hooks/translateMap.ts"
|
||||
import { Compare } from "../../utils/compare.ts"
|
||||
import { Translate, TranslateMap } from "../../utils/translate.ts"
|
||||
import "./LibraryEntry.scss"
|
||||
import { LibraryEntryContents } from "./LibraryEntryContents.tsx"
|
||||
import { LibraryEntryReferences } from "./LibraryEntryReferences.tsx"
|
||||
|
||||
/**
|
||||
* A function that provides common parameters to every entity desciption
|
||||
* creator.
|
||||
*/
|
||||
export type PartialEntityDescriptionCreator = (
|
||||
defaultDatabaseAccessors: { getPublicationById: GetById.Static.Publication },
|
||||
locale: {
|
||||
id: string
|
||||
translate: Translate
|
||||
translateMap: TranslateMap
|
||||
compare: Compare<string>
|
||||
},
|
||||
) => EntityDescription | undefined
|
||||
|
||||
type Props = {
|
||||
createLibraryEntry: LibraryEntryConfiguredCreator
|
||||
createEntityDescription: PartialEntityDescriptionCreator
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays all information about a library entry.
|
||||
*/
|
||||
export const LibraryEntry: FC<Props> = ({ createLibraryEntry }) => {
|
||||
export const LibraryEntry: FC<Props> = ({ createEntityDescription }) => {
|
||||
const translate = useTranslate()
|
||||
const translateMap = useTranslateMap()
|
||||
const localeCompare = useLocaleCompare()
|
||||
const getPublicationById = useAppSelector(SelectGetById.Static.Publication)
|
||||
const localeId = useAppSelector(selectLocale)
|
||||
|
||||
const libraryEntry = createLibraryEntry({ translate, translateMap, localeCompare })
|
||||
const libraryEntry = createEntityDescription(
|
||||
{ getPublicationById },
|
||||
{ id: localeId ?? "en-US", translate, translateMap, compare: localeCompare },
|
||||
)
|
||||
|
||||
if (libraryEntry === undefined) {
|
||||
return <InlineLibraryPlaceholder />
|
||||
}
|
||||
|
||||
const { title, subtitle, className, content, src } = libraryEntry
|
||||
const { title, subtitle, className, body, references } = libraryEntry
|
||||
|
||||
return (
|
||||
<div className={`library-entry info ${className}-info`}>
|
||||
@@ -34,8 +59,8 @@ export const LibraryEntry: FC<Props> = ({ createLibraryEntry }) => {
|
||||
<h2 className="title">{title}</h2>
|
||||
{subtitle === undefined ? null : <p className="subtitle">{subtitle}</p>}
|
||||
</div>
|
||||
<LibraryEntryContents contents={content} />
|
||||
{src === undefined ? null : <LibraryEntryReferences sources={src} />}
|
||||
<LibraryEntryContents contents={body} />
|
||||
{references === undefined ? null : <LibraryEntryReferences sources={references} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { EntityDescriptionSection } from "@optolith/entity-descriptions"
|
||||
import { FC, Fragment } from "react"
|
||||
import { LibraryEntryContent } from "../../domain/libraryEntry.ts"
|
||||
import { groupBy } from "../../utils/array.ts"
|
||||
import { classList } from "../../utils/classList.ts"
|
||||
import { Markdown } from "../markdown/Markdown.tsx"
|
||||
import { LibraryEntryLabelList } from "./LibraryEntryLabelList.tsx"
|
||||
|
||||
type Props = {
|
||||
contents: LibraryEntryContent[]
|
||||
contents: EntityDescriptionSection[]
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { EntityDescriptionSection } from "@optolith/entity-descriptions"
|
||||
import { FC } from "react"
|
||||
import { LibraryEntryContent } from "../../domain/libraryEntry.ts"
|
||||
import { Markdown } from "../markdown/Markdown.tsx"
|
||||
import "./LibraryEntryLabelList.scss"
|
||||
|
||||
type Props = {
|
||||
contents: LibraryEntryContent[]
|
||||
contents: EntityDescriptionSection[]
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,136 +1,12 @@
|
||||
import {
|
||||
Occurrence,
|
||||
Page,
|
||||
PublicationRefs,
|
||||
SimpleOccurrence,
|
||||
SimpleOccurrences,
|
||||
VersionedOccurrence,
|
||||
} from "optolith-database-schema/types/source/_PublicationRef"
|
||||
import { FC } from "react"
|
||||
import { useAppSelector } from "../../../main_window/hooks/redux.ts"
|
||||
import { selectStaticPublications } from "../../../main_window/slices/databaseSlice.ts"
|
||||
import {
|
||||
PageRange,
|
||||
fromRawPageRange,
|
||||
normalizePageRanges,
|
||||
numberRangeToPageRange,
|
||||
} from "../../domain/sources/pages.ts"
|
||||
import { useTranslate } from "../../hooks/translate.ts"
|
||||
import { useTranslateMap } from "../../hooks/translateMap.ts"
|
||||
import { isNotNullish } from "../../utils/nullable.ts"
|
||||
import { Translate } from "../../utils/translate.ts"
|
||||
import { assertExhaustive } from "../../utils/typeSafety.ts"
|
||||
|
||||
type Props = {
|
||||
sources: PublicationRefs
|
||||
sources: string
|
||||
}
|
||||
|
||||
const isSimpleOccurrence = (occurrence: Occurrence): occurrence is SimpleOccurrence =>
|
||||
Object.hasOwn(occurrence, "first_page")
|
||||
|
||||
const isSimpleOccurrences = (occurrence: Occurrence): occurrence is SimpleOccurrences =>
|
||||
Array.isArray(occurrence)
|
||||
|
||||
const isVersionedOccurrence = (occurrence: Occurrence): occurrence is VersionedOccurrence =>
|
||||
Object.hasOwn(occurrence, "initial")
|
||||
|
||||
const printPage = (translate: Translate, page: Page) => {
|
||||
switch (page.tag) {
|
||||
case "InsideCoverFront":
|
||||
return translate("Front Cover Inside")
|
||||
case "InsideCoverBack":
|
||||
return translate("Back Cover Inside")
|
||||
case "Numbered":
|
||||
return page.numbered.toString()
|
||||
default:
|
||||
return assertExhaustive(page)
|
||||
}
|
||||
}
|
||||
|
||||
const printPageRange = (translate: Translate, pageRange: PageRange) =>
|
||||
pageRange.lastPage === undefined
|
||||
? printPage(translate, pageRange.firstPage)
|
||||
: `${printPage(translate, pageRange.firstPage)}–${printPage(translate, pageRange.lastPage)}`
|
||||
|
||||
const printPageRanges = (translate: Translate, pageRanges: PageRange[]) =>
|
||||
pageRanges.map(pageRange => printPageRange(translate, pageRange)).join(", ")
|
||||
|
||||
/**
|
||||
* Displays the sources of an entry.
|
||||
*/
|
||||
export const LibraryEntryReferences: FC<Props> = ({ sources }) => {
|
||||
const translate = useTranslate()
|
||||
const translateMap = useTranslateMap()
|
||||
const publications = useAppSelector(selectStaticPublications)
|
||||
|
||||
return (
|
||||
<p className="sources no-indent">
|
||||
{sources
|
||||
.map(source => {
|
||||
const publication = publications[source.id.publication]
|
||||
const publicationTranslations = translateMap(publication?.translations)
|
||||
const occurrences = translateMap(source.occurrences)
|
||||
|
||||
if (
|
||||
publication === undefined ||
|
||||
publicationTranslations === undefined ||
|
||||
occurrences === undefined
|
||||
) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (isSimpleOccurrence(occurrences)) {
|
||||
return `${publicationTranslations.name} ${printPageRange(
|
||||
translate,
|
||||
numberRangeToPageRange(occurrences),
|
||||
)}`
|
||||
}
|
||||
|
||||
if (isSimpleOccurrences(occurrences)) {
|
||||
const ranges = normalizePageRanges(occurrences.map(numberRangeToPageRange))
|
||||
return `${publicationTranslations.name} ${printPageRanges(translate, ranges)}`
|
||||
}
|
||||
|
||||
if (isVersionedOccurrence(occurrences)) {
|
||||
const initialPageRanges = normalizePageRanges(
|
||||
occurrences.initial.pages.map(fromRawPageRange),
|
||||
)
|
||||
|
||||
const initial =
|
||||
occurrences.initial.printing === undefined
|
||||
? printPageRanges(translate, initialPageRanges)
|
||||
: `${printPageRanges(translate, initialPageRanges)} (${translate(
|
||||
"since the {0}. printing",
|
||||
occurrences.initial.printing,
|
||||
)})`
|
||||
|
||||
const revisions =
|
||||
occurrences.revisions?.map(rev => {
|
||||
switch (rev.tag) {
|
||||
case "Since": {
|
||||
const pageRanges = normalizePageRanges(rev.since.pages.map(fromRawPageRange))
|
||||
return `${printPageRanges(translate, pageRanges)} (${translate(
|
||||
"since the {0}. printing",
|
||||
rev.since.printing,
|
||||
)})`
|
||||
}
|
||||
case "Deprecated": {
|
||||
return translate("removed in {0}. printing", rev.deprecated.printing)
|
||||
}
|
||||
default:
|
||||
return assertExhaustive(rev)
|
||||
}
|
||||
}) ?? []
|
||||
|
||||
const allPageRanges = [initial, ...revisions].join("; ")
|
||||
|
||||
return `${publicationTranslations.name} ${allPageRanges}`
|
||||
}
|
||||
|
||||
return assertExhaustive(occurrences)
|
||||
})
|
||||
.filter(isNotNullish)
|
||||
.join("; ")}
|
||||
</p>
|
||||
)
|
||||
}
|
||||
export const LibraryEntryReferences: FC<Props> = ({ sources }) => (
|
||||
<p className="sources no-indent">{sources}</p>
|
||||
)
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { ImprovementCost as RawImprovementCost } from "optolith-database-schema/types/_ImprovementCost"
|
||||
import { rangeSafe, sum } from "../../utils/array.ts"
|
||||
import { Nullish } from "../../utils/nullable.ts"
|
||||
import { Translate } from "../../utils/translate.ts"
|
||||
import { assertExhaustive } from "../../utils/typeSafety.ts"
|
||||
import { LibraryEntryContent } from "../libraryEntry.ts"
|
||||
|
||||
/**
|
||||
* The cost category of an improvement.
|
||||
@@ -184,14 +182,3 @@ export const fromRaw = <T extends RawImprovementCost | undefined>(
|
||||
return assertExhaustive(ic)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the improvement cost as an inline library property.
|
||||
*/
|
||||
export const createImprovementCost = (
|
||||
translate: Translate,
|
||||
improvementCost: RawImprovementCost,
|
||||
): LibraryEntryContent => ({
|
||||
label: translate("Improvement Cost"),
|
||||
value: toString(fromRaw(improvementCost)),
|
||||
})
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { ExperienceLevel } from "optolith-database-schema/types/ExperienceLevel"
|
||||
import { createLibraryEntryCreator } from "./libraryEntry.ts"
|
||||
|
||||
/**
|
||||
* Returns the current experience level based on the total adventure points.
|
||||
@@ -15,52 +14,3 @@ export const getCurrentExperienceLevel = (
|
||||
experienceLevel.adventure_points <= totalAdventurePoints ? experienceLevel : acc,
|
||||
experienceLevels[0],
|
||||
)
|
||||
|
||||
/**
|
||||
* Get a JSON representation of the rules text for an experience level.
|
||||
*/
|
||||
export const getExperienceLevelLibraryEntry = createLibraryEntryCreator<ExperienceLevel>(
|
||||
entry =>
|
||||
({ translate, translateMap }) => {
|
||||
const translation = translateMap(entry.translations)
|
||||
|
||||
if (translation === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return {
|
||||
title: translation.name,
|
||||
className: "experience-level",
|
||||
content: [
|
||||
{
|
||||
label: translate("Adventure Points"),
|
||||
value: entry.adventure_points,
|
||||
},
|
||||
{
|
||||
label: translate("Maximum Attribute Value"),
|
||||
value: entry.max_attribute_value,
|
||||
},
|
||||
{
|
||||
label: translate("Maximum Skill Value"),
|
||||
value: entry.max_skill_rating,
|
||||
},
|
||||
{
|
||||
label: translate("Maximum Combat Technique"),
|
||||
value: entry.max_combat_technique_rating,
|
||||
},
|
||||
{
|
||||
label: translate("Maximum Attribute Total"),
|
||||
value: entry.max_attribute_total,
|
||||
},
|
||||
{
|
||||
label: translate("Number of Spells/Liturgical Chants"),
|
||||
value: entry.max_number_of_spells_liturgical_chants,
|
||||
},
|
||||
{
|
||||
label: translate("Number from other Traditions"),
|
||||
value: entry.max_number_of_unfamiliar_spells,
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
import { PublicationRefs } from "optolith-database-schema/types/source/_PublicationRef"
|
||||
import { filterNonNullable } from "../utils/array.ts"
|
||||
import { Compare } from "../utils/compare.ts"
|
||||
import { Translate, TranslateMap } from "../utils/translate.ts"
|
||||
|
||||
/**
|
||||
* Creates a function that creates the JSON representation of the rules text for
|
||||
* a library entry.
|
||||
*/
|
||||
export const createLibraryEntryCreator =
|
||||
<T, A = undefined>(
|
||||
fn: LibraryEntryCreator<T, A, RawLibraryEntry>,
|
||||
): LibraryEntryCreator<T | undefined, A> =>
|
||||
(entry, ...args) => {
|
||||
if (entry === undefined) {
|
||||
return () => undefined
|
||||
}
|
||||
|
||||
return params => {
|
||||
const rawEntry = fn(entry, ...args)(params)
|
||||
|
||||
if (rawEntry === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return { ...rawEntry, content: filterNonNullable(rawEntry.content) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that creates the JSON representation of the rules text for a
|
||||
* library entry if given further params to the returned function.
|
||||
*/
|
||||
export type LibraryEntryCreator<T, A = undefined, R = LibraryEntry> = (
|
||||
entry: T,
|
||||
...args: A extends undefined ? [] : [A]
|
||||
) => LibraryEntryConfiguredCreator<R>
|
||||
|
||||
/**
|
||||
* A function that is already configures for a specific entity and returns the
|
||||
* JSON representation of the rules text for a ibrary entry.
|
||||
*/
|
||||
export type LibraryEntryConfiguredCreator<R = LibraryEntry> = (params: {
|
||||
translate: Translate
|
||||
translateMap: TranslateMap
|
||||
localeCompare: Compare<string>
|
||||
}) => R | undefined
|
||||
|
||||
/**
|
||||
* A JSON representation of the rules text for a library entry.
|
||||
*/
|
||||
export type LibraryEntry = {
|
||||
title: string
|
||||
subtitle?: string
|
||||
className: string
|
||||
content: LibraryEntryContent[]
|
||||
src?: PublicationRefs
|
||||
}
|
||||
|
||||
/**
|
||||
* A JSON representation of the rules text for a library entry that has not been
|
||||
* cleaned up.
|
||||
*/
|
||||
export type RawLibraryEntry = {
|
||||
title: string
|
||||
subtitle?: string
|
||||
className: string
|
||||
content: (LibraryEntryContent | undefined)[]
|
||||
src?: PublicationRefs
|
||||
}
|
||||
|
||||
/**
|
||||
* A slice of the content of a library entry text.
|
||||
*/
|
||||
export type LibraryEntryContent = {
|
||||
label?: string
|
||||
value: string | number
|
||||
noIndent?: boolean
|
||||
className?: string
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
import {
|
||||
CastingTime,
|
||||
CastingTimeDuringLovemaking,
|
||||
FastCastingTime,
|
||||
FastSkillNonModifiableCastingTime,
|
||||
ModifiableCastingTime,
|
||||
SlowCastingTime,
|
||||
SlowSkillNonModifiableCastingTime,
|
||||
} from "optolith-database-schema/types/_ActivatableSkillCastingTime"
|
||||
import { isNotNullish, mapNullable } from "../../../utils/nullable.ts"
|
||||
import { Translate } from "../../../utils/translate.ts"
|
||||
import { assertExhaustive } from "../../../utils/typeSafety.ts"
|
||||
import { GetById } from "../../getTypes.ts"
|
||||
import { ResponsiveTextSize } from "../responsiveText.ts"
|
||||
import { MISSING_VALUE } from "../unknown.ts"
|
||||
import { Entity } from "./entity.ts"
|
||||
import { ModifiableParameter } from "./modifiableParameter.ts"
|
||||
import { getTextForNonModifiableSuffix } from "./nonModifiable.ts"
|
||||
import { Speed } from "./speed.ts"
|
||||
import { formatTimeSpan } from "./units.ts"
|
||||
|
||||
const getTextForModifiableCastingTime = (
|
||||
deps: {
|
||||
getSkillModificationLevelById: GetById.Static.SkillModificationLevel
|
||||
translate: Translate
|
||||
},
|
||||
value: ModifiableCastingTime,
|
||||
env: {
|
||||
speed: Speed
|
||||
responsiveText: ResponsiveTextSize
|
||||
},
|
||||
): string =>
|
||||
mapNullable(
|
||||
deps.getSkillModificationLevelById(value.initial_modification_level),
|
||||
({ fast: { casting_time: fastTime }, slow: { casting_time: slowTime } }) => {
|
||||
switch (env.speed) {
|
||||
case Speed.Fast:
|
||||
return formatTimeSpan(deps.translate, env.responsiveText, "Actions", fastTime)
|
||||
case Speed.Slow:
|
||||
return formatTimeSpan(deps.translate, env.responsiveText, slowTime.unit, slowTime.value)
|
||||
default:
|
||||
return assertExhaustive(env.speed)
|
||||
}
|
||||
},
|
||||
) ?? MISSING_VALUE
|
||||
|
||||
const getTextForFastSkillNonModifiableCastingTime = (
|
||||
deps: {
|
||||
translate: Translate
|
||||
},
|
||||
value: FastSkillNonModifiableCastingTime,
|
||||
env: {
|
||||
entity: Entity
|
||||
responsiveText: ResponsiveTextSize
|
||||
},
|
||||
): string =>
|
||||
formatTimeSpan(deps.translate, env.responsiveText, "Actions", value.actions) +
|
||||
getTextForNonModifiableSuffix(
|
||||
deps.translate,
|
||||
env.entity,
|
||||
ModifiableParameter.CastingTime,
|
||||
env.responsiveText,
|
||||
)
|
||||
|
||||
const getTextForSlowSkillNonModifiableCastingTime = (
|
||||
deps: {
|
||||
translate: Translate
|
||||
},
|
||||
value: SlowSkillNonModifiableCastingTime,
|
||||
env: {
|
||||
entity: Entity
|
||||
responsiveText: ResponsiveTextSize
|
||||
},
|
||||
): string =>
|
||||
formatTimeSpan(deps.translate, env.responsiveText, value.unit, value.value) +
|
||||
getTextForNonModifiableSuffix(
|
||||
deps.translate,
|
||||
env.entity,
|
||||
ModifiableParameter.CastingTime,
|
||||
env.responsiveText,
|
||||
)
|
||||
|
||||
const getTextForCastingTime = <NonModifiable extends object>(
|
||||
deps: {
|
||||
getSkillModificationLevelById: GetById.Static.SkillModificationLevel
|
||||
translate: Translate
|
||||
},
|
||||
value: CastingTime<NonModifiable>,
|
||||
env: {
|
||||
speed: Speed
|
||||
responsiveText: ResponsiveTextSize
|
||||
},
|
||||
getTextForNonModifiableCastingTime: (value: NonModifiable) => string,
|
||||
): string => {
|
||||
switch (value.tag) {
|
||||
case "Modifiable":
|
||||
return getTextForModifiableCastingTime(deps, value.modifiable, env)
|
||||
case "NonModifiable":
|
||||
return getTextForNonModifiableCastingTime(value.non_modifiable)
|
||||
default:
|
||||
return assertExhaustive(value)
|
||||
}
|
||||
}
|
||||
|
||||
const getTextForCastingTimeDuringLovemaking = (
|
||||
deps: {
|
||||
translate: Translate
|
||||
},
|
||||
value: CastingTimeDuringLovemaking,
|
||||
env: {
|
||||
responsiveText: ResponsiveTextSize
|
||||
},
|
||||
): string => formatTimeSpan(deps.translate, env.responsiveText, value.unit, value.value)
|
||||
|
||||
/**
|
||||
* Get the text for the casting time of a fast activatable skill.
|
||||
*/
|
||||
export const getTextForFastCastingTime = (
|
||||
deps: {
|
||||
getSkillModificationLevelById: GetById.Static.SkillModificationLevel
|
||||
translate: Translate
|
||||
},
|
||||
value: FastCastingTime,
|
||||
env: {
|
||||
entity: Entity
|
||||
responsiveText: ResponsiveTextSize
|
||||
},
|
||||
): string =>
|
||||
[
|
||||
mapNullable(value.default, def =>
|
||||
getTextForCastingTime(deps, def, { ...env, speed: Speed.Fast }, nonModifiableValue =>
|
||||
getTextForFastSkillNonModifiableCastingTime(deps, nonModifiableValue, env),
|
||||
),
|
||||
),
|
||||
mapNullable(value.during_lovemaking, duringLovemaking =>
|
||||
getTextForCastingTimeDuringLovemaking(deps, duringLovemaking, env),
|
||||
),
|
||||
]
|
||||
.filter(isNotNullish)
|
||||
.join(" / ")
|
||||
|
||||
/**
|
||||
* Get the text for the casting time of a slow activatable skill.
|
||||
*/
|
||||
export const getTextForSlowCastingTime = (
|
||||
deps: {
|
||||
getSkillModificationLevelById: GetById.Static.SkillModificationLevel
|
||||
translate: Translate
|
||||
},
|
||||
value: SlowCastingTime,
|
||||
env: {
|
||||
entity: Entity
|
||||
responsiveText: ResponsiveTextSize
|
||||
},
|
||||
): string =>
|
||||
[
|
||||
mapNullable(value.default, def =>
|
||||
getTextForCastingTime(deps, def, { ...env, speed: Speed.Slow }, nonModifiableValue =>
|
||||
getTextForSlowSkillNonModifiableCastingTime(deps, nonModifiableValue, env),
|
||||
),
|
||||
),
|
||||
mapNullable(value.during_lovemaking, duringLovemaking =>
|
||||
getTextForCastingTimeDuringLovemaking(deps, duringLovemaking, env),
|
||||
),
|
||||
]
|
||||
.filter(isNotNullish)
|
||||
.join(" / ")
|
||||
@@ -1,25 +0,0 @@
|
||||
import assert from "node:assert/strict"
|
||||
import { describe, it } from "node:test"
|
||||
import { translateMock } from "../../../utils/translate.ts"
|
||||
import { getTextForCheckResultBased } from "./checkResultBased.ts"
|
||||
|
||||
describe("getTextForCheckResultBased", () => {
|
||||
it("should return the value text for a check-result-based parameter of an activatable skill", () => {
|
||||
assert.equal(getTextForCheckResultBased({ base: "QualityLevels" }, translateMock), "QL")
|
||||
assert.equal(getTextForCheckResultBased({ base: "SkillPoints" }, translateMock), "SP")
|
||||
assert.equal(
|
||||
getTextForCheckResultBased(
|
||||
{ base: "QualityLevels", modifier: { arithmetic: "Divide", value: 2 } },
|
||||
translateMock,
|
||||
),
|
||||
"QL / 2",
|
||||
)
|
||||
assert.equal(
|
||||
getTextForCheckResultBased(
|
||||
{ base: "SkillPoints", modifier: { arithmetic: "Multiply", value: 3 } },
|
||||
translateMock,
|
||||
),
|
||||
"SP × 3",
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -1,42 +0,0 @@
|
||||
import {
|
||||
CheckResultArithmetic,
|
||||
CheckResultBased,
|
||||
CheckResultValue,
|
||||
} from "optolith-database-schema/types/_ActivatableSkillCheckResultBased"
|
||||
import { mapNullableDefault } from "../../../utils/nullable.ts"
|
||||
import { Translate } from "../../../utils/translate.ts"
|
||||
import { assertExhaustive } from "../../../utils/typeSafety.ts"
|
||||
|
||||
const getCheckResultBaseValue = (baseValue: CheckResultValue, translate: Translate) => {
|
||||
switch (baseValue) {
|
||||
case "QualityLevels":
|
||||
return translate("QL")
|
||||
case "SkillPoints":
|
||||
return translate("SP")
|
||||
default:
|
||||
return assertExhaustive(baseValue)
|
||||
}
|
||||
}
|
||||
|
||||
const getArithmeticSymbol = (arithmetic: CheckResultArithmetic) => {
|
||||
switch (arithmetic) {
|
||||
case "Divide":
|
||||
return ` / `
|
||||
case "Multiply":
|
||||
return ` × `
|
||||
default:
|
||||
return assertExhaustive(arithmetic)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value text for a check-result-based parameter of an activatable
|
||||
* skill.
|
||||
*/
|
||||
export const getTextForCheckResultBased = (value: CheckResultBased, translate: Translate): string =>
|
||||
getCheckResultBaseValue(value.base, translate) +
|
||||
mapNullableDefault(
|
||||
value.modifier,
|
||||
modifier => getArithmeticSymbol(modifier.arithmetic) + modifier.value,
|
||||
"",
|
||||
)
|
||||
@@ -1,456 +0,0 @@
|
||||
import {
|
||||
CostMap,
|
||||
IndefiniteOneTimeCost,
|
||||
ModifiableOneTimeCost,
|
||||
MultipleOneTimeCosts,
|
||||
NonModifiableOneTimeCost,
|
||||
NonModifiableOneTimeCostPerCountable,
|
||||
OneTimeCost,
|
||||
SingleOneTimeCost,
|
||||
SustainedCost,
|
||||
} from "optolith-database-schema/types/_ActivatableSkillCost"
|
||||
import { mapNullable, mapNullableDefault } from "../../../utils/nullable.ts"
|
||||
import { Translate, TranslateMap } from "../../../utils/translate.ts"
|
||||
import { assertExhaustive } from "../../../utils/typeSafety.ts"
|
||||
import { GetById } from "../../getTypes.ts"
|
||||
import {
|
||||
getResponsiveText,
|
||||
getResponsiveTextOptional,
|
||||
replaceTextIfRequested,
|
||||
responsive,
|
||||
ResponsiveTextSize,
|
||||
} from "../responsiveText.ts"
|
||||
import { MISSING_VALUE } from "../unknown.ts"
|
||||
import { Entity } from "./entity.ts"
|
||||
import { ModifiableParameter } from "./modifiableParameter.ts"
|
||||
import { getTextForNonModifiableSuffix } from "./nonModifiable.ts"
|
||||
import { getModifiableBySpeed, Speed } from "./speed.ts"
|
||||
import { formatCost, formatTimeSpan } from "./units.ts"
|
||||
|
||||
const getTextForModifiableOneTimeCost = (
|
||||
deps: {
|
||||
getSkillModificationLevelById: GetById.Static.SkillModificationLevel
|
||||
translate: Translate
|
||||
translateMap: TranslateMap
|
||||
},
|
||||
value: ModifiableOneTimeCost,
|
||||
env: {
|
||||
speed: Speed
|
||||
entity: Entity
|
||||
responsiveText: ResponsiveTextSize
|
||||
},
|
||||
): string =>
|
||||
mapNullable(
|
||||
deps.getSkillModificationLevelById(value.initial_modification_level),
|
||||
modificationLevel => {
|
||||
const cost = getModifiableBySpeed(
|
||||
modificationLevel,
|
||||
env.speed,
|
||||
config => config.cost,
|
||||
config => config.cost,
|
||||
)
|
||||
|
||||
return replaceTextIfRequested(
|
||||
value.translations,
|
||||
formatCost(deps.translate, env.entity, cost),
|
||||
deps.translateMap,
|
||||
env.responsiveText,
|
||||
)
|
||||
},
|
||||
) ?? MISSING_VALUE
|
||||
|
||||
const getMinimumText = (
|
||||
isMinimum: boolean | undefined,
|
||||
translate: Translate,
|
||||
responsiveText: ResponsiveTextSize,
|
||||
) =>
|
||||
isMinimum !== true
|
||||
? ""
|
||||
: responsive(
|
||||
responsiveText,
|
||||
() => translate("at least "),
|
||||
() => translate("min. "),
|
||||
)
|
||||
|
||||
const getTextForNonModifiableOneTimeCostPerCountable = (
|
||||
deps: {
|
||||
translate: Translate
|
||||
translateMap: TranslateMap
|
||||
formatCost: (x: number | string) => string
|
||||
},
|
||||
value: NonModifiableOneTimeCostPerCountable | undefined,
|
||||
env: {
|
||||
responsiveText: ResponsiveTextSize
|
||||
},
|
||||
) =>
|
||||
mapNullable(value, perCountable => {
|
||||
const countableText = responsive(
|
||||
env.responsiveText,
|
||||
entity => deps.translate(" per {0}", entity),
|
||||
entity => deps.translate("/{0}", entity),
|
||||
getResponsiveText(
|
||||
deps.translateMap(perCountable.translations)?.countable,
|
||||
env.responsiveText,
|
||||
),
|
||||
)
|
||||
|
||||
const minimumTotalText =
|
||||
mapNullable(perCountable.minimum_total, minimumTotal =>
|
||||
deps.translate(", minimum of {0}", deps.formatCost(minimumTotal)),
|
||||
) ?? ""
|
||||
|
||||
return countableText + minimumTotalText
|
||||
}) ?? ""
|
||||
|
||||
const getTextForPermanentValue = (
|
||||
value: number | undefined,
|
||||
responsiveText: ResponsiveTextSize,
|
||||
translate: Translate,
|
||||
) =>
|
||||
value === undefined
|
||||
? ""
|
||||
: responsive(
|
||||
responsiveText,
|
||||
perm => translate(", {0} of which are permanent", perm),
|
||||
perm => translate(" ({0} perm.)", perm),
|
||||
value,
|
||||
)
|
||||
|
||||
const getTextForNonModifiableOneTimeCost = (
|
||||
deps: { translate: Translate; translateMap: TranslateMap },
|
||||
value: NonModifiableOneTimeCost,
|
||||
env: {
|
||||
responsiveText: ResponsiveTextSize
|
||||
entity: Entity
|
||||
},
|
||||
): string => {
|
||||
const isMinimum = getMinimumText(value.is_minimum, deps.translate, env.responsiveText)
|
||||
const formatCostP = formatCost.bind(this, deps.translate, env.entity)
|
||||
const per = getTextForNonModifiableOneTimeCostPerCountable(
|
||||
{ ...deps, formatCost: formatCostP },
|
||||
value.per,
|
||||
env,
|
||||
)
|
||||
const permanent = getTextForPermanentValue(
|
||||
value.permanent_value,
|
||||
env.responsiveText,
|
||||
deps.translate,
|
||||
)
|
||||
const translation = deps.translateMap(value.translations)
|
||||
const note = mapNullableDefault(
|
||||
translation === undefined || translation.note === undefined
|
||||
? undefined
|
||||
: getResponsiveTextOptional(translation.note, env.responsiveText),
|
||||
noteIfPresent => ` (${noteIfPresent})`,
|
||||
"",
|
||||
)
|
||||
|
||||
const cannotModify = getTextForNonModifiableSuffix(
|
||||
deps.translate,
|
||||
env.entity,
|
||||
ModifiableParameter.Cost,
|
||||
env.responsiveText,
|
||||
)
|
||||
|
||||
return isMinimum + formatCostP(value.value) + per + permanent + note + cannotModify
|
||||
}
|
||||
|
||||
const getTextForIndefiniteOneTimeCost = (
|
||||
deps: { translate: Translate; translateMap: TranslateMap },
|
||||
value: IndefiniteOneTimeCost,
|
||||
env: {
|
||||
responsiveText: ResponsiveTextSize
|
||||
entity: Entity
|
||||
},
|
||||
): string =>
|
||||
(getResponsiveText(deps.translateMap(value.translations)?.description, env.responsiveText) ??
|
||||
"") +
|
||||
getTextForNonModifiableSuffix(
|
||||
deps.translate,
|
||||
env.entity,
|
||||
ModifiableParameter.Cost,
|
||||
env.responsiveText,
|
||||
)
|
||||
|
||||
const getTextForSingleOneTimeCost = (
|
||||
deps: {
|
||||
getSkillModificationLevelById: GetById.Static.SkillModificationLevel
|
||||
translate: Translate
|
||||
translateMap: TranslateMap
|
||||
},
|
||||
value: SingleOneTimeCost,
|
||||
env: {
|
||||
speed: Speed
|
||||
responsiveText: ResponsiveTextSize
|
||||
entity: Entity
|
||||
},
|
||||
): string => {
|
||||
switch (value.tag) {
|
||||
case "Modifiable":
|
||||
return getTextForModifiableOneTimeCost(deps, value.modifiable, env)
|
||||
case "NonModifiable":
|
||||
return getTextForNonModifiableOneTimeCost(deps, value.non_modifiable, env)
|
||||
case "Indefinite":
|
||||
return getTextForIndefiniteOneTimeCost(deps, value.indefinite, env)
|
||||
default:
|
||||
return assertExhaustive(value)
|
||||
}
|
||||
}
|
||||
|
||||
const getTextForMultipleOneTimeCosts = (
|
||||
deps: {
|
||||
getSkillModificationLevelById: GetById.Static.SkillModificationLevel
|
||||
translate: Translate
|
||||
translateMap: TranslateMap
|
||||
},
|
||||
value: MultipleOneTimeCosts,
|
||||
type: "conjunction" | "disjunction",
|
||||
env: {
|
||||
speed: Speed
|
||||
responsiveText: ResponsiveTextSize
|
||||
entity: Entity
|
||||
},
|
||||
): string => {
|
||||
const modifiable = !value.every(part => part.tag === "Modifiable")
|
||||
? getTextForNonModifiableSuffix(
|
||||
deps.translate,
|
||||
env.entity,
|
||||
ModifiableParameter.Cost,
|
||||
env.responsiveText,
|
||||
)
|
||||
: ""
|
||||
|
||||
return (
|
||||
value
|
||||
.map(part => getTextForSingleOneTimeCost(deps, part, env))
|
||||
.join(
|
||||
(() => {
|
||||
switch (type) {
|
||||
case "conjunction":
|
||||
return responsive(
|
||||
env.responsiveText,
|
||||
() => deps.translate(" and "),
|
||||
() => deps.translate(" + "),
|
||||
)
|
||||
case "disjunction":
|
||||
return responsive(
|
||||
env.responsiveText,
|
||||
() => deps.translate(" or "),
|
||||
() => deps.translate(" / "),
|
||||
)
|
||||
default:
|
||||
return assertExhaustive(type)
|
||||
}
|
||||
})(),
|
||||
) + modifiable
|
||||
)
|
||||
}
|
||||
|
||||
const getTextForCostMap = (
|
||||
deps: {
|
||||
translate: Translate
|
||||
translateMap: TranslateMap
|
||||
},
|
||||
value: CostMap,
|
||||
env: {
|
||||
responsiveText: ResponsiveTextSize
|
||||
entity: Entity
|
||||
},
|
||||
): string => {
|
||||
const translation = deps.translateMap(value.translations)
|
||||
|
||||
if (value.translations !== undefined && translation === undefined) {
|
||||
return MISSING_VALUE
|
||||
}
|
||||
|
||||
if (translation?.replacement !== undefined) {
|
||||
return translation.replacement
|
||||
}
|
||||
|
||||
const costs = value.options.map(option => option.value).join("/")
|
||||
const labels = value.options
|
||||
.map(option => deps.translateMap(option.translations)?.label ?? MISSING_VALUE)
|
||||
.join("/")
|
||||
const permanentCosts = value.options.every(option => option.permanent_value !== undefined)
|
||||
? value.options.map(option => option.permanent_value!).join("/")
|
||||
: undefined
|
||||
|
||||
const formatCostP = formatCost.bind(this, deps.translate, env.entity)
|
||||
const notModifiable = getTextForNonModifiableSuffix(
|
||||
deps.translate,
|
||||
env.entity,
|
||||
ModifiableParameter.Cost,
|
||||
env.responsiveText,
|
||||
)
|
||||
|
||||
return (
|
||||
formatCostP(costs) +
|
||||
deps.translate(" for ") +
|
||||
mapNullableDefault(translation?.list_prepend, listPrepend => `${listPrepend} `, "") +
|
||||
labels +
|
||||
(translation?.list_append ?? "") +
|
||||
(permanentCosts !== undefined
|
||||
? deps.translate(", {0} of which are permanent", formatCostP(permanentCosts))
|
||||
: "") +
|
||||
notModifiable
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the text for the cost of a one-time activatable skill.
|
||||
*/
|
||||
export const getTextForOneTimeCost = (
|
||||
deps: {
|
||||
getSkillModificationLevelById: GetById.Static.SkillModificationLevel
|
||||
translate: Translate
|
||||
translateMap: TranslateMap
|
||||
},
|
||||
value: OneTimeCost,
|
||||
env: {
|
||||
speed: Speed
|
||||
responsiveText: ResponsiveTextSize
|
||||
entity: Entity
|
||||
},
|
||||
): string => {
|
||||
switch (value.tag) {
|
||||
case "Single":
|
||||
return getTextForSingleOneTimeCost(deps, value.single, env)
|
||||
case "Conjunction":
|
||||
return getTextForMultipleOneTimeCosts(deps, value.conjunction, "conjunction", env)
|
||||
case "Disjunction":
|
||||
return getTextForMultipleOneTimeCosts(deps, value.disjunction, "disjunction", env)
|
||||
case "Map":
|
||||
return getTextForCostMap(deps, value.map, env)
|
||||
default:
|
||||
return assertExhaustive(value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the text for the cost of a sustained activatable skill.
|
||||
*/
|
||||
export const getTextForSustainedCost = (
|
||||
deps: {
|
||||
getSkillModificationLevelById: GetById.Static.SkillModificationLevel
|
||||
translate: Translate
|
||||
translateMap: TranslateMap
|
||||
},
|
||||
value: SustainedCost,
|
||||
env: {
|
||||
speed: Speed
|
||||
responsiveText: ResponsiveTextSize
|
||||
entity: Entity
|
||||
},
|
||||
): string => {
|
||||
switch (value.tag) {
|
||||
case "Modifiable": {
|
||||
const modificationLevel = deps.getSkillModificationLevelById(
|
||||
value.modifiable.initial_modification_level,
|
||||
)
|
||||
|
||||
if (modificationLevel === undefined) {
|
||||
return MISSING_VALUE
|
||||
}
|
||||
|
||||
const cost = (() => {
|
||||
switch (env.speed) {
|
||||
case Speed.Fast:
|
||||
return modificationLevel.fast.cost
|
||||
case Speed.Slow:
|
||||
return modificationLevel.slow.cost
|
||||
default:
|
||||
return assertExhaustive(env.speed)
|
||||
}
|
||||
})()
|
||||
|
||||
const formatCostP = formatCost.bind(this, deps.translate, env.entity)
|
||||
const interval = formatTimeSpan(
|
||||
deps.translate,
|
||||
env.responsiveText,
|
||||
value.modifiable.interval.unit,
|
||||
value.modifiable.interval.value,
|
||||
)
|
||||
|
||||
return responsive(
|
||||
env.responsiveText,
|
||||
() =>
|
||||
`${formatCostP(cost) + deps.translate(" (casting)")} + ${
|
||||
formatCostP(cost / 2) + deps.translate(" per {0}", interval)
|
||||
}`,
|
||||
() => `${formatCostP(cost)} + ${formatCostP(cost / 2) + deps.translate("/{0}", interval)}`,
|
||||
)
|
||||
}
|
||||
case "NonModifiable": {
|
||||
const isMinimum = getMinimumText(
|
||||
value.non_modifiable.is_minimum,
|
||||
deps.translate,
|
||||
env.responsiveText,
|
||||
)
|
||||
const cost = value.non_modifiable.value
|
||||
const formatCostP = formatCost.bind(this, deps.translate, env.entity)
|
||||
|
||||
const per = (() => {
|
||||
if (value.non_modifiable.per === undefined) {
|
||||
return { countable: "", minimumTotal: "" }
|
||||
}
|
||||
|
||||
const countable = responsive(
|
||||
env.responsiveText,
|
||||
entity => deps.translate(" per {0}", entity),
|
||||
entity => deps.translate("/{0}", entity),
|
||||
getResponsiveText(
|
||||
deps.translateMap(value.non_modifiable.per.translations)?.countable,
|
||||
env.responsiveText,
|
||||
),
|
||||
)
|
||||
|
||||
const minimumTotal =
|
||||
value.non_modifiable.per.minimum_total !== undefined
|
||||
? deps.translate(
|
||||
", minimum of {0}",
|
||||
formatCostP(value.non_modifiable.per.minimum_total),
|
||||
)
|
||||
: ""
|
||||
|
||||
return { countable, minimumTotal }
|
||||
})()
|
||||
|
||||
const interval = formatTimeSpan(
|
||||
deps.translate,
|
||||
env.responsiveText,
|
||||
value.non_modifiable.interval.unit,
|
||||
value.non_modifiable.interval.value,
|
||||
)
|
||||
|
||||
return (
|
||||
isMinimum +
|
||||
responsive(
|
||||
env.responsiveText,
|
||||
() =>
|
||||
`${formatCostP(cost) + deps.translate(" (casting)")} + ${
|
||||
(value.non_modifiable.is_minimum === true
|
||||
? deps.translate("half of the activation cost")
|
||||
: formatCostP(cost / 2)) +
|
||||
per.countable +
|
||||
deps.translate(" per {0}", interval)
|
||||
}`,
|
||||
() =>
|
||||
`${formatCostP(cost)} + ${
|
||||
(value.non_modifiable.is_minimum === true ? "50%" : formatCostP(cost / 2)) +
|
||||
per.countable +
|
||||
deps.translate("/{0}", interval)
|
||||
}`,
|
||||
) +
|
||||
per.minimumTotal +
|
||||
getTextForNonModifiableSuffix(
|
||||
deps.translate,
|
||||
env.entity,
|
||||
ModifiableParameter.Cost,
|
||||
env.responsiveText,
|
||||
)
|
||||
)
|
||||
}
|
||||
default:
|
||||
return assertExhaustive(value)
|
||||
}
|
||||
}
|
||||
@@ -1,222 +0,0 @@
|
||||
import {
|
||||
CheckResultBasedDuration,
|
||||
DurationForOneTime,
|
||||
DurationForSustained,
|
||||
FixedDuration,
|
||||
Immediate,
|
||||
PermanentDuration,
|
||||
} from "optolith-database-schema/types/_ActivatableSkillDuration"
|
||||
import { BlessingDuration } from "optolith-database-schema/types/Blessing"
|
||||
import { CantripDuration } from "optolith-database-schema/types/Cantrip"
|
||||
import { mapNullableDefault } from "../../../utils/nullable.ts"
|
||||
import { Translate, TranslateMap } from "../../../utils/translate.ts"
|
||||
import { assertExhaustive } from "../../../utils/typeSafety.ts"
|
||||
import {
|
||||
ResponsiveTextSize,
|
||||
getResponsiveText,
|
||||
replaceTextIfRequested,
|
||||
responsive,
|
||||
} from "../responsiveText.ts"
|
||||
import { getTextForCheckResultBased } from "./checkResultBased.ts"
|
||||
import { getTextForIsMaximum } from "./isMaximum.ts"
|
||||
import { formatTimeSpan } from "./units.ts"
|
||||
|
||||
const getTextForImmediateDuration = (
|
||||
deps: {
|
||||
translate: Translate
|
||||
translateMap: TranslateMap
|
||||
},
|
||||
value: Immediate,
|
||||
env: {
|
||||
responsiveText: ResponsiveTextSize
|
||||
},
|
||||
): string => {
|
||||
const text =
|
||||
deps.translate("Immediate") +
|
||||
mapNullableDefault(
|
||||
value.maximum,
|
||||
max => {
|
||||
const maxText = formatTimeSpan(deps.translate, env.responsiveText, max.unit, max.value)
|
||||
|
||||
return responsive(
|
||||
env.responsiveText,
|
||||
() => deps.translate(" (no more than {0})", maxText),
|
||||
() => deps.translate(" (max. {0})", maxText),
|
||||
)
|
||||
},
|
||||
"",
|
||||
)
|
||||
|
||||
return replaceTextIfRequested(value.translations, text, deps.translateMap, env.responsiveText)
|
||||
}
|
||||
|
||||
const getTextForPermanentDuration = (
|
||||
deps: {
|
||||
translate: Translate
|
||||
translateMap: TranslateMap
|
||||
},
|
||||
value: PermanentDuration,
|
||||
env: {
|
||||
responsiveText: ResponsiveTextSize
|
||||
},
|
||||
): string => {
|
||||
const translation = deps.translateMap(value.translations)
|
||||
const text = deps.translate("Permanent")
|
||||
|
||||
if (translation?.replacement !== undefined) {
|
||||
return getResponsiveText(translation.replacement, env.responsiveText).replace("$1", text)
|
||||
} else {
|
||||
return text
|
||||
}
|
||||
}
|
||||
|
||||
const getTextForFixedDuration = (
|
||||
deps: {
|
||||
translate: Translate
|
||||
translateMap: TranslateMap
|
||||
},
|
||||
value: FixedDuration,
|
||||
env: {
|
||||
responsiveText: ResponsiveTextSize
|
||||
},
|
||||
): string => {
|
||||
const isMaximum = getTextForIsMaximum(value.is_maximum, deps.translate, env.responsiveText)
|
||||
const unitValue = formatTimeSpan(deps.translate, env.responsiveText, value.unit, value.value)
|
||||
const text = isMaximum + unitValue
|
||||
const translation = deps.translateMap(value.translations)
|
||||
|
||||
if (translation?.replacement !== undefined) {
|
||||
return getResponsiveText(translation.replacement, env.responsiveText).replace("$1", text)
|
||||
} else {
|
||||
return text
|
||||
}
|
||||
}
|
||||
|
||||
const getTextForCheckResultBasedDuration = (
|
||||
deps: {
|
||||
translate: Translate
|
||||
translateMap: TranslateMap
|
||||
},
|
||||
value: CheckResultBasedDuration,
|
||||
env: {
|
||||
responsiveText: ResponsiveTextSize
|
||||
},
|
||||
): string => {
|
||||
const isMaximum = getTextForIsMaximum(value.is_maximum, deps.translate, env.responsiveText)
|
||||
|
||||
return formatTimeSpan(
|
||||
deps.translate,
|
||||
env.responsiveText,
|
||||
value.unit,
|
||||
isMaximum + getTextForCheckResultBased(value, deps.translate),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the text for the duration of a one-time activatable skill.
|
||||
*/
|
||||
export const getTextForDurationForOneTime = (
|
||||
deps: {
|
||||
translate: Translate
|
||||
translateMap: TranslateMap
|
||||
},
|
||||
value: DurationForOneTime,
|
||||
env: {
|
||||
responsiveText: ResponsiveTextSize
|
||||
},
|
||||
): string => {
|
||||
switch (value.tag) {
|
||||
case "Immediate":
|
||||
return getTextForImmediateDuration(deps, value.immediate, env)
|
||||
case "Permanent":
|
||||
return getTextForPermanentDuration(deps, value.permanent, env)
|
||||
case "Fixed":
|
||||
return getTextForFixedDuration(deps, value.fixed, env)
|
||||
case "CheckResultBased":
|
||||
return getTextForCheckResultBasedDuration(deps, value.check_result_based, env)
|
||||
case "Indefinite":
|
||||
return getResponsiveText(
|
||||
deps.translateMap(value.indefinite.translations)?.description,
|
||||
env.responsiveText,
|
||||
)
|
||||
default:
|
||||
return assertExhaustive(value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the text for the duration of a sustained activatable skill.
|
||||
*/
|
||||
export const getTextForDurationForSustained = (
|
||||
deps: { translate: Translate },
|
||||
value: DurationForSustained | undefined,
|
||||
env: {
|
||||
responsiveText: ResponsiveTextSize
|
||||
},
|
||||
): string =>
|
||||
value === undefined
|
||||
? responsive(
|
||||
env.responsiveText,
|
||||
() => deps.translate("Sustained"),
|
||||
() => deps.translate("(S)"),
|
||||
)
|
||||
: responsive(
|
||||
env.responsiveText,
|
||||
() => deps.translate("no more than "),
|
||||
() => deps.translate("max. "),
|
||||
) +
|
||||
formatTimeSpan(deps.translate, env.responsiveText, value.maximum.unit, value.maximum.value)
|
||||
|
||||
/**
|
||||
* Returns the text for the duration of a cantrip.
|
||||
*/
|
||||
export const getTextForCantripDuration = (
|
||||
deps: { translate: Translate; translateMap: TranslateMap },
|
||||
value: CantripDuration,
|
||||
env: {
|
||||
responsiveText: ResponsiveTextSize
|
||||
},
|
||||
): string => {
|
||||
switch (value.tag) {
|
||||
case "Immediate":
|
||||
return getTextForImmediateDuration(deps, value.immediate, env)
|
||||
case "Fixed":
|
||||
return getTextForFixedDuration(deps, value.fixed, env)
|
||||
case "Indefinite":
|
||||
return getResponsiveText(
|
||||
deps.translateMap(value.indefinite.translations)?.description,
|
||||
env.responsiveText,
|
||||
)
|
||||
case "DuringLovemaking": {
|
||||
const { value: lovemakingValue, unit: lovemakingUnit } = value.during_lovemaking
|
||||
return formatTimeSpan(deps.translate, env.responsiveText, lovemakingUnit, lovemakingValue)
|
||||
}
|
||||
default:
|
||||
return assertExhaustive(value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the text for the duration of a blessing.
|
||||
*/
|
||||
export const getTextForBlessingDuration = (
|
||||
deps: { translate: Translate; translateMap: TranslateMap },
|
||||
value: BlessingDuration,
|
||||
env: {
|
||||
responsiveText: ResponsiveTextSize
|
||||
},
|
||||
): string => {
|
||||
switch (value.tag) {
|
||||
case "Immediate":
|
||||
return getTextForImmediateDuration(deps, value.immediate, env)
|
||||
case "Fixed":
|
||||
return getTextForFixedDuration(deps, value.fixed, env)
|
||||
case "Indefinite":
|
||||
return getResponsiveText(
|
||||
deps.translateMap(value.indefinite.translations)?.description,
|
||||
env.responsiveText,
|
||||
)
|
||||
default:
|
||||
return assertExhaustive(value)
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
import { Effect } from "optolith-database-schema/types/_ActivatableSkillEffect"
|
||||
import { filterNonNullable } from "../../../utils/array.ts"
|
||||
import { mapNullable } from "../../../utils/nullable.ts"
|
||||
import { Translate } from "../../../utils/translate.ts"
|
||||
import { assertExhaustive } from "../../../utils/typeSafety.ts"
|
||||
import { LibraryEntryContent } from "../../libraryEntry.ts"
|
||||
|
||||
const getContentPartsForQualityLevels = (
|
||||
source: {
|
||||
text_before: string
|
||||
quality_levels: string[]
|
||||
text_after?: string
|
||||
},
|
||||
getQualityLevelString: (index: number) => string | number,
|
||||
translate: Translate,
|
||||
): LibraryEntryContent[] =>
|
||||
filterNonNullable([
|
||||
{
|
||||
label: translate("Effect"),
|
||||
value: source.text_before,
|
||||
},
|
||||
...source.quality_levels.map((text, index) => ({
|
||||
value: text,
|
||||
label: translate("QL {0}", 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 = (effect: Effect, translate: Translate): LibraryEntryContent[] => {
|
||||
switch (effect.tag) {
|
||||
case "Plain":
|
||||
return [
|
||||
{
|
||||
label: translate("Effect"),
|
||||
value: effect.plain.text,
|
||||
},
|
||||
]
|
||||
case "ForEachQualityLevel":
|
||||
return getContentPartsForQualityLevels(
|
||||
effect.for_each_quality_level,
|
||||
index => index + 1,
|
||||
translate,
|
||||
)
|
||||
case "ForEachTwoQualityLevels":
|
||||
return getContentPartsForQualityLevels(
|
||||
effect.for_each_two_quality_levels,
|
||||
index => `${index * 2 + 1}–${index * 2 + 2}`,
|
||||
translate,
|
||||
)
|
||||
default:
|
||||
return assertExhaustive(effect)
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
/**
|
||||
* The entity type of an activatable skill.
|
||||
*/
|
||||
export enum Entity {
|
||||
Cantrip,
|
||||
Spell,
|
||||
Ritual,
|
||||
Blessing,
|
||||
LiturgicalChant,
|
||||
Ceremony,
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import { Translate } from "../../../utils/translate.ts"
|
||||
import { ResponsiveTextSize, responsive } from "../responsiveText.ts"
|
||||
|
||||
/**
|
||||
* Returns the text to prepend for the `is_maximum` property.
|
||||
*/
|
||||
export const getTextForIsMaximum = (
|
||||
is_maximum: boolean | undefined,
|
||||
translate: Translate,
|
||||
responsiveText: ResponsiveTextSize,
|
||||
): string => {
|
||||
if (is_maximum !== true) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return responsive(
|
||||
responsiveText,
|
||||
() => translate("no more than "),
|
||||
() => translate("max. "),
|
||||
)
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
/**
|
||||
* A parameter that is designed to be modifiable.
|
||||
*/
|
||||
export enum ModifiableParameter {
|
||||
CastingTime,
|
||||
Cost,
|
||||
Range,
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
import { Translate } from "../../../utils/translate.ts"
|
||||
import { assertExhaustive } from "../../../utils/typeSafety.ts"
|
||||
import { ResponsiveTextSize } from "../responsiveText.ts"
|
||||
import { Entity } from "./entity.ts"
|
||||
import { ModifiableParameter } from "./modifiableParameter.ts"
|
||||
|
||||
/**
|
||||
* Returns the suffix for the text of a non-modifiable parameter that indicates
|
||||
* that the parameter cannot be modified.
|
||||
*/
|
||||
export const getTextForNonModifiableSuffix = (
|
||||
translate: Translate,
|
||||
entity: Entity,
|
||||
param: ModifiableParameter,
|
||||
responsiveText: ResponsiveTextSize,
|
||||
): string => {
|
||||
if (responsiveText === ResponsiveTextSize.Compressed) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
switch (entity) {
|
||||
case Entity.Spell:
|
||||
switch (param) {
|
||||
case ModifiableParameter.CastingTime:
|
||||
return translate(" (you cannot use a modification on this spell’s casting time)")
|
||||
case ModifiableParameter.Cost:
|
||||
return translate(" (you cannot use a modification on this spell’s cost)")
|
||||
case ModifiableParameter.Range:
|
||||
return translate(" (you cannot use a modification on this spell’s range)")
|
||||
default:
|
||||
return assertExhaustive(param)
|
||||
}
|
||||
case Entity.Ritual:
|
||||
switch (param) {
|
||||
case ModifiableParameter.CastingTime:
|
||||
return translate(" (you cannot use a modification on this ritual’s ritual time)")
|
||||
case ModifiableParameter.Cost:
|
||||
return translate(" (you cannot use a modification on this ritual’s cost)")
|
||||
case ModifiableParameter.Range:
|
||||
return translate(" (you cannot use a modification on this ritual’s range)")
|
||||
default:
|
||||
return assertExhaustive(param)
|
||||
}
|
||||
case Entity.LiturgicalChant:
|
||||
switch (param) {
|
||||
case ModifiableParameter.CastingTime:
|
||||
return translate(" (you cannot use a modification on this chant’s liturgical time)")
|
||||
case ModifiableParameter.Cost:
|
||||
return translate(" (you cannot use a modification on this chant’s cost)")
|
||||
case ModifiableParameter.Range:
|
||||
return translate(" (you cannot use a modification on this chant’s range)")
|
||||
default:
|
||||
return assertExhaustive(param)
|
||||
}
|
||||
case Entity.Ceremony:
|
||||
switch (param) {
|
||||
case ModifiableParameter.CastingTime:
|
||||
return translate(" (you cannot use a modification on this ceremony’s ceremonial time)")
|
||||
case ModifiableParameter.Cost:
|
||||
return translate(" (you cannot use a modification on this ceremony’s cost)")
|
||||
case ModifiableParameter.Range:
|
||||
return translate(" (you cannot use a modification on this ceremony’s range)")
|
||||
default:
|
||||
return assertExhaustive(param)
|
||||
}
|
||||
case Entity.Cantrip:
|
||||
case Entity.Blessing:
|
||||
return ""
|
||||
default:
|
||||
return assertExhaustive(entity)
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
/**
|
||||
* Wraps a string in parentheses with a leading space if it is not empty or
|
||||
* `undefined`.
|
||||
*/
|
||||
export const parensIf = (text: string | undefined): string =>
|
||||
text === undefined || text === "" ? "" : ` (${text})`
|
||||
|
||||
/**
|
||||
* Appends a string in parentheses with a leading space if it is not empty or
|
||||
* `undefined`.
|
||||
*/
|
||||
export const appendInParens = (text: string, append: string | undefined): string =>
|
||||
append === undefined || append === "" ? text : `${text} (${append})`
|
||||
@@ -1,221 +0,0 @@
|
||||
import { Range, RangeUnit } from "optolith-database-schema/types/_ActivatableSkillRange"
|
||||
import { BlessingRange } from "optolith-database-schema/types/Blessing"
|
||||
import { CantripRange } from "optolith-database-schema/types/Cantrip"
|
||||
import { Translate, TranslateMap } from "../../../utils/translate.ts"
|
||||
import { assertExhaustive } from "../../../utils/typeSafety.ts"
|
||||
import { GetById } from "../../getTypes.ts"
|
||||
import {
|
||||
getResponsiveText,
|
||||
getResponsiveTextOptional,
|
||||
responsive,
|
||||
ResponsiveTextSize,
|
||||
} from "../responsiveText.ts"
|
||||
import { MISSING_VALUE } from "../unknown.ts"
|
||||
import { getTextForCheckResultBased } from "./checkResultBased.ts"
|
||||
import { Entity } from "./entity.ts"
|
||||
import { getTextForIsMaximum } from "./isMaximum.ts"
|
||||
import { ModifiableParameter } from "./modifiableParameter.ts"
|
||||
import { getTextForNonModifiableSuffix } from "./nonModifiable.ts"
|
||||
import { Speed } from "./speed.ts"
|
||||
|
||||
const toRangeUnit = (
|
||||
unit: RangeUnit,
|
||||
value: number | string,
|
||||
translate: Translate,
|
||||
responsiveText: ResponsiveTextSize,
|
||||
) => {
|
||||
switch (unit) {
|
||||
case "Steps":
|
||||
return responsive(
|
||||
responsiveText,
|
||||
() => translate("{0} yards", value),
|
||||
() => translate("{0} yd", value),
|
||||
)
|
||||
case "Miles":
|
||||
return responsive(
|
||||
responsiveText,
|
||||
() => translate("{0} miles", value),
|
||||
() => translate("{0} mi.", value),
|
||||
)
|
||||
default:
|
||||
return assertExhaustive(unit)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the text for the range of an activatable skill.
|
||||
*/
|
||||
export const getTextForActivatableSkillRange = (
|
||||
deps: {
|
||||
getSkillModificationLevelById: GetById.Static.SkillModificationLevel
|
||||
translate: Translate
|
||||
translateMap: TranslateMap
|
||||
},
|
||||
value: Range,
|
||||
env: {
|
||||
speed: Speed
|
||||
responsiveText: ResponsiveTextSize
|
||||
entity: Entity
|
||||
},
|
||||
): string => {
|
||||
const translation = deps.translateMap(value.translations)
|
||||
|
||||
if (value.translations !== undefined && translation === undefined) {
|
||||
return MISSING_VALUE
|
||||
}
|
||||
|
||||
const rangeValue = (() => {
|
||||
switch (value.value.tag) {
|
||||
case "Modifiable": {
|
||||
const modificationLevel = deps.getSkillModificationLevelById(
|
||||
value.value.modifiable.initial_modification_level,
|
||||
)
|
||||
|
||||
if (modificationLevel === undefined) {
|
||||
return MISSING_VALUE
|
||||
}
|
||||
|
||||
const range = (() => {
|
||||
switch (env.speed) {
|
||||
case Speed.Fast:
|
||||
return modificationLevel.fast.range
|
||||
case Speed.Slow:
|
||||
return modificationLevel.slow.range
|
||||
default:
|
||||
return assertExhaustive(env.speed)
|
||||
}
|
||||
})()
|
||||
|
||||
if (range === 1) {
|
||||
return deps.translate("Touch")
|
||||
}
|
||||
|
||||
return toRangeUnit("Steps", range, deps.translate, env.responsiveText)
|
||||
}
|
||||
case "Sight":
|
||||
return deps.translate("Sight")
|
||||
case "Self":
|
||||
return deps.translate("Self")
|
||||
case "Global":
|
||||
return deps.translate("Global")
|
||||
case "Touch":
|
||||
return (
|
||||
deps.translate("Touch") +
|
||||
getTextForNonModifiableSuffix(
|
||||
deps.translate,
|
||||
env.entity,
|
||||
ModifiableParameter.Range,
|
||||
env.responsiveText,
|
||||
)
|
||||
)
|
||||
case "Fixed": {
|
||||
return (
|
||||
toRangeUnit(
|
||||
value.value.fixed.unit,
|
||||
value.value.fixed.value,
|
||||
deps.translate,
|
||||
env.responsiveText,
|
||||
) +
|
||||
getTextForNonModifiableSuffix(
|
||||
deps.translate,
|
||||
env.entity,
|
||||
ModifiableParameter.Range,
|
||||
env.responsiveText,
|
||||
)
|
||||
)
|
||||
}
|
||||
case "CheckResultBased": {
|
||||
const isMaximum = getTextForIsMaximum(
|
||||
value.value.check_result_based.is_maximum,
|
||||
deps.translate,
|
||||
env.responsiveText,
|
||||
)
|
||||
|
||||
const isRadius =
|
||||
value.value.check_result_based.is_radius === true ? ` ${deps.translate("Radius")}` : ""
|
||||
|
||||
return (
|
||||
isMaximum +
|
||||
toRangeUnit(
|
||||
value.value.check_result_based.unit,
|
||||
getTextForCheckResultBased(value.value.check_result_based, deps.translate),
|
||||
deps.translate,
|
||||
env.responsiveText,
|
||||
) +
|
||||
isRadius +
|
||||
getTextForNonModifiableSuffix(
|
||||
deps.translate,
|
||||
env.entity,
|
||||
ModifiableParameter.Range,
|
||||
env.responsiveText,
|
||||
)
|
||||
)
|
||||
}
|
||||
default:
|
||||
return assertExhaustive(value.value)
|
||||
}
|
||||
})()
|
||||
|
||||
const withReplacement =
|
||||
translation?.replacement !== undefined
|
||||
? getResponsiveText(translation.replacement, env.responsiveText).replace("$1", rangeValue)
|
||||
: rangeValue
|
||||
|
||||
const withNote = (() => {
|
||||
if (translation?.note === undefined) {
|
||||
return withReplacement
|
||||
}
|
||||
|
||||
const note = getResponsiveTextOptional(translation.note, env.responsiveText)
|
||||
|
||||
if (note === undefined) {
|
||||
return withReplacement
|
||||
}
|
||||
|
||||
return `${withReplacement} (${note})`
|
||||
})()
|
||||
|
||||
return withNote
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the text for the range of a cantrip.
|
||||
*/
|
||||
export const getTextForCantripRange = (
|
||||
deps: { translate: Translate },
|
||||
value: CantripRange,
|
||||
env: { responsiveText: ResponsiveTextSize },
|
||||
): string => {
|
||||
switch (value.tag) {
|
||||
case "Self":
|
||||
return deps.translate("Self")
|
||||
case "Touch":
|
||||
return deps.translate("Touch")
|
||||
case "Fixed": {
|
||||
return toRangeUnit(value.fixed.unit, value.fixed.value, deps.translate, env.responsiveText)
|
||||
}
|
||||
default:
|
||||
return assertExhaustive(value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the text for the range of a blessing.
|
||||
*/
|
||||
export const getTextForBlessingRange = (
|
||||
deps: { translate: Translate },
|
||||
value: BlessingRange,
|
||||
env: { responsiveText: ResponsiveTextSize },
|
||||
): string => {
|
||||
switch (value.tag) {
|
||||
case "Self":
|
||||
return deps.translate("Self")
|
||||
case "Touch":
|
||||
return deps.translate("Touch")
|
||||
case "Fixed": {
|
||||
return toRangeUnit(value.fixed.unit, value.fixed.value, deps.translate, env.responsiveText)
|
||||
}
|
||||
default:
|
||||
return assertExhaustive(value)
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
import {
|
||||
FastSkillModificationLevelConfig,
|
||||
SkillModificationLevel,
|
||||
SlowSkillModificationLevelConfig,
|
||||
} from "optolith-database-schema/types/SkillModificationLevel"
|
||||
import { assertExhaustive } from "../../../utils/typeSafety.ts"
|
||||
|
||||
/**
|
||||
* The speed of an activatable skill.
|
||||
*/
|
||||
export enum Speed {
|
||||
Fast,
|
||||
Slow,
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a common value for a skill modification level depending on the speed.
|
||||
*/
|
||||
export const getModifiableBySpeed = <T>(
|
||||
level: SkillModificationLevel,
|
||||
speed: Speed,
|
||||
fast: (config: FastSkillModificationLevelConfig) => T,
|
||||
slow: (config: SlowSkillModificationLevelConfig) => T,
|
||||
): T => {
|
||||
switch (speed) {
|
||||
case Speed.Fast:
|
||||
return fast(level.fast)
|
||||
case Speed.Slow:
|
||||
return slow(level.slow)
|
||||
default:
|
||||
return assertExhaustive(speed)
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
import { TargetCategory } from "optolith-database-schema/types/_ActivatableSkillTargetCategory"
|
||||
import { mapNullable } from "../../../utils/nullable.ts"
|
||||
import { Translate, TranslateMap } from "../../../utils/translate.ts"
|
||||
import { assertExhaustive } from "../../../utils/typeSafety.ts"
|
||||
import { GetById } from "../../getTypes.ts"
|
||||
import { LibraryEntryContent } from "../../libraryEntry.ts"
|
||||
import { MISSING_VALUE } from "../unknown.ts"
|
||||
import { appendInParens } from "./parensIf.ts"
|
||||
|
||||
/**
|
||||
* Get the text for the target category.
|
||||
*/
|
||||
export const getTextForTargetCategory = (
|
||||
deps: {
|
||||
translate: Translate
|
||||
translateMap: TranslateMap
|
||||
getTargetCategoryById: GetById.Static.TargetCategory
|
||||
},
|
||||
values: TargetCategory,
|
||||
): LibraryEntryContent => ({
|
||||
label: deps.translate("Target Category"),
|
||||
value:
|
||||
values.length === 0
|
||||
? deps.translate("all")
|
||||
: values
|
||||
.map(({ id, translations }) => {
|
||||
const mainName = (() => {
|
||||
switch (id.tag) {
|
||||
case "Self":
|
||||
return deps.translate("Self")
|
||||
case "Zone":
|
||||
return deps.translate("Zone")
|
||||
case "LiturgicalChantsAndCeremonies":
|
||||
return deps.translate("Liturgical Chants and Ceremonies")
|
||||
case "Cantrips":
|
||||
return deps.translate("Cantrips")
|
||||
case "Predefined": {
|
||||
const numericId = id.predefined.id.target_category
|
||||
const specificTargetCategory = deps.getTargetCategoryById(numericId)
|
||||
return (
|
||||
mapNullable(
|
||||
deps.translateMap(specificTargetCategory?.translations),
|
||||
translation => translation.name,
|
||||
) ?? MISSING_VALUE
|
||||
)
|
||||
}
|
||||
default:
|
||||
return assertExhaustive(id)
|
||||
}
|
||||
})()
|
||||
|
||||
return appendInParens(mainName, deps.translateMap(translations)?.note)
|
||||
})
|
||||
.join(", "),
|
||||
})
|
||||
@@ -1,123 +0,0 @@
|
||||
import { Translate } from "../../../utils/translate.ts"
|
||||
import { assertExhaustive } from "../../../utils/typeSafety.ts"
|
||||
import { responsive, ResponsiveTextSize } from "../responsiveText.ts"
|
||||
import { Entity } from "./entity.ts"
|
||||
|
||||
type TimeSpanUnit =
|
||||
| "Seconds"
|
||||
| "Minutes"
|
||||
| "Hours"
|
||||
| "Days"
|
||||
| "Weeks"
|
||||
| "Months"
|
||||
| "Years"
|
||||
| "Centuries"
|
||||
| "Actions"
|
||||
| "CombatRounds"
|
||||
| "SeductionActions"
|
||||
| "Rounds"
|
||||
|
||||
/**
|
||||
* Returns the text for a time span unit.
|
||||
*/
|
||||
export const formatTimeSpan = (
|
||||
translate: Translate,
|
||||
responsiveTextSize: ResponsiveTextSize,
|
||||
unit: TimeSpanUnit,
|
||||
value: number | string,
|
||||
): string => {
|
||||
switch (unit) {
|
||||
case "Seconds":
|
||||
return responsive(
|
||||
responsiveTextSize,
|
||||
() => translate("{0} seconds", value),
|
||||
() => translate("{0} s", value),
|
||||
)
|
||||
case "Minutes":
|
||||
return responsive(
|
||||
responsiveTextSize,
|
||||
() => translate("{0} minutes", value),
|
||||
() => translate("{0} min", value),
|
||||
)
|
||||
case "Hours":
|
||||
return responsive(
|
||||
responsiveTextSize,
|
||||
() => translate("{0} hours", value),
|
||||
() => translate("{0} h", value),
|
||||
)
|
||||
case "Days":
|
||||
return responsive(
|
||||
responsiveTextSize,
|
||||
() => translate("{0} days", value),
|
||||
() => translate("{0} d", value),
|
||||
)
|
||||
case "Weeks":
|
||||
return responsive(
|
||||
responsiveTextSize,
|
||||
() => translate("{0} weeks", value),
|
||||
() => translate("{0} wks.", value),
|
||||
)
|
||||
case "Months":
|
||||
return responsive(
|
||||
responsiveTextSize,
|
||||
() => translate("{0} months", value),
|
||||
() => translate("{0} mos.", value),
|
||||
)
|
||||
case "Years":
|
||||
return responsive(
|
||||
responsiveTextSize,
|
||||
() => translate("{0} years", value),
|
||||
() => translate("{0} yrs.", value),
|
||||
)
|
||||
case "Centuries":
|
||||
return responsive(
|
||||
responsiveTextSize,
|
||||
() => translate("{0} centuries", value),
|
||||
() => translate("{0} cent.", value),
|
||||
)
|
||||
case "Actions":
|
||||
return responsive(
|
||||
responsiveTextSize,
|
||||
() => translate("{0} actions", value),
|
||||
() => translate("{0} act", value),
|
||||
)
|
||||
case "CombatRounds":
|
||||
return responsive(
|
||||
responsiveTextSize,
|
||||
() => translate("{0} combat rounds", value),
|
||||
() => translate("{0} CR", value),
|
||||
)
|
||||
case "SeductionActions":
|
||||
return responsive(
|
||||
responsiveTextSize,
|
||||
() => translate("{0} seduction actions", value),
|
||||
() => translate("{0} SA", value),
|
||||
)
|
||||
case "Rounds":
|
||||
return responsive(
|
||||
responsiveTextSize,
|
||||
() => translate("{0} rounds", value),
|
||||
() => translate("{0} rnds", value),
|
||||
)
|
||||
default:
|
||||
return assertExhaustive(unit)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the text for a cost unit that is based on the entity type.
|
||||
*/
|
||||
export const formatCost = (translate: Translate, entity: Entity, value: number | string) => {
|
||||
switch (entity) {
|
||||
case Entity.Cantrip:
|
||||
case Entity.Spell:
|
||||
case Entity.Ritual:
|
||||
return translate("{0} AE", value)
|
||||
case Entity.Blessing:
|
||||
case Entity.LiturgicalChant:
|
||||
case Entity.Ceremony:
|
||||
return translate("{0} KP", value)
|
||||
default:
|
||||
return assertExhaustive(entity)
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
import { LocaleMap } from "optolith-database-schema/types/_LocaleMap"
|
||||
import {
|
||||
ResponsiveText,
|
||||
ResponsiveTextOptional,
|
||||
ResponsiveTextReplace,
|
||||
} from "optolith-database-schema/types/_ResponsiveText"
|
||||
import { mapNullable } from "../../utils/nullable.ts"
|
||||
import { TranslateMap } from "../../utils/translate.ts"
|
||||
import { assertExhaustive } from "../../utils/typeSafety.ts"
|
||||
import { MISSING_VALUE } from "./unknown.ts"
|
||||
|
||||
/**
|
||||
* Whether the entry is displayed in a normal or compressed setting. Normal/full
|
||||
* usually means a full library entry display, whether compressed usually means
|
||||
* the character sheet.
|
||||
*/
|
||||
export enum ResponsiveTextSize {
|
||||
Compressed,
|
||||
Full,
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes one of two functions depending on the responsive text size.
|
||||
*/
|
||||
export const responsive = <T, A extends unknown[]>(
|
||||
size: ResponsiveTextSize,
|
||||
full: (...args: A) => T,
|
||||
compressed: (...args: A) => T,
|
||||
...args: A
|
||||
): T => {
|
||||
switch (size) {
|
||||
case ResponsiveTextSize.Compressed:
|
||||
return compressed(...args)
|
||||
case ResponsiveTextSize.Full:
|
||||
return full(...args)
|
||||
default:
|
||||
return assertExhaustive(size)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the responsive text for a given size.
|
||||
*/
|
||||
export const getResponsiveText = (
|
||||
value: ResponsiveText | undefined,
|
||||
size: ResponsiveTextSize,
|
||||
): string => {
|
||||
if (value === undefined) {
|
||||
return MISSING_VALUE
|
||||
}
|
||||
|
||||
return responsive(
|
||||
size,
|
||||
() => value.full,
|
||||
() => value.compressed,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the responsive text for a given size if it is defined.
|
||||
*/
|
||||
export const getResponsiveTextOptional = (
|
||||
value: ResponsiveTextOptional | undefined,
|
||||
size: ResponsiveTextSize,
|
||||
): string | undefined => {
|
||||
if (value === undefined) {
|
||||
return MISSING_VALUE
|
||||
}
|
||||
|
||||
return responsive(
|
||||
size,
|
||||
() => value.full,
|
||||
() => value.compressed,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces a text with a given value if a replacement is requested, otherwise
|
||||
* just return the .
|
||||
*/
|
||||
export const replaceTextIfRequested = (
|
||||
translation: LocaleMap<{ replacement?: ResponsiveTextReplace }> | undefined,
|
||||
valueToReplace: string,
|
||||
translateMap: TranslateMap,
|
||||
responsiveText: ResponsiveTextSize,
|
||||
) =>
|
||||
mapNullable(translateMap(translation)?.replacement, replacement =>
|
||||
getResponsiveText(replacement, responsiveText).replace("$1", valueToReplace),
|
||||
) ?? valueToReplace
|
||||
@@ -1,4 +0,0 @@
|
||||
/**
|
||||
* String to display when a translation that should be present is missing.
|
||||
*/
|
||||
export const MISSING_VALUE = "???"
|
||||
@@ -1,128 +0,0 @@
|
||||
import {
|
||||
FastOneTimePerformanceParameters,
|
||||
FastSustainedPerformanceParameters,
|
||||
SlowOneTimePerformanceParameters,
|
||||
SlowSustainedPerformanceParameters,
|
||||
} from "optolith-database-schema/types/_ActivatableSkill"
|
||||
import { Translate, TranslateMap } from "../../utils/translate.ts"
|
||||
import { GetById } from "../getTypes.ts"
|
||||
import {
|
||||
getTextForFastCastingTime,
|
||||
getTextForSlowCastingTime,
|
||||
} from "../libraryEntry/activatableSkill/castingTime.ts"
|
||||
import {
|
||||
getTextForOneTimeCost,
|
||||
getTextForSustainedCost,
|
||||
} from "../libraryEntry/activatableSkill/cost.ts"
|
||||
import {
|
||||
getTextForDurationForOneTime,
|
||||
getTextForDurationForSustained,
|
||||
} from "../libraryEntry/activatableSkill/duration.ts"
|
||||
import { Entity } from "../libraryEntry/activatableSkill/entity.ts"
|
||||
import { getTextForActivatableSkillRange } from "../libraryEntry/activatableSkill/range.ts"
|
||||
import { Speed } from "../libraryEntry/activatableSkill/speed.ts"
|
||||
import { ResponsiveTextSize } from "../libraryEntry/responsiveText.ts"
|
||||
|
||||
/**
|
||||
* Get the texts for all fast one-time performance parameters.
|
||||
*/
|
||||
export const getTextForFastOneTimePerformanceParameters = (
|
||||
deps: {
|
||||
getSkillModificationLevelById: GetById.Static.SkillModificationLevel
|
||||
translate: Translate
|
||||
translateMap: TranslateMap
|
||||
},
|
||||
value: FastOneTimePerformanceParameters,
|
||||
env: {
|
||||
entity: Entity
|
||||
responsiveText: ResponsiveTextSize
|
||||
},
|
||||
): {
|
||||
castingTime: string
|
||||
cost: string
|
||||
range: string
|
||||
duration: string
|
||||
} => ({
|
||||
castingTime: getTextForFastCastingTime(deps, value.casting_time, env),
|
||||
cost: getTextForOneTimeCost(deps, value.cost, { speed: Speed.Fast, ...env }),
|
||||
range: getTextForActivatableSkillRange(deps, value.range, { speed: Speed.Fast, ...env }),
|
||||
duration: getTextForDurationForOneTime(deps, value.duration, env),
|
||||
})
|
||||
|
||||
/**
|
||||
* Get the texts for all fast sustained performance parameters.
|
||||
*/
|
||||
export const getTextForFastSustainedPerformanceParameters = (
|
||||
deps: {
|
||||
getSkillModificationLevelById: GetById.Static.SkillModificationLevel
|
||||
translate: Translate
|
||||
translateMap: TranslateMap
|
||||
},
|
||||
value: FastSustainedPerformanceParameters,
|
||||
env: {
|
||||
entity: Entity
|
||||
responsiveText: ResponsiveTextSize
|
||||
},
|
||||
): {
|
||||
castingTime: string
|
||||
cost: string
|
||||
range: string
|
||||
duration: string
|
||||
} => ({
|
||||
castingTime: getTextForFastCastingTime(deps, value.casting_time, env),
|
||||
cost: getTextForSustainedCost(deps, value.cost, { speed: Speed.Fast, ...env }),
|
||||
range: getTextForActivatableSkillRange(deps, value.range, { speed: Speed.Fast, ...env }),
|
||||
duration: getTextForDurationForSustained(deps, value.duration, env),
|
||||
})
|
||||
|
||||
/**
|
||||
* Get the texts for all slow one-time performance parameters.
|
||||
*/
|
||||
export const getTextForSlowOneTimePerformanceParameters = (
|
||||
deps: {
|
||||
getSkillModificationLevelById: GetById.Static.SkillModificationLevel
|
||||
translate: Translate
|
||||
translateMap: TranslateMap
|
||||
},
|
||||
value: SlowOneTimePerformanceParameters,
|
||||
env: {
|
||||
entity: Entity
|
||||
responsiveText: ResponsiveTextSize
|
||||
},
|
||||
): {
|
||||
castingTime: string
|
||||
cost: string
|
||||
range: string
|
||||
duration: string
|
||||
} => ({
|
||||
castingTime: getTextForSlowCastingTime(deps, value.casting_time, env),
|
||||
cost: getTextForOneTimeCost(deps, value.cost, { speed: Speed.Slow, ...env }),
|
||||
range: getTextForActivatableSkillRange(deps, value.range, { speed: Speed.Slow, ...env }),
|
||||
duration: getTextForDurationForOneTime(deps, value.duration, env),
|
||||
})
|
||||
|
||||
/**
|
||||
* Get the texts for all slow sustained performance parameters.
|
||||
*/
|
||||
export const getTextForSlowSustainedPerformanceParameters = (
|
||||
deps: {
|
||||
getSkillModificationLevelById: GetById.Static.SkillModificationLevel
|
||||
translate: Translate
|
||||
translateMap: TranslateMap
|
||||
},
|
||||
value: SlowSustainedPerformanceParameters,
|
||||
env: {
|
||||
entity: Entity
|
||||
responsiveText: ResponsiveTextSize
|
||||
},
|
||||
): {
|
||||
castingTime: string
|
||||
cost: string
|
||||
range: string
|
||||
duration: string
|
||||
} => ({
|
||||
castingTime: getTextForSlowCastingTime(deps, value.casting_time, env),
|
||||
cost: getTextForSustainedCost(deps, value.cost, { speed: Speed.Slow, ...env }),
|
||||
range: getTextForActivatableSkillRange(deps, value.range, { speed: Speed.Slow, ...env }),
|
||||
duration: getTextForDurationForSustained(deps, value.duration, env),
|
||||
})
|
||||
@@ -2,12 +2,8 @@ import { CloseCombatTechnique } from "optolith-database-schema/types/CombatTechn
|
||||
import { RangedCombatTechnique } from "optolith-database-schema/types/CombatTechnique_Ranged"
|
||||
import { CombatTechniqueIdentifier } from "optolith-database-schema/types/_IdentifierGroup"
|
||||
import { AttributeReference } from "optolith-database-schema/types/_SimpleReferences"
|
||||
import { mapNullable } from "../../utils/nullable.ts"
|
||||
import { Activatable, countOptions } from "../activatable/activatableEntry.ts"
|
||||
import { createImprovementCost } from "../adventurePoints/improvementCost.ts"
|
||||
import { GetById } from "../getTypes.ts"
|
||||
import { AttributeIdentifier } from "../identifier.ts"
|
||||
import { createLibraryEntryCreator } from "../libraryEntry.ts"
|
||||
import { getAttributeValue } from "./attribute.ts"
|
||||
import { Rated } from "./ratedEntry.ts"
|
||||
|
||||
@@ -157,73 +153,3 @@ export const getHighestRequiredAttributeForCombatTechnique = (
|
||||
value: dynamicCombatTechnique.value - 2 - exceptionalCombatTechniqueBonus,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a JSON representation of the rules text for a close combat technique.
|
||||
*/
|
||||
export const getCloseCombatTechniqueLibraryEntry = createLibraryEntryCreator<
|
||||
CloseCombatTechnique,
|
||||
{
|
||||
getAttributeById: GetById.Static.Attribute
|
||||
}
|
||||
>((entry, { getAttributeById }) => ({ translate, translateMap }) => {
|
||||
const translation = translateMap(entry.translations)
|
||||
|
||||
if (translation === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return {
|
||||
title: translation.name,
|
||||
className: "combat-technique close-combat-technique",
|
||||
content: [
|
||||
mapNullable(translation.special, value => ({
|
||||
label: translate("Special"),
|
||||
value,
|
||||
})),
|
||||
{
|
||||
label: translate("Primary Attribute"),
|
||||
value: entry.primary_attribute
|
||||
.map(attr => translateMap(getAttributeById(attr.id.attribute)?.translations)?.name)
|
||||
.join("/"),
|
||||
},
|
||||
createImprovementCost(translate, entry.improvement_cost),
|
||||
],
|
||||
src: entry.src,
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Get a JSON representation of the rules text for a ranged combat technique.
|
||||
*/
|
||||
export const getRangedCombatTechniqueLibraryEntry = createLibraryEntryCreator<
|
||||
RangedCombatTechnique,
|
||||
{
|
||||
getAttributeById: GetById.Static.Attribute
|
||||
}
|
||||
>((entry, { getAttributeById }) => ({ translate, translateMap }) => {
|
||||
const translation = translateMap(entry.translations)
|
||||
|
||||
if (translation === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return {
|
||||
title: translation.name,
|
||||
className: "combat-technique ranged-combat-technique",
|
||||
content: [
|
||||
mapNullable(translation.special, value => ({
|
||||
label: translate("Special"),
|
||||
value,
|
||||
})),
|
||||
{
|
||||
label: translate("Primary Attribute"),
|
||||
value: entry.primary_attribute
|
||||
.map(attr => translateMap(getAttributeById(attr.id.attribute)?.translations)?.name)
|
||||
.join("/"),
|
||||
},
|
||||
createImprovementCost(translate, entry.improvement_cost),
|
||||
],
|
||||
src: entry.src,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,41 +1,20 @@
|
||||
import { Aspect } from "optolith-database-schema/types/Aspect"
|
||||
import { Blessing } from "optolith-database-schema/types/Blessing"
|
||||
import { Ceremony } from "optolith-database-schema/types/Ceremony"
|
||||
import { ExperienceLevel } from "optolith-database-schema/types/ExperienceLevel"
|
||||
import { LiturgicalChant } from "optolith-database-schema/types/LiturgicalChant"
|
||||
import { SkillTradition } from "optolith-database-schema/types/_Blessed"
|
||||
import { LiturgyIdentifier } from "optolith-database-schema/types/_IdentifierGroup"
|
||||
import { ImprovementCost } from "optolith-database-schema/types/_ImprovementCost"
|
||||
import { AspectReference } from "optolith-database-schema/types/_SimpleReferences"
|
||||
import { SkillCheck } from "optolith-database-schema/types/_SkillCheck"
|
||||
import { BlessedTradition } from "optolith-database-schema/types/specialAbility/BlessedTradition"
|
||||
import { count, countByMany } from "../../utils/array.ts"
|
||||
import { Compare, compareAt, compareNullish, numAsc, reduceCompare } from "../../utils/compare.ts"
|
||||
import { isNotNullish } from "../../utils/nullable.ts"
|
||||
import { Translate, TranslateMap } from "../../utils/translate.ts"
|
||||
import { TranslateMap } from "../../utils/translate.ts"
|
||||
import { assertExhaustive } from "../../utils/typeSafety.ts"
|
||||
import { Activatable, countOptions } from "../activatable/activatableEntry.ts"
|
||||
import {
|
||||
compareImprovementCost,
|
||||
createImprovementCost,
|
||||
fromRaw,
|
||||
} from "../adventurePoints/improvementCost.ts"
|
||||
import { All, GetById } from "../getTypes.ts"
|
||||
import { compareImprovementCost, fromRaw } from "../adventurePoints/improvementCost.ts"
|
||||
import { All } from "../getTypes.ts"
|
||||
import { AspectIdentifier, createIdentifierObject } from "../identifier.ts"
|
||||
import { createLibraryEntryCreator, LibraryEntryContent } from "../libraryEntry.ts"
|
||||
import { getTextForBlessingDuration } from "../libraryEntry/activatableSkill/duration.ts"
|
||||
import { getTextForEffect } from "../libraryEntry/activatableSkill/effect.ts"
|
||||
import { Entity } from "../libraryEntry/activatableSkill/entity.ts"
|
||||
import { getTextForBlessingRange } from "../libraryEntry/activatableSkill/range.ts"
|
||||
import { getTextForTargetCategory } from "../libraryEntry/activatableSkill/targetCategory.ts"
|
||||
import { ResponsiveTextSize } from "../libraryEntry/responsiveText.ts"
|
||||
import { LiturgiesSortOrder } from "../sortOrders.ts"
|
||||
import {
|
||||
getTextForFastOneTimePerformanceParameters,
|
||||
getTextForFastSustainedPerformanceParameters,
|
||||
getTextForSlowOneTimePerformanceParameters,
|
||||
getTextForSlowSustainedPerformanceParameters,
|
||||
} from "./activatableSkill.ts"
|
||||
import { DisplayedActiveLiturgy } from "./liturgicalChantActive.ts"
|
||||
import { DisplayedInactiveLiturgy } from "./liturgicalChantInactive.ts"
|
||||
import {
|
||||
@@ -47,7 +26,6 @@ import {
|
||||
isRatedActive,
|
||||
isRatedWithEnhancementsActive,
|
||||
} from "./ratedEntry.ts"
|
||||
import { getTextForCheck } from "./skillCheck.ts"
|
||||
|
||||
/**
|
||||
* Returns the value for a dynamic liturgical chant entry that might not exist
|
||||
@@ -555,345 +533,3 @@ export const filterAndSortDisplayed = <T extends DisplayedActiveLiturgy | Displa
|
||||
// maybe (aspects_str)
|
||||
// (pipe (NumIdName.A.name, gr_str => `${aspects_str} / ${gr_str}`))
|
||||
// )
|
||||
|
||||
const getTextForTraditions = (
|
||||
deps: {
|
||||
translate: Translate
|
||||
translateMap: TranslateMap
|
||||
localeCompare: Compare<string>
|
||||
getBlessedTraditionById: GetById.Static.BlessedTradition
|
||||
getAspectById: GetById.Static.Aspect
|
||||
},
|
||||
values: SkillTradition[],
|
||||
): LibraryEntryContent => {
|
||||
const getAspectName = (ref: AspectReference) =>
|
||||
deps.translateMap(deps.getAspectById(ref.id.aspect)?.translations)?.name
|
||||
|
||||
const text = values
|
||||
.map(trad => {
|
||||
switch (trad.tag) {
|
||||
case "GeneralAspect":
|
||||
return getAspectName(trad.general_aspect)
|
||||
case "Tradition": {
|
||||
const traditionTranslation = deps.translateMap(
|
||||
deps.getBlessedTraditionById(trad.tradition.tradition.id.blessed_tradition)
|
||||
?.translations,
|
||||
)
|
||||
const name = traditionTranslation?.name_compressed ?? traditionTranslation?.name
|
||||
|
||||
if (name === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const aspects =
|
||||
trad.tradition.aspects
|
||||
?.map(getAspectName)
|
||||
.filter(isNotNullish)
|
||||
.sort(deps.localeCompare) ?? []
|
||||
|
||||
if (aspects.length === 0) {
|
||||
return name
|
||||
}
|
||||
|
||||
return `${name} (${aspects.join(" and ")})`
|
||||
}
|
||||
default:
|
||||
return assertExhaustive(trad)
|
||||
}
|
||||
})
|
||||
.filter(isNotNullish)
|
||||
.join(", ")
|
||||
|
||||
return {
|
||||
label: deps.translate("Traditions"),
|
||||
value: text,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a JSON representation of the rules text for a blessing.
|
||||
*/
|
||||
export const getBlessingLibraryEntry = createLibraryEntryCreator<
|
||||
Blessing,
|
||||
{
|
||||
getTargetCategoryById: GetById.Static.TargetCategory
|
||||
}
|
||||
>((entry, { getTargetCategoryById }) => ({ translate, translateMap }) => {
|
||||
const translation = translateMap(entry.translations)
|
||||
|
||||
if (translation === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const range = getTextForBlessingRange({ translate }, entry.parameters.range, {
|
||||
responsiveText: ResponsiveTextSize.Full,
|
||||
})
|
||||
|
||||
const duration = getTextForBlessingDuration(
|
||||
{ translate, translateMap },
|
||||
entry.parameters.duration,
|
||||
{
|
||||
responsiveText: ResponsiveTextSize.Full,
|
||||
},
|
||||
)
|
||||
|
||||
return {
|
||||
title: translation.name,
|
||||
className: "blessing",
|
||||
content: [
|
||||
{
|
||||
label: translate("Effect"),
|
||||
value: translation.effect,
|
||||
},
|
||||
{
|
||||
label: translate("Range"),
|
||||
value: range !== translation.range ? `***${range}*** (${translation.range})` : range,
|
||||
},
|
||||
{
|
||||
label: translate("Duration"),
|
||||
value:
|
||||
duration !== translation.duration
|
||||
? `***${duration}*** (${translation.duration})`
|
||||
: duration,
|
||||
},
|
||||
getTextForTargetCategory({ translate, translateMap, getTargetCategoryById }, entry.target),
|
||||
],
|
||||
src: entry.src,
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Get a JSON representation of the rules text for a liturgical chant.
|
||||
*/
|
||||
export const getLiturgicalChantLibraryEntry = createLibraryEntryCreator<
|
||||
LiturgicalChant,
|
||||
{
|
||||
getAttributeById: GetById.Static.Attribute
|
||||
getDerivedCharacteristicById: GetById.Static.DerivedCharacteristic
|
||||
getSkillModificationLevelById: GetById.Static.SkillModificationLevel
|
||||
getTargetCategoryById: GetById.Static.TargetCategory
|
||||
getBlessedTraditionById: GetById.Static.BlessedTradition
|
||||
getAspectById: GetById.Static.Aspect
|
||||
}
|
||||
>(
|
||||
(
|
||||
entry,
|
||||
{
|
||||
getAttributeById,
|
||||
getDerivedCharacteristicById,
|
||||
getSkillModificationLevelById,
|
||||
getTargetCategoryById,
|
||||
getBlessedTraditionById,
|
||||
getAspectById,
|
||||
},
|
||||
) =>
|
||||
({ translate, translateMap, localeCompare }) => {
|
||||
const translation = translateMap(entry.translations)
|
||||
|
||||
if (translation === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const { castingTime, cost, range, duration } = (() => {
|
||||
switch (entry.parameters.tag) {
|
||||
case "OneTime":
|
||||
return getTextForFastOneTimePerformanceParameters(
|
||||
{
|
||||
getSkillModificationLevelById,
|
||||
translate,
|
||||
translateMap,
|
||||
},
|
||||
entry.parameters.one_time,
|
||||
{
|
||||
entity: Entity.LiturgicalChant,
|
||||
responsiveText: ResponsiveTextSize.Full,
|
||||
},
|
||||
)
|
||||
|
||||
case "Sustained":
|
||||
return getTextForFastSustainedPerformanceParameters(
|
||||
{
|
||||
getSkillModificationLevelById,
|
||||
translate,
|
||||
translateMap,
|
||||
},
|
||||
entry.parameters.sustained,
|
||||
{
|
||||
entity: Entity.LiturgicalChant,
|
||||
responsiveText: ResponsiveTextSize.Full,
|
||||
},
|
||||
)
|
||||
|
||||
default:
|
||||
return assertExhaustive(entry.parameters)
|
||||
}
|
||||
})()
|
||||
|
||||
return {
|
||||
title: translation.name,
|
||||
className: "liturgical-chant",
|
||||
content: [
|
||||
getTextForCheck({ translate, translateMap, getAttributeById }, entry.check, {
|
||||
value: entry.check_penalty,
|
||||
responsiveText: ResponsiveTextSize.Full,
|
||||
getDerivedCharacteristicById,
|
||||
}),
|
||||
...getTextForEffect(translation.effect, translate),
|
||||
{
|
||||
label: translate("Liturgical Time"),
|
||||
value:
|
||||
castingTime !== translation.casting_time.full
|
||||
? `***${castingTime}*** (${translation.casting_time.full})`
|
||||
: castingTime,
|
||||
},
|
||||
{
|
||||
label: translate("KP Cost"),
|
||||
value:
|
||||
cost !== translation.cost.full ? `***${cost}*** (${translation.cost.full})` : cost,
|
||||
},
|
||||
{
|
||||
label: translate("Range"),
|
||||
value:
|
||||
range !== translation.range.full
|
||||
? `***${range}*** (${translation.range.full})`
|
||||
: range,
|
||||
},
|
||||
{
|
||||
label: translate("Duration"),
|
||||
value:
|
||||
duration !== translation.duration.full
|
||||
? `***${duration}*** (${translation.duration.full})`
|
||||
: duration,
|
||||
},
|
||||
getTextForTargetCategory(
|
||||
{ translate, translateMap, getTargetCategoryById },
|
||||
entry.target,
|
||||
),
|
||||
getTextForTraditions(
|
||||
{ translate, translateMap, localeCompare, getBlessedTraditionById, getAspectById },
|
||||
entry.traditions,
|
||||
),
|
||||
createImprovementCost(translate, entry.improvement_cost),
|
||||
],
|
||||
src: entry.src,
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
/**
|
||||
* Get a JSON representation of the rules text for a ceremony.
|
||||
*/
|
||||
export const getCeremonyLibraryEntry = createLibraryEntryCreator<
|
||||
Ceremony,
|
||||
{
|
||||
getAttributeById: GetById.Static.Attribute
|
||||
getDerivedCharacteristicById: GetById.Static.DerivedCharacteristic
|
||||
getSkillModificationLevelById: GetById.Static.SkillModificationLevel
|
||||
getTargetCategoryById: GetById.Static.TargetCategory
|
||||
getBlessedTraditionById: GetById.Static.BlessedTradition
|
||||
getAspectById: GetById.Static.Aspect
|
||||
}
|
||||
>(
|
||||
(
|
||||
entry,
|
||||
{
|
||||
getAttributeById,
|
||||
getDerivedCharacteristicById,
|
||||
getSkillModificationLevelById,
|
||||
getTargetCategoryById,
|
||||
getBlessedTraditionById,
|
||||
getAspectById,
|
||||
},
|
||||
) =>
|
||||
({ translate, translateMap, localeCompare }) => {
|
||||
const translation = translateMap(entry.translations)
|
||||
|
||||
if (translation === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const { castingTime, cost, range, duration } = (() => {
|
||||
switch (entry.parameters.tag) {
|
||||
case "OneTime":
|
||||
return getTextForSlowOneTimePerformanceParameters(
|
||||
{
|
||||
getSkillModificationLevelById,
|
||||
translate,
|
||||
translateMap,
|
||||
},
|
||||
entry.parameters.one_time,
|
||||
{
|
||||
entity: Entity.Ceremony,
|
||||
responsiveText: ResponsiveTextSize.Full,
|
||||
},
|
||||
)
|
||||
|
||||
case "Sustained":
|
||||
return getTextForSlowSustainedPerformanceParameters(
|
||||
{
|
||||
getSkillModificationLevelById,
|
||||
translate,
|
||||
translateMap,
|
||||
},
|
||||
entry.parameters.sustained,
|
||||
{
|
||||
entity: Entity.Ceremony,
|
||||
responsiveText: ResponsiveTextSize.Full,
|
||||
},
|
||||
)
|
||||
|
||||
default:
|
||||
return assertExhaustive(entry.parameters)
|
||||
}
|
||||
})()
|
||||
|
||||
return {
|
||||
title: translation.name,
|
||||
className: "ceremony",
|
||||
content: [
|
||||
getTextForCheck({ translate, translateMap, getAttributeById }, entry.check, {
|
||||
value: entry.check_penalty,
|
||||
responsiveText: ResponsiveTextSize.Full,
|
||||
getDerivedCharacteristicById,
|
||||
}),
|
||||
...getTextForEffect(translation.effect, translate),
|
||||
{
|
||||
label: translate("Ceremonial Time"),
|
||||
value:
|
||||
castingTime !== translation.casting_time.full
|
||||
? `***${castingTime}*** (${translation.casting_time.full})`
|
||||
: castingTime,
|
||||
},
|
||||
{
|
||||
label: translate("KP Cost"),
|
||||
value:
|
||||
cost !== translation.cost.full ? `***${cost}*** (${translation.cost.full})` : cost,
|
||||
},
|
||||
{
|
||||
label: translate("Range"),
|
||||
value:
|
||||
range !== translation.range.full
|
||||
? `***${range}*** (${translation.range.full})`
|
||||
: range,
|
||||
},
|
||||
{
|
||||
label: translate("Duration"),
|
||||
value:
|
||||
duration !== translation.duration.full
|
||||
? `***${duration}*** (${translation.duration.full})`
|
||||
: duration,
|
||||
},
|
||||
getTextForTargetCategory(
|
||||
{ translate, translateMap, getTargetCategoryById },
|
||||
entry.target,
|
||||
),
|
||||
getTextForTraditions(
|
||||
{ translate, translateMap, localeCompare, getBlessedTraditionById, getAspectById },
|
||||
entry.traditions,
|
||||
),
|
||||
createImprovementCost(translate, entry.improvement_cost),
|
||||
],
|
||||
src: entry.src,
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
import { NewApplicationsAndUsesCache } from "optolith-database-schema/cache/newApplicationsAndUses"
|
||||
import { Culture } from "optolith-database-schema/types/Culture"
|
||||
import { Skill } from "optolith-database-schema/types/Skill"
|
||||
import { SkillCheck } from "optolith-database-schema/types/_SkillCheck"
|
||||
import { isNotNullish } from "../../utils/nullable.ts"
|
||||
import { assertExhaustive } from "../../utils/typeSafety.ts"
|
||||
import { Activatable, countOptions } from "../activatable/activatableEntry.ts"
|
||||
import { createImprovementCost } from "../adventurePoints/improvementCost.ts"
|
||||
import { All, GetById } from "../getTypes.ts"
|
||||
import { createLibraryEntryCreator } from "../libraryEntry.ts"
|
||||
import { Rated } from "./ratedEntry.ts"
|
||||
import { getTextForCheck } from "./skillCheck.ts"
|
||||
|
||||
/**
|
||||
* The minimum value of a skill.
|
||||
@@ -74,126 +67,3 @@ export const getSkillCommonness = (
|
||||
: culture.uncommon_skills?.some(({ id: { skill: id } }) => skill.id === id) ?? false
|
||||
? "uncommon"
|
||||
: undefined
|
||||
|
||||
/**
|
||||
* Get a JSON representation of the rules text for a skill.
|
||||
*/
|
||||
export const getSkillLibraryEntry = createLibraryEntryCreator<
|
||||
Skill,
|
||||
{
|
||||
getAttributeById: GetById.Static.Attribute
|
||||
blessedTraditions: All.Static.BlessedTraditions
|
||||
diseases: All.Static.Diseases
|
||||
regions: All.Static.Regions
|
||||
cache: NewApplicationsAndUsesCache
|
||||
}
|
||||
>(
|
||||
(entry, { getAttributeById, blessedTraditions, diseases, regions, cache }) =>
|
||||
({ translate, translateMap, localeCompare }) => {
|
||||
const translation = translateMap(entry.translations)
|
||||
|
||||
if (translation === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const newApplications = (entry === undefined ? [] : cache.newApplications[entry.id] ?? [])
|
||||
.map(x => translateMap(x.data.translations)?.name)
|
||||
.filter(isNotNullish)
|
||||
.sort(localeCompare)
|
||||
|
||||
const uses = (entry === undefined ? [] : cache.uses[entry.id] ?? [])
|
||||
.map(x => translateMap(x.data.translations)?.name)
|
||||
.filter(isNotNullish)
|
||||
.sort(localeCompare)
|
||||
|
||||
const applications = (() => {
|
||||
switch (entry.applications.tag) {
|
||||
case "Derived":
|
||||
return (() => {
|
||||
switch (entry.applications.derived) {
|
||||
case "BlessedTraditions":
|
||||
return Object.values(blessedTraditions)
|
||||
.map(x => translateMap(x.translations)?.name)
|
||||
.filter(isNotNullish)
|
||||
.sort(localeCompare)
|
||||
case "Diseases":
|
||||
return Object.values(diseases)
|
||||
.map(x => translateMap(x.translations)?.name)
|
||||
.filter(isNotNullish)
|
||||
.sort(localeCompare)
|
||||
case "Regions":
|
||||
return Object.values(regions)
|
||||
.map(x => translateMap(x.translations)?.name)
|
||||
.filter(isNotNullish)
|
||||
.sort(localeCompare)
|
||||
default:
|
||||
return assertExhaustive(entry.applications.derived)
|
||||
}
|
||||
})()
|
||||
case "Explicit":
|
||||
return entry.applications.explicit
|
||||
.map(x => translateMap(x.translations)?.name)
|
||||
.filter(isNotNullish)
|
||||
.sort(localeCompare)
|
||||
default:
|
||||
return assertExhaustive(entry.applications)
|
||||
}
|
||||
})()
|
||||
|
||||
return {
|
||||
title: translation.name,
|
||||
className: "skill",
|
||||
content: [
|
||||
newApplications.length === 0
|
||||
? undefined
|
||||
: {
|
||||
label: translate("New Applications"),
|
||||
value: newApplications.join(", "),
|
||||
},
|
||||
uses.length === 0
|
||||
? undefined
|
||||
: {
|
||||
label: translate("Uses"),
|
||||
value: uses.join(", "),
|
||||
},
|
||||
getTextForCheck({ translate, translateMap, getAttributeById }, entry.check),
|
||||
{
|
||||
label: translate("Applications"),
|
||||
value: applications.join(", "),
|
||||
},
|
||||
{
|
||||
label: translate("Encumbrance"),
|
||||
value:
|
||||
entry.encumbrance === "True"
|
||||
? translate("Yes")
|
||||
: entry.encumbrance === "False"
|
||||
? translate("No")
|
||||
: translation.encumbrance_description ?? translate("Maybe"),
|
||||
},
|
||||
translation?.tools === undefined
|
||||
? undefined
|
||||
: {
|
||||
label: translate("Tools"),
|
||||
value: translation.tools,
|
||||
},
|
||||
{
|
||||
label: translate("Quality"),
|
||||
value: translation.quality,
|
||||
},
|
||||
{
|
||||
label: translate("Failed Check"),
|
||||
value: translation.failed,
|
||||
},
|
||||
{
|
||||
label: translate("Critical Success"),
|
||||
value: translation.critical,
|
||||
},
|
||||
{
|
||||
label: translate("Botch"),
|
||||
value: translation.botch,
|
||||
},
|
||||
createImprovementCost(translate, entry.improvement_cost),
|
||||
],
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
import { Attribute } from "optolith-database-schema/types/Attribute"
|
||||
import { SkillCheck, SkillCheckPenalty } from "optolith-database-schema/types/_SkillCheck"
|
||||
import { Translate, TranslateMap } from "../../utils/translate.ts"
|
||||
import { assertExhaustive } from "../../utils/typeSafety.ts"
|
||||
import { GetById } from "../getTypes.ts"
|
||||
import { SkillCheck } from "optolith-database-schema/types/_SkillCheck"
|
||||
import { getSingleHighestPair, IdValuePair } from "../idValue.ts"
|
||||
import { DerivedCharacteristicIdentifier } from "../identifier.ts"
|
||||
import { LibraryEntryContent } from "../libraryEntry.ts"
|
||||
import { responsive, ResponsiveTextSize } from "../libraryEntry/responsiveText.ts"
|
||||
import { getAttributeValue } from "./attribute.ts"
|
||||
import { Rated } from "./ratedEntry.ts"
|
||||
|
||||
@@ -89,122 +83,3 @@ export const getHighestCheckAttributeValue = (
|
||||
getDynamicAttribute: (id: number) => Rated | undefined,
|
||||
check: SkillCheck,
|
||||
): number => Math.max(8, ...getSkillCheckValues(getDynamicAttribute, check))
|
||||
|
||||
/**
|
||||
* Returns the skill check as an inline library property.
|
||||
*/
|
||||
export const getTextForCheck = (
|
||||
deps: {
|
||||
translate: Translate
|
||||
translateMap: TranslateMap
|
||||
getAttributeById: GetById.Static.Attribute
|
||||
},
|
||||
check: SkillCheck,
|
||||
checkPenalty?: {
|
||||
value: SkillCheckPenalty | undefined
|
||||
responsiveText: ResponsiveTextSize
|
||||
getDerivedCharacteristicById: GetById.Static.DerivedCharacteristic
|
||||
},
|
||||
): LibraryEntryContent => ({
|
||||
label: deps.translate("Check"),
|
||||
value:
|
||||
check
|
||||
.map(
|
||||
({ id: { attribute: id } }) =>
|
||||
deps.translateMap(deps.getAttributeById(id)?.translations)?.abbreviation ?? "??",
|
||||
)
|
||||
.join("/") +
|
||||
(() => {
|
||||
if (checkPenalty?.value === undefined) {
|
||||
return ""
|
||||
}
|
||||
|
||||
const { responsiveText, getDerivedCharacteristicById } = checkPenalty
|
||||
|
||||
const getDerivedCharacteristicTranslation = (id: DerivedCharacteristicIdentifier) =>
|
||||
deps.translateMap(getDerivedCharacteristicById(id)?.translations)
|
||||
|
||||
const getSpiritTranslation = () =>
|
||||
getDerivedCharacteristicTranslation(DerivedCharacteristicIdentifier.Spirit)
|
||||
|
||||
const getToughnessTranslation = () =>
|
||||
getDerivedCharacteristicTranslation(DerivedCharacteristicIdentifier.Toughness)
|
||||
|
||||
const penalty = (() => {
|
||||
switch (checkPenalty.value) {
|
||||
case "Spirit": {
|
||||
const translation = getSpiritTranslation()
|
||||
return translation === undefined
|
||||
? ""
|
||||
: responsive(
|
||||
responsiveText,
|
||||
() => translation.name,
|
||||
() => translation.abbreviation,
|
||||
)
|
||||
}
|
||||
|
||||
case "HalfOfSpirit": {
|
||||
const translation = getSpiritTranslation()
|
||||
return translation === undefined
|
||||
? ""
|
||||
: responsive(
|
||||
responsiveText,
|
||||
() => `${translation.name}/2`,
|
||||
() => `${translation.abbreviation}/2`,
|
||||
)
|
||||
}
|
||||
|
||||
case "Toughness": {
|
||||
const translation = getToughnessTranslation()
|
||||
return translation === undefined
|
||||
? ""
|
||||
: responsive(
|
||||
responsiveText,
|
||||
() => `${translation.name}/2`,
|
||||
() => `${translation.abbreviation}/2`,
|
||||
)
|
||||
}
|
||||
|
||||
case "HigherOfSpiritAndToughness": {
|
||||
const spiritTranslation = getSpiritTranslation()
|
||||
const toughnessTranslation = getToughnessTranslation()
|
||||
return spiritTranslation === undefined || toughnessTranslation === undefined
|
||||
? ""
|
||||
: responsive(
|
||||
responsiveText,
|
||||
() =>
|
||||
deps.translate(
|
||||
"{0} or {1}, depending on which value is higher",
|
||||
spiritTranslation.abbreviation,
|
||||
toughnessTranslation.abbreviation,
|
||||
),
|
||||
() => `${spiritTranslation.abbreviation}/${toughnessTranslation.abbreviation}`,
|
||||
)
|
||||
}
|
||||
|
||||
case "SummoningDifficulty":
|
||||
return responsive(
|
||||
responsiveText,
|
||||
() => deps.translate("Invocation Difficulty"),
|
||||
() => deps.translate("ID"),
|
||||
)
|
||||
|
||||
case "CreationDifficulty":
|
||||
return responsive(
|
||||
responsiveText,
|
||||
() => deps.translate("Creation Difficulty"),
|
||||
() => deps.translate("CD"),
|
||||
)
|
||||
|
||||
default:
|
||||
return assertExhaustive(checkPenalty.value)
|
||||
}
|
||||
})()
|
||||
|
||||
return responsive(
|
||||
responsiveText,
|
||||
() => deps.translate(" (modified by {0})", penalty),
|
||||
() => deps.translate(" (− {0})", penalty),
|
||||
)
|
||||
})(),
|
||||
})
|
||||
|
||||
@@ -9,23 +9,18 @@ import {
|
||||
SpellworkIdentifier,
|
||||
} from "optolith-database-schema/types/_IdentifierGroup"
|
||||
import { ImprovementCost as RawImprovementCost } from "optolith-database-schema/types/_ImprovementCost"
|
||||
import {
|
||||
MagicalTraditionReference,
|
||||
PropertyReference,
|
||||
} from "optolith-database-schema/types/_SimpleReferences"
|
||||
import { SkillCheck } from "optolith-database-schema/types/_SkillCheck"
|
||||
import { Traditions } from "optolith-database-schema/types/_Spellwork"
|
||||
import { count, countBy, sum } from "../../utils/array.ts"
|
||||
import { Compare, compareAt, compareNullish, numAsc, reduceCompare } from "../../utils/compare.ts"
|
||||
import { isNotNullish, mapNullable, mapNullableDefault } from "../../utils/nullable.ts"
|
||||
import { Translate, TranslateMap } from "../../utils/translate.ts"
|
||||
import { isNotNullish, mapNullableDefault } from "../../utils/nullable.ts"
|
||||
import { TranslateMap } from "../../utils/translate.ts"
|
||||
import { assertExhaustive } from "../../utils/typeSafety.ts"
|
||||
import { Activatable, countOptions } from "../activatable/activatableEntry.ts"
|
||||
import { CombinedActiveMagicalTradition } from "../activatable/magicalTradition.ts"
|
||||
import {
|
||||
ImprovementCost,
|
||||
compareImprovementCost,
|
||||
createImprovementCost,
|
||||
fromRaw,
|
||||
} from "../adventurePoints/improvementCost.ts"
|
||||
import { All, GetById } from "../getTypes.ts"
|
||||
@@ -34,21 +29,7 @@ import {
|
||||
createIdentifierObject,
|
||||
getCreateIdentifierObject,
|
||||
} from "../identifier.ts"
|
||||
import { LibraryEntryContent, createLibraryEntryCreator } from "../libraryEntry.ts"
|
||||
import { getTextForCantripDuration } from "../libraryEntry/activatableSkill/duration.ts"
|
||||
import { getTextForEffect } from "../libraryEntry/activatableSkill/effect.ts"
|
||||
import { Entity } from "../libraryEntry/activatableSkill/entity.ts"
|
||||
import { parensIf } from "../libraryEntry/activatableSkill/parensIf.ts"
|
||||
import { getTextForCantripRange } from "../libraryEntry/activatableSkill/range.ts"
|
||||
import { getTextForTargetCategory } from "../libraryEntry/activatableSkill/targetCategory.ts"
|
||||
import { ResponsiveTextSize } from "../libraryEntry/responsiveText.ts"
|
||||
import { SpellsSortOrder } from "../sortOrders.ts"
|
||||
import {
|
||||
getTextForFastOneTimePerformanceParameters,
|
||||
getTextForFastSustainedPerformanceParameters,
|
||||
getTextForSlowOneTimePerformanceParameters,
|
||||
getTextForSlowSustainedPerformanceParameters,
|
||||
} from "./activatableSkill.ts"
|
||||
import {
|
||||
cursesImprovementCost,
|
||||
dominationRitualsImprovementCost,
|
||||
@@ -64,7 +45,6 @@ import {
|
||||
isRatedActive,
|
||||
isRatedWithEnhancementsActive,
|
||||
} from "./ratedEntry.ts"
|
||||
import { getTextForCheck } from "./skillCheck.ts"
|
||||
import { DisplayedActiveSpellwork } from "./spellActive.ts"
|
||||
import { DisplayedInactiveSpellwork } from "./spellInactive.ts"
|
||||
|
||||
@@ -720,423 +700,3 @@ export const filterAndSortDisplayed = <
|
||||
})(),
|
||||
)
|
||||
}
|
||||
|
||||
const getTextForProperty = (
|
||||
deps: {
|
||||
translate: Translate
|
||||
translateMap: TranslateMap
|
||||
getPropertyById: GetById.Static.Property
|
||||
},
|
||||
value: PropertyReference,
|
||||
): LibraryEntryContent => {
|
||||
const text = (() => {
|
||||
const staticEntry = deps.getPropertyById(value.id.property)
|
||||
const staticEntryTranslation = deps.translateMap(staticEntry?.translations)
|
||||
|
||||
if (staticEntryTranslation === undefined) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return staticEntryTranslation.name
|
||||
})()
|
||||
|
||||
return {
|
||||
label: deps.translate("Property"),
|
||||
value: text,
|
||||
}
|
||||
}
|
||||
|
||||
const getTextForTraditions = (
|
||||
deps: {
|
||||
translate: Translate
|
||||
translateMap: TranslateMap
|
||||
localeCompare: Compare<string>
|
||||
getMagicalTraditionById: GetById.Static.MagicalTradition
|
||||
},
|
||||
value: Traditions,
|
||||
): LibraryEntryContent => {
|
||||
const text = (() => {
|
||||
switch (value.tag) {
|
||||
case "General":
|
||||
return deps.translate("General")
|
||||
case "Specific":
|
||||
return value.specific
|
||||
.map(trad =>
|
||||
deps.translateMap(deps.getMagicalTraditionById(trad.magical_tradition)?.translations),
|
||||
)
|
||||
.filter(isNotNullish)
|
||||
.map(trad => trad.name_for_arcane_spellworks ?? trad.name)
|
||||
.sort(deps.localeCompare)
|
||||
.join(", ")
|
||||
default:
|
||||
return assertExhaustive(value)
|
||||
}
|
||||
})()
|
||||
|
||||
return {
|
||||
label: deps.translate("Traditions"),
|
||||
value: text,
|
||||
}
|
||||
}
|
||||
|
||||
const getTraditionNameForArcaneSpellworksById = (
|
||||
ref: MagicalTraditionReference,
|
||||
getMagicalTraditionById: GetById.Static.MagicalTradition,
|
||||
translateMap: TranslateMap,
|
||||
) => {
|
||||
const translation = translateMap(getMagicalTraditionById(ref.id.magical_tradition)?.translations)
|
||||
return translation?.name_for_arcane_spellworks ?? translation?.name
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a JSON representation of the rules text for a cantrip.
|
||||
*/
|
||||
export const getCantripLibraryEntry = createLibraryEntryCreator<
|
||||
Cantrip,
|
||||
{
|
||||
getTargetCategoryById: GetById.Static.TargetCategory
|
||||
getPropertyById: GetById.Static.Property
|
||||
getMagicalTraditionById: GetById.Static.MagicalTradition
|
||||
getCurriculumById: GetById.Static.Curriculum
|
||||
}
|
||||
>(
|
||||
(entry, { getTargetCategoryById, getPropertyById, getMagicalTraditionById, getCurriculumById }) =>
|
||||
({ translate, translateMap, localeCompare }) => {
|
||||
const translation = translateMap(entry.translations)
|
||||
|
||||
if (translation === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const range = getTextForCantripRange({ translate }, entry.parameters.range, {
|
||||
responsiveText: ResponsiveTextSize.Full,
|
||||
})
|
||||
|
||||
const duration = getTextForCantripDuration(
|
||||
{ translate, translateMap },
|
||||
entry.parameters.duration,
|
||||
{
|
||||
responsiveText: ResponsiveTextSize.Full,
|
||||
},
|
||||
)
|
||||
|
||||
return {
|
||||
title: translation.name,
|
||||
className: "cantrip",
|
||||
content: [
|
||||
{
|
||||
label: translate("Effect"),
|
||||
value: translation.effect,
|
||||
},
|
||||
{
|
||||
label: translate("Range"),
|
||||
value: range !== translation.range ? `***${range}*** (${translation.range})` : range,
|
||||
},
|
||||
{
|
||||
label: translate("Duration"),
|
||||
value:
|
||||
duration !== translation.duration
|
||||
? `***${duration}*** (${translation.duration})`
|
||||
: duration,
|
||||
},
|
||||
getTextForTargetCategory(
|
||||
{ translate, translateMap, getTargetCategoryById },
|
||||
entry.target,
|
||||
),
|
||||
getTextForProperty({ translate, translateMap, getPropertyById }, entry.property),
|
||||
mapNullable(entry.note, note => ({
|
||||
label: translate("Note"),
|
||||
value: (() => {
|
||||
switch (note.tag) {
|
||||
case "Common":
|
||||
return note.common.list
|
||||
.map(academyOrTradition => {
|
||||
switch (academyOrTradition.tag) {
|
||||
case "Academy":
|
||||
return translateMap(
|
||||
getCurriculumById(academyOrTradition.academy.id.curriculum)
|
||||
?.translations,
|
||||
)?.name
|
||||
case "Tradition": {
|
||||
return mapNullable(
|
||||
getTraditionNameForArcaneSpellworksById(
|
||||
academyOrTradition.tradition,
|
||||
getMagicalTraditionById,
|
||||
translateMap,
|
||||
),
|
||||
name =>
|
||||
name +
|
||||
parensIf(
|
||||
translateMap(academyOrTradition.tradition.translations)?.note,
|
||||
),
|
||||
)
|
||||
}
|
||||
default:
|
||||
return assertExhaustive(academyOrTradition)
|
||||
}
|
||||
})
|
||||
.filter(isNotNullish)
|
||||
.sort(localeCompare)
|
||||
.join(", ")
|
||||
|
||||
case "Exclusive":
|
||||
return note.exclusive.traditions
|
||||
.map(tradition =>
|
||||
getTraditionNameForArcaneSpellworksById(
|
||||
tradition,
|
||||
getMagicalTraditionById,
|
||||
translateMap,
|
||||
),
|
||||
)
|
||||
.filter(isNotNullish)
|
||||
.sort(localeCompare)
|
||||
.join(", ")
|
||||
|
||||
default:
|
||||
return assertExhaustive(note)
|
||||
}
|
||||
})(),
|
||||
})),
|
||||
],
|
||||
src: entry.src,
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
/**
|
||||
* Get a JSON representation of the rules text for a skill.
|
||||
*/
|
||||
export const getSpellLibraryEntry = createLibraryEntryCreator<
|
||||
Spell,
|
||||
{
|
||||
getAttributeById: GetById.Static.Attribute
|
||||
getDerivedCharacteristicById: GetById.Static.DerivedCharacteristic
|
||||
getSkillModificationLevelById: GetById.Static.SkillModificationLevel
|
||||
getTargetCategoryById: GetById.Static.TargetCategory
|
||||
getPropertyById: GetById.Static.Property
|
||||
getMagicalTraditionById: GetById.Static.MagicalTradition
|
||||
}
|
||||
>(
|
||||
(
|
||||
entry,
|
||||
{
|
||||
getAttributeById,
|
||||
getDerivedCharacteristicById,
|
||||
getSkillModificationLevelById,
|
||||
getTargetCategoryById,
|
||||
getPropertyById,
|
||||
getMagicalTraditionById,
|
||||
},
|
||||
) =>
|
||||
({ translate, translateMap, localeCompare }) => {
|
||||
const translation = translateMap(entry.translations)
|
||||
|
||||
if (translation === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const { castingTime, cost, range, duration } = (() => {
|
||||
switch (entry.parameters.tag) {
|
||||
case "OneTime":
|
||||
return getTextForFastOneTimePerformanceParameters(
|
||||
{
|
||||
getSkillModificationLevelById,
|
||||
translate,
|
||||
translateMap,
|
||||
},
|
||||
entry.parameters.one_time,
|
||||
{
|
||||
entity: Entity.Spell,
|
||||
responsiveText: ResponsiveTextSize.Full,
|
||||
},
|
||||
)
|
||||
|
||||
case "Sustained":
|
||||
return getTextForFastSustainedPerformanceParameters(
|
||||
{
|
||||
getSkillModificationLevelById,
|
||||
translate,
|
||||
translateMap,
|
||||
},
|
||||
entry.parameters.sustained,
|
||||
{
|
||||
entity: Entity.Spell,
|
||||
responsiveText: ResponsiveTextSize.Full,
|
||||
},
|
||||
)
|
||||
|
||||
default:
|
||||
return assertExhaustive(entry.parameters)
|
||||
}
|
||||
})()
|
||||
|
||||
return {
|
||||
title: translation.name,
|
||||
className: "spell",
|
||||
content: [
|
||||
getTextForCheck({ translate, translateMap, getAttributeById }, entry.check, {
|
||||
value: entry.check_penalty,
|
||||
responsiveText: ResponsiveTextSize.Full,
|
||||
getDerivedCharacteristicById,
|
||||
}),
|
||||
...getTextForEffect(translation.effect, translate),
|
||||
{
|
||||
label: translate("Casting Time"),
|
||||
value:
|
||||
castingTime !== translation.casting_time.full
|
||||
? `***${castingTime}*** (${translation.casting_time.full})`
|
||||
: castingTime,
|
||||
},
|
||||
{
|
||||
label: translate("AE Cost"),
|
||||
value:
|
||||
cost !== translation.cost.full ? `***${cost}*** (${translation.cost.full})` : cost,
|
||||
},
|
||||
{
|
||||
label: translate("Range"),
|
||||
value:
|
||||
range !== translation.range.full
|
||||
? `***${range}*** (${translation.range.full})`
|
||||
: range,
|
||||
},
|
||||
{
|
||||
label: translate("Duration"),
|
||||
value:
|
||||
duration !== translation.duration.full
|
||||
? `***${duration}*** (${translation.duration.full})`
|
||||
: duration,
|
||||
},
|
||||
getTextForTargetCategory(
|
||||
{ translate, translateMap, getTargetCategoryById },
|
||||
entry.target,
|
||||
),
|
||||
getTextForProperty({ translate, translateMap, getPropertyById }, entry.property),
|
||||
getTextForTraditions(
|
||||
{ translate, translateMap, localeCompare, getMagicalTraditionById },
|
||||
entry.traditions,
|
||||
),
|
||||
createImprovementCost(translate, entry.improvement_cost),
|
||||
],
|
||||
src: entry.src,
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
/**
|
||||
* Get a JSON representation of the rules text for a ritual.
|
||||
*/
|
||||
export const getRitualLibraryEntry = createLibraryEntryCreator<
|
||||
Ritual,
|
||||
{
|
||||
getAttributeById: GetById.Static.Attribute
|
||||
getDerivedCharacteristicById: GetById.Static.DerivedCharacteristic
|
||||
getSkillModificationLevelById: GetById.Static.SkillModificationLevel
|
||||
getTargetCategoryById: GetById.Static.TargetCategory
|
||||
getPropertyById: GetById.Static.Property
|
||||
getMagicalTraditionById: GetById.Static.MagicalTradition
|
||||
}
|
||||
>(
|
||||
(
|
||||
entry,
|
||||
{
|
||||
getAttributeById,
|
||||
getDerivedCharacteristicById,
|
||||
getSkillModificationLevelById,
|
||||
getTargetCategoryById,
|
||||
getPropertyById,
|
||||
getMagicalTraditionById,
|
||||
},
|
||||
) =>
|
||||
({ translate, translateMap, localeCompare }) => {
|
||||
const translation = translateMap(entry.translations)
|
||||
|
||||
if (translation === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const { castingTime, cost, range, duration } = (() => {
|
||||
switch (entry.parameters.tag) {
|
||||
case "OneTime":
|
||||
return getTextForSlowOneTimePerformanceParameters(
|
||||
{
|
||||
getSkillModificationLevelById,
|
||||
translate,
|
||||
translateMap,
|
||||
},
|
||||
entry.parameters.one_time,
|
||||
{
|
||||
entity: Entity.Ritual,
|
||||
responsiveText: ResponsiveTextSize.Full,
|
||||
},
|
||||
)
|
||||
|
||||
case "Sustained":
|
||||
return getTextForSlowSustainedPerformanceParameters(
|
||||
{
|
||||
getSkillModificationLevelById,
|
||||
translate,
|
||||
translateMap,
|
||||
},
|
||||
entry.parameters.sustained,
|
||||
{
|
||||
entity: Entity.Ritual,
|
||||
responsiveText: ResponsiveTextSize.Full,
|
||||
},
|
||||
)
|
||||
|
||||
default:
|
||||
return assertExhaustive(entry.parameters)
|
||||
}
|
||||
})()
|
||||
|
||||
return {
|
||||
title: translation.name,
|
||||
className: "ritual",
|
||||
content: [
|
||||
getTextForCheck({ translate, translateMap, getAttributeById }, entry.check, {
|
||||
value: entry.check_penalty,
|
||||
responsiveText: ResponsiveTextSize.Full,
|
||||
getDerivedCharacteristicById,
|
||||
}),
|
||||
...getTextForEffect(translation.effect, translate),
|
||||
{
|
||||
label: translate("Ritual Time"),
|
||||
value:
|
||||
castingTime !== translation.casting_time.full
|
||||
? `***${castingTime}*** (${translation.casting_time.full})`
|
||||
: castingTime,
|
||||
},
|
||||
{
|
||||
label: translate("AE Cost"),
|
||||
value:
|
||||
cost !== translation.cost.full ? `***${cost}*** (${translation.cost.full})` : cost,
|
||||
},
|
||||
{
|
||||
label: translate("Range"),
|
||||
value:
|
||||
range !== translation.range.full
|
||||
? `***${range}*** (${translation.range.full})`
|
||||
: range,
|
||||
},
|
||||
{
|
||||
label: translate("Duration"),
|
||||
value:
|
||||
duration !== translation.duration.full
|
||||
? `***${duration}*** (${translation.duration.full})`
|
||||
: duration,
|
||||
},
|
||||
getTextForTargetCategory(
|
||||
{ translate, translateMap, getTargetCategoryById },
|
||||
entry.target,
|
||||
),
|
||||
getTextForProperty({ translate, translateMap, getPropertyById }, entry.property),
|
||||
getTextForTraditions(
|
||||
{ translate, translateMap, localeCompare, getMagicalTraditionById },
|
||||
entry.traditions,
|
||||
),
|
||||
createImprovementCost(translate, entry.improvement_cost),
|
||||
],
|
||||
src: entry.src,
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import { FocusRule } from "optolith-database-schema/types/rule/FocusRule"
|
||||
import { romanize } from "../../utils/roman.ts"
|
||||
import { GetById } from "../getTypes.ts"
|
||||
import { createLibraryEntryCreator } from "../libraryEntry.ts"
|
||||
import { RuleDependency } from "./ruleDependency.ts"
|
||||
|
||||
/**
|
||||
@@ -37,25 +34,3 @@ export const isFocusRuleActive = (
|
||||
getDynamicFocusRuleById: GetById.Dynamic.FocusRule,
|
||||
focusRuleId: number,
|
||||
): boolean => getDynamicFocusRuleById(focusRuleId)?.active ?? false
|
||||
|
||||
/**
|
||||
* Get a JSON representation of the rules text for a focus rule.
|
||||
*/
|
||||
export const getFocusRuleLibraryEntry = createLibraryEntryCreator<
|
||||
FocusRule,
|
||||
{ getSubjectById: GetById.Static.Subject }
|
||||
>((entry, { getSubjectById }) => ({ translateMap }) => {
|
||||
const translation = translateMap(entry.translations)
|
||||
|
||||
if (translation === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return {
|
||||
title: `${translation.name} (${romanize(entry.level)})`,
|
||||
subtitle: translateMap(getSubjectById(entry.subject.id.subject)?.translations)?.name,
|
||||
className: "focus-rule",
|
||||
content: [{ value: translation.description }],
|
||||
src: entry.src,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { OptionalRule } from "optolith-database-schema/types/rule/OptionalRule"
|
||||
import { GetById } from "../getTypes.ts"
|
||||
import { createLibraryEntryCreator } from "../libraryEntry.ts"
|
||||
import { RuleDependency } from "./ruleDependency.ts"
|
||||
|
||||
/**
|
||||
@@ -41,24 +39,3 @@ export const isOptionalRuleActive = (
|
||||
getDynamicOptionalRuleById: GetById.Dynamic.OptionalRule,
|
||||
optionalRuleId: number,
|
||||
): boolean => getDynamicOptionalRuleById(optionalRuleId)?.active ?? false
|
||||
|
||||
/**
|
||||
* Get a JSON representation of the rules text for an optional rule.
|
||||
*/
|
||||
export const getOptionalRuleLibraryEntry = createLibraryEntryCreator<OptionalRule>(
|
||||
entry =>
|
||||
({ translateMap }) => {
|
||||
const translation = translateMap(entry.translations)
|
||||
|
||||
if (translation === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return {
|
||||
title: translation.name,
|
||||
className: "optional-rule",
|
||||
content: [{ value: translation.description }],
|
||||
src: entry.src,
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
import assert from "node:assert/strict"
|
||||
import { describe, it } from "node:test"
|
||||
import { comparePage } from "./pages.ts"
|
||||
|
||||
describe("comparePage", () => {
|
||||
it("returns 0 if the pages are equal", () => {
|
||||
assert.equal(
|
||||
comparePage(
|
||||
{ tag: "InsideCoverBack", inside_cover_back: {} },
|
||||
{ tag: "InsideCoverBack", inside_cover_back: {} },
|
||||
),
|
||||
0,
|
||||
)
|
||||
assert.equal(
|
||||
comparePage(
|
||||
{ tag: "InsideCoverFront", inside_cover_front: {} },
|
||||
{ tag: "InsideCoverFront", inside_cover_front: {} },
|
||||
),
|
||||
0,
|
||||
)
|
||||
assert.equal(
|
||||
comparePage({ tag: "Numbered", numbered: 42 }, { tag: "Numbered", numbered: 42 }),
|
||||
0,
|
||||
)
|
||||
})
|
||||
|
||||
it("returns a negative number if first should be sorted before the second", () => {
|
||||
assert.equal(
|
||||
comparePage(
|
||||
{ tag: "InsideCoverFront", inside_cover_front: {} },
|
||||
{ tag: "InsideCoverBack", inside_cover_back: {} },
|
||||
),
|
||||
-1,
|
||||
)
|
||||
assert.equal(
|
||||
comparePage(
|
||||
{ tag: "InsideCoverFront", inside_cover_front: {} },
|
||||
{ tag: "Numbered", numbered: 42 },
|
||||
),
|
||||
-1,
|
||||
)
|
||||
assert.equal(
|
||||
comparePage(
|
||||
{ tag: "Numbered", numbered: 42 },
|
||||
{ tag: "InsideCoverBack", inside_cover_back: {} },
|
||||
),
|
||||
-1,
|
||||
)
|
||||
assert.equal(
|
||||
comparePage({ tag: "Numbered", numbered: 24 }, { tag: "Numbered", numbered: 42 }),
|
||||
-18,
|
||||
)
|
||||
})
|
||||
|
||||
it("returns a positive number if first should be sorted after the second", () => {
|
||||
assert.equal(
|
||||
comparePage(
|
||||
{ tag: "InsideCoverBack", inside_cover_back: {} },
|
||||
{ tag: "InsideCoverFront", inside_cover_front: {} },
|
||||
),
|
||||
1,
|
||||
)
|
||||
assert.equal(
|
||||
comparePage(
|
||||
{ tag: "Numbered", numbered: 42 },
|
||||
{ tag: "InsideCoverFront", inside_cover_front: {} },
|
||||
),
|
||||
1,
|
||||
)
|
||||
assert.equal(
|
||||
comparePage(
|
||||
{ tag: "InsideCoverBack", inside_cover_back: {} },
|
||||
{ tag: "Numbered", numbered: 42 },
|
||||
),
|
||||
1,
|
||||
)
|
||||
assert.equal(
|
||||
comparePage({ tag: "Numbered", numbered: 42 }, { tag: "Numbered", numbered: 24 }),
|
||||
18,
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -1,114 +0,0 @@
|
||||
import {
|
||||
Page,
|
||||
PageRange as RawPageRange,
|
||||
SimpleOccurrence,
|
||||
} from "optolith-database-schema/types/source/_PublicationRef"
|
||||
import { range } from "../../utils/array.ts"
|
||||
import { Compare } from "../../utils/compare.ts"
|
||||
import { assertExhaustive } from "../../utils/typeSafety.ts"
|
||||
|
||||
/**
|
||||
* A comparison function for two pages.
|
||||
*/
|
||||
export const comparePage: Compare<Page> = (a, b) => {
|
||||
switch (a.tag) {
|
||||
case "InsideCoverFront":
|
||||
return b.tag === "InsideCoverFront" ? 0 : -1
|
||||
case "InsideCoverBack":
|
||||
return b.tag === "InsideCoverBack" ? 0 : 1
|
||||
case "Numbered":
|
||||
return b.tag === "Numbered" ? a.numbered - b.numbered : b.tag === "InsideCoverFront" ? 1 : -1
|
||||
default:
|
||||
return assertExhaustive(a)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if two pages are equal.
|
||||
*/
|
||||
export const equalsPage = (a: Page, b: Page): boolean => comparePage(a, b) === 0
|
||||
|
||||
/**
|
||||
* Returns the successor of a page.
|
||||
*/
|
||||
export const succ = (page: Page): Page => {
|
||||
switch (page.tag) {
|
||||
case "InsideCoverFront":
|
||||
return { tag: "Numbered", numbered: 1 }
|
||||
case "InsideCoverBack":
|
||||
return { tag: "InsideCoverFront", inside_cover_front: {} }
|
||||
case "Numbered":
|
||||
return { tag: "Numbered", numbered: page.numbered + 1 }
|
||||
default:
|
||||
return assertExhaustive(page)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a page object for a page number.
|
||||
*/
|
||||
export const numberToPage = (number: number): Page => ({ tag: "Numbered", numbered: number })
|
||||
|
||||
/**
|
||||
* A range of pages, including the first and last page, if the range includes
|
||||
* more than on page.
|
||||
*/
|
||||
export type PageRange = {
|
||||
firstPage: Page
|
||||
lastPage?: Page
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a numeric range to a page object range.
|
||||
*/
|
||||
export const numberRangeToPageRange = (numberRange: SimpleOccurrence): PageRange =>
|
||||
numberRange.last_page === undefined
|
||||
? { firstPage: numberToPage(numberRange.first_page) }
|
||||
: {
|
||||
firstPage: numberToPage(numberRange.first_page),
|
||||
lastPage: numberToPage(numberRange.last_page),
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a page object range from the database to a local page object range.
|
||||
*/
|
||||
export const fromRawPageRange = (pageRange: RawPageRange): PageRange =>
|
||||
pageRange.last_page === undefined
|
||||
? { firstPage: pageRange.first_page }
|
||||
: {
|
||||
firstPage: pageRange.first_page,
|
||||
lastPage: pageRange.last_page,
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts and combines page ranges while removing duplicates.
|
||||
*/
|
||||
export const normalizePageRanges = (ranges: PageRange[]): PageRange[] =>
|
||||
ranges
|
||||
.flatMap(({ firstPage, lastPage = firstPage }): Page[] => {
|
||||
if (firstPage.tag === "Numbered" && lastPage.tag === "Numbered") {
|
||||
return range(firstPage.numbered, lastPage.numbered).map(numbered => ({
|
||||
tag: "Numbered",
|
||||
numbered,
|
||||
}))
|
||||
} else {
|
||||
return [firstPage, lastPage]
|
||||
}
|
||||
})
|
||||
.filter((page, i, pages) => pages.findIndex(p => equalsPage(page, p)) === i)
|
||||
.sort(comparePage)
|
||||
.reduce<PageRange[]>((acc, page): PageRange[] => {
|
||||
const lastRange = acc[acc.length - 1]
|
||||
|
||||
if (lastRange === undefined) {
|
||||
return [{ firstPage: page }]
|
||||
}
|
||||
|
||||
const { firstPage, lastPage = firstPage } = lastRange
|
||||
|
||||
if (equalsPage(page, succ(lastPage))) {
|
||||
return [...acc.slice(0, -1), { firstPage, lastPage: page }]
|
||||
}
|
||||
|
||||
return [...acc, { firstPage: page }]
|
||||
}, [])
|
||||
Reference in New Issue
Block a user