fix: check available adventure points when adding rated entries

This commit is contained in:
Lukas Obermann
2024-02-29 18:56:45 +01:00
parent 8fb0542fe9
commit 739264f725
117 changed files with 3002 additions and 1777 deletions
@@ -1,36 +0,0 @@
/**
* Filter and count `ActivatableSkill` entries.
*
* @author Lukas Obermann
*/
import { filter, OrderedMap, size } from "../../../Data/OrderedMap"
import { Record } from "../../../Data/Record"
import { ActivatableSkillDependent } from "../../Models/ActiveEntries/ActivatableSkillDependent"
import { HeroModel, HeroModelRecord } from "../../Models/Hero/HeroModel"
import { pipe } from "../pipe"
type ActivatableSkillEntriesAccessor =
(hero: HeroModelRecord) => OrderedMap<string, Record<ActivatableSkillDependent>>
const { active } = ActivatableSkillDependent.AL
/**
* Get all active `ActivatableSkillDependent` entries from the specified domain.
*/
export const getActiveSkillEntries =
(domain: "spells" | "liturgicalChants") =>
pipe (
HeroModel.AL[domain] as ActivatableSkillEntriesAccessor,
filter (active)
)
/**
* Count all active skills from the specified domain.
*/
export const countActiveSkillEntries =
(domain: "spells" | "liturgicalChants") =>
pipe (
getActiveSkillEntries (domain),
size
)
+286
View File
@@ -0,0 +1,286 @@
import { ActionCreatorWithPayload } from "@reduxjs/toolkit"
import { useCallback } from "react"
import {
ImprovementCost,
adventurePointsForActivation,
adventurePointsForIncrement,
adventurePointsForRange,
} from "../../shared/domain/adventurePoints/improvementCost.ts"
import { useTranslate } from "../../shared/hooks/translate.ts"
import { isOk } from "../../shared/utils/result.ts"
import { Translate } from "../../shared/utils/translate.ts"
import { showAlert } from "../slices/alertsSlice.ts"
import {
getAlertForReason,
useAreEnoughAdventurePointsAvailableToBuy,
} from "./areEnoughAdventurePointsAvailableToBuy.ts"
import { useAppDispatch } from "./redux.ts"
const useHandleAdd = (
id: number,
improvementCost: ImprovementCost,
createAddAction: ActionCreatorWithPayload<{ id: number }>,
dispatch: ReturnType<typeof useAppDispatch>,
areEnoughAdventurePointsAvailableToBuy: ReturnType<
typeof useAreEnoughAdventurePointsAvailableToBuy
>,
translate: Translate,
) =>
useCallback((): void => {
const enoughAP = areEnoughAdventurePointsAvailableToBuy(
adventurePointsForActivation(improvementCost),
)
if (enoughAP !== undefined) {
if (isOk(enoughAP)) {
dispatch(createAddAction({ id }))
} else {
dispatch(showAlert(getAlertForReason(enoughAP.error, translate)))
}
}
}, [
areEnoughAdventurePointsAvailableToBuy,
createAddAction,
dispatch,
id,
improvementCost,
translate,
])
const useHandleAddPoint = (
id: number,
value: number,
improvementCost: ImprovementCost,
createIncrementAction: ActionCreatorWithPayload<{ id: number }>,
dispatch: ReturnType<typeof useAppDispatch>,
areEnoughAdventurePointsAvailableToBuy: ReturnType<
typeof useAreEnoughAdventurePointsAvailableToBuy
>,
translate: Translate,
) =>
useCallback((): void => {
const enoughAP = areEnoughAdventurePointsAvailableToBuy(
adventurePointsForIncrement(improvementCost, value),
)
if (enoughAP !== undefined) {
if (isOk(enoughAP)) {
dispatch(createIncrementAction({ id }))
} else {
dispatch(showAlert(getAlertForReason(enoughAP.error, translate)))
}
}
}, [
areEnoughAdventurePointsAvailableToBuy,
createIncrementAction,
dispatch,
id,
improvementCost,
translate,
value,
])
const useHandleSetToMaximumPoints = (
id: number,
value: number,
maximum: number,
improvementCost: ImprovementCost,
createSetAction: ActionCreatorWithPayload<{ id: number; value: number }>,
dispatch: ReturnType<typeof useAppDispatch>,
areEnoughAdventurePointsAvailableToBuy: ReturnType<
typeof useAreEnoughAdventurePointsAvailableToBuy
>,
translate: Translate,
) =>
useCallback((): void => {
const enoughAP = areEnoughAdventurePointsAvailableToBuy(
adventurePointsForRange(improvementCost, value, maximum),
)
if (enoughAP !== undefined) {
if (isOk(enoughAP)) {
dispatch(createSetAction({ id, value: maximum }))
} else {
dispatch(showAlert(getAlertForReason(enoughAP.error, translate)))
}
}
}, [
areEnoughAdventurePointsAvailableToBuy,
createSetAction,
dispatch,
id,
improvementCost,
maximum,
translate,
value,
])
const useHandleSetToMinimumPoints = (
id: number,
minimum: number,
createSetAction: ActionCreatorWithPayload<{ id: number; value: number }>,
dispatch: ReturnType<typeof useAppDispatch>,
) =>
useCallback((): void => {
dispatch(createSetAction({ id, value: minimum }))
}, [createSetAction, dispatch, id, minimum])
const useHandleRemovePoint = (
id: number,
createDecrementAction: ActionCreatorWithPayload<{ id: number }>,
dispatch: ReturnType<typeof useAppDispatch>,
) =>
useCallback(() => {
dispatch(createDecrementAction({ id }))
}, [createDecrementAction, dispatch, id])
const useHandleRemove = (
id: number,
createRemoveAction: ActionCreatorWithPayload<{ id: number }>,
dispatch: ReturnType<typeof useAppDispatch>,
) =>
useCallback(() => {
dispatch(createRemoveAction({ id }))
}, [createRemoveAction, dispatch, id])
/**
* Returns a set of callbacks for interaction with rated entries.
*/
export const useRatedActions = (
id: number,
value: number,
maximum: number,
minimum: number,
improvementCost: ImprovementCost,
createIncrementAction: ActionCreatorWithPayload<{ id: number }>,
createDecrementAction: ActionCreatorWithPayload<{ id: number }>,
createSetAction: ActionCreatorWithPayload<{ id: number; value: number }>,
) => {
const dispatch = useAppDispatch()
const areEnoughAdventurePointsAvailableToBuy = useAreEnoughAdventurePointsAvailableToBuy()
const translate = useTranslate()
const handleAddPoint = useHandleAddPoint(
id,
value,
improvementCost,
createIncrementAction,
dispatch,
areEnoughAdventurePointsAvailableToBuy,
translate,
)
const handleSetToMaximumPoints = useHandleSetToMaximumPoints(
id,
value,
maximum,
improvementCost,
createSetAction,
dispatch,
areEnoughAdventurePointsAvailableToBuy,
translate,
)
const handleSetToMinimumPoints = useHandleSetToMinimumPoints(
id,
minimum,
createSetAction,
dispatch,
)
const handleRemovePoint = useHandleRemovePoint(id, createDecrementAction, dispatch)
return {
handleAddPoint,
handleRemovePoint,
handleSetToMaximumPoints,
handleSetToMinimumPoints,
}
}
/**
* Returns a set of callbacks for interaction with active activatable rated
* entries.
*/
export const useActiveActivatableActions = (
id: number,
value: number,
maximum: number,
minimum: number,
improvementCost: ImprovementCost,
createIncrementAction: ActionCreatorWithPayload<{ id: number }>,
createDecrementAction: ActionCreatorWithPayload<{ id: number }>,
createSetAction: ActionCreatorWithPayload<{ id: number; value: number }>,
createRemoveAction: ActionCreatorWithPayload<{ id: number }>,
) => {
const dispatch = useAppDispatch()
const areEnoughAdventurePointsAvailableToBuy = useAreEnoughAdventurePointsAvailableToBuy()
const translate = useTranslate()
const handleAddPoint = useHandleAddPoint(
id,
value,
improvementCost,
createIncrementAction,
dispatch,
areEnoughAdventurePointsAvailableToBuy,
translate,
)
const handleSetToMaximumPoints = useHandleSetToMaximumPoints(
id,
value,
maximum,
improvementCost,
createSetAction,
dispatch,
areEnoughAdventurePointsAvailableToBuy,
translate,
)
const handleSetToMinimumPoints = useHandleSetToMinimumPoints(
id,
minimum,
createSetAction,
dispatch,
)
const handleRemovePoint = useHandleRemovePoint(id, createDecrementAction, dispatch)
const handleRemove = useHandleRemove(id, createRemoveAction, dispatch)
return {
handleAddPoint,
handleRemovePoint,
handleSetToMaximumPoints,
handleSetToMinimumPoints,
handleRemove,
}
}
/**
* Returns a set of callbacks for interaction with inactive activatable rated
* entries.
*/
export const useInactiveActivatableActions = (
id: number,
improvementCost: ImprovementCost,
createAddAction: ActionCreatorWithPayload<{ id: number }>,
) => {
const dispatch = useAppDispatch()
const areEnoughAdventurePointsAvailableToBuy = useAreEnoughAdventurePointsAvailableToBuy()
const translate = useTranslate()
const handleAdd = useHandleAdd(
id,
improvementCost,
createAddAction,
dispatch,
areEnoughAdventurePointsAvailableToBuy,
translate,
)
return {
handleAdd,
}
}
@@ -1,4 +1,4 @@
import { FC } from "react"
import { FC, useMemo } from "react"
import { useLocaleCompare } from "../../../shared/hooks/localeCompare.ts"
import { useTranslate } from "../../../shared/hooks/translate.ts"
import { useTranslateMap } from "../../../shared/hooks/translateMap.ts"
@@ -6,6 +6,7 @@ import { isNotNullish } from "../../../shared/utils/nullable.ts"
import { assertExhaustive } from "../../../shared/utils/typeSafety.ts"
import { useAppSelector } from "../../hooks/redux.ts"
import {
selectNewApplicationsAndUsesCache,
selectStaticAttributes,
selectStaticBlessedTraditions,
selectStaticDiseases,
@@ -37,17 +38,30 @@ export const InlineLibrarySkill: FC<Props> = ({ id }) => {
const regions = useAppSelector(selectStaticRegions)
const entry = useAppSelector(selectStaticSkills)[id]
const translation = translateMap(entry?.translations)
const cache = useAppSelector(selectNewApplicationsAndUsesCache)
const newApplications = useMemo(
() =>
(entry === undefined ? [] : cache.newApplications[entry.id] ?? [])
.map(x => translateMap(x.data.translations)?.name)
.filter(isNotNullish)
.sort(localeCompare),
[cache.newApplications, entry, localeCompare, translateMap],
)
const uses = useMemo(
() =>
(entry === undefined ? [] : cache.uses[entry.id] ?? [])
.map(x => translateMap(x.data.translations)?.name)
.filter(isNotNullish)
.sort(localeCompare),
[cache.uses, entry, localeCompare, translateMap],
)
if (entry === undefined || translation === undefined) {
return <InlineLibraryPlaceholder />
}
// TODO: Implement
const newApplications = []
// TODO: Implement
const uses = []
const applications = (() => {
switch (entry.applications.tag) {
case "Derived":
@@ -86,14 +100,18 @@ export const InlineLibrarySkill: FC<Props> = ({ id }) => {
<InlineLibraryTemplate className="Skill" title={translation?.name ?? entry.id.toString()}>
<InlineLibraryProperties
list={[
{
label: translate("New Applications"),
value: newApplications.join(", "),
},
{
label: translate("Uses"),
value: uses.join(", "),
},
newApplications.length === 0
? undefined
: {
label: translate("New Applications"),
value: newApplications.join(", "),
},
uses.length === 0
? undefined
: {
label: translate("Uses"),
value: uses.join(", "),
},
createCheck(translate, translateMap, attributes, entry.check),
{
label: translate("Applications"),
@@ -1,11 +1,19 @@
import { FC, useCallback } from "react"
import { FC, MouseEvent, useCallback } from "react"
import { IconButton } from "../../../../../shared/components/iconButton/IconButton.tsx"
import { NumberBox } from "../../../../../shared/components/numberBox/NumberBox.tsx"
import {
attributeImprovementCost,
minimumAttributeValue,
} from "../../../../../shared/domain/rated/attribute.ts"
import { useTranslate } from "../../../../../shared/hooks/translate.ts"
import { useTranslateMap } from "../../../../../shared/hooks/translateMap.ts"
import { useAppDispatch } from "../../../../hooks/redux.ts"
import { useRatedActions } from "../../../../hooks/ratedActions.ts"
import { DisplayedAttribute } from "../../../../selectors/attributeSelectors.ts"
import { decrementAttribute, incrementAttribute } from "../../../../slices/attributesSlice.ts"
import {
decrementAttribute,
incrementAttribute,
setAttribute,
} from "../../../../slices/attributesSlice.ts"
import { AttributeBorder } from "./AttributeBorder.tsx"
type Props = {
@@ -24,21 +32,53 @@ export const AttributeListItem: FC<Props> = props => {
const translateMap = useTranslateMap()
const translations = translateMap(attribute.static.translations)
const dispatch = useAppDispatch()
const {
dynamic: { value },
static: { id },
maximum: max,
maximum,
isDecreasable,
isIncreasable,
} = attribute
const valueHeader = isInCharacterCreation ? `${value} / ${max}` : value
const valueHeader = isInCharacterCreation ? `${value} / ${maximum}` : value
const handleAdd = useCallback(() => dispatch(incrementAttribute(id)), [dispatch, id])
const { handleAddPoint, handleRemovePoint, handleSetToMaximumPoints, handleSetToMinimumPoints } =
useRatedActions(
id,
value,
maximum ?? value,
minimumAttributeValue,
attributeImprovementCost,
incrementAttribute,
decrementAttribute,
setAttribute,
)
const handleRemove = useCallback(() => dispatch(decrementAttribute(id)), [dispatch, id])
const handleAdd = useCallback(
(event: MouseEvent<HTMLButtonElement>) => {
if (isIncreasable) {
if (event.shiftKey && maximum !== undefined) {
handleSetToMaximumPoints()
} else {
handleAddPoint()
}
}
},
[handleAddPoint, handleSetToMaximumPoints, isIncreasable, maximum],
)
const handleRemove = useCallback(
(event: MouseEvent<HTMLButtonElement>) => {
if (isDecreasable) {
if (event.shiftKey) {
handleSetToMinimumPoints()
} else {
handleRemovePoint()
}
}
},
[handleRemovePoint, handleSetToMinimumPoints, isDecreasable],
)
return (
<AttributeBorder
@@ -56,7 +96,7 @@ export const AttributeListItem: FC<Props> = props => {
}
tooltipMargin={11}
>
{isInCharacterCreation ? <NumberBox max={max} /> : null}
{isInCharacterCreation ? <NumberBox max={maximum} /> : null}
<IconButton
className="add"
icon="&#xE908;"
@@ -1,15 +1,21 @@
import { AttributeReference } from "optolith-database-schema/types/_SimpleReferences"
import { FC, memo, useCallback } from "react"
import { ListItem } from "../../../../../shared/components/list/ListItem.tsx"
import { ListItemGroup } from "../../../../../shared/components/list/ListItemGroup.tsx"
import { ListItemName } from "../../../../../shared/components/list/ListItemName.tsx"
import { ListItemSeparator } from "../../../../../shared/components/list/ListItemSeparator.tsx"
import { ListItemValues } from "../../../../../shared/components/list/ListItemValues.tsx"
import { ImprovementCost } from "../../../../../shared/domain/adventurePoints/improvementCost.ts"
import { fromRaw } from "../../../../../shared/domain/adventurePoints/improvementCost.ts"
import { useTranslate } from "../../../../../shared/hooks/translate.ts"
import { useTranslateMap } from "../../../../../shared/hooks/translateMap.ts"
import { useRatedActions } from "../../../../hooks/ratedActions.ts"
import { useAppDispatch, useAppSelector } from "../../../../hooks/redux.ts"
import { selectCanRemove } from "../../../../selectors/characterSelectors.ts"
import { DisplayedCloseCombatTechnique } from "../../../../selectors/combatTechniquesSelectors.ts"
import {
decrementCloseCombatTechnique,
incrementCloseCombatTechnique,
setCloseCombatTechnique,
} from "../../../../slices/closeCombatTechniqueSlice.ts"
import { selectStaticAttributes } from "../../../../slices/databaseSlice.ts"
import {
changeInlineLibraryEntry,
@@ -22,33 +28,22 @@ import { SkillRating } from "../skills/SkillRating.tsx"
type Props = {
insertTopMargin?: boolean
id: number
name: string
sr: number
primary: AttributeReference[]
ic: ImprovementCost
addDisabled: boolean
removeDisabled: boolean
at: number
pa?: number
addPoint: (id: number) => void
removePoint: (id: number) => void
closeCombatTechnique: DisplayedCloseCombatTechnique
}
const CloseCombatTechniquesListItem: FC<Props> = props => {
const {
insertTopMargin,
id,
name,
sr,
primary,
ic,
addDisabled,
removeDisabled,
at,
pa,
addPoint,
removePoint,
closeCombatTechnique: {
static: { id, primary_attribute, improvement_cost, translations },
dynamic: { value },
maximum,
minimum,
isIncreasable,
isDecreasable,
attackBase,
parryBase,
},
} = props
const translate = useTranslate()
@@ -57,6 +52,20 @@ const CloseCombatTechniquesListItem: FC<Props> = props => {
const inlineLibraryEntryId = useAppSelector(selectInlineLibraryEntryId)
const canRemove = useAppSelector(selectCanRemove)
const { name = "???" } = translateMap(translations) ?? {}
const { handleAddPoint, handleRemovePoint, handleSetToMaximumPoints, handleSetToMinimumPoints } =
useRatedActions(
id,
value,
maximum,
minimum,
fromRaw(improvement_cost),
incrementCloseCombatTechnique,
decrementCloseCombatTechnique,
setCloseCombatTechnique,
)
const handleSelectForInfo = useCallback(
() =>
dispatch(
@@ -67,11 +76,11 @@ const CloseCombatTechniquesListItem: FC<Props> = props => {
const attributes = useAppSelector(selectStaticAttributes)
const primaryStr = primary
const primaryStr = primary_attribute
.map(ref => translateMap(attributes[ref.id.attribute]?.translations)?.abbreviation ?? "")
.join("/")
const customClassName = `attr--${primary.map(ref => ref.id.attribute).join("-")}`
const customClassName = `attr--${primary_attribute.map(ref => ref.id.attribute).join("-")}`
const primaryClassName = `primary ${customClassName}`
@@ -87,28 +96,27 @@ const CloseCombatTechniquesListItem: FC<Props> = props => {
<ListItemSeparator />
<ListItemGroup text={translate("Close Combat")} />
<ListItemValues>
<SkillRating sr={sr} addPoint={addPoint} />
<SkillImprovementCost ic={ic} />
<SkillRating sr={value} />
<SkillImprovementCost ic={fromRaw(improvement_cost)} />
<SkillAdditionalValues
addValues={[
{ className: primaryClassName, value: primaryStr },
{ className: "at", value: at },
{ className: "at", value: attackBase },
{ className: "atpa" },
{
className: "pa",
value: pa ?? "—",
value: parryBase ?? "—",
},
]}
/>
</ListItemValues>
<SkillButtons
addDisabled={addDisabled}
ic={ic}
id={id}
removeDisabled={removeDisabled}
sr={sr}
addPoint={addPoint}
removePoint={canRemove ? removePoint : undefined}
addDisabled={!isIncreasable}
removeDisabled={!isDecreasable}
addPoint={handleAddPoint}
setToMax={handleSetToMaximumPoints}
removePoint={canRemove ? handleRemovePoint : undefined}
setToMin={canRemove ? handleSetToMinimumPoints : undefined}
selectForInfo={handleSelectForInfo}
/>
</ListItem>
@@ -26,14 +26,6 @@ import {
DisplayedCombatTechnique,
selectVisibleCombatTechniques,
} from "../../../../selectors/combatTechniquesSelectors.ts"
import {
decrementCloseCombatTechnique,
incrementCloseCombatTechnique,
} from "../../../../slices/closeCombatTechniqueSlice.ts"
import {
decrementRangedCombatTechnique,
incrementRangedCombatTechnique,
} from "../../../../slices/rangedCombatTechniqueSlice.ts"
import {
changeCombatTechniquesSortOrder,
selectCombatTechniquesSortOrder,
@@ -100,26 +92,6 @@ export const CombatTechniques: FC = () => {
[filterText, localeCompare, sortOrder, translateMap, visibleCombatTechniques],
)
const handleAddCloseCombatTechniquePoint = useCallback(
(id: number) => dispatch(incrementCloseCombatTechnique(id)),
[dispatch],
)
const handleRemoveCloseCombatTechniquePoint = useCallback(
(id: number) => dispatch(decrementCloseCombatTechnique(id)),
[dispatch],
)
const handleAddRangedCombatTechniquePoint = useCallback(
(id: number) => dispatch(incrementRangedCombatTechnique(id)),
[dispatch],
)
const handleRemoveRangedCombatTechniquePoint = useCallback(
(id: number) => dispatch(decrementRangedCombatTechnique(id)),
[dispatch],
)
return (
<Page id="combat-techniques">
<Options>
@@ -146,35 +118,22 @@ export const CombatTechniques: FC = () => {
</Options>
<Main>
<ListHeader>
<ListHeaderTag className="name">
{translate("combattechniques.header.name")}
<ListHeaderTag className="name">{translate("Name")}</ListHeaderTag>
<ListHeaderTag className="group">{translate("Group")}</ListHeaderTag>
<ListHeaderTag className="value" hint={translate("Combat Technique Rating")}>
{translate("CTR")}
</ListHeaderTag>
<ListHeaderTag className="group">
{translate("combattechniques.header.group")}
<ListHeaderTag className="ic" hint={translate("Improvement Cost")}>
{translate("IC")}
</ListHeaderTag>
<ListHeaderTag
className="value"
hint={translate("combattechniques.header.combattechniquerating.tooltip")}
>
{translate("combattechniques.header.combattechniquerating")}
<ListHeaderTag className="primary" hint={translate("Primary Attribute(s)")}>
{translate("P")}
</ListHeaderTag>
<ListHeaderTag
className="ic"
hint={translate("combattechniques.header.improvementcost.tooltip")}
>
{translate("combattechniques.header.improvementcost")}
<ListHeaderTag className="at" hint={translate("Attack")}>
{translate("AT")}
</ListHeaderTag>
<ListHeaderTag
className="primary"
hint={translate("combattechniques.header.primaryattribute.tooltip")}
>
{translate("combattechniques.header.primaryattribute")}
</ListHeaderTag>
<ListHeaderTag className="at" hint={translate("combattechniques.header.attack.tooltip")}>
{translate("combattechniques.header.attack")}
</ListHeaderTag>
<ListHeaderTag className="pa" hint={translate("combattechniques.header.parry.tooltip")}>
{translate("combattechniques.header.parry")}
<ListHeaderTag className="pa" hint={translate("Parry")}>
{translate("PA")}
</ListHeaderTag>
{canRemove ? <ListHeaderTag className="btn-placeholder" /> : null}
<ListHeaderTag className="btn-placeholder" />
@@ -184,44 +143,26 @@ export const CombatTechniques: FC = () => {
{list.length > 0 ? (
<List>
{list.map((x, i) => {
const translation = translateMap(x.static.translations)
if (x.kind === "close") {
return (
<CloseCombatTechniquesListItem
key={`close--${x.static.id}`}
insertTopMargin={isTopMarginNeeded(sortOrder, x, list[i - 1])}
id={x.static.id}
name={translation?.name ?? ""}
sr={x.dynamic.value}
primary={x.static.primary_attribute}
ic={fromRaw(x.static.improvement_cost)}
addDisabled={!x.isIncreasable}
removeDisabled={!x.isDecreasable}
at={x.attackBase}
pa={x.parryBase}
addPoint={handleAddCloseCombatTechniquePoint}
removePoint={handleRemoveCloseCombatTechniquePoint}
/>
)
switch (x.kind) {
case "close":
return (
<CloseCombatTechniquesListItem
key={`close--${x.static.id}`}
insertTopMargin={isTopMarginNeeded(sortOrder, x, list[i - 1])}
closeCombatTechnique={x}
/>
)
case "ranged":
return (
<RangedCombatTechniquesListItem
key={`ranged--${x.static.id}`}
insertTopMargin={isTopMarginNeeded(sortOrder, x, list[i - 1])}
rangedCombatTechnique={x}
/>
)
default:
return assertExhaustive(x)
}
return (
<RangedCombatTechniquesListItem
key={`ranged--${x.static.id}`}
insertTopMargin={isTopMarginNeeded(sortOrder, x, list[i - 1])}
id={x.static.id}
name={translation?.name ?? ""}
sr={x.dynamic.value}
primary={x.static.primary_attribute}
ic={fromRaw(x.static.improvement_cost)}
addDisabled={!x.isIncreasable}
removeDisabled={!x.isDecreasable}
at={x.attackBase}
addPoint={handleAddRangedCombatTechniquePoint}
removePoint={handleRemoveRangedCombatTechniquePoint}
/>
)
})}
</List>
) : (
@@ -1,20 +1,26 @@
import { AttributeReference } from "optolith-database-schema/types/_SimpleReferences"
import { FC, memo, useCallback } from "react"
import { ListItem } from "../../../../../shared/components/list/ListItem.tsx"
import { ListItemGroup } from "../../../../../shared/components/list/ListItemGroup.tsx"
import { ListItemName } from "../../../../../shared/components/list/ListItemName.tsx"
import { ListItemSeparator } from "../../../../../shared/components/list/ListItemSeparator.tsx"
import { ListItemValues } from "../../../../../shared/components/list/ListItemValues.tsx"
import { ImprovementCost } from "../../../../../shared/domain/adventurePoints/improvementCost.ts"
import { fromRaw } from "../../../../../shared/domain/adventurePoints/improvementCost.ts"
import { useTranslate } from "../../../../../shared/hooks/translate.ts"
import { useTranslateMap } from "../../../../../shared/hooks/translateMap.ts"
import { useRatedActions } from "../../../../hooks/ratedActions.ts"
import { useAppDispatch, useAppSelector } from "../../../../hooks/redux.ts"
import { selectCanRemove } from "../../../../selectors/characterSelectors.ts"
import { DisplayedRangedCombatTechnique } from "../../../../selectors/combatTechniquesSelectors.ts"
import { selectStaticAttributes } from "../../../../slices/databaseSlice.ts"
import {
changeInlineLibraryEntry,
selectInlineLibraryEntryId,
} from "../../../../slices/inlineWikiSlice.ts"
import {
decrementRangedCombatTechnique,
incrementRangedCombatTechnique,
setRangedCombatTechnique,
} from "../../../../slices/rangedCombatTechniqueSlice.ts"
import { SkillAdditionalValues } from "../skills/SkillAdditionalValues.tsx"
import { SkillButtons } from "../skills/SkillButtons.tsx"
import { SkillImprovementCost } from "../skills/SkillImprovementCost.tsx"
@@ -22,31 +28,21 @@ import { SkillRating } from "../skills/SkillRating.tsx"
type Props = {
insertTopMargin?: boolean
id: number
name: string
sr: number
primary: AttributeReference[]
ic: ImprovementCost
addDisabled: boolean
removeDisabled: boolean
at: number
addPoint: (id: number) => void
removePoint: (id: number) => void
rangedCombatTechnique: DisplayedRangedCombatTechnique
}
const RangedCombatTechniquesListItem: FC<Props> = props => {
const {
insertTopMargin,
id,
name,
sr,
primary,
ic,
addDisabled,
removeDisabled,
at,
addPoint,
removePoint,
rangedCombatTechnique: {
static: { id, primary_attribute, improvement_cost, translations },
dynamic: { value },
maximum,
minimum,
isIncreasable,
isDecreasable,
attackBase,
},
} = props
const translate = useTranslate()
@@ -55,6 +51,20 @@ const RangedCombatTechniquesListItem: FC<Props> = props => {
const inlineLibraryEntryId = useAppSelector(selectInlineLibraryEntryId)
const canRemove = useAppSelector(selectCanRemove)
const { name = "???" } = translateMap(translations) ?? {}
const { handleAddPoint, handleRemovePoint, handleSetToMaximumPoints, handleSetToMinimumPoints } =
useRatedActions(
id,
value,
maximum,
minimum,
fromRaw(improvement_cost),
incrementRangedCombatTechnique,
decrementRangedCombatTechnique,
setRangedCombatTechnique,
)
const handleSelectForInfo = useCallback(
() =>
dispatch(
@@ -65,11 +75,11 @@ const RangedCombatTechniquesListItem: FC<Props> = props => {
const attributes = useAppSelector(selectStaticAttributes)
const primaryStr = primary
const primaryStr = primary_attribute
.map(ref => translateMap(attributes[ref.id.attribute]?.translations)?.abbreviation ?? "")
.join("/")
const customClassName = `attr--${primary.map(ref => ref.id.attribute).join("-")}`
const customClassName = `attr--${primary_attribute.map(ref => ref.id.attribute).join("-")}`
const primaryClassName = `primary ${customClassName}`
@@ -85,12 +95,12 @@ const RangedCombatTechniquesListItem: FC<Props> = props => {
<ListItemSeparator />
<ListItemGroup text={translate("Ranged Combat")} />
<ListItemValues>
<SkillRating sr={sr} addPoint={addPoint} />
<SkillImprovementCost ic={ic} />
<SkillRating sr={value} />
<SkillImprovementCost ic={fromRaw(improvement_cost)} />
<SkillAdditionalValues
addValues={[
{ className: primaryClassName, value: primaryStr },
{ className: "at", value: at },
{ className: "at", value: attackBase },
{ className: "atpa" },
{
className: "pa",
@@ -100,13 +110,12 @@ const RangedCombatTechniquesListItem: FC<Props> = props => {
/>
</ListItemValues>
<SkillButtons
addDisabled={addDisabled}
ic={ic}
id={id}
removeDisabled={removeDisabled}
sr={sr}
addPoint={addPoint}
removePoint={canRemove ? removePoint : undefined}
addDisabled={!isIncreasable}
removeDisabled={!isDecreasable}
addPoint={handleAddPoint}
setToMax={handleSetToMaximumPoints}
removePoint={canRemove ? handleRemovePoint : undefined}
setToMin={canRemove ? handleSetToMinimumPoints : undefined}
selectForInfo={handleSelectForInfo}
/>
</ListItem>
@@ -45,6 +45,10 @@ const ActiveBlessingsListItem: FC<Props> = props => {
const { name = "" } = translateMap(translations) ?? {}
const handleRemove = useCallback(() => {
remove(id)
}, [remove, id])
const handleSelectForInfo = useCallback(
() => dispatch(changeInlineLibraryEntry({ tag: "Blessing", blessing: id })),
[dispatch, id],
@@ -71,8 +75,7 @@ const ActiveBlessingsListItem: FC<Props> = props => {
<SkillFill />
</ListItemValues>
<SkillButtons
id={id}
removePoint={canRemove ? remove : undefined}
removePoint={canRemove ? handleRemove : undefined}
selectForInfo={handleSelectForInfo}
/>
</ListItem>
@@ -11,9 +11,16 @@ import { LiturgiesSortOrder } from "../../../../../shared/domain/sortOrders.ts"
import { useLocaleCompare } from "../../../../../shared/hooks/localeCompare.ts"
import { useTranslate } from "../../../../../shared/hooks/translate.ts"
import { useTranslateMap } from "../../../../../shared/hooks/translateMap.ts"
import { useActiveActivatableActions } from "../../../../hooks/ratedActions.ts"
import { useAppDispatch, useAppSelector } from "../../../../hooks/redux.ts"
import { selectCanRemove } from "../../../../selectors/characterSelectors.ts"
import { selectActiveBlessedTradition } from "../../../../selectors/traditionSelectors.ts"
import {
decrementCeremony,
incrementCeremony,
removeCeremony,
setCeremony,
} from "../../../../slices/ceremoniesSlice.ts"
import { selectStaticAspects } from "../../../../slices/databaseSlice.ts"
import {
changeInlineLibraryEntry,
@@ -29,9 +36,6 @@ type Props = {
insertTopMargin?: boolean
ceremony: DisplayedActiveCeremony
sortOrder: LiturgiesSortOrder
addPoint: (id: number) => void
removePoint: (id: number) => void
remove: (id: number) => void
}
const ActiveCeremoniesListItem: FC<Props> = props => {
@@ -40,13 +44,12 @@ const ActiveCeremoniesListItem: FC<Props> = props => {
ceremony: {
dynamic: { value },
static: { id, check, check_penalty, traditions, improvement_cost, translations },
maximum,
minimum,
isDecreasable,
isIncreasable,
},
sortOrder,
addPoint,
removePoint,
remove,
} = props
const translate = useTranslate()
@@ -60,6 +63,24 @@ const ActiveCeremoniesListItem: FC<Props> = props => {
const { name = "" } = translateMap(translations) ?? {}
const {
handleAddPoint,
handleRemovePoint,
handleSetToMaximumPoints,
handleSetToMinimumPoints,
handleRemove,
} = useActiveActivatableActions(
id,
value,
maximum,
minimum ?? 0,
fromRaw(improvement_cost),
incrementCeremony,
decrementCeremony,
setCeremony,
removeCeremony,
)
const handleSelectForInfo = useCallback(
() => dispatch(changeInlineLibraryEntry({ tag: "Ceremony", ceremony: id })),
[dispatch, id],
@@ -95,19 +116,19 @@ const ActiveCeremoniesListItem: FC<Props> = props => {
}
/>
<ListItemValues>
<SkillRating sr={value} addPoint={addPoint} />
<SkillRating sr={value} />
<SkillCheck check={check} checkPenalty={check_penalty} />
<SkillFill />
<SkillImprovementCost ic={fromRaw(improvement_cost)} />
</ListItemValues>
<SkillButtons
addDisabled={!isIncreasable}
ic={fromRaw(improvement_cost)}
id={id}
removeDisabled={!isDecreasable}
sr={value}
addPoint={addPoint}
removePoint={canRemove ? (value === 0 ? remove : removePoint) : undefined}
addPoint={handleAddPoint}
setToMax={handleSetToMaximumPoints}
removePoint={canRemove ? (value === 0 ? handleRemovePoint : handleRemove) : undefined}
setToMin={canRemove && value > 0 ? handleSetToMinimumPoints : undefined}
decrementIsRemove={value === 0}
selectForInfo={handleSelectForInfo}
/>
</ListItem>
@@ -11,6 +11,7 @@ import { LiturgiesSortOrder } from "../../../../../shared/domain/sortOrders.ts"
import { useLocaleCompare } from "../../../../../shared/hooks/localeCompare.ts"
import { useTranslate } from "../../../../../shared/hooks/translate.ts"
import { useTranslateMap } from "../../../../../shared/hooks/translateMap.ts"
import { useActiveActivatableActions } from "../../../../hooks/ratedActions.ts"
import { useAppDispatch, useAppSelector } from "../../../../hooks/redux.ts"
import { selectCanRemove } from "../../../../selectors/characterSelectors.ts"
import { selectActiveBlessedTradition } from "../../../../selectors/traditionSelectors.ts"
@@ -19,6 +20,12 @@ import {
changeInlineLibraryEntry,
selectInlineLibraryEntryId,
} from "../../../../slices/inlineWikiSlice.ts"
import {
decrementLiturgicalChant,
incrementLiturgicalChant,
removeLiturgicalChant,
setLiturgicalChant,
} from "../../../../slices/liturgicalChantsSlice.ts"
import { SkillButtons } from "../skills/SkillButtons.tsx"
import { SkillCheck } from "../skills/SkillCheck.tsx"
import { SkillFill } from "../skills/SkillFill.tsx"
@@ -29,9 +36,6 @@ type Props = {
insertTopMargin?: boolean
liturgicalChant: DisplayedActiveLiturgicalChant
sortOrder: LiturgiesSortOrder
addPoint: (id: number) => void
removePoint: (id: number) => void
remove: (id: number) => void
}
const ActiveLiturgicalChantsListItem: FC<Props> = props => {
@@ -40,13 +44,12 @@ const ActiveLiturgicalChantsListItem: FC<Props> = props => {
liturgicalChant: {
dynamic: { value },
static: { id, check, check_penalty, traditions, improvement_cost, translations },
maximum,
minimum,
isDecreasable,
isIncreasable,
},
sortOrder,
addPoint,
removePoint,
remove,
} = props
const translate = useTranslate()
@@ -60,6 +63,24 @@ const ActiveLiturgicalChantsListItem: FC<Props> = props => {
const { name = "" } = translateMap(translations) ?? {}
const {
handleAddPoint,
handleRemovePoint,
handleSetToMaximumPoints,
handleSetToMinimumPoints,
handleRemove,
} = useActiveActivatableActions(
id,
value,
maximum,
minimum ?? 0,
fromRaw(improvement_cost),
incrementLiturgicalChant,
decrementLiturgicalChant,
setLiturgicalChant,
removeLiturgicalChant,
)
const handleSelectForInfo = useCallback(
() => dispatch(changeInlineLibraryEntry({ tag: "LiturgicalChant", liturgical_chant: id })),
[dispatch, id],
@@ -98,19 +119,19 @@ const ActiveLiturgicalChantsListItem: FC<Props> = props => {
}
/>
<ListItemValues>
<SkillRating sr={value} addPoint={addPoint} />
<SkillRating sr={value} />
<SkillCheck check={check} checkPenalty={check_penalty} />
<SkillFill />
<SkillImprovementCost ic={fromRaw(improvement_cost)} />
</ListItemValues>
<SkillButtons
addDisabled={!isIncreasable}
ic={fromRaw(improvement_cost)}
id={id}
removeDisabled={!isDecreasable}
sr={value}
addPoint={addPoint}
removePoint={canRemove ? (value === 0 ? remove : removePoint) : undefined}
addPoint={handleAddPoint}
setToMax={handleSetToMaximumPoints}
removePoint={canRemove ? (value === 0 ? handleRemovePoint : handleRemove) : undefined}
setToMin={canRemove && value > 0 ? handleSetToMinimumPoints : undefined}
decrementIsRemove={value === 0}
selectForInfo={handleSelectForInfo}
/>
</ListItem>
@@ -43,6 +43,10 @@ const InactiveBlessingsListItem: FC<Props> = props => {
const { name = "" } = translateMap(translations) ?? {}
const handleAdd = useCallback(() => {
add(id)
}, [add, id])
const handleSelectForInfo = useCallback(
() => dispatch(changeInlineLibraryEntry({ tag: "Blessing", blessing: id })),
[dispatch, id],
@@ -68,7 +72,7 @@ const InactiveBlessingsListItem: FC<Props> = props => {
<ListItemValues>
<SkillFill />
</ListItemValues>
<SkillButtons id={id} addPoint={add} selectForInfo={handleSelectForInfo} />
<SkillButtons addPoint={handleAdd} selectForInfo={handleSelectForInfo} />
</ListItem>
)
}
@@ -11,8 +11,10 @@ import { LiturgiesSortOrder } from "../../../../../shared/domain/sortOrders.ts"
import { useLocaleCompare } from "../../../../../shared/hooks/localeCompare.ts"
import { useTranslate } from "../../../../../shared/hooks/translate.ts"
import { useTranslateMap } from "../../../../../shared/hooks/translateMap.ts"
import { useInactiveActivatableActions } from "../../../../hooks/ratedActions.ts"
import { useAppDispatch, useAppSelector } from "../../../../hooks/redux.ts"
import { selectActiveBlessedTradition } from "../../../../selectors/traditionSelectors.ts"
import { addCeremony } from "../../../../slices/ceremoniesSlice.ts"
import { selectStaticAspects } from "../../../../slices/databaseSlice.ts"
import {
changeInlineLibraryEntry,
@@ -27,7 +29,6 @@ type Props = {
insertTopMargin?: boolean
ceremony: DisplayedInactiveCeremony
sortOrder: LiturgiesSortOrder
add: (id: number) => void
}
const InactiveCeremoniesListItem: FC<Props> = props => {
@@ -38,7 +39,6 @@ const InactiveCeremoniesListItem: FC<Props> = props => {
isAvailable,
},
sortOrder,
add,
} = props
const translate = useTranslate()
@@ -51,6 +51,8 @@ const InactiveCeremoniesListItem: FC<Props> = props => {
const { name = "" } = translateMap(translations) ?? {}
const { handleAdd } = useInactiveActivatableActions(id, fromRaw(improvement_cost), addCeremony)
const handleSelectForInfo = useCallback(
() => dispatch(changeInlineLibraryEntry({ tag: "Ceremony", ceremony: id })),
[dispatch, id],
@@ -93,9 +95,7 @@ const InactiveCeremoniesListItem: FC<Props> = props => {
</ListItemValues>
<SkillButtons
addDisabled={!isAvailable}
ic={fromRaw(improvement_cost)}
id={id}
addPoint={add}
addPoint={handleAdd}
selectForInfo={handleSelectForInfo}
/>
</ListItem>
@@ -11,6 +11,7 @@ import { LiturgiesSortOrder } from "../../../../../shared/domain/sortOrders.ts"
import { useLocaleCompare } from "../../../../../shared/hooks/localeCompare.ts"
import { useTranslate } from "../../../../../shared/hooks/translate.ts"
import { useTranslateMap } from "../../../../../shared/hooks/translateMap.ts"
import { useInactiveActivatableActions } from "../../../../hooks/ratedActions.ts"
import { useAppDispatch, useAppSelector } from "../../../../hooks/redux.ts"
import { selectActiveBlessedTradition } from "../../../../selectors/traditionSelectors.ts"
import { selectStaticAspects } from "../../../../slices/databaseSlice.ts"
@@ -18,6 +19,7 @@ import {
changeInlineLibraryEntry,
selectInlineLibraryEntryId,
} from "../../../../slices/inlineWikiSlice.ts"
import { addLiturgicalChant } from "../../../../slices/liturgicalChantsSlice.ts"
import { SkillButtons } from "../skills/SkillButtons.tsx"
import { SkillCheck } from "../skills/SkillCheck.tsx"
import { SkillFill } from "../skills/SkillFill.tsx"
@@ -27,7 +29,6 @@ type Props = {
insertTopMargin?: boolean
liturgicalChant: DisplayedInactiveLiturgicalChant
sortOrder: LiturgiesSortOrder
add: (id: number) => void
}
const InactiveLiturgicalChantsListItem: FC<Props> = props => {
@@ -38,7 +39,6 @@ const InactiveLiturgicalChantsListItem: FC<Props> = props => {
isAvailable,
},
sortOrder,
add,
} = props
const translate = useTranslate()
@@ -51,6 +51,12 @@ const InactiveLiturgicalChantsListItem: FC<Props> = props => {
const { name = "" } = translateMap(translations) ?? {}
const { handleAdd } = useInactiveActivatableActions(
id,
fromRaw(improvement_cost),
addLiturgicalChant,
)
const handleSelectForInfo = useCallback(
() => dispatch(changeInlineLibraryEntry({ tag: "LiturgicalChant", liturgical_chant: id })),
[dispatch, id],
@@ -96,9 +102,7 @@ const InactiveLiturgicalChantsListItem: FC<Props> = props => {
</ListItemValues>
<SkillButtons
addDisabled={!isAvailable}
ic={fromRaw(improvement_cost)}
id={id}
addPoint={add}
addPoint={handleAdd}
selectForInfo={handleSelectForInfo}
/>
</ListItem>
@@ -28,18 +28,6 @@ import {
selectVisibleInactiveLiturgies,
} from "../../../../selectors/liturgicalChantSelectors.ts"
import { addBlessing, removeBlessing } from "../../../../slices/blessingsSlice.ts"
import {
addCeremony,
decrementCeremony,
incrementCeremony,
removeCeremony,
} from "../../../../slices/ceremoniesSlice.ts"
import {
addLiturgicalChant,
decrementLiturgicalChant,
incrementLiturgicalChant,
removeLiturgicalChant,
} from "../../../../slices/liturgicalChantsSlice.ts"
import {
changeLiturgiesSortOrder,
selectLiturgiesSortOrder,
@@ -103,44 +91,13 @@ export const LiturgicalChants: FC = () => {
[visibleActiveLiturgies, inactiveFilterText, sortOrder, translateMap, localeCompare],
)
const handleAddLiturgicalChantPoint = useCallback(
(id: number) => dispatch(incrementLiturgicalChant(id)),
// TODO: Check AP
const handleAddBlessing = useCallback((id: number) => dispatch(addBlessing({ id })), [dispatch])
const handleRemoveBlessing = useCallback(
(id: number) => dispatch(removeBlessing({ id })),
[dispatch],
)
const handleRemoveLiturgicalChantPoint = useCallback(
(id: number) => dispatch(decrementLiturgicalChant(id)),
[dispatch],
)
const handleAddCeremonyPoint = useCallback(
(id: number) => dispatch(incrementCeremony(id)),
[dispatch],
)
const handleRemoveCeremonyPoint = useCallback(
(id: number) => dispatch(decrementCeremony(id)),
[dispatch],
)
const handleAddBlessing = useCallback((id: number) => dispatch(addBlessing(id)), [dispatch])
const handleAddLiturgicalChant = useCallback(
(id: number) => dispatch(addLiturgicalChant(id)),
[dispatch],
)
const handleAddCeremony = useCallback((id: number) => dispatch(addCeremony(id)), [dispatch])
const handleRemoveBlessing = useCallback((id: number) => dispatch(removeBlessing(id)), [dispatch])
const handleRemoveLiturgicalChant = useCallback(
(id: number) => dispatch(removeLiturgicalChant(id)),
[dispatch],
)
const handleRemoveCeremony = useCallback((id: number) => dispatch(removeCeremony(id)), [dispatch])
const { isOpen: isSlideinVisible, open: openSlidein, close: closeSlidein } = useModalState()
return (
@@ -177,29 +134,17 @@ export const LiturgicalChants: FC = () => {
</Options>
<Main classOnly>
<ListHeader>
<ListHeaderTag className="name">
{translate("liturgicalchants.header.name")}
</ListHeaderTag>
<ListHeaderTag className="name">{translate("Name")}</ListHeaderTag>
<ListHeaderTag className="group">
{translate("liturgicalchants.header.traditions")}
{sortOrder === LiturgiesSortOrder.Group
? ` / ${translate("liturgicalchants.header.group")}`
: null}
{sortOrder === LiturgiesSortOrder.Group ? ` / ${translate("Group")}` : null}
</ListHeaderTag>
<ListHeaderTag className="check">
{translate("liturgicalchants.header.check")}
<ListHeaderTag className="check">{translate("Check")}</ListHeaderTag>
<ListHeaderTag className="mod" hint={translate("Check Modifier")}>
{translate("Mod")}
</ListHeaderTag>
<ListHeaderTag
className="mod"
hint={translate("liturgicalchants.header.checkmodifier.tooltip")}
>
{translate("liturgicalchants.header.checkmodifier")}
</ListHeaderTag>
<ListHeaderTag
className="ic"
hint={translate("liturgicalchants.header.improvementcost.tooltip")}
>
{translate("liturgicalchants.header.improvementcost")}
<ListHeaderTag className="ic" hint={translate("Improvement Cost")}>
{translate("IC")}
</ListHeaderTag>
<ListHeaderTag className="btn-placeholder" />
</ListHeader>
@@ -226,7 +171,6 @@ export const LiturgicalChants: FC = () => {
insertTopMargin={isTopMarginNeeded(sortOrder, x, inactiveList[i - 1])}
liturgicalChant={x}
sortOrder={sortOrder}
add={handleAddLiturgicalChant}
/>
)
@@ -237,7 +181,6 @@ export const LiturgicalChants: FC = () => {
insertTopMargin={isTopMarginNeeded(sortOrder, x, inactiveList[i - 1])}
ceremony={x}
sortOrder={sortOrder}
add={handleAddCeremony}
/>
)
@@ -282,35 +225,20 @@ export const LiturgicalChants: FC = () => {
</Options>
<Main>
<ListHeader>
<ListHeaderTag className="name">
{translate("liturgicalchants.header.name")}
</ListHeaderTag>
<ListHeaderTag className="name">{translate("Name")}</ListHeaderTag>
<ListHeaderTag className="group">
{translate("liturgicalchants.header.traditions")}
{sortOrder === LiturgiesSortOrder.Group
? ` / ${translate("liturgicalchants.header.group")}`
: null}
{sortOrder === LiturgiesSortOrder.Group ? ` / ${translate("Group")}` : null}
</ListHeaderTag>
<ListHeaderTag
className="value"
hint={translate("liturgicalchants.header.skillrating.tooltip")}
>
{translate("liturgicalchants.header.skillrating")}
<ListHeaderTag className="value" hint={translate("Skill Rating")}>
{translate("SR")}
</ListHeaderTag>
<ListHeaderTag className="check">
{translate("liturgicalchants.header.check")}
<ListHeaderTag className="check">{translate("Check")}</ListHeaderTag>
<ListHeaderTag className="mod" hint={translate("Check Modifier")}>
{translate("Mod")}
</ListHeaderTag>
<ListHeaderTag
className="mod"
hint={translate("liturgicalchants.header.checkmodifier.tooltip")}
>
{translate("liturgicalchants.header.checkmodifier")}
</ListHeaderTag>
<ListHeaderTag
className="ic"
hint={translate("liturgicalchants.header.improvementcost.tooltip")}
>
{translate("liturgicalchants.header.improvementcost")}
<ListHeaderTag className="ic" hint={translate("Improvement Cost")}>
{translate("IC")}
</ListHeaderTag>
{canRemove ? <ListHeaderTag className="btn-placeholder" /> : null}
<ListHeaderTag className="btn-placeholder" />
@@ -339,9 +267,6 @@ export const LiturgicalChants: FC = () => {
insertTopMargin={isTopMarginNeeded(sortOrder, x, activeList[i - 1])}
liturgicalChant={x}
sortOrder={sortOrder}
addPoint={handleAddLiturgicalChantPoint}
removePoint={handleRemoveLiturgicalChantPoint}
remove={handleRemoveLiturgicalChant}
/>
)
@@ -352,9 +277,6 @@ export const LiturgicalChants: FC = () => {
insertTopMargin={isTopMarginNeeded(sortOrder, x, activeList[i - 1])}
ceremony={x}
sortOrder={sortOrder}
addPoint={handleAddCeremonyPoint}
removePoint={handleRemoveCeremonyPoint}
remove={handleRemoveCeremony}
/>
)
@@ -1,21 +1,20 @@
import { useCallback } from "react"
import { MouseEvent, useCallback } from "react"
import { IconButton } from "../../../../../shared/components/iconButton/IconButton.tsx"
import { ListItemButtons } from "../../../../../shared/components/list/ListItemButtons.tsx"
import { ImprovementCost } from "../../../../../shared/domain/adventurePoints/improvementCost.ts"
import { useTranslate } from "../../../../../shared/hooks/translate.ts"
type Props = {
activateDisabled?: boolean
addDisabled?: boolean
ic?: ImprovementCost
id: number
isNotActive?: boolean
removeDisabled?: boolean
sr?: number
activate?(id: number): void
addPoint?(id: number): void
removePoint?(id: number): void
selectForInfo(id: number): void
decrementIsRemove?: boolean
activate?(): void
addPoint?(): void
setToMax?(): void
removePoint?(): void
setToMin?(): void
selectForInfo(): void
}
/**
@@ -25,40 +24,43 @@ export const SkillButtons: React.FC<Props> = props => {
const {
activateDisabled,
addDisabled,
ic,
id,
isNotActive,
removeDisabled,
sr,
decrementIsRemove,
activate,
addPoint,
setToMax,
setToMin,
removePoint,
selectForInfo,
} = props
const translate = useTranslate()
const boundSelectForInfo = useCallback(() => selectForInfo(id), [selectForInfo, id])
const getRemoveIcon = () =>
(sr !== undefined && sr === 0 && removeDisabled !== true) || ic === undefined
? "\uE90b"
: "\uE909"
const handleActivation = useCallback(
() => (typeof activate === "function" ? activate(id) : undefined),
[activate, id],
)
const handleAddPoint = useCallback(
() => (addDisabled !== true && typeof addPoint === "function" ? addPoint(id) : undefined),
[addPoint, id, addDisabled],
(event: MouseEvent<HTMLButtonElement>) => {
if (addDisabled !== true && typeof addPoint === "function") {
if (event.shiftKey && typeof setToMax === "function") {
setToMax()
} else {
addPoint()
}
}
},
[addDisabled, addPoint, setToMax],
)
const handleRemovePoint = useCallback(
() =>
removeDisabled !== true && typeof removePoint === "function" ? removePoint(id) : undefined,
[removePoint, id, removeDisabled],
(event: MouseEvent<HTMLButtonElement>) => {
if (removeDisabled !== true && typeof removePoint === "function") {
if (event.shiftKey && typeof setToMin === "function") {
setToMin()
} else {
removePoint()
}
}
},
[removeDisabled, removePoint, setToMin],
)
return (
@@ -66,7 +68,7 @@ export const SkillButtons: React.FC<Props> = props => {
{isNotActive === true ? (
<IconButton
icon="&#xE916;"
onClick={handleActivation}
onClick={activate}
disabled={activateDisabled}
label={translate("Activate")}
flat
@@ -84,7 +86,7 @@ export const SkillButtons: React.FC<Props> = props => {
) : null}
{typeof removePoint === "function" ? (
<IconButton
icon={getRemoveIcon()}
icon={decrementIsRemove === true ? "\uE90b" : "\uE909"}
onClick={handleRemovePoint}
disabled={removeDisabled}
label={translate("Decrement")}
@@ -93,12 +95,7 @@ export const SkillButtons: React.FC<Props> = props => {
) : null}
</>
)}
<IconButton
icon="&#xE912;"
onClick={boundSelectForInfo}
label={translate("Show details")}
flat
/>
<IconButton icon="&#xE912;" onClick={selectForInfo} label={translate("Show details")} flat />
</ListItemButtons>
)
}
@@ -1,21 +0,0 @@
import { FC } from "react"
import { ListItemGroup } from "../../../../../shared/components/list/ListItemGroup.tsx"
type Props = {
addText?: string
group?: number
getGroupName?: (id: number) => string
}
/**
* Returns a row section that display a skill group.
*/
export const SkillGroup: FC<Props> = props => {
const { addText, group, getGroupName } = props
if (addText === undefined && (group === undefined || getGroupName === undefined)) {
return null
}
return <ListItemGroup group={group} getGroupName={getGroupName} text={addText} />
}
@@ -1,125 +1,100 @@
import {
SkillCheckPenalty,
SkillCheck as SkillCheckType,
} from "optolith-database-schema/types/_SkillCheck"
import { memo, useCallback } from "react"
import { memo, useCallback, useMemo } from "react"
import { ListItem } from "../../../../../shared/components/list/ListItem.tsx"
import { ListItemGroup } from "../../../../../shared/components/list/ListItemGroup.tsx"
import { ListItemName } from "../../../../../shared/components/list/ListItemName.tsx"
import { ListItemSeparator } from "../../../../../shared/components/list/ListItemSeparator.tsx"
import { ListItemValues } from "../../../../../shared/components/list/ListItemValues.tsx"
import { ImprovementCost } from "../../../../../shared/domain/adventurePoints/improvementCost.ts"
import { fromRaw } from "../../../../../shared/domain/adventurePoints/improvementCost.ts"
import { useTranslateMap } from "../../../../../shared/hooks/translateMap.ts"
import { useRatedActions } from "../../../../hooks/ratedActions.ts"
import { useAppDispatch, useAppSelector } from "../../../../hooks/redux.ts"
import { SelectGetById } from "../../../../selectors/basicCapabilitySelectors.ts"
import { selectCanRemove } from "../../../../selectors/characterSelectors.ts"
import { DisplayedSkill } from "../../../../selectors/skillsSelectors.ts"
import {
changeInlineLibraryEntry,
selectInlineLibraryEntryId,
} from "../../../../slices/inlineWikiSlice.ts"
import { AdditionalValue, SkillAdditionalValues } from "./SkillAdditionalValues.tsx"
import { selectSkillsCultureRatingVisibility } from "../../../../slices/settingsSlice.ts"
import { decrementSkill, incrementSkill, setSkill } from "../../../../slices/skillsSlice.ts"
import { SkillButtons } from "./SkillButtons.tsx"
import { SkillCheck } from "./SkillCheck.tsx"
import { SkillFill } from "./SkillFill.tsx"
import { SkillGroup } from "./SkillGroup.tsx"
import { SkillImprovementCost } from "./SkillImprovementCost.tsx"
import { SkillRating } from "./SkillRating.tsx"
type Props = {
activateDisabled?: boolean
addDisabled?: boolean
addFillElement?: boolean
addValues?: AdditionalValue[]
addText?: string
check?: SkillCheckType
checkDisabled?: boolean
checkmod?: SkillCheckPenalty
group?: number
ic?: ImprovementCost
id: number
insertTopMargin?: boolean
isNotActive?: boolean
name: string
noIncrease?: boolean
removeDisabled?: boolean
sr?: number
typ?: boolean
untyp?: boolean
activate?(id: number): void
addPoint?(id: number): void
removePoint?(id: number): void
getGroupName?: (id: number) => string
skill: DisplayedSkill
}
const SkillListItem: React.FC<Props> = props => {
const {
activateDisabled,
addDisabled,
addFillElement,
addValues,
addText,
check,
checkDisabled,
checkmod,
getGroupName,
group,
ic,
id,
insertTopMargin,
isNotActive,
name,
noIncrease,
removeDisabled,
sr,
typ,
untyp,
activate,
addPoint,
removePoint,
skill: {
static: { id, check, group, improvement_cost, translations },
dynamic: { value },
maximum,
minimum,
isIncreasable,
isDecreasable,
commonness,
},
} = props
const translateMap = useTranslateMap()
const dispatch = useAppDispatch()
const inlineLibraryEntryId = useAppSelector(selectInlineLibraryEntryId)
const canRemove = useAppSelector(selectCanRemove)
const cultureRatingVisibility = useAppSelector(selectSkillsCultureRatingVisibility)
const { name = "???" } = translateMap(translations) ?? {}
const { handleAddPoint, handleRemovePoint, handleSetToMaximumPoints, handleSetToMinimumPoints } =
useRatedActions(
id,
value,
maximum,
minimum,
fromRaw(improvement_cost),
incrementSkill,
decrementSkill,
setSkill,
)
const handleSelectForInfo = useCallback(
() => dispatch(changeInlineLibraryEntry({ tag: "Skill", skill: id })),
[dispatch, id],
)
const getStaticSkillGroupById = useAppSelector(SelectGetById.Static.SkillGroup)
const groupName = useMemo(
() => translateMap(getStaticSkillGroupById(group.id.skill_group)?.translations)?.name,
[getStaticSkillGroupById, group.id.skill_group, translateMap],
)
return (
<ListItem
noIncrease={noIncrease}
recommended={typ}
unrecommended={untyp}
insertTopMargin={insertTopMargin}
active={inlineLibraryEntryId?.tag === "Skill" && inlineLibraryEntryId.skill === id}
recommended={cultureRatingVisibility && commonness === "common"}
unrecommended={cultureRatingVisibility && commonness === "uncommon"}
>
<ListItemName name={name} onClick={handleSelectForInfo} />
<ListItemSeparator />
<SkillGroup addText={addText} group={group} getGroupName={getGroupName} />
<ListItemGroup text={groupName} />
<ListItemValues>
<SkillRating
isNotActive={isNotActive}
noIncrease={noIncrease}
sr={sr}
addPoint={addPoint}
/>
{checkDisabled !== true && check !== undefined ? (
<SkillCheck check={check} checkPenalty={checkmod} />
) : null}
{addFillElement === true ? <SkillFill /> : null}
<SkillImprovementCost ic={ic} />
<SkillAdditionalValues addValues={addValues} />
<SkillRating sr={value} />
<SkillCheck check={check} />
<SkillImprovementCost ic={fromRaw(improvement_cost)} />
</ListItemValues>
<SkillButtons
activateDisabled={activateDisabled}
addDisabled={addDisabled}
ic={ic}
id={id}
isNotActive={isNotActive}
removeDisabled={removeDisabled}
sr={sr}
activate={activate}
addPoint={addPoint}
removePoint={canRemove ? removePoint : undefined}
addDisabled={!isIncreasable}
removeDisabled={!isDecreasable}
addPoint={handleAddPoint}
setToMax={handleSetToMaximumPoints}
removePoint={canRemove ? handleRemovePoint : undefined}
setToMin={canRemove ? handleSetToMinimumPoints : undefined}
selectForInfo={handleSelectForInfo}
/>
</ListItem>
@@ -4,18 +4,17 @@ type Props = {
isNotActive?: boolean
noIncrease?: boolean
sr?: number
addPoint?(id: number): void
}
/**
* Returns a row section that displays a skill rating.
*/
export const SkillRating: FC<Props> = props => {
const { isNotActive, noIncrease, sr, addPoint } = props
const { isNotActive, noIncrease, sr } = props
if (typeof sr === "number") {
return <div className="sr">{sr}</div>
} else if (addPoint === undefined && isNotActive !== true && noIncrease !== true) {
} else if (isNotActive !== true && noIncrease !== true) {
return <div className="sr empty" />
}
@@ -26,14 +26,12 @@ import { useAppDispatch, useAppSelector } from "../../../../hooks/redux.ts"
import { InlineLibrary } from "../../../../inlineLibrary/InlineLibrary.tsx"
import { selectCanRemove } from "../../../../selectors/characterSelectors.ts"
import { DisplayedSkill, selectVisibleSkills } from "../../../../selectors/skillsSelectors.ts"
import { selectStaticSkillGroups } from "../../../../slices/databaseSlice.ts"
import {
changeSkillsSortOrder,
selectSkillsCultureRatingVisibility,
selectSkillsSortOrder,
switchSkillsCultureRatingVisibility,
} from "../../../../slices/settingsSlice.ts"
import { decrementSkill, incrementSkill } from "../../../../slices/skillsSlice.ts"
import { SkillListItem } from "./SkillListItem.tsx"
import "./Skills.scss"
@@ -62,7 +60,6 @@ export const Skills: FC = () => {
)
const canRemove = useAppSelector(selectCanRemove)
const skillGroups = useAppSelector(selectStaticSkillGroups)
const [filterText, setFilterText] = useState("")
const sortOrder = useAppSelector(selectSkillsSortOrder)
@@ -70,7 +67,9 @@ export const Skills: FC = () => {
(id: SkillsSortOrder) => dispatch(changeSkillsSortOrder(id)),
[dispatch],
)
const visibleSkills = useAppSelector(selectVisibleSkills)
const list = useMemo(
() =>
visibleSkills
@@ -106,15 +105,6 @@ export const Skills: FC = () => {
[filterText, localeCompare, sortOrder, translateMap, visibleSkills],
)
const handleAdd = useCallback((id: number) => dispatch(incrementSkill(id)), [dispatch])
const handleRemove = useCallback((id: number) => dispatch(decrementSkill(id)), [dispatch])
const getGroupName = useCallback(
(id: number) => translateMap(skillGroups[id]?.translations)?.name ?? "",
[skillGroups, translateMap],
)
return (
<Page id="skills">
<Options>
@@ -140,21 +130,21 @@ export const Skills: FC = () => {
/>
<Grid size="medium">
<Checkbox checked={cultureRatingVisibility} onClick={handlSwitchCultureRatingVisibility}>
{translate("skills.commonskills")}
{translate("Common Skills")}
</Checkbox>
{cultureRatingVisibility ? <RecommendedReference /> : null}
</Grid>
</Options>
<Main>
<ListHeader>
<ListHeaderTag className="name">{translate("skills.header.name")}</ListHeaderTag>
<ListHeaderTag className="group">{translate("skills.header.group")}</ListHeaderTag>
<ListHeaderTag className="value" hint={translate("skills.header.skillrating.tooltip")}>
{translate("skills.header.skillrating")}
<ListHeaderTag className="name">{translate("Name")}</ListHeaderTag>
<ListHeaderTag className="group">{translate("Group")}</ListHeaderTag>
<ListHeaderTag className="value" hint={translate("Skill Rating")}>
{translate("SR")}
</ListHeaderTag>
<ListHeaderTag className="check">{translate("skills.header.check")}</ListHeaderTag>
<ListHeaderTag className="ic" hint={translate("skills.header.improvementcost.tooltip")}>
{translate("skills.header.improvementcost")}
<ListHeaderTag className="check">{translate("Check")}</ListHeaderTag>
<ListHeaderTag className="ic" hint={translate("Improvement Cost")}>
{translate("IC")}
</ListHeaderTag>
{canRemove ? <ListHeaderTag className="btn-placeholder" /> : null}
<ListHeaderTag className="btn-placeholder" />
@@ -163,29 +153,13 @@ export const Skills: FC = () => {
<Scroll stable>
{list.length > 0 ? (
<List>
{list.map((x, i) => {
const translation = translateMap(x.static.translations)
return (
<SkillListItem
key={x.static.id}
id={x.static.id}
typ={cultureRatingVisibility && x.commonness === "common"}
untyp={cultureRatingVisibility && x.commonness === "uncommon"}
name={translation?.name ?? ""}
sr={x.dynamic.value}
check={x.static.check}
ic={fromRaw(x.static.improvement_cost)}
addDisabled={!x.isIncreasable}
addPoint={handleAdd}
removeDisabled={!x.isDecreasable}
removePoint={handleRemove}
addFillElement
insertTopMargin={isTopMarginNeeded(sortOrder, x, list[i - 1])}
group={x.static.group.id.skill_group}
getGroupName={getGroupName}
/>
)
})}
{list.map((x, i) => (
<SkillListItem
key={x.static.id}
insertTopMargin={isTopMarginNeeded(sortOrder, x, list[i - 1])}
skill={x}
/>
))}
</List>
) : (
<ListPlaceholder type="skills" message={translate("No Results")} />
@@ -1,28 +1,45 @@
import { FC, memo } from "react"
import {
ImprovementCost,
fromRaw,
} from "../../../../../shared/domain/adventurePoints/improvementCost.ts"
import { DisplayedActiveAnimistPower } from "../../../../../shared/domain/rated/spellActive.ts"
import { SpellsSortOrder } from "../../../../../shared/domain/sortOrders.ts"
import { useTranslate } from "../../../../../shared/hooks/translate.ts"
import { assertExhaustive } from "../../../../../shared/utils/typeSafety.ts"
import { useActiveActivatableActions } from "../../../../hooks/ratedActions.ts"
import {
decrementAnimistPower,
incrementAnimistPower,
removeAnimistPower,
setAnimistPower,
} from "../../../../slices/magicalActions/animistPowersSlice.ts"
import { ActiveMagicalActionsListItem } from "./ActiveMagicalActionsListItem.tsx"
type Props = {
insertTopMargin?: boolean
animistPower: DisplayedActiveAnimistPower
sortOrder: SpellsSortOrder
addPoint: (id: number) => void
removePoint: (id: number) => void
remove: (id: number) => void
}
const ActiveAnimistPowersListItem: FC<Props> = props => {
const { insertTopMargin, animistPower, sortOrder, addPoint, removePoint, remove } = props
const { insertTopMargin, animistPower, sortOrder } = props
const translate = useTranslate()
const {
handleAddPoint,
handleRemovePoint,
handleSetToMaximumPoints,
handleSetToMinimumPoints,
handleRemove,
} = useActiveActivatableActions(
animistPower.static.id,
animistPower.dynamic.value,
animistPower.maximum,
animistPower.minimum ?? 0,
animistPower.improvementCost,
incrementAnimistPower,
decrementAnimistPower,
setAnimistPower,
removeAnimistPower,
)
return (
<ActiveMagicalActionsListItem
kind="AnimistPower"
@@ -30,20 +47,12 @@ const ActiveAnimistPowersListItem: FC<Props> = props => {
magicalAction={animistPower}
sortOrder={sortOrder}
groupName={translate("Animist Powers")}
improvementCost={(() => {
switch (animistPower.static.improvement_cost.tag) {
case "Fixed":
return fromRaw(animistPower.static.improvement_cost.fixed)
case "ByPrimaryPatron":
// TODO: Replace with derived improvement cost
return ImprovementCost.D
default:
return assertExhaustive(animistPower.static.improvement_cost)
}
})()}
addPoint={addPoint}
removePoint={removePoint}
remove={remove}
improvementCost={animistPower.improvementCost}
addPoint={handleAddPoint}
removePoint={handleRemovePoint}
setToMaximumPoints={handleSetToMaximumPoints}
setToMinimumPoints={handleSetToMinimumPoints}
remove={handleRemove}
/>
)
}
@@ -45,6 +45,10 @@ const ActiveCantripsListItem: FC<Props> = props => {
const { name = "" } = translateMap(translations) ?? {}
const handleRemove = useCallback(() => {
remove(id)
}, [remove, id])
const handleSelectForInfo = useCallback(
() => dispatch(changeInlineLibraryEntry({ tag: "Cantrip", cantrip: id })),
[dispatch, id],
@@ -75,8 +79,7 @@ const ActiveCantripsListItem: FC<Props> = props => {
<SkillFill />
</ListItemValues>
<SkillButtons
id={id}
removePoint={canRemove ? remove : undefined}
removePoint={canRemove ? handleRemove : undefined}
selectForInfo={handleSelectForInfo}
/>
</ListItem>
@@ -3,22 +3,44 @@ import { cursesImprovementCost } from "../../../../../shared/domain/rated/magica
import { DisplayedActiveCurse } from "../../../../../shared/domain/rated/spellActive.ts"
import { SpellsSortOrder } from "../../../../../shared/domain/sortOrders.ts"
import { useTranslate } from "../../../../../shared/hooks/translate.ts"
import { useActiveActivatableActions } from "../../../../hooks/ratedActions.ts"
import {
decrementCurse,
incrementCurse,
removeCurse,
setCurse,
} from "../../../../slices/magicalActions/cursesSlice.ts"
import { ActiveMagicalActionsListItem } from "./ActiveMagicalActionsListItem.tsx"
type Props = {
insertTopMargin?: boolean
curse: DisplayedActiveCurse
sortOrder: SpellsSortOrder
addPoint: (id: number) => void
removePoint: (id: number) => void
remove: (id: number) => void
}
const ActiveCursesListItem: FC<Props> = props => {
const { insertTopMargin, curse, sortOrder, addPoint, removePoint, remove } = props
const { insertTopMargin, curse, sortOrder } = props
const translate = useTranslate()
const {
handleAddPoint,
handleRemovePoint,
handleSetToMaximumPoints,
handleSetToMinimumPoints,
handleRemove,
} = useActiveActivatableActions(
curse.static.id,
curse.dynamic.value,
curse.maximum,
curse.minimum ?? 0,
cursesImprovementCost,
incrementCurse,
decrementCurse,
setCurse,
removeCurse,
)
return (
<ActiveMagicalActionsListItem
kind="Curse"
@@ -28,9 +50,11 @@ const ActiveCursesListItem: FC<Props> = props => {
groupName={translate("Curses")}
checkPenalty={curse.static.check_penalty}
improvementCost={cursesImprovementCost}
addPoint={addPoint}
removePoint={removePoint}
remove={remove}
addPoint={handleAddPoint}
removePoint={handleRemovePoint}
setToMaximumPoints={handleSetToMaximumPoints}
setToMinimumPoints={handleSetToMinimumPoints}
remove={handleRemove}
/>
)
}
@@ -3,22 +3,44 @@ import { dominationRitualsImprovementCost } from "../../../../../shared/domain/r
import { DisplayedActiveDominationRitual } from "../../../../../shared/domain/rated/spellActive.ts"
import { SpellsSortOrder } from "../../../../../shared/domain/sortOrders.ts"
import { useTranslate } from "../../../../../shared/hooks/translate.ts"
import { useActiveActivatableActions } from "../../../../hooks/ratedActions.ts"
import {
decrementDominationRitual,
incrementDominationRitual,
removeDominationRitual,
setDominationRitual,
} from "../../../../slices/magicalActions/dominationRitualsSlice.ts"
import { ActiveMagicalActionsListItem } from "./ActiveMagicalActionsListItem.tsx"
type Props = {
insertTopMargin?: boolean
dominationRitual: DisplayedActiveDominationRitual
sortOrder: SpellsSortOrder
addPoint: (id: number) => void
removePoint: (id: number) => void
remove: (id: number) => void
}
const ActiveDominationRitualsListItem: FC<Props> = props => {
const { insertTopMargin, dominationRitual, sortOrder, addPoint, removePoint, remove } = props
const { insertTopMargin, dominationRitual, sortOrder } = props
const translate = useTranslate()
const {
handleAddPoint,
handleRemovePoint,
handleSetToMaximumPoints,
handleSetToMinimumPoints,
handleRemove,
} = useActiveActivatableActions(
dominationRitual.static.id,
dominationRitual.dynamic.value,
dominationRitual.maximum,
dominationRitual.minimum ?? 0,
dominationRitualsImprovementCost,
incrementDominationRitual,
decrementDominationRitual,
setDominationRitual,
removeDominationRitual,
)
return (
<ActiveMagicalActionsListItem
kind="DominationRitual"
@@ -28,9 +50,11 @@ const ActiveDominationRitualsListItem: FC<Props> = props => {
groupName={translate("Domination Rituals")}
checkPenalty={dominationRitual.static.check_penalty}
improvementCost={dominationRitualsImprovementCost}
addPoint={addPoint}
removePoint={removePoint}
remove={remove}
addPoint={handleAddPoint}
removePoint={handleRemovePoint}
setToMaximumPoints={handleSetToMaximumPoints}
setToMinimumPoints={handleSetToMinimumPoints}
remove={handleRemove}
/>
)
}
@@ -3,22 +3,44 @@ import { fromRaw } from "../../../../../shared/domain/adventurePoints/improvemen
import { DisplayedActiveElvenMagicalSong } from "../../../../../shared/domain/rated/spellActive.ts"
import { SpellsSortOrder } from "../../../../../shared/domain/sortOrders.ts"
import { useTranslate } from "../../../../../shared/hooks/translate.ts"
import { useActiveActivatableActions } from "../../../../hooks/ratedActions.ts"
import {
decrementElvenMagicalSong,
incrementElvenMagicalSong,
removeElvenMagicalSong,
setElvenMagicalSong,
} from "../../../../slices/magicalActions/elvenMagicalSongsSlice.ts"
import { ActiveMagicalActionsListItem } from "./ActiveMagicalActionsListItem.tsx"
type Props = {
insertTopMargin?: boolean
elvenMagicalSong: DisplayedActiveElvenMagicalSong
sortOrder: SpellsSortOrder
addPoint: (id: number) => void
removePoint: (id: number) => void
remove: (id: number) => void
}
const ActiveElvenMagicalSongsListItem: FC<Props> = props => {
const { insertTopMargin, elvenMagicalSong, sortOrder, addPoint, removePoint, remove } = props
const { insertTopMargin, elvenMagicalSong, sortOrder } = props
const translate = useTranslate()
const {
handleAddPoint,
handleRemovePoint,
handleSetToMaximumPoints,
handleSetToMinimumPoints,
handleRemove,
} = useActiveActivatableActions(
elvenMagicalSong.static.id,
elvenMagicalSong.dynamic.value,
elvenMagicalSong.maximum,
elvenMagicalSong.minimum ?? 0,
fromRaw(elvenMagicalSong.static.improvement_cost),
incrementElvenMagicalSong,
decrementElvenMagicalSong,
setElvenMagicalSong,
removeElvenMagicalSong,
)
return (
<ActiveMagicalActionsListItem
kind="ElvenMagicalSong"
@@ -28,9 +50,11 @@ const ActiveElvenMagicalSongsListItem: FC<Props> = props => {
groupName={translate("Elven Magical Songs")}
checkPenalty={elvenMagicalSong.static.check_penalty}
improvementCost={fromRaw(elvenMagicalSong.static.improvement_cost)}
addPoint={addPoint}
removePoint={removePoint}
remove={remove}
addPoint={handleAddPoint}
removePoint={handleRemovePoint}
setToMaximumPoints={handleSetToMaximumPoints}
setToMinimumPoints={handleSetToMinimumPoints}
remove={handleRemove}
/>
)
}
@@ -3,22 +3,44 @@ import { geodeRitualsImprovementCost } from "../../../../../shared/domain/rated/
import { DisplayedActiveGeodeRitual } from "../../../../../shared/domain/rated/spellActive.ts"
import { SpellsSortOrder } from "../../../../../shared/domain/sortOrders.ts"
import { useTranslate } from "../../../../../shared/hooks/translate.ts"
import { useActiveActivatableActions } from "../../../../hooks/ratedActions.ts"
import {
decrementGeodeRitual,
incrementGeodeRitual,
removeGeodeRitual,
setGeodeRitual,
} from "../../../../slices/magicalActions/geodeRitualsSlice.ts"
import { ActiveMagicalActionsListItem } from "./ActiveMagicalActionsListItem.tsx"
type Props = {
insertTopMargin?: boolean
geodeRitual: DisplayedActiveGeodeRitual
sortOrder: SpellsSortOrder
addPoint: (id: number) => void
removePoint: (id: number) => void
remove: (id: number) => void
}
const ActiveGeodeRitualsListItem: FC<Props> = props => {
const { insertTopMargin, geodeRitual, sortOrder, addPoint, removePoint, remove } = props
const { insertTopMargin, geodeRitual, sortOrder } = props
const translate = useTranslate()
const {
handleAddPoint,
handleRemovePoint,
handleSetToMaximumPoints,
handleSetToMinimumPoints,
handleRemove,
} = useActiveActivatableActions(
geodeRitual.static.id,
geodeRitual.dynamic.value,
geodeRitual.maximum,
geodeRitual.minimum ?? 0,
geodeRitualsImprovementCost,
incrementGeodeRitual,
decrementGeodeRitual,
setGeodeRitual,
removeGeodeRitual,
)
return (
<ActiveMagicalActionsListItem
kind="GeodeRitual"
@@ -27,9 +49,11 @@ const ActiveGeodeRitualsListItem: FC<Props> = props => {
sortOrder={sortOrder}
groupName={translate("Geode Rituals")}
improvementCost={geodeRitualsImprovementCost}
addPoint={addPoint}
removePoint={removePoint}
remove={remove}
addPoint={handleAddPoint}
removePoint={handleRemovePoint}
setToMaximumPoints={handleSetToMaximumPoints}
setToMinimumPoints={handleSetToMinimumPoints}
remove={handleRemove}
/>
)
}
@@ -3,22 +3,44 @@ import { fromRaw } from "../../../../../shared/domain/adventurePoints/improvemen
import { DisplayedActiveJesterTrick } from "../../../../../shared/domain/rated/spellActive.ts"
import { SpellsSortOrder } from "../../../../../shared/domain/sortOrders.ts"
import { useTranslate } from "../../../../../shared/hooks/translate.ts"
import { useActiveActivatableActions } from "../../../../hooks/ratedActions.ts"
import {
decrementJesterTrick,
incrementJesterTrick,
removeJesterTrick,
setJesterTrick,
} from "../../../../slices/magicalActions/jesterTricksSlice.ts"
import { ActiveMagicalActionsListItem } from "./ActiveMagicalActionsListItem.tsx"
type Props = {
insertTopMargin?: boolean
jesterTrick: DisplayedActiveJesterTrick
sortOrder: SpellsSortOrder
addPoint: (id: number) => void
removePoint: (id: number) => void
remove: (id: number) => void
}
const ActiveJesterTricksListItem: FC<Props> = props => {
const { insertTopMargin, jesterTrick, sortOrder, addPoint, removePoint, remove } = props
const { insertTopMargin, jesterTrick, sortOrder } = props
const translate = useTranslate()
const {
handleAddPoint,
handleRemovePoint,
handleSetToMaximumPoints,
handleSetToMinimumPoints,
handleRemove,
} = useActiveActivatableActions(
jesterTrick.static.id,
jesterTrick.dynamic.value,
jesterTrick.maximum,
jesterTrick.minimum ?? 0,
fromRaw(jesterTrick.static.improvement_cost),
incrementJesterTrick,
decrementJesterTrick,
setJesterTrick,
removeJesterTrick,
)
return (
<ActiveMagicalActionsListItem
kind="JesterTrick"
@@ -28,9 +50,11 @@ const ActiveJesterTricksListItem: FC<Props> = props => {
groupName={translate("Jester Tricks")}
checkPenalty={jesterTrick.static.check_penalty}
improvementCost={fromRaw(jesterTrick.static.improvement_cost)}
addPoint={addPoint}
removePoint={removePoint}
remove={remove}
addPoint={handleAddPoint}
removePoint={handleRemovePoint}
setToMaximumPoints={handleSetToMaximumPoints}
setToMinimumPoints={handleSetToMinimumPoints}
remove={handleRemove}
/>
)
}
@@ -42,10 +42,12 @@ type Props<T extends DisplayedActiveMagicalAction> = {
sortOrder: SpellsSortOrder
groupName: string
checkPenalty?: SkillCheckPenalty
improvementCost?: ImprovementCost
addPoint: (id: number) => void
removePoint: (id: number) => void
remove: (id: number) => void
improvementCost: ImprovementCost
addPoint: () => void
removePoint: () => void
setToMaximumPoints: () => void
setToMinimumPoints: () => void
remove: () => void
}
/**
@@ -64,14 +66,16 @@ export const ActiveMagicalActionsListItem = <T extends DisplayedActiveMagicalAct
improvementCost,
addPoint,
removePoint,
setToMaximumPoints,
setToMinimumPoints,
remove,
} = props
const {
dynamic: { value },
static: { id, check, property, translations },
isDecreasable,
dynamic: { value },
isIncreasable,
isDecreasable,
} = magicalAction
const translateMap = useTranslateMap()
@@ -107,19 +111,19 @@ export const ActiveMagicalActionsListItem = <T extends DisplayedActiveMagicalAct
text={sortOrder === SpellsSortOrder.Group ? `${propertyName} / ${groupName}` : propertyName}
/>
<ListItemValues>
<SkillRating sr={value} addPoint={addPoint} />
<SkillRating sr={value} />
<SkillCheck check={check} checkPenalty={checkPenalty} />
<SkillFill />
<SkillImprovementCost ic={improvementCost} />
</ListItemValues>
<SkillButtons
addDisabled={!isIncreasable}
ic={improvementCost}
id={id}
removeDisabled={!isDecreasable}
sr={value}
addPoint={addPoint}
setToMax={setToMaximumPoints}
removePoint={canRemove ? (value === 0 ? remove : removePoint) : undefined}
setToMin={canRemove && value > 0 ? setToMinimumPoints : undefined}
decrementIsRemove={value === 0}
selectForInfo={handleSelectForInfo}
/>
</ListItem>
@@ -3,22 +3,44 @@ import { fromRaw } from "../../../../../shared/domain/adventurePoints/improvemen
import { DisplayedActiveMagicalDance } from "../../../../../shared/domain/rated/spellActive.ts"
import { SpellsSortOrder } from "../../../../../shared/domain/sortOrders.ts"
import { useTranslate } from "../../../../../shared/hooks/translate.ts"
import { useActiveActivatableActions } from "../../../../hooks/ratedActions.ts"
import {
decrementMagicalDance,
incrementMagicalDance,
removeMagicalDance,
setMagicalDance,
} from "../../../../slices/magicalActions/magicalDancesSlice.ts"
import { ActiveMagicalActionsListItem } from "./ActiveMagicalActionsListItem.tsx"
type Props = {
insertTopMargin?: boolean
magicalDance: DisplayedActiveMagicalDance
sortOrder: SpellsSortOrder
addPoint: (id: number) => void
removePoint: (id: number) => void
remove: (id: number) => void
}
const ActiveMagicalDancesListItem: FC<Props> = props => {
const { insertTopMargin, magicalDance, sortOrder, addPoint, removePoint, remove } = props
const { insertTopMargin, magicalDance, sortOrder } = props
const translate = useTranslate()
const {
handleAddPoint,
handleRemovePoint,
handleSetToMaximumPoints,
handleSetToMinimumPoints,
handleRemove,
} = useActiveActivatableActions(
magicalDance.static.id,
magicalDance.dynamic.value,
magicalDance.maximum,
magicalDance.minimum ?? 0,
fromRaw(magicalDance.static.improvement_cost),
incrementMagicalDance,
decrementMagicalDance,
setMagicalDance,
removeMagicalDance,
)
return (
<ActiveMagicalActionsListItem
kind="MagicalDance"
@@ -27,9 +49,11 @@ const ActiveMagicalDancesListItem: FC<Props> = props => {
sortOrder={sortOrder}
groupName={translate("Magical Dances")}
improvementCost={fromRaw(magicalDance.static.improvement_cost)}
addPoint={addPoint}
removePoint={removePoint}
remove={remove}
addPoint={handleAddPoint}
removePoint={handleRemovePoint}
setToMaximumPoints={handleSetToMaximumPoints}
setToMinimumPoints={handleSetToMinimumPoints}
remove={handleRemove}
/>
)
}
@@ -3,22 +3,44 @@ import { fromRaw } from "../../../../../shared/domain/adventurePoints/improvemen
import { DisplayedActiveMagicalMelody } from "../../../../../shared/domain/rated/spellActive.ts"
import { SpellsSortOrder } from "../../../../../shared/domain/sortOrders.ts"
import { useTranslate } from "../../../../../shared/hooks/translate.ts"
import { useActiveActivatableActions } from "../../../../hooks/ratedActions.ts"
import {
decrementMagicalMelody,
incrementMagicalMelody,
removeMagicalMelody,
setMagicalMelody,
} from "../../../../slices/magicalActions/magicalMelodiesSlice.ts"
import { ActiveMagicalActionsListItem } from "./ActiveMagicalActionsListItem.tsx"
type Props = {
insertTopMargin?: boolean
magicalMelody: DisplayedActiveMagicalMelody
sortOrder: SpellsSortOrder
addPoint: (id: number) => void
removePoint: (id: number) => void
remove: (id: number) => void
}
const ActiveMagicalMelodiesListItem: FC<Props> = props => {
const { insertTopMargin, magicalMelody, sortOrder, addPoint, removePoint, remove } = props
const { insertTopMargin, magicalMelody, sortOrder } = props
const translate = useTranslate()
const {
handleAddPoint,
handleRemovePoint,
handleSetToMaximumPoints,
handleSetToMinimumPoints,
handleRemove,
} = useActiveActivatableActions(
magicalMelody.static.id,
magicalMelody.dynamic.value,
magicalMelody.maximum,
magicalMelody.minimum ?? 0,
fromRaw(magicalMelody.static.improvement_cost),
incrementMagicalMelody,
decrementMagicalMelody,
setMagicalMelody,
removeMagicalMelody,
)
return (
<ActiveMagicalActionsListItem
kind="MagicalMelody"
@@ -28,9 +50,11 @@ const ActiveMagicalMelodiesListItem: FC<Props> = props => {
groupName={translate("Magical Melodies")}
checkPenalty={magicalMelody.static.check_penalty}
improvementCost={fromRaw(magicalMelody.static.improvement_cost)}
addPoint={addPoint}
removePoint={removePoint}
remove={remove}
addPoint={handleAddPoint}
removePoint={handleRemovePoint}
setToMaximumPoints={handleSetToMaximumPoints}
setToMinimumPoints={handleSetToMinimumPoints}
remove={handleRemove}
/>
)
}
@@ -9,6 +9,7 @@ import { DisplayedActiveRitual } from "../../../../../shared/domain/rated/spellA
import { SpellsSortOrder } from "../../../../../shared/domain/sortOrders.ts"
import { useTranslate } from "../../../../../shared/hooks/translate.ts"
import { useTranslateMap } from "../../../../../shared/hooks/translateMap.ts"
import { useActiveActivatableActions } from "../../../../hooks/ratedActions.ts"
import { useAppDispatch, useAppSelector } from "../../../../hooks/redux.ts"
import { SelectGetById } from "../../../../selectors/basicCapabilitySelectors.ts"
import { selectCanRemove } from "../../../../selectors/characterSelectors.ts"
@@ -16,6 +17,12 @@ import {
changeInlineLibraryEntry,
selectInlineLibraryEntryId,
} from "../../../../slices/inlineWikiSlice.ts"
import {
decrementRitual,
incrementRitual,
removeRitual,
setRitual,
} from "../../../../slices/ritualsSlice.ts"
import { SkillButtons } from "../skills/SkillButtons.tsx"
import { SkillCheck } from "../skills/SkillCheck.tsx"
import { SkillFill } from "../skills/SkillFill.tsx"
@@ -26,9 +33,6 @@ type Props = {
insertTopMargin?: boolean
ritual: DisplayedActiveRitual
sortOrder: SpellsSortOrder
addPoint: (id: number) => void
removePoint: (id: number) => void
remove: (id: number) => void
}
const ActiveRitualsListItem: FC<Props> = props => {
@@ -37,14 +41,13 @@ const ActiveRitualsListItem: FC<Props> = props => {
ritual: {
dynamic: { value },
static: { id, check, check_penalty, property, improvement_cost, translations },
maximum,
minimum,
isDecreasable,
isIncreasable,
isUnfamiliar,
},
sortOrder,
addPoint,
removePoint,
remove,
} = props
const translate = useTranslate()
@@ -56,6 +59,24 @@ const ActiveRitualsListItem: FC<Props> = props => {
const { name = "" } = translateMap(translations) ?? {}
const {
handleAddPoint,
handleRemovePoint,
handleSetToMaximumPoints,
handleSetToMinimumPoints,
handleRemove,
} = useActiveActivatableActions(
id,
value,
maximum,
minimum ?? 0,
fromRaw(improvement_cost),
incrementRitual,
decrementRitual,
setRitual,
removeRitual,
)
const handleSelectForInfo = useCallback(
() => dispatch(changeInlineLibraryEntry({ tag: "Ritual", ritual: id })),
[dispatch, id],
@@ -82,19 +103,19 @@ const ActiveRitualsListItem: FC<Props> = props => {
}
/>
<ListItemValues>
<SkillRating sr={value} addPoint={addPoint} />
<SkillRating sr={value} />
<SkillCheck check={check} checkPenalty={check_penalty} />
<SkillFill />
<SkillImprovementCost ic={fromRaw(improvement_cost)} />
</ListItemValues>
<SkillButtons
addDisabled={!isIncreasable}
ic={fromRaw(improvement_cost)}
id={id}
removeDisabled={!isDecreasable}
sr={value}
addPoint={addPoint}
removePoint={canRemove ? (value === 0 ? remove : removePoint) : undefined}
addPoint={handleAddPoint}
setToMax={handleSetToMaximumPoints}
removePoint={canRemove ? (value === 0 ? handleRemovePoint : handleRemove) : undefined}
setToMin={canRemove && value > 0 ? handleSetToMinimumPoints : undefined}
decrementIsRemove={value === 0}
selectForInfo={handleSelectForInfo}
/>
</ListItem>
@@ -9,6 +9,7 @@ import { DisplayedActiveSpell } from "../../../../../shared/domain/rated/spellAc
import { SpellsSortOrder } from "../../../../../shared/domain/sortOrders.ts"
import { useTranslate } from "../../../../../shared/hooks/translate.ts"
import { useTranslateMap } from "../../../../../shared/hooks/translateMap.ts"
import { useActiveActivatableActions } from "../../../../hooks/ratedActions.ts"
import { useAppDispatch, useAppSelector } from "../../../../hooks/redux.ts"
import { SelectGetById } from "../../../../selectors/basicCapabilitySelectors.ts"
import { selectCanRemove } from "../../../../selectors/characterSelectors.ts"
@@ -16,6 +17,12 @@ import {
changeInlineLibraryEntry,
selectInlineLibraryEntryId,
} from "../../../../slices/inlineWikiSlice.ts"
import {
decrementSpell,
incrementSpell,
removeSpell,
setSpell,
} from "../../../../slices/spellsSlice.ts"
import { SkillButtons } from "../skills/SkillButtons.tsx"
import { SkillCheck } from "../skills/SkillCheck.tsx"
import { SkillFill } from "../skills/SkillFill.tsx"
@@ -26,9 +33,6 @@ type Props = {
insertTopMargin?: boolean
spell: DisplayedActiveSpell
sortOrder: SpellsSortOrder
addPoint: (id: number) => void
removePoint: (id: number) => void
remove: (id: number) => void
}
const ActiveSpellsListItem: FC<Props> = props => {
@@ -37,14 +41,13 @@ const ActiveSpellsListItem: FC<Props> = props => {
spell: {
dynamic: { value },
static: { id, check, check_penalty, property, improvement_cost, translations },
maximum,
minimum,
isDecreasable,
isIncreasable,
isUnfamiliar,
},
sortOrder,
addPoint,
removePoint,
remove,
} = props
const translate = useTranslate()
@@ -54,7 +57,25 @@ const ActiveSpellsListItem: FC<Props> = props => {
const canRemove = useAppSelector(selectCanRemove)
const getProperty = useAppSelector(SelectGetById.Static.Property)
const { name = "" } = translateMap(translations) ?? {}
const { name = "???" } = translateMap(translations) ?? {}
const {
handleAddPoint,
handleRemovePoint,
handleSetToMaximumPoints,
handleSetToMinimumPoints,
handleRemove,
} = useActiveActivatableActions(
id,
value,
maximum,
minimum ?? 0,
fromRaw(improvement_cost),
incrementSpell,
decrementSpell,
setSpell,
removeSpell,
)
const handleSelectForInfo = useCallback(
() => dispatch(changeInlineLibraryEntry({ tag: "Spell", spell: id })),
@@ -82,19 +103,19 @@ const ActiveSpellsListItem: FC<Props> = props => {
}
/>
<ListItemValues>
<SkillRating sr={value} addPoint={addPoint} />
<SkillRating sr={value} />
<SkillCheck check={check} checkPenalty={check_penalty} />
<SkillFill />
<SkillImprovementCost ic={fromRaw(improvement_cost)} />
</ListItemValues>
<SkillButtons
addDisabled={!isIncreasable}
ic={fromRaw(improvement_cost)}
id={id}
removeDisabled={!isDecreasable}
sr={value}
addPoint={addPoint}
removePoint={canRemove ? (value === 0 ? remove : removePoint) : undefined}
addPoint={handleAddPoint}
setToMax={handleSetToMaximumPoints}
removePoint={canRemove ? (value === 0 ? handleRemovePoint : handleRemove) : undefined}
setToMin={canRemove && value > 0 ? handleSetToMinimumPoints : undefined}
decrementIsRemove={value === 0}
selectForInfo={handleSelectForInfo}
/>
</ListItem>
@@ -3,22 +3,44 @@ import { fromRaw } from "../../../../../shared/domain/adventurePoints/improvemen
import { DisplayedActiveZibiljaRitual } from "../../../../../shared/domain/rated/spellActive.ts"
import { SpellsSortOrder } from "../../../../../shared/domain/sortOrders.ts"
import { useTranslate } from "../../../../../shared/hooks/translate.ts"
import { useActiveActivatableActions } from "../../../../hooks/ratedActions.ts"
import {
decrementZibiljaRitual,
incrementZibiljaRitual,
removeZibiljaRitual,
setZibiljaRitual,
} from "../../../../slices/magicalActions/zibiljaRitualsSlice.ts"
import { ActiveMagicalActionsListItem } from "./ActiveMagicalActionsListItem.tsx"
type Props = {
insertTopMargin?: boolean
zibiljaRitual: DisplayedActiveZibiljaRitual
sortOrder: SpellsSortOrder
addPoint: (id: number) => void
removePoint: (id: number) => void
remove: (id: number) => void
}
const ActiveZibiljaRitualsListItem: FC<Props> = props => {
const { insertTopMargin, zibiljaRitual, sortOrder, addPoint, removePoint, remove } = props
const { insertTopMargin, zibiljaRitual, sortOrder } = props
const translate = useTranslate()
const {
handleAddPoint,
handleRemovePoint,
handleSetToMaximumPoints,
handleSetToMinimumPoints,
handleRemove,
} = useActiveActivatableActions(
zibiljaRitual.static.id,
zibiljaRitual.dynamic.value,
zibiljaRitual.maximum,
zibiljaRitual.minimum ?? 0,
fromRaw(zibiljaRitual.static.improvement_cost),
incrementZibiljaRitual,
decrementZibiljaRitual,
setZibiljaRitual,
removeZibiljaRitual,
)
return (
<ActiveMagicalActionsListItem
kind="ZibiljaRitual"
@@ -28,9 +50,11 @@ const ActiveZibiljaRitualsListItem: FC<Props> = props => {
groupName={translate("Zibilja Rituals")}
checkPenalty={zibiljaRitual.static.check_penalty}
improvementCost={fromRaw(zibiljaRitual.static.improvement_cost)}
addPoint={addPoint}
removePoint={removePoint}
remove={remove}
addPoint={handleAddPoint}
removePoint={handleRemovePoint}
setToMaximumPoints={handleSetToMaximumPoints}
setToMinimumPoints={handleSetToMinimumPoints}
remove={handleRemove}
/>
)
}
@@ -1,26 +1,28 @@
import { FC, memo } from "react"
import {
ImprovementCost,
fromRaw,
} from "../../../../../shared/domain/adventurePoints/improvementCost.ts"
import { DisplayedInactiveAnimistPower } from "../../../../../shared/domain/rated/spellInactive.ts"
import { SpellsSortOrder } from "../../../../../shared/domain/sortOrders.ts"
import { useTranslate } from "../../../../../shared/hooks/translate.ts"
import { assertExhaustive } from "../../../../../shared/utils/typeSafety.ts"
import { useInactiveActivatableActions } from "../../../../hooks/ratedActions.ts"
import { addAnimistPower } from "../../../../slices/magicalActions/animistPowersSlice.ts"
import { InactiveMagicalActionsListItem } from "./InactiveMagicalActionsListItem.tsx"
type Props = {
insertTopMargin?: boolean
animistPower: DisplayedInactiveAnimistPower
sortOrder: SpellsSortOrder
add: (id: number) => void
}
const InactiveAnimistPowersListItem: FC<Props> = props => {
const { insertTopMargin, animistPower, sortOrder, add } = props
const { insertTopMargin, animistPower, sortOrder } = props
const translate = useTranslate()
const { handleAdd } = useInactiveActivatableActions(
animistPower.static.id,
animistPower.improvementCost,
addAnimistPower,
)
return (
<InactiveMagicalActionsListItem
kind="AnimistPower"
@@ -28,18 +30,8 @@ const InactiveAnimistPowersListItem: FC<Props> = props => {
magicalAction={animistPower}
sortOrder={sortOrder}
groupName={translate("Animist Powers")}
improvementCost={(() => {
switch (animistPower.static.improvement_cost.tag) {
case "Fixed":
return fromRaw(animistPower.static.improvement_cost.fixed)
case "ByPrimaryPatron":
// TODO: Replace with derived improvement cost
return ImprovementCost.D
default:
return assertExhaustive(animistPower.static.improvement_cost)
}
})()}
add={add}
improvementCost={animistPower.improvementCost}
add={handleAdd}
/>
)
}
@@ -43,6 +43,10 @@ const InactiveCantripsListItem: FC<Props> = props => {
const { name = "" } = translateMap(translations) ?? {}
const handleAdd = useCallback(() => {
add(id)
}, [add, id])
const handleSelectForInfo = useCallback(
() => dispatch(changeInlineLibraryEntry({ tag: "Cantrip", cantrip: id })),
[dispatch, id],
@@ -72,7 +76,7 @@ const InactiveCantripsListItem: FC<Props> = props => {
<ListItemValues>
<SkillFill />
</ListItemValues>
<SkillButtons id={id} addPoint={add} selectForInfo={handleSelectForInfo} />
<SkillButtons addPoint={handleAdd} selectForInfo={handleSelectForInfo} />
</ListItem>
)
}
@@ -3,20 +3,27 @@ import { cursesImprovementCost } from "../../../../../shared/domain/rated/magica
import { DisplayedInactiveCurse } from "../../../../../shared/domain/rated/spellInactive.ts"
import { SpellsSortOrder } from "../../../../../shared/domain/sortOrders.ts"
import { useTranslate } from "../../../../../shared/hooks/translate.ts"
import { useInactiveActivatableActions } from "../../../../hooks/ratedActions.ts"
import { addCurse } from "../../../../slices/magicalActions/cursesSlice.ts"
import { InactiveMagicalActionsListItem } from "./InactiveMagicalActionsListItem.tsx"
type Props = {
insertTopMargin?: boolean
curse: DisplayedInactiveCurse
sortOrder: SpellsSortOrder
add: (id: number) => void
}
const InactiveCursesListItem: FC<Props> = props => {
const { insertTopMargin, curse, sortOrder, add } = props
const { insertTopMargin, curse, sortOrder } = props
const translate = useTranslate()
const { handleAdd } = useInactiveActivatableActions(
curse.static.id,
cursesImprovementCost,
addCurse,
)
return (
<InactiveMagicalActionsListItem
kind="Curse"
@@ -26,7 +33,7 @@ const InactiveCursesListItem: FC<Props> = props => {
groupName={translate("Curses")}
checkPenalty={curse.static.check_penalty}
improvementCost={cursesImprovementCost}
add={add}
add={handleAdd}
/>
)
}
@@ -3,20 +3,27 @@ import { dominationRitualsImprovementCost } from "../../../../../shared/domain/r
import { DisplayedInactiveDominationRitual } from "../../../../../shared/domain/rated/spellInactive.ts"
import { SpellsSortOrder } from "../../../../../shared/domain/sortOrders.ts"
import { useTranslate } from "../../../../../shared/hooks/translate.ts"
import { useInactiveActivatableActions } from "../../../../hooks/ratedActions.ts"
import { addDominationRitual } from "../../../../slices/magicalActions/dominationRitualsSlice.ts"
import { InactiveMagicalActionsListItem } from "./InactiveMagicalActionsListItem.tsx"
type Props = {
insertTopMargin?: boolean
dominationRitual: DisplayedInactiveDominationRitual
sortOrder: SpellsSortOrder
add: (id: number) => void
}
const InactiveDominationRitualsListItem: FC<Props> = props => {
const { insertTopMargin, dominationRitual, sortOrder, add } = props
const { insertTopMargin, dominationRitual, sortOrder } = props
const translate = useTranslate()
const { handleAdd } = useInactiveActivatableActions(
dominationRitual.static.id,
dominationRitualsImprovementCost,
addDominationRitual,
)
return (
<InactiveMagicalActionsListItem
kind="DominationRitual"
@@ -26,7 +33,7 @@ const InactiveDominationRitualsListItem: FC<Props> = props => {
groupName={translate("Domination Rituals")}
checkPenalty={dominationRitual.static.check_penalty}
improvementCost={dominationRitualsImprovementCost}
add={add}
add={handleAdd}
/>
)
}
@@ -3,20 +3,27 @@ import { fromRaw } from "../../../../../shared/domain/adventurePoints/improvemen
import { DisplayedInactiveElvenMagicalSong } from "../../../../../shared/domain/rated/spellInactive.ts"
import { SpellsSortOrder } from "../../../../../shared/domain/sortOrders.ts"
import { useTranslate } from "../../../../../shared/hooks/translate.ts"
import { useInactiveActivatableActions } from "../../../../hooks/ratedActions.ts"
import { addElvenMagicalSong } from "../../../../slices/magicalActions/elvenMagicalSongsSlice.ts"
import { InactiveMagicalActionsListItem } from "./InactiveMagicalActionsListItem.tsx"
type Props = {
insertTopMargin?: boolean
elvenMagicalSong: DisplayedInactiveElvenMagicalSong
sortOrder: SpellsSortOrder
add: (id: number) => void
}
const InactiveElvenMagicalSongsListItem: FC<Props> = props => {
const { insertTopMargin, elvenMagicalSong, sortOrder, add } = props
const { insertTopMargin, elvenMagicalSong, sortOrder } = props
const translate = useTranslate()
const { handleAdd } = useInactiveActivatableActions(
elvenMagicalSong.static.id,
fromRaw(elvenMagicalSong.static.improvement_cost),
addElvenMagicalSong,
)
return (
<InactiveMagicalActionsListItem
kind="ElvenMagicalSong"
@@ -26,7 +33,7 @@ const InactiveElvenMagicalSongsListItem: FC<Props> = props => {
groupName={translate("Elven Magical Songs")}
checkPenalty={elvenMagicalSong.static.check_penalty}
improvementCost={fromRaw(elvenMagicalSong.static.improvement_cost)}
add={add}
add={handleAdd}
/>
)
}
@@ -3,20 +3,27 @@ import { geodeRitualsImprovementCost } from "../../../../../shared/domain/rated/
import { DisplayedInactiveGeodeRitual } from "../../../../../shared/domain/rated/spellInactive.ts"
import { SpellsSortOrder } from "../../../../../shared/domain/sortOrders.ts"
import { useTranslate } from "../../../../../shared/hooks/translate.ts"
import { useInactiveActivatableActions } from "../../../../hooks/ratedActions.ts"
import { addGeodeRitual } from "../../../../slices/magicalActions/geodeRitualsSlice.ts"
import { InactiveMagicalActionsListItem } from "./InactiveMagicalActionsListItem.tsx"
type Props = {
insertTopMargin?: boolean
geodeRitual: DisplayedInactiveGeodeRitual
sortOrder: SpellsSortOrder
add: (id: number) => void
}
const InactiveGeodeRitualsListItem: FC<Props> = props => {
const { insertTopMargin, geodeRitual, sortOrder, add } = props
const { insertTopMargin, geodeRitual, sortOrder } = props
const translate = useTranslate()
const { handleAdd } = useInactiveActivatableActions(
geodeRitual.static.id,
geodeRitualsImprovementCost,
addGeodeRitual,
)
return (
<InactiveMagicalActionsListItem
kind="GeodeRitual"
@@ -25,7 +32,7 @@ const InactiveGeodeRitualsListItem: FC<Props> = props => {
sortOrder={sortOrder}
groupName={translate("Geode Rituals")}
improvementCost={geodeRitualsImprovementCost}
add={add}
add={handleAdd}
/>
)
}
@@ -3,20 +3,27 @@ import { fromRaw } from "../../../../../shared/domain/adventurePoints/improvemen
import { DisplayedInactiveJesterTrick } from "../../../../../shared/domain/rated/spellInactive.ts"
import { SpellsSortOrder } from "../../../../../shared/domain/sortOrders.ts"
import { useTranslate } from "../../../../../shared/hooks/translate.ts"
import { useInactiveActivatableActions } from "../../../../hooks/ratedActions.ts"
import { addJesterTrick } from "../../../../slices/magicalActions/jesterTricksSlice.ts"
import { InactiveMagicalActionsListItem } from "./InactiveMagicalActionsListItem.tsx"
type Props = {
insertTopMargin?: boolean
jesterTrick: DisplayedInactiveJesterTrick
sortOrder: SpellsSortOrder
add: (id: number) => void
}
const InactiveJesterTricksListItem: FC<Props> = props => {
const { insertTopMargin, jesterTrick, sortOrder, add } = props
const { insertTopMargin, jesterTrick, sortOrder } = props
const translate = useTranslate()
const { handleAdd } = useInactiveActivatableActions(
jesterTrick.static.id,
fromRaw(jesterTrick.static.improvement_cost),
addJesterTrick,
)
return (
<InactiveMagicalActionsListItem
kind="JesterTrick"
@@ -26,7 +33,7 @@ const InactiveJesterTricksListItem: FC<Props> = props => {
groupName={translate("Jester Tricks")}
checkPenalty={jesterTrick.static.check_penalty}
improvementCost={fromRaw(jesterTrick.static.improvement_cost)}
add={add}
add={handleAdd}
/>
)
}
@@ -41,7 +41,7 @@ type Props<T extends DisplayedInactiveMagicalAction> = {
groupName: string
checkPenalty?: SkillCheckPenalty
improvementCost?: ImprovementCost
add: (id: number) => void
add: () => void
}
/**
@@ -101,13 +101,7 @@ export const InactiveMagicalActionsListItem = <T extends DisplayedInactiveMagica
<SkillFill />
<SkillImprovementCost ic={improvementCost} />
</ListItemValues>
<SkillButtons
addDisabled={!isAvailable}
ic={improvementCost}
id={id}
addPoint={add}
selectForInfo={handleSelectForInfo}
/>
<SkillButtons addDisabled={!isAvailable} addPoint={add} selectForInfo={handleSelectForInfo} />
</ListItem>
)
}
@@ -3,20 +3,27 @@ import { fromRaw } from "../../../../../shared/domain/adventurePoints/improvemen
import { DisplayedInactiveMagicalDance } from "../../../../../shared/domain/rated/spellInactive.ts"
import { SpellsSortOrder } from "../../../../../shared/domain/sortOrders.ts"
import { useTranslate } from "../../../../../shared/hooks/translate.ts"
import { useInactiveActivatableActions } from "../../../../hooks/ratedActions.ts"
import { addMagicalDance } from "../../../../slices/magicalActions/magicalDancesSlice.ts"
import { InactiveMagicalActionsListItem } from "./InactiveMagicalActionsListItem.tsx"
type Props = {
insertTopMargin?: boolean
magicalDance: DisplayedInactiveMagicalDance
sortOrder: SpellsSortOrder
add: (id: number) => void
}
const InactiveMagicalDancesListItem: FC<Props> = props => {
const { insertTopMargin, magicalDance, sortOrder, add } = props
const { insertTopMargin, magicalDance, sortOrder } = props
const translate = useTranslate()
const { handleAdd } = useInactiveActivatableActions(
magicalDance.static.id,
fromRaw(magicalDance.static.improvement_cost),
addMagicalDance,
)
return (
<InactiveMagicalActionsListItem
kind="MagicalDance"
@@ -25,7 +32,7 @@ const InactiveMagicalDancesListItem: FC<Props> = props => {
sortOrder={sortOrder}
groupName={translate("Magical Dances")}
improvementCost={fromRaw(magicalDance.static.improvement_cost)}
add={add}
add={handleAdd}
/>
)
}
@@ -3,20 +3,27 @@ import { fromRaw } from "../../../../../shared/domain/adventurePoints/improvemen
import { DisplayedInactiveMagicalMelody } from "../../../../../shared/domain/rated/spellInactive.ts"
import { SpellsSortOrder } from "../../../../../shared/domain/sortOrders.ts"
import { useTranslate } from "../../../../../shared/hooks/translate.ts"
import { useInactiveActivatableActions } from "../../../../hooks/ratedActions.ts"
import { addMagicalMelody } from "../../../../slices/magicalActions/magicalMelodiesSlice.ts"
import { InactiveMagicalActionsListItem } from "./InactiveMagicalActionsListItem.tsx"
type Props = {
insertTopMargin?: boolean
magicalMelody: DisplayedInactiveMagicalMelody
sortOrder: SpellsSortOrder
add: (id: number) => void
}
const InactiveMagicalMelodiesListItem: FC<Props> = props => {
const { insertTopMargin, magicalMelody, sortOrder, add } = props
const { insertTopMargin, magicalMelody, sortOrder } = props
const translate = useTranslate()
const { handleAdd } = useInactiveActivatableActions(
magicalMelody.static.id,
fromRaw(magicalMelody.static.improvement_cost),
addMagicalMelody,
)
return (
<InactiveMagicalActionsListItem
kind="MagicalMelody"
@@ -26,7 +33,7 @@ const InactiveMagicalMelodiesListItem: FC<Props> = props => {
groupName={translate("Magical Melodies")}
checkPenalty={magicalMelody.static.check_penalty}
improvementCost={fromRaw(magicalMelody.static.improvement_cost)}
add={add}
add={handleAdd}
/>
)
}
@@ -9,12 +9,14 @@ import { DisplayedInactiveRitual } from "../../../../../shared/domain/rated/spel
import { SpellsSortOrder } from "../../../../../shared/domain/sortOrders.ts"
import { useTranslate } from "../../../../../shared/hooks/translate.ts"
import { useTranslateMap } from "../../../../../shared/hooks/translateMap.ts"
import { useInactiveActivatableActions } from "../../../../hooks/ratedActions.ts"
import { useAppDispatch, useAppSelector } from "../../../../hooks/redux.ts"
import { SelectGetById } from "../../../../selectors/basicCapabilitySelectors.ts"
import {
changeInlineLibraryEntry,
selectInlineLibraryEntryId,
} from "../../../../slices/inlineWikiSlice.ts"
import { addRitual } from "../../../../slices/ritualsSlice.ts"
import { SkillButtons } from "../skills/SkillButtons.tsx"
import { SkillCheck } from "../skills/SkillCheck.tsx"
import { SkillFill } from "../skills/SkillFill.tsx"
@@ -24,7 +26,6 @@ type Props = {
insertTopMargin?: boolean
ritual: DisplayedInactiveRitual
sortOrder: SpellsSortOrder
add: (id: number) => void
}
const InactiveRitualsListItem: FC<Props> = props => {
@@ -36,7 +37,6 @@ const InactiveRitualsListItem: FC<Props> = props => {
isUnfamiliar,
},
sortOrder,
add,
} = props
const translate = useTranslate()
@@ -47,6 +47,8 @@ const InactiveRitualsListItem: FC<Props> = props => {
const { name = "" } = translateMap(translations) ?? {}
const { handleAdd } = useInactiveActivatableActions(id, fromRaw(improvement_cost), addRitual)
const handleSelectForInfo = useCallback(
() => dispatch(changeInlineLibraryEntry({ tag: "Ritual", ritual: id })),
[dispatch, id],
@@ -62,6 +64,7 @@ const InactiveRitualsListItem: FC<Props> = props => {
insertTopMargin={insertTopMargin}
active={inlineLibraryEntryId?.tag === "Ritual" && inlineLibraryEntryId.ritual === id}
unrecommended={isUnfamiliar}
disabled={!isAvailable}
>
<ListItemName name={name} onClick={handleSelectForInfo} />
<ListItemSeparator />
@@ -79,9 +82,7 @@ const InactiveRitualsListItem: FC<Props> = props => {
</ListItemValues>
<SkillButtons
addDisabled={!isAvailable}
ic={fromRaw(improvement_cost)}
id={id}
addPoint={add}
addPoint={handleAdd}
selectForInfo={handleSelectForInfo}
/>
</ListItem>
@@ -9,12 +9,14 @@ import { DisplayedInactiveSpell } from "../../../../../shared/domain/rated/spell
import { SpellsSortOrder } from "../../../../../shared/domain/sortOrders.ts"
import { useTranslate } from "../../../../../shared/hooks/translate.ts"
import { useTranslateMap } from "../../../../../shared/hooks/translateMap.ts"
import { useInactiveActivatableActions } from "../../../../hooks/ratedActions.ts"
import { useAppDispatch, useAppSelector } from "../../../../hooks/redux.ts"
import { SelectGetById } from "../../../../selectors/basicCapabilitySelectors.ts"
import {
changeInlineLibraryEntry,
selectInlineLibraryEntryId,
} from "../../../../slices/inlineWikiSlice.ts"
import { addSpell } from "../../../../slices/spellsSlice.ts"
import { SkillButtons } from "../skills/SkillButtons.tsx"
import { SkillCheck } from "../skills/SkillCheck.tsx"
import { SkillFill } from "../skills/SkillFill.tsx"
@@ -24,7 +26,6 @@ type Props = {
insertTopMargin?: boolean
spell: DisplayedInactiveSpell
sortOrder: SpellsSortOrder
add: (id: number) => void
}
const InactiveSpellsListItem: FC<Props> = props => {
@@ -36,7 +37,6 @@ const InactiveSpellsListItem: FC<Props> = props => {
isUnfamiliar,
},
sortOrder,
add,
} = props
const translate = useTranslate()
@@ -47,6 +47,8 @@ const InactiveSpellsListItem: FC<Props> = props => {
const { name = "" } = translateMap(translations) ?? {}
const { handleAdd } = useInactiveActivatableActions(id, fromRaw(improvement_cost), addSpell)
const handleSelectForInfo = useCallback(
() => dispatch(changeInlineLibraryEntry({ tag: "Spell", spell: id })),
[dispatch, id],
@@ -62,6 +64,7 @@ const InactiveSpellsListItem: FC<Props> = props => {
insertTopMargin={insertTopMargin}
active={inlineLibraryEntryId?.tag === "Spell" && inlineLibraryEntryId.spell === id}
unrecommended={isUnfamiliar}
disabled={!isAvailable}
>
<ListItemName name={name} onClick={handleSelectForInfo} />
<ListItemSeparator />
@@ -79,9 +82,7 @@ const InactiveSpellsListItem: FC<Props> = props => {
</ListItemValues>
<SkillButtons
addDisabled={!isAvailable}
ic={fromRaw(improvement_cost)}
id={id}
addPoint={add}
addPoint={handleAdd}
selectForInfo={handleSelectForInfo}
/>
</ListItem>
@@ -3,20 +3,27 @@ import { fromRaw } from "../../../../../shared/domain/adventurePoints/improvemen
import { DisplayedInactiveZibiljaRitual } from "../../../../../shared/domain/rated/spellInactive.ts"
import { SpellsSortOrder } from "../../../../../shared/domain/sortOrders.ts"
import { useTranslate } from "../../../../../shared/hooks/translate.ts"
import { useInactiveActivatableActions } from "../../../../hooks/ratedActions.ts"
import { addZibiljaRitual } from "../../../../slices/magicalActions/zibiljaRitualsSlice.ts"
import { InactiveMagicalActionsListItem } from "./InactiveMagicalActionsListItem.tsx"
type Props = {
insertTopMargin?: boolean
zibiljaRitual: DisplayedInactiveZibiljaRitual
sortOrder: SpellsSortOrder
add: (id: number) => void
}
const InactiveZibiljaRitualsListItem: FC<Props> = props => {
const { insertTopMargin, zibiljaRitual, sortOrder, add } = props
const { insertTopMargin, zibiljaRitual, sortOrder } = props
const translate = useTranslate()
const { handleAdd } = useInactiveActivatableActions(
zibiljaRitual.static.id,
fromRaw(zibiljaRitual.static.improvement_cost),
addZibiljaRitual,
)
return (
<InactiveMagicalActionsListItem
kind="ZibiljaRitual"
@@ -26,7 +33,7 @@ const InactiveZibiljaRitualsListItem: FC<Props> = props => {
groupName={translate("Zibilja Rituals")}
checkPenalty={zibiljaRitual.static.check_penalty}
improvementCost={fromRaw(zibiljaRitual.static.improvement_cost)}
add={add}
add={handleAdd}
/>
)
}
@@ -30,73 +30,7 @@ import {
selectVisibleInactiveSpellworks,
} from "../../../../selectors/spellSelectors.ts"
import { addCantrip, removeCantrip } from "../../../../slices/cantripsSlice.ts"
import {
addAnimistPower,
decrementAnimistPower,
incrementAnimistPower,
removeAnimistPower,
} from "../../../../slices/magicalActions/animistPowersSlice.ts"
import {
addCurse,
decrementCurse,
incrementCurse,
removeCurse,
} from "../../../../slices/magicalActions/cursesSlice.ts"
import {
addDominationRitual,
decrementDominationRitual,
incrementDominationRitual,
removeDominationRitual,
} from "../../../../slices/magicalActions/dominationRitualsSlice.ts"
import {
addElvenMagicalSong,
decrementElvenMagicalSong,
incrementElvenMagicalSong,
removeElvenMagicalSong,
} from "../../../../slices/magicalActions/elvenMagicalSongsSlice.ts"
import {
addGeodeRitual,
decrementGeodeRitual,
incrementGeodeRitual,
removeGeodeRitual,
} from "../../../../slices/magicalActions/geodeRitualsSlice.ts"
import {
addJesterTrick,
decrementJesterTrick,
incrementJesterTrick,
removeJesterTrick,
} from "../../../../slices/magicalActions/jesterTricksSlice.ts"
import {
addMagicalDance,
decrementMagicalDance,
incrementMagicalDance,
removeMagicalDance,
} from "../../../../slices/magicalActions/magicalDancesSlice.ts"
import {
addMagicalMelody,
decrementMagicalMelody,
incrementMagicalMelody,
removeMagicalMelody,
} from "../../../../slices/magicalActions/magicalMelodiesSlice.ts"
import {
addZibiljaRitual,
decrementZibiljaRitual,
incrementZibiljaRitual,
removeZibiljaRitual,
} from "../../../../slices/magicalActions/zibiljaRitualsSlice.ts"
import {
addRitual,
decrementRitual,
incrementRitual,
removeRitual,
} from "../../../../slices/ritualsSlice.ts"
import { changeSpellsSortOrder, selectSpellsSortOrder } from "../../../../slices/settingsSlice.ts"
import {
addSpell,
decrementSpell,
incrementSpell,
removeSpell,
} from "../../../../slices/spellsSlice.ts"
import { ActiveAnimistPowersListItem } from "./ActiveAnimistPowersListItem.tsx"
import { ActiveCantripsListItem } from "./ActiveCantripsListItem.tsx"
import { ActiveCursesListItem } from "./ActiveCursesListItem.tsx"
@@ -191,163 +125,10 @@ export const Spells: FC = () => {
],
)
const handleAddCantrip = useCallback((id: number) => dispatch(addCantrip(id)), [dispatch])
const handleRemoveCantrip = useCallback((id: number) => dispatch(removeCantrip(id)), [dispatch])
const handleAddSpell = useCallback((id: number) => dispatch(addSpell(id)), [dispatch])
const handleRemoveSpell = useCallback((id: number) => dispatch(removeSpell(id)), [dispatch])
const handleAddSpellPoint = useCallback((id: number) => dispatch(incrementSpell(id)), [dispatch])
const handleRemoveSpellPoint = useCallback(
(id: number) => dispatch(decrementSpell(id)),
[dispatch],
)
const handleAddRitual = useCallback((id: number) => dispatch(addRitual(id)), [dispatch])
const handleRemoveRitual = useCallback((id: number) => dispatch(removeRitual(id)), [dispatch])
const handleAddRitualPoint = useCallback(
(id: number) => dispatch(incrementRitual(id)),
[dispatch],
)
const handleRemoveRitualPoint = useCallback(
(id: number) => dispatch(decrementRitual(id)),
[dispatch],
)
const handleAddCurse = useCallback((id: number) => dispatch(addCurse(id)), [dispatch])
const handleRemoveCurse = useCallback((id: number) => dispatch(removeCurse(id)), [dispatch])
const handleAddCursePoint = useCallback((id: number) => dispatch(incrementCurse(id)), [dispatch])
const handleRemoveCursePoint = useCallback(
(id: number) => dispatch(decrementCurse(id)),
[dispatch],
)
const handleAddElvenMagicalSong = useCallback(
(id: number) => dispatch(addElvenMagicalSong(id)),
[dispatch],
)
const handleRemoveElvenMagicalSong = useCallback(
(id: number) => dispatch(removeElvenMagicalSong(id)),
[dispatch],
)
const handleAddElvenMagicalSongPoint = useCallback(
(id: number) => dispatch(incrementElvenMagicalSong(id)),
[dispatch],
)
const handleRemoveElvenMagicalSongPoint = useCallback(
(id: number) => dispatch(decrementElvenMagicalSong(id)),
[dispatch],
)
const handleAddDominationRitual = useCallback(
(id: number) => dispatch(addDominationRitual(id)),
[dispatch],
)
const handleRemoveDominationRitual = useCallback(
(id: number) => dispatch(removeDominationRitual(id)),
[dispatch],
)
const handleAddDominationRitualPoint = useCallback(
(id: number) => dispatch(incrementDominationRitual(id)),
[dispatch],
)
const handleRemoveDominationRitualPoint = useCallback(
(id: number) => dispatch(decrementDominationRitual(id)),
[dispatch],
)
const handleAddMagicalDance = useCallback(
(id: number) => dispatch(addMagicalDance(id)),
[dispatch],
)
const handleRemoveMagicalDance = useCallback(
(id: number) => dispatch(removeMagicalDance(id)),
[dispatch],
)
const handleAddMagicalDancePoint = useCallback(
(id: number) => dispatch(incrementMagicalDance(id)),
[dispatch],
)
const handleRemoveMagicalDancePoint = useCallback(
(id: number) => dispatch(decrementMagicalDance(id)),
[dispatch],
)
const handleAddMagicalMelody = useCallback(
(id: number) => dispatch(addMagicalMelody(id)),
[dispatch],
)
const handleRemoveMagicalMelody = useCallback(
(id: number) => dispatch(removeMagicalMelody(id)),
[dispatch],
)
const handleAddMagicalMelodyPoint = useCallback(
(id: number) => dispatch(incrementMagicalMelody(id)),
[dispatch],
)
const handleRemoveMagicalMelodyPoint = useCallback(
(id: number) => dispatch(decrementMagicalMelody(id)),
[dispatch],
)
const handleAddJesterTrick = useCallback((id: number) => dispatch(addJesterTrick(id)), [dispatch])
const handleRemoveJesterTrick = useCallback(
(id: number) => dispatch(removeJesterTrick(id)),
[dispatch],
)
const handleAddJesterTrickPoint = useCallback(
(id: number) => dispatch(incrementJesterTrick(id)),
[dispatch],
)
const handleRemoveJesterTrickPoint = useCallback(
(id: number) => dispatch(decrementJesterTrick(id)),
[dispatch],
)
const handleAddAnimistPower = useCallback(
(id: number) => dispatch(addAnimistPower(id)),
[dispatch],
)
const handleRemoveAnimistPower = useCallback(
(id: number) => dispatch(removeAnimistPower(id)),
[dispatch],
)
const handleAddAnimistPowerPoint = useCallback(
(id: number) => dispatch(incrementAnimistPower(id)),
[dispatch],
)
const handleRemoveAnimistPowerPoint = useCallback(
(id: number) => dispatch(decrementAnimistPower(id)),
[dispatch],
)
const handleAddGeodeRitual = useCallback((id: number) => dispatch(addGeodeRitual(id)), [dispatch])
const handleRemoveGeodeRitual = useCallback(
(id: number) => dispatch(removeGeodeRitual(id)),
[dispatch],
)
const handleAddGeodeRitualPoint = useCallback(
(id: number) => dispatch(incrementGeodeRitual(id)),
[dispatch],
)
const handleRemoveGeodeRitualPoint = useCallback(
(id: number) => dispatch(decrementGeodeRitual(id)),
[dispatch],
)
const handleAddZibiljaRitual = useCallback(
(id: number) => dispatch(addZibiljaRitual(id)),
[dispatch],
)
const handleRemoveZibiljaRitual = useCallback(
(id: number) => dispatch(removeZibiljaRitual(id)),
[dispatch],
)
const handleAddZibiljaRitualPoint = useCallback(
(id: number) => dispatch(incrementZibiljaRitual(id)),
[dispatch],
)
const handleRemoveZibiljaRitualPoint = useCallback(
(id: number) => dispatch(decrementZibiljaRitual(id)),
// TODO: Check available AP
const handleAddCantrip = useCallback((id: number) => dispatch(addCantrip({ id })), [dispatch])
const handleRemoveCantrip = useCallback(
(id: number) => dispatch(removeCantrip({ id })),
[dispatch],
)
@@ -391,17 +172,17 @@ export const Spells: FC = () => {
</Options>
<Main classOnly>
<ListHeader>
<ListHeaderTag className="name">{translate("spells.header.name")}</ListHeaderTag>
<ListHeaderTag className="name">{translate("Name")}</ListHeaderTag>
<ListHeaderTag className="group">
{translate("spells.header.property")}
{sortOrder === "group" ? ` / ${translate("spells.header.group")}` : null}
{translate("Property")}
{sortOrder === "group" ? ` / ${translate("Group")}` : null}
</ListHeaderTag>
<ListHeaderTag className="check">{translate("spells.header.check")}</ListHeaderTag>
<ListHeaderTag className="mod" hint={translate("spells.header.checkmodifier.tooltip")}>
{translate("spells.header.checkmodifier")}
<ListHeaderTag className="check">{translate("Check")}</ListHeaderTag>
<ListHeaderTag className="mod" hint={translate("Check Modifier")}>
{translate("Mod")}
</ListHeaderTag>
<ListHeaderTag className="ic" hint={translate("spells.header.improvementcost.tooltip")}>
{translate("spells.header.improvementcost")}
<ListHeaderTag className="ic" hint={translate("Improvement Cost")}>
{translate("IC")}
</ListHeaderTag>
<ListHeaderTag className="btn-placeholder" />
<ListHeaderTag className="btn-placeholder" />
@@ -429,7 +210,6 @@ export const Spells: FC = () => {
insertTopMargin={isTopMarginNeeded(sortOrder, x, inactiveList[i - 1])}
spell={x}
sortOrder={sortOrder}
add={handleAddSpell}
/>
)
@@ -440,7 +220,6 @@ export const Spells: FC = () => {
insertTopMargin={isTopMarginNeeded(sortOrder, x, inactiveList[i - 1])}
ritual={x}
sortOrder={sortOrder}
add={handleAddRitual}
/>
)
@@ -451,7 +230,6 @@ export const Spells: FC = () => {
insertTopMargin={isTopMarginNeeded(sortOrder, x, inactiveList[i - 1])}
curse={x}
sortOrder={sortOrder}
add={handleAddCurse}
/>
)
@@ -462,7 +240,6 @@ export const Spells: FC = () => {
insertTopMargin={isTopMarginNeeded(sortOrder, x, inactiveList[i - 1])}
elvenMagicalSong={x}
sortOrder={sortOrder}
add={handleAddElvenMagicalSong}
/>
)
@@ -473,7 +250,6 @@ export const Spells: FC = () => {
insertTopMargin={isTopMarginNeeded(sortOrder, x, inactiveList[i - 1])}
dominationRitual={x}
sortOrder={sortOrder}
add={handleAddDominationRitual}
/>
)
@@ -484,7 +260,6 @@ export const Spells: FC = () => {
insertTopMargin={isTopMarginNeeded(sortOrder, x, inactiveList[i - 1])}
magicalDance={x}
sortOrder={sortOrder}
add={handleAddMagicalDance}
/>
)
@@ -495,7 +270,6 @@ export const Spells: FC = () => {
insertTopMargin={isTopMarginNeeded(sortOrder, x, inactiveList[i - 1])}
magicalMelody={x}
sortOrder={sortOrder}
add={handleAddMagicalMelody}
/>
)
@@ -506,7 +280,6 @@ export const Spells: FC = () => {
insertTopMargin={isTopMarginNeeded(sortOrder, x, inactiveList[i - 1])}
jesterTrick={x}
sortOrder={sortOrder}
add={handleAddJesterTrick}
/>
)
@@ -517,7 +290,6 @@ export const Spells: FC = () => {
insertTopMargin={isTopMarginNeeded(sortOrder, x, inactiveList[i - 1])}
animistPower={x}
sortOrder={sortOrder}
add={handleAddAnimistPower}
/>
)
@@ -528,7 +300,6 @@ export const Spells: FC = () => {
insertTopMargin={isTopMarginNeeded(sortOrder, x, inactiveList[i - 1])}
geodeRitual={x}
sortOrder={sortOrder}
add={handleAddGeodeRitual}
/>
)
@@ -539,7 +310,6 @@ export const Spells: FC = () => {
insertTopMargin={isTopMarginNeeded(sortOrder, x, inactiveList[i - 1])}
zibiljaRitual={x}
sortOrder={sortOrder}
add={handleAddZibiljaRitual}
/>
)
@@ -589,20 +359,20 @@ export const Spells: FC = () => {
</Options>
<Main>
<ListHeader>
<ListHeaderTag className="name">{translate("spells.header.name")}</ListHeaderTag>
<ListHeaderTag className="name">{translate("Name")}</ListHeaderTag>
<ListHeaderTag className="group">
{translate("spells.header.property")}
{sortOrder === "group" ? ` / ${translate("spells.header.group")}` : null}
{translate("Property")}
{sortOrder === "group" ? ` / ${translate("Group")}` : null}
</ListHeaderTag>
<ListHeaderTag className="value" hint={translate("spells.header.skillrating.tooltip")}>
{translate("spells.header.skillrating")}
<ListHeaderTag className="value" hint={translate("Skill Rating")}>
{translate("SR")}
</ListHeaderTag>
<ListHeaderTag className="check">{translate("spells.header.check")}</ListHeaderTag>
<ListHeaderTag className="mod" hint={translate("spells.header.checkmodifier.tooltip")}>
{translate("spells.header.checkmodifier")}
<ListHeaderTag className="check">{translate("Check")}</ListHeaderTag>
<ListHeaderTag className="mod" hint={translate("Check Modifier")}>
{translate("Mod")}
</ListHeaderTag>
<ListHeaderTag className="ic" hint={translate("spells.header.improvementcost.tooltip")}>
{translate("spells.header.improvementcost")}
<ListHeaderTag className="ic" hint={translate("Improvement Cost")}>
{translate("IC")}
</ListHeaderTag>
{canRemove ? <ListHeaderTag className="btn-placeholder" /> : null}
<ListHeaderTag className="btn-placeholder" />
@@ -631,9 +401,6 @@ export const Spells: FC = () => {
insertTopMargin={isTopMarginNeeded(sortOrder, x, activeList[i - 1])}
spell={x}
sortOrder={sortOrder}
addPoint={handleAddSpellPoint}
removePoint={handleRemoveSpellPoint}
remove={handleRemoveSpell}
/>
)
@@ -644,9 +411,6 @@ export const Spells: FC = () => {
insertTopMargin={isTopMarginNeeded(sortOrder, x, activeList[i - 1])}
ritual={x}
sortOrder={sortOrder}
addPoint={handleAddRitualPoint}
removePoint={handleRemoveRitualPoint}
remove={handleRemoveRitual}
/>
)
@@ -657,9 +421,6 @@ export const Spells: FC = () => {
insertTopMargin={isTopMarginNeeded(sortOrder, x, activeList[i - 1])}
curse={x}
sortOrder={sortOrder}
addPoint={handleAddCursePoint}
removePoint={handleRemoveCursePoint}
remove={handleRemoveCurse}
/>
)
@@ -670,9 +431,6 @@ export const Spells: FC = () => {
insertTopMargin={isTopMarginNeeded(sortOrder, x, activeList[i - 1])}
elvenMagicalSong={x}
sortOrder={sortOrder}
addPoint={handleAddElvenMagicalSongPoint}
removePoint={handleRemoveElvenMagicalSongPoint}
remove={handleRemoveElvenMagicalSong}
/>
)
@@ -683,9 +441,6 @@ export const Spells: FC = () => {
insertTopMargin={isTopMarginNeeded(sortOrder, x, activeList[i - 1])}
dominationRitual={x}
sortOrder={sortOrder}
addPoint={handleAddDominationRitualPoint}
removePoint={handleRemoveDominationRitualPoint}
remove={handleRemoveDominationRitual}
/>
)
@@ -696,9 +451,6 @@ export const Spells: FC = () => {
insertTopMargin={isTopMarginNeeded(sortOrder, x, activeList[i - 1])}
magicalDance={x}
sortOrder={sortOrder}
addPoint={handleAddMagicalDancePoint}
removePoint={handleRemoveMagicalDancePoint}
remove={handleRemoveMagicalDance}
/>
)
@@ -709,9 +461,6 @@ export const Spells: FC = () => {
insertTopMargin={isTopMarginNeeded(sortOrder, x, activeList[i - 1])}
magicalMelody={x}
sortOrder={sortOrder}
addPoint={handleAddMagicalMelodyPoint}
removePoint={handleRemoveMagicalMelodyPoint}
remove={handleRemoveMagicalMelody}
/>
)
@@ -722,9 +471,6 @@ export const Spells: FC = () => {
insertTopMargin={isTopMarginNeeded(sortOrder, x, activeList[i - 1])}
jesterTrick={x}
sortOrder={sortOrder}
addPoint={handleAddJesterTrickPoint}
removePoint={handleRemoveJesterTrickPoint}
remove={handleRemoveJesterTrick}
/>
)
@@ -735,9 +481,6 @@ export const Spells: FC = () => {
insertTopMargin={isTopMarginNeeded(sortOrder, x, activeList[i - 1])}
animistPower={x}
sortOrder={sortOrder}
addPoint={handleAddAnimistPowerPoint}
removePoint={handleRemoveAnimistPowerPoint}
remove={handleRemoveAnimistPower}
/>
)
@@ -748,9 +491,6 @@ export const Spells: FC = () => {
insertTopMargin={isTopMarginNeeded(sortOrder, x, activeList[i - 1])}
geodeRitual={x}
sortOrder={sortOrder}
addPoint={handleAddGeodeRitualPoint}
removePoint={handleRemoveGeodeRitualPoint}
remove={handleRemoveGeodeRitual}
/>
)
@@ -761,9 +501,6 @@ export const Spells: FC = () => {
insertTopMargin={isTopMarginNeeded(sortOrder, x, activeList[i - 1])}
zibiljaRitual={x}
sortOrder={sortOrder}
addPoint={handleAddZibiljaRitualPoint}
removePoint={handleRemoveZibiljaRitualPoint}
remove={handleRemoveZibiljaRitual}
/>
)
@@ -12,7 +12,6 @@ import {
ImprovementCost,
adventurePointsForRange,
} from "../../shared/domain/adventurePoints/improvementCost.ts"
import { RatedAdventurePointsCache } from "../../shared/domain/adventurePoints/ratedEntry.ts"
import { count } from "../../shared/utils/array.ts"
import {
selectCurrentCharacter,
@@ -43,16 +42,13 @@ import { selectMagicalAndBlessedAdvantagesAndDisadvantagesCache } from "../slice
import { SelectAll } from "./basicCapabilitySelectors.ts"
const sumRatedMaps = (
...ratedMaps: (Record<number, { cachedAdventurePoints: RatedAdventurePointsCache }> | undefined)[]
): SpentAdventurePoints =>
...ratedMaps: (Record<number, { cachedAdventurePoints: AdventurePointsCache }> | undefined)[]
): AdventurePointsCache =>
ratedMaps
.flatMap(ratedMap => Object.values(ratedMap ?? {}))
.reduce(
(acc, rated) => ({
general: acc.general + rated.cachedAdventurePoints.general,
bound: acc.bound + rated.cachedAdventurePoints.bound,
}),
{ general: 0, bound: 0 },
(acc, rated) => addAdventurePointsCaches(acc, rated.cachedAdventurePoints),
emptyAdventurePointsCache,
)
/**
@@ -104,7 +100,7 @@ export const selectAdventurePointsSpentOnLiturgicalChants = createSelector(
sumRatedMaps,
)
const sumTinyActivatables = (tinyActivatables: TinyActivatable[]): SpentAdventurePoints => ({
const sumTinyActivatables = (tinyActivatables: TinyActivatable[]): AdventurePointsCache => ({
general: count(tinyActivatables, isTinyActivatableActive),
bound: 0,
})
@@ -1,10 +1,15 @@
import { createSelector } from "@reduxjs/toolkit"
import { Attribute } from "optolith-database-schema/types/Attribute"
import { getCarryingCapacity } from "../../shared/domain/equipment.ts"
import {
AdvantageIdentifier,
AttributeIdentifier,
OptionalRuleIdentifier,
} from "../../shared/domain/identifier.ts"
import {
createEmptyDynamicAttribute,
getAttributeValue,
} from "../../shared/domain/rated/attribute.ts"
import {
getAttributeMaximum,
getAttributeMinimaByAssociatedAttributes,
@@ -15,7 +20,6 @@ import {
import { Rated } from "../../shared/domain/rated/ratedEntry.ts"
import { isNotNullish } from "../../shared/utils/nullable.ts"
import { createPropertySelector } from "../../shared/utils/redux.ts"
import { attributeValue, createInitialDynamicAttribute } from "../slices/attributesSlice.ts"
import {
selectAttributeAdjustmentId,
selectBlessedPrimaryAttributeDependencies,
@@ -160,7 +164,7 @@ export const selectVisibleAttributes = createSelector(
.sort((a, b) => a.id - b.id)
.map(attribute => {
const dynamicAttribute =
getDynamicAttributeById(attribute.id) ?? createInitialDynamicAttribute(attribute.id)
getDynamicAttributeById(attribute.id) ?? createEmptyDynamicAttribute(attribute.id)
const minimum = getAttributeMinimum(
derivedCharacteristics.lifePoints.purchased,
@@ -228,7 +232,7 @@ export const selectVisibleAttributes = createSelector(
*/
export const selectCarryingCapacity = createSelector(
createPropertySelector(selectDynamicAttributes, AttributeIdentifier.Strength),
(strength): number => (strength?.value ?? 8) * 2,
(strength): number => getCarryingCapacity(getAttributeValue(strength)),
)
/**
@@ -258,7 +262,7 @@ export const selectAvailableAdjustments = createSelector(
const canNotSwitch =
current !== undefined &&
current.maximum !== undefined &&
current.maximum - selectableAdjustment.value < attributeValue(current.dynamic)
current.maximum - selectableAdjustment.value < getAttributeValue(current.dynamic)
if (canNotSwitch) {
return {
@@ -9,6 +9,7 @@ import {
} from "../../shared/domain/identifier.ts"
import { getHighestAttributeValue } from "../../shared/domain/rated/attribute.ts"
import {
createEmptyDynamicCombatTechnique,
getAttackBaseForClose,
getAttackBaseForRanged,
getParryBaseForClose,
@@ -26,8 +27,6 @@ import {
selectDynamicAdvantages,
selectDynamicGeneralSpecialAbilities,
} from "../slices/characterSlice.ts"
import { createInitialDynamicCloseCombatTechnique } from "../slices/closeCombatTechniqueSlice.ts"
import { createInitialDynamicRangedCombatTechnique } from "../slices/rangedCombatTechniqueSlice.ts"
import { SelectAll, SelectGetById } from "./basicCapabilitySelectors.ts"
import { selectCanRemove, selectIsInCharacterCreation } from "./characterSelectors.ts"
import { selectFilterApplyingRatedDependencies } from "./dependencySelectors.ts"
@@ -123,7 +122,7 @@ export const selectVisibleCloseCombatTechniques = createSelector(
.map(combatTechnique => {
const dynamicCloseCombatTechnique =
getDynamicCloseCombatTechniqueById(combatTechnique.id) ??
createInitialDynamicCloseCombatTechnique(combatTechnique.id)
createEmptyDynamicCombatTechnique(combatTechnique.id)
const minimum = getCombatTechniqueMinimum(
rangedCombatTechniquesAt10,
@@ -220,7 +219,7 @@ export const selectVisibleRangedCombatTechniques = createSelector(
const dynamicRangedCombatTechnique =
getDynamicRangedCombatTechniqueById(combatTechnique.id) ??
createInitialDynamicRangedCombatTechnique(combatTechnique.id)
createEmptyDynamicCombatTechnique(combatTechnique.id)
const minimum = getCombatTechniqueMinimum(
rangedCombatTechniquesAt10,
@@ -3,7 +3,11 @@ import {
DerivedCharacteristic,
DerivedCharacteristicTranslation,
} from "optolith-database-schema/types/DerivedCharacteristic"
import { firstLevel, isActive } from "../../shared/domain/activatable/activatableEntry.ts"
import {
firstLevel,
isActive,
isOptionActive,
} from "../../shared/domain/activatable/activatableEntry.ts"
import {
modifierByIsActive,
modifierByIsActives,
@@ -20,10 +24,10 @@ import {
MagicalSpecialAbilityIdentifier,
OptionalRuleIdentifier,
} from "../../shared/domain/identifier.ts"
import { getAttributeValue } from "../../shared/domain/rated/attribute.ts"
import { Rated } from "../../shared/domain/rated/ratedEntry.ts"
import { filterNonNullable } from "../../shared/utils/array.ts"
import { createPropertySelector } from "../../shared/utils/redux.ts"
import { attributeValue } from "../slices/attributesSlice.ts"
import {
selectArcaneEnergyPermanentlyLost,
selectArcaneEnergyPermanentlyLostBoughtBack,
@@ -119,7 +123,7 @@ export const selectLifePoints = createSelector(
if (race === undefined || staticEntry === undefined) {
return undefined
} else {
const conValue = attributeValue(con)
const conValue = getAttributeValue(con)
const base = race.base_values.life_points + conValue * 2
const modifier = modifierByLevel(incrementor, decrementor)
const value = base + modifier + purchased - permanentlyLost
@@ -289,7 +293,7 @@ export const selectKarmaPoints = createSelector(
)
const divideAttributeSumByRound = (attributes: (Rated | undefined)[], divisor: number) =>
Math.round(attributes.reduce((acc, attr) => acc + attributeValue(attr), 0) / divisor)
Math.round(attributes.reduce((acc, attr) => acc + getAttributeValue(attr), 0) / divisor)
/**
* Returns the static and dynamic values for spirit.
@@ -453,13 +457,7 @@ export const selectMovement = createSelector(
return undefined
} else {
const oneLegged = 3
const isOneLeggedActive =
maimed?.instances.some(
instance =>
instance.options?.[0]?.type === "Predefined" &&
instance.options?.[0]?.id.type === "Generic" &&
instance.options?.[0]?.id.value === oneLegged,
) ?? false
const isOneLeggedActive = isOptionActive(maimed, { tag: "General", general: oneLegged })
const base = isOneLeggedActive
? Math.round(race.base_values.movement / 2)
@@ -1,5 +1,7 @@
import { createSelector } from "@reduxjs/toolkit"
import { Blessing } from "optolith-database-schema/types/Blessing"
import {
TinyActivatable,
getOptions,
isTinyActivatableActive,
} from "../../shared/domain/activatable/activatableEntry.ts"
@@ -72,23 +74,30 @@ const selectActiveAspectKnowledges = createSelector(
),
aspectKnowledge =>
getOptions(aspectKnowledge).flatMap(option =>
option.type === "Predefined" && option.id.type === "Aspect" ? [option.id.value] : [],
option.type === "Predefined" && option.id.tag === "Aspect" ? [option.id.aspect] : [],
),
)
/**
* Returns the blessings, split by active and inactive.
*/
export const selectVisibleBlessings = createSelector(
const selectVisibleBlessings = createSelector(
SelectAll.Static.Blessings,
SelectGetById.Dynamic.Blessing,
(
staticBlessings,
getDynamicBlessingById,
): [active: DisplayedActiveBlessing[], inactive: DisplayedActiveBlessing[]] =>
(staticBlessings, getDynamicBlessingById) =>
partition(
staticBlessings.map(blessing => ({ kind: "blessing", static: blessing })),
staticBlessing => isTinyActivatableActive(getDynamicBlessingById(staticBlessing.static.id)),
staticBlessings.map(staticBlessing => ({
kind: "blessing" as const,
static: staticBlessing,
dynamic: getDynamicBlessingById(staticBlessing.id),
})),
(
blessing,
): blessing is {
kind: "blessing"
static: Blessing
dynamic: TinyActivatable
} => isTinyActivatableActive(blessing.dynamic),
),
)
@@ -1,5 +1,6 @@
import { createSelector } from "@reduxjs/toolkit"
import { ProfessionIdentifier } from "../../shared/domain/identifier.ts"
import { isInRange } from "../../shared/utils/range.ts"
import { selectIsCharacterCreationFinished } from "../slices/characterSlice.ts"
import { selectAdventurePointsAvailable } from "./adventurePointSelectors.ts"
import { selectCurrentProfession } from "./professionSelectors.ts"
@@ -17,8 +18,7 @@ export const selectShowFinishCharacterCreation = createSelector(
*/
export const selectCanFinishCharacterCreation = createSelector(
selectAdventurePointsAvailable,
(availableAdventurePoints): boolean =>
availableAdventurePoints >= 0 && availableAdventurePoints <= 10,
(availableAdventurePoints): boolean => isInRange([0, 10], availableAdventurePoints),
)
/**
+2 -3
View File
@@ -6,7 +6,7 @@ import {
} from "../../shared/domain/identifier.ts"
import { getHighestAttributeValue } from "../../shared/domain/rated/attribute.ts"
import { Rated } from "../../shared/domain/rated/ratedEntry.ts"
import { getSkillCommonness } from "../../shared/domain/rated/skill.ts"
import { createEmptyDynamicSkill, getSkillCommonness } from "../../shared/domain/rated/skill.ts"
import {
getSkillMaximum,
getSkillMinimum,
@@ -18,7 +18,6 @@ import {
selectDynamicAdvantages,
selectDynamicGeneralSpecialAbilities,
} from "../slices/characterSlice.ts"
import { createInitialDynamicSkill } from "../slices/skillsSlice.ts"
import { SelectAll, SelectGetById } from "./basicCapabilitySelectors.ts"
import { selectCanRemove, selectIsInCharacterCreation } from "./characterSelectors.ts"
import { selectCurrentCulture } from "./cultureSelectors.ts"
@@ -72,7 +71,7 @@ export const selectVisibleSkills = createSelector(
filterApplyingDependencies,
): DisplayedSkill[] =>
staticSkills.map(skill => {
const dynamicSkill = getDynamicSkillById(skill.id) ?? createInitialDynamicSkill(skill.id)
const dynamicSkill = getDynamicSkillById(skill.id) ?? createEmptyDynamicSkill(skill.id)
const minimum = getSkillMinimum(
getDynamicSkillById,
+57 -3
View File
@@ -3,8 +3,10 @@ import { getOptions } from "../../shared/domain/activatable/activatableEntry.ts"
import {
AdvantageIdentifier,
MagicalSpecialAbilityIdentifier,
MagicalTraditionIdentifier,
createIdentifierObject,
} from "../../shared/domain/identifier.ts"
import { getImprovementCostForAnimistPower } from "../../shared/domain/rated/animistPower.ts"
import {
countActiveSpellworks,
countActiveSpellworksByImprovementCost,
@@ -78,6 +80,7 @@ const selectSpellworksAbove10ByProperty = createSelector(
SelectGetById.Static.AnimistPower,
SelectGetById.Static.GeodeRitual,
SelectGetById.Static.ZibiljaRitual,
SelectGetById.Static.MagicalRune,
SelectAll.Dynamic.Spells,
SelectAll.Dynamic.Rituals,
SelectAll.Dynamic.Curses,
@@ -89,6 +92,7 @@ const selectSpellworksAbove10ByProperty = createSelector(
SelectAll.Dynamic.AnimistPowers,
SelectAll.Dynamic.GeodeRituals,
SelectAll.Dynamic.ZibiljaRituals,
SelectAll.Dynamic.MagicalRunes,
(
getStaticSpellById,
getStaticRitualById,
@@ -101,6 +105,7 @@ const selectSpellworksAbove10ByProperty = createSelector(
getStaticAnimistPowerById,
getStaticGeodeRitualById,
getStaticZibiljaRitualById,
getStaticMagicalRuneById,
dynamicSpells,
dynamicRituals,
dynamicCurses,
@@ -112,6 +117,7 @@ const selectSpellworksAbove10ByProperty = createSelector(
dynamicAnimistPowers,
dynamicGeodeRituals,
dynamicZibiljaRituals,
dynamicMagicalRunes,
) =>
getSpellworksAbove10ByProperty(
id =>
@@ -139,6 +145,8 @@ const selectSpellworksAbove10ByProperty = createSelector(
return getStaticGeodeRitualById(id.geode_ritual)
case "ZibiljaRitual":
return getStaticZibiljaRitualById(id.zibilja_ritual)
case "MagicalRune":
return getStaticMagicalRuneById(id.magical_rune)
default:
return assertExhaustive(id)
}
@@ -154,6 +162,7 @@ const selectSpellworksAbove10ByProperty = createSelector(
dynamicAnimistPowers,
dynamicGeodeRituals,
dynamicZibiljaRituals,
dynamicMagicalRunes,
),
)
@@ -164,7 +173,7 @@ const selectActivePropertyKnowledges = createSelector(
),
propertyKnowledge =>
getOptions(propertyKnowledge).flatMap(option =>
option.type === "Predefined" && option.id.type === "Property" ? [option.id.value] : [],
option.type === "Predefined" && option.id.tag === "Property" ? [option.id.property] : [],
),
)
@@ -646,6 +655,8 @@ export const selectVisibleActiveAnimistPowers = createSelector(
selectFilterApplyingRatedDependencies,
selectSpellworksAbove10ByProperty,
selectActivePropertyKnowledges,
SelectGetById.Static.Patron,
SelectGetById.Dynamic.MagicalTradition,
(
getStaticAnimistPowerById,
dynamicAnimistPowers,
@@ -656,8 +667,12 @@ export const selectVisibleActiveAnimistPowers = createSelector(
filterApplyingDependencies,
spellworksAbove10ByProperty,
activePropertyKnowledges,
getStaticPatronById,
getDynamicMagicalTraditionById,
): DisplayedActiveAnimistPower[] => {
if (startExperienceLevel === undefined) {
const traditionAnimists = getDynamicMagicalTraditionById(MagicalTraditionIdentifier.Animisten)
if (startExperienceLevel === undefined || traditionAnimists === undefined) {
return []
}
@@ -673,6 +688,18 @@ export const selectVisibleActiveAnimistPowers = createSelector(
spellworksAbove10ByProperty,
activePropertyKnowledges,
)
.map(animistPower => ({
...animistPower,
improvementCost: getImprovementCostForAnimistPower(
getStaticPatronById,
traditionAnimists,
animistPower.static.improvement_cost,
),
}))
.filter(
(animistPower): animistPower is DisplayedActiveAnimistPower =>
animistPower.improvementCost !== undefined,
)
},
)
@@ -1013,7 +1040,34 @@ export const selectVisibleInactiveAnimistPowers = createSelector(
selectActiveMagicalTraditions,
selectIsMaximumOfSpellworksReached,
selectIsEntryAvailable,
getInactiveAnimistPowers,
SelectGetById.Static.Patron,
SelectGetById.Dynamic.MagicalTradition,
(
staticAnimistPowers,
getDynamicAnimistPowerById,
activeMagicalTraditions,
isMaximumCountReached,
isEntryAvailable,
getStaticPatronById,
getDynamicMagicalTraditionById,
) => {
const traditionAnimists = getDynamicMagicalTraditionById(MagicalTraditionIdentifier.Animisten)
if (traditionAnimists === undefined) {
return []
}
return getInactiveAnimistPowers(
staticAnimistPowers,
getDynamicAnimistPowerById,
activeMagicalTraditions,
isMaximumCountReached,
isEntryAvailable,
getStaticPatronById,
traditionAnimists,
)
},
)
/**
+48 -98
View File
@@ -1,15 +1,7 @@
import { ActionCreatorWithPayload, AnyAction, Draft, createAction } from "@reduxjs/toolkit"
import { ImprovementCost } from "../../shared/domain/adventurePoints/improvementCost.ts"
import {
BoundAdventurePoints,
cachedAdventurePointsForActivatable,
} from "../../shared/domain/adventurePoints/ratedEntry.ts"
import { RatedDependency } from "../../shared/domain/rated/ratedDependency.ts"
import {
ActivatableRated,
ActivatableRatedMap,
ActivatableRatedValue,
} from "../../shared/domain/rated/ratedEntry.ts"
import { cachedAdventurePointsForActivatable } from "../../shared/domain/adventurePoints/ratedEntry.ts"
import { ActivatableRated, ActivatableRatedMap } from "../../shared/domain/rated/ratedEntry.ts"
import { Reducer, createImmerReducer } from "../../shared/utils/redux.ts"
import { CharacterState } from "./characterSlice.ts"
import { DatabaseState } from "./databaseSlice.ts"
@@ -19,37 +11,6 @@ import { DatabaseState } from "./databaseSlice.ts"
* enhancements.
*/
export type ActivatableRatedSlice<N extends string, E extends string> = {
/**
* Creates a new entry with an initial value if active. The initial adventure
* points cache is calculated from the initial value.
*/
create: (
database: DatabaseState,
id: number,
value: ActivatableRatedValue,
options?: Partial<{
dependencies: RatedDependency[]
boundAdventurePoints: BoundAdventurePoints[]
}>,
) => ActivatableRated
/**
* Creates a new entry with no initial value.
*/
createInitial: (
id: number,
options?: Partial<{
dependencies: RatedDependency[]
boundAdventurePoints: BoundAdventurePoints[]
}>,
) => ActivatableRated
/**
* Takes an entry that may not exist (because its instance has not been used
* yet) and returns its value.
*/
getValue: (entry: ActivatableRated | undefined) => ActivatableRatedValue
/**
* The actions that can be dispatched to modify the state.
*/
@@ -57,22 +18,27 @@ export type ActivatableRatedSlice<N extends string, E extends string> = {
/**
* Add the entry with the given id.
*/
addAction: ActionCreatorWithPayload<number, `${N}/add${E}`>
addAction: ActionCreatorWithPayload<{ id: number }, `${N}/add${E}`>
/**
* Remove the entry with the given id.
*/
removeAction: ActionCreatorWithPayload<number, `${N}/remove${E}`>
removeAction: ActionCreatorWithPayload<{ id: number }, `${N}/remove${E}`>
/**
* Increments the value of the entry with the given id.
*/
incrementAction: ActionCreatorWithPayload<number, `${N}/increment${E}`>
incrementAction: ActionCreatorWithPayload<{ id: number }, `${N}/increment${E}`>
/**
* Decrements the value of the entry with the given id.
*/
decrementAction: ActionCreatorWithPayload<number, `${N}/decrement${E}`>
decrementAction: ActionCreatorWithPayload<{ id: number }, `${N}/decrement${E}`>
/**
* Sets the value of the entry with the given id.
*/
setAction: ActionCreatorWithPayload<{ id: number; value: number }, `${N}/set${E}`>
}
/**
@@ -88,103 +54,87 @@ export const createActivatableRatedSlice = <N extends string, E extends string>(
namespace: N
entityName: E
getState: (state: Draft<CharacterState>) => Draft<ActivatableRatedMap>
getImprovementCost: (id: number, database: DatabaseState) => ImprovementCost
getImprovementCost: (
id: number,
database: DatabaseState,
character: CharacterState,
) => ImprovementCost
createEmptyActivatableRated: (id: number) => ActivatableRated
}): ActivatableRatedSlice<N, E> => {
const updateCachedAdventurePoints = (entry: Draft<ActivatableRated>, database: DatabaseState) => {
const updateCachedAdventurePoints = (
entry: Draft<ActivatableRated>,
database: DatabaseState,
character: Draft<CharacterState>,
) => {
entry.cachedAdventurePoints = cachedAdventurePointsForActivatable(
entry.value,
entry.boundAdventurePoints,
config.getImprovementCost(entry.id, database),
config.getImprovementCost(entry.id, database, character),
)
return entry
}
const create: ActivatableRatedSlice<N, E>["create"] = (
database,
id,
value,
{ dependencies = [], boundAdventurePoints = [] } = {},
) =>
updateCachedAdventurePoints(
{
id,
value: value === undefined ? undefined : Math.max(0, value),
cachedAdventurePoints: {
general: 0,
bound: 0,
},
dependencies,
boundAdventurePoints,
},
database,
)
const createInitial: ActivatableRatedSlice<N, E>["createInitial"] = (
id,
{ dependencies = [], boundAdventurePoints = [] } = {},
) => ({
id,
value: undefined,
cachedAdventurePoints: {
general: 0,
bound: 0,
},
dependencies,
boundAdventurePoints,
enhancements: {},
})
const getValue: ActivatableRatedSlice<N, E>["getValue"] = entry => entry?.value
const addAction = createAction<number, `${N}/add${E}`>(
const addAction = createAction<{ id: number }, `${N}/add${E}`>(
`${config.namespace}/add${config.entityName}`,
)
const removeAction = createAction<number, `${N}/remove${E}`>(
const removeAction = createAction<{ id: number }, `${N}/remove${E}`>(
`${config.namespace}/remove${config.entityName}`,
)
const incrementAction = createAction<number, `${N}/increment${E}`>(
const incrementAction = createAction<{ id: number }, `${N}/increment${E}`>(
`${config.namespace}/increment${config.entityName}`,
)
const decrementAction = createAction<number, `${N}/decrement${E}`>(
const decrementAction = createAction<{ id: number }, `${N}/decrement${E}`>(
`${config.namespace}/decrement${config.entityName}`,
)
const setAction = createAction<{ id: number; value: number }, `${N}/set${E}`>(
`${config.namespace}/set${config.entityName}`,
)
const reducer = createImmerReducer(
(state: Draft<CharacterState>, action, database: DatabaseState) => {
if (addAction.match(action)) {
config.getState(state)[action.payload] ??= create(database, action.payload, 0)
config.getState(state)[action.payload.id] ??= config.createEmptyActivatableRated(
action.payload.id,
)
} else if (removeAction.match(action)) {
delete config.getState(state)[action.payload]
delete config.getState(state)[action.payload.id]
} else if (incrementAction.match(action)) {
const entry = (config.getState(state)[action.payload] ??= createInitial(action.payload))
const entry = (config.getState(state)[action.payload.id] ??=
config.createEmptyActivatableRated(action.payload.id))
if (entry.value === undefined) {
entry.value = 0
} else {
entry.value++
}
updateCachedAdventurePoints(entry, database)
updateCachedAdventurePoints(entry, database, state)
} else if (decrementAction.match(action)) {
const entry = config.getState(state)[action.payload]
const entry = config.getState(state)[action.payload.id]
if (entry !== undefined && entry.value !== undefined && entry.value > 0) {
entry.value--
updateCachedAdventurePoints(entry, database)
updateCachedAdventurePoints(entry, database, state)
}
} else if (setAction.match(action)) {
const entry = (config.getState(state)[action.payload.id] ??=
config.createEmptyActivatableRated(action.payload.id))
if (action.payload.value >= 0) {
entry.value = action.payload.value
updateCachedAdventurePoints(entry, database, state)
}
}
},
)
return {
create,
createInitial,
getValue,
actions: {
addAction,
removeAction,
incrementAction,
decrementAction,
setAction,
},
reducer,
}
@@ -1,14 +1,8 @@
import { ActionCreatorWithPayload, AnyAction, Draft, createAction } from "@reduxjs/toolkit"
import { ImprovementCost } from "../../shared/domain/adventurePoints/improvementCost.ts"
import {
BoundAdventurePoints,
cachedAdventurePointsForActivatableWithEnhancements,
} from "../../shared/domain/adventurePoints/ratedEntry.ts"
import { cachedAdventurePointsForActivatableWithEnhancements } from "../../shared/domain/adventurePoints/ratedEntry.ts"
import { RegistrationMethod } from "../../shared/domain/dependencies/registrationHelpers.ts"
import { Enhancement } from "../../shared/domain/rated/enhancement.ts"
import { RatedDependency } from "../../shared/domain/rated/ratedDependency.ts"
import {
ActivatableRatedValue,
ActivatableRatedWithEnhancements,
ActivatableRatedWithEnhancementsMap,
} from "../../shared/domain/rated/ratedEntry.ts"
@@ -21,40 +15,6 @@ import { DatabaseState } from "./databaseSlice.ts"
* enhancements.
*/
export type ActivatableRatedWithEnhancementsSlice<N extends string, E extends string> = {
/**
* Creates a new entry with an initial value if active. The initial adventure
* points cache is calculated from the initial value.
*/
create: (
database: DatabaseState,
id: number,
value: ActivatableRatedValue,
options?: Partial<{
dependencies: RatedDependency[]
boundAdventurePoints: BoundAdventurePoints[]
enhancements: {
[id: number]: Enhancement
}
}>,
) => ActivatableRatedWithEnhancements
/**
* Creates a new entry with no initial value.
*/
createInitial: (
id: number,
options?: Partial<{
dependencies: RatedDependency[]
boundAdventurePoints: BoundAdventurePoints[]
}>,
) => ActivatableRatedWithEnhancements
/**
* Takes an entry that may not exist (because its instance has not been used
* yet) and returns its value.
*/
getValue: (entry: ActivatableRatedWithEnhancements | undefined) => ActivatableRatedValue
/**
* The actions that can be dispatched to modify the state.
*/
@@ -62,22 +22,27 @@ export type ActivatableRatedWithEnhancementsSlice<N extends string, E extends st
/**
* Add the entry with the given id.
*/
addAction: ActionCreatorWithPayload<number, `${N}/add${E}`>
addAction: ActionCreatorWithPayload<{ id: number }, `${N}/add${E}`>
/**
* Remove the entry with the given id.
*/
removeAction: ActionCreatorWithPayload<number, `${N}/remove${E}`>
removeAction: ActionCreatorWithPayload<{ id: number }, `${N}/remove${E}`>
/**
* Increments the value of the entry with the given id.
*/
incrementAction: ActionCreatorWithPayload<number, `${N}/increment${E}`>
incrementAction: ActionCreatorWithPayload<{ id: number }, `${N}/increment${E}`>
/**
* Decrements the value of the entry with the given id.
*/
decrementAction: ActionCreatorWithPayload<number, `${N}/decrement${E}`>
decrementAction: ActionCreatorWithPayload<{ id: number }, `${N}/decrement${E}`>
/**
* Sets the value of the entry with the given id.
*/
setAction: ActionCreatorWithPayload<{ id: number; value: number }, `${N}/set${E}`>
}
/**
@@ -112,6 +77,7 @@ export const createActivatableRatedWithEnhancementsSlice = <
prerequisites: P[],
sourceId: IdO,
) => void
createEmptyActivatableRatedWithEnhancements: (id: number) => ActivatableRatedWithEnhancements
}): ActivatableRatedWithEnhancementsSlice<N, E> => {
const updateCachedAdventurePoints = (
entry: Draft<ActivatableRatedWithEnhancements>,
@@ -128,80 +94,48 @@ export const createActivatableRatedWithEnhancementsSlice = <
return entry
}
const create: ActivatableRatedWithEnhancementsSlice<N, E>["create"] = (
database,
id,
value,
{ dependencies = [], boundAdventurePoints = [], enhancements = {} } = {},
) =>
updateCachedAdventurePoints(
{
id,
value: value === undefined ? undefined : Math.max(0, value),
cachedAdventurePoints: {
general: 0,
bound: 0,
},
dependencies,
boundAdventurePoints,
enhancements,
},
database,
)
const createInitial: ActivatableRatedWithEnhancementsSlice<N, E>["createInitial"] = (
id,
{ dependencies = [], boundAdventurePoints = [] } = {},
) => ({
id,
value: undefined,
cachedAdventurePoints: {
general: 0,
bound: 0,
},
dependencies,
boundAdventurePoints,
enhancements: {},
})
const getValue: ActivatableRatedWithEnhancementsSlice<N, E>["getValue"] = entry => entry?.value
const addAction = createAction<number, `${N}/add${E}`>(
const addAction = createAction<{ id: number }, `${N}/add${E}`>(
`${config.namespace}/add${config.entityName}`,
)
const removeAction = createAction<number, `${N}/remove${E}`>(
const removeAction = createAction<{ id: number }, `${N}/remove${E}`>(
`${config.namespace}/remove${config.entityName}`,
)
const incrementAction = createAction<number, `${N}/increment${E}`>(
const incrementAction = createAction<{ id: number }, `${N}/increment${E}`>(
`${config.namespace}/increment${config.entityName}`,
)
const decrementAction = createAction<number, `${N}/decrement${E}`>(
const decrementAction = createAction<{ id: number }, `${N}/decrement${E}`>(
`${config.namespace}/decrement${config.entityName}`,
)
const setAction = createAction<{ id: number; value: number }, `${N}/set${E}`>(
`${config.namespace}/set${config.entityName}`,
)
const reducer = createImmerReducer(
(state: Draft<CharacterState>, action, database: DatabaseState) => {
if (addAction.match(action)) {
config.getState(state)[action.payload] ??= create(database, action.payload, 0)
config.getState(state)[action.payload.id] ??=
config.createEmptyActivatableRatedWithEnhancements(action.payload.id)
config.registerOrUnregisterPrerequisitesAsDependencies(
RegistrationMethod.Add,
state,
config.getPrerequisites(action.payload, database),
config.createIdentifierObject(action.payload),
config.getPrerequisites(action.payload.id, database),
config.createIdentifierObject(action.payload.id),
)
} else if (removeAction.match(action)) {
config.registerOrUnregisterPrerequisitesAsDependencies(
RegistrationMethod.Remove,
state,
config.getPrerequisites(action.payload, database),
config.createIdentifierObject(action.payload),
config.getPrerequisites(action.payload.id, database),
config.createIdentifierObject(action.payload.id),
)
delete config.getState(state)[action.payload]
delete config.getState(state)[action.payload.id]
} else if (incrementAction.match(action)) {
const entry = (config.getState(state)[action.payload] ??= createInitial(action.payload))
const entry = (config.getState(state)[action.payload.id] ??=
config.createEmptyActivatableRatedWithEnhancements(action.payload.id))
if (entry.value === undefined) {
entry.value = 0
} else {
@@ -209,24 +143,29 @@ export const createActivatableRatedWithEnhancementsSlice = <
}
updateCachedAdventurePoints(entry, database)
} else if (decrementAction.match(action)) {
const entry = config.getState(state)[action.payload]
const entry = config.getState(state)[action.payload.id]
if (entry !== undefined && entry.value !== undefined && entry.value > 0) {
entry.value--
updateCachedAdventurePoints(entry, database)
}
} else if (setAction.match(action)) {
const entry = (config.getState(state)[action.payload.id] ??=
config.createEmptyActivatableRatedWithEnhancements(action.payload.id))
if (action.payload.value >= 0) {
entry.value = action.payload.value
updateCachedAdventurePoints(entry, database)
}
}
},
)
return {
create,
createInitial,
getValue,
actions: {
addAction,
removeAction,
incrementAction,
decrementAction,
setAction,
},
reducer,
}
+14 -17
View File
@@ -1,25 +1,22 @@
import { ImprovementCost } from "../../shared/domain/adventurePoints/improvementCost.ts"
import {
attributeImprovementCost,
createEmptyDynamicAttribute,
} from "../../shared/domain/rated/attribute.ts"
import { createRatedSlice } from "./ratedSlice.ts"
const {
create,
createInitial,
getValue,
actions: { incrementAction, decrementAction },
reducer,
// eslint-disable-next-line jsdoc/require-jsdoc
export const {
actions: {
incrementEntry: incrementAttribute,
setEntry: setAttribute,
decrementEntry: decrementAttribute,
},
reducer: attributesReducer,
} = createRatedSlice({
namespace: "attributes",
entityName: "Attribute",
getState: state => state.attributes,
minValue: 8,
getImprovementCost: () => ImprovementCost.E,
getImprovementCost: () => attributeImprovementCost,
createEmptyRated: createEmptyDynamicAttribute,
})
export {
getValue as attributeValue,
reducer as attributesReducer,
create as createDynamicAttribute,
createInitial as createInitialDynamicAttribute,
decrementAction as decrementAttribute,
incrementAction as incrementAttribute,
}
+3 -3
View File
@@ -1,18 +1,17 @@
import { ImprovementCost, fromRaw } from "../../shared/domain/adventurePoints/improvementCost.ts"
import { registerOrUnregisterPrerequisitesOfLiturgyAsDependencies } from "../../shared/domain/dependencies/fullPrerequisiteRegistrationAsDependencyForType.ts"
import { createIdentifierObject } from "../../shared/domain/identifier.ts"
import { createEmptyDynamicLiturgicalChant } from "../../shared/domain/rated/liturgicalChant.ts"
import { createActivatableRatedWithEnhancementsSlice } from "./activatableRatedWithEnhancementsSlice.ts"
// eslint-disable-next-line jsdoc/require-jsdoc
export const {
create: createDynamicCeremony,
createInitial: createInitialDynamicCeremony,
getValue: getCeremonyValue,
actions: {
addAction: addCeremony,
removeAction: removeCeremony,
incrementAction: incrementCeremony,
decrementAction: decrementCeremony,
setAction: setCeremony,
},
reducer: ceremoniesReducer,
} = createActivatableRatedWithEnhancementsSlice({
@@ -27,4 +26,5 @@ export const {
createIdentifierObject: id => createIdentifierObject("Ceremony", id),
registerOrUnregisterPrerequisitesAsDependencies:
registerOrUnregisterPrerequisitesOfLiturgyAsDependencies,
createEmptyActivatableRatedWithEnhancements: createEmptyDynamicLiturgicalChant,
})
+102 -13
View File
@@ -1,12 +1,12 @@
import { AnyAction, createAction } from "@reduxjs/toolkit"
import { Draft } from "immer"
import { createDynamicActivatable } from "../../shared/domain/activatable/activatableEntry.ts"
import { Character } from "../../shared/domain/character.ts"
import {
AdvantageIdentifier,
BlessedTraditionIdentifier,
MagicalTraditionIdentifier,
} from "../../shared/domain/identifier.ts"
import { createImmerReducer, reduceReducers } from "../../shared/utils/redux.ts"
import { DraftReducer, reduceDraftReducers } from "../../shared/utils/redux.ts"
import { RootState } from "../store.ts"
import { advantagesReducer } from "./advantagesSlice.ts"
import { attributesReducer } from "./attributesSlice.ts"
@@ -26,6 +26,7 @@ import { geodeRitualsReducer } from "./magicalActions/geodeRitualsSlice.ts"
import { jesterTricksReducer } from "./magicalActions/jesterTricksSlice.ts"
import { magicalDancesReducer } from "./magicalActions/magicalDancesSlice.ts"
import { magicalMelodiesReducer } from "./magicalActions/magicalMelodiesSlice.ts"
import { magicalRunesReducer } from "./magicalActions/magicalRunesSlice.ts"
import { zibiljaRitualsReducer } from "./magicalActions/zibiljaRitualsSlice.ts"
import { personalDataReducer } from "./personalDataSlice.ts"
import { professionReducer } from "./professionSlice.ts"
@@ -53,10 +54,12 @@ const staticInitialState: Omit<CharacterState, "dateCreated" | "dateLastModified
id: 1,
variantId: 1,
selectedAttributeAdjustmentId: 1,
dependencies: [],
},
culture: {
id: 1,
isCulturalPackageApplied: false,
dependencies: [],
},
profession: {
id: 1,
@@ -65,12 +68,14 @@ const staticInitialState: Omit<CharacterState, "dateCreated" | "dateLastModified
rules: {
includeAllPublications: true,
includePublications: [],
publicationDependencies: [],
focusRules: {},
optionalRules: {},
},
states: {},
personalData: {
sex: { type: "Male" },
sexDependencies: [],
socialStatus: {
dependencies: [],
},
@@ -118,7 +123,6 @@ const staticInitialState: Omit<CharacterState, "dateCreated" | "dateLastModified
krallenkettenzauber: {},
liturgicalStyleSpecialAbilities: {},
lycantropicGifts: {},
magicalRunes: {},
magicalSigns: {},
magicalSpecialAbilities: {},
magicalTraditions: {
@@ -147,6 +151,88 @@ const staticInitialState: Omit<CharacterState, "dateCreated" | "dateLastModified
wandEnchantments: {},
weaponEnchantments: {},
},
attributes: {
1: {
id: 1,
value: 13,
dependencies: [],
cachedAdventurePoints: {
general: 75,
bound: 0,
},
boundAdventurePoints: [],
},
2: {
id: 2,
value: 13,
dependencies: [],
cachedAdventurePoints: {
general: 75,
bound: 0,
},
boundAdventurePoints: [],
},
3: {
id: 3,
value: 13,
dependencies: [],
cachedAdventurePoints: {
general: 75,
bound: 0,
},
boundAdventurePoints: [],
},
4: {
id: 4,
value: 13,
dependencies: [],
cachedAdventurePoints: {
general: 75,
bound: 0,
},
boundAdventurePoints: [],
},
5: {
id: 5,
value: 12,
dependencies: [],
cachedAdventurePoints: {
general: 60,
bound: 0,
},
boundAdventurePoints: [],
},
6: {
id: 6,
value: 12,
dependencies: [],
cachedAdventurePoints: {
general: 60,
bound: 0,
},
boundAdventurePoints: [],
},
7: {
id: 7,
value: 12,
dependencies: [],
cachedAdventurePoints: {
general: 60,
bound: 0,
},
boundAdventurePoints: [],
},
8: {
id: 8,
value: 12,
dependencies: [],
cachedAdventurePoints: {
general: 60,
bound: 0,
},
boundAdventurePoints: [],
},
},
magicalPrimaryAttributeDependencies: [],
blessedPrimaryAttributeDependencies: [],
derivedCharacteristics: {
@@ -212,6 +298,7 @@ const staticInitialState: Omit<CharacterState, "dateCreated" | "dateLastModified
animistPowers: {},
geodeRituals: {},
zibiljaRituals: {},
magicalRunes: {},
},
blessings: {
1: {
@@ -255,6 +342,7 @@ const staticInitialState: Omit<CharacterState, "dateCreated" | "dateLastModified
},
// creatures: {}
// pact: {}
pactDependencies: [],
}
/**
@@ -654,12 +742,6 @@ export const selectDynamicLiturgicalStyleSpecialAbilities = (state: RootState) =
export const selectDynamicLycantropicGifts = (state: RootState) =>
selectCurrentCharacter(state)?.specialAbilities.lycantropicGifts
/**
* Select the magical runes of the currently open character.
*/
export const selectDynamicMagicalRunes = (state: RootState) =>
selectCurrentCharacter(state)?.specialAbilities.magicalRunes
/**
* Select the magical signs of the currently open character.
*/
@@ -932,6 +1014,12 @@ export const selectDynamicGeodeRituals = (state: RootState) =>
export const selectDynamicZibiljaRituals = (state: RootState) =>
selectCurrentCharacter(state)?.magicalActions.zibiljaRituals
/**
* Select the magical runes of the currently open character.
*/
export const selectDynamicMagicalRunes = (state: RootState) =>
selectCurrentCharacter(state)?.magicalActions.magicalRunes
/**
* Select the blessings of the currently open character.
*/
@@ -986,7 +1074,7 @@ export const deleteAvatar = createAction("character/deleteAvatar")
*/
export const finishCharacterCreation = createAction("character/finishCharacterCreation")
const generalCharacterReducer = createImmerReducer((state: Draft<CharacterState>, action) => {
const generalCharacterReducer: DraftReducer<CharacterState> = (state, action) => {
if (setName.match(action)) {
state.name = action.payload
} else if (setAvatar.match(action)) {
@@ -996,13 +1084,13 @@ const generalCharacterReducer = createImmerReducer((state: Draft<CharacterState>
} else if (finishCharacterCreation.match(action)) {
state.isCharacterCreationFinished = true
}
})
}
/**
* The state reducer for a character.
*/
export const characterReducer = reduceReducers<
Draft<CharacterState>,
export const characterReducer = reduceDraftReducers<
CharacterState,
AnyAction,
[database: DatabaseState]
>(
@@ -1030,6 +1118,7 @@ export const characterReducer = reduceReducers<
animistPowersReducer,
geodeRitualsReducer,
zibiljaRitualsReducer,
magicalRunesReducer,
blessingsReducer,
liturgicalChantsReducer,
ceremoniesReducer,
@@ -1,12 +1,15 @@
import { ImprovementCost, fromRaw } from "../../shared/domain/adventurePoints/improvementCost.ts"
import { createEmptyDynamicCombatTechnique } from "../../shared/domain/rated/combatTechnique.ts"
import { createRatedSlice } from "./ratedSlice.ts"
const {
create,
createInitial,
getValue,
actions: { incrementAction, decrementAction },
reducer,
// eslint-disable-next-line jsdoc/require-jsdoc
export const {
actions: {
incrementEntry: incrementCloseCombatTechnique,
setEntry: setCloseCombatTechnique,
decrementEntry: decrementCloseCombatTechnique,
},
reducer: closeCombatTechniquesReducer,
} = createRatedSlice({
namespace: "combatTechniques/close",
entityName: "CloseCombatTechnique",
@@ -14,13 +17,5 @@ const {
minValue: 6,
getImprovementCost: (id, database) =>
fromRaw(database.closeCombatTechniques[id]?.improvement_cost) ?? ImprovementCost.D,
createEmptyRated: createEmptyDynamicCombatTechnique,
})
export {
reducer as closeCombatTechniquesReducer,
create as createDynamicCloseCombatTechnique,
createInitial as createInitialDynamicCloseCombatTechnique,
decrementAction as decrementCloseCombatTechnique,
getValue as getCloseCombatTechniqueValue,
incrementAction as incrementCloseCombatTechnique,
}
+82
View File
@@ -45,6 +45,78 @@ const initialDatabaseState: DatabaseState = {
brews: {},
cantrips: {},
cache: {
activatableSelectOptions: {
advancedCombatSpecialAbilities: {},
advancedKarmaSpecialAbilities: {},
advancedMagicalSpecialAbilities: {},
advancedSkillSpecialAbilities: {},
advantages: {},
ancestorGlyphs: {},
arcaneOrbEnchantments: {},
attireEnchantments: {},
blessedTraditions: {},
bowlEnchantments: {},
brawlingSpecialAbilities: {},
cauldronEnchantments: {},
ceremonialItemSpecialAbilities: {},
chronicleEnchantments: {},
combatSpecialAbilities: {},
combatStyleSpecialAbilities: {},
commandSpecialAbilities: {},
daggerRituals: {},
disadvantages: {},
familiarSpecialAbilities: {},
fatePointSexSpecialAbilities: {},
fatePointSpecialAbilities: {},
foolsHatEnchantments: {},
generalSpecialAbilities: {},
instrumentEnchantments: {},
karmaSpecialAbilities: {},
krallenkettenzauber: {},
liturgicalStyleSpecialAbilities: {},
lycantropicGifts: {},
magicalSpecialAbilities: {},
magicalTraditions: {},
magicStyleSpecialAbilities: {},
orbEnchantments: {},
pactGifts: {},
protectiveWardingCircleSpecialAbilities: {},
ringEnchantments: {},
sermons: {},
sexSpecialAbilities: {},
sickleRituals: {},
sikaryanDrainSpecialAbilities: {},
staffEnchantments: {},
skillStyleSpecialAbilities: {},
spellSwordEnchantments: {},
toyEnchantments: {},
trinkhornzauber: {},
vampiricGifts: {},
visions: {},
wandEnchantments: {},
weaponEnchantments: {},
},
ancestorBloodAdvantages: {
ids: [],
},
magicalAndBlessedAdvantagesAndDisadvantages: {
advantages: {
magical: {
ids: [],
},
blessed: {
ids: [],
},
},
disadvantages: {
magical: {
ids: [],
},
blessed: {
ids: [],
},
},
},
newApplicationsAndUses: {
newApplications: {},
uses: {},
@@ -614,4 +686,14 @@ export const selectStaticWeaponEnchantments = (state: RootState) =>
export const selectStaticWeapons = (state: RootState) => state.database.weapons
export const selectStaticZibiljaRituals = (state: RootState) => state.database.zibiljaRituals
export const selectCache = (state: RootState) => state.database.cache
export const selectActivatableSelectOptionsCache = (state: RootState) =>
state.database.cache.activatableSelectOptions
export const selectAncestorBloodAdvantagesCache = (state: RootState) =>
state.database.cache.ancestorBloodAdvantages
export const selectMagicalAndBlessedAdvantagesAndDisadvantagesCache = (state: RootState) =>
state.database.cache.magicalAndBlessedAdvantagesAndDisadvantages
export const selectNewApplicationsAndUsesCache = (state: RootState) =>
state.database.cache.newApplicationsAndUses
export const databaseReducer = databaseSlice.reducer
@@ -1,7 +1,7 @@
/* eslint-disable jsdoc/require-jsdoc */
/* eslint-disable max-len */
import { createAction } from "@reduxjs/toolkit"
import { createImmerReducer } from "../../shared/utils/redux.ts"
import { DraftReducer } from "../../shared/utils/redux.ts"
import { CharacterState } from "./characterSlice.ts"
export const incrementLifePoints = createAction("derivedCharacteristics/incrementLifePoints")
@@ -50,7 +50,7 @@ export const decrementKarmaPointsBoughtBack = createAction(
"derivedCharacteristics/decrementKarmaPointsBoughtBack",
)
export const derivedCharacteristicsReducer = createImmerReducer<CharacterState>((state, action) => {
export const derivedCharacteristicsReducer: DraftReducer<CharacterState> = (state, action) => {
if (incrementLifePoints.match(action)) {
state.derivedCharacteristics.lifePoints.purchased++
} else if (decrementLifePoints.match(action)) {
@@ -106,4 +106,4 @@ export const derivedCharacteristicsReducer = createImmerReducer<CharacterState>(
state.derivedCharacteristics.karmaPoints.permanentlyLostBoughtBack--
}
}
})
}
@@ -1,18 +1,17 @@
import { ImprovementCost, fromRaw } from "../../shared/domain/adventurePoints/improvementCost.ts"
import { registerOrUnregisterPrerequisitesOfLiturgyAsDependencies } from "../../shared/domain/dependencies/fullPrerequisiteRegistrationAsDependencyForType.ts"
import { createIdentifierObject } from "../../shared/domain/identifier.ts"
import { createEmptyDynamicLiturgicalChant } from "../../shared/domain/rated/liturgicalChant.ts"
import { createActivatableRatedWithEnhancementsSlice } from "./activatableRatedWithEnhancementsSlice.ts"
// eslint-disable-next-line jsdoc/require-jsdoc
export const {
create: createDynamicLiturgicalChant,
createInitial: createInitialDynamicLiturgicalChant,
getValue: getLiturgicalChantValue,
actions: {
addAction: addLiturgicalChant,
removeAction: removeLiturgicalChant,
incrementAction: incrementLiturgicalChant,
decrementAction: decrementLiturgicalChant,
setAction: setLiturgicalChant,
},
reducer: liturgicalChantsReducer,
} = createActivatableRatedWithEnhancementsSlice({
@@ -27,4 +26,5 @@ export const {
createIdentifierObject: id => createIdentifierObject("LiturgicalChant", id),
registerOrUnregisterPrerequisitesAsDependencies:
registerOrUnregisterPrerequisitesOfLiturgyAsDependencies,
createEmptyActivatableRatedWithEnhancements: createEmptyDynamicLiturgicalChant,
})
@@ -1,36 +1,39 @@
import { ImprovementCost, fromRaw } from "../../../shared/domain/adventurePoints/improvementCost.ts"
import { assertExhaustive } from "../../../shared/utils/typeSafety.ts"
import { ImprovementCost } from "../../../shared/domain/adventurePoints/improvementCost.ts"
import { MagicalTraditionIdentifier } from "../../../shared/domain/identifier.ts"
import { getImprovementCostForAnimistPower } from "../../../shared/domain/rated/animistPower.ts"
import { createEmptyDynamicMagicalAction } from "../../../shared/domain/rated/magicalActions.ts"
import { createActivatableRatedSlice } from "../activatableRatedSlice.ts"
// eslint-disable-next-line jsdoc/require-jsdoc
export const {
create: createDynamicAnimistPower,
createInitial: createInitialDynamicAnimistPower,
getValue: getAnimistPowerValue,
actions: {
addAction: addAnimistPower,
removeAction: removeAnimistPower,
incrementAction: incrementAnimistPower,
decrementAction: decrementAnimistPower,
setAction: setAnimistPower,
},
reducer: animistPowersReducer,
} = createActivatableRatedSlice({
namespace: "animistPowers",
entityName: "AnimistPower",
getState: state => state.magicalActions.animistPowers,
getImprovementCost: (id, database) => {
getImprovementCost: (id, database, character) => {
const animistPower = database.animistPowers[id]
if (animistPower === undefined) {
const traditionAnimists =
character.specialAbilities.magicalTraditions[MagicalTraditionIdentifier.Animisten]
if (animistPower === undefined || traditionAnimists === undefined) {
return ImprovementCost.D
}
switch (animistPower.improvement_cost.tag) {
case "Fixed":
return fromRaw(animistPower.improvement_cost.fixed)
case "ByPrimaryPatron":
// TODO: Replace with derived improvement cost
return ImprovementCost.D
default:
return assertExhaustive(animistPower.improvement_cost)
}
return (
getImprovementCostForAnimistPower(
patronId => database.patrons[patronId],
traditionAnimists,
animistPower.improvement_cost,
) ?? ImprovementCost.D
)
},
createEmptyActivatableRated: createEmptyDynamicMagicalAction,
})
@@ -1,16 +1,17 @@
import { cursesImprovementCost } from "../../../shared/domain/rated/magicalActions.ts"
import {
createEmptyDynamicMagicalAction,
cursesImprovementCost,
} from "../../../shared/domain/rated/magicalActions.ts"
import { createActivatableRatedSlice } from "../activatableRatedSlice.ts"
// eslint-disable-next-line jsdoc/require-jsdoc
export const {
create: createDynamicCurse,
createInitial: createInitialDynamicCurse,
getValue: getCurseValue,
actions: {
addAction: addCurse,
removeAction: removeCurse,
incrementAction: incrementCurse,
decrementAction: decrementCurse,
setAction: setCurse,
},
reducer: cursesReducer,
} = createActivatableRatedSlice({
@@ -18,4 +19,5 @@ export const {
entityName: "Curse",
getState: state => state.magicalActions.curses,
getImprovementCost: (_id, _database) => cursesImprovementCost,
createEmptyActivatableRated: createEmptyDynamicMagicalAction,
})
@@ -1,16 +1,17 @@
import { dominationRitualsImprovementCost } from "../../../shared/domain/rated/magicalActions.ts"
import {
createEmptyDynamicMagicalAction,
dominationRitualsImprovementCost,
} from "../../../shared/domain/rated/magicalActions.ts"
import { createActivatableRatedSlice } from "../activatableRatedSlice.ts"
// eslint-disable-next-line jsdoc/require-jsdoc
export const {
create: createDynamicDominationRitual,
createInitial: createInitialDynamicDominationRitual,
getValue: getDominationRitualValue,
actions: {
addAction: addDominationRitual,
removeAction: removeDominationRitual,
incrementAction: incrementDominationRitual,
decrementAction: decrementDominationRitual,
setAction: setDominationRitual,
},
reducer: dominationRitualsReducer,
} = createActivatableRatedSlice({
@@ -18,4 +19,5 @@ export const {
entityName: "DominationRitual",
getState: state => state.magicalActions.dominationRituals,
getImprovementCost: (_id, _database) => dominationRitualsImprovementCost,
createEmptyActivatableRated: createEmptyDynamicMagicalAction,
})
@@ -1,16 +1,15 @@
import { ImprovementCost, fromRaw } from "../../../shared/domain/adventurePoints/improvementCost.ts"
import { createEmptyDynamicMagicalAction } from "../../../shared/domain/rated/magicalActions.ts"
import { createActivatableRatedSlice } from "../activatableRatedSlice.ts"
// eslint-disable-next-line jsdoc/require-jsdoc
export const {
create: createDynamicElvenMagicalSong,
createInitial: createInitialDynamicElvenMagicalSong,
getValue: getElvenMagicalSongValue,
actions: {
addAction: addElvenMagicalSong,
removeAction: removeElvenMagicalSong,
incrementAction: incrementElvenMagicalSong,
decrementAction: decrementElvenMagicalSong,
setAction: setElvenMagicalSong,
},
reducer: elvenMagicalSongsReducer,
} = createActivatableRatedSlice({
@@ -19,4 +18,5 @@ export const {
getState: state => state.magicalActions.elvenMagicalSongs,
getImprovementCost: (id, database) =>
fromRaw(database.elvenMagicalSongs[id]?.improvement_cost) ?? ImprovementCost.D,
createEmptyActivatableRated: createEmptyDynamicMagicalAction,
})
@@ -1,16 +1,17 @@
import { geodeRitualsImprovementCost } from "../../../shared/domain/rated/magicalActions.ts"
import {
createEmptyDynamicMagicalAction,
geodeRitualsImprovementCost,
} from "../../../shared/domain/rated/magicalActions.ts"
import { createActivatableRatedSlice } from "../activatableRatedSlice.ts"
// eslint-disable-next-line jsdoc/require-jsdoc
export const {
create: createDynamicGeodeRitual,
createInitial: createInitialDynamicGeodeRitual,
getValue: getGeodeRitualValue,
actions: {
addAction: addGeodeRitual,
removeAction: removeGeodeRitual,
incrementAction: incrementGeodeRitual,
decrementAction: decrementGeodeRitual,
setAction: setGeodeRitual,
},
reducer: geodeRitualsReducer,
} = createActivatableRatedSlice({
@@ -18,4 +19,5 @@ export const {
entityName: "GeodeRitual",
getState: state => state.magicalActions.geodeRituals,
getImprovementCost: (_id, _database) => geodeRitualsImprovementCost,
createEmptyActivatableRated: createEmptyDynamicMagicalAction,
})
@@ -1,16 +1,15 @@
import { ImprovementCost, fromRaw } from "../../../shared/domain/adventurePoints/improvementCost.ts"
import { createEmptyDynamicMagicalAction } from "../../../shared/domain/rated/magicalActions.ts"
import { createActivatableRatedSlice } from "../activatableRatedSlice.ts"
// eslint-disable-next-line jsdoc/require-jsdoc
export const {
create: createDynamicJesterTrick,
createInitial: createInitialDynamicJesterTrick,
getValue: getJesterTrickValue,
actions: {
addAction: addJesterTrick,
removeAction: removeJesterTrick,
incrementAction: incrementJesterTrick,
decrementAction: decrementJesterTrick,
setAction: setJesterTrick,
},
reducer: jesterTricksReducer,
} = createActivatableRatedSlice({
@@ -19,4 +18,5 @@ export const {
getState: state => state.magicalActions.jesterTricks,
getImprovementCost: (id, database) =>
fromRaw(database.jesterTricks[id]?.improvement_cost) ?? ImprovementCost.D,
createEmptyActivatableRated: createEmptyDynamicMagicalAction,
})
@@ -1,16 +1,15 @@
import { ImprovementCost, fromRaw } from "../../../shared/domain/adventurePoints/improvementCost.ts"
import { createEmptyDynamicMagicalAction } from "../../../shared/domain/rated/magicalActions.ts"
import { createActivatableRatedSlice } from "../activatableRatedSlice.ts"
// eslint-disable-next-line jsdoc/require-jsdoc
export const {
create: createDynamicMagicalDance,
createInitial: createInitialDynamicMagicalDance,
getValue: getMagicalDanceValue,
actions: {
addAction: addMagicalDance,
removeAction: removeMagicalDance,
incrementAction: incrementMagicalDance,
decrementAction: decrementMagicalDance,
setAction: setMagicalDance,
},
reducer: magicalDancesReducer,
} = createActivatableRatedSlice({
@@ -19,4 +18,5 @@ export const {
getState: state => state.magicalActions.magicalDances,
getImprovementCost: (id, database) =>
fromRaw(database.magicalDances[id]?.improvement_cost) ?? ImprovementCost.D,
createEmptyActivatableRated: createEmptyDynamicMagicalAction,
})
@@ -1,16 +1,15 @@
import { ImprovementCost, fromRaw } from "../../../shared/domain/adventurePoints/improvementCost.ts"
import { createEmptyDynamicMagicalAction } from "../../../shared/domain/rated/magicalActions.ts"
import { createActivatableRatedSlice } from "../activatableRatedSlice.ts"
// eslint-disable-next-line jsdoc/require-jsdoc
export const {
create: createDynamicMagicalMelody,
createInitial: createInitialDynamicMagicalMelody,
getValue: getMagicalMelodyValue,
actions: {
addAction: addMagicalMelody,
removeAction: removeMagicalMelody,
incrementAction: incrementMagicalMelody,
decrementAction: decrementMagicalMelody,
setAction: setMagicalMelody,
},
reducer: magicalMelodiesReducer,
} = createActivatableRatedSlice({
@@ -19,4 +18,5 @@ export const {
getState: state => state.magicalActions.magicalMelodies,
getImprovementCost: (id, database) =>
fromRaw(database.magicalMelodies[id]?.improvement_cost) ?? ImprovementCost.D,
createEmptyActivatableRated: createEmptyDynamicMagicalAction,
})
@@ -0,0 +1,30 @@
import { ImprovementCost } from "../../../shared/domain/adventurePoints/improvementCost.ts"
import { createEmptyDynamicMagicalAction } from "../../../shared/domain/rated/magicalActions.ts"
import { getImprovementCostForMagicalRune } from "../../../shared/domain/rated/magicalRune.ts"
import { createActivatableRatedSlice } from "../activatableRatedSlice.ts"
// eslint-disable-next-line jsdoc/require-jsdoc
export const {
actions: {
addAction: addMagicalRune,
removeAction: removeMagicalRune,
incrementAction: incrementMagicalRune,
decrementAction: decrementMagicalRune,
setAction: setMagicalRune,
},
reducer: magicalRunesReducer,
} = createActivatableRatedSlice({
namespace: "magicalRunes",
entityName: "MagicalRune",
getState: state => state.magicalActions.magicalRunes,
getImprovementCost: (id, database) => {
const staticEntry = database.magicalRunes[id]
return (
getImprovementCostForMagicalRune(
// staticEntry?.options,
staticEntry?.improvement_cost ?? { tag: "Constant", constant: { value: "D" } },
) ?? ImprovementCost.D
)
},
createEmptyActivatableRated: createEmptyDynamicMagicalAction,
})
@@ -1,16 +1,15 @@
import { ImprovementCost, fromRaw } from "../../../shared/domain/adventurePoints/improvementCost.ts"
import { createEmptyDynamicMagicalAction } from "../../../shared/domain/rated/magicalActions.ts"
import { createActivatableRatedSlice } from "../activatableRatedSlice.ts"
// eslint-disable-next-line jsdoc/require-jsdoc
export const {
create: createDynamicZibiljaRitual,
createInitial: createInitialDynamicZibiljaRitual,
getValue: getZibiljaRitualValue,
actions: {
addAction: addZibiljaRitual,
removeAction: removeZibiljaRitual,
incrementAction: incrementZibiljaRitual,
decrementAction: decrementZibiljaRitual,
setAction: setZibiljaRitual,
},
reducer: zibiljaRitualsReducer,
} = createActivatableRatedSlice({
@@ -19,4 +18,5 @@ export const {
getState: state => state.magicalActions.zibiljaRituals,
getImprovementCost: (id, database) =>
fromRaw(database.zibiljaRituals[id]?.improvement_cost) ?? ImprovementCost.D,
createEmptyActivatableRated: createEmptyDynamicMagicalAction,
})
+3 -3
View File
@@ -4,7 +4,7 @@ import { Height, Weight, WeightDiceOffsetStrategy } from "optolith-database-sche
import { DieType } from "optolith-database-schema/types/_Dice"
import { rollDice, separateDice } from "../../shared/utils/dice.ts"
import { even, parseInt, randomInt } from "../../shared/utils/math.ts"
import { createImmerReducer } from "../../shared/utils/redux.ts"
import { DraftReducer } from "../../shared/utils/redux.ts"
import { assertExhaustive } from "../../shared/utils/typeSafety.ts"
import { CharacterState } from "./characterSlice.ts"
@@ -78,7 +78,7 @@ export const rerollWeight = createAction(
}),
)
export const personalDataReducer = createImmerReducer<CharacterState>((state, action) => {
export const personalDataReducer: DraftReducer<CharacterState> = (state, action) => {
if (setFamily.match(action)) {
state.personalData.family = action.payload === "" ? undefined : action.payload
} else if (setPlaceOfBirth.match(action)) {
@@ -131,4 +131,4 @@ export const personalDataReducer = createImmerReducer<CharacterState>((state, ac
} else if (setOtherInfo.match(action)) {
state.personalData.otherInfo = action.payload === "" ? undefined : action.payload
}
})
}
+3 -3
View File
@@ -1,6 +1,6 @@
/* eslint-disable jsdoc/require-jsdoc */
import { createAction } from "@reduxjs/toolkit"
import { createImmerReducer } from "../../shared/utils/redux.ts"
import { DraftReducer } from "../../shared/utils/redux.ts"
import { CharacterState } from "./characterSlice.ts"
export const setProfession = createAction<{ id: number; instanceId: number }>(
@@ -9,7 +9,7 @@ export const setProfession = createAction<{ id: number; instanceId: number }>(
export const setProfessionVariant = createAction<number>("profession/setProfessionVariant")
export const setCustomProfessionName = createAction<string>("profession/setCustomProfessionName")
export const professionReducer = createImmerReducer<CharacterState>((state, action) => {
export const professionReducer: DraftReducer<CharacterState> = (state, action) => {
if (setProfession.match(action)) {
state.profession.id = action.payload.id
state.profession.instanceId = action.payload.instanceId
@@ -19,4 +19,4 @@ export const professionReducer = createImmerReducer<CharacterState>((state, acti
} else if (setCustomProfessionName.match(action)) {
state.profession.customName = action.payload.length > 0 ? action.payload : undefined
}
})
}
+3 -3
View File
@@ -1,12 +1,12 @@
/* eslint-disable jsdoc/require-jsdoc */
import { createAction } from "@reduxjs/toolkit"
import { createImmerReducer } from "../../shared/utils/redux.ts"
import { DraftReducer } from "../../shared/utils/redux.ts"
import { CharacterState } from "./characterSlice.ts"
export const changeAttributeAdjustmentId = createAction<number>("race/changeAttributeAdjustmentId")
export const raceReducer = createImmerReducer<CharacterState>((state, action) => {
export const raceReducer: DraftReducer<CharacterState> = (state, action) => {
if (changeAttributeAdjustmentId.match(action)) {
state.race.selectedAttributeAdjustmentId = action.payload
}
})
}
@@ -1,12 +1,15 @@
import { ImprovementCost, fromRaw } from "../../shared/domain/adventurePoints/improvementCost.ts"
import { createEmptyDynamicCombatTechnique } from "../../shared/domain/rated/combatTechnique.ts"
import { createRatedSlice } from "./ratedSlice.ts"
const {
create,
createInitial,
getValue,
actions: { incrementAction, decrementAction },
reducer,
// eslint-disable-next-line jsdoc/require-jsdoc
export const {
actions: {
incrementEntry: incrementRangedCombatTechnique,
setEntry: setRangedCombatTechnique,
decrementEntry: decrementRangedCombatTechnique,
},
reducer: rangedCombatTechniquesReducer,
} = createRatedSlice({
namespace: "combatTechniques/ranged",
entityName: "RangedCombatTechnique",
@@ -14,13 +17,5 @@ const {
minValue: 6,
getImprovementCost: (id, database) =>
fromRaw(database.rangedCombatTechniques[id]?.improvement_cost) ?? ImprovementCost.D,
createEmptyRated: createEmptyDynamicCombatTechnique,
})
export {
create as createDynamicRangedCombatTechnique,
createInitial as createInitialDynamicRangedCombatTechnique,
decrementAction as decrementRangedCombatTechnique,
getValue as getRangedCombatTechniqueValue,
incrementAction as incrementRangedCombatTechnique,
reducer as rangedCombatTechniquesReducer,
}
+45 -97
View File
@@ -1,12 +1,8 @@
import { ActionCreatorWithPayload, AnyAction, Draft, createAction } from "@reduxjs/toolkit"
import { ImprovementCost } from "../../shared/domain/adventurePoints/improvementCost.ts"
import {
BoundAdventurePoints,
cachedAdventurePoints,
} from "../../shared/domain/adventurePoints/ratedEntry.ts"
import { RatedDependency } from "../../shared/domain/rated/ratedDependency.ts"
import { Rated, RatedMap, RatedValue } from "../../shared/domain/rated/ratedEntry.ts"
import { Reducer, createImmerReducer } from "../../shared/utils/redux.ts"
import { cachedAdventurePoints } from "../../shared/domain/adventurePoints/ratedEntry.ts"
import { Rated, RatedMap } from "../../shared/domain/rated/ratedEntry.ts"
import { DraftReducer } from "../../shared/utils/redux.ts"
import { CharacterState } from "./characterSlice.ts"
import { DatabaseState } from "./databaseSlice.ts"
@@ -14,37 +10,6 @@ import { DatabaseState } from "./databaseSlice.ts"
* A slice that handles rated entries.
*/
export type RatedSlice<N extends string, E extends string> = {
/**
* Creates a new entry with an initial value if active. The initial adventure
* points cache is calculated from the initial value.
*/
create: (
database: DatabaseState,
id: number,
value: RatedValue,
options?: Partial<{
dependencies: RatedDependency[]
boundAdventurePoints: BoundAdventurePoints[]
}>,
) => Rated
/**
* Creates a new entry with no initial value.
*/
createInitial: (
id: number,
options?: Partial<{
dependencies: RatedDependency[]
boundAdventurePoints: BoundAdventurePoints[]
}>,
) => Rated
/**
* Takes an entry that may not exist (because its instance has not been used
* yet) and returns its value.
*/
getValue: (entry: Rated | undefined) => RatedValue
/**
* The actions that can be dispatched to modify the state.
*/
@@ -52,18 +17,23 @@ export type RatedSlice<N extends string, E extends string> = {
/**
* Increments the value of the entry with the given id.
*/
incrementAction: ActionCreatorWithPayload<number, `${N}/increment${E}`>
incrementEntry: ActionCreatorWithPayload<{ id: number }, `${N}/increment${E}`>
/**
* Sets the value of the entry with the given id.
*/
setEntry: ActionCreatorWithPayload<{ id: number; value: number }, `${N}/set${E}`>
/**
* Decrements the value of the entry with the given id.
*/
decrementAction: ActionCreatorWithPayload<number, `${N}/decrement${E}`>
decrementEntry: ActionCreatorWithPayload<{ id: number }, `${N}/decrement${E}`>
}
/**
* The reducer that handles the actions.
*/
reducer: Reducer<CharacterState, AnyAction, [database: DatabaseState]>
reducer: DraftReducer<CharacterState, AnyAction, [database: DatabaseState]>
}
/**
@@ -76,6 +46,7 @@ export const createRatedSlice = <N extends string, E extends string>(config: {
getState: (state: Draft<CharacterState>) => Draft<RatedMap>
minValue: number
getImprovementCost: (id: number, database: DatabaseState) => ImprovementCost
createEmptyRated: (id: number) => Rated
}): RatedSlice<N, E> => {
const updateCachedAdventurePoints = (entry: Draft<Rated>, database: DatabaseState) => {
entry.cachedAdventurePoints = cachedAdventurePoints(
@@ -87,72 +58,49 @@ export const createRatedSlice = <N extends string, E extends string>(config: {
return entry
}
const create: RatedSlice<N, E>["create"] = (
database,
id,
value,
{ dependencies = [], boundAdventurePoints = [] } = {},
) =>
updateCachedAdventurePoints(
{
id,
value: Math.max(config.minValue, value),
cachedAdventurePoints: {
general: 0,
bound: 0,
},
dependencies,
boundAdventurePoints,
},
database,
)
const createInitial: RatedSlice<N, E>["createInitial"] = (
id,
{ dependencies = [], boundAdventurePoints = [] } = {},
) => ({
id,
value: config.minValue,
cachedAdventurePoints: {
general: 0,
bound: 0,
},
dependencies,
boundAdventurePoints,
})
const getValue: RatedSlice<N, E>["getValue"] = entry => entry?.value ?? config.minValue
const incrementAction = createAction<number, `${N}/increment${E}`>(
const incrementEntry = createAction<{ id: number }, `${N}/increment${E}`>(
`${config.namespace}/increment${config.entityName}`,
)
const decrementAction = createAction<number, `${N}/decrement${E}`>(
const setEntry = createAction<{ id: number; value: number }, `${N}/set${E}`>(
`${config.namespace}/set${config.entityName}`,
)
const decrementEntry = createAction<{ id: number }, `${N}/decrement${E}`>(
`${config.namespace}/decrement${config.entityName}`,
)
const reducer = createImmerReducer(
(state: Draft<CharacterState>, action, database: DatabaseState) => {
if (incrementAction.match(action)) {
const entry = (config.getState(state)[action.payload] ??= createInitial(action.payload))
entry.value++
const reducer: DraftReducer<CharacterState, AnyAction, [database: DatabaseState]> = (
state,
action,
database,
) => {
if (incrementEntry.match(action)) {
const entry = (config.getState(state)[action.payload.id] ??= config.createEmptyRated(
action.payload.id,
))
entry.value++
updateCachedAdventurePoints(entry, database)
} else if (setEntry.match(action)) {
const entry = (config.getState(state)[action.payload.id] ??= config.createEmptyRated(
action.payload.id,
))
if (action.payload.value >= config.minValue) {
entry.value = action.payload.value
updateCachedAdventurePoints(entry, database)
} else if (decrementAction.match(action)) {
const entry = config.getState(state)[action.payload]
if (entry !== undefined && entry.value > config.minValue) {
entry.value--
updateCachedAdventurePoints(entry, database)
}
}
},
)
} else if (decrementEntry.match(action)) {
const entry = config.getState(state)[action.payload.id]
if (entry !== undefined && entry.value > config.minValue) {
entry.value--
updateCachedAdventurePoints(entry, database)
}
}
}
return {
create,
createInitial,
getValue,
actions: {
incrementAction,
decrementAction,
incrementEntry,
decrementEntry,
setEntry,
},
reducer,
}
+3 -3
View File
@@ -1,18 +1,17 @@
import { ImprovementCost, fromRaw } from "../../shared/domain/adventurePoints/improvementCost.ts"
import { registerOrUnregisterPrerequisitesOfSpellworkAsDependencies } from "../../shared/domain/dependencies/fullPrerequisiteRegistrationAsDependencyForType.ts"
import { createIdentifierObject } from "../../shared/domain/identifier.ts"
import { createEmptyDynamicSpell } from "../../shared/domain/rated/spell.ts"
import { createActivatableRatedWithEnhancementsSlice } from "./activatableRatedWithEnhancementsSlice.ts"
// eslint-disable-next-line jsdoc/require-jsdoc
export const {
create: createDynamicRitual,
createInitial: createInitialDynamicRitual,
getValue: getRitualValue,
actions: {
addAction: addRitual,
removeAction: removeRitual,
incrementAction: incrementRitual,
decrementAction: decrementRitual,
setAction: setRitual,
},
reducer: ritualsReducer,
} = createActivatableRatedWithEnhancementsSlice({
@@ -27,4 +26,5 @@ export const {
createIdentifierObject: id => createIdentifierObject("Ritual", id),
registerOrUnregisterPrerequisitesAsDependencies:
registerOrUnregisterPrerequisitesOfSpellworkAsDependencies,
createEmptyActivatableRatedWithEnhancements: createEmptyDynamicSpell,
})
+3 -3
View File
@@ -1,7 +1,7 @@
/* eslint-disable jsdoc/require-jsdoc */
import { createAction } from "@reduxjs/toolkit"
import { OptionalRuleIdentifier } from "../../shared/domain/identifier.ts"
import { createImmerReducer } from "../../shared/utils/redux.ts"
import { DraftReducer } from "../../shared/utils/redux.ts"
import { CharacterState } from "./characterSlice.ts"
export const switchIncludeAllPublications = createAction("rules/switchIncludeAllPublications")
@@ -12,7 +12,7 @@ export const changeOptionalRuleOption = createAction<{ id: number; option: numbe
"rules/changeOptionalRuleOption",
)
export const rulesReducer = createImmerReducer<CharacterState>((state, action) => {
export const rulesReducer: DraftReducer<CharacterState> = (state, action) => {
if (switchIncludeAllPublications.match(action)) {
state.rules.includeAllPublications = !state.rules.includeAllPublications
} else if (switchIncludePublication.match(action)) {
@@ -54,4 +54,4 @@ export const rulesReducer = createImmerReducer<CharacterState>((state, action) =
;(state.rules.optionalRules[action.payload.id]!.options ??= [])[0] = action.payload.option
}
}
})
}
+6 -15
View File
@@ -1,12 +1,11 @@
import { ImprovementCost, fromRaw } from "../../shared/domain/adventurePoints/improvementCost.ts"
import { createEmptyDynamicSkill } from "../../shared/domain/rated/skill.ts"
import { createRatedSlice } from "./ratedSlice.ts"
const {
create,
createInitial,
getValue,
actions: { incrementAction, decrementAction },
reducer,
// eslint-disable-next-line jsdoc/require-jsdoc
export const {
actions: { incrementEntry: incrementSkill, decrementEntry: decrementSkill, setEntry: setSkill },
reducer: skillsReducer,
} = createRatedSlice({
namespace: "skills",
entityName: "Skill",
@@ -14,13 +13,5 @@ const {
minValue: 0,
getImprovementCost: (id, database) =>
fromRaw(database.skills[id]?.improvement_cost) ?? ImprovementCost.D,
createEmptyRated: createEmptyDynamicSkill,
})
export {
create as createDynamicSkill,
createInitial as createInitialDynamicSkill,
decrementAction as decrementSkill,
incrementAction as incrementSkill,
getValue as skillValue,
reducer as skillsReducer,
}
+3 -3
View File
@@ -1,18 +1,17 @@
import { ImprovementCost, fromRaw } from "../../shared/domain/adventurePoints/improvementCost.ts"
import { registerOrUnregisterPrerequisitesOfSpellworkAsDependencies } from "../../shared/domain/dependencies/fullPrerequisiteRegistrationAsDependencyForType.ts"
import { createIdentifierObject } from "../../shared/domain/identifier.ts"
import { createEmptyDynamicSpell } from "../../shared/domain/rated/spell.ts"
import { createActivatableRatedWithEnhancementsSlice } from "./activatableRatedWithEnhancementsSlice.ts"
// eslint-disable-next-line jsdoc/require-jsdoc
export const {
create: createDynamicSpell,
createInitial: createInitialDynamicSpell,
getValue: getSpellValue,
actions: {
addAction: addSpell,
removeAction: removeSpell,
incrementAction: incrementSpell,
decrementAction: decrementSpell,
setAction: setSpell,
},
reducer: spellsReducer,
} = createActivatableRatedWithEnhancementsSlice({
@@ -27,4 +26,5 @@ export const {
createIdentifierObject: id => createIdentifierObject("Ritual", id),
registerOrUnregisterPrerequisitesAsDependencies:
registerOrUnregisterPrerequisitesOfSpellworkAsDependencies,
createEmptyActivatableRatedWithEnhancements: createEmptyDynamicSpell,
})
@@ -14,12 +14,12 @@ export type TinyActivatableSlice<N extends string, E extends string> = {
/**
* Adds the entry with the given id.
*/
addAction: ActionCreatorWithPayload<number, `${N}/add${E}`>
addAction: ActionCreatorWithPayload<{ id: number }, `${N}/add${E}`>
/**
* Remove the entry with the given id.
*/
removeAction: ActionCreatorWithPayload<number, `${N}/remove${E}`>
removeAction: ActionCreatorWithPayload<{ id: number }, `${N}/remove${E}`>
}
/**
@@ -36,25 +36,25 @@ export const createTinyActivatableSlice = <N extends string, E extends string>(c
entityName: E
getState: (state: Draft<CharacterState>) => Draft<TinyActivatableMap>
}): TinyActivatableSlice<N, E> => {
const addAction = createAction<number, `${N}/add${E}`>(
const addAction = createAction<{ id: number }, `${N}/add${E}`>(
`${config.namespace}/add${config.entityName}`,
)
const removeAction = createAction<number, `${N}/remove${E}`>(
const removeAction = createAction<{ id: number }, `${N}/remove${E}`>(
`${config.namespace}/remove${config.entityName}`,
)
const reducer = createImmerReducer((state: Draft<CharacterState>, action) => {
const focusedState = config.getState(state)
if (addAction.match(action)) {
if (!Object.hasOwn(focusedState, action.payload)) {
focusedState[action.payload] = {
id: action.payload,
if (!Object.hasOwn(focusedState, action.payload.id)) {
focusedState[action.payload.id] = {
id: action.payload.id,
active: true,
}
}
} else if (removeAction.match(action)) {
if (Object.hasOwn(focusedState, action.payload)) {
delete focusedState[action.payload]
if (Object.hasOwn(focusedState, action.payload.id)) {
delete focusedState[action.payload.id]
}
}
})
@@ -21,7 +21,7 @@ export const RecommendedReference: FC<Props> = props => {
<div className="recommended-ref">
<div className="unrec">
<div className="icon" />
<div className="name">{translate("showfrequency.unfamiliarspells")}</div>
<div className="name">{translate("Unfamiliar Spells")}</div>
</div>
</div>
)
@@ -32,16 +32,16 @@ export const RecommendedReference: FC<Props> = props => {
{strongly === true ? (
<div className="strongly-recommended">
<div className="icon" />
<div className="name">{translate("showfrequency.stronglyrecommended")}</div>
<div className="name">{translate("Strongly Recommended")}</div>
</div>
) : null}
<div className="rec">
<div className="icon" />
<div className="name">{translate("showfrequency.common")}</div>
<div className="name">{translate("Common")}</div>
</div>
<div className="unrec">
<div className="icon" />
<div className="name">{translate("showfrequency.uncommon")}</div>
<div className="name">{translate("Uncommon")}</div>
</div>
</div>
)
@@ -1,11 +1,12 @@
import assert from "node:assert/strict"
import { describe, it } from "node:test"
import { AdventurePointsCache } from "./cache.ts"
import { ImprovementCost } from "./improvementCost.ts"
import { RatedAdventurePointsCache, cachedAdventurePoints } from "./ratedEntry.ts"
import { cachedAdventurePoints } from "./ratedEntry.ts"
describe("cachedAdventurePoints", () => {
it("returns the calculated value if bound adventure points are only granted at at least the current rating", () => {
assert.deepEqual<RatedAdventurePointsCache>(
assert.deepEqual<AdventurePointsCache>(
cachedAdventurePoints(9, 8, [{ rating: 9, adventurePoints: 10 }], ImprovementCost.E),
{
general: 15,
@@ -15,7 +16,7 @@ describe("cachedAdventurePoints", () => {
})
it("returns the split calculated value if bound adventure points have to be considered", () => {
assert.deepEqual<RatedAdventurePointsCache>(
assert.deepEqual<AdventurePointsCache>(
cachedAdventurePoints(9, 8, [{ rating: 8, adventurePoints: 10 }], ImprovementCost.E),
{
general: 5,
+18 -49
View File
@@ -1,4 +1,9 @@
import { range } from "../../utils/array.ts"
import {
AdventurePointsCache,
addAdventurePointsCaches,
emptyAdventurePointsCache,
} from "./cache.ts"
import { ImprovementCost, adventurePointsForIncrement } from "./improvementCost.ts"
/**
@@ -6,7 +11,7 @@ import { ImprovementCost, adventurePointsForIncrement } from "./improvementCost.
* entry. They dont effect the costs of the rating at the time of granting, so
* the rating at which they have been granted is stored as well.
*/
export type BoundAdventurePoints = {
export type BoundAdventurePointsForRated = {
/**
* The rating at which they have been granted. If the adventure points have
* been granted when the entry was not active yet, the rating is `undefined`.
@@ -19,35 +24,8 @@ export type BoundAdventurePoints = {
adventurePoints: number
}
/**
* The accumulated used adventure points value of all value increases. It is
* split by used bound and used general adventure points.
*/
export type RatedAdventurePointsCache = {
/**
* The used general adventure points.
*/
general: number
/**
* The used bound adventure points.
*/
bound: number
}
/**
* Adds two caches together.
*/
const addCache = (
cache1: RatedAdventurePointsCache,
cache2: RatedAdventurePointsCache,
): RatedAdventurePointsCache => ({
general: cache1.general + cache2.general,
bound: cache1.bound + cache2.bound,
})
const groupBoundAdventurePointsByRating = (
boundAdventurePoints: BoundAdventurePoints[],
boundAdventurePoints: BoundAdventurePointsForRated[],
): ReadonlyMap<number | "activation", number> =>
boundAdventurePoints.reduce((map, { rating: boundRating, adventurePoints }) => {
const key = boundRating ?? "activation"
@@ -60,7 +38,7 @@ const accumulateCache = (
initialApplicableBoundKey: number | "activation",
boundByValue: ReadonlyMap<number | "activation", number>,
ic: ImprovementCost,
): RatedAdventurePointsCache => {
): AdventurePointsCache => {
const { usedGeneral, usedBound } = range(startValue, endValue).reduce(
(acc, currentValue) => {
const costForStep = adventurePointsForIncrement(ic, currentValue - 1)
@@ -97,14 +75,11 @@ const accumulateCache = (
export const cachedAdventurePoints = (
value: number,
minValue: number,
boundAdventurePoints: BoundAdventurePoints[],
boundAdventurePoints: BoundAdventurePointsForRated[],
ic: ImprovementCost,
): RatedAdventurePointsCache => {
): AdventurePointsCache => {
if (minValue >= value) {
return {
general: 0,
bound: 0,
}
return emptyAdventurePointsCache
} else {
const boundByValue = groupBoundAdventurePointsByRating(boundAdventurePoints)
return accumulateCache(minValue + 1, value, minValue, boundByValue, ic)
@@ -118,14 +93,11 @@ export const cachedAdventurePoints = (
*/
export const cachedAdventurePointsForActivatable = (
value: number | undefined,
boundAdventurePoints: BoundAdventurePoints[],
boundAdventurePoints: BoundAdventurePointsForRated[],
ic: ImprovementCost,
): RatedAdventurePointsCache => {
): AdventurePointsCache => {
if (value === undefined) {
return {
general: 0,
bound: 0,
}
return emptyAdventurePointsCache
} else {
const boundByValue = groupBoundAdventurePointsByRating(boundAdventurePoints)
return accumulateCache(0, value, "activation", boundByValue, ic)
@@ -140,23 +112,20 @@ export const cachedAdventurePointsForActivatable = (
*/
export const cachedAdventurePointsForActivatableWithEnhancements = (
value: number | undefined,
boundAdventurePoints: BoundAdventurePoints[],
boundAdventurePoints: BoundAdventurePointsForRated[],
ic: ImprovementCost,
enhancements: number[],
getAdventurePointsModifierForEnhancement: (enhancementId: number) => number,
): RatedAdventurePointsCache => {
): AdventurePointsCache => {
if (value === undefined) {
return {
general: 0,
bound: 0,
}
return emptyAdventurePointsCache
} else {
const boundByValue = groupBoundAdventurePointsByRating(boundAdventurePoints)
const enhancementAdventurePoints = enhancements.reduce(
(acc, enhancementId) => acc + getAdventurePointsModifierForEnhancement(enhancementId),
0,
)
return addCache(accumulateCache(0, value, "activation", boundByValue, ic), {
return addAdventurePointsCaches(accumulateCache(0, value, "activation", boundByValue, ic), {
general: enhancementAdventurePoints,
bound: 0,
})
+8 -3
View File
@@ -17,10 +17,12 @@ export const baseCharacter: Character = {
id: 1,
variantId: 1,
selectedAttributeAdjustmentId: 1,
dependencies: [],
},
culture: {
id: 1,
isCulturalPackageApplied: false,
dependencies: [],
},
profession: {
id: 1,
@@ -29,12 +31,14 @@ export const baseCharacter: Character = {
rules: {
includeAllPublications: false,
includePublications: [],
publicationDependencies: [],
focusRules: {},
optionalRules: {},
},
states: {},
personalData: {
sex: { type: "Male" },
sexDependencies: [],
socialStatus: {
dependencies: [],
},
@@ -69,7 +73,6 @@ export const baseCharacter: Character = {
krallenkettenzauber: {},
liturgicalStyleSpecialAbilities: {},
lycantropicGifts: {},
magicalRunes: {},
magicalSigns: {},
magicalSpecialAbilities: {},
magicalTraditions: {},
@@ -93,6 +96,8 @@ export const baseCharacter: Character = {
weaponEnchantments: {},
},
attributes: {},
magicalPrimaryAttributeDependencies: [],
blessedPrimaryAttributeDependencies: [],
derivedCharacteristics: {
lifePoints: {
purchased: 0,
@@ -127,12 +132,11 @@ export const baseCharacter: Character = {
animistPowers: {},
geodeRituals: {},
zibiljaRituals: {},
magicalRunes: {},
},
blessings: [],
liturgicalChants: {},
ceremonies: {},
magicalPrimaryAttributeDependencies: [],
blessedPrimaryAttributeDependencies: [],
// items: {}
// hitZoneArmors: {}
purse: {
@@ -143,4 +147,5 @@ export const baseCharacter: Character = {
},
// creatures: {}
// pact: {}
pactDependencies: [],
}
+31 -7
View File
@@ -1,9 +1,11 @@
import { ActivatableMap, TinyActivatableMap } from "./activatable/activatableEntry.ts"
import { Color } from "./color.ts"
import { CultureDependency } from "./culture.ts"
import { Energy, EnergyWithBuyBack } from "./energy.ts"
import { Pact } from "./pact.ts"
import { Pact, PactDependency } from "./pact.ts"
import { Purse } from "./purse.ts"
import { RatedDependency } from "./rated/ratedDependency.ts"
import { RaceDependency } from "./race.ts"
import { PrimaryAttributeDependency } from "./rated/primaryAttribute.ts"
import {
ActivatableRatedMap,
ActivatableRatedWithEnhancementsMap,
@@ -11,8 +13,9 @@ import {
} from "./rated/ratedEntry.ts"
import { FocusRuleInstance } from "./rules/focusRule.ts"
import { OptionalRuleInstance } from "./rules/optionalRule.ts"
import { Sex } from "./sex.ts"
import { Sex, SexDependency } from "./sex.ts"
import { SocialStatusDependency } from "./socialStatus.ts"
import { PublicationDependency } from "./sources/publicationDependency.ts"
import { StateInstance } from "./state.ts"
/**
@@ -81,6 +84,11 @@ export type Character = {
* The identifier of the attribute adjustment that has been selected from the race.
*/
selectedAttributeAdjustmentId: number
/**
* Dependencies on the selected race.
*/
dependencies: RaceDependency[]
}
/**
@@ -96,6 +104,11 @@ export type Character = {
* Describes whether the cultural package has been applied when creating the character.
*/
isCulturalPackageApplied: boolean
/**
* Dependencies on the selected culture.
*/
dependencies: CultureDependency[]
}
/**
@@ -152,6 +165,11 @@ export type Character = {
*/
includePublications: number[]
/**
* Dependencies on certain publications.
*/
publicationDependencies: PublicationDependency[]
/**
* A map of focus rules that may be active for the character.
*/
@@ -183,6 +201,11 @@ export type Character = {
*/
sex: Sex
/**
* Dependencies on the characters sex.
*/
sexDependencies: SexDependency[]
/**
* The family names and/or family members.
*/
@@ -285,7 +308,6 @@ export type Character = {
krallenkettenzauber: ActivatableMap
liturgicalStyleSpecialAbilities: ActivatableMap
lycantropicGifts: ActivatableMap
magicalRunes: ActivatableMap
magicalSigns: ActivatableMap
magicalSpecialAbilities: ActivatableMap
magicalTraditions: ActivatableMap
@@ -311,6 +333,9 @@ export type Character = {
attributes: RatedMap
magicalPrimaryAttributeDependencies: PrimaryAttributeDependency[]
blessedPrimaryAttributeDependencies: PrimaryAttributeDependency[]
derivedCharacteristics: {
lifePoints: Energy
arcaneEnergy: EnergyWithBuyBack
@@ -338,18 +363,17 @@ export type Character = {
animistPowers: ActivatableRatedMap
geodeRituals: ActivatableRatedMap
zibiljaRituals: ActivatableRatedMap
magicalRunes: ActivatableRatedMap
}
blessings: TinyActivatableMap
liturgicalChants: ActivatableRatedWithEnhancementsMap
ceremonies: ActivatableRatedWithEnhancementsMap
magicalPrimaryAttributeDependencies: RatedDependency[]
blessedPrimaryAttributeDependencies: RatedDependency[]
// items: {}
// hitZoneArmors: {}
purse: Purse
// creatures: {}
pact?: Pact
pactDependencies: PactDependency[]
}
+27
View File
@@ -1,4 +1,5 @@
import { Culture } from "optolith-database-schema/types/Culture"
import { ActivatableIdentifier } from "optolith-database-schema/types/_IdentifierGroup"
import { TranslateMap } from "../utils/translate.ts"
/**
@@ -12,3 +13,29 @@ export const getCulture = (cultures: Record<number, Culture>, id: number): Cultu
*/
export const getFullCultureName = (translateMap: TranslateMap, culture: Culture): string =>
translateMap(culture.translations)?.name ?? ""
/**
* A dependency on a culture.
*/
export type CultureDependency = Readonly<{
/**
* The identifier of the dependency source.
*/
sourceId: ActivatableIdentifier
/**
* The top-level index of the prerequisite. If the prerequisite is part of a
* group or disjunction, this is the index of the group or disjunction.
*/
index: number
/**
* Is the source prerequisite part of a prerequisite disjunction?
*/
isPartOfDisjunction: boolean
/**
* The required culture's identifier.
*/
id: number
}>
+4
View File
@@ -0,0 +1,4 @@
/**
* Returns the carrying capacity of a character with the given strength value.
*/
export const getCarryingCapacity = (strength: number): number => strength * 2
+657 -111
View File
@@ -1,4 +1,5 @@
import * as ID from "optolith-database-schema/types/_Identifier"
import { assertExhaustive } from "../utils/typeSafety.ts"
// TODO: Update for new identifier mappings
@@ -143,6 +144,17 @@ export enum SkillIdentifier {
Clothworking = 59,
}
/**
* Used identifiers of skill groups.
*/
export enum SkillGroupIdentifier {
Physical = 1,
Social = 2,
Nature = 3,
Knowledge = 4,
Craft = 5,
}
/**
* Used identifiers of advantages.
*/
@@ -154,28 +166,28 @@ export enum AdvantageIdentifier {
Luck = 14,
ExceptionalSkill = 16,
ExceptionalCombatTechnique = 17,
IncreasedAstralPower = 23,
IncreasedKarmaPoints = 24,
IncreasedLifePoints = 25,
IncreasedSpirit = 26,
IncreasedToughness = 27,
ImmunityToPoison = 28,
ImmunityToDisease = 29,
MagicalAttunement = 32,
Rich = 36,
SociallyAdaptable = 40,
InspireConfidence = 46,
WeaponAptitude = 47,
Spellcaster = 50,
Unyielding = 54, // Eisern
LargeSpellSelection = 58,
HatredOf = 68,
Prediger = 77,
Visionaer = 78,
ZahlreichePredigten = 79,
ZahlreicheVisionen = 80,
LeichterGang = 92,
Einkommen = 99,
IncreasedAstralPower = 20,
IncreasedKarmaPoints = 21,
IncreasedLifePoints = 22,
IncreasedSpirit = 23,
IncreasedToughness = 24,
ImmunityToPoison = 25,
ImmunityToDisease = 26,
MagicalAttunement = 29,
Rich = 33,
SociallyAdaptable = 37,
InspireConfidence = 43,
WeaponAptitude = 44,
Spellcaster = 47,
Unyielding = 51, // Eisern
HatredOf = 55,
LargeSpellSelection = 66,
LeichterGang = 85,
Preacher = 91,
Visionary = 92,
ManySermons = 93,
ManyVisions = 94,
Einkommen = 129,
}
/**
@@ -186,28 +198,28 @@ export enum DisadvantageIdentifier {
AfraidOf = 1,
Poor = 2,
Slow = 4,
NoFlyingBalm = 17,
NoFamiliar = 18,
MagicalRestriction = 24,
DecreasedArcanePower = 26,
DecreasedKarmaPoints = 27,
DecreasedLifePoints = 28,
DecreasedSpirit = 29,
DecreasedToughness = 30,
BadLuck = 31,
PersonalityFlaw = 33,
Principles = 34,
BadHabit = 36,
NegativeTrait = 37, // Schlechte Eigenschaft
Stigma = 45,
Deaf = 47, // Taub
Incompetent = 48,
Obligations = 50, // Verpflichtungen
Maimed = 51, // Verstümmelt
NoFlyingBalm = 14,
NoFamiliar = 15,
MagicalRestriction = 21,
DecreasedArcanePower = 23,
DecreasedKarmaPoints = 24,
DecreasedLifePoints = 25,
DecreasedSpirit = 26,
DecreasedToughness = 27,
BadLuck = 28,
PersonalityFlaw = 30,
Principles = 31,
BadHabit = 33,
NegativeTrait = 34, // Schlechte Eigenschaft
Stigma = 42,
Deaf = 44, // Taub
Incompetent = 45,
Obligations = 47, // Verpflichtungen
Maimed = 48, // Verstümmelt
BrittleBones = 56, // Gläsern
SmallSpellSelection = 59,
WenigePredigten = 72,
WenigeVisionen = 73,
SmallSpellSelection = 64,
FewerSermons = 70,
FewerVisions = 71,
}
/**
@@ -228,8 +240,12 @@ export enum CombatSpecialAbilityIdentifier {
* Used identifiers of general special abilities.
*/
export enum GeneralSpecialAbilityIdentifier {
SkillSpecialization = 9,
CraftInstruments = 17,
Hunter = 18,
Literacy = 27,
Language = 29,
LanguageSpecialization = 30,
FireEater = 53,
}
@@ -239,6 +255,7 @@ export enum GeneralSpecialAbilityIdentifier {
export enum MagicalSpecialAbilityIdentifier {
PropertyKnowledge = 3,
GrosseMeditation = 12,
Adaptation = 18,
Imitationszauberei = 51,
}
@@ -269,6 +286,21 @@ export enum MagicalTraditionIdentifier {
Runenschoepfer = 24,
}
/**
* Used identifiers of magical special abilities.
*/
export enum MagicStyleSpecialAbilityIdentifier {
ScholarDesMagierkollegsZuHoningen = 24,
MadaschwesternStil = 55,
}
/**
* Used identifiers of pact gifts.
*/
export enum PactGiftIdentifier {
DunklesAbbildDerBuendnisgabe = 3,
}
/**
* Used identifiers of aspects.
*/
@@ -305,6 +337,18 @@ export enum BlessedTraditionIdentifier {
}
type TagPropertyOptions = {
Publication: ID.PublicationIdentifier
ExperienceLevel: ID.ExperienceLevelIdentifier
CoreRule: ID.CoreRuleIdentifier
FocusRule: ID.FocusRuleIdentifier
Subject: ID.SubjectIdentifier
OptionalRule: ID.OptionalRuleIdentifier
Race: ID.RaceIdentifier
Culture: ID.CultureIdentifier
Profession: ID.ProfessionIdentifier
ProfessionVariant: ID.ProfessionVariantIdentifier
Curriculum: ID.CurriculumIdentifier
Guideline: ID.GuidelineIdentifier
Advantage: ID.AdvantageIdentifier
Disadvantage: ID.DisadvantageIdentifier
GeneralSpecialAbility: ID.GeneralSpecialAbilityIdentifier
@@ -336,6 +380,7 @@ type TagPropertyOptions = {
MagicalTradition: ID.MagicalTraditionIdentifier
BlessedTradition: ID.BlessedTraditionIdentifier
PactGift: ID.PactGiftIdentifier
VampiricGift: ID.VampiricGiftIdentifier
SikaryanDrainSpecialAbility: ID.SikaryanDrainSpecialAbilityIdentifier
LycantropicGift: ID.LycantropicGiftIdentifier
SkillStyleSpecialAbility: ID.SkillStyleSpecialAbilityIdentifier
@@ -353,13 +398,25 @@ type TagPropertyOptions = {
ChronicleEnchantment: ID.ChronicleEnchantmentIdentifier
Krallenkettenzauber: ID.KrallenkettenzauberIdentifier
Trinkhornzauber: ID.TrinkhornzauberIdentifier
MagicalRune: ID.MagicalRuneIdentifier
MagicalSign: ID.MagicalSignIdentifier
Language: ID.LanguageIdentifier
Script: ID.ScriptIdentifier
Continent: ID.ContinentIdentifier
SocialStatus: ID.SocialStatusIdentifier
Attribute: ID.AttributeIdentifier
Skill: ID.SkillIdentifier
SkillGroup: ID.SkillGroupIdentifier
CloseCombatTechnique: ID.CloseCombatTechniqueIdentifier
RangedCombatTechnique: ID.RangedCombatTechniqueIdentifier
Cantrip: ID.CantripIdentifier
Spell: ID.SpellIdentifier
Ritual: ID.RitualIdentifier
Cantrip: ID.CantripIdentifier
Property: ID.PropertyIdentifier
LiturgicalChant: ID.LiturgicalChantIdentifier
Ceremony: ID.CeremonyIdentifier
Blessing: ID.BlessingIdentifier
Aspect: ID.AspectIdentifier
Curse: ID.CurseIdentifier
ElvenMagicalSong: ID.ElvenMagicalSongIdentifier
DominationRitual: ID.DominationRitualIdentifier
@@ -369,9 +426,12 @@ type TagPropertyOptions = {
AnimistPower: ID.AnimistPowerIdentifier
GeodeRitual: ID.GeodeRitualIdentifier
ZibiljaRitual: ID.ZibiljaRitualIdentifier
Blessing: ID.BlessingIdentifier
LiturgicalChant: ID.LiturgicalChantIdentifier
Ceremony: ID.CeremonyIdentifier
AnimalType: ID.AnimalTypeIdentifier
TargetCategory: ID.TargetCategoryIdentifier
General: ID.GeneralIdentifier
Element: ID.ElementIdentifier
AnimalShapeSize: ID.AnimalShapeSizeIdentifier
Patron: ID.PatronIdentifier
Ammunition: ID.AmmunitionIdentifier
Animal: ID.AnimalIdentifier
AnimalCare: ID.AnimalCareIdentifier
@@ -401,44 +461,56 @@ type TagPropertyOptions = {
Vehicle: ID.VehicleIdentifier
Weapon: ID.WeaponIdentifier
WeaponAccessory: ID.WeaponAccessoryIdentifier
Reach: ID.ReachIdentifier
PatronCategory: ID.PatronCategoryIdentifier
PersonalityTrait: ID.PersonalityTraitIdentifier
HairColor: ID.HairColorIdentifier
EyeColor: ID.EyeColorIdentifier
PactCategory: ID.PactCategoryIdentifier
PactDomain: ID.PactDomainIdentifier
AnimistTribe: ID.AnimistTribeIdentifier
Influence: ID.InfluenceIdentifier
Condition: ID.ConditionIdentifier
State: ID.StateIdentifier
Disease: ID.DiseaseIdentifier
SexPractice: ID.SexPracticeIdentifier
TradeSecret: ID.TradeSecretIdentifier
AnimalShape: ID.AnimalShapeIdentifier
ArcaneBardTradition: ID.ArcaneBardTraditionIdentifier
ArcaneDancerTradition: ID.ArcaneDancerTraditionIdentifier
}
// prettier-ignore
const TagPropertyMap: {
[K in keyof TagPropertyOptions]: (id: number) => TagPropertyOptions[K]
} = {
Publication: id => ({ tag: "Publication", publication: id }),
ExperienceLevel: id => ({ tag: "ExperienceLevel", experience_level: id }),
CoreRule: id => ({ tag: "CoreRule", core_rule: id }),
FocusRule: id => ({ tag: "FocusRule", focus_rule: id }),
Subject: id => ({ tag: "Subject", subject: id }),
OptionalRule: id => ({ tag: "OptionalRule", optional_rule: id }),
Race: id => ({ tag: "Race", race: id }),
Culture: id => ({ tag: "Culture", culture: id }),
Profession: id => ({ tag: "Profession", profession: id }),
ProfessionVariant: id => ({ tag: "ProfessionVariant", profession_variant: id }),
Curriculum: id => ({ tag: "Curriculum", curriculum: id }),
Guideline: id => ({ tag: "Guideline", guideline: id }),
Advantage: id => ({ tag: "Advantage", advantage: id }),
Disadvantage: id => ({ tag: "Disadvantage", disadvantage: id }),
GeneralSpecialAbility: id => ({ tag: "GeneralSpecialAbility", general_special_ability: id }),
FatePointSpecialAbility: id => ({
tag: "FatePointSpecialAbility",
fate_point_special_ability: id,
}),
FatePointSpecialAbility: id => ({ tag: "FatePointSpecialAbility", fate_point_special_ability: id }),
CombatSpecialAbility: id => ({ tag: "CombatSpecialAbility", combat_special_ability: id }),
MagicalSpecialAbility: id => ({ tag: "MagicalSpecialAbility", magical_special_ability: id }),
StaffEnchantment: id => ({ tag: "StaffEnchantment", staff_enchantment: id }),
FamiliarSpecialAbility: id => ({ tag: "FamiliarSpecialAbility", familiar_special_ability: id }),
KarmaSpecialAbility: id => ({ tag: "KarmaSpecialAbility", karma_special_ability: id }),
ProtectiveWardingCircleSpecialAbility: id => ({
tag: "ProtectiveWardingCircleSpecialAbility",
protective_warding_circle_special_ability: id,
}),
CombatStyleSpecialAbility: id => ({
tag: "CombatStyleSpecialAbility",
combat_style_special_ability: id,
}),
AdvancedCombatSpecialAbility: id => ({
tag: "AdvancedCombatSpecialAbility",
advanced_combat_special_ability: id,
}),
ProtectiveWardingCircleSpecialAbility: id => ({ tag: "ProtectiveWardingCircleSpecialAbility", protective_warding_circle_special_ability: id }),
CombatStyleSpecialAbility: id => ({ tag: "CombatStyleSpecialAbility", combat_style_special_ability: id }),
AdvancedCombatSpecialAbility: id => ({ tag: "AdvancedCombatSpecialAbility", advanced_combat_special_ability: id }),
CommandSpecialAbility: id => ({ tag: "CommandSpecialAbility", command_special_ability: id }),
MagicStyleSpecialAbility: id => ({
tag: "MagicStyleSpecialAbility",
magic_style_special_ability: id,
}),
AdvancedMagicalSpecialAbility: id => ({
tag: "AdvancedMagicalSpecialAbility",
advanced_magical_special_ability: id,
}),
MagicStyleSpecialAbility: id => ({ tag: "MagicStyleSpecialAbility", magic_style_special_ability: id }),
AdvancedMagicalSpecialAbility: id => ({ tag: "AdvancedMagicalSpecialAbility", advanced_magical_special_ability: id }),
SpellSwordEnchantment: id => ({ tag: "SpellSwordEnchantment", spell_sword_enchantment: id }),
DaggerRitual: id => ({ tag: "DaggerRitual", dagger_ritual: id }),
InstrumentEnchantment: id => ({ tag: "InstrumentEnchantment", instrument_enchantment: id }),
@@ -447,45 +519,25 @@ const TagPropertyMap: {
WandEnchantment: id => ({ tag: "WandEnchantment", wand_enchantment: id }),
BrawlingSpecialAbility: id => ({ tag: "BrawlingSpecialAbility", brawling_special_ability: id }),
AncestorGlyph: id => ({ tag: "AncestorGlyph", ancestor_glyph: id }),
CeremonialItemSpecialAbility: id => ({
tag: "CeremonialItemSpecialAbility",
ceremonial_item_special_ability: id,
}),
CeremonialItemSpecialAbility: id => ({ tag: "CeremonialItemSpecialAbility", ceremonial_item_special_ability: id }),
Sermon: id => ({ tag: "Sermon", sermon: id }),
LiturgicalStyleSpecialAbility: id => ({
tag: "LiturgicalStyleSpecialAbility",
liturgical_style_special_ability: id,
}),
AdvancedKarmaSpecialAbility: id => ({
tag: "AdvancedKarmaSpecialAbility",
advanced_karma_special_ability: id,
}),
LiturgicalStyleSpecialAbility: id => ({ tag: "LiturgicalStyleSpecialAbility", liturgical_style_special_ability: id }),
AdvancedKarmaSpecialAbility: id => ({ tag: "AdvancedKarmaSpecialAbility", advanced_karma_special_ability: id }),
Vision: id => ({ tag: "Vision", vision: id }),
MagicalTradition: id => ({ tag: "MagicalTradition", magical_tradition: id }),
BlessedTradition: id => ({ tag: "BlessedTradition", blessed_tradition: id }),
PactGift: id => ({ tag: "PactGift", pact_gift: id }),
SikaryanDrainSpecialAbility: id => ({
tag: "SikaryanDrainSpecialAbility",
sikaryan_drain_special_ability: id,
}),
VampiricGift: id => ({ tag: "VampiricGift", vampiric_gift: id }),
SikaryanDrainSpecialAbility: id => ({ tag: "SikaryanDrainSpecialAbility", sikaryan_drain_special_ability: id }),
LycantropicGift: id => ({ tag: "LycantropicGift", lycantropic_gift: id }),
SkillStyleSpecialAbility: id => ({
tag: "SkillStyleSpecialAbility",
skill_style_special_ability: id,
}),
AdvancedSkillSpecialAbility: id => ({
tag: "AdvancedSkillSpecialAbility",
advanced_skill_special_ability: id,
}),
SkillStyleSpecialAbility: id => ({ tag: "SkillStyleSpecialAbility", skill_style_special_ability: id }),
AdvancedSkillSpecialAbility: id => ({ tag: "AdvancedSkillSpecialAbility", advanced_skill_special_ability: id }),
ArcaneOrbEnchantment: id => ({ tag: "ArcaneOrbEnchantment", arcane_orb_enchantment: id }),
CauldronEnchantment: id => ({ tag: "CauldronEnchantment", cauldron_enchantment: id }),
FoolsHatEnchantment: id => ({ tag: "FoolsHatEnchantment", fools_hat_enchantment: id }),
ToyEnchantment: id => ({ tag: "ToyEnchantment", toy_enchantment: id }),
BowlEnchantment: id => ({ tag: "BowlEnchantment", bowl_enchantment: id }),
FatePointSexSpecialAbility: id => ({
tag: "FatePointSexSpecialAbility",
fate_point_sex_special_ability: id,
}),
FatePointSexSpecialAbility: id => ({ tag: "FatePointSexSpecialAbility", fate_point_sex_special_ability: id }),
SexSpecialAbility: id => ({ tag: "SexSpecialAbility", sex_special_ability: id }),
WeaponEnchantment: id => ({ tag: "WeaponEnchantment", weapon_enchantment: id }),
SickleRitual: id => ({ tag: "SickleRitual", sickle_ritual: id }),
@@ -493,13 +545,25 @@ const TagPropertyMap: {
ChronicleEnchantment: id => ({ tag: "ChronicleEnchantment", chronicle_enchantment: id }),
Krallenkettenzauber: id => ({ tag: "Krallenkettenzauber", krallenkettenzauber: id }),
Trinkhornzauber: id => ({ tag: "Trinkhornzauber", trinkhornzauber: id }),
MagicalRune: id => ({ tag: "MagicalRune", magical_rune: id }),
MagicalSign: id => ({ tag: "MagicalSign", magical_sign: id }),
Language: id => ({ tag: "Language", language: id }),
Script: id => ({ tag: "Script", script: id }),
Continent: id => ({ tag: "Continent", continent: id }),
SocialStatus: id => ({ tag: "SocialStatus", social_status: id }),
Attribute: id => ({ tag: "Attribute", attribute: id }),
Skill: id => ({ tag: "Skill", skill: id }),
SkillGroup: id => ({ tag: "SkillGroup", skill_group: id }),
CloseCombatTechnique: id => ({ tag: "CloseCombatTechnique", close_combat_technique: id }),
RangedCombatTechnique: id => ({ tag: "RangedCombatTechnique", ranged_combat_technique: id }),
Cantrip: id => ({ tag: "Cantrip", cantrip: id }),
Spell: id => ({ tag: "Spell", spell: id }),
Ritual: id => ({ tag: "Ritual", ritual: id }),
Cantrip: id => ({ tag: "Cantrip", cantrip: id }),
Property: id => ({ tag: "Property", property: id }),
LiturgicalChant: id => ({ tag: "LiturgicalChant", liturgical_chant: id }),
Ceremony: id => ({ tag: "Ceremony", ceremony: id }),
Blessing: id => ({ tag: "Blessing", blessing: id }),
Aspect: id => ({ tag: "Aspect", aspect: id }),
Curse: id => ({ tag: "Curse", curse: id }),
ElvenMagicalSong: id => ({ tag: "ElvenMagicalSong", elven_magical_song: id }),
DominationRitual: id => ({ tag: "DominationRitual", domination_ritual: id }),
@@ -509,9 +573,12 @@ const TagPropertyMap: {
AnimistPower: id => ({ tag: "AnimistPower", animist_power: id }),
GeodeRitual: id => ({ tag: "GeodeRitual", geode_ritual: id }),
ZibiljaRitual: id => ({ tag: "ZibiljaRitual", zibilja_ritual: id }),
Blessing: id => ({ tag: "Blessing", blessing: id }),
LiturgicalChant: id => ({ tag: "LiturgicalChant", liturgical_chant: id }),
Ceremony: id => ({ tag: "Ceremony", ceremony: id }),
AnimalType: id => ({ tag: "AnimalType", animal_type: id }),
TargetCategory: id => ({ tag: "TargetCategory", target_category: id }),
General: id => ({ tag: "General", general: id }),
Element: id => ({ tag: "Element", element: id }),
AnimalShapeSize: id => ({ tag: "AnimalShapeSize", animal_shape_size: id }),
Patron: id => ({ tag: "Patron", patron: id }),
Ammunition: id => ({ tag: "Ammunition", ammunition: id }),
Animal: id => ({ tag: "Animal", animal: id }),
AnimalCare: id => ({ tag: "AnimalCare", animal_care: id }),
@@ -524,14 +591,8 @@ const TagPropertyMap: {
Elixir: id => ({ tag: "Elixir", elixir: id }),
EquipmentOfBlessedOnes: id => ({ tag: "EquipmentOfBlessedOnes", equipment_of_blessed_ones: id }),
GemOrPreciousStone: id => ({ tag: "GemOrPreciousStone", gem_or_precious_stone: id }),
IlluminationLightSource: id => ({
tag: "IlluminationLightSource",
illumination_light_source: id,
}),
IlluminationRefillsOrSupplies: id => ({
tag: "IlluminationRefillsOrSupplies",
illumination_refills_or_supplies: id,
}),
IlluminationLightSource: id => ({ tag: "IlluminationLightSource", illumination_light_source: id }),
IlluminationRefillsOrSupplies: id => ({ tag: "IlluminationRefillsOrSupplies", illumination_refills_or_supplies: id }),
Jewelry: id => ({ tag: "Jewelry", jewelry: id }),
Liebesspielzeug: id => ({ tag: "Liebesspielzeug", liebesspielzeug: id }),
LuxuryGood: id => ({ tag: "LuxuryGood", luxury_good: id }),
@@ -547,6 +608,23 @@ const TagPropertyMap: {
Vehicle: id => ({ tag: "Vehicle", vehicle: id }),
Weapon: id => ({ tag: "Weapon", weapon: id }),
WeaponAccessory: id => ({ tag: "WeaponAccessory", weapon_accessory: id }),
Reach: id => ({ tag: "Reach", reach: id }),
PatronCategory: id => ({ tag: "PatronCategory", patron_category: id }),
PersonalityTrait: id => ({ tag: "PersonalityTrait", personality_trait: id }),
HairColor: id => ({ tag: "HairColor", hair_color: id }),
EyeColor: id => ({ tag: "EyeColor", eye_color: id }),
PactCategory: id => ({ tag: "PactCategory", pact_category: id }),
PactDomain: id => ({ tag: "PactDomain", pact_domain: id }),
AnimistTribe: id => ({ tag: "AnimistTribe", animist_tribe: id }),
Influence: id => ({ tag: "Influence", influence: id }),
Condition: id => ({ tag: "Condition", condition: id }),
State: id => ({ tag: "State", state: id }),
Disease: id => ({ tag: "Disease", disease: id }),
SexPractice: id => ({ tag: "SexPractice", sex_practice: id }),
TradeSecret: id => ({ tag: "TradeSecret", trade_secret: id }),
AnimalShape: id => ({ tag: "AnimalShape", animal_shape: id }),
ArcaneBardTradition: id => ({ tag: "ArcaneBardTradition", arcane_bard_tradition: id }),
ArcaneDancerTradition: id => ({ tag: "ArcaneDancerTradition", arcane_dancer_tradition: id }),
}
/**
@@ -556,3 +634,471 @@ export const createIdentifierObject = <T extends keyof TagPropertyOptions>(
tag: T,
id: number,
): TagPropertyOptions[T] => TagPropertyMap[tag](id)
/**
* Returns a function by a type name that creates an identifier object from a
* plain identifier.
*/
export const getCreateIdentifierObject = <T extends keyof TagPropertyOptions>(
tag: T,
): ((id: number) => TagPropertyOptions[T]) => TagPropertyMap[tag]
/**
* Splits an identifier object into its type name and plain identifier.
*/
export const splitIdentifierObject = <T extends keyof TagPropertyOptions>(
obj: TagPropertyOptions[T],
): [tag: T, id: number] => {
// prettier-ignore
switch (obj.tag) {
case "Publication": return [obj.tag as T, obj.publication]
case "ExperienceLevel": return [obj.tag as T, obj.experience_level]
case "CoreRule": return [obj.tag as T, obj.core_rule]
case "FocusRule": return [obj.tag as T, obj.focus_rule]
case "Subject": return [obj.tag as T, obj.subject]
case "OptionalRule": return [obj.tag as T, obj.optional_rule]
case "Race": return [obj.tag as T, obj.race]
case "Culture": return [obj.tag as T, obj.culture]
case "Profession": return [obj.tag as T, obj.profession]
case "ProfessionVariant": return [obj.tag as T, obj.profession_variant]
case "Curriculum": return [obj.tag as T, obj.curriculum]
case "Guideline": return [obj.tag as T, obj.guideline]
case "Advantage": return [obj.tag as T, obj.advantage]
case "Disadvantage": return [obj.tag as T, obj.disadvantage]
case "GeneralSpecialAbility": return [obj.tag as T, obj.general_special_ability]
case "FatePointSpecialAbility": return [obj.tag as T, obj.fate_point_special_ability]
case "CombatSpecialAbility": return [obj.tag as T, obj.combat_special_ability]
case "MagicalSpecialAbility": return [obj.tag as T, obj.magical_special_ability]
case "StaffEnchantment": return [obj.tag as T, obj.staff_enchantment]
case "FamiliarSpecialAbility": return [obj.tag as T, obj.familiar_special_ability]
case "KarmaSpecialAbility": return [obj.tag as T, obj.karma_special_ability]
case "ProtectiveWardingCircleSpecialAbility": return [obj.tag as T, obj.protective_warding_circle_special_ability]
case "CombatStyleSpecialAbility": return [obj.tag as T, obj.combat_style_special_ability]
case "AdvancedCombatSpecialAbility": return [obj.tag as T, obj.advanced_combat_special_ability]
case "CommandSpecialAbility": return [obj.tag as T, obj.command_special_ability]
case "MagicStyleSpecialAbility": return [obj.tag as T, obj.magic_style_special_ability]
case "AdvancedMagicalSpecialAbility": return [obj.tag as T, obj.advanced_magical_special_ability]
case "SpellSwordEnchantment": return [obj.tag as T, obj.spell_sword_enchantment]
case "DaggerRitual": return [obj.tag as T, obj.dagger_ritual]
case "InstrumentEnchantment": return [obj.tag as T, obj.instrument_enchantment]
case "AttireEnchantment": return [obj.tag as T, obj.attire_enchantment]
case "OrbEnchantment": return [obj.tag as T, obj.orb_enchantment]
case "WandEnchantment": return [obj.tag as T, obj.wand_enchantment]
case "BrawlingSpecialAbility": return [obj.tag as T, obj.brawling_special_ability]
case "AncestorGlyph": return [obj.tag as T, obj.ancestor_glyph]
case "CeremonialItemSpecialAbility": return [obj.tag as T, obj.ceremonial_item_special_ability]
case "Sermon": return [obj.tag as T, obj.sermon]
case "LiturgicalStyleSpecialAbility": return [obj.tag as T, obj.liturgical_style_special_ability]
case "AdvancedKarmaSpecialAbility": return [obj.tag as T, obj.advanced_karma_special_ability]
case "Vision": return [obj.tag as T, obj.vision]
case "MagicalTradition": return [obj.tag as T, obj.magical_tradition]
case "BlessedTradition": return [obj.tag as T, obj.blessed_tradition]
case "PactGift": return [obj.tag as T, obj.pact_gift]
case "VampiricGift": return [obj.tag as T, obj.vampiric_gift]
case "SikaryanDrainSpecialAbility": return [obj.tag as T, obj.sikaryan_drain_special_ability]
case "LycantropicGift": return [obj.tag as T, obj.lycantropic_gift]
case "SkillStyleSpecialAbility": return [obj.tag as T, obj.skill_style_special_ability]
case "AdvancedSkillSpecialAbility": return [obj.tag as T, obj.advanced_skill_special_ability]
case "ArcaneOrbEnchantment": return [obj.tag as T, obj.arcane_orb_enchantment]
case "CauldronEnchantment": return [obj.tag as T, obj.cauldron_enchantment]
case "FoolsHatEnchantment": return [obj.tag as T, obj.fools_hat_enchantment]
case "ToyEnchantment": return [obj.tag as T, obj.toy_enchantment]
case "BowlEnchantment": return [obj.tag as T, obj.bowl_enchantment]
case "FatePointSexSpecialAbility": return [obj.tag as T, obj.fate_point_sex_special_ability]
case "SexSpecialAbility": return [obj.tag as T, obj.sex_special_ability]
case "WeaponEnchantment": return [obj.tag as T, obj.weapon_enchantment]
case "SickleRitual": return [obj.tag as T, obj.sickle_ritual]
case "RingEnchantment": return [obj.tag as T, obj.ring_enchantment]
case "ChronicleEnchantment": return [obj.tag as T, obj.chronicle_enchantment]
case "Krallenkettenzauber": return [obj.tag as T, obj.krallenkettenzauber]
case "Trinkhornzauber": return [obj.tag as T, obj.trinkhornzauber]
case "MagicalRune": return [obj.tag as T, obj.magical_rune]
case "MagicalSign": return [obj.tag as T, obj.magical_sign]
case "Language": return [obj.tag as T, obj.language]
case "Script": return [obj.tag as T, obj.script]
case "Continent": return [obj.tag as T, obj.continent]
case "SocialStatus": return [obj.tag as T, obj.social_status]
case "Attribute": return [obj.tag as T, obj.attribute]
case "Skill": return [obj.tag as T, obj.skill]
case "SkillGroup": return [obj.tag as T, obj.skill_group]
case "CloseCombatTechnique": return [obj.tag as T, obj.close_combat_technique]
case "RangedCombatTechnique": return [obj.tag as T, obj.ranged_combat_technique]
case "Spell": return [obj.tag as T, obj.spell]
case "Ritual": return [obj.tag as T, obj.ritual]
case "Cantrip": return [obj.tag as T, obj.cantrip]
case "Property": return [obj.tag as T, obj.property]
case "LiturgicalChant": return [obj.tag as T, obj.liturgical_chant]
case "Ceremony": return [obj.tag as T, obj.ceremony]
case "Blessing": return [obj.tag as T, obj.blessing]
case "Aspect": return [obj.tag as T, obj.aspect]
case "Curse": return [obj.tag as T, obj.curse]
case "ElvenMagicalSong": return [obj.tag as T, obj.elven_magical_song]
case "DominationRitual": return [obj.tag as T, obj.domination_ritual]
case "MagicalMelody": return [obj.tag as T, obj.magical_melody]
case "MagicalDance": return [obj.tag as T, obj.magical_dance]
case "JesterTrick": return [obj.tag as T, obj.jester_trick]
case "AnimistPower": return [obj.tag as T, obj.animist_power]
case "GeodeRitual": return [obj.tag as T, obj.geode_ritual]
case "ZibiljaRitual": return [obj.tag as T, obj.zibilja_ritual]
case "AnimalType": return [obj.tag as T, obj.animal_type]
case "TargetCategory": return [obj.tag as T, obj.target_category]
case "General": return [obj.tag as T, obj.general]
case "Element": return [obj.tag as T, obj.element]
case "AnimalShapeSize": return [obj.tag as T, obj.animal_shape_size]
case "Patron": return [obj.tag as T, obj.patron]
case "Ammunition": return [obj.tag as T, obj.ammunition]
case "Animal": return [obj.tag as T, obj.animal]
case "AnimalCare": return [obj.tag as T, obj.animal_care]
case "Armor": return [obj.tag as T, obj.armor]
case "BandageOrRemedy": return [obj.tag as T, obj.bandage_or_remedy]
case "Book": return [obj.tag as T, obj.book]
case "CeremonialItem": return [obj.tag as T, obj.ceremonial_item]
case "Clothes": return [obj.tag as T, obj.clothes]
case "Container": return [obj.tag as T, obj.container]
case "Elixir": return [obj.tag as T, obj.elixir]
case "EquipmentOfBlessedOnes": return [obj.tag as T, obj.equipment_of_blessed_ones]
case "GemOrPreciousStone": return [obj.tag as T, obj.gem_or_precious_stone]
case "IlluminationLightSource": return [obj.tag as T, obj.illumination_light_source]
case "IlluminationRefillsOrSupplies": return [obj.tag as T, obj.illumination_refills_or_supplies]
case "Jewelry": return [obj.tag as T, obj.jewelry]
case "Liebesspielzeug": return [obj.tag as T, obj.liebesspielzeug]
case "LuxuryGood": return [obj.tag as T, obj.luxury_good]
case "MagicalArtifact": return [obj.tag as T, obj.magical_artifact]
case "MusicalInstrument": return [obj.tag as T, obj.musical_instrument]
case "OrienteeringAid": return [obj.tag as T, obj.orienteering_aid]
case "Poison": return [obj.tag as T, obj.poison]
case "RopeOrChain": return [obj.tag as T, obj.rope_or_chain]
case "Stationary": return [obj.tag as T, obj.stationary]
case "ThievesTool": return [obj.tag as T, obj.thieves_tool]
case "ToolOfTheTrade": return [obj.tag as T, obj.tool_of_the_trade]
case "TravelGearOrTool": return [obj.tag as T, obj.travel_gear_or_tool]
case "Vehicle": return [obj.tag as T, obj.vehicle]
case "Weapon": return [obj.tag as T, obj.weapon]
case "WeaponAccessory": return [obj.tag as T, obj.weapon_accessory]
case "Reach": return [obj.tag as T, obj.reach]
case "PatronCategory": return [obj.tag as T, obj.patron_category]
case "PersonalityTrait": return [obj.tag as T, obj.personality_trait]
case "HairColor": return [obj.tag as T, obj.hair_color]
case "EyeColor": return [obj.tag as T, obj.eye_color]
case "PactCategory": return [obj.tag as T, obj.pact_category]
case "PactDomain": return [obj.tag as T, obj.pact_domain]
case "AnimistTribe": return [obj.tag as T, obj.animist_tribe]
case "Influence": return [obj.tag as T, obj.influence]
case "Condition": return [obj.tag as T, obj.condition]
case "State": return [obj.tag as T, obj.state]
case "Disease": return [obj.tag as T, obj.disease]
case "SexPractice": return [obj.tag as T, obj.sex_practice]
case "TradeSecret": return [obj.tag as T, obj.trade_secret]
case "AnimalShape": return [obj.tag as T, obj.animal_shape]
case "ArcaneBardTradition": return [obj.tag as T, obj.arcane_bard_tradition]
case "ArcaneDancerTradition": return [obj.tag as T, obj.arcane_dancer_tradition]
default: return assertExhaustive(obj)
}
}
/**
* Checks if two identifiers are equal.
*/
export const equalsIdentifier = <T extends TagPropertyOptions[keyof TagPropertyOptions]>(
a: T,
b: T,
): boolean => {
// prettier-ignore
switch (a.tag) {
case "Publication": return b.tag === "Publication" && a.publication === b.publication
case "ExperienceLevel": return b.tag === "ExperienceLevel" && a.experience_level === b.experience_level
case "CoreRule": return b.tag === "CoreRule" && a.core_rule === b.core_rule
case "FocusRule": return b.tag === "FocusRule" && a.focus_rule === b.focus_rule
case "Subject": return b.tag === "Subject" && a.subject === b.subject
case "OptionalRule": return b.tag === "OptionalRule" && a.optional_rule === b.optional_rule
case "Race": return b.tag === "Race" && a.race === b.race
case "Culture": return b.tag === "Culture" && a.culture === b.culture
case "Profession": return b.tag === "Profession" && a.profession === b.profession
case "ProfessionVariant": return b.tag === "ProfessionVariant" && a.profession_variant === b.profession_variant
case "Curriculum": return b.tag === "Curriculum" && a.curriculum === b.curriculum
case "Guideline": return b.tag === "Guideline" && a.guideline === b.guideline
case "Advantage": return b.tag === "Advantage" && a.advantage === b.advantage
case "Disadvantage": return b.tag === "Disadvantage" && a.disadvantage === b.disadvantage
case "GeneralSpecialAbility": return b.tag === "GeneralSpecialAbility" && a.general_special_ability === b.general_special_ability
case "FatePointSpecialAbility": return b.tag === "FatePointSpecialAbility" && a.fate_point_special_ability === b.fate_point_special_ability
case "CombatSpecialAbility": return b.tag === "CombatSpecialAbility" && a.combat_special_ability === b.combat_special_ability
case "MagicalSpecialAbility": return b.tag === "MagicalSpecialAbility" && a.magical_special_ability === b.magical_special_ability
case "StaffEnchantment": return b.tag === "StaffEnchantment" && a.staff_enchantment === b.staff_enchantment
case "FamiliarSpecialAbility": return b.tag === "FamiliarSpecialAbility" && a.familiar_special_ability === b.familiar_special_ability
case "KarmaSpecialAbility": return b.tag === "KarmaSpecialAbility" && a.karma_special_ability === b.karma_special_ability
case "ProtectiveWardingCircleSpecialAbility": return b.tag === "ProtectiveWardingCircleSpecialAbility" && a.protective_warding_circle_special_ability === b.protective_warding_circle_special_ability
case "CombatStyleSpecialAbility": return b.tag === "CombatStyleSpecialAbility" && a.combat_style_special_ability === b.combat_style_special_ability
case "AdvancedCombatSpecialAbility": return b.tag === "AdvancedCombatSpecialAbility" && a.advanced_combat_special_ability === b.advanced_combat_special_ability
case "CommandSpecialAbility": return b.tag === "CommandSpecialAbility" && a.command_special_ability === b.command_special_ability
case "MagicStyleSpecialAbility": return b.tag === "MagicStyleSpecialAbility" && a.magic_style_special_ability === b.magic_style_special_ability
case "AdvancedMagicalSpecialAbility": return b.tag === "AdvancedMagicalSpecialAbility" && a.advanced_magical_special_ability === b.advanced_magical_special_ability
case "SpellSwordEnchantment": return b.tag === "SpellSwordEnchantment" && a.spell_sword_enchantment === b.spell_sword_enchantment
case "DaggerRitual": return b.tag === "DaggerRitual" && a.dagger_ritual === b.dagger_ritual
case "InstrumentEnchantment": return b.tag === "InstrumentEnchantment" && a.instrument_enchantment === b.instrument_enchantment
case "AttireEnchantment": return b.tag === "AttireEnchantment" && a.attire_enchantment === b.attire_enchantment
case "OrbEnchantment": return b.tag === "OrbEnchantment" && a.orb_enchantment === b.orb_enchantment
case "WandEnchantment": return b.tag === "WandEnchantment" && a.wand_enchantment === b.wand_enchantment
case "BrawlingSpecialAbility": return b.tag === "BrawlingSpecialAbility" && a.brawling_special_ability === b.brawling_special_ability
case "AncestorGlyph": return b.tag === "AncestorGlyph" && a.ancestor_glyph === b.ancestor_glyph
case "CeremonialItemSpecialAbility": return b.tag === "CeremonialItemSpecialAbility" && a.ceremonial_item_special_ability === b.ceremonial_item_special_ability
case "Sermon": return b.tag === "Sermon" && a.sermon === b.sermon
case "LiturgicalStyleSpecialAbility": return b.tag === "LiturgicalStyleSpecialAbility" && a.liturgical_style_special_ability === b.liturgical_style_special_ability
case "AdvancedKarmaSpecialAbility": return b.tag === "AdvancedKarmaSpecialAbility" && a.advanced_karma_special_ability === b.advanced_karma_special_ability
case "Vision": return b.tag === "Vision" && a.vision === b.vision
case "MagicalTradition": return b.tag === "MagicalTradition" && a.magical_tradition === b.magical_tradition
case "BlessedTradition": return b.tag === "BlessedTradition" && a.blessed_tradition === b.blessed_tradition
case "PactGift": return b.tag === "PactGift" && a.pact_gift === b.pact_gift
case "VampiricGift": return b.tag === "VampiricGift" && a.vampiric_gift === b.vampiric_gift
case "SikaryanDrainSpecialAbility": return b.tag === "SikaryanDrainSpecialAbility" && a.sikaryan_drain_special_ability === b.sikaryan_drain_special_ability
case "LycantropicGift": return b.tag === "LycantropicGift" && a.lycantropic_gift === b.lycantropic_gift
case "SkillStyleSpecialAbility": return b.tag === "SkillStyleSpecialAbility" && a.skill_style_special_ability === b.skill_style_special_ability
case "AdvancedSkillSpecialAbility": return b.tag === "AdvancedSkillSpecialAbility" && a.advanced_skill_special_ability === b.advanced_skill_special_ability
case "ArcaneOrbEnchantment": return b.tag === "ArcaneOrbEnchantment" && a.arcane_orb_enchantment === b.arcane_orb_enchantment
case "CauldronEnchantment": return b.tag === "CauldronEnchantment" && a.cauldron_enchantment === b.cauldron_enchantment
case "FoolsHatEnchantment": return b.tag === "FoolsHatEnchantment" && a.fools_hat_enchantment === b.fools_hat_enchantment
case "ToyEnchantment": return b.tag === "ToyEnchantment" && a.toy_enchantment === b.toy_enchantment
case "BowlEnchantment": return b.tag === "BowlEnchantment" && a.bowl_enchantment === b.bowl_enchantment
case "FatePointSexSpecialAbility": return b.tag === "FatePointSexSpecialAbility" && a.fate_point_sex_special_ability === b.fate_point_sex_special_ability
case "SexSpecialAbility": return b.tag === "SexSpecialAbility" && a.sex_special_ability === b.sex_special_ability
case "WeaponEnchantment": return b.tag === "WeaponEnchantment" && a.weapon_enchantment === b.weapon_enchantment
case "SickleRitual": return b.tag === "SickleRitual" && a.sickle_ritual === b.sickle_ritual
case "RingEnchantment": return b.tag === "RingEnchantment" && a.ring_enchantment === b.ring_enchantment
case "ChronicleEnchantment": return b.tag === "ChronicleEnchantment" && a.chronicle_enchantment === b.chronicle_enchantment
case "Krallenkettenzauber": return b.tag === "Krallenkettenzauber" && a.krallenkettenzauber === b.krallenkettenzauber
case "Trinkhornzauber": return b.tag === "Trinkhornzauber" && a.trinkhornzauber === b.trinkhornzauber
case "MagicalRune": return b.tag === "MagicalRune" && a.magical_rune === b.magical_rune
case "MagicalSign": return b.tag === "MagicalSign" && a.magical_sign === b.magical_sign
case "Language": return b.tag === "Language" && a.language === b.language
case "Script": return b.tag === "Script" && a.script === b.script
case "Continent": return b.tag === "Continent" && a.continent === b.continent
case "SocialStatus": return b.tag === "SocialStatus" && a.social_status === b.social_status
case "Attribute": return b.tag === "Attribute" && a.attribute === b.attribute
case "Skill": return b.tag === "Skill" && a.skill === b.skill
case "SkillGroup": return b.tag === "SkillGroup" && a.skill_group === b.skill_group
case "CloseCombatTechnique": return b.tag === "CloseCombatTechnique" && a.close_combat_technique === b.close_combat_technique
case "RangedCombatTechnique": return b.tag === "RangedCombatTechnique" && a.ranged_combat_technique === b.ranged_combat_technique
case "Spell": return b.tag === "Spell" && a.spell === b.spell
case "Ritual": return b.tag === "Ritual" && a.ritual === b.ritual
case "Cantrip": return b.tag === "Cantrip" && a.cantrip === b.cantrip
case "Property": return b.tag === "Property" && a.property === b.property
case "LiturgicalChant": return b.tag === "LiturgicalChant" && a.liturgical_chant === b.liturgical_chant
case "Ceremony": return b.tag === "Ceremony" && a.ceremony === b.ceremony
case "Blessing": return b.tag === "Blessing" && a.blessing === b.blessing
case "Aspect": return b.tag === "Aspect" && a.aspect === b.aspect
case "Curse": return b.tag === "Curse" && a.curse === b.curse
case "ElvenMagicalSong": return b.tag === "ElvenMagicalSong" && a.elven_magical_song === b.elven_magical_song
case "DominationRitual": return b.tag === "DominationRitual" && a.domination_ritual === b.domination_ritual
case "MagicalMelody": return b.tag === "MagicalMelody" && a.magical_melody === b.magical_melody
case "MagicalDance": return b.tag === "MagicalDance" && a.magical_dance === b.magical_dance
case "JesterTrick": return b.tag === "JesterTrick" && a.jester_trick === b.jester_trick
case "AnimistPower": return b.tag === "AnimistPower" && a.animist_power === b.animist_power
case "GeodeRitual": return b.tag === "GeodeRitual" && a.geode_ritual === b.geode_ritual
case "ZibiljaRitual": return b.tag === "ZibiljaRitual" && a.zibilja_ritual === b.zibilja_ritual
case "AnimalType": return b.tag === "AnimalType" && a.animal_type === b.animal_type
case "TargetCategory": return b.tag === "TargetCategory" && a.target_category === b.target_category
case "General": return b.tag === "General" && a.general === b.general
case "Element": return b.tag === "Element" && a.element === b.element
case "AnimalShapeSize": return b.tag === "AnimalShapeSize" && a.animal_shape_size === b.animal_shape_size
case "Patron": return b.tag === "Patron" && a.patron === b.patron
case "Ammunition": return b.tag === "Ammunition" && a.ammunition === b.ammunition
case "Animal": return b.tag === "Animal" && a.animal === b.animal
case "AnimalCare": return b.tag === "AnimalCare" && a.animal_care === b.animal_care
case "Armor": return b.tag === "Armor" && a.armor === b.armor
case "BandageOrRemedy": return b.tag === "BandageOrRemedy" && a.bandage_or_remedy === b.bandage_or_remedy
case "Book": return b.tag === "Book" && a.book === b.book
case "CeremonialItem": return b.tag === "CeremonialItem" && a.ceremonial_item === b.ceremonial_item
case "Clothes": return b.tag === "Clothes" && a.clothes === b.clothes
case "Container": return b.tag === "Container" && a.container === b.container
case "Elixir": return b.tag === "Elixir" && a.elixir === b.elixir
case "EquipmentOfBlessedOnes": return b.tag === "EquipmentOfBlessedOnes" && a.equipment_of_blessed_ones === b.equipment_of_blessed_ones
case "GemOrPreciousStone": return b.tag === "GemOrPreciousStone" && a.gem_or_precious_stone === b.gem_or_precious_stone
case "IlluminationLightSource": return b.tag === "IlluminationLightSource" && a.illumination_light_source === b.illumination_light_source
case "IlluminationRefillsOrSupplies": return b.tag === "IlluminationRefillsOrSupplies" && a.illumination_refills_or_supplies === b.illumination_refills_or_supplies
case "Jewelry": return b.tag === "Jewelry" && a.jewelry === b.jewelry
case "Liebesspielzeug": return b.tag === "Liebesspielzeug" && a.liebesspielzeug === b.liebesspielzeug
case "LuxuryGood": return b.tag === "LuxuryGood" && a.luxury_good === b.luxury_good
case "MagicalArtifact": return b.tag === "MagicalArtifact" && a.magical_artifact === b.magical_artifact
case "MusicalInstrument": return b.tag === "MusicalInstrument" && a.musical_instrument === b.musical_instrument
case "OrienteeringAid": return b.tag === "OrienteeringAid" && a.orienteering_aid === b.orienteering_aid
case "Poison": return b.tag === "Poison" && a.poison === b.poison
case "RopeOrChain": return b.tag === "RopeOrChain" && a.rope_or_chain === b.rope_or_chain
case "Stationary": return b.tag === "Stationary" && a.stationary === b.stationary
case "ThievesTool": return b.tag === "ThievesTool" && a.thieves_tool === b.thieves_tool
case "ToolOfTheTrade": return b.tag === "ToolOfTheTrade" && a.tool_of_the_trade === b.tool_of_the_trade
case "TravelGearOrTool": return b.tag === "TravelGearOrTool" && a.travel_gear_or_tool === b.travel_gear_or_tool
case "Vehicle": return b.tag === "Vehicle" && a.vehicle === b.vehicle
case "Weapon": return b.tag === "Weapon" && a.weapon === b.weapon
case "WeaponAccessory": return b.tag === "WeaponAccessory" && a.weapon_accessory === b.weapon_accessory
case "Reach": return b.tag === "Reach" && a.reach === b.reach
case "PatronCategory": return b.tag === "PatronCategory" && a.patron_category === b.patron_category
case "PersonalityTrait": return b.tag === "PersonalityTrait" && a.personality_trait === b.personality_trait
case "HairColor": return b.tag === "HairColor" && a.hair_color === b.hair_color
case "EyeColor": return b.tag === "EyeColor" && a.eye_color === b.eye_color
case "PactCategory": return b.tag === "PactCategory" && a.pact_category === b.pact_category
case "PactDomain": return b.tag === "PactDomain" && a.pact_domain === b.pact_domain
case "AnimistTribe": return b.tag === "AnimistTribe" && a.animist_tribe === b.animist_tribe
case "Influence": return b.tag === "Influence" && a.influence === b.influence
case "Condition": return b.tag === "Condition" && a.condition === b.condition
case "State": return b.tag === "State" && a.state === b.state
case "Disease": return b.tag === "Disease" && a.disease === b.disease
case "SexPractice": return b.tag === "SexPractice" && a.sex_practice === b.sex_practice
case "TradeSecret": return b.tag === "TradeSecret" && a.trade_secret === b.trade_secret
case "AnimalShape": return b.tag === "AnimalShape" && a.animal_shape === b.animal_shape
case "ArcaneBardTradition": return b.tag === "ArcaneBardTradition" && a.arcane_bard_tradition === b.arcane_bard_tradition
case "ArcaneDancerTradition": return b.tag === "ArcaneDancerTradition" && a.arcane_dancer_tradition === b.arcane_dancer_tradition
default: return assertExhaustive(a)
}
}
/**
* Converts an identifier object to a string.
*/
export const identifierObjectToString = (
obj: TagPropertyOptions[keyof TagPropertyOptions],
): string => {
// prettier-ignore
switch (obj.tag) {
case "Publication": return `${obj.tag}_${obj.publication}`
case "ExperienceLevel": return `${obj.tag}_${obj.experience_level}`
case "CoreRule": return `${obj.tag}_${obj.core_rule}`
case "FocusRule": return `${obj.tag}_${obj.focus_rule}`
case "Subject": return `${obj.tag}_${obj.subject}`
case "OptionalRule": return `${obj.tag}_${obj.optional_rule}`
case "Race": return `${obj.tag}_${obj.race}`
case "Culture": return `${obj.tag}_${obj.culture}`
case "Profession": return `${obj.tag}_${obj.profession}`
case "ProfessionVariant": return `${obj.tag}_${obj.profession_variant}`
case "Curriculum": return `${obj.tag}_${obj.curriculum}`
case "Guideline": return `${obj.tag}_${obj.guideline}`
case "Advantage": return `${obj.tag}_${obj.advantage}`
case "Disadvantage": return `${obj.tag}_${obj.disadvantage}`
case "GeneralSpecialAbility": return `${obj.tag}_${obj.general_special_ability}`
case "FatePointSpecialAbility": return `${obj.tag}_${obj.fate_point_special_ability}`
case "CombatSpecialAbility": return `${obj.tag}_${obj.combat_special_ability}`
case "MagicalSpecialAbility": return `${obj.tag}_${obj.magical_special_ability}`
case "StaffEnchantment": return `${obj.tag}_${obj.staff_enchantment}`
case "FamiliarSpecialAbility": return `${obj.tag}_${obj.familiar_special_ability}`
case "KarmaSpecialAbility": return `${obj.tag}_${obj.karma_special_ability}`
case "ProtectiveWardingCircleSpecialAbility": return `${obj.tag}_${obj.protective_warding_circle_special_ability}`
case "CombatStyleSpecialAbility": return `${obj.tag}_${obj.combat_style_special_ability}`
case "AdvancedCombatSpecialAbility": return `${obj.tag}_${obj.advanced_combat_special_ability}`
case "CommandSpecialAbility": return `${obj.tag}_${obj.command_special_ability}`
case "MagicStyleSpecialAbility": return `${obj.tag}_${obj.magic_style_special_ability}`
case "AdvancedMagicalSpecialAbility": return `${obj.tag}_${obj.advanced_magical_special_ability}`
case "SpellSwordEnchantment": return `${obj.tag}_${obj.spell_sword_enchantment}`
case "DaggerRitual": return `${obj.tag}_${obj.dagger_ritual}`
case "InstrumentEnchantment": return `${obj.tag}_${obj.instrument_enchantment}`
case "AttireEnchantment": return `${obj.tag}_${obj.attire_enchantment}`
case "OrbEnchantment": return `${obj.tag}_${obj.orb_enchantment}`
case "WandEnchantment": return `${obj.tag}_${obj.wand_enchantment}`
case "BrawlingSpecialAbility": return `${obj.tag}_${obj.brawling_special_ability}`
case "AncestorGlyph": return `${obj.tag}_${obj.ancestor_glyph}`
case "CeremonialItemSpecialAbility": return `${obj.tag}_${obj.ceremonial_item_special_ability}`
case "Sermon": return `${obj.tag}_${obj.sermon}`
case "LiturgicalStyleSpecialAbility": return `${obj.tag}_${obj.liturgical_style_special_ability}`
case "AdvancedKarmaSpecialAbility": return `${obj.tag}_${obj.advanced_karma_special_ability}`
case "Vision": return `${obj.tag}_${obj.vision}`
case "MagicalTradition": return `${obj.tag}_${obj.magical_tradition}`
case "BlessedTradition": return `${obj.tag}_${obj.blessed_tradition}`
case "PactGift": return `${obj.tag}_${obj.pact_gift}`
case "VampiricGift": return `${obj.tag}_${obj.vampiric_gift}`
case "SikaryanDrainSpecialAbility": return `${obj.tag}_${obj.sikaryan_drain_special_ability}`
case "LycantropicGift": return `${obj.tag}_${obj.lycantropic_gift}`
case "SkillStyleSpecialAbility": return `${obj.tag}_${obj.skill_style_special_ability}`
case "AdvancedSkillSpecialAbility": return `${obj.tag}_${obj.advanced_skill_special_ability}`
case "ArcaneOrbEnchantment": return `${obj.tag}_${obj.arcane_orb_enchantment}`
case "CauldronEnchantment": return `${obj.tag}_${obj.cauldron_enchantment}`
case "FoolsHatEnchantment": return `${obj.tag}_${obj.fools_hat_enchantment}`
case "ToyEnchantment": return `${obj.tag}_${obj.toy_enchantment}`
case "BowlEnchantment": return `${obj.tag}_${obj.bowl_enchantment}`
case "FatePointSexSpecialAbility": return `${obj.tag}_${obj.fate_point_sex_special_ability}`
case "SexSpecialAbility": return `${obj.tag}_${obj.sex_special_ability}`
case "WeaponEnchantment": return `${obj.tag}_${obj.weapon_enchantment}`
case "SickleRitual": return `${obj.tag}_${obj.sickle_ritual}`
case "RingEnchantment": return `${obj.tag}_${obj.ring_enchantment}`
case "ChronicleEnchantment": return `${obj.tag}_${obj.chronicle_enchantment}`
case "Krallenkettenzauber": return `${obj.tag}_${obj.krallenkettenzauber}`
case "Trinkhornzauber": return `${obj.tag}_${obj.trinkhornzauber}`
case "MagicalRune": return `${obj.tag}_${obj.magical_rune}`
case "MagicalSign": return `${obj.tag}_${obj.magical_sign}`
case "Language": return `${obj.tag}_${obj.language}`
case "Script": return `${obj.tag}_${obj.script}`
case "Continent": return `${obj.tag}_${obj.continent}`
case "SocialStatus": return `${obj.tag}_${obj.social_status}`
case "Attribute": return `${obj.tag}_${obj.attribute}`
case "Skill": return `${obj.tag}_${obj.skill}`
case "SkillGroup": return `${obj.tag}_${obj.skill_group}`
case "CloseCombatTechnique": return `${obj.tag}_${obj.close_combat_technique}`
case "RangedCombatTechnique": return `${obj.tag}_${obj.ranged_combat_technique}`
case "Spell": return `${obj.tag}_${obj.spell}`
case "Ritual": return `${obj.tag}_${obj.ritual}`
case "Cantrip": return `${obj.tag}_${obj.cantrip}`
case "Property": return `${obj.tag}_${obj.property}`
case "LiturgicalChant": return `${obj.tag}_${obj.liturgical_chant}`
case "Ceremony": return `${obj.tag}_${obj.ceremony}`
case "Blessing": return `${obj.tag}_${obj.blessing}`
case "Aspect": return `${obj.tag}_${obj.aspect}`
case "Curse": return `${obj.tag}_${obj.curse}`
case "ElvenMagicalSong": return `${obj.tag}_${obj.elven_magical_song}`
case "DominationRitual": return `${obj.tag}_${obj.domination_ritual}`
case "MagicalMelody": return `${obj.tag}_${obj.magical_melody}`
case "MagicalDance": return `${obj.tag}_${obj.magical_dance}`
case "JesterTrick": return `${obj.tag}_${obj.jester_trick}`
case "AnimistPower": return `${obj.tag}_${obj.animist_power}`
case "GeodeRitual": return `${obj.tag}_${obj.geode_ritual}`
case "ZibiljaRitual": return `${obj.tag}_${obj.zibilja_ritual}`
case "AnimalType": return `${obj.tag}_${obj.animal_type}`
case "TargetCategory": return `${obj.tag}_${obj.target_category}`
case "General": return `${obj.tag}_${obj.general}`
case "Element": return `${obj.tag}_${obj.element}`
case "AnimalShapeSize": return `${obj.tag}_${obj.animal_shape_size}`
case "Patron": return `${obj.tag}_${obj.patron}`
case "Ammunition": return `${obj.tag}_${obj.ammunition}`
case "Animal": return `${obj.tag}_${obj.animal}`
case "AnimalCare": return `${obj.tag}_${obj.animal_care}`
case "Armor": return `${obj.tag}_${obj.armor}`
case "BandageOrRemedy": return `${obj.tag}_${obj.bandage_or_remedy}`
case "Book": return `${obj.tag}_${obj.book}`
case "CeremonialItem": return `${obj.tag}_${obj.ceremonial_item}`
case "Clothes": return `${obj.tag}_${obj.clothes}`
case "Container": return `${obj.tag}_${obj.container}`
case "Elixir": return `${obj.tag}_${obj.elixir}`
case "EquipmentOfBlessedOnes": return `${obj.tag}_${obj.equipment_of_blessed_ones}`
case "GemOrPreciousStone": return `${obj.tag}_${obj.gem_or_precious_stone}`
case "IlluminationLightSource": return `${obj.tag}_${obj.illumination_light_source}`
case "IlluminationRefillsOrSupplies": return `${obj.tag}_${obj.illumination_refills_or_supplies}`
case "Jewelry": return `${obj.tag}_${obj.jewelry}`
case "Liebesspielzeug": return `${obj.tag}_${obj.liebesspielzeug}`
case "LuxuryGood": return `${obj.tag}_${obj.luxury_good}`
case "MagicalArtifact": return `${obj.tag}_${obj.magical_artifact}`
case "MusicalInstrument": return `${obj.tag}_${obj.musical_instrument}`
case "OrienteeringAid": return `${obj.tag}_${obj.orienteering_aid}`
case "Poison": return `${obj.tag}_${obj.poison}`
case "RopeOrChain": return `${obj.tag}_${obj.rope_or_chain}`
case "Stationary": return `${obj.tag}_${obj.stationary}`
case "ThievesTool": return `${obj.tag}_${obj.thieves_tool}`
case "ToolOfTheTrade": return `${obj.tag}_${obj.tool_of_the_trade}`
case "TravelGearOrTool": return `${obj.tag}_${obj.travel_gear_or_tool}`
case "Vehicle": return `${obj.tag}_${obj.vehicle}`
case "Weapon": return `${obj.tag}_${obj.weapon}`
case "WeaponAccessory": return `${obj.tag}_${obj.weapon_accessory}`
case "Reach": return `${obj.tag}_${obj.reach}`
case "PatronCategory": return `${obj.tag}_${obj.patron_category}`
case "PersonalityTrait": return `${obj.tag}_${obj.personality_trait}`
case "HairColor": return `${obj.tag}_${obj.hair_color}`
case "EyeColor": return `${obj.tag}_${obj.eye_color}`
case "PactCategory": return `${obj.tag}_${obj.pact_category}`
case "PactDomain": return `${obj.tag}_${obj.pact_domain}`
case "AnimistTribe": return `${obj.tag}_${obj.animist_tribe}`
case "Influence": return `${obj.tag}_${obj.influence}`
case "Condition": return `${obj.tag}_${obj.condition}`
case "State": return `${obj.tag}_${obj.state}`
case "Disease": return `${obj.tag}_${obj.disease}`
case "SexPractice": return `${obj.tag}_${obj.sex_practice}`
case "TradeSecret": return `${obj.tag}_${obj.trade_secret}`
case "AnimalShape": return `${obj.tag}_${obj.animal_shape}`
case "ArcaneBardTradition": return `${obj.tag}_${obj.arcane_bard_tradition}`
case "ArcaneDancerTradition": return `${obj.tag}_${obj.arcane_dancer_tradition}`
default: return assertExhaustive(obj)
}
}
+30
View File
@@ -1,3 +1,9 @@
import { ActivatableIdentifier } from "optolith-database-schema/types/_IdentifierGroup"
import {
PactCategoryReference,
PactDomainReference,
} from "optolith-database-schema/types/_SimpleReferences"
/**
* A active pact between a character and a creature.
*/
@@ -44,3 +50,27 @@ export type PactDomain =
kind: "Custom"
name: string
}
/**
* A dependency on a pact.
*/
export type PactDependency = {
sourceId: ActivatableIdentifier
index: number
isPartOfDisjunction: boolean
/**
* The required pact category.
*/
category: PactCategoryReference
/**
* The required domain(s).
*/
domain?: PactDomainReference[]
/**
* The required pact level.
*/
level?: number
}
+32
View File
@@ -1,4 +1,5 @@
import { Race, RaceVariant } from "optolith-database-schema/types/Race"
import { ActivatableIdentifier } from "optolith-database-schema/types/_IdentifierGroup"
import { mapNullableDefault } from "../utils/nullable.ts"
import { TranslateMap } from "../utils/translate.ts"
@@ -26,3 +27,34 @@ export const getFullRaceName = (
str => ` (${str})`,
"",
)}`
/**
* A dependency on a race.
*/
export type RaceDependency = Readonly<{
/**
* The identifier of the dependency source.
*/
sourceId: ActivatableIdentifier
/**
* The top-level index of the prerequisite. If the prerequisite is part of a
* group or disjunction, this is the index of the group or disjunction.
*/
index: number
/**
* Is the source prerequisite part of a prerequisite disjunction?
*/
isPartOfDisjunction: boolean
/**
* The required race's identifier.
*/
id: number
/**
* Whether the required race is required to be active or inactive.
*/
active: boolean
}>
+27
View File
@@ -0,0 +1,27 @@
import { AnimistPowerImprovementCost } from "optolith-database-schema/types/magicalActions/AnimistPower"
import { mapNullable } from "../../utils/nullable.ts"
import { assertExhaustive } from "../../utils/typeSafety.ts"
import { Activatable, getFirstOptionOfType } from "../activatable/activatableEntry.ts"
import { ImprovementCost, fromRaw } from "../adventurePoints/improvementCost.ts"
import { GetById } from "../getTypes.ts"
/**
* Returns the improvement cost for an animist power.
*/
export const getImprovementCostForAnimistPower = (
getStaticPatronById: GetById.Static.Patron,
dynamicTradition: Activatable,
improvementCost: AnimistPowerImprovementCost,
): ImprovementCost | undefined => {
switch (improvementCost.tag) {
case "Fixed":
return fromRaw(improvementCost.fixed)
case "ByPrimaryPatron": {
const primaryPatronId = getFirstOptionOfType(dynamicTradition, "Patron")
const primaryPatron = mapNullable(primaryPatronId, getStaticPatronById)
return mapNullable(primaryPatron?.improvement_cost, fromRaw)
}
default:
return assertExhaustive(improvementCost)
}
}
+6
View File
@@ -1,4 +1,5 @@
import { AttributeReference } from "optolith-database-schema/types/_SimpleReferences"
import { ImprovementCost } from "../adventurePoints/improvementCost.ts"
import { Rated } from "./ratedEntry.ts"
/**
@@ -6,6 +7,11 @@ import { Rated } from "./ratedEntry.ts"
*/
export const minimumAttributeValue = 8
/**
* The improvement cost of an attribute.
*/
export const attributeImprovementCost = ImprovementCost.E
/**
* Creates an initial dynamic attribute entry.
*/
+12 -4
View File
@@ -9,6 +9,10 @@ import { AttributeIdentifier } from "../identifier.ts"
import { getAttributeValue } from "./attribute.ts"
import { getHighestRequiredAttributeForCombatTechnique } from "./combatTechnique.ts"
import { getHighestRequiredAttributeForLiturgicalChant } from "./liturgicalChant.ts"
import {
PrimaryAttributeDependency,
primaryAttributeDependencyToRatedDependency,
} from "./primaryAttribute.ts"
import { RatedDependency, flattenMinimumRestrictions } from "./ratedDependency.ts"
import { Rated } from "./ratedEntry.ts"
import { getHighestRequiredAttributeForSkill } from "./skill.ts"
@@ -23,9 +27,9 @@ export const getAttributeMinimum = (
purchasedArcaneEnergy: number,
purchasedKarmaPoints: number,
singleHighestMagicalPrimaryAttributeId: number | undefined,
magicalPrimaryAttributeDependencies: RatedDependency[],
magicalPrimaryAttributeDependencies: PrimaryAttributeDependency[],
blessedPrimaryAttributeId: number | undefined,
blessedPrimaryAttributeDependencies: RatedDependency[],
blessedPrimaryAttributeDependencies: PrimaryAttributeDependency[],
filterApplyingDependencies: (dependencies: RatedDependency[]) => RatedDependency[],
getSkillCheckAttributeMinimum: (id: number) => number | undefined,
dynamicAttribute: Rated,
@@ -43,7 +47,9 @@ export const getAttributeMinimum = (
? [
purchasedArcaneEnergy,
...flattenMinimumRestrictions(
filterApplyingDependencies(magicalPrimaryAttributeDependencies),
filterApplyingDependencies(
magicalPrimaryAttributeDependencies.map(primaryAttributeDependencyToRatedDependency),
),
),
]
: []),
@@ -51,7 +57,9 @@ export const getAttributeMinimum = (
? [
purchasedKarmaPoints,
...flattenMinimumRestrictions(
filterApplyingDependencies(blessedPrimaryAttributeDependencies),
filterApplyingDependencies(
blessedPrimaryAttributeDependencies.map(primaryAttributeDependencyToRatedDependency),
),
),
]
: []),
+11 -8
View File
@@ -1,11 +1,8 @@
import { CloseCombatTechnique } from "optolith-database-schema/types/CombatTechnique_Close"
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 {
Activatable,
PredefinedActivatableOption,
countOptions,
} from "../activatable/activatableEntry.ts"
import { Activatable, countOptions } from "../activatable/activatableEntry.ts"
import { AttributeIdentifier } from "../identifier.ts"
import { getAttributeValue } from "./attribute.ts"
import { Rated } from "./ratedEntry.ts"
@@ -48,10 +45,16 @@ export type CombinedCombatTechnique =
*/
export const getCombinedId = (
combatTechnique: CombinedCombatTechnique,
): PredefinedActivatableOption["id"] =>
): CombatTechniqueIdentifier =>
combatTechnique.tag === "CloseCombatTechnique"
? { type: "CloseCombatTechnique", value: combatTechnique.closeCombatTechnique.id }
: { type: "RangedCombatTechnique", value: combatTechnique.rangedCombatTechnique.id }
? {
tag: "CloseCombatTechnique",
close_combat_technique: combatTechnique.closeCombatTechnique.id,
}
: {
tag: "RangedCombatTechnique",
ranged_combat_technique: combatTechnique.rangedCombatTechnique.id,
}
/**
* Returns the primary attribute of a combined combat technique.
@@ -2,12 +2,13 @@ import { ExperienceLevel } from "optolith-database-schema/types/ExperienceLevel"
import { AttributeReference } from "optolith-database-schema/types/_SimpleReferences"
import { filterNonNullable } from "../../utils/array.ts"
import { Activatable, countOptions, isActive } from "../activatable/activatableEntry.ts"
import { FilterApplyingRatedDependencies } from "../dependencies/filterApplyingDependencies.ts"
import {
CombinedCombatTechnique,
getCombinedId,
getCombinedPrimaryAttribute,
} from "./combatTechnique.ts"
import { RatedDependency, flattenMinimumRestrictions } from "./ratedDependency.ts"
import { flattenMinimumRestrictions } from "./ratedDependency.ts"
import { Rated } from "./ratedEntry.ts"
const getCombatTechniqueMinimumForHunter = (
@@ -31,7 +32,7 @@ export const getCombatTechniqueMinimum = (
staticCombatTechnique: CombinedCombatTechnique,
dynamicCombatTechnique: Rated,
hunter: Activatable | undefined,
filterApplyingDependencies: (dependencies: RatedDependency[]) => RatedDependency[],
filterApplyingDependencies: FilterApplyingRatedDependencies,
): number => {
const minimumValues = filterNonNullable([
6,
+14 -3
View File
@@ -10,15 +10,26 @@ export type EnhancementDependency =
/**
* The depending enhancement.
*/
id: number
sourceId: number
}
| {
tag: "External"
/**
* The depending activatable.
* The identifier of the dependency source.
*/
id: ActivatableIdentifier
sourceId: ActivatableIdentifier
/**
* The top-level index of the prerequisite. If the prerequisite is part of a
* group or disjunction, this is the index of the group or disjunction.
*/
index: number
/**
* Is the source prerequisite part of a prerequisite disjunction?
*/
isPartOfDisjunction: boolean
}
/**
+20 -5
View File
@@ -13,7 +13,7 @@ import { assertExhaustive } from "../../utils/typeSafety.ts"
import { Activatable, countOptions } from "../activatable/activatableEntry.ts"
import { compareImprovementCost, fromRaw } from "../adventurePoints/improvementCost.ts"
import { All } from "../getTypes.ts"
import { AspectIdentifier } from "../identifier.ts"
import { AspectIdentifier, createIdentifierObject } from "../identifier.ts"
import { LiturgiesSortOrder } from "../sortOrders.ts"
import { DisplayedActiveLiturgy } from "./liturgicalChantActive.ts"
import { DisplayedInactiveLiturgy } from "./liturgicalChantInactive.ts"
@@ -83,6 +83,21 @@ export const flattenAspectIds = (traditions: SkillTradition[]): number[] =>
})
.map(aspect => aspect.id.aspect)
/**
* Checks if the passed liturgical chant features the passed aspect.
*/
export const hasAspectById = (id: number, traditions: SkillTradition[]): boolean =>
traditions.some(tradition => {
switch (tradition.tag) {
case "GeneralAspect":
return tradition.general_aspect.id.aspect === id
case "Tradition":
return tradition.tradition.aspects?.some(aspect => aspect.id.aspect === id) ?? false
default:
return assertExhaustive(tradition)
}
})
/**
* Returns the translation of all aspects relevant to the current active blessed
* tradition. If the tradition has no aspects, the name of the tradition is used
@@ -221,10 +236,10 @@ export const getHighestRequiredAttributeForLiturgicalChant = (
return undefined
}
const exceptionalSkillBonus = countOptions(exceptionalSkill, {
type,
value: staticLiturgicalChant.id,
})
const exceptionalSkillBonus = countOptions(
exceptionalSkill,
createIdentifierObject(type, staticLiturgicalChant.id),
)
return {
id: singleHighestAttributeId,

Some files were not shown because too many files have changed in this diff Show More