refactor!: migrate advantages and disadvantages functionality
This commit is contained in:
Generated
+16
-13
@@ -18,7 +18,7 @@
|
||||
"moment": "^2.29.4",
|
||||
"mousetrap": "1.6.5",
|
||||
"optolith-character-schema": "^0.0.3",
|
||||
"optolith-database-schema": "^0.15.1",
|
||||
"optolith-database-schema": "^0.16.5",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-markdown": "^9.0.0",
|
||||
@@ -8726,9 +8726,9 @@
|
||||
"integrity": "sha512-65wA26OjrFjvtCMaAieLZl2TMjYBx63E0nWRrZyWAzT0gjZ/FI8G8X7ouEF2EuTWYM2YYQMblq8/fK+PhGtWug=="
|
||||
},
|
||||
"node_modules/optolith-database-schema": {
|
||||
"version": "0.15.1",
|
||||
"resolved": "https://registry.npmjs.org/optolith-database-schema/-/optolith-database-schema-0.15.1.tgz",
|
||||
"integrity": "sha512-Fpy2JklGxP12kSKSm8naySkpCdj++pfAnhRSmdQ4JIDIjMuhfQWfasFsf4xOIVSkpS1HL2uixxZldPVhXA7XZA==",
|
||||
"version": "0.16.5",
|
||||
"resolved": "https://registry.npmjs.org/optolith-database-schema/-/optolith-database-schema-0.16.5.tgz",
|
||||
"integrity": "sha512-laG0Cpx8yuwGpoCTr0pWE7xnl2YH5iI7dhPFPl8xjnM7E6vs7zXan7/6GJYbZLZBqSyDn6HU/xmvpYKpV2TUlg==",
|
||||
"dependencies": {
|
||||
"ajv": "^8.12.0",
|
||||
"ajv-formats": "^2.1.1",
|
||||
@@ -11899,9 +11899,12 @@
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.3.tgz",
|
||||
"integrity": "sha512-zw0VAJxgeZ6+++/su5AFoqBbZbrEakwu+X0M5HmcwUiBL7AzcuPKjj5we4xfQLp78LkEMpD0cOnUhmgOVy3KdQ==",
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.0.tgz",
|
||||
"integrity": "sha512-j9iR8g+/t0lArF4V6NE/QCfT+CO7iLqrXAHZbJdo+LfjqP1vR8Fg5bSiaq6Q2lOD1AUEVrEVIgABvBFYojJVYQ==",
|
||||
"bin": {
|
||||
"yaml": "bin.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
@@ -18267,9 +18270,9 @@
|
||||
"integrity": "sha512-65wA26OjrFjvtCMaAieLZl2TMjYBx63E0nWRrZyWAzT0gjZ/FI8G8X7ouEF2EuTWYM2YYQMblq8/fK+PhGtWug=="
|
||||
},
|
||||
"optolith-database-schema": {
|
||||
"version": "0.15.1",
|
||||
"resolved": "https://registry.npmjs.org/optolith-database-schema/-/optolith-database-schema-0.15.1.tgz",
|
||||
"integrity": "sha512-Fpy2JklGxP12kSKSm8naySkpCdj++pfAnhRSmdQ4JIDIjMuhfQWfasFsf4xOIVSkpS1HL2uixxZldPVhXA7XZA==",
|
||||
"version": "0.16.5",
|
||||
"resolved": "https://registry.npmjs.org/optolith-database-schema/-/optolith-database-schema-0.16.5.tgz",
|
||||
"integrity": "sha512-laG0Cpx8yuwGpoCTr0pWE7xnl2YH5iI7dhPFPl8xjnM7E6vs7zXan7/6GJYbZLZBqSyDn6HU/xmvpYKpV2TUlg==",
|
||||
"requires": {
|
||||
"ajv": "^8.12.0",
|
||||
"ajv-formats": "^2.1.1",
|
||||
@@ -20605,9 +20608,9 @@
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
},
|
||||
"yaml": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.3.tgz",
|
||||
"integrity": "sha512-zw0VAJxgeZ6+++/su5AFoqBbZbrEakwu+X0M5HmcwUiBL7AzcuPKjj5we4xfQLp78LkEMpD0cOnUhmgOVy3KdQ=="
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.0.tgz",
|
||||
"integrity": "sha512-j9iR8g+/t0lArF4V6NE/QCfT+CO7iLqrXAHZbJdo+LfjqP1vR8Fg5bSiaq6Q2lOD1AUEVrEVIgABvBFYojJVYQ=="
|
||||
},
|
||||
"yargs-parser": {
|
||||
"version": "20.2.7",
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@
|
||||
"moment": "^2.29.4",
|
||||
"mousetrap": "1.6.5",
|
||||
"optolith-character-schema": "^0.0.3",
|
||||
"optolith-database-schema": "^0.15.1",
|
||||
"optolith-database-schema": "^0.16.5",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-markdown": "^9.0.0",
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
import { set } from "../../../../Data/Lens"
|
||||
import { List } from "../../../../Data/List"
|
||||
import { Just, Nothing } from "../../../../Data/Maybe"
|
||||
import { makeLenses } from "../../../../Data/Record"
|
||||
import { DisadvantageId } from "../../../Constants/Ids"
|
||||
import { ActiveObjectWithId } from "../../../Models/ActiveEntries/ActiveObjectWithId"
|
||||
import { ActivatableActivationValidation } from "../../../Models/View/ActivatableActivationValidationObject"
|
||||
import { ActivatableCombinedName } from "../../../Models/View/ActivatableCombinedName"
|
||||
import { ActivatableNameCost } from "../../../Models/View/ActivatableNameCost"
|
||||
import { ActiveActivatable } from "../../../Models/View/ActiveActivatable"
|
||||
import { L10n } from "../../../Models/Wiki/L10n"
|
||||
import { StaticData, StaticDataL } from "../../../Models/Wiki/WikiModel"
|
||||
import { composeL } from "../../compose"
|
||||
import { compressList } from "../activatableNameUtils"
|
||||
|
||||
const L10nL = makeLenses (L10n)
|
||||
|
||||
const StaticDataMockup = set (composeL (StaticDataL.ui, L10nL.id)) ("de-DE") (StaticData.default)
|
||||
|
||||
test ("compressList", () => {
|
||||
expect (compressList (StaticDataMockup)
|
||||
(List (
|
||||
ActiveActivatable ({
|
||||
nameAndCost: ActivatableNameCost ({
|
||||
active: ActiveObjectWithId ({
|
||||
id: DisadvantageId.PersonalityFlaw,
|
||||
index: Nothing,
|
||||
}),
|
||||
finalCost: Nothing,
|
||||
naming: ActivatableCombinedName ({
|
||||
addName: Just ("Arroganz"),
|
||||
baseName: "Persönlichkeitsschwäche",
|
||||
name: "Persönlichkeitsschwäche (Arroganz)",
|
||||
}),
|
||||
isAutomatic: false,
|
||||
}),
|
||||
validation: ActivatableActivationValidation ({
|
||||
disabled: Nothing,
|
||||
}),
|
||||
heroEntry: Nothing,
|
||||
wikiEntry: Nothing,
|
||||
}),
|
||||
ActiveActivatable ({
|
||||
nameAndCost: ActivatableNameCost ({
|
||||
active: ActiveObjectWithId ({
|
||||
id: DisadvantageId.PersonalityFlaw,
|
||||
index: Nothing,
|
||||
}),
|
||||
finalCost: Nothing,
|
||||
naming: ActivatableCombinedName ({
|
||||
addName: Just ("Weltfremd"),
|
||||
baseName: "Persönlichkeitsschwäche",
|
||||
name: "Persönlichkeitsschwäche (Weltfremd)",
|
||||
}),
|
||||
isAutomatic: false,
|
||||
}),
|
||||
validation: ActivatableActivationValidation ({
|
||||
disabled: Nothing,
|
||||
}),
|
||||
heroEntry: Nothing,
|
||||
wikiEntry: Nothing,
|
||||
})
|
||||
)))
|
||||
.toEqual ("Persönlichkeitsschwäche (Arroganz, Weltfremd)")
|
||||
})
|
||||
@@ -1,163 +0,0 @@
|
||||
/**
|
||||
* This file provides combining functions for displaying `Activatable`
|
||||
* entries.
|
||||
*
|
||||
* @file src/Utilities/activatableActiveUtils.ts
|
||||
* @author Lukas Obermann
|
||||
* @since 1.1.0
|
||||
*/
|
||||
|
||||
import { fmap } from "../../../Data/Functor"
|
||||
import { List } from "../../../Data/List"
|
||||
import { bind, liftM2, mapMaybe, Maybe } from "../../../Data/Maybe"
|
||||
import { lookup, OrderedMap } from "../../../Data/OrderedMap"
|
||||
import { Record } from "../../../Data/Record"
|
||||
import { fst, Pair, snd } from "../../../Data/Tuple"
|
||||
import { ActivatableCategory, Category } from "../../Constants/Categories"
|
||||
import { ActivatableDependent } from "../../Models/ActiveEntries/ActivatableDependent"
|
||||
import { ActiveObjectWithId } from "../../Models/ActiveEntries/ActiveObjectWithId"
|
||||
import { HeroModel, HeroModelRecord } from "../../Models/Hero/HeroModel"
|
||||
import { ActivatableActivationValidation } from "../../Models/View/ActivatableActivationValidationObject"
|
||||
import { ActivatableCombinedName } from "../../Models/View/ActivatableCombinedName"
|
||||
import { ActivatableNameCost, ActivatableNameCostSafeCost } from "../../Models/View/ActivatableNameCost"
|
||||
import { ActiveActivatable } from "../../Models/View/ActiveActivatable"
|
||||
import { Advantage } from "../../Models/Wiki/Advantage"
|
||||
import { Disadvantage } from "../../Models/Wiki/Disadvantage"
|
||||
import { SpecialAbility } from "../../Models/Wiki/SpecialAbility"
|
||||
import { StaticData, StaticDataRecord } from "../../Models/Wiki/WikiModel"
|
||||
import { WikiEntryByCategory } from "../../Models/Wiki/wikiTypeHelpers"
|
||||
import { MatchingScriptAndLanguageRelated } from "../../Selectors/activatableSelectors"
|
||||
import { convertPerTierCostToFinalCost, getCost } from "../AdventurePoints/activatableCostUtils"
|
||||
import { pipe_ } from "../pipe"
|
||||
import { getIsRemovalOrChangeDisabled } from "./activatableActiveValidationUtils"
|
||||
import { getActiveFromState } from "./activatableConvertUtils"
|
||||
import { getName } from "./activatableNameUtils"
|
||||
|
||||
const SDA = StaticData.A
|
||||
const HA = HeroModel.A
|
||||
|
||||
/**
|
||||
* Takes an Activatable category and a hero and returns the state slice matching
|
||||
* the passed category.
|
||||
*/
|
||||
export const getActivatableHeroSliceByCategory =
|
||||
(category: ActivatableCategory) =>
|
||||
(hero: HeroModelRecord): OrderedMap<string, Record<ActivatableDependent>> =>
|
||||
category === Category.ADVANTAGES
|
||||
? HA.advantages (hero)
|
||||
: category === Category.DISADVANTAGES
|
||||
? HA.disadvantages (hero)
|
||||
: HA.specialAbilities (hero)
|
||||
|
||||
type ActivatableWikiSliceByCategory<A extends ActivatableCategory> =
|
||||
A extends Category.ADVANTAGES
|
||||
? Record<Advantage>
|
||||
: A extends Category.ADVANTAGES
|
||||
? Record<Disadvantage>
|
||||
: Record<SpecialAbility>
|
||||
|
||||
/**
|
||||
* Takes an Activatable category and a hero and returns the state slice matching
|
||||
* the passed category.
|
||||
*/
|
||||
export const getActivatableWikiSliceByCategory =
|
||||
<A extends ActivatableCategory>
|
||||
(category: A) =>
|
||||
(wiki: StaticDataRecord): OrderedMap<string, ActivatableWikiSliceByCategory<A>> =>
|
||||
category === Category.ADVANTAGES
|
||||
? SDA.advantages (wiki) as OrderedMap<string, ActivatableWikiSliceByCategory<A>>
|
||||
: category === Category.DISADVANTAGES
|
||||
? SDA.disadvantages (wiki) as OrderedMap<string, ActivatableWikiSliceByCategory<A>>
|
||||
: SDA.specialAbilities (wiki) as OrderedMap<string, ActivatableWikiSliceByCategory<A>>
|
||||
|
||||
/**
|
||||
* Returns name, splitted and combined, as well as the AP you get when removing
|
||||
* the ActiveObject.
|
||||
* @param obj The ActiveObject with origin id.
|
||||
* @param wiki The wiki state.
|
||||
* @param state The current hero's state.
|
||||
* @param isEntryToAdd If the cost are going to be added or removed from AP left.
|
||||
* @param locale The locale-dependent messages.
|
||||
*/
|
||||
export const getNameCost =
|
||||
(isEntryToAdd: boolean) =>
|
||||
(automatic_advantages: List<string>) =>
|
||||
(staticData: StaticDataRecord) =>
|
||||
(hero: HeroModelRecord) =>
|
||||
(entry: Record<ActiveObjectWithId>): Maybe<Record<ActivatableNameCost>> =>
|
||||
liftM2 ((finalCost: Pair<number | List<number>, boolean>) =>
|
||||
(naming: Record<ActivatableCombinedName>) =>
|
||||
ActivatableNameCost ({
|
||||
naming,
|
||||
active: entry,
|
||||
finalCost: fst (finalCost),
|
||||
isAutomatic: snd (finalCost),
|
||||
}))
|
||||
(getCost (isEntryToAdd) (automatic_advantages) (staticData) (hero) (entry))
|
||||
(getName (staticData) (entry))
|
||||
|
||||
/**
|
||||
* Returns name, splitted and combined, as well as the AP you get when removing
|
||||
* the ActiveObject.
|
||||
* @param obj The ActiveObject with origin id.
|
||||
* @param wiki The wiki state.
|
||||
* @param l10n The locale-dependent messages.
|
||||
*/
|
||||
export const getNameCostForWiki =
|
||||
(staticData: StaticDataRecord) =>
|
||||
(active: Record<ActiveObjectWithId>): Maybe<Record<ActivatableNameCost>> =>
|
||||
liftM2 ((finalCost: Pair<number | List<number>, boolean>) =>
|
||||
(naming: Record<ActivatableCombinedName>) =>
|
||||
ActivatableNameCost ({
|
||||
active,
|
||||
naming,
|
||||
finalCost: fst (finalCost),
|
||||
isAutomatic: snd (finalCost),
|
||||
}))
|
||||
(getCost (true) (List ()) (staticData) (HeroModel.default) (active))
|
||||
(getName (staticData) (active))
|
||||
|
||||
export const getAllActiveByCategory =
|
||||
<T extends ActivatableCategory>
|
||||
(category: T) =>
|
||||
(addLevelToName: boolean) =>
|
||||
(automatic_advantages: List<string>) =>
|
||||
(matchingScriptAndLanguageRelated: MatchingScriptAndLanguageRelated) =>
|
||||
(staticData: StaticDataRecord) =>
|
||||
(hero: HeroModelRecord): List<Record<ActiveActivatable<WikiEntryByCategory[T]>>> => {
|
||||
const convertCost = convertPerTierCostToFinalCost (addLevelToName) (staticData)
|
||||
|
||||
const wiki_slice = getActivatableWikiSliceByCategory (category) (staticData)
|
||||
const hero_slice = getActivatableHeroSliceByCategory (category) (hero)
|
||||
|
||||
type Entry = Maybe<Record<ActiveActivatable<WikiEntryByCategory[T]>>>
|
||||
|
||||
return pipe_ (
|
||||
hero_slice,
|
||||
getActiveFromState,
|
||||
mapMaybe ((active: Record<ActiveObjectWithId>): Entry => {
|
||||
const current_id = ActiveObjectWithId.A.id (active)
|
||||
|
||||
return bind (lookup (current_id) (wiki_slice)) (staticEntry =>
|
||||
bind (lookup (current_id) (hero_slice)) (characterEntry =>
|
||||
liftM2 ((nameAndCost: Record<ActivatableNameCostSafeCost>) =>
|
||||
(validation: Record<ActivatableActivationValidation>) =>
|
||||
ActiveActivatable ({
|
||||
nameAndCost,
|
||||
validation,
|
||||
heroEntry: characterEntry,
|
||||
wikiEntry: staticEntry,
|
||||
}))
|
||||
(fmap (convertCost)
|
||||
(getNameCost (false)
|
||||
(automatic_advantages)
|
||||
(staticData)
|
||||
(hero)
|
||||
(active)))
|
||||
(getIsRemovalOrChangeDisabled (staticData)
|
||||
(hero)
|
||||
(matchingScriptAndLanguageRelated)
|
||||
(active)) as Entry))
|
||||
})
|
||||
)
|
||||
}
|
||||
@@ -1,770 +0,0 @@
|
||||
/**
|
||||
* Contains helper functions for calculating restrictions of changing active
|
||||
* `Activatables`: Minimum level, maximum level and if the entry can be removed.
|
||||
*
|
||||
* @author Lukas Obermann
|
||||
*/
|
||||
|
||||
import { not, notP } from "../../../Data/Bool"
|
||||
import { equals } from "../../../Data/Eq"
|
||||
import { flip, Functn, thrush } from "../../../Data/Function"
|
||||
import { fmap } from "../../../Data/Functor"
|
||||
import { all, any, countWith, elem, filter, find, flength, foldl, isList, List, mapByIdKeyMap, notElemF, notNull, sdelete } from "../../../Data/List"
|
||||
import { alt, bind, bindF, catMaybes, ensure, fromJust, fromMaybe, isJust, isNothing, Just, liftM2, mapMaybe, Maybe, maybe, Nothing, or, sum } from "../../../Data/Maybe"
|
||||
import { add, gt, gte, inc, lt, lte, max, min, subtract, subtractBy } from "../../../Data/Num"
|
||||
import { elems, isOrderedMap, lookup, lookupF, OrderedMap } from "../../../Data/OrderedMap"
|
||||
import { size } from "../../../Data/OrderedSet"
|
||||
import { Record } from "../../../Data/Record"
|
||||
import { MagicalGroup, SpecialAbilityGroup } from "../../Constants/Groups"
|
||||
import { AdvantageId, DisadvantageId, SpecialAbilityId } from "../../Constants/Ids"
|
||||
import { ActivatableDependent, isActivatableDependent } from "../../Models/ActiveEntries/ActivatableDependent"
|
||||
import { ActivatableSkillDependent } from "../../Models/ActiveEntries/ActivatableSkillDependent"
|
||||
import { ActiveObject } from "../../Models/ActiveEntries/ActiveObject"
|
||||
import { ActiveObjectWithId } from "../../Models/ActiveEntries/ActiveObjectWithId"
|
||||
import { DependencyObject } from "../../Models/ActiveEntries/DependencyObject"
|
||||
import { SkillDependent } from "../../Models/ActiveEntries/SkillDependent"
|
||||
import { HeroModel, HeroModelRecord } from "../../Models/Hero/HeroModel"
|
||||
import { ActivatableDependency, Dependent } from "../../Models/Hero/heroTypeHelpers"
|
||||
import { Pact } from "../../Models/Hero/Pact"
|
||||
import { TransferUnfamiliar, UnfamiliarGroup } from "../../Models/Hero/TransferUnfamiliar"
|
||||
import { ActivatableActivationValidation } from "../../Models/View/ActivatableActivationValidationObject"
|
||||
import { Advantage } from "../../Models/Wiki/Advantage"
|
||||
import { ExperienceLevel } from "../../Models/Wiki/ExperienceLevel"
|
||||
import { LiturgicalChant } from "../../Models/Wiki/LiturgicalChant"
|
||||
import { RequireActivatable } from "../../Models/Wiki/prerequisites/ActivatableRequirement"
|
||||
import { SocialPrerequisite } from "../../Models/Wiki/prerequisites/SocialPrerequisite"
|
||||
import { SpecialAbility } from "../../Models/Wiki/SpecialAbility"
|
||||
import { Spell } from "../../Models/Wiki/Spell"
|
||||
import { SelectOption } from "../../Models/Wiki/sub/SelectOption"
|
||||
import { StaticData, StaticDataRecord } from "../../Models/Wiki/WikiModel"
|
||||
import { Activatable, EntryWithCategory, LevelAwarePrerequisites, PrerequisitesWithIds, SID } from "../../Models/Wiki/wikiTypeHelpers"
|
||||
import { MatchingScriptAndLanguageRelated } from "../../Selectors/activatableSelectors"
|
||||
import { countActiveGroupEntries } from "../entryGroupUtils"
|
||||
import { getAllEntriesByGroup, getHeroStateItem } from "../heroStateUtils"
|
||||
import { isOwnTradition } from "../Increasable/liturgicalChantUtils"
|
||||
import { ensure as newEnsure, toNewMaybe } from "../Maybe"
|
||||
import { pipe, pipe_ } from "../pipe"
|
||||
import { flattenPrerequisites } from "../Prerequisites/flattenPrerequisites"
|
||||
import { setPrerequisiteId } from "../Prerequisites/setPrerequisiteId"
|
||||
import { validateLevel, validateObject } from "../Prerequisites/validatePrerequisitesUtils"
|
||||
import { isBoolean, isNumber, misNumberM, misStringM } from "../typeCheckUtils"
|
||||
import { getWikiEntry, isActivatableWikiEntry } from "../WikiUtils"
|
||||
import { countActiveSkillEntries } from "./activatableSkillUtils"
|
||||
import { isStyleValidToRemove } from "./ExtendedStyleUtils"
|
||||
import { isActive } from "./isActive"
|
||||
import { findSelectOption, getActiveSelections } from "./selectionUtils"
|
||||
import { getBlessedTraditionFromWiki, getMagicalTraditionsHeroEntries, isBlessedTradId, isMagicalTradId } from "./traditionUtils"
|
||||
|
||||
const hasRequiredMinimumLevel =
|
||||
(min_level: Maybe<number>) => (max_level: Maybe<number>): boolean =>
|
||||
isJust (max_level) && isJust (min_level)
|
||||
|
||||
const HA = HeroModel.A
|
||||
const SDA = StaticData.A
|
||||
const ELA = ExperienceLevel.A
|
||||
const SOA = SelectOption.A
|
||||
const AAL = Advantage.AL
|
||||
const ADA = ActivatableDependent.A
|
||||
const ASDA = ActivatableSkillDependent.A
|
||||
const DOA = DependencyObject.A
|
||||
const AOA = ActiveObject.A
|
||||
const AOWIA = ActiveObjectWithId.A
|
||||
const RAAL = RequireActivatable.AL
|
||||
const PA = Pact.A
|
||||
const SA = Spell.A
|
||||
const LCA = LiturgicalChant.A
|
||||
const TUA = TransferUnfamiliar.A
|
||||
|
||||
/**
|
||||
* Checks if you can somehow remove an ActiveObject from the given entry.
|
||||
*/
|
||||
const isRemovalDisabledEntrySpecific =
|
||||
(wiki: StaticDataRecord) =>
|
||||
(hero: HeroModelRecord) =>
|
||||
({
|
||||
isEntryRequiringMatchingScriptAndLangActive,
|
||||
scriptsWithMatchingLanguages,
|
||||
languagesWithMatchingScripts,
|
||||
languagesWithDependingScripts,
|
||||
}: MatchingScriptAndLanguageRelated) =>
|
||||
(wiki_entry: Activatable) =>
|
||||
(hero_entry: Record<ActivatableDependent>) =>
|
||||
|
||||
// tslint:disable-next-line: cyclomatic-complexity
|
||||
(active: Record<ActiveObjectWithId>): boolean => {
|
||||
const id = AAL.id (wiki_entry)
|
||||
|
||||
const mstart_el =
|
||||
lookupF (SDA.experienceLevels (wiki))
|
||||
(HA.experienceLevel (hero))
|
||||
|
||||
if (isMagicalTradId (id)) {
|
||||
// All active tradition entries
|
||||
const traditions =
|
||||
getMagicalTraditionsHeroEntries (HA.specialAbilities (hero))
|
||||
|
||||
const multiple_traditions = flength (traditions) > 1
|
||||
|
||||
// multiple traditions are currently not supported and there must be no
|
||||
// active spell or cantrip
|
||||
return multiple_traditions
|
||||
|| countActiveSkillEntries ("spells") (hero) > 0
|
||||
|| size (HA.cantrips (hero)) > 0
|
||||
}
|
||||
else if (isBlessedTradId (id)) {
|
||||
// there must be no active liturgical chant or blessing
|
||||
return countActiveSkillEntries ("liturgicalChants") (hero) > 0
|
||||
|| size (HA.blessings (hero)) > 0
|
||||
}
|
||||
|
||||
switch (id) {
|
||||
case AdvantageId.ExceptionalSkill: {
|
||||
// value of target skill
|
||||
const mvalue =
|
||||
pipe_ (
|
||||
active,
|
||||
AOWIA.sid,
|
||||
misStringM,
|
||||
bindF (lookupF (HA.skills (hero))),
|
||||
fmap (SkillDependent.AL.value)
|
||||
)
|
||||
|
||||
// amount of active Exceptional Skill advantages for the same skill
|
||||
const counter = countWith (pipe (AOA.sid, equals (AOWIA.sid (active))))
|
||||
(ADA.active (hero_entry))
|
||||
|
||||
// if the maximum value is reached removal needs to be disabled
|
||||
return or (liftM2 (gte)
|
||||
(mvalue)
|
||||
(fmap (pipe (ELA.maxSkillRating, add (counter)))
|
||||
(mstart_el)))
|
||||
}
|
||||
|
||||
case AdvantageId.ExceptionalCombatTechnique: {
|
||||
// value of target combat technique
|
||||
const value =
|
||||
pipe_ (
|
||||
active,
|
||||
AOWIA.sid,
|
||||
misStringM,
|
||||
bindF (lookupF (HeroModel.A.combatTechniques (hero))),
|
||||
maybe (6) (SkillDependent.A.value)
|
||||
)
|
||||
|
||||
// if the maximum value is reached removal needs to be disabled
|
||||
return maybe (true) (pipe (ELA.maxCombatTechniqueRating, inc, lte (value))) (mstart_el)
|
||||
}
|
||||
|
||||
case SpecialAbilityId.Literacy: {
|
||||
return isEntryRequiringMatchingScriptAndLangActive
|
||||
&& scriptsWithMatchingLanguages.length < 2
|
||||
&& toNewMaybe (AOWIA.sid (active))
|
||||
.bind (sid => newEnsure (sid, isNumber))
|
||||
.maybe (false, sid => scriptsWithMatchingLanguages.includes (sid))
|
||||
}
|
||||
|
||||
case SpecialAbilityId.Language: {
|
||||
const isRequiredByEntryRequiringMatchingScriptAndLanguage =
|
||||
isEntryRequiringMatchingScriptAndLangActive
|
||||
&& languagesWithMatchingScripts.length < 2
|
||||
&& toNewMaybe (AOWIA.sid (active))
|
||||
.bind (sid => newEnsure (sid, isNumber))
|
||||
.maybe (false, sid => languagesWithMatchingScripts.includes (sid))
|
||||
|
||||
const isRequiredByDependingLanguage =
|
||||
toNewMaybe (AOWIA.sid (active))
|
||||
.bind (sid => newEnsure (sid, isNumber))
|
||||
.maybe (false, sid =>
|
||||
languagesWithDependingScripts
|
||||
.findM (languageGroupOfScript =>
|
||||
languageGroupOfScript.includes (sid))
|
||||
.maybe (false, languageGroupOfScript =>
|
||||
languageGroupOfScript
|
||||
.mapMaybe (idOfGroup =>
|
||||
toNewMaybe (find ((ao: Record<ActiveObject>) =>
|
||||
Maybe.elemF (AOA.sid (ao)) (idOfGroup))
|
||||
(ADA.active (hero_entry))))
|
||||
.length < 2))
|
||||
|
||||
return isRequiredByEntryRequiringMatchingScriptAndLanguage || isRequiredByDependingLanguage
|
||||
}
|
||||
|
||||
case SpecialAbilityId.PropertyKnowledge:
|
||||
return pipe_ (
|
||||
active,
|
||||
AOWIA.sid,
|
||||
misNumberM,
|
||||
maybe (false)
|
||||
(prop_id => OrderedMap.any ((spell: Record<ActivatableSkillDependent>) =>
|
||||
ASDA.value (spell) > 14
|
||||
&& pipe_ (
|
||||
spell,
|
||||
ASDA.id,
|
||||
lookupF (SDA.spells (wiki)),
|
||||
maybe (true)
|
||||
(pipe (SA.property, equals (prop_id)))
|
||||
))
|
||||
(HA.spells (hero)))
|
||||
)
|
||||
|
||||
case SpecialAbilityId.AspectKnowledge: {
|
||||
const all_aspcs = getActiveSelections (hero_entry)
|
||||
|
||||
return pipe_ (
|
||||
active,
|
||||
AOWIA.sid,
|
||||
misNumberM,
|
||||
maybe (false)
|
||||
(aspc_id => {
|
||||
const other_aspcs = sdelete<string | number> (aspc_id) (all_aspcs)
|
||||
|
||||
return OrderedMap.any ((chant: Record<ActivatableSkillDependent>) =>
|
||||
ASDA.value (chant) > 14
|
||||
&& pipe_ (
|
||||
chant,
|
||||
ASDA.id,
|
||||
lookupF (SDA.liturgicalChants (wiki)),
|
||||
maybe (true)
|
||||
(pipe (
|
||||
LCA.aspects,
|
||||
aspcs => elem (aspc_id) (aspcs)
|
||||
&& all (notElemF (other_aspcs))
|
||||
(aspcs)
|
||||
))
|
||||
))
|
||||
(HA.liturgicalChants (hero))
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
case SpecialAbilityId.CombatStyleCombination: {
|
||||
const armedStyleActive = countActiveGroupEntries (wiki)
|
||||
(hero)
|
||||
(SpecialAbilityGroup.CombatStylesArmed)
|
||||
|
||||
const unarmedStyleActive = countActiveGroupEntries (wiki)
|
||||
(hero)
|
||||
(SpecialAbilityGroup.CombatStylesUnarmed)
|
||||
|
||||
const totalActive = armedStyleActive + unarmedStyleActive
|
||||
|
||||
// default is 1 per group (armed/unarmed), but with this SA 1 more in
|
||||
// one group: maximum of 3, but max 2 per group. If max is reached, this
|
||||
// SA cannot be removed
|
||||
return totalActive >= 3
|
||||
|| armedStyleActive >= 2
|
||||
|| unarmedStyleActive >= 2
|
||||
}
|
||||
|
||||
case SpecialAbilityId.MagicStyleCombination: {
|
||||
const totalActive = countActiveGroupEntries (wiki) (hero) (13)
|
||||
|
||||
// default is 1, but with this SA its 2. If it's 2 this SA is neccessary
|
||||
// and cannot be removed
|
||||
return totalActive >= 2
|
||||
}
|
||||
|
||||
// Extended Blessed Special Abilities that allow to learn liturgical
|
||||
// chants of different traditions
|
||||
case SpecialAbilityId.Zugvoegel:
|
||||
case SpecialAbilityId.JaegerinnenDerWeissenMaid:
|
||||
case SpecialAbilityId.AnhaengerDesGueldenen: {
|
||||
const mblessed_tradition =
|
||||
getBlessedTraditionFromWiki (SDA.specialAbilities (wiki))
|
||||
(HA.specialAbilities (hero))
|
||||
|
||||
// Wiki entries for all active liturgical chants
|
||||
const active_chants =
|
||||
pipe_ (
|
||||
hero,
|
||||
HA.liturgicalChants,
|
||||
elems,
|
||||
filter<Record<ActivatableSkillDependent>> (ASDA.active),
|
||||
mapByIdKeyMap (SDA.liturgicalChants (wiki))
|
||||
)
|
||||
|
||||
// If there are chants active that do not belong to the own tradition
|
||||
const mactive_unfamiliar_chants =
|
||||
fmap (pipe (isOwnTradition, notP, any, thrush (active_chants)))
|
||||
(mblessed_tradition)
|
||||
|
||||
return or (mactive_unfamiliar_chants)
|
||||
}
|
||||
|
||||
// entries transferring unfamiliar special abilities (start)
|
||||
|
||||
case SpecialAbilityId.TraditionGuildMages:
|
||||
case SpecialAbilityId.MadaschwesternStil:
|
||||
case SpecialAbilityId.ScholarDesMagierkollegsZuHoningen:
|
||||
case SpecialAbilityId.Zaubervariabilitaet:
|
||||
case SpecialAbilityId.ScholarDerHalleDesLebensZuNorburg:
|
||||
case SpecialAbilityId.ScholarDesKreisesDerEinfuehlung: {
|
||||
const m_static_spell_enhancements = pipe_ (
|
||||
wiki,
|
||||
SDA.specialAbilities,
|
||||
lookup<string> (SpecialAbilityId.SpellEnhancement)
|
||||
)
|
||||
|
||||
const active_spell_enhancements = pipe_ (
|
||||
hero,
|
||||
HA.specialAbilities,
|
||||
lookup<string> (SpecialAbilityId.SpellEnhancement),
|
||||
liftM2 ((static_spell_enhancements: Record<SpecialAbility>) =>
|
||||
pipe (
|
||||
ADA.active,
|
||||
mapMaybe (pipe (
|
||||
AOA.sid,
|
||||
misNumberM,
|
||||
findSelectOption (static_spell_enhancements),
|
||||
bindF (SOA.target)
|
||||
))
|
||||
))
|
||||
(m_static_spell_enhancements),
|
||||
fromMaybe (List<string> ())
|
||||
)
|
||||
|
||||
type Target = string | UnfamiliarGroup
|
||||
|
||||
const targets =
|
||||
id === SpecialAbilityId.Zaubervariabilitaet
|
||||
? List<Target> (UnfamiliarGroup.Spells)
|
||||
: catMaybes (List (
|
||||
pipe_ (active, AOWIA.sid, misStringM),
|
||||
pipe_ (active, AOWIA.sid2, misStringM),
|
||||
pipe_ (active, AOWIA.sid3, misStringM)
|
||||
))
|
||||
|
||||
const target_matches_id =
|
||||
(target: Target) =>
|
||||
(spell_id: string) =>
|
||||
typeof target === "string"
|
||||
// The target must be the same …
|
||||
? target === spell_id
|
||||
// … or it must be from the same category
|
||||
: target === UnfamiliarGroup.Spells
|
||||
|
||||
const relevant_spell_enhancements = pipe_ (
|
||||
active_spell_enhancements,
|
||||
filter (spell_id => any (Functn.flip (target_matches_id) (spell_id)) (targets))
|
||||
)
|
||||
|
||||
// spell enhancements must exist to possibly be a dependency
|
||||
// also, a spell must be selected and it has to have an active spell
|
||||
// enhancement to be relevant
|
||||
if (notNull (active_spell_enhancements)
|
||||
&& notNull (targets)
|
||||
&& notNull (relevant_spell_enhancements)) {
|
||||
const transferred = HA.transferredUnfamiliarSpells (hero)
|
||||
|
||||
return pipe_ (
|
||||
relevant_spell_enhancements,
|
||||
// check for alternative transferred dependency matches for all
|
||||
// targets that would allow this entry to be removed
|
||||
all (spell_id => pipe_ (
|
||||
transferred,
|
||||
// It cannot be the same entry
|
||||
any (tu => TUA.srcId (tu) !== id && target_matches_id (TUA.id (tu)) (spell_id))
|
||||
)),
|
||||
// if all spell enhancement have alternative, it can be safely
|
||||
// removed and thus removal is "not" disabled
|
||||
not
|
||||
)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const isLevelMatched = (required_level_opt: Maybe<number>, active_level_opt: Maybe<number>) =>
|
||||
maybe (true)
|
||||
((required_level: number) => maybe (false) (gte (required_level)) (active_level_opt))
|
||||
(required_level_opt)
|
||||
|
||||
const isSidMatched = (
|
||||
required_sid_opt: Maybe<string | number | List<number>>,
|
||||
active_sid_opt: Maybe<SID>
|
||||
) =>
|
||||
maybe (true)
|
||||
((required_sid: string | number | List<number>) =>
|
||||
maybe (false) (equals (required_sid)) (active_sid_opt))
|
||||
(required_sid_opt)
|
||||
|
||||
const isEntryDisabledByDependencies =
|
||||
(wiki: StaticDataRecord) =>
|
||||
(hero: HeroModelRecord) =>
|
||||
(wiki_entry: Activatable) =>
|
||||
(state_entry: Record<ActivatableDependent>) =>
|
||||
(current_active: Record<ActiveObjectWithId>): boolean => {
|
||||
const active_sid_opt = AOWIA.sid (current_active)
|
||||
const active_sid2_opt = AOWIA.sid2 (current_active)
|
||||
const active_level_opt = AOWIA.tier (current_active)
|
||||
|
||||
const active_num = flength (ADA.active (state_entry))
|
||||
|
||||
return pipe_ (
|
||||
state_entry,
|
||||
ADA.dependencies,
|
||||
|
||||
// if there is any dependency that disables the possibility to remove
|
||||
// the entry
|
||||
any (dep => {
|
||||
if (isBoolean (dep)) {
|
||||
return dep && active_num < 2
|
||||
}
|
||||
|
||||
// Try to find out if the dependency object targets this entry
|
||||
const applies_to_sid1 = isSidMatched (DOA.sid (dep), active_sid_opt)
|
||||
const applies_to_sid2 = isSidMatched (DOA.sid2 (dep), active_sid2_opt)
|
||||
const applies_to_level = isLevelMatched (DOA.tier (dep), active_level_opt)
|
||||
|
||||
if (applies_to_sid1 && applies_to_sid2 && applies_to_level) {
|
||||
// A Just if the depedency exists because of a list of ids
|
||||
// in a prerequiste. Contains the source id of the object
|
||||
// where the prerequisite is from.
|
||||
const current_origin = DOA.origin (dep)
|
||||
|
||||
if (isJust (current_origin)) {
|
||||
return pipe_ (
|
||||
fromJust (current_origin),
|
||||
getWikiEntry (wiki),
|
||||
bindF<EntryWithCategory, Activatable>
|
||||
(ensure (isActivatableWikiEntry)),
|
||||
|
||||
// Get flat prerequisites for origin entry
|
||||
fmap ((origin_entry: Activatable) =>
|
||||
flattenPrerequisites (Nothing)
|
||||
(alt (AAL.tiers (origin_entry)) (Just (1)))
|
||||
(AAL.prerequisites (origin_entry))),
|
||||
|
||||
// Get the prerequisite that matches this entry
|
||||
// to get all other options from list
|
||||
bindF (find ((req): req is PrerequisitesWithIds => {
|
||||
if (typeof req === "string" || SocialPrerequisite.is (req)) {
|
||||
return false
|
||||
}
|
||||
|
||||
const current_id = RAAL.id (req)
|
||||
|
||||
// the id must be a list of ids
|
||||
// because otherwise no options in
|
||||
// terms of fulfilling the
|
||||
// prerequisite would be possible
|
||||
return isList (current_id)
|
||||
|
||||
// check if the current entry's
|
||||
// id is actually a member of
|
||||
// the prerequisite
|
||||
&& elem (AAL.id (wiki_entry))
|
||||
(current_id)
|
||||
})),
|
||||
|
||||
// Check if there are other entries that would
|
||||
// match the prerequisite so that this entry
|
||||
// could be removed
|
||||
fmap ((req: PrerequisitesWithIds) =>
|
||||
!any ((x: string) =>
|
||||
validateObject (wiki)
|
||||
(hero)
|
||||
(setPrerequisiteId (x) (req))
|
||||
(AAL.id (wiki_entry)))
|
||||
(sdelete (AAL.id (wiki_entry))
|
||||
(RAAL.id (req) as List<string>))),
|
||||
or
|
||||
)
|
||||
}
|
||||
|
||||
return pipe_ (
|
||||
state_entry,
|
||||
ADA.active,
|
||||
countWith (other_active =>
|
||||
isSidMatched (DOA.sid (dep), AOA.sid (other_active))
|
||||
&& isSidMatched (DOA.sid2 (dep), AOA.sid2 (other_active))
|
||||
&& isLevelMatched (DOA.tier (dep), AOA.tier (other_active))),
|
||||
|
||||
// If multiple entries match the dependency, the current one must be
|
||||
// one of them, another one can fulfill it so that the current one
|
||||
// can be safely removed
|
||||
lt (2)
|
||||
)
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const isStyleSpecialAbilityRemovalDisabled =
|
||||
(hero: HeroModelRecord) =>
|
||||
(wiki_entry: Activatable): boolean =>
|
||||
SpecialAbility.is (wiki_entry)
|
||||
&& !isStyleValidToRemove (hero)
|
||||
(Just (wiki_entry))
|
||||
|
||||
export const getMinLevelForIncreaseEntry: (def: number) => (count: number) => Maybe<number> =
|
||||
|
||||
// the entry allows to have more entries, which would not be possible without.
|
||||
// The minimum is simply the count - def, because if there are def + 1
|
||||
// entries, it must be at least 1, if there are def + 2 entries,
|
||||
// it must be at least 2. And if the count is not greater than def, the
|
||||
// increase entry is not used so we dont need a restriction
|
||||
def => pipe (ensure (gt (def)), fmap (subtractBy (def)))
|
||||
|
||||
export const getMaxLevelForDecreaseEntry: (def: number) => (count: number) => Maybe<number> =
|
||||
|
||||
// the more entries the user buys, the less levels are
|
||||
// possible. If the user has 3 or more entries, the decrease entry cannot
|
||||
// be used at all. In those cases (which should not happen), the maximum
|
||||
// will be 0.
|
||||
def => pipe (subtract (def), max (0), Just)
|
||||
|
||||
export const getSermonsAndVisionsCount =
|
||||
(wiki: StaticDataRecord) =>
|
||||
(state: HeroModelRecord):
|
||||
(gr: number) => number =>
|
||||
pipe (
|
||||
getAllEntriesByGroup (SDA.specialAbilities (wiki))
|
||||
(HA.specialAbilities (state)),
|
||||
countWith (isActive)
|
||||
)
|
||||
|
||||
const getEntrySpecificMinimumLevel =
|
||||
(staticData: StaticDataRecord) =>
|
||||
(hero: HeroModelRecord) =>
|
||||
({
|
||||
isEntryRequiringMatchingScriptAndLangActive,
|
||||
scriptsWithMatchingLanguages,
|
||||
languagesWithMatchingScripts,
|
||||
}: MatchingScriptAndLanguageRelated) =>
|
||||
(x: Record<ActiveObjectWithId>): Maybe<number> => {
|
||||
switch (AOWIA.id (x)) {
|
||||
case AdvantageId.LargeSpellSelection:
|
||||
return pipe_ (
|
||||
hero,
|
||||
countActiveSkillEntries ("spells"),
|
||||
getMinLevelForIncreaseEntry (3)
|
||||
)
|
||||
|
||||
case AdvantageId.ZahlreichePredigten:
|
||||
return pipe_ (
|
||||
24,
|
||||
getSermonsAndVisionsCount (staticData) (hero),
|
||||
getMinLevelForIncreaseEntry (3)
|
||||
)
|
||||
|
||||
case AdvantageId.ZahlreicheVisionen:
|
||||
return pipe_ (
|
||||
27,
|
||||
getSermonsAndVisionsCount (staticData) (hero),
|
||||
getMinLevelForIncreaseEntry (3)
|
||||
)
|
||||
|
||||
case SpecialAbilityId.Literacy: {
|
||||
return isEntryRequiringMatchingScriptAndLangActive
|
||||
&& scriptsWithMatchingLanguages.length < 2
|
||||
&& toNewMaybe (AOWIA.sid (x))
|
||||
.bind (sid => newEnsure (sid, isNumber))
|
||||
.maybe (false, sid => scriptsWithMatchingLanguages.includes (sid))
|
||||
? Just (3)
|
||||
: Nothing
|
||||
}
|
||||
|
||||
case SpecialAbilityId.Language: {
|
||||
return isEntryRequiringMatchingScriptAndLangActive
|
||||
&& languagesWithMatchingScripts.length < 2
|
||||
&& toNewMaybe (AOWIA.sid (x))
|
||||
.bind (sid => newEnsure (sid, isNumber))
|
||||
.maybe (false, sid => languagesWithMatchingScripts.includes (sid))
|
||||
? Just (3)
|
||||
: Nothing
|
||||
}
|
||||
|
||||
case SpecialAbilityId.Imitationszauberei:
|
||||
return pipe_ (
|
||||
hero,
|
||||
HA.spells,
|
||||
elems,
|
||||
countWith (pipe (
|
||||
ASDA.id,
|
||||
lookupF (SDA.spells (staticData)),
|
||||
maybe (false)
|
||||
(pipe (SA.gr, gr => gr === MagicalGroup.Spells))
|
||||
)),
|
||||
ensure (gt (0))
|
||||
)
|
||||
|
||||
default:
|
||||
return Nothing
|
||||
}
|
||||
}
|
||||
|
||||
const getEntrySpecificMaximumLevel =
|
||||
(wiki: StaticDataRecord) =>
|
||||
(hero: HeroModelRecord) =>
|
||||
(entry_id: string): Maybe<number> => {
|
||||
switch (entry_id) {
|
||||
case DisadvantageId.SmallSpellSelection:
|
||||
return pipe_ (hero, countActiveSkillEntries ("spells"), getMaxLevelForDecreaseEntry (3))
|
||||
|
||||
case DisadvantageId.WenigePredigten:
|
||||
return pipe_ (24, getSermonsAndVisionsCount (wiki) (hero), getMaxLevelForDecreaseEntry (3))
|
||||
|
||||
case DisadvantageId.WenigeVisionen:
|
||||
return pipe_ (27, getSermonsAndVisionsCount (wiki) (hero), getMaxLevelForDecreaseEntry (3))
|
||||
|
||||
case SpecialAbilityId.DunklesAbbildDerBuendnisgabe:
|
||||
return pipe_ (hero, HA.pact, fmap (PA.level))
|
||||
|
||||
default:
|
||||
return Nothing
|
||||
}
|
||||
}
|
||||
|
||||
type MinLevelDepSid = string | number | List<number>
|
||||
|
||||
const adjustMinimumLevelByDependencies =
|
||||
(entry: Record<ActiveObjectWithId>) =>
|
||||
flip (foldl ((min_level: Maybe<number>): (dep: ActivatableDependency) => Maybe<number> =>
|
||||
pipe (
|
||||
|
||||
// dependency must include a minimum level, which only occurs
|
||||
// in a DependencyObject
|
||||
ensure (DependencyObject.is),
|
||||
|
||||
// get the level dependency from the object and ensure it's
|
||||
// greater than the current minimum level and that
|
||||
bindF (dep => bind (DOA.tier (dep))
|
||||
|
||||
// new min must be lower than current
|
||||
// min level
|
||||
(ensure (dep_level => sum (min_level) < dep_level
|
||||
|
||||
// if the DependencyObject defines a
|
||||
// sid, too, the entry must match the
|
||||
// sid as well. A DependencyObject
|
||||
// without a sid is valid for all
|
||||
// entries (in case of calculating a
|
||||
// minimum level)
|
||||
&& maybe (true)
|
||||
(flip (Maybe.elem)
|
||||
<MinLevelDepSid>
|
||||
(AOWIA.sid (entry)))
|
||||
(DOA.sid (dep))))),
|
||||
|
||||
// if the current dependency's level is not valid, return
|
||||
// the current minimum
|
||||
flip (alt) (min_level)
|
||||
)))
|
||||
|
||||
/**
|
||||
* Get minimum valid tier.
|
||||
*/
|
||||
export const getMinTier =
|
||||
(wiki: StaticDataRecord) =>
|
||||
(hero: HeroModelRecord) =>
|
||||
(matchingScriptAndLanguageRelated: MatchingScriptAndLanguageRelated) =>
|
||||
(entry: Record<ActiveObjectWithId>) =>
|
||||
(entry_dependencies: List<ActivatableDependency>): Maybe<number> =>
|
||||
adjustMinimumLevelByDependencies (entry)
|
||||
(entry_dependencies)
|
||||
(getEntrySpecificMinimumLevel (wiki)
|
||||
(hero)
|
||||
(matchingScriptAndLanguageRelated)
|
||||
(entry))
|
||||
|
||||
const minMaybe: (mx: Maybe<number>) => (my: Maybe<number>) => Maybe<number> =
|
||||
mx => my => isNothing (mx) ? my : isNothing (my) ? mx : Just (min (fromJust (mx)) (fromJust (my)))
|
||||
|
||||
/**
|
||||
* Get maximum valid tier.
|
||||
*/
|
||||
export const getMaxTier =
|
||||
(wiki: StaticDataRecord) =>
|
||||
(hero: HeroModelRecord) =>
|
||||
(entry_prerequisites: LevelAwarePrerequisites) =>
|
||||
(entry_dependencies: List<ActivatableDependency>) =>
|
||||
(entry_id: string): Maybe<number> => {
|
||||
const entry_specific_max_level = getEntrySpecificMaximumLevel (wiki) (hero) (entry_id)
|
||||
|
||||
if (isOrderedMap (entry_prerequisites)) {
|
||||
return minMaybe (entry_specific_max_level)
|
||||
(validateLevel (wiki)
|
||||
(hero)
|
||||
(entry_prerequisites)
|
||||
(entry_dependencies)
|
||||
(entry_id))
|
||||
}
|
||||
|
||||
return entry_specific_max_level
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given ActiveObject can be removed or changed in tier.
|
||||
* @param obj The ActiveObject with origin id.
|
||||
* @param state The current hero's state.
|
||||
*/
|
||||
export const getIsRemovalOrChangeDisabled =
|
||||
(wiki: StaticDataRecord) =>
|
||||
(hero: HeroModelRecord) =>
|
||||
(matchingScriptAndLanguageRelated: MatchingScriptAndLanguageRelated) =>
|
||||
(entry: Record<ActiveObjectWithId>): Maybe<Record<ActivatableActivationValidation>> =>
|
||||
pipe (
|
||||
getWikiEntry (wiki),
|
||||
bindF<EntryWithCategory, Activatable> (ensure (isActivatableWikiEntry)),
|
||||
bindF (
|
||||
wiki_entry =>
|
||||
pipe_ (
|
||||
entry,
|
||||
AOWIA.id,
|
||||
getHeroStateItem (hero),
|
||||
bindF<Dependent, Record<ActivatableDependent>>
|
||||
(ensure (isActivatableDependent)),
|
||||
fmap ((hero_entry: Record<ActivatableDependent>) => {
|
||||
const minimum_level = getMinTier (wiki)
|
||||
(hero)
|
||||
(matchingScriptAndLanguageRelated)
|
||||
(entry)
|
||||
(ADA.dependencies (hero_entry))
|
||||
|
||||
return ActivatableActivationValidation ({
|
||||
disabled:
|
||||
|
||||
// Disable if a minimum level is required
|
||||
hasRequiredMinimumLevel (minimum_level)
|
||||
(AAL.tiers (wiki_entry))
|
||||
|
||||
// Disable if style special ability is required for
|
||||
// extended special abilities
|
||||
|| isStyleSpecialAbilityRemovalDisabled (hero)
|
||||
(wiki_entry)
|
||||
|
||||
// Disable if other entries depend on this entry
|
||||
|| isEntryDisabledByDependencies (wiki)
|
||||
(hero)
|
||||
(wiki_entry)
|
||||
(hero_entry)
|
||||
(entry)
|
||||
|
||||
// Disable if specific entry conditions disallow
|
||||
// remove
|
||||
|| isRemovalDisabledEntrySpecific (wiki)
|
||||
(hero)
|
||||
(matchingScriptAndLanguageRelated)
|
||||
(wiki_entry)
|
||||
(hero_entry)
|
||||
(entry),
|
||||
minLevel: minimum_level,
|
||||
maxLevel: getMaxTier (wiki)
|
||||
(hero)
|
||||
(AAL.prerequisites (wiki_entry))
|
||||
(ADA.dependencies (hero_entry))
|
||||
(AOWIA.id (entry)),
|
||||
})
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
(AOWIA.id (entry))
|
||||
@@ -1,83 +0,0 @@
|
||||
// import { Activatable } from "../../Models/Wiki/wikiTypeHelpers";
|
||||
// import { ActiveObjectWithId } from "../../Models/ActiveEntries/ActiveObjectWithId";
|
||||
// import { Record } from "../../../Data/Record";
|
||||
// import { Maybe } from "../../../Data/Maybe";
|
||||
// import { ActivatableDependent } from "../../Models/ActiveEntries/ActivatableDependent";
|
||||
// import { HeroModelRecord } from "../../Models/Hero/HeroModel";
|
||||
// import { addDependencies } from "../Dependencies/dependencyUtils";
|
||||
// import { getCombinedPrerequisites } from "./activatableActivationUtils";
|
||||
// import { RequireActivatable, reqToActive, reqToActiveFst } from "../../Models/Wiki/prerequisites/ActivatableRequirement";
|
||||
// import { pipe } from "../pipe";
|
||||
// import { isList, head } from "../../../Data/List";
|
||||
// import { fmap } from "../../../Data/Functor";
|
||||
|
||||
// const RAA = RequireActivatable.A
|
||||
|
||||
// export const addActivatableDeep =
|
||||
// (x: Record<RequireActivatable>) =>
|
||||
// (wiki_entry: Activatable) =>
|
||||
// (mhero_entry: Maybe<Record<ActivatableDependent>>) =>
|
||||
// (hero: HeroModelRecord) => {
|
||||
// const a = reqToActiveFst (x)
|
||||
|
||||
// const combined_prereq = getCombinedPrerequisites (true)
|
||||
// (wiki_entry)
|
||||
// (mhero_entry)
|
||||
// (a)
|
||||
|
||||
// const curr_ids = RAA.id (x)
|
||||
// const curr_id = isList (curr_ids) ? head (curr_ids) : curr_ids
|
||||
|
||||
// return pipe (
|
||||
// (hero: HeroModelRecord) =>
|
||||
// fmap (addDependencies (curr_id)
|
||||
// (combined_prereq))
|
||||
// )
|
||||
// }
|
||||
|
||||
|
||||
// /**
|
||||
// * Adds or removes active instance and related prerequisites based on passed
|
||||
// * functions.
|
||||
// * @param getActive
|
||||
// * @param changeDependencies
|
||||
// * @param changeActive
|
||||
// * @param add If an entry should be added or removed.
|
||||
// */
|
||||
// const changeActiveLength =
|
||||
// (modifyDependencies: typeof addDependencies) =>
|
||||
// (modifyActiveObjects: ident<List<Record<ActiveObject>>>) =>
|
||||
// (add: boolean) =>
|
||||
// (entry: Record<ActiveObject>) =>
|
||||
// (wiki_entry: Activatable) =>
|
||||
// (mhero_entry: Maybe<Record<ActivatableDependent>>) =>
|
||||
// (hero: HeroModelRecord) =>
|
||||
// // Source id
|
||||
// modifyDependencies (id (wiki_entry))
|
||||
|
||||
// // get the prerequisites that need to be applied as
|
||||
// // dependencies to all objects the activation or
|
||||
// // deactivation depends on
|
||||
// (getCombinedPrerequisites (add)
|
||||
// (wiki_entry)
|
||||
// (mhero_entry)
|
||||
// (entry))
|
||||
|
||||
// // modify the list of `ActiveObjects` and pass the hero
|
||||
// // to `changeDependencies` so that it can apply all the
|
||||
// // dependencies to the updated hero
|
||||
// (adjustEntryDef (over (ActivatableDependentL.active) (modifyActiveObjects))
|
||||
// (id (wiki_entry))
|
||||
// (hero))
|
||||
|
||||
// /**
|
||||
// * Activates the entry with the given parameters and adds all needed
|
||||
// * dependencies.
|
||||
// * @param entry The `ActiveObject`.
|
||||
// */
|
||||
// export const activateByObject =
|
||||
// (entry: Record<ActiveObject>) =>
|
||||
// changeActiveLength (addDependencies)
|
||||
// (consF (entry))
|
||||
// (true)
|
||||
// (entry)
|
||||
@@ -1,101 +0,0 @@
|
||||
/**
|
||||
* This file provides several helper functions for working with `Activatable`s.
|
||||
*
|
||||
* @file src/Utilities/activatableConvertUtils.ts
|
||||
* @author Lukas Obermann
|
||||
* @since 1.1.0
|
||||
*/
|
||||
|
||||
import { fmap } from "../../../Data/Functor"
|
||||
import { append, elemF, empty, imap, List } from "../../../Data/List"
|
||||
import { alt, guard, isJust, Nothing, or, then } from "../../../Data/Maybe"
|
||||
import { foldr } from "../../../Data/OrderedMap"
|
||||
import { Record } from "../../../Data/Record"
|
||||
import { AdvantageId, DisadvantageId, SpecialAbilityId } from "../../Constants/Ids"
|
||||
import { ActivatableActivationOptions } from "../../Models/Actions/ActivatableActivationOptions"
|
||||
import { ActivatableDependent } from "../../Models/ActiveEntries/ActivatableDependent"
|
||||
import { ActiveObject } from "../../Models/ActiveEntries/ActiveObject"
|
||||
import { toActiveObjectWithId } from "../../Models/ActiveEntries/ActiveObjectWithId"
|
||||
import { pipe, pipe_ } from "../pipe"
|
||||
|
||||
const ADA = ActivatableDependent.A
|
||||
const AAOA = ActivatableActivationOptions.A
|
||||
|
||||
/**
|
||||
* Converts the object generated by the list item to an object that can be
|
||||
* inserted into an array of ActiveObjects.
|
||||
* @param obj The entry for which you want to convert the object.
|
||||
* @param activate The object generated by the list item.
|
||||
*/
|
||||
export const convertUIStateToActiveObject =
|
||||
(activate: Record<ActivatableActivationOptions>): Record<ActiveObject> => {
|
||||
const id = AAOA.id (activate)
|
||||
const selectOptionId1 = AAOA.selectOptionId1 (activate)
|
||||
const selectOptionId2 = AAOA.selectOptionId2 (activate)
|
||||
const selectOptionId3 = AAOA.selectOptionId3 (activate)
|
||||
const input = AAOA.input (activate)
|
||||
const level = AAOA.level (activate)
|
||||
const customCost = AAOA.customCost (activate)
|
||||
|
||||
return id === AdvantageId.HatredOf
|
||||
? ActiveObject ({
|
||||
sid: selectOptionId1,
|
||||
sid2: input,
|
||||
cost: customCost,
|
||||
})
|
||||
: id === DisadvantageId.PersonalityFlaw
|
||||
? ActiveObject ({
|
||||
sid: selectOptionId1,
|
||||
sid2: or (fmap (elemF (List<number | string> (7, 8)))
|
||||
(selectOptionId1))
|
||||
? input
|
||||
: Nothing,
|
||||
cost: customCost,
|
||||
})
|
||||
: id === SpecialAbilityId.SkillSpecialization
|
||||
? ActiveObject ({
|
||||
sid: selectOptionId1,
|
||||
sid2: alt<number | string> (input)
|
||||
(selectOptionId2),
|
||||
cost: customCost,
|
||||
})
|
||||
: isJust (input) && isJust (selectOptionId1)
|
||||
? ActiveObject ({
|
||||
sid: selectOptionId1,
|
||||
sid2: input,
|
||||
sid3: selectOptionId2,
|
||||
tier: level,
|
||||
cost: customCost,
|
||||
})
|
||||
: ActiveObject ({
|
||||
sid: alt<number | string> (input)
|
||||
(selectOptionId1),
|
||||
sid2: then (guard (isJust (input) || isJust (selectOptionId1)))
|
||||
(selectOptionId2),
|
||||
sid3: selectOptionId3,
|
||||
tier: level,
|
||||
cost: customCost,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a list of ActiveObjects based on the given instance.
|
||||
*/
|
||||
export const convertActivatableToArray =
|
||||
(x: Record<ActivatableDependent>) =>
|
||||
pipe_ (
|
||||
x,
|
||||
ADA.active,
|
||||
imap (index => toActiveObjectWithId (index) (ADA.id (x)))
|
||||
)
|
||||
|
||||
/**
|
||||
* Get all active items in an array.
|
||||
* @param state A state slice.
|
||||
*/
|
||||
export const getActiveFromState =
|
||||
foldr (pipe (convertActivatableToArray, append)) (empty)
|
||||
|
||||
export interface ActiveObjectAny extends ActiveObject {
|
||||
[key: string]: any
|
||||
}
|
||||
@@ -1,955 +0,0 @@
|
||||
/**
|
||||
* Get the needed options for `Activatable` entries that are available to
|
||||
* activate.
|
||||
*
|
||||
* @file src/Utilities/activatableInactiveUtils.ts
|
||||
* @author Lukas Obermann
|
||||
* @since 1.1.0
|
||||
*/
|
||||
|
||||
import { notP } from "../../../Data/Bool"
|
||||
import { equals, notEquals } from "../../../Data/Eq"
|
||||
import { cnst, ident } from "../../../Data/Function"
|
||||
import { fmap, fmapF, mapReplace } from "../../../Data/Functor"
|
||||
import { over, set } from "../../../Data/Lens"
|
||||
import { consF, countWith, elem, elemF, filter, find, flength, fnull, foldr, isList, List, map, mapByIdKeyMap, notElem, notElemF, notNull, nub, subscript } from "../../../Data/List"
|
||||
import { all, ap, bind, bindF, ensure, fromJust, fromMaybe, guard, isJust, isNothing, join, Just, liftM2, listToMaybe, Maybe, maybe, maybeToList, Nothing, or, thenF } from "../../../Data/Maybe"
|
||||
import { add, gt, gte, inc, multiply } from "../../../Data/Num"
|
||||
import { alter, elems, foldrWithKey, isOrderedMap, lookup, lookupF, member, OrderedMap } from "../../../Data/OrderedMap"
|
||||
import { Record, RecordI } from "../../../Data/Record"
|
||||
import { filterMapListT, filterT, mapT } from "../../../Data/Transducer"
|
||||
import { traceShowId } from "../../../Debug/Trace"
|
||||
import { Aspect, CombatTechniqueGroupId, MagicalTradition, SkillGroup, SpecialAbilityGroup } from "../../Constants/Groups"
|
||||
import { AdvantageId, DisadvantageId, SkillId, SpecialAbilityId } from "../../Constants/Ids"
|
||||
import { ActivatableDependent } from "../../Models/ActiveEntries/ActivatableDependent"
|
||||
import { ActivatableSkillDependent } from "../../Models/ActiveEntries/ActivatableSkillDependent"
|
||||
import { ActiveObject } from "../../Models/ActiveEntries/ActiveObject"
|
||||
import { SkillDependent } from "../../Models/ActiveEntries/SkillDependent"
|
||||
import { HeroModel, HeroModelRecord } from "../../Models/Hero/HeroModel"
|
||||
import { Pact } from "../../Models/Hero/Pact"
|
||||
import { Rules } from "../../Models/Hero/Rules"
|
||||
import { AdventurePointsCategories } from "../../Models/View/AdventurePointsCategories"
|
||||
import { InactiveActivatable, InactiveActivatableL } from "../../Models/View/InactiveActivatable"
|
||||
import { Advantage } from "../../Models/Wiki/Advantage"
|
||||
import { LiturgicalChant } from "../../Models/Wiki/LiturgicalChant"
|
||||
import { Skill } from "../../Models/Wiki/Skill"
|
||||
import { Spell } from "../../Models/Wiki/Spell"
|
||||
import { Application } from "../../Models/Wiki/sub/Application"
|
||||
import { SelectOption, SelectOptionL } from "../../Models/Wiki/sub/SelectOption"
|
||||
import { StaticData, StaticDataRecord } from "../../Models/Wiki/WikiModel"
|
||||
import { Activatable } from "../../Models/Wiki/wikiTypeHelpers"
|
||||
import { MatchingScriptAndLanguageRelated } from "../../Selectors/activatableSelectors"
|
||||
import { composeT } from "../compose"
|
||||
import { filterUnfamiliar } from "../Dependencies/TransferredUnfamiliarUtils"
|
||||
import { countActiveGroupEntries } from "../entryGroupUtils"
|
||||
import { getAllEntriesByGroup } from "../heroStateUtils"
|
||||
import { getAPForActivatation } from "../ImprovementCost"
|
||||
import { getTraditionOfAspect } from "../Increasable/liturgicalChantUtils"
|
||||
import { isUnfamiliarSpell } from "../Increasable/spellUtils"
|
||||
import { ensure as newensure, toNewMaybe } from "../Maybe"
|
||||
import { pipe, pipe_ } from "../pipe"
|
||||
import { validateLevel, validatePrerequisites } from "../Prerequisites/validatePrerequisitesUtils"
|
||||
import { isEntryAvailable } from "../RulesUtils"
|
||||
import { sortRecordsByName } from "../sortBy"
|
||||
import { isNumber, isString, isStringM, misNumberM, misStringM } from "../typeCheckUtils"
|
||||
import { getMaxLevelForDecreaseEntry, getSermonsAndVisionsCount } from "./activatableActiveValidationUtils"
|
||||
import { isAdditionDisabled } from "./activatableInactiveValidationUtils"
|
||||
import { modifyByLevel } from "./activatableModifierUtils"
|
||||
import { countActiveSkillEntries } from "./activatableSkillUtils"
|
||||
import { isMaybeActive } from "./isActive"
|
||||
import { getActiveSecondarySelections, getActiveSelections, getActiveSelectionsMaybe, getRequiredSelections } from "./selectionUtils"
|
||||
import { getBlessedTradition, getMagicalTraditionsHeroEntries, mapBlessedNumIdToTradId } from "./traditionUtils"
|
||||
|
||||
const SDA = StaticData.A
|
||||
const HA = HeroModel.A
|
||||
const AAL = Advantage.AL
|
||||
const LCA = LiturgicalChant.A
|
||||
const SpA = Spell.A
|
||||
const ADA = ActivatableDependent.A
|
||||
const ASDA = ActivatableSkillDependent.A
|
||||
const SkDA = SkillDependent.A
|
||||
const SOA = SelectOption.A
|
||||
const AppA = Application.A
|
||||
const AOA = ActiveObject.A
|
||||
const SpAL = Spell.AL
|
||||
const SA = Skill.A
|
||||
const RA = Rules.A
|
||||
const SOL = SelectOptionL
|
||||
const IAA = InactiveActivatable.A
|
||||
const IAL = InactiveActivatableL
|
||||
const PA = Pact.A
|
||||
const APCA = AdventurePointsCategories.A
|
||||
|
||||
/**
|
||||
* `isNotActive :: Maybe ActivatableDependent -> SelectOption -> Bool`
|
||||
*
|
||||
* Test if the id of the passed select option is activated for the passed
|
||||
* `ActivatableDependent`.
|
||||
*/
|
||||
const isNotActive =
|
||||
pipe (
|
||||
getActiveSelectionsMaybe,
|
||||
fromMaybe<List<string | number>> (List.empty),
|
||||
activeSelections => pipe (
|
||||
SOA.id,
|
||||
notElemF (activeSelections)
|
||||
)
|
||||
)
|
||||
|
||||
/**
|
||||
* `areNoSameActive :: Maybe ActivatableDependent -> SelectOption -> Bool`
|
||||
*
|
||||
* Test if a select option is not activated more than once for the passed
|
||||
* `ActivatableDependent`.
|
||||
*/
|
||||
const areNoSameActive =
|
||||
pipe (
|
||||
getActiveSelectionsMaybe,
|
||||
fromMaybe<List<string | number>> (List.empty),
|
||||
activeSelections => pipe (
|
||||
SOA.id,
|
||||
current_id => countWith (equals (current_id)) (activeSelections) < 2
|
||||
)
|
||||
)
|
||||
|
||||
/**
|
||||
* `isNotRequired :: Maybe ActivatableDependent -> SelectOption -> Bool`
|
||||
*
|
||||
* Test if the id of the passed select option is required for the passed
|
||||
* `ActivatableDependent`.
|
||||
*/
|
||||
const isNotRequired =
|
||||
pipe (
|
||||
getRequiredSelections,
|
||||
fromMaybe<List<string | number | List<number>>> (List.empty),
|
||||
requiredSelections => pipe (
|
||||
SOA.id,
|
||||
notElemF (requiredSelections)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
/**
|
||||
* `isNotRequiredNotActive :: Maybe ActivatableDependent -> SelectOption -> Bool`
|
||||
*
|
||||
* Test if the id of the passed select option is neither required nor activated
|
||||
* for the passed `ActivatableDependent`.
|
||||
*/
|
||||
const isNotRequiredNotActive =
|
||||
(mhero_entry: Maybe<Record<ActivatableDependent>>) => {
|
||||
const isNoActiveSelection = isNotActive (mhero_entry)
|
||||
const isNoRequiredSelection = isNotRequired (mhero_entry)
|
||||
|
||||
return (e: Record<SelectOption>) =>
|
||||
isNoActiveSelection (e) && isNoRequiredSelection (e)
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the value at the specified key by `1`. If there is no value at that
|
||||
* key, the value will be set to `0`.
|
||||
*/
|
||||
const incMapVal = alter (pipe (maybe (1) (inc), Just))
|
||||
|
||||
const addChantToCounter =
|
||||
(chant: Record<LiturgicalChant>) =>
|
||||
(mp: OrderedMap<number, number>) =>
|
||||
foldr ((aspect: Aspect) => OrderedMap.insertWith (add) <number> (aspect) (1))
|
||||
(mp)
|
||||
(LCA.aspects (chant))
|
||||
|
||||
const addSpellToCounter = pipe (SpA.property, incMapVal)
|
||||
|
||||
const filterSkillsGte10 = filter (pipe (ASDA.value, gte (10)))
|
||||
|
||||
const foldCounter =
|
||||
foldrWithKey<number, number, List<number>> (k => x => x >= 3 ? consF (k) : ident)
|
||||
(List.empty)
|
||||
|
||||
/**
|
||||
* `getPropsWith3Gte10 :: Wiki -> Hero -> [Int]`
|
||||
*
|
||||
* Returns a list containing all properties where at least 3 spells with at
|
||||
* least SR 10 belong to.
|
||||
*/
|
||||
const getPropsWith3Gte10 =
|
||||
(staticData: StaticDataRecord) =>
|
||||
pipe (
|
||||
HA.spells,
|
||||
elems,
|
||||
filterSkillsGte10,
|
||||
mapByIdKeyMap (SDA.spells (staticData)),
|
||||
foldr (addSpellToCounter) (OrderedMap.empty),
|
||||
foldCounter
|
||||
)
|
||||
|
||||
/**
|
||||
* `getAspectsWith3Gte10 :: Wiki -> Hero -> [Int]`
|
||||
*
|
||||
* Returns a list containing all aspects where at least 3 chants with at least
|
||||
* SR 10 belong to.
|
||||
*/
|
||||
const getAspectsWith3Gte10 =
|
||||
(staticData: StaticDataRecord) =>
|
||||
pipe (
|
||||
HA.liturgicalChants,
|
||||
elems,
|
||||
filterSkillsGte10,
|
||||
traceShowId,
|
||||
mapByIdKeyMap (SDA.liturgicalChants (staticData)),
|
||||
traceShowId,
|
||||
foldr (addChantToCounter) (OrderedMap.empty),
|
||||
traceShowId,
|
||||
OrderedMap.foldrWithKey ((k: number) => (x: number) => (xs: List<number>) =>
|
||||
x >= 3 ? consF (k) (xs) : xs)
|
||||
(List.empty),
|
||||
traceShowId,
|
||||
)
|
||||
|
||||
const isSocialSkill =
|
||||
(staticData: StaticDataRecord) =>
|
||||
pipe (
|
||||
SOA.id,
|
||||
ensure (isString),
|
||||
bindF (lookupF (SDA.skills (staticData))),
|
||||
fmap (pipe (SA.gr, equals (SkillGroup.Social))),
|
||||
or
|
||||
)
|
||||
|
||||
const isAddExistSkillSpecAllowed =
|
||||
(hero: HeroModelRecord) =>
|
||||
(counter: OrderedMap<string | number, List<string | number>>) =>
|
||||
(curr_select_id: string | number) =>
|
||||
pipe_ (
|
||||
curr_select_id,
|
||||
ensure (isString),
|
||||
bindF (lookupF (HA.skills (hero))),
|
||||
liftM2 ((apps: List<string | number>) =>
|
||||
(skill: Record<SkillDependent>) =>
|
||||
flength (apps) < 3 && SkDA.value (skill) >= (flength (apps) + 1) * 6)
|
||||
(lookupF (counter) (curr_select_id)),
|
||||
or
|
||||
)
|
||||
|
||||
const isAddNotExistSkillSpecAllowed =
|
||||
(hero: HeroModelRecord) =>
|
||||
(selectId: string | number) =>
|
||||
newensure (selectId, isString)
|
||||
.bind (id => toNewMaybe (lookup (id) (HA.skills (hero))))
|
||||
.maybe (false, skill => SkDA.value (skill) >= 6)
|
||||
|
||||
const is3or4 = (x: string | number): x is number => x === 3 || x === 4
|
||||
|
||||
/**
|
||||
* Modifies the select options of specific entries to match current conditions.
|
||||
*/
|
||||
const modifySelectOptions =
|
||||
(staticData: StaticDataRecord) =>
|
||||
(hero: HeroModelRecord) =>
|
||||
(hero_magical_traditions: List<Record<ActivatableDependent>>) =>
|
||||
(wiki_entry: Activatable) =>
|
||||
(mhero_entry: Maybe<Record<ActivatableDependent>>): ident<Maybe<List<Record<SelectOption>>>> => {
|
||||
const current_id = AAL.id (wiki_entry)
|
||||
|
||||
const isAvailable =
|
||||
composeT (
|
||||
filterT (isEntryAvailable (SDA.books (staticData))
|
||||
(RA.enabledRuleBooks (HA.rules (hero)))
|
||||
(RA.enableAllRuleBooks (HA.rules (hero)))
|
||||
(SOA.src)),
|
||||
filterT (pipe (
|
||||
SOA.prerequisites,
|
||||
Maybe.all (reqs => validatePrerequisites (staticData)
|
||||
(hero)
|
||||
(reqs)
|
||||
(current_id))
|
||||
))
|
||||
)
|
||||
|
||||
const isNoRequiredOrActiveSelection =
|
||||
composeT (isAvailable, filterT (isNotRequiredNotActive (mhero_entry)))
|
||||
|
||||
const isNoRequiredSelection =
|
||||
composeT (isAvailable, filterT (isNotRequired (mhero_entry)))
|
||||
|
||||
switch (current_id) {
|
||||
case AdvantageId.ExceptionalSkill: {
|
||||
const hasLessThanTwoSameIdActiveSelections = filterT (areNoSameActive (mhero_entry))
|
||||
|
||||
return fmap (filterMapListT (composeT (
|
||||
isNoRequiredSelection,
|
||||
hasLessThanTwoSameIdActiveSelections
|
||||
)))
|
||||
}
|
||||
|
||||
case DisadvantageId.PersonalityFlaw: {
|
||||
const unique_selections = maybe (List<number> ())
|
||||
(pipe (
|
||||
getActiveSelections,
|
||||
filter (isNumber),
|
||||
nub
|
||||
))
|
||||
(mhero_entry)
|
||||
|
||||
const isInfiniteActive: (id: number) => (x: Record<SelectOption>) => boolean =
|
||||
id => x => SOA.id (x) === id && elem (id) (unique_selections)
|
||||
|
||||
const isPrejudiceAndActive = isInfiniteActive (7)
|
||||
|
||||
const isUnworldlyAndActive = isInfiniteActive (8)
|
||||
|
||||
const isNotActiveAndMaxNotReached: (x: Record<SelectOption>) => boolean =
|
||||
x => isNotActive (mhero_entry) (x)
|
||||
&& isNotRequired (mhero_entry) (x)
|
||||
&& flength (unique_selections) < 2
|
||||
|
||||
const filterOptions =
|
||||
composeT (
|
||||
isAvailable,
|
||||
filterT (a => isPrejudiceAndActive (a)
|
||||
|| isUnworldlyAndActive (a)
|
||||
|| isNotActiveAndMaxNotReached (a))
|
||||
)
|
||||
|
||||
return fmap (filterMapListT (filterOptions))
|
||||
}
|
||||
|
||||
case DisadvantageId.NegativeTrait:
|
||||
case DisadvantageId.Maimed:
|
||||
return fmap (filterMapListT (isNoRequiredOrActiveSelection))
|
||||
|
||||
case DisadvantageId.Incompetent: {
|
||||
const isAdvActive =
|
||||
pipe (lookupF (HA.advantages (hero)), isMaybeActive)
|
||||
|
||||
const isNotSocialSkill = notP (isSocialSkill (staticData))
|
||||
|
||||
return fmap (filterMapListT (composeT (
|
||||
isNoRequiredOrActiveSelection,
|
||||
filterT (e =>
|
||||
|
||||
// Socially Adaptable and Inspire Confidence
|
||||
// require no Incompetence in social skills
|
||||
(isAdvActive (AdvantageId.SociallyAdaptable)
|
||||
|| isAdvActive (AdvantageId.InspireConfidence)
|
||||
? isNotSocialSkill (e)
|
||||
: true))
|
||||
)))
|
||||
}
|
||||
|
||||
case SpecialAbilityId.SkillSpecialization: {
|
||||
const mcounter = getActiveSecondarySelections (mhero_entry)
|
||||
|
||||
return fmap (filterMapListT (composeT (
|
||||
isNoRequiredSelection,
|
||||
filterT (e => {
|
||||
const curr_select_id = SOA.id (e)
|
||||
|
||||
// if mcounter is available, mhero_entry must be a Just and thus
|
||||
// there can be active selections
|
||||
if (isJust (mcounter)) {
|
||||
const counter = fromJust (mcounter)
|
||||
|
||||
if (member (curr_select_id) (counter)) {
|
||||
return isAddExistSkillSpecAllowed (hero)
|
||||
(counter)
|
||||
(curr_select_id)
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise we only need to check if the skill rating is at
|
||||
// least 6, as there can't be an activated selection.
|
||||
return isAddNotExistSkillSpecAllowed (hero) (curr_select_id)
|
||||
}),
|
||||
mapT (e => {
|
||||
const curr_select_id = SOA.id (e)
|
||||
|
||||
const mcounts = bind (mcounter) (lookup (curr_select_id))
|
||||
|
||||
const adjustSelectOption =
|
||||
pipe (
|
||||
over (SOL.cost)
|
||||
(isJust (mcounts)
|
||||
|
||||
// Increase cost if there are active specializations
|
||||
// for the same skill
|
||||
? fmap (multiply (flength (fromJust (mcounts)) + 1))
|
||||
|
||||
// otherwise return current cost
|
||||
: ident),
|
||||
over (SOL.applications)
|
||||
(fmap (filter ((app: Record<Application>) => {
|
||||
const isInactive =
|
||||
all (notElem<number | string>
|
||||
(AppA.id (app)))
|
||||
(mcounts)
|
||||
|
||||
const arePrerequisitesMet =
|
||||
validatePrerequisites (staticData)
|
||||
(hero)
|
||||
(maybeToList (AppA.prerequisite (app)))
|
||||
(current_id)
|
||||
|
||||
return isInactive
|
||||
&& arePrerequisitesMet
|
||||
})))
|
||||
)
|
||||
|
||||
return adjustSelectOption (e)
|
||||
})
|
||||
)))
|
||||
}
|
||||
|
||||
case SpecialAbilityId.TraditionGuildMages: {
|
||||
return fmap (filterUnfamiliar (pipe (
|
||||
SpA.tradition,
|
||||
trads => notElem (MagicalTradition.General) (trads)
|
||||
&& notElem (MagicalTradition.GuildMages) (trads)
|
||||
))
|
||||
(staticData))
|
||||
}
|
||||
|
||||
case SpecialAbilityId.PropertyKnowledge: {
|
||||
const isValidProperty =
|
||||
filterT (pipe (SOA.id, elemF<string | number> (getPropsWith3Gte10 (staticData) (hero))))
|
||||
|
||||
return fmap (filterMapListT (composeT (
|
||||
isNoRequiredOrActiveSelection,
|
||||
isValidProperty
|
||||
)))
|
||||
}
|
||||
|
||||
case SpecialAbilityId.PropertyFocus: {
|
||||
const isActivePropertyKnowledge =
|
||||
filterT (notP (pipe_ (
|
||||
hero,
|
||||
HA.specialAbilities,
|
||||
lookup<string> (SpecialAbilityId.PropertyKnowledge),
|
||||
isNotActive
|
||||
)))
|
||||
|
||||
return fmap (filterMapListT (composeT (
|
||||
isNoRequiredOrActiveSelection,
|
||||
isActivePropertyKnowledge
|
||||
)))
|
||||
}
|
||||
|
||||
case SpecialAbilityId.AspectKnowledge: {
|
||||
const valid_aspects = getAspectsWith3Gte10 (staticData) (hero)
|
||||
|
||||
traceShowId (valid_aspects)
|
||||
|
||||
const isAspectValid = pipe (SOA.id, elemF<string | number> (valid_aspects))
|
||||
|
||||
return maybe (ident as ident<Maybe<List<Record<SelectOption>>>>)
|
||||
((blessed_trad: Record<ActivatableDependent>) =>
|
||||
fmap (filterMapListT (composeT (
|
||||
filterT (pipe (
|
||||
SOA.id,
|
||||
ensure (isNumber),
|
||||
bindF (pipe (
|
||||
getTraditionOfAspect,
|
||||
mapBlessedNumIdToTradId
|
||||
)),
|
||||
Maybe.elem (AAL.id (blessed_trad))
|
||||
)),
|
||||
isNoRequiredOrActiveSelection,
|
||||
filterT (isAspectValid)
|
||||
))))
|
||||
(getBlessedTradition (HA.specialAbilities (hero)))
|
||||
}
|
||||
|
||||
case SpecialAbilityId.AdaptionZauber: {
|
||||
const isWikiEntryFromUnfamiliarTrad =
|
||||
isUnfamiliarSpell (HA.transferredUnfamiliarSpells (hero))
|
||||
(hero_magical_traditions)
|
||||
|
||||
const isSpellAbove10 =
|
||||
pipe (
|
||||
SOA.id,
|
||||
ensure (isString),
|
||||
bindF (lookupF (HA.spells (hero))),
|
||||
maybe (false) (pipe (ASDA.value, gte (10)))
|
||||
)
|
||||
|
||||
const isFromUnfamiliarTrad =
|
||||
pipe (
|
||||
SOA.id,
|
||||
ensure (isString),
|
||||
bindF (lookupF (SDA.spells (staticData))),
|
||||
maybe (false) (isWikiEntryFromUnfamiliarTrad)
|
||||
)
|
||||
|
||||
return fmap (filterMapListT (composeT (
|
||||
isNoRequiredOrActiveSelection,
|
||||
filterT (isSpellAbove10),
|
||||
filterT (isFromUnfamiliarTrad)
|
||||
)))
|
||||
}
|
||||
|
||||
case SpecialAbilityId.SpellEnhancement:
|
||||
case SpecialAbilityId.ChantEnhancement: {
|
||||
const getTargetHeroEntry = current_id === SpecialAbilityId.SpellEnhancement
|
||||
? bindF (lookupF (HA.spells (hero)))
|
||||
: bindF (lookupF (HA.liturgicalChants (hero)))
|
||||
|
||||
const getTargetWikiEntry:
|
||||
((x: Maybe<string>) => Maybe<Record<Spell> | Record<LiturgicalChant>>) =
|
||||
current_id === SpecialAbilityId.SpellEnhancement
|
||||
? bindF (lookupF (SDA.spells (staticData)))
|
||||
: bindF (lookupF (SDA.liturgicalChants (staticData)))
|
||||
|
||||
const isNotUnfamiliar =
|
||||
(x: Record<Spell> | Record<LiturgicalChant>) =>
|
||||
LiturgicalChant.is (x)
|
||||
|| !isUnfamiliarSpell (HA.transferredUnfamiliarSpells (hero))
|
||||
(hero_magical_traditions)
|
||||
(x)
|
||||
|
||||
return fmap (foldr (isNoRequiredOrActiveSelection (e => {
|
||||
const mtarget_hero_entry = getTargetHeroEntry (SOA.target (e))
|
||||
const mtarget_wiki_entry = getTargetWikiEntry (SOA.target (e))
|
||||
|
||||
if (
|
||||
isJust (mtarget_wiki_entry)
|
||||
&& isJust (mtarget_hero_entry)
|
||||
&& isNotUnfamiliar (fromJust (mtarget_wiki_entry))
|
||||
&& ASDA.value (fromJust (mtarget_hero_entry))
|
||||
>= maybe (0)
|
||||
(pipe (multiply (4), add (4)))
|
||||
(SOA.level (e))
|
||||
) {
|
||||
const target_wiki_entry = fromJust (mtarget_wiki_entry)
|
||||
|
||||
return consF (
|
||||
set (SOL.name)
|
||||
(`${SpAL.name (target_wiki_entry)}: ${SOA.name (e)}`)
|
||||
(e)
|
||||
)
|
||||
}
|
||||
|
||||
return ident as ident<List<Record<SelectOption>>>
|
||||
}))
|
||||
(List ()))
|
||||
}
|
||||
|
||||
case SpecialAbilityId.LanguageSpecializations: {
|
||||
return pipe_ (
|
||||
staticData,
|
||||
SDA.specialAbilities,
|
||||
lookup<string> (SpecialAbilityId.Language),
|
||||
bindF (AAL.select),
|
||||
maybe (cnst (Nothing) as ident<Maybe<List<Record<SelectOption>>>>)
|
||||
(current_select => {
|
||||
const available_langs =
|
||||
|
||||
// Pair: fst = sid, snd = current_level
|
||||
maybe (List<number> ())
|
||||
(pipe (
|
||||
ADA.active,
|
||||
foldr ((obj: Record<ActiveObject>) =>
|
||||
pipe_ (
|
||||
obj,
|
||||
AOA.tier,
|
||||
bindF (current_level =>
|
||||
pipe_ (
|
||||
guard (is3or4 (current_level)),
|
||||
thenF (AOA.sid (obj)),
|
||||
misNumberM,
|
||||
fmap ((level: number) => consF (level))
|
||||
)),
|
||||
fromMaybe (
|
||||
ident as ident<List<number>>
|
||||
)
|
||||
))
|
||||
(List ())
|
||||
))
|
||||
(pipe_ (
|
||||
hero,
|
||||
HA.specialAbilities,
|
||||
lookup<string> (SpecialAbilityId.Language)
|
||||
))
|
||||
|
||||
const filterLanguages =
|
||||
foldr (isNoRequiredSelection
|
||||
(e => {
|
||||
const lang =
|
||||
find ((l: number) => l === SOA.id (e))
|
||||
(available_langs)
|
||||
|
||||
// a language must provide either a set of
|
||||
// possible specializations or an input where a
|
||||
// custom specialization can be entered.
|
||||
const provides_specific_or_input =
|
||||
Maybe.any (notNull) (SOA.specializations (e))
|
||||
|| isJust (SOA.specializationInput (e))
|
||||
|
||||
if (isJust (lang) && provides_specific_or_input) {
|
||||
return consF (e)
|
||||
}
|
||||
|
||||
return ident as ident<List<Record<SelectOption>>>
|
||||
}))
|
||||
(List ())
|
||||
|
||||
return cnst (Just (filterLanguages (current_select)))
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
case SpecialAbilityId.MadaschwesternStil: {
|
||||
return fmap (filterUnfamiliar (pipe (
|
||||
SpA.tradition,
|
||||
trads => notElem (MagicalTradition.General) (trads)
|
||||
&& notElem (MagicalTradition.Witches) (trads)
|
||||
))
|
||||
(staticData))
|
||||
}
|
||||
|
||||
case SpecialAbilityId.ScholarDesMagierkollegsZuHoningen: {
|
||||
const allowed_traditions = List (
|
||||
MagicalTradition.Druids,
|
||||
MagicalTradition.Elves,
|
||||
MagicalTradition.Witches
|
||||
)
|
||||
|
||||
const mtransferred_spell_trads = pipe_ (
|
||||
HA.specialAbilities (hero),
|
||||
lookup<string> (SpecialAbilityId.TraditionGuildMages),
|
||||
bindF (pipe (ADA.active, listToMaybe)),
|
||||
bindF (pipe (AOA.sid, isStringM)),
|
||||
bindF (lookupF (SDA.spells (staticData))),
|
||||
fmap (SpA.tradition)
|
||||
)
|
||||
|
||||
if (isNothing (mtransferred_spell_trads)) {
|
||||
return ident
|
||||
}
|
||||
|
||||
const transferred_spell_trads = fromJust (mtransferred_spell_trads)
|
||||
|
||||
// Contains all allowed trads the first spell does not have
|
||||
const trad_diff = filter (notElemF (transferred_spell_trads))
|
||||
(allowed_traditions)
|
||||
|
||||
const has_transferred_all_traditions_allowed = fnull (trad_diff)
|
||||
|
||||
return fmap (filterUnfamiliar (pipe (
|
||||
SpA.tradition,
|
||||
has_transferred_all_traditions_allowed
|
||||
? trads =>
|
||||
notElem (MagicalTradition.General) (trads)
|
||||
&& List.any (elemF (allowed_traditions)) (trads)
|
||||
: trads =>
|
||||
notElem (MagicalTradition.General) (trads)
|
||||
&& List.any (elemF (trad_diff)) (trads)
|
||||
))
|
||||
(staticData))
|
||||
}
|
||||
|
||||
default:
|
||||
return fmap (filterMapListT (isNoRequiredOrActiveSelection))
|
||||
}
|
||||
}
|
||||
|
||||
type OtherOptionsModifier = ident<Record<InactiveActivatable>>
|
||||
|
||||
const getSermonOrVisionCountMax =
|
||||
(hero: HeroModelRecord) =>
|
||||
(adv_id: string) =>
|
||||
(disadv_id: string) =>
|
||||
modifyByLevel (3)
|
||||
(lookup (adv_id) (HA.advantages (hero)))
|
||||
(lookup (disadv_id) (HA.disadvantages (hero)))
|
||||
|
||||
const modifyOtherOptions =
|
||||
(wiki: StaticDataRecord) =>
|
||||
(hero: HeroModelRecord) =>
|
||||
(adventure_points: Record<AdventurePointsCategories>) =>
|
||||
(wiki_entry: Activatable) =>
|
||||
(mhero_entry: Maybe<Record<ActivatableDependent>>): Maybe<OtherOptionsModifier> => {
|
||||
const current_id = AAL.id (wiki_entry)
|
||||
|
||||
switch (current_id) {
|
||||
case DisadvantageId.WenigePredigten:
|
||||
return pipe_ (
|
||||
SpecialAbilityGroup.Predigten,
|
||||
getSermonsAndVisionsCount (wiki) (hero),
|
||||
getMaxLevelForDecreaseEntry (3),
|
||||
set (IAL.maxLevel),
|
||||
Just
|
||||
)
|
||||
|
||||
case DisadvantageId.WenigeVisionen:
|
||||
return pipe_ (
|
||||
SpecialAbilityGroup.Visionen,
|
||||
getSermonsAndVisionsCount (wiki) (hero),
|
||||
getMaxLevelForDecreaseEntry (3),
|
||||
set (IAL.maxLevel),
|
||||
Just
|
||||
)
|
||||
|
||||
case DisadvantageId.SmallSpellSelection: {
|
||||
return pipe_ (
|
||||
hero,
|
||||
countActiveSkillEntries ("spells"),
|
||||
getMaxLevelForDecreaseEntry (3),
|
||||
set (IAL.maxLevel),
|
||||
Just
|
||||
)
|
||||
}
|
||||
|
||||
case SpecialAbilityId.CraftInstruments: {
|
||||
return join (liftM2 (
|
||||
(woodworking: Record<SkillDependent>) =>
|
||||
(metalworking: Record<SkillDependent>) =>
|
||||
SkDA.value (woodworking) + SkDA.value (metalworking) >= 12
|
||||
? Just (ident)
|
||||
: Nothing
|
||||
)
|
||||
(pipe (HA.skills, lookup<string> (SkillId.Woodworking)) (hero))
|
||||
(pipe (HA.skills, lookup<string> (SkillId.Metalworking)) (hero)))
|
||||
}
|
||||
|
||||
case SpecialAbilityId.Hunter: {
|
||||
return pipe_ (
|
||||
CombatTechniqueGroupId.Ranged,
|
||||
getAllEntriesByGroup (SDA.combatTechniques (wiki))
|
||||
(HA.combatTechniques (hero)),
|
||||
filter (pipe (SkDA.value, gte (10))),
|
||||
flength,
|
||||
ensure (gt (0)),
|
||||
mapReplace (ident)
|
||||
)
|
||||
}
|
||||
|
||||
case SpecialAbilityId.TraditionGuildMages:
|
||||
case SpecialAbilityId.TraditionWitches:
|
||||
case SpecialAbilityId.TraditionElves:
|
||||
case SpecialAbilityId.TraditionDruids:
|
||||
case SpecialAbilityId.TraditionIllusionist:
|
||||
case SpecialAbilityId.TraditionQabalyaMage:
|
||||
case SpecialAbilityId.TraditionGeoden:
|
||||
case SpecialAbilityId.TraditionZauberalchimisten:
|
||||
case SpecialAbilityId.TraditionSchelme:
|
||||
case SpecialAbilityId.TraditionBrobimGeoden: {
|
||||
return pipe_ (
|
||||
hero,
|
||||
HA.specialAbilities,
|
||||
getMagicalTraditionsHeroEntries,
|
||||
ensure (List.fnull),
|
||||
mapReplace (ident)
|
||||
)
|
||||
}
|
||||
|
||||
case SpecialAbilityId.PropertyKnowledge:
|
||||
case SpecialAbilityId.AspectKnowledge: {
|
||||
return pipe_ (
|
||||
wiki_entry,
|
||||
AAL.cost,
|
||||
bindF<number | List<number>, List<number>> (ensure (isList)),
|
||||
bindF (costs => subscript (costs)
|
||||
(maybe (0)
|
||||
(pipe (ADA.active, flength))
|
||||
(mhero_entry))),
|
||||
fmap ((cost: number) => pipe_ (cost, Just, set (IAL.cost)))
|
||||
)
|
||||
}
|
||||
|
||||
case SpecialAbilityId.TraditionChurchOfPraios:
|
||||
case SpecialAbilityId.TraditionChurchOfRondra:
|
||||
case SpecialAbilityId.TraditionChurchOfBoron:
|
||||
case SpecialAbilityId.TraditionChurchOfHesinde:
|
||||
case SpecialAbilityId.TraditionChurchOfPhex:
|
||||
case SpecialAbilityId.TraditionChurchOfPeraine:
|
||||
case SpecialAbilityId.TraditionChurchOfEfferd:
|
||||
case SpecialAbilityId.TraditionChurchOfTravia:
|
||||
case SpecialAbilityId.TraditionChurchOfFirun:
|
||||
case SpecialAbilityId.TraditionChurchOfTsa:
|
||||
case SpecialAbilityId.TraditionChurchOfIngerimm:
|
||||
case SpecialAbilityId.TraditionChurchOfRahja:
|
||||
case SpecialAbilityId.TraditionCultOfTheNamelessOne:
|
||||
case SpecialAbilityId.TraditionChurchOfAves:
|
||||
case SpecialAbilityId.TraditionChurchOfIfirn:
|
||||
case SpecialAbilityId.TraditionChurchOfKor:
|
||||
case SpecialAbilityId.TraditionChurchOfNandus:
|
||||
case SpecialAbilityId.TraditionChurchOfSwafnir:
|
||||
case SpecialAbilityId.TraditionCultOfNuminoru: {
|
||||
return pipe_ (
|
||||
hero,
|
||||
HA.specialAbilities,
|
||||
getBlessedTradition,
|
||||
x => isJust (x) ? Nothing : Just (ident)
|
||||
)
|
||||
}
|
||||
|
||||
case SpecialAbilityId.Recherchegespuer: {
|
||||
return pipe_ (
|
||||
hero,
|
||||
HA.specialAbilities,
|
||||
lookup<string> (SpecialAbilityId.Wissensdurst),
|
||||
fmap (ADA.active),
|
||||
bindF (listToMaybe),
|
||||
bindF (AOA.sid),
|
||||
misStringM,
|
||||
bindF (lookupF (SDA.skills (wiki))),
|
||||
bindF (skill => pipe (
|
||||
bindF<number | List<number>, List<number>> (ensure (isList)),
|
||||
fmap (pipe (
|
||||
map (add (getAPForActivatation (SA.ic (skill)))),
|
||||
Just,
|
||||
set (IAL.cost)
|
||||
))
|
||||
)
|
||||
(AAL.cost (wiki_entry)))
|
||||
)
|
||||
}
|
||||
|
||||
case SpecialAbilityId.PredigtDerGemeinschaft:
|
||||
case SpecialAbilityId.PredigtDerZuversicht:
|
||||
case SpecialAbilityId.PredigtDesGottvertrauens:
|
||||
case SpecialAbilityId.PredigtDesWohlgefallens:
|
||||
case SpecialAbilityId.PredigtWiderMissgeschicke: {
|
||||
const isAdvActive =
|
||||
pipe (lookupF (HA.advantages (hero)), isMaybeActive)
|
||||
|
||||
const max =
|
||||
getSermonOrVisionCountMax (hero)
|
||||
(AdvantageId.ZahlreichePredigten)
|
||||
(DisadvantageId.WenigePredigten)
|
||||
|
||||
const isLessThanMax =
|
||||
countActiveGroupEntries (wiki)
|
||||
(hero)
|
||||
(SpecialAbilityGroup.Predigten) < max
|
||||
|
||||
return (isAdvActive (AdvantageId.Prediger) && isLessThanMax)
|
||||
|| isAdvActive (AdvantageId.Blessed)
|
||||
? Just (ident)
|
||||
: Nothing
|
||||
}
|
||||
|
||||
case SpecialAbilityId.VisionDerBestimmung:
|
||||
case SpecialAbilityId.VisionDerEntrueckung:
|
||||
case SpecialAbilityId.VisionDerGottheit:
|
||||
case SpecialAbilityId.VisionDesSchicksals:
|
||||
case SpecialAbilityId.VisionDesWahrenGlaubens: {
|
||||
const isAdvActive =
|
||||
pipe (lookupF (HA.advantages (hero)), isMaybeActive)
|
||||
|
||||
const max =
|
||||
getSermonOrVisionCountMax (hero)
|
||||
(AdvantageId.ZahlreicheVisionen)
|
||||
(DisadvantageId.WenigeVisionen)
|
||||
|
||||
const isLessThanMax =
|
||||
countActiveGroupEntries (wiki)
|
||||
(hero)
|
||||
(SpecialAbilityGroup.Visionen) < max
|
||||
|
||||
return (isAdvActive (AdvantageId.Visionaer) && isLessThanMax)
|
||||
|| isAdvActive (AdvantageId.Blessed)
|
||||
? Just (ident)
|
||||
: Nothing
|
||||
}
|
||||
|
||||
case SpecialAbilityId.DunklesAbbildDerBuendnisgabe: {
|
||||
return pipe_ (
|
||||
hero,
|
||||
HA.pact,
|
||||
fmap (pipe (PA.level, Just, set (IAL.maxLevel)))
|
||||
)
|
||||
}
|
||||
|
||||
case SpecialAbilityId.TraditionArcaneBard:
|
||||
case SpecialAbilityId.TraditionArcaneDancer:
|
||||
case SpecialAbilityId.TraditionIntuitiveMage:
|
||||
case SpecialAbilityId.TraditionSavant:
|
||||
case SpecialAbilityId.TraditionAnimisten: {
|
||||
return APCA.spentOnMagicalAdvantages (adventure_points) <= 25
|
||||
&& APCA.spentOnMagicalDisadvantages (adventure_points) <= 25
|
||||
&& pipe_ (
|
||||
hero,
|
||||
HA.specialAbilities,
|
||||
getMagicalTraditionsHeroEntries,
|
||||
fnull
|
||||
)
|
||||
? Just (ident)
|
||||
: Nothing
|
||||
}
|
||||
|
||||
default:
|
||||
return Just (ident)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates whether an Activatable is valid to add or not and, if valid,
|
||||
* calculates and filters necessary properties and selection lists. Returns a
|
||||
* Maybe of the result or `undefined` if invalid.
|
||||
*/
|
||||
export const getInactiveView =
|
||||
(staticData: StaticDataRecord) =>
|
||||
(hero: HeroModelRecord) =>
|
||||
(automatic_advantages: List<string>) =>
|
||||
(required_apply_to_mag_actions: boolean) =>
|
||||
(matchingScriptAndLanguageRelated: MatchingScriptAndLanguageRelated) =>
|
||||
(adventure_points: Record<AdventurePointsCategories>) =>
|
||||
(validExtendedSpecialAbilities: List<string>) =>
|
||||
(hero_magical_traditions: List<Record<ActivatableDependent>>) =>
|
||||
(wiki_entry: Activatable) =>
|
||||
(mhero_entry: Maybe<Record<ActivatableDependent>>): Maybe<Record<InactiveActivatable>> => {
|
||||
const current_id = AAL.id (wiki_entry)
|
||||
const current_prerequisites = AAL.prerequisites (wiki_entry)
|
||||
|
||||
const max_level = isOrderedMap (current_prerequisites)
|
||||
? validateLevel (staticData)
|
||||
(hero)
|
||||
(current_prerequisites)
|
||||
(maybe<ActivatableDependent["dependencies"]> (List ())
|
||||
(ADA.dependencies)
|
||||
(mhero_entry))
|
||||
(current_id)
|
||||
: Nothing
|
||||
|
||||
const isNotValid = isAdditionDisabled (staticData)
|
||||
(hero)
|
||||
(required_apply_to_mag_actions)
|
||||
(validExtendedSpecialAbilities)
|
||||
(matchingScriptAndLanguageRelated)
|
||||
(wiki_entry)
|
||||
(mhero_entry)
|
||||
(max_level)
|
||||
|
||||
if (!isNotValid) {
|
||||
return pipe_ (
|
||||
wiki_entry,
|
||||
AAL.select,
|
||||
modifySelectOptions (staticData)
|
||||
(hero)
|
||||
(hero_magical_traditions)
|
||||
(wiki_entry)
|
||||
(mhero_entry),
|
||||
ensure (maybe (true) (notNull)),
|
||||
fmap ((select_options: Maybe<List<Record<SelectOption>>>) => InactiveActivatable ({
|
||||
id: current_id,
|
||||
name: SpAL.name (wiki_entry),
|
||||
cost: AAL.cost (wiki_entry),
|
||||
maxLevel: max_level,
|
||||
heroEntry: mhero_entry,
|
||||
wikiEntry: wiki_entry as Record<RecordI<Activatable>>,
|
||||
selectOptions: fmapF (select_options)
|
||||
(sortRecordsByName (staticData)),
|
||||
isAutomatic: List.elem (AAL.id (wiki_entry))
|
||||
(automatic_advantages),
|
||||
})),
|
||||
ap (modifyOtherOptions (staticData)
|
||||
(hero)
|
||||
(adventure_points)
|
||||
(wiki_entry)
|
||||
(mhero_entry)),
|
||||
bindF (ensure (pipe (IAA.maxLevel, maybe (true) (notEquals (0)))))
|
||||
)
|
||||
}
|
||||
|
||||
return Nothing
|
||||
}
|
||||
@@ -1,290 +0,0 @@
|
||||
/**
|
||||
* Checks if an `Activatable` entry is available to be activated.
|
||||
*
|
||||
* @file src/Utilities/activatableInactiveValidationUtils.ts
|
||||
* @author Lukas Obermann
|
||||
* @since 1.1.0
|
||||
*/
|
||||
|
||||
import { not } from "../../../Data/Bool"
|
||||
import { ident } from "../../../Data/Function"
|
||||
import { fmap } from "../../../Data/Functor"
|
||||
import { elem, flength, foldr, isList, List, notElem, toArray } from "../../../Data/List"
|
||||
import { all, any, bind, bindF, fromMaybe, isJust, listToMaybe, Maybe, sum } from "../../../Data/Maybe"
|
||||
import { add, inc, lte } from "../../../Data/Num"
|
||||
import { isOrderedMap, lookup, lookupF } from "../../../Data/OrderedMap"
|
||||
import { Record } from "../../../Data/Record"
|
||||
import { SkillGroup, SpecialAbilityGroup } from "../../Constants/Groups"
|
||||
import { AdvantageId, DisadvantageId, SpecialAbilityId } from "../../Constants/Ids"
|
||||
import { ActivatableDependent } from "../../Models/ActiveEntries/ActivatableDependent"
|
||||
import { ActiveObject } from "../../Models/ActiveEntries/ActiveObject"
|
||||
import { HeroModel, HeroModelRecord } from "../../Models/Hero/HeroModel"
|
||||
import { ActivatableDependency } from "../../Models/Hero/heroTypeHelpers"
|
||||
import { Pact } from "../../Models/Hero/Pact"
|
||||
import { Rules } from "../../Models/Hero/Rules"
|
||||
import { Advantage } from "../../Models/Wiki/Advantage"
|
||||
import { Skill } from "../../Models/Wiki/Skill"
|
||||
import { SpecialAbility } from "../../Models/Wiki/SpecialAbility"
|
||||
import { StaticData, StaticDataRecord } from "../../Models/Wiki/WikiModel"
|
||||
import { Activatable } from "../../Models/Wiki/wikiTypeHelpers"
|
||||
import { MatchingScriptAndLanguageRelated } from "../../Selectors/activatableSelectors"
|
||||
import { countActiveGroupEntries, hasActiveGroupEntry } from "../entryGroupUtils"
|
||||
import { getAllEntriesByGroup } from "../heroStateUtils"
|
||||
import { ensure, toNewMaybe } from "../Maybe"
|
||||
import { pipe, pipe_ } from "../pipe"
|
||||
import { getFirstLevelPrerequisites } from "../Prerequisites/flattenPrerequisites"
|
||||
import { validatePrerequisites } from "../Prerequisites/validatePrerequisitesUtils"
|
||||
import { isString } from "../typeCheckUtils"
|
||||
import * as CheckStyleUtils from "./checkStyleUtils"
|
||||
import { isActive, isMaybeActive } from "./isActive"
|
||||
|
||||
const SDA = StaticData.A
|
||||
const AA = Advantage.A
|
||||
const AAL = Advantage.AL
|
||||
const SAA = SpecialAbility.A
|
||||
const HA = HeroModel.A
|
||||
const ADA = ActivatableDependent.A
|
||||
const AOA = ActiveObject.A
|
||||
const PA = Pact.A
|
||||
const RA = Rules.A
|
||||
|
||||
const isAdditionDisabledForCombatStyle =
|
||||
(staticData: StaticDataRecord) =>
|
||||
(hero: HeroModelRecord) =>
|
||||
(wiki_entry: Record<SpecialAbility>): boolean => {
|
||||
const combination_hero_entry = lookup<string> (SpecialAbilityId.CombatStyleCombination)
|
||||
(HA.specialAbilities (hero))
|
||||
|
||||
// Combination-SA is active, which allows 3 styles to be active,
|
||||
// but only a maximum of 2 from one type (armed/unarmed).
|
||||
if (isMaybeActive (combination_hero_entry)) {
|
||||
const totalActive =
|
||||
countActiveGroupEntries (staticData)
|
||||
(hero)
|
||||
(
|
||||
SpecialAbilityGroup.CombatStylesArmed,
|
||||
SpecialAbilityGroup.CombatStylesUnarmed
|
||||
)
|
||||
|
||||
const equalTypeStylesActive =
|
||||
countActiveGroupEntries (staticData) (hero) (SAA.gr (wiki_entry))
|
||||
|
||||
return totalActive >= 3 || equalTypeStylesActive >= 2
|
||||
}
|
||||
|
||||
// Otherwise, only one of each type can be active.
|
||||
else {
|
||||
return pipe_ (wiki_entry, SAA.gr, hasActiveGroupEntry (staticData) (hero))
|
||||
}
|
||||
}
|
||||
|
||||
const isAdditionDisabledSpecialAbilitySpecific =
|
||||
(staticData: StaticDataRecord) =>
|
||||
(hero: HeroModelRecord) =>
|
||||
({
|
||||
languagesWithMatchingScripts,
|
||||
scriptsWithMatchingLanguages,
|
||||
}: MatchingScriptAndLanguageRelated) =>
|
||||
(wiki_entry: Activatable): boolean => {
|
||||
if (Advantage.is (wiki_entry)) {
|
||||
switch (AA.id (wiki_entry)) {
|
||||
case AdvantageId.InspireConfidence: {
|
||||
return toNewMaybe (lookup<string> (DisadvantageId.Incompetent) (HA.disadvantages (hero)))
|
||||
.maybe (false, incompetentStateEntry =>
|
||||
toArray (ADA.active (incompetentStateEntry))
|
||||
.some (ao =>
|
||||
toNewMaybe (AOA.sid (ao))
|
||||
.bind (skillId => ensure (skillId, isString))
|
||||
.bind (skillId => toNewMaybe (lookupF (SDA.skills (staticData))
|
||||
(skillId)))
|
||||
.maybe (
|
||||
false,
|
||||
skill => Skill.A.gr (skill) === SkillGroup.Social
|
||||
)))
|
||||
}
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
else if (SpecialAbility.is (wiki_entry)) {
|
||||
const current_id = SAA.id (wiki_entry)
|
||||
|
||||
if (CheckStyleUtils.isCombatStyleSpecialAbility (wiki_entry)) {
|
||||
return isAdditionDisabledForCombatStyle (staticData) (hero) (wiki_entry)
|
||||
}
|
||||
|
||||
if (CheckStyleUtils.isMagicalStyleSpecialAbility (wiki_entry)) {
|
||||
const combination_hero_entry = lookup<string> (SpecialAbilityId.MagicStyleCombination)
|
||||
(HA.specialAbilities (hero))
|
||||
|
||||
const total_active = countActiveGroupEntries (staticData)
|
||||
(hero)
|
||||
(SpecialAbilityGroup.MagicalStyles)
|
||||
|
||||
return total_active >= (isMaybeActive (combination_hero_entry) ? 2 : 1)
|
||||
}
|
||||
|
||||
if (CheckStyleUtils.isBlessedStyleSpecialAbility (wiki_entry)) {
|
||||
return hasActiveGroupEntry (staticData) (hero) (SpecialAbilityGroup.BlessedStyles)
|
||||
}
|
||||
|
||||
if (current_id === SpecialAbilityId.CombatStyleCombination) {
|
||||
return !hasActiveGroupEntry (staticData)
|
||||
(hero)
|
||||
(
|
||||
SpecialAbilityGroup.CombatStylesArmed,
|
||||
SpecialAbilityGroup.CombatStylesUnarmed
|
||||
)
|
||||
}
|
||||
|
||||
if (current_id === SpecialAbilityId.MagicStyleCombination) {
|
||||
return !hasActiveGroupEntry (staticData) (hero) (SpecialAbilityGroup.MagicalStyles)
|
||||
}
|
||||
|
||||
if (current_id === SpecialAbilityId.SpellEnhancement) {
|
||||
const traditionSchelme = toNewMaybe (lookup<string> (SpecialAbilityId.TraditionSchelme)
|
||||
(HA.specialAbilities (hero)))
|
||||
|
||||
if (traditionSchelme.isJust && isActive (traditionSchelme.value)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if (current_id === SpecialAbilityId.DunklesAbbildDerBuendnisgabe) {
|
||||
return hasActiveGroupEntry (staticData) (hero) (SpecialAbilityGroup.Paktgeschenke)
|
||||
}
|
||||
|
||||
if (current_id === SpecialAbilityId.WegDerSchreiberin) {
|
||||
return languagesWithMatchingScripts.length < 1 || scriptsWithMatchingLanguages.length < 1
|
||||
}
|
||||
|
||||
if (SAA.gr (wiki_entry) === SpecialAbilityGroup.Paktgeschenke) {
|
||||
const dunkles_abbild = lookup<string> (SpecialAbilityId.DunklesAbbildDerBuendnisgabe)
|
||||
(HA.specialAbilities (hero))
|
||||
|
||||
const allPactPresents = getAllEntriesByGroup (SDA.specialAbilities (staticData))
|
||||
(HA.specialAbilities (hero))
|
||||
(SpecialAbilityGroup.Paktgeschenke)
|
||||
|
||||
const countPactPresents =
|
||||
foldr ((obj: Record<ActivatableDependent>) => {
|
||||
if (isActive (obj)) {
|
||||
const wikiObj = lookup (ADA.id (obj)) (SDA.specialAbilities (staticData))
|
||||
|
||||
if (
|
||||
any (pipe (SAA.prerequisites, isOrderedMap))
|
||||
(wikiObj)
|
||||
&& any (pipe (SAA.cost, isList))
|
||||
(wikiObj)
|
||||
&& isJust (bind (wikiObj) (SAA.tiers))
|
||||
) {
|
||||
return add (sum (
|
||||
bindF (AOA.tier) (listToMaybe (ADA.active (obj)))
|
||||
))
|
||||
}
|
||||
|
||||
return inc
|
||||
}
|
||||
|
||||
return ident as ident<number>
|
||||
})
|
||||
(0)
|
||||
(allPactPresents)
|
||||
|
||||
// isFaeriePact?
|
||||
const isDisabled = all (pipe (PA.category, lte (1))) (HA.pact (hero))
|
||||
? isMaybeActive (dunkles_abbild)
|
||||
|| all (pipe (PA.level, lte (countPactPresents))) (HA.pact (hero))
|
||||
|
||||
// is Lesser Pact?
|
||||
: all (pipe (PA.level, lte (0))) (HA.pact (hero))
|
||||
|
||||
// Lesser Pact only provides 3 PactGifts
|
||||
? countPactPresents >= 3
|
||||
|
||||
// Normal DemonPact: KdV + 7 PactGifts
|
||||
: all (pipe (PA.level, lte (countPactPresents - 7))) (HA.pact (hero))
|
||||
|
||||
return isDisabled
|
||||
}
|
||||
|
||||
if (current_id === SpecialAbilityId.LanguageSpecializations) {
|
||||
return pipe (HA.rules, RA.enableLanguageSpecializations, not) (hero)
|
||||
}
|
||||
|
||||
if (elem (SAA.gr (wiki_entry)) (List (31, 32))) {
|
||||
// TODO: add option to activate vampire or lycanthropy and activate this
|
||||
// SAs based on that option
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if you can somehow add an ActiveObject to the given entry.
|
||||
* @param state The present state of the current hero.
|
||||
* @param instance The entry.
|
||||
*/
|
||||
const isAdditionDisabledEntrySpecific =
|
||||
(wiki: StaticDataRecord) =>
|
||||
(hero: HeroModelRecord) =>
|
||||
(matchingScriptAndLanguageRelated: MatchingScriptAndLanguageRelated) =>
|
||||
(wiki_entry: Activatable): boolean =>
|
||||
isAdditionDisabledSpecialAbilitySpecific (wiki)
|
||||
(hero)
|
||||
(matchingScriptAndLanguageRelated)
|
||||
(wiki_entry)
|
||||
|| !validatePrerequisites (wiki)
|
||||
(hero)
|
||||
(getFirstLevelPrerequisites (AAL.prerequisites (wiki_entry)))
|
||||
(AAL.id (wiki_entry))
|
||||
|
||||
const hasGeneralRestrictionToAdd =
|
||||
any (pipe (ADA.dependencies, elem<ActivatableDependency> (false)))
|
||||
|
||||
const hasReachedMaximumEntries =
|
||||
(wiki_entry: Activatable) =>
|
||||
(mhero_entry: Maybe<Record<ActivatableDependent>>) =>
|
||||
any (lte (fromMaybe (0)
|
||||
(fmap (pipe (ADA.active, flength))
|
||||
(mhero_entry))))
|
||||
(AAL.max (wiki_entry))
|
||||
|
||||
const hasReachedImpossibleMaximumLevel = Maybe.elem (0)
|
||||
|
||||
const isInvalidExtendedSpecialAbility =
|
||||
(wiki_entry: Activatable) =>
|
||||
(validExtendedSpecialAbilities: List<string>) =>
|
||||
CheckStyleUtils.isExtendedSpecialAbility (wiki_entry)
|
||||
&& notElem (AAL.id (wiki_entry)) (validExtendedSpecialAbilities)
|
||||
|
||||
const doesNotApplyToMagActionsThoughRequired =
|
||||
(required_apply_to_mag_actions: boolean) =>
|
||||
(wiki_entry: Activatable): boolean =>
|
||||
SpecialAbility.is (wiki_entry)
|
||||
? false
|
||||
: required_apply_to_mag_actions && Advantage.AL.isExclusiveToArcaneSpellworks (wiki_entry)
|
||||
|
||||
/**
|
||||
* Checks if the given entry can be added.
|
||||
* @param obj
|
||||
* @param state The current hero's state.
|
||||
*/
|
||||
export const isAdditionDisabled =
|
||||
(wiki: StaticDataRecord) =>
|
||||
(hero: HeroModelRecord) =>
|
||||
(required_apply_to_mag_actions: boolean) =>
|
||||
(validExtendedSpecialAbilities: List<string>) =>
|
||||
(matchingScriptAndLanguageRelated: MatchingScriptAndLanguageRelated) =>
|
||||
(wiki_entry: Activatable) =>
|
||||
(mhero_entry: Maybe<Record<ActivatableDependent>>) =>
|
||||
(max_level: Maybe<number>): boolean =>
|
||||
isAdditionDisabledEntrySpecific (wiki) (hero) (matchingScriptAndLanguageRelated) (wiki_entry)
|
||||
|| hasGeneralRestrictionToAdd (mhero_entry)
|
||||
|| hasReachedMaximumEntries (wiki_entry) (mhero_entry)
|
||||
|| hasReachedImpossibleMaximumLevel (max_level)
|
||||
|| isInvalidExtendedSpecialAbility (wiki_entry) (validExtendedSpecialAbilities)
|
||||
|| doesNotApplyToMagActionsThoughRequired (required_apply_to_mag_actions) (wiki_entry)
|
||||
@@ -1,81 +0,0 @@
|
||||
import { countWith, List } from "../../../Data/List"
|
||||
import { bindF, listToMaybe, Maybe, maybe, sum } from "../../../Data/Maybe"
|
||||
import { Record } from "../../../Data/Record"
|
||||
import { ActivatableDependent } from "../../Models/ActiveEntries/ActivatableDependent"
|
||||
import { ActiveObject } from "../../Models/ActiveEntries/ActiveObject"
|
||||
import { pipe } from "../pipe"
|
||||
import { isMaybeActive } from "./isActive"
|
||||
|
||||
const ADA = ActivatableDependent.A
|
||||
const AOA = ActiveObject.A
|
||||
|
||||
const getLevel = pipe (bindF (pipe (ADA.active, listToMaybe)), bindF (AOA.tier), sum)
|
||||
|
||||
/**
|
||||
* ```haskell
|
||||
* modifyByLevel :: Int -> ActivatableDependent -> ActivatableDependent -> Int
|
||||
* ```
|
||||
*
|
||||
* `modifyByLevel value inc dec` modifies a base `value` by the active level of
|
||||
* the passed entries. The `inc` entry's level increases the value while the
|
||||
* `dec` entry's level decreases the value.
|
||||
*/
|
||||
export const modifyByLevel =
|
||||
(value: number) =>
|
||||
(mincrease: Maybe<Record<ActivatableDependent>>) =>
|
||||
(mdecrease: Maybe<Record<ActivatableDependent>>): number =>
|
||||
value + getLevel (mincrease) - getLevel (mdecrease)
|
||||
|
||||
/**
|
||||
* ```haskell
|
||||
* modifyByLevelM :: Maybe Int -> ActivatableDependent -> ActivatableDependent -> Int
|
||||
* ```
|
||||
*
|
||||
* `modifyByLevel value inc dec` modifies a base `value` by the active level of
|
||||
* the passed entries. The `inc` entry's level increases the value while the
|
||||
* `dec` entry's level decreases the value. If `value` is `Nothing`, this
|
||||
* function always returns `0`.
|
||||
*/
|
||||
export const modifyByLevelM =
|
||||
(mvalue: Maybe<number>) =>
|
||||
(mincrease: Maybe<Record<ActivatableDependent>>) =>
|
||||
(mdecrease: Maybe<Record<ActivatableDependent>>): number =>
|
||||
maybe (0)
|
||||
((value: number) => modifyByLevel (value) (mincrease) (mdecrease))
|
||||
(mvalue)
|
||||
|
||||
/**
|
||||
* `getModifierByActiveLevel mbase_mod mincrease mdecrease` adjusts the given
|
||||
* base `mbase_mod`. If the entry `mincrease`, that should increase the base, is
|
||||
* active, it adds `1` to the base. If the entry `mdecrease`, that should
|
||||
* decrease the base, is active, it subtracts `1` from the base. If the
|
||||
* passed base is `Nothing`, this function always returns `0`.
|
||||
*/
|
||||
export const getModifierByIsActive =
|
||||
(mbase_mod: Maybe<number>) =>
|
||||
(mincrease: Maybe<Record<ActivatableDependent>>) =>
|
||||
(mdecrease: Maybe<Record<ActivatableDependent>>): number => {
|
||||
const hasIncrease = isMaybeActive (mincrease)
|
||||
const hasDecrease = isMaybeActive (mdecrease)
|
||||
|
||||
return maybe (0)
|
||||
((baseMod: number) =>
|
||||
hasIncrease ? baseMod + 1 : hasDecrease ? baseMod - 1 : baseMod)
|
||||
(mbase_mod)
|
||||
}
|
||||
|
||||
/**
|
||||
* `getModifierByActiveLevels mbase_mod mincreases mdecreases` adjusts the given
|
||||
* base `mbase_mod`.
|
||||
*/
|
||||
export const getModifierByIsActives =
|
||||
(mbase_mod: Maybe<number>) =>
|
||||
(mincreases: List<Maybe<Record<ActivatableDependent>>>) =>
|
||||
(mdecreases: List<Maybe<Record<ActivatableDependent>>>): number => {
|
||||
const incs = countWith (isMaybeActive) (mincreases)
|
||||
const decs = countWith (isMaybeActive) (mdecreases)
|
||||
|
||||
return maybe (0)
|
||||
((baseMod: number) => baseMod + incs - decs)
|
||||
(mbase_mod)
|
||||
}
|
||||
@@ -1,494 +0,0 @@
|
||||
/**
|
||||
* Generates the complete name of an active `Activatable`. Also provides helper
|
||||
* functions for compressing `Activatable` name lists and combining or
|
||||
* extracting parts of the name.
|
||||
*
|
||||
* @file src/Utilities/activatableNameUtils.ts
|
||||
* @author Lukas Obermann
|
||||
* @since 1.1.0
|
||||
*/
|
||||
|
||||
import { equals } from "../../../Data/Eq"
|
||||
import { flip, thrush } from "../../../Data/Function"
|
||||
import { fmap } from "../../../Data/Functor"
|
||||
import { set } from "../../../Data/Lens"
|
||||
import { append, appendStr, elem, filter, find, flength, groupByKey, intercalate, List, map, replaceStr, subscriptF } from "../../../Data/List"
|
||||
import { altF_, any, bind, bindF, catMaybes, elemF, ensure, fromJust, fromMaybe, isJust, isNothing, Just, liftM2, listToMaybe, maybe, Maybe, Nothing, thenF } from "../../../Data/Maybe"
|
||||
import { elems, lookup, lookupF, OrderedMap, sdelete } from "../../../Data/OrderedMap"
|
||||
import { Record } from "../../../Data/Record"
|
||||
import { AdvantageId, AdvantageId as AdvantageIdEnum, DisadvantageId, DisadvantageId as DisadvantageIdEnum, SpecialAbilityId, SpecialAbilityId as SpecialAbilityIdEnum } from "../../Constants/Ids"
|
||||
import { ActiveObjectWithId } from "../../Models/ActiveEntries/ActiveObjectWithId"
|
||||
import { ActivatableCombinedName, ActivatableCombinedNameL } from "../../Models/View/ActivatableCombinedName"
|
||||
import { ActivatableNameCostSafeCostL } from "../../Models/View/ActivatableNameCost"
|
||||
import { ActiveActivatable, ActiveActivatableA_, ActiveActivatableL } from "../../Models/View/ActiveActivatable"
|
||||
import { Advantage } from "../../Models/Wiki/Advantage"
|
||||
import { Skill } from "../../Models/Wiki/Skill"
|
||||
import { Application } from "../../Models/Wiki/sub/Application"
|
||||
import { SelectOption } from "../../Models/Wiki/sub/SelectOption"
|
||||
import { StaticData, StaticDataRecord } from "../../Models/Wiki/WikiModel"
|
||||
import { Activatable, ActivatableSkillEntry, EntryWithCategory, SID, SkillishEntry } from "../../Models/Wiki/wikiTypeHelpers"
|
||||
import { mdash } from "../Chars"
|
||||
import { composeL } from "../compose"
|
||||
import { formatList, translate } from "../I18n"
|
||||
import { ifElse } from "../ifElse"
|
||||
import { toRoman } from "../NumberUtils"
|
||||
import { pipe, pipe_ } from "../pipe"
|
||||
import { sortStrings } from "../sortBy"
|
||||
import { isNumber, isString, misStringM } from "../typeCheckUtils"
|
||||
import { getWikiEntry, isActivatableWikiEntry, isSkillishWikiEntry } from "../WikiUtils"
|
||||
import { findSelectOption, getSelectOptionName } from "./selectionUtils"
|
||||
|
||||
const SDA = StaticData.A
|
||||
const AOWIA = ActiveObjectWithId.A
|
||||
const AAA_ = ActiveActivatableA_
|
||||
const AAL = Advantage.AL
|
||||
const SA = Skill.A
|
||||
const SAL = Skill.AL
|
||||
const SOA = SelectOption.A
|
||||
const AA = Application.A
|
||||
|
||||
/**
|
||||
* Returns the name of the given object. If the object is a string, it returns
|
||||
* the string.
|
||||
*/
|
||||
export const getFullName =
|
||||
(obj: string | Record<ActiveActivatable>): string => {
|
||||
if (typeof obj === "string") {
|
||||
return obj
|
||||
}
|
||||
|
||||
return AAA_.name (obj)
|
||||
}
|
||||
|
||||
/**
|
||||
* Accepts the full special ability name and returns only the text between
|
||||
* parentheses. If no parentheses were found, returns an empty string.
|
||||
*/
|
||||
export const getBracketedNameFromFullName =
|
||||
(full_name: string): string => {
|
||||
const result = /\((?<subname>.+)\)/u .exec (full_name)
|
||||
|
||||
if (result === null || result .groups === undefined) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return result .groups .subname
|
||||
}
|
||||
|
||||
/**
|
||||
* A lot of entries have customization options: Text input, select option or
|
||||
* both. This function creates a string that can be appended to the `name`
|
||||
* property of the respective record to create the full active name.
|
||||
*/
|
||||
const getEntrySpecificNameAddition =
|
||||
(staticData: StaticDataRecord) =>
|
||||
(wiki_entry: Activatable) =>
|
||||
(hero_entry: Record<ActiveObjectWithId>): Maybe<string> => {
|
||||
switch (AOWIA.id (hero_entry)) {
|
||||
// Entry with Skill selection (string id)
|
||||
case AdvantageId.Aptitude:
|
||||
case AdvantageId.ExceptionalSkill:
|
||||
case AdvantageId.ExceptionalCombatTechnique:
|
||||
case AdvantageId.WeaponAptitude:
|
||||
case DisadvantageId.Incompetent:
|
||||
case SpecialAbilityId.AdaptionZauber:
|
||||
case SpecialAbilityId.FavoriteSpellwork:
|
||||
case SpecialAbilityId.Forschungsgebiet:
|
||||
case SpecialAbilityId.Expertenwissen:
|
||||
case SpecialAbilityId.Wissensdurst:
|
||||
case SpecialAbilityId.Recherchegespuer:
|
||||
case SpecialAbilityId.Lieblingsliturgie:
|
||||
return pipe (
|
||||
AOWIA.sid,
|
||||
misStringM,
|
||||
bindF (getWikiEntry (staticData)),
|
||||
bindF<EntryWithCategory, SkillishEntry> (ensure (isSkillishWikiEntry)),
|
||||
fmap (SAL.name)
|
||||
)
|
||||
(hero_entry)
|
||||
|
||||
case AdvantageId.HatredOf:
|
||||
return pipe (
|
||||
AOWIA.sid,
|
||||
findSelectOption (wiki_entry),
|
||||
liftM2 ((type: string | number) => (frequency: Record<SelectOption>) =>
|
||||
`${type} (${SOA.name (frequency)})`)
|
||||
(AOWIA.sid2 (hero_entry))
|
||||
)
|
||||
(hero_entry)
|
||||
|
||||
case DisadvantageId.PersonalityFlaw:
|
||||
return pipe (
|
||||
AOWIA.sid,
|
||||
getSelectOptionName (wiki_entry),
|
||||
fmap ((option_name: string) =>
|
||||
maybe (option_name)
|
||||
|
||||
// if there is additional input, add to name
|
||||
((specialInput: string | number) =>
|
||||
`${option_name}: ${specialInput}`)
|
||||
(pipe (
|
||||
AOWIA.sid,
|
||||
|
||||
// Check if the select option allows
|
||||
// additional input
|
||||
bindF<SID, number> (
|
||||
ensure (
|
||||
(x): x is number => isNumber (x)
|
||||
&& elem (x) (List (7, 8))
|
||||
)
|
||||
),
|
||||
bindF (() => AOWIA.sid2 (hero_entry))
|
||||
)
|
||||
(hero_entry)))
|
||||
)
|
||||
(hero_entry)
|
||||
|
||||
case SpecialAbilityId.SkillSpecialization:
|
||||
return pipe (
|
||||
AOWIA.sid,
|
||||
misStringM,
|
||||
bindF (lookupF (SDA.skills (staticData))),
|
||||
bindF (skill => pipe (
|
||||
AOWIA.sid2,
|
||||
|
||||
// If input string use input
|
||||
misStringM,
|
||||
|
||||
// Otherwise lookup application name
|
||||
altF_ (() => pipe (
|
||||
SA.applications,
|
||||
find<Record<Application>> (pipe (
|
||||
Application.AL.id,
|
||||
elemF (AOWIA.sid2 (hero_entry))
|
||||
)),
|
||||
fmap (AA.name)
|
||||
)
|
||||
(skill)),
|
||||
|
||||
// Merge skill name and application name
|
||||
fmap (appl => `${SA.name (skill)}: ${appl}`)
|
||||
)
|
||||
(hero_entry))
|
||||
)
|
||||
(hero_entry)
|
||||
|
||||
case SpecialAbilityId.Exorzist:
|
||||
return pipe_ (
|
||||
hero_entry,
|
||||
AOWIA.tier,
|
||||
Maybe.product,
|
||||
ensure (equals (1)),
|
||||
thenF (AOWIA.sid (hero_entry)),
|
||||
findSelectOption (wiki_entry),
|
||||
fmap (SOA.name)
|
||||
)
|
||||
|
||||
case SpecialAbilityId.SpellEnhancement:
|
||||
case SpecialAbilityId.ChantEnhancement:
|
||||
return pipe (
|
||||
AOWIA.sid,
|
||||
findSelectOption (wiki_entry),
|
||||
bindF (ext => pipe (
|
||||
bindF ((target_id: string) => {
|
||||
const acc =
|
||||
AOWIA.id (hero_entry)
|
||||
=== SpecialAbilityId.SpellEnhancement
|
||||
? SDA.spells
|
||||
: SDA.liturgicalChants
|
||||
|
||||
return lookupF<string, ActivatableSkillEntry>
|
||||
(acc (staticData))
|
||||
(target_id)
|
||||
}),
|
||||
fmap (
|
||||
(target_entry: ActivatableSkillEntry) =>
|
||||
`${SAL.name (target_entry)}: ${SOA.name (ext)}`
|
||||
)
|
||||
)
|
||||
(SOA.target (ext)))
|
||||
)
|
||||
(hero_entry)
|
||||
|
||||
case SpecialAbilityId.TraditionSavant:
|
||||
return pipe (
|
||||
AOWIA.sid,
|
||||
misStringM,
|
||||
bindF (lookupF (SDA.skills (staticData))),
|
||||
fmap (SA.name)
|
||||
)
|
||||
(hero_entry)
|
||||
|
||||
case SpecialAbilityId.LanguageSpecializations:
|
||||
return pipe (
|
||||
SDA.specialAbilities,
|
||||
lookup<string> (SpecialAbilityId.Language),
|
||||
bindF (pipe (
|
||||
findSelectOption,
|
||||
thrush (AOWIA.sid (hero_entry))
|
||||
)),
|
||||
bindF (lang => pipe (
|
||||
AOWIA.sid2,
|
||||
bindF (
|
||||
ifElse<string | number, string>
|
||||
(isString)
|
||||
<Maybe<string>>
|
||||
(Just)
|
||||
(spec_id => bind (SOA.specializations (lang))
|
||||
(subscriptF (spec_id - 1)))
|
||||
),
|
||||
fmap (spec => `${SOA.name (lang)}: ${spec}`)
|
||||
)
|
||||
(hero_entry))
|
||||
)
|
||||
(staticData)
|
||||
|
||||
case SpecialAbilityId.Fachwissen: {
|
||||
const getApp = (getSid: (r: Record<ActiveObjectWithId>) => Maybe<string | number>) =>
|
||||
pipe (
|
||||
SA.applications,
|
||||
filter (pipe (AA.prerequisite, isNothing)),
|
||||
find (pipe (AA.id, elemF (getSid (hero_entry)))),
|
||||
fmap (AA.name)
|
||||
)
|
||||
|
||||
return pipe_ (
|
||||
hero_entry,
|
||||
AOWIA.sid,
|
||||
misStringM,
|
||||
bindF (lookupF (SDA.skills (staticData))),
|
||||
bindF (skill => pipe_ (
|
||||
List (
|
||||
getApp (AOWIA.sid2) (skill),
|
||||
getApp (AOWIA.sid3) (skill)
|
||||
),
|
||||
catMaybes,
|
||||
ensure (xs => flength (xs) === 2),
|
||||
fmap (pipe (
|
||||
sortStrings (staticData),
|
||||
formatList ("conjunction") (staticData),
|
||||
apps => `${SA.name (skill)}: ${apps}`
|
||||
))
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
case SpecialAbilityId.Handwerkskunst:
|
||||
case SpecialAbilityId.KindDerNatur:
|
||||
case SpecialAbilityId.KoerperlichesGeschick:
|
||||
case SpecialAbilityId.SozialeKompetenz:
|
||||
case SpecialAbilityId.Universalgenie: {
|
||||
return pipe_ (
|
||||
List (AOWIA.sid (hero_entry), AOWIA.sid2 (hero_entry), AOWIA.sid3 (hero_entry)),
|
||||
map (getSelectOptionName (wiki_entry)),
|
||||
catMaybes,
|
||||
sortStrings (staticData),
|
||||
intercalate (", "),
|
||||
Just
|
||||
)
|
||||
}
|
||||
|
||||
default: {
|
||||
const current_sid = AOWIA.sid (hero_entry)
|
||||
const current_sid2 = AOWIA.sid2 (hero_entry)
|
||||
|
||||
// Text input
|
||||
if (isJust (AAL.input (wiki_entry)) && any (isString) (current_sid)) {
|
||||
return current_sid
|
||||
}
|
||||
|
||||
// Select option and text input
|
||||
if (isJust (AAL.select (wiki_entry))
|
||||
&& isJust (AAL.input (wiki_entry))
|
||||
&& isJust (current_sid)
|
||||
&& any (isString) (current_sid2)) {
|
||||
const name1 = fromMaybe ("") (getSelectOptionName (wiki_entry) (current_sid))
|
||||
const name2 = fromJust (current_sid2)
|
||||
|
||||
return Just (`${name1}: ${name2}`)
|
||||
}
|
||||
|
||||
// Plain select option
|
||||
if (isJust (AAL.select (wiki_entry))) {
|
||||
return getSelectOptionName (wiki_entry) (current_sid)
|
||||
}
|
||||
|
||||
return Nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const addSndinParenthesis = (snd: string) => replaceStr (")") (`: ${snd})`)
|
||||
|
||||
/**
|
||||
* Some entries cannot use the default `name` property from wiki entries. The
|
||||
* value returned by may not use the default `name` property. For all entries
|
||||
* that do not need to handle a specific display format, the default `name`
|
||||
* property is used.
|
||||
*/
|
||||
const getEntrySpecificNameReplacements =
|
||||
(staticData: StaticDataRecord) =>
|
||||
(wiki_entry: Activatable) =>
|
||||
(hero_entry: Record<ActiveObjectWithId>) =>
|
||||
(mname_add: Maybe<string>): string => {
|
||||
const def = fromMaybe (AAL.name (wiki_entry))
|
||||
|
||||
const maybeMap = (f: (x: string) => string) => maybe (AAL.name (wiki_entry))
|
||||
(f)
|
||||
(mname_add)
|
||||
|
||||
switch (AAL.id (wiki_entry)) {
|
||||
case AdvantageId.ImmunityToPoison:
|
||||
case AdvantageId.ImmunityToDisease:
|
||||
return maybeMap (
|
||||
name_add => `${translate (staticData) ("advantagesdisadvantages.immunityto")} ${name_add}`
|
||||
)
|
||||
|
||||
case AdvantageId.HatredOf:
|
||||
return maybeMap (
|
||||
name_add => `${translate (staticData) ("advantagesdisadvantages.hatredfor")} ${name_add}`
|
||||
)
|
||||
|
||||
case DisadvantageId.AfraidOf:
|
||||
return maybeMap (
|
||||
name_add => `${translate (staticData) ("advantagesdisadvantages.afraidof")} ${name_add}`
|
||||
)
|
||||
|
||||
case DisadvantageId.Principles:
|
||||
case DisadvantageId.Obligations:
|
||||
return def (liftM2 ((level: number) => (name_add: string) =>
|
||||
`${AAL.name (wiki_entry)} ${toRoman (level)} (${name_add})`)
|
||||
(AOWIA.tier (hero_entry))
|
||||
(mname_add))
|
||||
|
||||
case SpecialAbilityId.GebieterDesAspekts:
|
||||
return maybeMap (name_add => `${AAL.name (wiki_entry)} ${name_add}`)
|
||||
|
||||
case SpecialAbilityId.TraditionArcaneBard:
|
||||
case SpecialAbilityId.TraditionArcaneDancer:
|
||||
case SpecialAbilityId.TraditionSavant: {
|
||||
return maybeMap (flip (addSndinParenthesis) (AAL.name (wiki_entry)))
|
||||
}
|
||||
|
||||
default:
|
||||
return maybeMap (name_add => `${AAL.name (wiki_entry)} (${name_add})`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns name, splitted and combined, of advantage/disadvantage/special
|
||||
* ability as a Maybe (in case the wiki entry does not exist).
|
||||
* @param instance The ActiveObject with origin id.
|
||||
* @param wiki The current hero's state.
|
||||
* @param l10n The locale-dependent messages.
|
||||
*/
|
||||
export const getName =
|
||||
(staticData: StaticDataRecord) =>
|
||||
(hero_entry: Record<ActiveObjectWithId>): Maybe<Record<ActivatableCombinedName>> =>
|
||||
pipe (
|
||||
AOWIA.id,
|
||||
getWikiEntry (staticData),
|
||||
bindF<EntryWithCategory, Activatable> (ensure (isActivatableWikiEntry)),
|
||||
fmap ((wiki_entry: Activatable) => {
|
||||
const maddName = getEntrySpecificNameAddition (staticData)
|
||||
(wiki_entry)
|
||||
(hero_entry)
|
||||
|
||||
const fullName = getEntrySpecificNameReplacements (staticData)
|
||||
(wiki_entry)
|
||||
(hero_entry)
|
||||
(maddName)
|
||||
|
||||
return ActivatableCombinedName ({
|
||||
name: fullName,
|
||||
baseName: AAL.name (wiki_entry),
|
||||
addName: maddName,
|
||||
})
|
||||
})
|
||||
)
|
||||
(hero_entry)
|
||||
|
||||
const extractCustom = (
|
||||
id: string,
|
||||
groupedEntries: OrderedMap<string, List<Record<ActiveActivatable>>>
|
||||
) =>
|
||||
pipe_ (
|
||||
groupedEntries,
|
||||
lookup (id),
|
||||
maybe (List<List<Record<ActiveActivatable>>> ())
|
||||
(map (active => List (set (composeL (
|
||||
ActiveActivatableL.nameAndCost,
|
||||
ActivatableNameCostSafeCostL.naming,
|
||||
ActivatableCombinedNameL.name
|
||||
))
|
||||
(fromMaybe (mdash) (AAA_.addName (active)))
|
||||
(active))))
|
||||
)
|
||||
|
||||
/**
|
||||
* `compressList :: L10n -> [ActiveActivatable] -> String`
|
||||
*
|
||||
* Takes a list of active Activatables and merges them together. Used to display
|
||||
* lists of Activatables on character sheet.
|
||||
*/
|
||||
export const compressList =
|
||||
(staticData: StaticDataRecord) =>
|
||||
(xs: List<Record<ActiveActivatable>>): string => {
|
||||
const groupedEntries = groupByKey<Record<ActiveActivatable>, string> (AAA_.id) (xs)
|
||||
|
||||
const customAdvantages =
|
||||
extractCustom (AdvantageIdEnum.CustomAdvantage, groupedEntries)
|
||||
|
||||
const customDisadvantages =
|
||||
extractCustom (DisadvantageIdEnum.CustomDisadvantage, groupedEntries)
|
||||
|
||||
const customSpecialAbilities =
|
||||
extractCustom (SpecialAbilityIdEnum.CustomSpecialAbility, groupedEntries)
|
||||
|
||||
const grouped_xs =
|
||||
pipe_ (
|
||||
groupedEntries,
|
||||
sdelete <string> (AdvantageIdEnum.CustomAdvantage),
|
||||
sdelete <string> (DisadvantageIdEnum.CustomDisadvantage),
|
||||
sdelete <string> (SpecialAbilityIdEnum.CustomSpecialAbility),
|
||||
elems,
|
||||
append (customAdvantages),
|
||||
append (customDisadvantages),
|
||||
append (customSpecialAbilities),
|
||||
)
|
||||
|
||||
return pipe (
|
||||
map (
|
||||
ifElse<List<Record<ActiveActivatable>>>
|
||||
(xs_group => flength (xs_group) === 1)
|
||||
(pipe (listToMaybe, maybe ("") (AAA_.name)))
|
||||
(xs_group => pipe (
|
||||
map ((x: Record<ActiveActivatable>) => {
|
||||
const levelPart =
|
||||
pipe (
|
||||
AAA_.level,
|
||||
fmap (pipe (toRoman, appendStr (" "))),
|
||||
fromMaybe ("")
|
||||
)
|
||||
(x)
|
||||
|
||||
const selectOptionPart =
|
||||
fromMaybe ("") (AAA_.addName (x))
|
||||
|
||||
return selectOptionPart + levelPart
|
||||
}),
|
||||
sortStrings (staticData),
|
||||
intercalate (", "),
|
||||
x => ` (${x})`,
|
||||
x => maybe ("")
|
||||
((r: Record<ActiveActivatable>) =>
|
||||
AAA_.baseName (r) + x)
|
||||
(listToMaybe (xs_group))
|
||||
)
|
||||
(xs_group))
|
||||
),
|
||||
sortStrings (staticData),
|
||||
intercalate (", ")
|
||||
)
|
||||
(grouped_xs)
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import { notNull } from "../../../Data/List"
|
||||
import { Just, Maybe, maybe } from "../../../Data/Maybe"
|
||||
import { Record } from "../../../Data/Record"
|
||||
import { ActivatableDependent } from "../../Models/ActiveEntries/ActivatableDependent"
|
||||
import { pipe } from "../pipe"
|
||||
|
||||
const { active } = ActivatableDependent.AL
|
||||
|
||||
/**
|
||||
* Checks if the entry is active. This will be the case if there is at least one
|
||||
* `ActiveObject` in the `obj.active` array.
|
||||
* @param obj The entry.
|
||||
*/
|
||||
export const isActive: (obj: Record<ActivatableDependent>) => boolean =
|
||||
pipe (active, notNull)
|
||||
|
||||
/**
|
||||
* Checks if the entry is active. This will be the case if there is at least one
|
||||
* `ActiveObject` in the `obj.active` array.
|
||||
* @param obj The entry.
|
||||
*/
|
||||
export const isMaybeActive =
|
||||
(obj: Maybe<Record<ActivatableDependent>>): obj is Just<Record<ActivatableDependent>> =>
|
||||
maybe (false) (isActive) (obj)
|
||||
@@ -1,383 +0,0 @@
|
||||
import { filter, List } from "../../../Data/List"
|
||||
import { bindF, Just, mapMaybe, Maybe, Nothing } from "../../../Data/Maybe"
|
||||
import { elems, find, lookupF, OrderedMap } from "../../../Data/OrderedMap"
|
||||
import { Record } from "../../../Data/Record"
|
||||
import { BlessedTradition, MagicalTradition } from "../../Constants/Groups"
|
||||
import { SpecialAbilityId } from "../../Constants/Ids"
|
||||
import { ActivatableDependent } from "../../Models/ActiveEntries/ActivatableDependent"
|
||||
import { SpecialAbility } from "../../Models/Wiki/SpecialAbility"
|
||||
import { pipe } from "../pipe"
|
||||
import { isActive } from "./isActive"
|
||||
|
||||
const ADA = ActivatableDependent.A
|
||||
|
||||
/**
|
||||
* Checks if an ID is a magical tradition's id.
|
||||
*/
|
||||
export const isMagicalTradId =
|
||||
(tradition_id: string): boolean => {
|
||||
switch (tradition_id) {
|
||||
case SpecialAbilityId.TraditionGuildMages:
|
||||
case SpecialAbilityId.TraditionWitches:
|
||||
case SpecialAbilityId.TraditionElves:
|
||||
case SpecialAbilityId.TraditionDruids:
|
||||
case SpecialAbilityId.TraditionIllusionist:
|
||||
case SpecialAbilityId.TraditionArcaneBard:
|
||||
case SpecialAbilityId.TraditionArcaneDancer:
|
||||
case SpecialAbilityId.TraditionIntuitiveMage:
|
||||
case SpecialAbilityId.TraditionSavant:
|
||||
case SpecialAbilityId.TraditionQabalyaMage:
|
||||
case SpecialAbilityId.TraditionZauberalchimisten:
|
||||
case SpecialAbilityId.TraditionKristallomanten:
|
||||
case SpecialAbilityId.TraditionGeoden:
|
||||
case SpecialAbilityId.TraditionSchelme:
|
||||
case SpecialAbilityId.TraditionAnimisten:
|
||||
case SpecialAbilityId.TraditionZibilijas:
|
||||
case SpecialAbilityId.TraditionBrobimGeoden:
|
||||
return true
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an ID is a blessed tradition's id.
|
||||
*/
|
||||
export const isBlessedTradId =
|
||||
(tradition_id: string): boolean => {
|
||||
switch (tradition_id) {
|
||||
case SpecialAbilityId.TraditionChurchOfPraios:
|
||||
case SpecialAbilityId.TraditionChurchOfRondra:
|
||||
case SpecialAbilityId.TraditionChurchOfBoron:
|
||||
case SpecialAbilityId.TraditionChurchOfHesinde:
|
||||
case SpecialAbilityId.TraditionChurchOfPhex:
|
||||
case SpecialAbilityId.TraditionChurchOfPeraine:
|
||||
case SpecialAbilityId.TraditionChurchOfEfferd:
|
||||
case SpecialAbilityId.TraditionChurchOfTravia:
|
||||
case SpecialAbilityId.TraditionChurchOfFirun:
|
||||
case SpecialAbilityId.TraditionChurchOfTsa:
|
||||
case SpecialAbilityId.TraditionChurchOfIngerimm:
|
||||
case SpecialAbilityId.TraditionChurchOfRahja:
|
||||
case SpecialAbilityId.TraditionCultOfTheNamelessOne:
|
||||
case SpecialAbilityId.TraditionChurchOfAves:
|
||||
case SpecialAbilityId.TraditionChurchOfIfirn:
|
||||
case SpecialAbilityId.TraditionChurchOfKor:
|
||||
case SpecialAbilityId.TraditionChurchOfNandus:
|
||||
case SpecialAbilityId.TraditionChurchOfSwafnir:
|
||||
case SpecialAbilityId.TraditionCultOfNuminoru:
|
||||
case SpecialAbilityId.TraditionCultOfLevthan:
|
||||
return true
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const isActiveMagicalTradition =
|
||||
(e: Record<ActivatableDependent>) => isMagicalTradId (ADA.id (e)) && isActive (e)
|
||||
|
||||
const isActiveBlessedTradition =
|
||||
(e: Record<ActivatableDependent>) => isBlessedTradId (ADA.id (e)) && isActive (e)
|
||||
|
||||
/**
|
||||
* Get magical traditions' dependent entries.
|
||||
* @param list
|
||||
*/
|
||||
export const getMagicalTraditionsHeroEntries =
|
||||
pipe (
|
||||
elems as
|
||||
(list: OrderedMap<any, Record<ActivatableDependent>>) => List<Record<ActivatableDependent>>,
|
||||
filter (isActiveMagicalTradition)
|
||||
)
|
||||
|
||||
/**
|
||||
* Get magical traditions' wiki entries.
|
||||
* @param wiki
|
||||
* @param list
|
||||
*/
|
||||
export const getMagicalTraditionsFromWiki =
|
||||
(wiki: OrderedMap<string, Record<SpecialAbility>>) =>
|
||||
pipe (getMagicalTraditionsHeroEntries, mapMaybe (pipe (ADA.id, lookupF (wiki))))
|
||||
|
||||
/**
|
||||
* Get blessed traditions' dependent entry.
|
||||
* @param list
|
||||
*/
|
||||
export const getBlessedTradition = find (isActiveBlessedTradition)
|
||||
|
||||
/**
|
||||
* Get blessed tradition's' wiki entry.
|
||||
* @param wiki
|
||||
* @param list
|
||||
*/
|
||||
export const getBlessedTraditionFromWiki =
|
||||
(wiki: OrderedMap<string, Record<SpecialAbility>>) =>
|
||||
pipe (getBlessedTradition, bindF (pipe (ADA.id, lookupF (wiki))))
|
||||
|
||||
/**
|
||||
* Map a magical tradition's ID to a numeric magical tradition ID used by
|
||||
* spells.
|
||||
*
|
||||
* *Note: Zauberalchimisten do not get spells and thus do not have a numeric
|
||||
* magical tradition ID.*
|
||||
*/
|
||||
export const mapMagicalTradIdToNumId =
|
||||
(tradition_id: string): Maybe<MagicalTradition> => {
|
||||
switch (tradition_id) {
|
||||
case SpecialAbilityId.TraditionGuildMages:
|
||||
return Just (MagicalTradition.GuildMages)
|
||||
|
||||
case SpecialAbilityId.TraditionWitches:
|
||||
return Just (MagicalTradition.Witches)
|
||||
|
||||
case SpecialAbilityId.TraditionElves:
|
||||
return Just (MagicalTradition.Elves)
|
||||
|
||||
case SpecialAbilityId.TraditionDruids:
|
||||
return Just (MagicalTradition.Druids)
|
||||
|
||||
case SpecialAbilityId.TraditionIllusionist:
|
||||
return Just (MagicalTradition.Scharlatane)
|
||||
|
||||
case SpecialAbilityId.TraditionArcaneBard:
|
||||
return Just (MagicalTradition.ArcaneBards)
|
||||
|
||||
case SpecialAbilityId.TraditionArcaneDancer:
|
||||
return Just (MagicalTradition.ArcaneDancers)
|
||||
|
||||
case SpecialAbilityId.TraditionIntuitiveMage:
|
||||
return Just (MagicalTradition.IntuitiveZauberer)
|
||||
|
||||
case SpecialAbilityId.TraditionSavant:
|
||||
return Just (MagicalTradition.Meistertalentierte)
|
||||
|
||||
case SpecialAbilityId.TraditionQabalyaMage:
|
||||
return Just (MagicalTradition.Qabalyamagier)
|
||||
|
||||
case SpecialAbilityId.TraditionKristallomanten:
|
||||
return Just (MagicalTradition.Kristallomanten)
|
||||
|
||||
case SpecialAbilityId.TraditionGeoden:
|
||||
return Just (MagicalTradition.Geodes)
|
||||
|
||||
case SpecialAbilityId.TraditionSchelme:
|
||||
return Just (MagicalTradition.Rogues)
|
||||
|
||||
case SpecialAbilityId.TraditionAnimisten:
|
||||
return Just (MagicalTradition.Animists)
|
||||
|
||||
case SpecialAbilityId.TraditionZibilijas:
|
||||
return Just (MagicalTradition.Zibilija)
|
||||
|
||||
case SpecialAbilityId.TraditionBrobimGeoden:
|
||||
return Just (MagicalTradition.BrobimGeoden)
|
||||
|
||||
default:
|
||||
return Nothing
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a numeric magical tradition ID used by spells to a magical tradition's
|
||||
* ID.
|
||||
*/
|
||||
export const mapMagicalNumIdToTradId =
|
||||
(tradition_id: MagicalTradition): Maybe<SpecialAbilityId> => {
|
||||
switch (tradition_id) {
|
||||
case MagicalTradition.GuildMages:
|
||||
return Just (SpecialAbilityId.TraditionGuildMages)
|
||||
|
||||
case MagicalTradition.Witches:
|
||||
return Just (SpecialAbilityId.TraditionWitches)
|
||||
|
||||
case MagicalTradition.Elves:
|
||||
return Just (SpecialAbilityId.TraditionElves)
|
||||
|
||||
case MagicalTradition.Druids:
|
||||
return Just (SpecialAbilityId.TraditionDruids)
|
||||
|
||||
case MagicalTradition.Scharlatane:
|
||||
return Just (SpecialAbilityId.TraditionIllusionist)
|
||||
|
||||
case MagicalTradition.ArcaneBards:
|
||||
return Just (SpecialAbilityId.TraditionArcaneBard)
|
||||
|
||||
case MagicalTradition.ArcaneDancers:
|
||||
return Just (SpecialAbilityId.TraditionArcaneDancer)
|
||||
|
||||
case MagicalTradition.IntuitiveZauberer:
|
||||
return Just (SpecialAbilityId.TraditionIntuitiveMage)
|
||||
|
||||
case MagicalTradition.Meistertalentierte:
|
||||
return Just (SpecialAbilityId.TraditionSavant)
|
||||
|
||||
case MagicalTradition.Qabalyamagier:
|
||||
return Just (SpecialAbilityId.TraditionQabalyaMage)
|
||||
|
||||
case MagicalTradition.Kristallomanten:
|
||||
return Just (SpecialAbilityId.TraditionKristallomanten)
|
||||
|
||||
case MagicalTradition.Geodes:
|
||||
return Just (SpecialAbilityId.TraditionGeoden)
|
||||
|
||||
case MagicalTradition.Rogues:
|
||||
return Just (SpecialAbilityId.TraditionSchelme)
|
||||
|
||||
case MagicalTradition.Animists:
|
||||
return Just (SpecialAbilityId.TraditionAnimisten)
|
||||
|
||||
case MagicalTradition.Zibilija:
|
||||
return Just (SpecialAbilityId.TraditionZibilijas)
|
||||
|
||||
case MagicalTradition.BrobimGeoden:
|
||||
return Just (SpecialAbilityId.TraditionBrobimGeoden)
|
||||
|
||||
default:
|
||||
return Nothing
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a blessed tradition's ID to a numeric blessed tradition ID used by
|
||||
* chants.
|
||||
*/
|
||||
export const mapBlessedTradIdToNumId =
|
||||
(tradition_id: string): Maybe<BlessedTradition> => {
|
||||
switch (tradition_id) {
|
||||
case SpecialAbilityId.TraditionChurchOfPraios:
|
||||
return Just (BlessedTradition.ChurchOfPraios)
|
||||
|
||||
case SpecialAbilityId.TraditionChurchOfRondra:
|
||||
return Just (BlessedTradition.ChurchOfRondra)
|
||||
|
||||
case SpecialAbilityId.TraditionChurchOfBoron:
|
||||
return Just (BlessedTradition.ChurchOfBoron)
|
||||
|
||||
case SpecialAbilityId.TraditionChurchOfHesinde:
|
||||
return Just (BlessedTradition.ChurchOfHesinde)
|
||||
|
||||
case SpecialAbilityId.TraditionChurchOfPhex:
|
||||
return Just (BlessedTradition.ChurchOfPhex)
|
||||
|
||||
case SpecialAbilityId.TraditionChurchOfPeraine:
|
||||
return Just (BlessedTradition.ChurchOfPeraine)
|
||||
|
||||
case SpecialAbilityId.TraditionChurchOfEfferd:
|
||||
return Just (BlessedTradition.ChurchOfEfferd)
|
||||
|
||||
case SpecialAbilityId.TraditionChurchOfTravia:
|
||||
return Just (BlessedTradition.ChurchOfTravia)
|
||||
|
||||
case SpecialAbilityId.TraditionChurchOfFirun:
|
||||
return Just (BlessedTradition.ChurchOfFirun)
|
||||
|
||||
case SpecialAbilityId.TraditionChurchOfTsa:
|
||||
return Just (BlessedTradition.ChurchOfTsa)
|
||||
|
||||
case SpecialAbilityId.TraditionChurchOfIngerimm:
|
||||
return Just (BlessedTradition.ChurchOfIngerimm)
|
||||
|
||||
case SpecialAbilityId.TraditionChurchOfRahja:
|
||||
return Just (BlessedTradition.ChurchOfRahja)
|
||||
|
||||
case SpecialAbilityId.TraditionCultOfTheNamelessOne:
|
||||
return Just (BlessedTradition.CultOfTheNamelessOne)
|
||||
|
||||
case SpecialAbilityId.TraditionChurchOfAves:
|
||||
return Just (BlessedTradition.ChurchOfAves)
|
||||
|
||||
case SpecialAbilityId.TraditionChurchOfIfirn:
|
||||
return Just (BlessedTradition.ChurchOfIfirn)
|
||||
|
||||
case SpecialAbilityId.TraditionChurchOfKor:
|
||||
return Just (BlessedTradition.ChurchOfKor)
|
||||
|
||||
case SpecialAbilityId.TraditionChurchOfNandus:
|
||||
return Just (BlessedTradition.ChurchOfNandus)
|
||||
|
||||
case SpecialAbilityId.TraditionChurchOfSwafnir:
|
||||
return Just (BlessedTradition.ChurchOfSwafnir)
|
||||
|
||||
case SpecialAbilityId.TraditionCultOfNuminoru:
|
||||
return Just (BlessedTradition.CultOfNuminoru)
|
||||
|
||||
case SpecialAbilityId.TraditionCultOfLevthan:
|
||||
return Just (BlessedTradition.Levthankult)
|
||||
|
||||
default:
|
||||
return Nothing
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a blessed tradition's ID to a numeric blessed tradition ID used by
|
||||
* chants.
|
||||
*/
|
||||
export const mapBlessedNumIdToTradId =
|
||||
(tradition_id: BlessedTradition): Maybe<SpecialAbilityId> => {
|
||||
switch (tradition_id) {
|
||||
case BlessedTradition.ChurchOfPraios:
|
||||
return Just (SpecialAbilityId.TraditionChurchOfPraios)
|
||||
|
||||
case BlessedTradition.ChurchOfRondra:
|
||||
return Just (SpecialAbilityId.TraditionChurchOfRondra)
|
||||
|
||||
case BlessedTradition.ChurchOfBoron:
|
||||
return Just (SpecialAbilityId.TraditionChurchOfBoron)
|
||||
|
||||
case BlessedTradition.ChurchOfHesinde:
|
||||
return Just (SpecialAbilityId.TraditionChurchOfHesinde)
|
||||
|
||||
case BlessedTradition.ChurchOfPhex:
|
||||
return Just (SpecialAbilityId.TraditionChurchOfPhex)
|
||||
|
||||
case BlessedTradition.ChurchOfPeraine:
|
||||
return Just (SpecialAbilityId.TraditionChurchOfPeraine)
|
||||
|
||||
case BlessedTradition.ChurchOfEfferd:
|
||||
return Just (SpecialAbilityId.TraditionChurchOfEfferd)
|
||||
|
||||
case BlessedTradition.ChurchOfTravia:
|
||||
return Just (SpecialAbilityId.TraditionChurchOfTravia)
|
||||
|
||||
case BlessedTradition.ChurchOfFirun:
|
||||
return Just (SpecialAbilityId.TraditionChurchOfFirun)
|
||||
|
||||
case BlessedTradition.ChurchOfTsa:
|
||||
return Just (SpecialAbilityId.TraditionChurchOfTsa)
|
||||
|
||||
case BlessedTradition.ChurchOfIngerimm:
|
||||
return Just (SpecialAbilityId.TraditionChurchOfIngerimm)
|
||||
|
||||
case BlessedTradition.ChurchOfRahja:
|
||||
return Just (SpecialAbilityId.TraditionChurchOfRahja)
|
||||
|
||||
case BlessedTradition.CultOfTheNamelessOne:
|
||||
return Just (SpecialAbilityId.TraditionCultOfTheNamelessOne)
|
||||
|
||||
case BlessedTradition.ChurchOfAves:
|
||||
return Just (SpecialAbilityId.TraditionChurchOfAves)
|
||||
|
||||
case BlessedTradition.ChurchOfIfirn:
|
||||
return Just (SpecialAbilityId.TraditionChurchOfIfirn)
|
||||
|
||||
case BlessedTradition.ChurchOfKor:
|
||||
return Just (SpecialAbilityId.TraditionChurchOfKor)
|
||||
|
||||
case BlessedTradition.ChurchOfNandus:
|
||||
return Just (SpecialAbilityId.TraditionChurchOfNandus)
|
||||
|
||||
case BlessedTradition.ChurchOfSwafnir:
|
||||
return Just (SpecialAbilityId.TraditionChurchOfSwafnir)
|
||||
|
||||
case BlessedTradition.CultOfNuminoru:
|
||||
return Just (SpecialAbilityId.TraditionCultOfNuminoru)
|
||||
|
||||
case BlessedTradition.Levthankult:
|
||||
return Just (SpecialAbilityId.TraditionCultOfLevthan)
|
||||
|
||||
default:
|
||||
return Nothing
|
||||
}
|
||||
}
|
||||
@@ -1,596 +0,0 @@
|
||||
/**
|
||||
* Calculate the Adventure Points for when removing the entry. This might not
|
||||
* be the actual cost. In those cases the AP difference needs to be calculated
|
||||
* for AP spent total.
|
||||
*
|
||||
* @file src/Utilities/activatableCostUtils.ts
|
||||
* @author Lukas Obermann
|
||||
* @since 1.1.0
|
||||
*/
|
||||
|
||||
import { cnst, flip, ident } from "../../../Data/Function"
|
||||
import { fmap, fmapF } from "../../../Data/Functor"
|
||||
import { over, set } from "../../../Data/Lens"
|
||||
import { appendStr, countWith, filter, foldl, ifoldr, isList, List, map, notElem, notNull, subscript, subscriptF, sum } from "../../../Data/List"
|
||||
import { any, bind, bindF, elem, elemF, ensure, fromJust, fromMaybe, isJust, isNothing, joinMaybeList, Just, liftM2, listToMaybe, mapMaybe, Maybe, maybe, Nothing } from "../../../Data/Maybe"
|
||||
import { add, gt, multiply, negate } from "../../../Data/Num"
|
||||
import { lookup, lookupF } from "../../../Data/OrderedMap"
|
||||
import { Record } from "../../../Data/Record"
|
||||
import { Pair } from "../../../Data/Tuple"
|
||||
import { Category } from "../../Constants/Categories"
|
||||
import { AdvantageId, DisadvantageId, SpecialAbilityId } from "../../Constants/Ids"
|
||||
import { ActivatableDependent, isActivatableDependent } from "../../Models/ActiveEntries/ActivatableDependent"
|
||||
import { ActiveObject } from "../../Models/ActiveEntries/ActiveObject"
|
||||
import { ActiveObjectWithId } from "../../Models/ActiveEntries/ActiveObjectWithId"
|
||||
import { HeroModel, HeroModelRecord } from "../../Models/Hero/HeroModel"
|
||||
import { ActivatableNameCost, ActivatableNameCostA_, ActivatableNameCostL, ActivatableNameCostL_, ActivatableNameCostSafeCost } from "../../Models/View/ActivatableNameCost"
|
||||
import { Advantage } from "../../Models/Wiki/Advantage"
|
||||
import { Disadvantage, isDisadvantage } from "../../Models/Wiki/Disadvantage"
|
||||
import { Skill } from "../../Models/Wiki/Skill"
|
||||
import { StaticData, StaticDataRecord } from "../../Models/Wiki/WikiModel"
|
||||
import { Activatable, EntryWithCategory, SkillishEntry } from "../../Models/Wiki/wikiTypeHelpers"
|
||||
import { isMaybeActive } from "../Activatable/isActive"
|
||||
import { getSelectOptionCost } from "../Activatable/selectionUtils"
|
||||
import { nbsp, nobr } from "../Chars"
|
||||
import { getHeroStateItem } from "../heroStateUtils"
|
||||
import { translate } from "../I18n"
|
||||
import { getCategoryById } from "../IDUtils"
|
||||
import { getAPForActivatation, icToIx } from "../ImprovementCost"
|
||||
import { toRoman } from "../NumberUtils"
|
||||
import { pipe, pipe_ } from "../pipe"
|
||||
import { isNumber, misNumberM, misStringM } from "../typeCheckUtils"
|
||||
import { getWikiEntry, isActivatableWikiEntry, isSkillishWikiEntry } from "../WikiUtils"
|
||||
|
||||
const HA = HeroModel.A
|
||||
const AAL = Advantage.AL
|
||||
const AOWIA = ActiveObjectWithId.A
|
||||
|
||||
const isDisadvantageActive =
|
||||
(id: string) =>
|
||||
pipe (
|
||||
HA.disadvantages,
|
||||
lookup (id),
|
||||
isMaybeActive
|
||||
)
|
||||
|
||||
const getCostForEntryWithSkillSel =
|
||||
(ensureId: (x: Maybe<number | string>) => Maybe<string>) =>
|
||||
(wiki: StaticDataRecord) =>
|
||||
(mcurrent_sid: Maybe<string | number>) =>
|
||||
(mcurrent_cost: Maybe<number | List<number>>) =>
|
||||
pipe_ (
|
||||
mcurrent_sid,
|
||||
ensureId,
|
||||
bindF (getWikiEntry (wiki)),
|
||||
bindF<EntryWithCategory, SkillishEntry> (ensure (isSkillishWikiEntry)),
|
||||
bindF (skill => pipe_ (
|
||||
mcurrent_cost,
|
||||
bindF (ensure (isList)),
|
||||
|
||||
// Use the IC as an index for the list
|
||||
// of AP
|
||||
bindF (subscriptF (icToIx (Skill.AL.ic (skill))))
|
||||
))
|
||||
)
|
||||
|
||||
const isPersonalityFlawNotPaid =
|
||||
(sid: number) =>
|
||||
(paid_entries_max: number) =>
|
||||
(isEntryToAdd: boolean) =>
|
||||
(all_active: List<Record<ActiveObject>>) =>
|
||||
(mcurrent_sid: Maybe<string | number>) =>
|
||||
elemF (mcurrent_sid) (sid)
|
||||
&& countWith ((e: Record<ActiveObject>) =>
|
||||
pipe (ActiveObject.AL.sid, elem<string | number> (sid)) (e)
|
||||
|
||||
// Entries with custom cost are ignored for the rule
|
||||
&& isNothing (ActiveObject.AL.cost (e)))
|
||||
(all_active) > (isEntryToAdd ? paid_entries_max - 1 : paid_entries_max)
|
||||
|
||||
/**
|
||||
* A function for folding over a list of `ActiveObject`s to get the highest
|
||||
* level. Ignores entries with custom cost.
|
||||
*
|
||||
* `foldl compareMaxLevel 0 all_entries`
|
||||
*/
|
||||
export const compareMaxLevel =
|
||||
(previous_max: number) =>
|
||||
(active: Record<ActiveObject>) => {
|
||||
const mactive_level = ActiveObject.AL.tier (active)
|
||||
|
||||
if (isJust (mactive_level)) {
|
||||
const active_level = fromJust (mactive_level)
|
||||
|
||||
return active_level > previous_max
|
||||
&& isNothing (ActiveObject.AL.cost (active))
|
||||
? active_level
|
||||
: previous_max
|
||||
}
|
||||
|
||||
return previous_max
|
||||
}
|
||||
|
||||
/**
|
||||
* A function for folding over a list of `ActiveObject`s to get the
|
||||
* second-highest level. Ignores entries with custom cost.
|
||||
*
|
||||
* `foldl (compareSubMaxLevel max_level) 0 all_entries`
|
||||
*/
|
||||
export const compareSubMaxLevel =
|
||||
(max: number) =>
|
||||
(previous_max: number) =>
|
||||
(active: Record<ActiveObject>) => {
|
||||
const mactive_level = ActiveObject.AL.tier (active)
|
||||
|
||||
if (isJust (mactive_level)) {
|
||||
const active_level = fromJust (mactive_level)
|
||||
|
||||
return active_level > previous_max
|
||||
&& active_level < max
|
||||
&& isNothing (ActiveObject.AL.cost (active))
|
||||
? active_level
|
||||
: previous_max
|
||||
}
|
||||
|
||||
return previous_max
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value(s) how the spent AP value would change after removing the
|
||||
* respective entry.
|
||||
*
|
||||
* @param isEntryToAdd If `entry` has not been added to the list of active
|
||||
* entries yet, this must be `true`, otherwise `false`.
|
||||
*/
|
||||
const getEntrySpecificCost =
|
||||
(isEntryToAdd: boolean) =>
|
||||
(wiki: StaticDataRecord) =>
|
||||
(hero: HeroModelRecord) =>
|
||||
(wiki_entry: Activatable) =>
|
||||
(hero_entry: Maybe<Record<ActivatableDependent>>) =>
|
||||
|
||||
// tslint:disable-next-line: cyclomatic-complexity
|
||||
(entry: Record<ActiveObjectWithId>): Maybe<number | List<number>> => {
|
||||
const current_id = AOWIA.id (entry)
|
||||
const mcurrent_sid = AOWIA.sid (entry)
|
||||
const mcurrent_sid2 = AOWIA.sid2 (entry)
|
||||
const mcurrent_sid3 = AOWIA.sid3 (entry)
|
||||
const mcurrent_level = AOWIA.tier (entry)
|
||||
|
||||
const mcurrent_cost = AAL.cost (wiki_entry)
|
||||
|
||||
const all_active = joinMaybeList (fmap (ActivatableDependent.A.active) (hero_entry))
|
||||
|
||||
switch (current_id) {
|
||||
// Entry with Skill selection
|
||||
case AdvantageId.Aptitude:
|
||||
case AdvantageId.ExceptionalSkill:
|
||||
case AdvantageId.ExceptionalCombatTechnique:
|
||||
case AdvantageId.WeaponAptitude:
|
||||
case DisadvantageId.Incompetent:
|
||||
case SpecialAbilityId.AdaptionZauber:
|
||||
case SpecialAbilityId.FavoriteSpellwork:
|
||||
case SpecialAbilityId.Forschungsgebiet:
|
||||
case SpecialAbilityId.Expertenwissen:
|
||||
case SpecialAbilityId.Wissensdurst:
|
||||
case SpecialAbilityId.Lieblingsliturgie:
|
||||
case SpecialAbilityId.WegDerGelehrten:
|
||||
case SpecialAbilityId.WegDerKuenstlerin:
|
||||
case SpecialAbilityId.Fachwissen: {
|
||||
return getCostForEntryWithSkillSel (misStringM)
|
||||
(wiki)
|
||||
(mcurrent_sid)
|
||||
(mcurrent_cost)
|
||||
}
|
||||
|
||||
case DisadvantageId.PersonalityFlaw: {
|
||||
if (
|
||||
// 7 = "Prejudice" => more than one entry possible
|
||||
// more than one entry of Prejudice does not contribute to AP spent
|
||||
isPersonalityFlawNotPaid (7) (1) (isEntryToAdd) (all_active) (mcurrent_sid)
|
||||
|
||||
// 8 = "Unworldly" => more than one entry possible
|
||||
// more than two entries of Unworldly do not contribute to AP spent
|
||||
|| isPersonalityFlawNotPaid (8) (2) (isEntryToAdd) (all_active) (mcurrent_sid)
|
||||
) {
|
||||
return Just (0)
|
||||
}
|
||||
|
||||
return getSelectOptionCost (wiki_entry) (mcurrent_sid)
|
||||
}
|
||||
|
||||
case DisadvantageId.Principles:
|
||||
case DisadvantageId.Obligations: {
|
||||
const current_max_level = foldl (compareMaxLevel)
|
||||
(0)
|
||||
(all_active)
|
||||
|
||||
const current_second_max_level = foldl (compareSubMaxLevel (current_max_level))
|
||||
(0)
|
||||
(all_active)
|
||||
|
||||
if (isNothing (mcurrent_level)) {
|
||||
return Nothing
|
||||
}
|
||||
|
||||
const current_level = fromJust (mcurrent_level)
|
||||
|
||||
if (
|
||||
// If the entry is not the one with the highest level, adding or
|
||||
// removing it won't affect AP spent at all
|
||||
current_max_level > current_level
|
||||
|
||||
// If there is more than one entry on the same level if this entry is
|
||||
// active, it won't affect AP spent at all. Thus, if the entry is to
|
||||
// be added, there must be at least one (> 0) entry for this rule to
|
||||
// take effect.
|
||||
|| countWith (pipe (ActiveObject.AL.tier, elem (current_level)))
|
||||
(all_active) > (isEntryToAdd ? 0 : 1)
|
||||
) {
|
||||
return Nothing
|
||||
}
|
||||
|
||||
// Otherwise, the level difference results in the cost.
|
||||
return fmapF (misNumberM (mcurrent_cost))
|
||||
(multiply (current_level - current_second_max_level))
|
||||
}
|
||||
|
||||
case DisadvantageId.BadHabit: {
|
||||
// more than three entries cannot contribute to AP spent; entries with
|
||||
// custom cost are ignored for the rule's effect
|
||||
if (countWith (pipe (ActiveObject.AL.cost, isNothing))
|
||||
(all_active) > (isEntryToAdd ? 2 : 3)) {
|
||||
return Nothing
|
||||
}
|
||||
|
||||
return mcurrent_cost
|
||||
}
|
||||
|
||||
case SpecialAbilityId.SkillSpecialization: {
|
||||
return pipe_ (
|
||||
mcurrent_sid,
|
||||
misStringM,
|
||||
bindF (
|
||||
current_sid =>
|
||||
fmapF (lookup (current_sid)
|
||||
(StaticData.A.skills (wiki)))
|
||||
(skill =>
|
||||
|
||||
// Multiply number of final occurences of the
|
||||
// same skill...
|
||||
(countWith ((e: Record<ActiveObject>) =>
|
||||
pipe (
|
||||
ActiveObject.AL.sid,
|
||||
elem<string | number> (current_sid)
|
||||
)
|
||||
(e)
|
||||
|
||||
// Entries with custom cost are ignored for the rule
|
||||
&& isNothing (ActiveObject.AL.cost (e)))
|
||||
(all_active) + (isEntryToAdd ? 1 : 0))
|
||||
|
||||
// ...with the skill's IC
|
||||
* getAPForActivatation (Skill.AL.ic (skill)))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
case SpecialAbilityId.Language: {
|
||||
// Native Tongue (level 4) does not cost anything
|
||||
return elem (4) (mcurrent_level) ? Nothing : mcurrent_cost
|
||||
}
|
||||
|
||||
case SpecialAbilityId.PropertyKnowledge:
|
||||
case SpecialAbilityId.AspectKnowledge: {
|
||||
// Does not include custom cost activations in terms of calculated cost
|
||||
const amount = countWith (pipe (ActiveObject.AL.cost, isNothing))
|
||||
(all_active)
|
||||
|
||||
const index = amount + (isEntryToAdd ? 0 : -1)
|
||||
|
||||
if (isNothing (mcurrent_cost)) {
|
||||
return Nothing
|
||||
}
|
||||
|
||||
const current_cost = fromJust (mcurrent_cost)
|
||||
|
||||
return isList (current_cost) ? subscript (current_cost) (index) : Nothing
|
||||
}
|
||||
|
||||
case SpecialAbilityId.TraditionWitches: {
|
||||
// There are two disadvantages that, when active, decrease the cost of
|
||||
// this tradition by 10 AP each
|
||||
const decreaseCost = (id: string) => (cost: number) =>
|
||||
isDisadvantageActive (id) (hero) ? cost - 10 : cost
|
||||
|
||||
return pipe_ (
|
||||
mcurrent_cost,
|
||||
misNumberM,
|
||||
fmap (pipe (
|
||||
decreaseCost (DisadvantageId.NoFlyingBalm),
|
||||
decreaseCost (DisadvantageId.NoFamiliar)
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
case SpecialAbilityId.Recherchegespuer: {
|
||||
// The AP cost for this SA consist of two parts: AP based on the IC of
|
||||
// the main subject (from "SA_531"/Wissensdurst) in addition to AP based
|
||||
// on the IC of the side subject selected in this SA.
|
||||
|
||||
const mhero_entry_SA_531 = lookup<string> (SpecialAbilityId.Wissensdurst)
|
||||
(HA.specialAbilities (hero))
|
||||
|
||||
if (isNothing (mhero_entry_SA_531)) {
|
||||
return Nothing
|
||||
}
|
||||
|
||||
if (isNothing (mcurrent_cost) || isNothing (mcurrent_cost)) {
|
||||
return Nothing
|
||||
}
|
||||
|
||||
const current_cost = fromJust (mcurrent_cost)
|
||||
|
||||
if (isNumber (current_cost)) {
|
||||
return Nothing
|
||||
}
|
||||
|
||||
const hero_entry_SA_531 = fromJust (mhero_entry_SA_531)
|
||||
|
||||
const getCostFromHeroEntry =
|
||||
pipe (
|
||||
ActiveObject.AL.sid,
|
||||
misStringM,
|
||||
bindF (lookupF (StaticData.A.skills (wiki))),
|
||||
bindF (pipe (Skill.A.ic, icToIx, subscript (current_cost)))
|
||||
)
|
||||
|
||||
return liftM2 (add)
|
||||
(getCostFromHeroEntry (entry))
|
||||
(pipe_ (
|
||||
hero_entry_SA_531,
|
||||
ActivatableDependent.A.active,
|
||||
listToMaybe,
|
||||
bindF (getCostFromHeroEntry)
|
||||
))
|
||||
}
|
||||
|
||||
case SpecialAbilityId.Handwerkskunst:
|
||||
case SpecialAbilityId.KindDerNatur:
|
||||
case SpecialAbilityId.KoerperlichesGeschick:
|
||||
case SpecialAbilityId.SozialeKompetenz:
|
||||
case SpecialAbilityId.Universalgenie: {
|
||||
return pipe_ (
|
||||
List (mcurrent_sid, mcurrent_sid2, mcurrent_sid3),
|
||||
mapMaybe (sid => getCostForEntryWithSkillSel (misStringM)
|
||||
(wiki)
|
||||
(sid)
|
||||
(mcurrent_cost)),
|
||||
sum,
|
||||
ensure (gt (0))
|
||||
)
|
||||
}
|
||||
|
||||
default: {
|
||||
if (any (notNull) (Advantage.AL.select (wiki_entry)) && isNothing (mcurrent_cost)) {
|
||||
return getSelectOptionCost (wiki_entry) (mcurrent_sid)
|
||||
}
|
||||
|
||||
return mcurrent_cost
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the AP value and if the entry is an automatic entry
|
||||
*/
|
||||
const getTotalCost =
|
||||
(isEntryToAdd: boolean) =>
|
||||
(automatic_advantages: List<string>) =>
|
||||
(wiki: StaticDataRecord) =>
|
||||
(hero: HeroModelRecord) =>
|
||||
(entry: Record<ActiveObjectWithId>) =>
|
||||
(hero_entry: Maybe<Record<ActivatableDependent>>) =>
|
||||
(wiki_entry: Activatable): Pair<number | List<number>, boolean> => {
|
||||
const custom_cost = AOWIA.cost (entry)
|
||||
|
||||
const is_automatic = List.elem (AAL.id (wiki_entry)) (automatic_advantages)
|
||||
|
||||
if (isJust (custom_cost)) {
|
||||
const is_disadvantage = Disadvantage.is (wiki_entry)
|
||||
|
||||
return Pair (is_disadvantage ? -fromJust (custom_cost) : fromJust (custom_cost), is_automatic)
|
||||
}
|
||||
|
||||
const mentry_specifc_cost = getEntrySpecificCost (isEntryToAdd)
|
||||
(wiki)
|
||||
(hero)
|
||||
(wiki_entry)
|
||||
(hero_entry)
|
||||
(entry)
|
||||
|
||||
const current_cost = fromMaybe<number | List<number>> (0) (mentry_specifc_cost)
|
||||
|
||||
if (isDisadvantage (wiki_entry)) {
|
||||
return Pair (
|
||||
isList (current_cost) ? map (negate) (current_cost) : -current_cost,
|
||||
is_automatic
|
||||
)
|
||||
}
|
||||
|
||||
return Pair (current_cost, is_automatic)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the AP you get when removing the ActiveObject.
|
||||
*
|
||||
* @param isEntryToAdd If `entry` has not been added to the list of active
|
||||
* entries yet, this must be `true`, otherwise `false`.
|
||||
*/
|
||||
export const getCost =
|
||||
(isEntryToAdd: boolean) =>
|
||||
(automatic_advantages: List<string>) =>
|
||||
(wiki: StaticDataRecord) =>
|
||||
(hero: HeroModelRecord) =>
|
||||
(entry: Record<ActiveObjectWithId>): Maybe<Pair<number | List<number>, boolean>> => {
|
||||
const current_id = AOWIA.id (entry)
|
||||
|
||||
return pipe_ (
|
||||
getWikiEntry (wiki) (current_id),
|
||||
bindF (ensure (isActivatableWikiEntry)),
|
||||
fmap (getTotalCost (isEntryToAdd)
|
||||
(automatic_advantages)
|
||||
(wiki)
|
||||
(hero)
|
||||
(entry)
|
||||
(bind (getHeroStateItem (hero) (current_id))
|
||||
(ensure (isActivatableDependent))))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the results from `getCost` saved in `ActivatableNameCost` to calculate
|
||||
* the final cost value (and no list).
|
||||
*/
|
||||
const putCurrentCost =
|
||||
(entry: Record<ActivatableNameCost>): Record<ActivatableNameCostSafeCost> =>
|
||||
over (ActivatableNameCostL.finalCost)
|
||||
((current_cost): number => {
|
||||
const current_id = ActivatableNameCostA_.id (entry)
|
||||
const category = getCategoryById (current_id)
|
||||
const mcurrent_level = ActivatableNameCostA_.tier (entry)
|
||||
|
||||
// If the AP cost is still a List, and it is a Special Ability, it
|
||||
// must be a list that represents the cost *for* each level separate,
|
||||
// thus all relevant values must be summed up. In case of an
|
||||
// advantage or disadvantage, it represents the cost *at* each level,
|
||||
// so it does not need to be accumulated.
|
||||
if (isList (current_cost)) {
|
||||
const current_level = fromMaybe (1) (mcurrent_level)
|
||||
|
||||
if (elem (Category.SPECIAL_ABILITIES) (category)) {
|
||||
return ifoldr (i => i <= (current_level - 1) ? add : cnst (ident as ident<number>))
|
||||
(0)
|
||||
(current_cost)
|
||||
}
|
||||
|
||||
return fromMaybe (0) (subscript (current_cost) (current_level - 1))
|
||||
}
|
||||
|
||||
// Usually, a single AP value represents the value has to be
|
||||
// multiplied by the level to result in the final cost. There are two
|
||||
// disadvantages where this is not valid.
|
||||
if (
|
||||
isJust (mcurrent_level)
|
||||
&& current_id !== DisadvantageId.Principles
|
||||
&& current_id !== DisadvantageId.Obligations
|
||||
&& isNothing (ActivatableNameCostA_.customCost (entry))
|
||||
) {
|
||||
return maybe (0) (multiply (current_cost)) (mcurrent_level)
|
||||
}
|
||||
|
||||
return current_cost
|
||||
})
|
||||
(entry) as Record<ActivatableNameCostSafeCost>
|
||||
|
||||
/**
|
||||
* Gets the level string that has to be appended to the name.
|
||||
*/
|
||||
const getLevel = (level: number) => `${nbsp}${toRoman (level)}`
|
||||
|
||||
/**
|
||||
* Gets the level string that has to be appended to the name. For special
|
||||
* abilities, where levels are bought separately, this means it has to display a
|
||||
* range when multiple levels have been bought.
|
||||
*/
|
||||
const getSpecialAbilityLevel =
|
||||
(level: number) => level > 1 ? `${nbsp}I${nobr}–${nobr}${toRoman (level)}` : getLevel (level)
|
||||
|
||||
/**
|
||||
* Id-based check if the entry is a special ability.
|
||||
*/
|
||||
const isSpecialAbilityById =
|
||||
pipe (getCategoryById, elem<Category> (Category.SPECIAL_ABILITIES))
|
||||
|
||||
/**
|
||||
* Gets the level string that hast to be appended to the name. This string is
|
||||
* aware of differences between dis/advantages and special abilties as well as
|
||||
* it handles the Native Tongue level for languages.
|
||||
*/
|
||||
const getFinalLevelName =
|
||||
(staticData: StaticDataRecord) =>
|
||||
(entry: Record<ActivatableNameCost>) =>
|
||||
|
||||
/**
|
||||
* @param current_level This is the same value from param `entry`, but this is
|
||||
* ensured to be a number.
|
||||
*/
|
||||
(current_level: number) => {
|
||||
const current_id = ActivatableNameCostA_.id (entry)
|
||||
const current_cost = ActivatableNameCost.A.finalCost (entry)
|
||||
|
||||
if (current_id === SpecialAbilityId.Language && current_level === 4) {
|
||||
return `${nbsp}${translate (staticData) ("specialabilities.nativetonguelevel")}`
|
||||
}
|
||||
|
||||
if (isList (current_cost) || isSpecialAbilityById (current_id)) {
|
||||
return getSpecialAbilityLevel (current_level)
|
||||
}
|
||||
|
||||
return getLevel (current_level)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a `Just` of the level string, if no level string available, returns
|
||||
* `Nothing`.
|
||||
*/
|
||||
const getLevelNameIfValid =
|
||||
(staticData: StaticDataRecord) =>
|
||||
(entry: Record<ActivatableNameCost>): Maybe<string> => {
|
||||
const current_id = ActivatableNameCostA_.id (entry)
|
||||
const mcurrent_level = ActivatableNameCostA_.tier (entry)
|
||||
|
||||
if (isJust (mcurrent_level)
|
||||
&& notElem (current_id) (List (DisadvantageId.Principles, DisadvantageId.Obligations))) {
|
||||
return Just (getFinalLevelName (staticData) (entry) (fromJust (mcurrent_level)))
|
||||
}
|
||||
|
||||
return Nothing
|
||||
}
|
||||
|
||||
export const putLevelName =
|
||||
(addLevelToName: boolean) =>
|
||||
(staticData: StaticDataRecord) =>
|
||||
(entry: Record<ActivatableNameCost>): Record<ActivatableNameCost> =>
|
||||
pipe_ (
|
||||
entry,
|
||||
getLevelNameIfValid (staticData),
|
||||
fmap ((levelName: string) => addLevelToName
|
||||
? pipe_ (
|
||||
entry,
|
||||
over (ActivatableNameCostL_.name)
|
||||
(flip (appendStr) (levelName)),
|
||||
set (ActivatableNameCostL_.levelName)
|
||||
(Just (levelName))
|
||||
)
|
||||
: set (ActivatableNameCostL_.levelName)
|
||||
(Just (levelName))
|
||||
(entry)),
|
||||
fromMaybe (entry)
|
||||
)
|
||||
|
||||
/**
|
||||
* Calculates level name and level-based cost and (optionally) updates `name`.
|
||||
* @param locale
|
||||
* @param addLevelToName If true, does not add `current_level` to `name`.
|
||||
*/
|
||||
export const convertPerTierCostToFinalCost =
|
||||
(addLevelToName: boolean) =>
|
||||
(staticData: StaticDataRecord) =>
|
||||
pipe (
|
||||
putLevelName (addLevelToName) (staticData),
|
||||
putCurrentCost
|
||||
)
|
||||
|
||||
export const getActiveWithNoCustomCost =
|
||||
filter (pipe (ActiveObject.A.cost, isNothing))
|
||||
@@ -1,470 +0,0 @@
|
||||
import { equals } from "../../../Data/Eq"
|
||||
import { cnst, ident, join } from "../../../Data/Function"
|
||||
import { fmap } from "../../../Data/Functor"
|
||||
import { elem, filter, List, sum } from "../../../Data/List"
|
||||
import { bindF, ensure, maybe, Maybe, thenF } from "../../../Data/Maybe"
|
||||
import { add, multiply } from "../../../Data/Num"
|
||||
import { foldr, lookupF } from "../../../Data/OrderedMap"
|
||||
import { OrderedSet } from "../../../Data/OrderedSet"
|
||||
import { Record } from "../../../Data/Record"
|
||||
import { bimap, first, fst, Pair, second, snd } from "../../../Data/Tuple"
|
||||
import { curryN3 } from "../../../Data/Tuple/Curry"
|
||||
import { Category } from "../../Constants/Categories"
|
||||
import { AdvantageIdsNoMaxInfl } from "../../Constants/Ids"
|
||||
import { ActivatableSkillDependent } from "../../Models/ActiveEntries/ActivatableSkillDependent"
|
||||
import { AttributeDependent } from "../../Models/ActiveEntries/AttributeDependent"
|
||||
import { SkillDependent } from "../../Models/ActiveEntries/SkillDependent"
|
||||
import { Energies } from "../../Models/Hero/Energies"
|
||||
import { HeroModel, HeroModelRecord } from "../../Models/Hero/HeroModel"
|
||||
import { PermanentEnergyLossAndBoughtBack } from "../../Models/Hero/PermanentEnergyLossAndBoughtBack"
|
||||
import { ActiveActivatable, ActiveActivatableA_ } from "../../Models/View/ActiveActivatable"
|
||||
import { AdventurePointsCategories } from "../../Models/View/AdventurePointsCategories"
|
||||
import { Advantage } from "../../Models/Wiki/Advantage"
|
||||
import { CombatTechnique } from "../../Models/Wiki/CombatTechnique"
|
||||
import { Disadvantage } from "../../Models/Wiki/Disadvantage"
|
||||
import { LiturgicalChant } from "../../Models/Wiki/LiturgicalChant"
|
||||
import { Profession } from "../../Models/Wiki/Profession"
|
||||
import { ProfessionVariant } from "../../Models/Wiki/ProfessionVariant"
|
||||
import { Race } from "../../Models/Wiki/Race"
|
||||
import { Skill } from "../../Models/Wiki/Skill"
|
||||
import { SpecialAbility } from "../../Models/Wiki/SpecialAbility"
|
||||
import { Spell } from "../../Models/Wiki/Spell"
|
||||
import { StaticData, StaticDataRecord } from "../../Models/Wiki/WikiModel"
|
||||
import { MatchingScriptAndLanguageRelated } from "../../Selectors/activatableSelectors"
|
||||
import { getAllActiveByCategory } from "../Activatable/activatableActiveUtils"
|
||||
import { getAPForActivatation, getAPForRange, ImprovementCost } from "../ImprovementCost"
|
||||
import { pipe, pipe_ } from "../pipe"
|
||||
import { getAdventurePointsSpentDifference } from "./adventurePointsUtils"
|
||||
|
||||
const SDA = StaticData.A
|
||||
const HA = HeroModel.A
|
||||
|
||||
// Attributes
|
||||
|
||||
const getAPForAttribute = pipe (
|
||||
AttributeDependent.A.value,
|
||||
curryN3 (getAPForRange) (ImprovementCost.E) (8)
|
||||
)
|
||||
|
||||
export const getAPSpentForAttributes = foldr (pipe (getAPForAttribute, add)) (0)
|
||||
|
||||
// Skills
|
||||
|
||||
const getAPForSkill =
|
||||
(x: Record<Skill>) =>
|
||||
pipe (SkillDependent.A.value, curryN3 (getAPForRange) (Skill.A.ic (x)) (0))
|
||||
|
||||
type skillsFold = (x: Record<SkillDependent>) => (s: number) => number
|
||||
|
||||
export const getAPSpentForSkills =
|
||||
(xmap: StaticData["skills"]) =>
|
||||
foldr (join (pipe (
|
||||
SkillDependent.A.id,
|
||||
lookupF (xmap),
|
||||
maybe<skillsFold> (cnst (ident))
|
||||
(x => pipe (getAPForSkill (x), add))
|
||||
)))
|
||||
(0)
|
||||
|
||||
// Combat Techniques
|
||||
|
||||
const getAPForCombatTechnique =
|
||||
(x: Record<CombatTechnique>) =>
|
||||
pipe (SkillDependent.A.value, curryN3 (getAPForRange) (CombatTechnique.A.ic (x)) (6))
|
||||
|
||||
export const getAPSpentForCombatTechniques =
|
||||
(xmap: StaticData["combatTechniques"]) =>
|
||||
foldr (join (pipe (
|
||||
SkillDependent.A.id,
|
||||
lookupF (xmap),
|
||||
maybe<skillsFold> (cnst (ident))
|
||||
(x => pipe (getAPForCombatTechnique (x), add))
|
||||
)))
|
||||
(0)
|
||||
|
||||
// Spells / Liturgical Chants
|
||||
|
||||
const getAPForSpellOrChant =
|
||||
(x: Record<Spell> | Record<LiturgicalChant>) =>
|
||||
(asd: Record<ActivatableSkillDependent>) =>
|
||||
ActivatableSkillDependent.A.active (asd)
|
||||
? pipe_ (
|
||||
asd,
|
||||
ActivatableSkillDependent.A.value,
|
||||
curryN3 (getAPForRange) (Spell.AL.ic (x)) (0),
|
||||
add (getAPForActivatation (Spell.AL.ic (x)))
|
||||
)
|
||||
: 0
|
||||
|
||||
type actSkillsFold = (x: Record<ActivatableSkillDependent>) => (s: number) => number
|
||||
|
||||
export const getAPSpentForSpells =
|
||||
(xmap: StaticData["spells"]) =>
|
||||
foldr (join (pipe (
|
||||
ActivatableSkillDependent.A.id,
|
||||
lookupF (xmap),
|
||||
maybe<actSkillsFold> (cnst (ident))
|
||||
(x => pipe (getAPForSpellOrChant (x), add))
|
||||
)))
|
||||
(0)
|
||||
|
||||
export const getAPSpentForLiturgicalChants =
|
||||
(xmap: StaticData["liturgicalChants"]) =>
|
||||
foldr (join (pipe (
|
||||
ActivatableSkillDependent.A.id,
|
||||
lookupF (xmap),
|
||||
maybe<actSkillsFold> (cnst (ident))
|
||||
(x => pipe (getAPForSpellOrChant (x), add))
|
||||
)))
|
||||
(0)
|
||||
|
||||
// Cantrips / Blessings
|
||||
|
||||
export const getAPSpentForCantrips = OrderedSet.size
|
||||
export const getAPSpentForBlessings = OrderedSet.size
|
||||
|
||||
// Advantages / Disadvantages / Special Abilities
|
||||
|
||||
type ActiveAdvantage = Record<ActiveActivatable<Advantage>>
|
||||
type ActiveDisadvantage = Record<ActiveActivatable<Disadvantage>>
|
||||
type ActiveSpecialAbility = Record<ActiveActivatable<SpecialAbility>>
|
||||
|
||||
const AAA_ = ActiveActivatableA_
|
||||
|
||||
/**
|
||||
* The return value is a triple:
|
||||
*
|
||||
* - The second value is the simple AP cost sum that needs to be subtracted from
|
||||
* the amount of total AP. Automatic advantage costs are exluded here.
|
||||
* - The first value is like the first, but it is the value that needs to be
|
||||
* compared with the AP max for advantages. Some entries do not count towards
|
||||
* this max. But automatic advantages are relevant here.
|
||||
*/
|
||||
export const getAPSpentForAdvantages =
|
||||
(wiki: StaticDataRecord) =>
|
||||
(xmap: HeroModel["advantages"]) =>
|
||||
(active: List<ActiveAdvantage>) =>
|
||||
pipe_ (
|
||||
active,
|
||||
List.foldr ((e: Record<ActiveActivatable<Advantage>>): ident<Pair<number, number>> =>
|
||||
AAA_.isAutomatic (e)
|
||||
? first (add (AAA_.finalCost (e)))
|
||||
: elem (AAA_.id (e)) (AdvantageIdsNoMaxInfl)
|
||||
? second (add (AAA_.finalCost (e)))
|
||||
: bimap (add (AAA_.finalCost (e)))
|
||||
(add (AAA_.finalCost (e))))
|
||||
(Pair (0, 0)),
|
||||
join <ident<number>, ident<Pair<number, number>>>
|
||||
(bimap)
|
||||
(add (getAdventurePointsSpentDifference (wiki) (xmap) (active)))
|
||||
)
|
||||
|
||||
export const getAPSpentForMagicalAdvantages =
|
||||
(wiki: StaticDataRecord) =>
|
||||
(xmap: HeroModel["advantages"]) =>
|
||||
pipe (
|
||||
filter<ActiveAdvantage> (pipe (
|
||||
ActiveActivatable.A.wikiEntry,
|
||||
Advantage.AL.gr,
|
||||
equals (2)
|
||||
)) as ident<List<ActiveAdvantage>>,
|
||||
getAPSpentForAdvantages (wiki) (xmap)
|
||||
)
|
||||
|
||||
export const getAPSpentForBlessedAdvantages =
|
||||
(wiki: StaticDataRecord) =>
|
||||
(xmap: HeroModel["advantages"]) =>
|
||||
pipe (
|
||||
filter<ActiveAdvantage> (pipe (
|
||||
ActiveActivatable.A.wikiEntry,
|
||||
Advantage.AL.gr,
|
||||
equals (3)
|
||||
)) as ident<List<ActiveAdvantage>>,
|
||||
getAPSpentForAdvantages (wiki) (xmap)
|
||||
)
|
||||
|
||||
/**
|
||||
* Returns (AP spent on disadvantages, actual AP lost)
|
||||
*/
|
||||
export const getAPSpentForDisadvantages =
|
||||
(wiki: StaticDataRecord) =>
|
||||
(xmap: HeroModel["disadvantages"]) =>
|
||||
(active: List<ActiveDisadvantage>): Pair<number, number> =>
|
||||
pipe_ (
|
||||
active,
|
||||
List.foldr ((e: Record<ActiveActivatable<Disadvantage>>): ident<Pair<number, number>> =>
|
||||
AAA_.isAutomatic (e)
|
||||
? first (add (AAA_.finalCost (e)))
|
||||
: bimap (add (AAA_.finalCost (e)))
|
||||
(add (AAA_.finalCost (e))))
|
||||
(Pair (0, 0)),
|
||||
join <ident<number>, ident<Pair<number, number>>>
|
||||
(bimap)
|
||||
(add (getAdventurePointsSpentDifference (wiki) (xmap) (active)))
|
||||
)
|
||||
|
||||
export const getAPSpentForMagicalDisadvantages =
|
||||
(wiki: StaticDataRecord) =>
|
||||
(xmap: HeroModel["disadvantages"]) =>
|
||||
pipe (
|
||||
filter<ActiveDisadvantage> (pipe (
|
||||
ActiveActivatable.A.wikiEntry,
|
||||
Disadvantage.AL.gr,
|
||||
equals (2)
|
||||
)) as ident<List<ActiveDisadvantage>>,
|
||||
getAPSpentForDisadvantages (wiki) (xmap)
|
||||
)
|
||||
|
||||
export const getAPSpentForBlessedDisadvantages =
|
||||
(wiki: StaticDataRecord) =>
|
||||
(xmap: HeroModel["disadvantages"]) =>
|
||||
pipe (
|
||||
filter<ActiveDisadvantage> (pipe (
|
||||
ActiveActivatable.A.wikiEntry,
|
||||
Disadvantage.AL.gr,
|
||||
equals (3)
|
||||
)) as ident<List<ActiveDisadvantage>>,
|
||||
getAPSpentForDisadvantages (wiki) (xmap)
|
||||
)
|
||||
|
||||
export const getAPSpentForSpecialAbilities =
|
||||
(wiki: StaticDataRecord) =>
|
||||
(xmap: HeroModel["specialAbilities"]) =>
|
||||
(active: List<ActiveSpecialAbility>) =>
|
||||
pipe_ (
|
||||
active,
|
||||
List.foldr<Record<ActiveActivatable<SpecialAbility>>, number>
|
||||
(pipe (ActiveActivatableA_.finalCost, add))
|
||||
(0),
|
||||
add (getAdventurePointsSpentDifference (wiki)
|
||||
(xmap)
|
||||
(active))
|
||||
)
|
||||
|
||||
export const getAPSpentForEnergies =
|
||||
(energies: Record<Energies>) => {
|
||||
const addedArcaneEnergyCost =
|
||||
pipe_ (
|
||||
energies,
|
||||
Energies.A.addedArcaneEnergyPoints,
|
||||
curryN3 (getAPForRange) (ImprovementCost.D) (0)
|
||||
)
|
||||
|
||||
const addedKarmaPointsCost =
|
||||
pipe_ (
|
||||
energies,
|
||||
Energies.A.addedKarmaPoints,
|
||||
curryN3 (getAPForRange) (ImprovementCost.D) (0)
|
||||
)
|
||||
|
||||
const addedLifePointsCost =
|
||||
pipe_ (
|
||||
energies,
|
||||
Energies.A.addedLifePoints,
|
||||
curryN3 (getAPForRange) (ImprovementCost.D) (0)
|
||||
)
|
||||
|
||||
const boughtBackArcaneEnergyCost =
|
||||
pipe_ (
|
||||
energies,
|
||||
Energies.A.permanentArcaneEnergyPoints,
|
||||
PermanentEnergyLossAndBoughtBack.A.redeemed,
|
||||
multiply (2)
|
||||
)
|
||||
|
||||
const boughtBackKarmaPointsCost =
|
||||
pipe_ (
|
||||
energies,
|
||||
Energies.A.permanentKarmaPoints,
|
||||
PermanentEnergyLossAndBoughtBack.A.redeemed,
|
||||
multiply (2)
|
||||
)
|
||||
|
||||
return sum (List (
|
||||
addedArcaneEnergyCost,
|
||||
addedKarmaPointsCost,
|
||||
addedLifePointsCost,
|
||||
boughtBackArcaneEnergyCost,
|
||||
boughtBackKarmaPointsCost
|
||||
))
|
||||
}
|
||||
|
||||
export const getAPSpentForRace =
|
||||
(wiki: StaticDataRecord) =>
|
||||
pipe (
|
||||
bindF (lookupF (SDA.races (wiki))),
|
||||
maybe (0) (Race.A.ap)
|
||||
)
|
||||
|
||||
/**
|
||||
* Pass wiki, profession id, optional profession variant id and the current
|
||||
* phase.
|
||||
*/
|
||||
export const getAPSpentForProfession =
|
||||
(wiki: StaticDataRecord) =>
|
||||
(mprofId: Maybe<string>) =>
|
||||
(mprofVarId: Maybe<string>) =>
|
||||
pipe (
|
||||
ensure (equals (1)),
|
||||
thenF (mprofId),
|
||||
bindF (lookupF (SDA.professions (wiki))),
|
||||
fmap (pipe (
|
||||
Profession.A.ap,
|
||||
Maybe.sum,
|
||||
add (pipe_ (
|
||||
mprofVarId,
|
||||
bindF (lookupF (SDA.professionVariants (wiki))),
|
||||
fmap (ProfessionVariant.A.ap),
|
||||
Maybe.sum
|
||||
))
|
||||
))
|
||||
)
|
||||
|
||||
export const getAPObject =
|
||||
(staticData: StaticDataRecord) =>
|
||||
(hero: HeroModelRecord) =>
|
||||
(automatic_advantages: List<string>) =>
|
||||
(matchingScriptAndLanguageRelated: MatchingScriptAndLanguageRelated) => {
|
||||
const total = HA.adventurePointsTotal (hero)
|
||||
|
||||
const spentOnAttributes = getAPSpentForAttributes (HA.attributes (hero))
|
||||
|
||||
const spentOnSkills = getAPSpentForSkills (SDA.skills (staticData))
|
||||
(HA.skills (hero))
|
||||
|
||||
const spentOnCombatTechniques =
|
||||
getAPSpentForCombatTechniques (SDA.combatTechniques (staticData))
|
||||
(HA.combatTechniques (hero))
|
||||
|
||||
const spentOnSpells = getAPSpentForSpells (SDA.spells (staticData))
|
||||
(HA.spells (hero))
|
||||
|
||||
const spentOnLiturgicalChants =
|
||||
getAPSpentForLiturgicalChants (SDA.liturgicalChants (staticData))
|
||||
(HA.liturgicalChants (hero))
|
||||
|
||||
const spentOnCantrips = getAPSpentForCantrips (HA.cantrips (hero))
|
||||
|
||||
const spentOnBlessings = getAPSpentForBlessings (HA.blessings (hero))
|
||||
|
||||
const spentOnEnergies = getAPSpentForEnergies (HA.energies (hero))
|
||||
|
||||
const spentOnRace = getAPSpentForRace (staticData) (HA.race (hero))
|
||||
|
||||
const spentOnProfession = getAPSpentForProfession (staticData)
|
||||
(HA.profession (hero))
|
||||
(HA.professionVariant (hero))
|
||||
(HA.phase (hero))
|
||||
|
||||
const spentOnSpecialAbilities =
|
||||
getAPSpentForSpecialAbilities (staticData)
|
||||
(HA.specialAbilities (hero))
|
||||
(getAllActiveByCategory (Category.SPECIAL_ABILITIES)
|
||||
(false)
|
||||
(automatic_advantages)
|
||||
(matchingScriptAndLanguageRelated)
|
||||
(staticData)
|
||||
(hero))
|
||||
|
||||
const spentOnAdvantages =
|
||||
getAPSpentForAdvantages (staticData)
|
||||
(HA.advantages (hero))
|
||||
(getAllActiveByCategory (Category.ADVANTAGES)
|
||||
(false)
|
||||
(automatic_advantages)
|
||||
(matchingScriptAndLanguageRelated)
|
||||
(staticData)
|
||||
(hero))
|
||||
|
||||
const spentOnMagicalAdvantages =
|
||||
getAPSpentForMagicalAdvantages (staticData)
|
||||
(HA.advantages (hero))
|
||||
(getAllActiveByCategory (Category.ADVANTAGES)
|
||||
(false)
|
||||
(automatic_advantages)
|
||||
(matchingScriptAndLanguageRelated)
|
||||
(staticData)
|
||||
(hero))
|
||||
|
||||
const spentOnBlessedAdvantages =
|
||||
getAPSpentForBlessedAdvantages (staticData)
|
||||
(HA.advantages (hero))
|
||||
(getAllActiveByCategory (Category.ADVANTAGES)
|
||||
(false)
|
||||
(automatic_advantages)
|
||||
(matchingScriptAndLanguageRelated)
|
||||
(staticData)
|
||||
(hero))
|
||||
|
||||
const spentOnDisadvantages =
|
||||
getAPSpentForDisadvantages (staticData)
|
||||
(HA.disadvantages (hero))
|
||||
(getAllActiveByCategory (Category.DISADVANTAGES)
|
||||
(false)
|
||||
(automatic_advantages)
|
||||
(matchingScriptAndLanguageRelated)
|
||||
(staticData)
|
||||
(hero))
|
||||
|
||||
const spentOnMagicalDisadvantages =
|
||||
getAPSpentForMagicalDisadvantages (staticData)
|
||||
(HA.disadvantages (hero))
|
||||
(getAllActiveByCategory (Category.DISADVANTAGES)
|
||||
(false)
|
||||
(automatic_advantages)
|
||||
(matchingScriptAndLanguageRelated)
|
||||
(staticData)
|
||||
(hero))
|
||||
|
||||
const spentOnBlessedDisadvantages =
|
||||
getAPSpentForBlessedDisadvantages (staticData)
|
||||
(HA.disadvantages (hero))
|
||||
(getAllActiveByCategory (Category.DISADVANTAGES)
|
||||
(false)
|
||||
(automatic_advantages)
|
||||
(matchingScriptAndLanguageRelated)
|
||||
(staticData)
|
||||
(hero))
|
||||
|
||||
const spent =
|
||||
spentOnAttributes
|
||||
+ spentOnSkills
|
||||
+ spentOnCombatTechniques
|
||||
+ spentOnSpells
|
||||
+ spentOnLiturgicalChants
|
||||
+ spentOnCantrips
|
||||
+ spentOnBlessings
|
||||
+ spentOnEnergies
|
||||
+ spentOnRace
|
||||
+ Maybe.sum (spentOnProfession)
|
||||
+ spentOnSpecialAbilities
|
||||
+ snd (spentOnAdvantages)
|
||||
+ snd (spentOnMagicalAdvantages)
|
||||
+ snd (spentOnBlessedAdvantages)
|
||||
+ snd (spentOnDisadvantages)
|
||||
+ snd (spentOnMagicalDisadvantages)
|
||||
+ snd (spentOnBlessedDisadvantages)
|
||||
|
||||
return AdventurePointsCategories ({
|
||||
total,
|
||||
spent,
|
||||
available: total - spent,
|
||||
spentOnAttributes,
|
||||
spentOnSkills,
|
||||
spentOnCombatTechniques,
|
||||
spentOnSpells,
|
||||
spentOnLiturgicalChants,
|
||||
spentOnCantrips,
|
||||
spentOnBlessings,
|
||||
spentOnEnergies,
|
||||
spentOnRace,
|
||||
spentOnProfession,
|
||||
spentOnSpecialAbilities,
|
||||
spentOnAdvantages: fst (spentOnAdvantages),
|
||||
spentOnMagicalAdvantages: fst (spentOnMagicalAdvantages),
|
||||
spentOnBlessedAdvantages: fst (spentOnBlessedAdvantages),
|
||||
spentOnDisadvantages: fst (spentOnDisadvantages),
|
||||
spentOnMagicalDisadvantages: fst (spentOnMagicalDisadvantages),
|
||||
spentOnBlessedDisadvantages: fst (spentOnBlessedDisadvantages),
|
||||
})
|
||||
}
|
||||
@@ -1,452 +0,0 @@
|
||||
import { bool_ } from "../../../Data/Bool"
|
||||
import { equals } from "../../../Data/Eq"
|
||||
import { ident } from "../../../Data/Function"
|
||||
import { fmap, fmapF } from "../../../Data/Functor"
|
||||
import { any, countWith, countWithByKeyMaybe, elemF, find, flength, foldl, foldr, isList, List, notElem, subscript, take } from "../../../Data/List"
|
||||
import { all, altF, bind, bindF, elem, ensure, fromJust, fromMaybe, guard, isJust, isNothing, Just, listToMaybe, Maybe, maybe, Nothing, or, sum, then } from "../../../Data/Maybe"
|
||||
import { add, gt, inc, lt, multiply, negate, subtractBy } from "../../../Data/Num"
|
||||
import { alter, empty, findWithDefault, lookup, OrderedMap } from "../../../Data/OrderedMap"
|
||||
import { fromDefault, Record } from "../../../Data/Record"
|
||||
import { fst, Pair, snd } from "../../../Data/Tuple"
|
||||
import { AdvantageIdsNoMaxInfl, DisadvantageId, SpecialAbilityId } from "../../Constants/Ids"
|
||||
import { ActivatableDependent } from "../../Models/ActiveEntries/ActivatableDependent"
|
||||
import { ActiveObject } from "../../Models/ActiveEntries/ActiveObject"
|
||||
import { HeroModel, HeroModelRecord } from "../../Models/Hero/HeroModel"
|
||||
import { ActiveActivatable, ActiveActivatableAL_, ActiveActivatableA_ } from "../../Models/View/ActiveActivatable"
|
||||
import { AdventurePointsCategories } from "../../Models/View/AdventurePointsCategories"
|
||||
import { Disadvantage } from "../../Models/Wiki/Disadvantage"
|
||||
import { Skill } from "../../Models/Wiki/Skill"
|
||||
import { StaticData, StaticDataRecord } from "../../Models/Wiki/WikiModel"
|
||||
import { Activatable } from "../../Models/Wiki/wikiTypeHelpers"
|
||||
import { getSelectOptionCost } from "../Activatable/selectionUtils"
|
||||
import { getMagicalTraditionsHeroEntries } from "../Activatable/traditionUtils"
|
||||
import { getAPForActivatation } from "../ImprovementCost"
|
||||
import { pipe, pipe_ } from "../pipe"
|
||||
import { misNumberM, misStringM } from "../typeCheckUtils"
|
||||
import { compareMaxLevel, compareSubMaxLevel, getActiveWithNoCustomCost } from "./activatableCostUtils"
|
||||
|
||||
const SDA = StaticData.A
|
||||
const AP = AdventurePointsCategories.AL
|
||||
const AAA = ActiveActivatable.A
|
||||
const AOA = ActiveObject.A
|
||||
|
||||
/**
|
||||
* Checks if there are enough AP available. If there are, returns `Nothing`.
|
||||
* Otherwise returns a `Just` of the missing AP.
|
||||
* @param negativeApValid If the character's AP left can be a negative value
|
||||
* (during character creation) or not.
|
||||
* @param availableAP The AP currently available.
|
||||
* @param cost The AP value you want to check.
|
||||
*/
|
||||
export const getMissingAP =
|
||||
(negativeApValid: boolean) => (cost: number) => (availableAP: number): Maybe<number> => {
|
||||
if (cost > 0 && !negativeApValid) {
|
||||
return cost <= availableAP ? Nothing : Just (cost - availableAP)
|
||||
}
|
||||
|
||||
return Nothing
|
||||
}
|
||||
|
||||
const semiTraditionIds =
|
||||
List<string> (
|
||||
SpecialAbilityId.TraditionArcaneBard,
|
||||
SpecialAbilityId.TraditionArcaneDancer,
|
||||
SpecialAbilityId.TraditionIntuitiveMage,
|
||||
SpecialAbilityId.TraditionSavant,
|
||||
SpecialAbilityId.TraditionZauberalchimisten,
|
||||
SpecialAbilityId.TraditionAnimisten,
|
||||
)
|
||||
|
||||
/**
|
||||
* Returns the maximum AP value you can spend on magical/blessed
|
||||
* advantages/disadvantages.
|
||||
* @param state The list of dependent instances.
|
||||
* @param index The index in the AP array. `0` equals General, `1` equals
|
||||
* Magical and `2` equals Blessed.
|
||||
*/
|
||||
export const getDisAdvantagesSubtypeMax =
|
||||
(isMagical: boolean) => (state: HeroModelRecord): number => {
|
||||
if (isMagical) {
|
||||
const mtradition =
|
||||
listToMaybe (getMagicalTraditionsHeroEntries (HeroModel.AL.specialAbilities (state)))
|
||||
|
||||
const misSemiTradition =
|
||||
fmap (pipe (ActivatableDependent.AL.id, elemF (semiTraditionIds)))
|
||||
(mtradition)
|
||||
|
||||
if (or (misSemiTradition)) {
|
||||
return 25
|
||||
}
|
||||
}
|
||||
|
||||
return 50
|
||||
}
|
||||
|
||||
export interface MissingAPForDisAdvantage {
|
||||
"@@name": "MissingAPForDisAdvantage"
|
||||
totalMissing: Maybe<number>
|
||||
mainMissing: Maybe<number>
|
||||
subMissing: Maybe<number>
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
||||
export const MissingAPForDisAdvantage =
|
||||
fromDefault ("MissingAPForDisAdvantage")
|
||||
<MissingAPForDisAdvantage> ({
|
||||
totalMissing: Nothing,
|
||||
mainMissing: Nothing,
|
||||
subMissing: Nothing,
|
||||
})
|
||||
|
||||
const getDisAdvantageSubtypeAPSpent =
|
||||
(isBlessedOrMagical: Pair<boolean, boolean>) =>
|
||||
(isDisadvantage: boolean) =>
|
||||
(ap: Record<AdventurePointsCategories>): Maybe<number> => {
|
||||
if (isDisadvantage) {
|
||||
if (snd (isBlessedOrMagical)) {
|
||||
return Just (AP.spentOnMagicalDisadvantages (ap))
|
||||
}
|
||||
|
||||
if (fst (isBlessedOrMagical)) {
|
||||
return Just (AP.spentOnBlessedDisadvantages (ap))
|
||||
}
|
||||
}
|
||||
|
||||
if (snd (isBlessedOrMagical)) {
|
||||
return Just (AP.spentOnMagicalAdvantages (ap))
|
||||
}
|
||||
|
||||
if (fst (isBlessedOrMagical)) {
|
||||
return Just (AP.spentOnBlessedAdvantages (ap))
|
||||
}
|
||||
|
||||
return Nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if there are enough AP available and if the restrictions for
|
||||
* advantages/disadvantages will be met.
|
||||
* @param cost The AP value you want to check.
|
||||
* @param ap The current AP state.
|
||||
* @param state The list of dependent instances.
|
||||
* @param isMagicalOrBlessed If the the advantage/disadvantage is magical or
|
||||
* blessed.
|
||||
* @param isDisadvantage If the entry is a disadvantage.
|
||||
* @param isInCharacterCreation If the character's AP left can be a negative
|
||||
* value (during character creation) or not.
|
||||
*/
|
||||
export const getMissingAPForDisAdvantage =
|
||||
(isInCharacterCreation: boolean) =>
|
||||
(isBlessedOrMagical: Pair<boolean, boolean>) =>
|
||||
(isDisadvantage: boolean) =>
|
||||
(hero: HeroModelRecord) =>
|
||||
(ap: Record<AdventurePointsCategories>) =>
|
||||
(id: string) =>
|
||||
(cost: number): Record<MissingAPForDisAdvantage> => {
|
||||
const currentAPSpent =
|
||||
isDisadvantage
|
||||
? AP.spentOnDisadvantages (ap)
|
||||
: AP.spentOnAdvantages (ap)
|
||||
|
||||
const subCurrentAPSpent = getDisAdvantageSubtypeAPSpent (isBlessedOrMagical)
|
||||
(isDisadvantage)
|
||||
(ap)
|
||||
|
||||
const smallMax = getDisAdvantagesSubtypeMax (snd (isBlessedOrMagical)) (hero)
|
||||
|
||||
// a disadvantage has negative cost, but the sum to check is always positive
|
||||
// (to be able to use one function for both advantages and disadvantages)
|
||||
const normalizedCost = isDisadvantage ? cost * -1 : cost
|
||||
|
||||
// checks if there are enough AP below the max for the subtype
|
||||
// (magical/blessed)
|
||||
const subMissing =
|
||||
isInCharacterCreation && notElem (id) (AdvantageIdsNoMaxInfl)
|
||||
|
||||
// (current + spent) - max > 0 => invalid
|
||||
? bind (subCurrentAPSpent)
|
||||
(pipe (
|
||||
add (normalizedCost),
|
||||
subtractBy (smallMax),
|
||||
ensure (gt (0))
|
||||
))
|
||||
: Nothing
|
||||
|
||||
// Checks if there are enough AP below the max for advantages/disadvantages
|
||||
const mainMissing =
|
||||
isInCharacterCreation && notElem (id) (AdvantageIdsNoMaxInfl)
|
||||
|
||||
// (current + spent) - max > 0 => invalid
|
||||
? ensure (gt (0)) (currentAPSpent + normalizedCost - 80)
|
||||
: Nothing
|
||||
|
||||
// Checks if there are enough AP available in total
|
||||
const totalMissing = getMissingAP (isInCharacterCreation)
|
||||
(cost)
|
||||
(AP.available (ap))
|
||||
|
||||
return MissingAPForDisAdvantage ({ totalMissing, mainMissing, subMissing })
|
||||
}
|
||||
|
||||
const getPrinciplesObligationsDiff =
|
||||
(id: string) =>
|
||||
(staticData: StaticDataRecord) =>
|
||||
(hero_slice: OrderedMap<string, Record<ActivatableDependent>>) =>
|
||||
(entries: List<Record<ActiveActivatable>>): number => {
|
||||
if (any (pipe (ActiveActivatableAL_.id, equals (id))) (entries)) {
|
||||
return pipe_ (
|
||||
hero_slice,
|
||||
lookup (id),
|
||||
bindF (entry => {
|
||||
const current_active = ActivatableDependent.A.active (entry)
|
||||
|
||||
const current_max_level = foldl (compareMaxLevel)
|
||||
(0)
|
||||
(current_active)
|
||||
|
||||
const current_second_max_level = foldl (compareSubMaxLevel (current_max_level))
|
||||
(0)
|
||||
(current_active)
|
||||
|
||||
const at_max_level =
|
||||
countWith (pipe (ActiveObject.A.tier, elem (current_max_level)))
|
||||
(current_active)
|
||||
|
||||
const mbase_cost =
|
||||
pipe_ (
|
||||
staticData,
|
||||
SDA.disadvantages,
|
||||
lookup (id),
|
||||
bindF (Disadvantage.A.cost),
|
||||
misNumberM
|
||||
)
|
||||
|
||||
return at_max_level > 1
|
||||
? fmapF (mbase_cost) (base => current_max_level * -base)
|
||||
: fmapF (mbase_cost) (base => current_second_max_level * -base)
|
||||
}),
|
||||
sum
|
||||
)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
const getPropertyOrAspectKnowledgeDiff =
|
||||
(id: string) =>
|
||||
pipe (
|
||||
find (pipe (ActiveActivatableA_.id, equals (id))),
|
||||
fmap ((entry: Record<ActiveActivatable>) => {
|
||||
const current_active_length =
|
||||
pipe_ (entry, ActiveActivatable.AL.heroEntry, ActivatableDependent.A.active, flength)
|
||||
|
||||
const mcost = pipe_ (entry, ActiveActivatable.AL.wikiEntry, Disadvantage.AL.cost)
|
||||
|
||||
if (isJust (mcost)) {
|
||||
const cost = fromJust (mcost)
|
||||
|
||||
if (isList (cost)) {
|
||||
const actualAPSum = pipe_ (cost, take (current_active_length), List.sum)
|
||||
|
||||
// Sum of *displayed* AP values for entries
|
||||
const current_cost_sum =
|
||||
sum (subscript (cost) (current_active_length - 1)) * current_active_length
|
||||
|
||||
return actualAPSum - current_cost_sum
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}),
|
||||
sum
|
||||
)
|
||||
|
||||
/**
|
||||
* `getSinglePersFlawDiff :: Int -> Int -> ActiveActivatable -> SID -> Int -> Int`
|
||||
*
|
||||
* `getSinglePersFlawDiff sid paid_entries entry current_sid current_entries`
|
||||
*
|
||||
* @param sid SID the diff is for.
|
||||
* @param paid_entries Amount of active entries of the same SID that you get AP
|
||||
* for.
|
||||
* @param entry An entry of Personality Flaw.
|
||||
* @param current_sid The current SID to check.
|
||||
* @param current_entries The current amount of active entries of the current
|
||||
* SID.
|
||||
*/
|
||||
const getSinglePersFlawDiff =
|
||||
(sid: number) =>
|
||||
(paid_entries: number) =>
|
||||
(entry: Record<ActiveActivatable>) =>
|
||||
(current_sid: number | string) =>
|
||||
(current_entries: number): number =>
|
||||
current_sid === sid && current_entries > paid_entries
|
||||
? maybe (0)
|
||||
(pipe (multiply (paid_entries), negate))
|
||||
(getSelectOptionCost (AAA.wikiEntry (entry) as Activatable)
|
||||
(Just (sid)))
|
||||
: 0
|
||||
|
||||
const getPersonalityFlawsDiff =
|
||||
(entries: List<Record<ActiveActivatable>>): number =>
|
||||
pipe_ (
|
||||
entries,
|
||||
|
||||
// Find any Personality Flaw entry, as all of them have the same list of
|
||||
// active objects
|
||||
find (pipe (ActiveActivatableA_.id, equals<string> (DisadvantageId.PersonalityFlaw))),
|
||||
fmap ((entry: Record<ActiveActivatable>) => pipe_ (
|
||||
entry,
|
||||
ActiveActivatableA_.active,
|
||||
countWithByKeyMaybe (e => then (guard (isNothing (AOA.cost (e))))
|
||||
(AOA.sid (e))),
|
||||
OrderedMap.foldrWithKey ((sid: string | number) => (val: number) =>
|
||||
pipe_ (
|
||||
List (
|
||||
getSinglePersFlawDiff (7) (1) (entry) (sid) (val),
|
||||
getSinglePersFlawDiff (8) (2) (entry) (sid) (val)
|
||||
),
|
||||
List.sum,
|
||||
add
|
||||
))
|
||||
(0)
|
||||
)),
|
||||
|
||||
// If no Personality Flaw was found, there's no diff.
|
||||
Maybe.sum
|
||||
)
|
||||
|
||||
const getBadHabitsDiff =
|
||||
(staticData: StaticDataRecord) =>
|
||||
(hero_slice: OrderedMap<string, Record<ActivatableDependent>>) =>
|
||||
(entries: List<Record<ActiveActivatable>>): number =>
|
||||
any (pipe (ActiveActivatableAL_.id, equals<string> (DisadvantageId.BadHabit))) (entries)
|
||||
? sum (pipe_ (
|
||||
hero_slice,
|
||||
lookup<string> (DisadvantageId.PersonalityFlaw),
|
||||
fmap (pipe (
|
||||
|
||||
// get current active
|
||||
ActivatableDependent.A.active,
|
||||
getActiveWithNoCustomCost,
|
||||
flength,
|
||||
gt (3),
|
||||
bool_ (() => 0)
|
||||
(() => pipe_ (
|
||||
staticData,
|
||||
SDA.disadvantages,
|
||||
lookup<string> (DisadvantageId.BadHabit),
|
||||
bindF (Disadvantage.A.cost),
|
||||
misNumberM,
|
||||
fmap (multiply (-3)),
|
||||
sum
|
||||
))
|
||||
))
|
||||
))
|
||||
: 0
|
||||
|
||||
const getSkillSpecializationsDiff =
|
||||
(staticData: StaticDataRecord) =>
|
||||
(hero_slice: OrderedMap<string, Record<ActivatableDependent>>) =>
|
||||
(entries: List<Record<ActiveActivatable>>): number => {
|
||||
if (any (pipe (ActiveActivatableAL_.id, equals<string> (SpecialAbilityId.SkillSpecialization)))
|
||||
(entries)) {
|
||||
return sum (pipe_ (
|
||||
hero_slice,
|
||||
lookup<string> (SpecialAbilityId.SkillSpecialization),
|
||||
fmap ((entry: Record<ActivatableDependent>) => {
|
||||
const current_active = ActivatableDependent.A.active (entry)
|
||||
|
||||
// Count how many specializations are for the same skill
|
||||
const sameSkill =
|
||||
countWithByKeyMaybe (pipe (ActiveObject.A.sid, misStringM))
|
||||
(current_active)
|
||||
|
||||
// Return the accumulated value, otherwise 0.
|
||||
const getFlatSkillDone = findWithDefault (0)
|
||||
|
||||
// Calculates the diff for a single skill specialization
|
||||
const getSingleDiff =
|
||||
(accMap: OrderedMap<string, number>) =>
|
||||
(sid: string) =>
|
||||
(counter: number) =>
|
||||
(skill: Record<Skill>) =>
|
||||
getAPForActivatation (Skill.A.ic (skill))
|
||||
* (getFlatSkillDone (sid) (accMap) + 1 - counter)
|
||||
|
||||
type TrackingPair = Pair<number, OrderedMap<string, number>>
|
||||
|
||||
// Iterates through the counter and sums up all cost differences for
|
||||
// each specialization.
|
||||
//
|
||||
// It keeps track of how many specializations have been already
|
||||
// taken into account.
|
||||
const skillDone =
|
||||
foldr (pipe (
|
||||
ActiveObject.A.sid,
|
||||
misStringM,
|
||||
bindF (current_sid =>
|
||||
fmapF (lookup (current_sid) (sameSkill))
|
||||
(count => (p: TrackingPair) => {
|
||||
const m = snd (p)
|
||||
|
||||
// Check if the value in the map is either
|
||||
// Nothing or a Just of a lower number than
|
||||
// the complete counter
|
||||
// => which means there are still actions to
|
||||
// be done
|
||||
if (all (lt (count)) (lookup (current_sid) (m))) {
|
||||
const mskill =
|
||||
pipe_ (staticData, SDA.skills, lookup (current_sid))
|
||||
|
||||
return Pair (
|
||||
fst (p) + sum (fmap (getSingleDiff (m)
|
||||
(current_sid)
|
||||
(count))
|
||||
(mskill)),
|
||||
alter (pipe (altF (Just (0)), fmap (inc)))
|
||||
(current_sid)
|
||||
(m)
|
||||
)
|
||||
}
|
||||
|
||||
return p
|
||||
})),
|
||||
fromMaybe<ident<TrackingPair>> (ident)
|
||||
))
|
||||
(Pair (0, empty))
|
||||
(current_active)
|
||||
|
||||
return fst (skillDone)
|
||||
})
|
||||
))
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
const getPropertyKnowledgeDiff =
|
||||
getPropertyOrAspectKnowledgeDiff (SpecialAbilityId.PropertyKnowledge)
|
||||
|
||||
const getAspectKnowledgeDiff =
|
||||
getPropertyOrAspectKnowledgeDiff (SpecialAbilityId.AspectKnowledge)
|
||||
|
||||
/**
|
||||
* The returned number modifies the current AP spent.
|
||||
*/
|
||||
export const getAdventurePointsSpentDifference =
|
||||
(staticData: StaticDataRecord) =>
|
||||
(hero_slice: OrderedMap<string, Record<ActivatableDependent>>) =>
|
||||
(entries: List<Record<ActiveActivatable>>): number => {
|
||||
const adventurePointsSpentDifferences = List (
|
||||
getPrinciplesObligationsDiff (DisadvantageId.Principles) (staticData) (hero_slice) (entries),
|
||||
getPrinciplesObligationsDiff (DisadvantageId.Obligations) (staticData) (hero_slice) (entries),
|
||||
getPersonalityFlawsDiff (entries),
|
||||
getBadHabitsDiff (staticData) (hero_slice) (entries),
|
||||
getSkillSpecializationsDiff (staticData) (hero_slice) (entries),
|
||||
getPropertyKnowledgeDiff (entries),
|
||||
getAspectKnowledgeDiff (entries)
|
||||
)
|
||||
|
||||
return List.sum (adventurePointsSpentDifferences)
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
import * as React from "react"
|
||||
import { flip, thrush } from "../../../Data/Function"
|
||||
import { fmapF } from "../../../Data/Functor"
|
||||
import { fnull, List, map, toArray } from "../../../Data/List"
|
||||
import { all, fromJust, Maybe, normalize, or } from "../../../Data/Maybe"
|
||||
import { OrderedMap } from "../../../Data/OrderedMap"
|
||||
import { Record } from "../../../Data/Record"
|
||||
import { ActivatableAddListItemContainer } from "../../Containers/InactiveActivatableContainer"
|
||||
import { ActivatableActivationOptions } from "../../Models/Actions/ActivatableActivationOptions"
|
||||
import { ActiveObjectWithId } from "../../Models/ActiveEntries/ActiveObjectWithId"
|
||||
import { EntryRating } from "../../Models/Hero/heroTypeHelpers"
|
||||
import { ActivatableNameCost } from "../../Models/View/ActivatableNameCost"
|
||||
import { ActiveActivatable, ActiveActivatableA_ } from "../../Models/View/ActiveActivatable"
|
||||
import { InactiveActivatable } from "../../Models/View/InactiveActivatable"
|
||||
import { StaticDataRecord } from "../../Models/Wiki/WikiModel"
|
||||
import { getFullName } from "../../Utilities/Activatable/activatableNameUtils"
|
||||
import { pipe_ } from "../../Utilities/pipe"
|
||||
import { isInactiveRated } from "../../Utilities/ratingUtils"
|
||||
import { ListView } from "../Universal/List"
|
||||
import { ListItem } from "../Universal/ListItem"
|
||||
import { ListItemName } from "../Universal/ListItemName"
|
||||
import { ListPlaceholder } from "../Universal/ListPlaceholder"
|
||||
import { Scroll } from "../Universal/Scroll"
|
||||
|
||||
export interface ActivatableAddListProps {
|
||||
hideGroup?: boolean
|
||||
inactiveList: Maybe<List<Record<ActiveActivatable> | Record<InactiveActivatable>>>
|
||||
staticData: StaticDataRecord
|
||||
rating?: Maybe<OrderedMap<string, EntryRating>>
|
||||
showRating?: boolean
|
||||
selectedForInfo: Maybe<string>
|
||||
addToList (args: Record<ActivatableActivationOptions>): void
|
||||
selectForInfo (id: string): void
|
||||
}
|
||||
|
||||
const AAA = ActiveActivatable.A
|
||||
const AAA_ = ActiveActivatableA_
|
||||
const IAA = InactiveActivatable.A
|
||||
const ANCA = ActivatableNameCost.A
|
||||
const AOWIA = ActiveObjectWithId.A
|
||||
|
||||
export function ActivatableAddList (props: ActivatableAddListProps) {
|
||||
const {
|
||||
hideGroup,
|
||||
inactiveList: minactives,
|
||||
staticData,
|
||||
rating,
|
||||
showRating: mshow_rating,
|
||||
selectedForInfo,
|
||||
addToList,
|
||||
selectForInfo,
|
||||
} = props
|
||||
|
||||
if (all (fnull) (minactives)) {
|
||||
return <ListPlaceholder staticData={staticData} noResults type="inactiveSpecialAbilities" />
|
||||
}
|
||||
|
||||
const normalizedRating = normalize (rating)
|
||||
|
||||
const isRatedWithRating = fmapF (Maybe (mshow_rating)) (flip (isInactiveRated) (normalizedRating))
|
||||
|
||||
return (
|
||||
<Scroll>
|
||||
<ListView>
|
||||
{pipe_ (
|
||||
minactives,
|
||||
fromJust,
|
||||
map (item => {
|
||||
if (ActiveActivatable.is (item)) {
|
||||
const name = getFullName (item)
|
||||
const id = AAA_.id (item)
|
||||
const index = pipe_ (item, AAA.nameAndCost, ANCA.active, AOWIA.index)
|
||||
|
||||
return (
|
||||
<ListItem key={`${id}_${index}`} disabled>
|
||||
<ListItemName name={name} />
|
||||
</ListItem>
|
||||
)
|
||||
}
|
||||
|
||||
const isRatedForItem = fmapF (isRatedWithRating) (thrush (item))
|
||||
|
||||
return (
|
||||
<ActivatableAddListItemContainer
|
||||
key={IAA.id (item)}
|
||||
hideGroup={hideGroup}
|
||||
item={item}
|
||||
isImportant={or (fmapF (isRatedForItem) (thrush (EntryRating.Essential)))}
|
||||
isTypical={or (fmapF (isRatedForItem) (thrush (EntryRating.Common)))}
|
||||
isUntypical={or (fmapF (isRatedForItem) (thrush (EntryRating.Uncommon)))}
|
||||
selectedForInfo={selectedForInfo}
|
||||
addToList={addToList}
|
||||
selectForInfo={selectForInfo}
|
||||
/>
|
||||
)
|
||||
}),
|
||||
toArray
|
||||
)}
|
||||
</ListView>
|
||||
</Scroll>
|
||||
)
|
||||
}
|
||||
@@ -1,341 +0,0 @@
|
||||
import * as React from "react"
|
||||
import { List, notNullStr } from "../../../Data/List"
|
||||
import { any, ensure, fromMaybe, guardReplace, isJust, isNothing, Just, Maybe, maybe, maybeToUndefined, Nothing, orN } from "../../../Data/Maybe"
|
||||
import { lookup } from "../../../Data/OrderedMap"
|
||||
import { Record } from "../../../Data/Record"
|
||||
import { fst, snd } from "../../../Data/Tuple"
|
||||
import { AdvantageId, DisadvantageId, SpecialAbilityId } from "../../Constants/Ids"
|
||||
import { ActivatableActivationOptions } from "../../Models/Actions/ActivatableActivationOptions"
|
||||
import { HeroModel } from "../../Models/Hero/HeroModel"
|
||||
import { NumIdName } from "../../Models/NumIdName"
|
||||
import { InactiveActivatable } from "../../Models/View/InactiveActivatable"
|
||||
import { Advantage } from "../../Models/Wiki/Advantage"
|
||||
import { Disadvantage } from "../../Models/Wiki/Disadvantage"
|
||||
import { SpecialAbility } from "../../Models/Wiki/SpecialAbility"
|
||||
import { StaticData, StaticDataRecord } from "../../Models/Wiki/WikiModel"
|
||||
import { getIdSpecificAffectedAndDispatchProps, getInactiveActivatableControlElements, InactiveActivatableControlElements, insertFinalCurrentCost, PropertiesAffectedByState } from "../../Utilities/Activatable/activatableInactiveViewUtils"
|
||||
import { classListMaybe } from "../../Utilities/CSS"
|
||||
import { translate, translateP } from "../../Utilities/I18n"
|
||||
import { pipe_ } from "../../Utilities/pipe"
|
||||
import { renderMaybeWith } from "../../Utilities/ReactUtils"
|
||||
import { BasicInputDialog } from "../Universal/BasicInputDialog"
|
||||
import { IconButton } from "../Universal/IconButton"
|
||||
import { ListItem } from "../Universal/ListItem"
|
||||
import { ListItemButtons } from "../Universal/ListItemButtons"
|
||||
import { ListItemGroup } from "../Universal/ListItemGroup"
|
||||
import { ListItemLeft } from "../Universal/ListItemLeft"
|
||||
import { ListItemName } from "../Universal/ListItemName"
|
||||
import { ListItemSeparator } from "../Universal/ListItemSeparator"
|
||||
import { ListItemValues } from "../Universal/ListItemValues"
|
||||
|
||||
export interface ActivatableAddListItemOwnProps {
|
||||
item: Record<InactiveActivatable>
|
||||
isImportant?: boolean
|
||||
isTypical?: boolean
|
||||
isUntypical?: boolean
|
||||
hideGroup?: boolean
|
||||
selectedForInfo: Maybe<string>
|
||||
addToList (args: Record<ActivatableActivationOptions>): void
|
||||
selectForInfo (id: string): void
|
||||
}
|
||||
|
||||
export interface ActivatableAddListItemStateProps {
|
||||
skills: Maybe<HeroModel["skills"]>
|
||||
staticData: StaticDataRecord
|
||||
isEditingAllowed: boolean
|
||||
}
|
||||
|
||||
export interface ActivatableAddListItemDispatchProps { }
|
||||
|
||||
export type ActivatableAddListItemProps =
|
||||
ActivatableAddListItemStateProps
|
||||
& ActivatableAddListItemDispatchProps
|
||||
& ActivatableAddListItemOwnProps
|
||||
|
||||
export interface ActivatableAddListItemSelectedOptions {
|
||||
selected: Maybe<string | number>
|
||||
selected2: Maybe<string | number>
|
||||
selected3: Maybe<string | number>
|
||||
selectedTier: Maybe<number>
|
||||
input: Maybe<string>
|
||||
customCost: Maybe<string>
|
||||
}
|
||||
|
||||
const IAA = InactiveActivatable.A
|
||||
const IACEA = InactiveActivatableControlElements.A
|
||||
const PABSA = PropertiesAffectedByState.A
|
||||
|
||||
export const ActivatableAddListItem: React.FC<ActivatableAddListItemProps> = props => {
|
||||
const {
|
||||
item,
|
||||
isImportant,
|
||||
isTypical,
|
||||
isUntypical,
|
||||
hideGroup,
|
||||
selectForInfo,
|
||||
selectedForInfo,
|
||||
staticData,
|
||||
addToList,
|
||||
isEditingAllowed,
|
||||
} = props
|
||||
|
||||
const id = IAA.id (item)
|
||||
|
||||
const [ mselected, setSelected ] = React.useState<Maybe<string | number>> (Nothing)
|
||||
const [ mselected2, setSelected2 ] = React.useState<Maybe<string | number>> (Nothing)
|
||||
const [ mselected3, setSelected3 ] = React.useState<Maybe<string | number>> (Nothing)
|
||||
const [ mselected_level, setSelectedLevel ] = React.useState<Maybe<number>> (Nothing)
|
||||
const [ minput, setInput ] = React.useState<Maybe<string>> (Nothing)
|
||||
const [ mcustom_cost, setCustomCost ] = React.useState<Maybe<string>> (Nothing)
|
||||
const [ mcustom_cost_preview, setCustomCostPreview ] = React.useState<Maybe<string>> (Nothing)
|
||||
const [ showCustomCostDialog, setShowCustomCostDialog ] = React.useState<boolean> (false)
|
||||
|
||||
const isCustomCostAvailable = React.useMemo (
|
||||
() => {
|
||||
const staticEntry = IAA.wikiEntry (item)
|
||||
|
||||
return Advantage.is (staticEntry)
|
||||
|| Disadvantage.is (staticEntry)
|
||||
|| id === SpecialAbilityId.CustomSpecialAbility
|
||||
},
|
||||
[ item, id ]
|
||||
)
|
||||
|
||||
const handleSelect =
|
||||
React.useCallback (
|
||||
(nextSelected: Maybe<string | number>) => {
|
||||
setSelected (nextSelected)
|
||||
setSelected2 (Nothing)
|
||||
setSelected3 (Nothing)
|
||||
},
|
||||
[ setSelected, setSelected2, setSelected3 ]
|
||||
)
|
||||
|
||||
const handleLevel =
|
||||
React.useCallback (
|
||||
(mlevel: Maybe<number>) => {
|
||||
if (isJust (mlevel)) {
|
||||
if (DisadvantageId.Principles === id || DisadvantageId.Obligations === id) {
|
||||
setSelectedLevel (mlevel)
|
||||
setSelected (Nothing)
|
||||
}
|
||||
else {
|
||||
setSelectedLevel (mlevel)
|
||||
}
|
||||
}
|
||||
},
|
||||
[ setSelectedLevel, setSelected, id ]
|
||||
)
|
||||
|
||||
const handleInput =
|
||||
React.useCallback (
|
||||
(input: string) => setInput (ensure (notNullStr) (input)),
|
||||
[ setInput ]
|
||||
)
|
||||
|
||||
const handleShowCustomCostDialog =
|
||||
React.useCallback (
|
||||
() => {
|
||||
if (isCustomCostAvailable) {
|
||||
setShowCustomCostDialog (true)
|
||||
setCustomCostPreview (mcustom_cost)
|
||||
}
|
||||
},
|
||||
[ setShowCustomCostDialog, setCustomCostPreview, isCustomCostAvailable, mcustom_cost ]
|
||||
)
|
||||
|
||||
const handleCloseCustomCostDialog =
|
||||
React.useCallback (
|
||||
() => setShowCustomCostDialog (false),
|
||||
[ setShowCustomCostDialog ]
|
||||
)
|
||||
|
||||
const handleSetCustomCost =
|
||||
React.useCallback (
|
||||
() => setCustomCost (mcustom_cost_preview),
|
||||
[ setCustomCost, mcustom_cost_preview ]
|
||||
)
|
||||
|
||||
const handleSetCustomCostPreview =
|
||||
React.useCallback (
|
||||
(customCostStr: string) => pipe_ (customCostStr, ensure (notNullStr), setCustomCostPreview),
|
||||
[ setCustomCostPreview ]
|
||||
)
|
||||
|
||||
const handleDeleteCustomCost =
|
||||
React.useCallback (
|
||||
() => setCustomCost (Nothing),
|
||||
[ setCustomCost ]
|
||||
)
|
||||
|
||||
const selectElementDisabled =
|
||||
([
|
||||
AdvantageId.MagicalAttunement,
|
||||
DisadvantageId.AfraidOf,
|
||||
DisadvantageId.MagicalRestriction,
|
||||
DisadvantageId.Principles,
|
||||
DisadvantageId.BadHabit,
|
||||
DisadvantageId.Stigma,
|
||||
DisadvantageId.Obligations,
|
||||
] as string[]) .includes (id)
|
||||
&& any (notNullStr) (minput)
|
||||
|
||||
const selectedOptions: ActivatableAddListItemSelectedOptions = {
|
||||
selected: mselected,
|
||||
selected2: mselected2,
|
||||
selected3: mselected3,
|
||||
selectedTier: mselected_level,
|
||||
input: minput,
|
||||
customCost: mcustom_cost,
|
||||
}
|
||||
|
||||
const propsAndActivationArgs =
|
||||
getIdSpecificAffectedAndDispatchProps ({
|
||||
handleInput,
|
||||
handleSelect,
|
||||
selectElementDisabled,
|
||||
})
|
||||
(staticData)
|
||||
(item)
|
||||
(selectedOptions)
|
||||
|
||||
const finalProps = insertFinalCurrentCost (item)
|
||||
(selectedOptions)
|
||||
(propsAndActivationArgs)
|
||||
|
||||
const controlElements =
|
||||
getInactiveActivatableControlElements (staticData)
|
||||
(isEditingAllowed)
|
||||
({
|
||||
handleInput,
|
||||
handleSelect,
|
||||
handleSecondSelect: setSelected2,
|
||||
handleThirdSelect: setSelected3,
|
||||
handleLevel,
|
||||
selectElementDisabled,
|
||||
})
|
||||
(item)
|
||||
(selectedOptions)
|
||||
(finalProps)
|
||||
|
||||
const mlevelElementBefore = IACEA.levelElementBefore (controlElements)
|
||||
const mlevelElementAfter = IACEA.levelElementAfter (controlElements)
|
||||
const mselectElement = IACEA.selectElement (controlElements)
|
||||
const msecondSelectElement = IACEA.secondSelectElement (controlElements)
|
||||
const mthirdSelectElement = IACEA.thirdSelectElement (controlElements)
|
||||
const minputElement = IACEA.inputElement (controlElements)
|
||||
const mdisabled = IACEA.disabled (controlElements)
|
||||
|
||||
const handleAddToList =
|
||||
React.useCallback (
|
||||
() => {
|
||||
const args = fst (finalProps)
|
||||
|
||||
addToList (args)
|
||||
|
||||
if (isJust (mselected) || isJust (mselected_level) || isJust (minput)) {
|
||||
setInput (Nothing)
|
||||
setSelected (Nothing)
|
||||
setSelected2 (Nothing)
|
||||
setSelected3 (Nothing)
|
||||
setSelectedLevel (Nothing)
|
||||
setCustomCost (Nothing)
|
||||
}
|
||||
},
|
||||
[
|
||||
mselected,
|
||||
mselected_level,
|
||||
minput,
|
||||
setInput,
|
||||
setSelected,
|
||||
setSelected2,
|
||||
setSelected3,
|
||||
setSelectedLevel,
|
||||
setCustomCost,
|
||||
finalProps,
|
||||
addToList,
|
||||
]
|
||||
)
|
||||
|
||||
const handleSelectForInfo =
|
||||
React.useCallback (
|
||||
() => selectForInfo (id),
|
||||
[ selectForInfo, id ]
|
||||
)
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
important={isImportant}
|
||||
recommended={isTypical}
|
||||
unrecommended={isUntypical}
|
||||
active={Maybe.elem (IAA.id (item)) (selectedForInfo)}
|
||||
>
|
||||
<ListItemLeft>
|
||||
<ListItemName name={IAA.name (item)} onClick={handleSelectForInfo} />
|
||||
{fromMaybe (null as React.ReactNode) (mlevelElementBefore)}
|
||||
{fromMaybe (null as React.ReactNode) (mselectElement)}
|
||||
{fromMaybe (null as React.ReactNode) (msecondSelectElement)}
|
||||
{fromMaybe (null as React.ReactNode) (mthirdSelectElement)}
|
||||
{fromMaybe (null as React.ReactNode) (minputElement)}
|
||||
{fromMaybe (null as React.ReactNode) (mlevelElementAfter)}
|
||||
</ListItemLeft>
|
||||
<ListItemSeparator />
|
||||
{orN (hideGroup)
|
||||
? null
|
||||
: (
|
||||
<ListItemGroup
|
||||
text={pipe_ (
|
||||
staticData,
|
||||
StaticData.A.specialAbilityGroups,
|
||||
lookup (pipe_ (item, IAA.wikiEntry, SpecialAbility.AL.gr)),
|
||||
maybe ("") (NumIdName.A.name)
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<ListItemValues>
|
||||
<div
|
||||
className={classListMaybe (List (
|
||||
Just ("cost"),
|
||||
guardReplace (isCustomCostAvailable) ("value-btn"),
|
||||
guardReplace (isJust (mcustom_cost)) ("custom-cost")
|
||||
))}
|
||||
onClick={handleShowCustomCostDialog}
|
||||
>
|
||||
{renderMaybeWith ((x: number | string) => IAA.isAutomatic (item) ? `(${x})` : x)
|
||||
(PABSA.currentCost (snd (finalProps)))}
|
||||
</div>
|
||||
<BasicInputDialog
|
||||
id="custom-cost-dialog"
|
||||
isOpen={showCustomCostDialog}
|
||||
title={translate (staticData) ("advantagesdisadvantages.dialogs.customcost.title")}
|
||||
description={
|
||||
translateP (staticData)
|
||||
("advantagesdisadvantages.dialogs.customcost.for")
|
||||
(List (IAA.name (item)))
|
||||
}
|
||||
value={maybeToUndefined (mcustom_cost_preview)}
|
||||
acceptLabel={translate (staticData) ("general.dialogs.donebtn")}
|
||||
rejectLabel={translate (staticData) ("general.dialogs.deletebtn")}
|
||||
rejectDisabled={isNothing (mcustom_cost)}
|
||||
onClose={handleCloseCustomCostDialog}
|
||||
onAccept={handleSetCustomCost}
|
||||
onReject={handleDeleteCustomCost}
|
||||
onChange={handleSetCustomCostPreview}
|
||||
/>
|
||||
</ListItemValues>
|
||||
<ListItemButtons>
|
||||
<IconButton
|
||||
icon=""
|
||||
disabled={mdisabled}
|
||||
onClick={handleAddToList}
|
||||
flat
|
||||
/>
|
||||
<IconButton
|
||||
icon=""
|
||||
onClick={handleSelectForInfo}
|
||||
flat
|
||||
/>
|
||||
</ListItemButtons>
|
||||
</ListItem>
|
||||
)
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
import * as React from "react"
|
||||
import { flip, thrush } from "../../../Data/Function"
|
||||
import { fmapF } from "../../../Data/Functor"
|
||||
import { fnull, List, map, toArray } from "../../../Data/List"
|
||||
import { all, fromJust, Maybe, normalize, or } from "../../../Data/Maybe"
|
||||
import { OrderedMap } from "../../../Data/OrderedMap"
|
||||
import { Record } from "../../../Data/Record"
|
||||
import { ActivatableDeactivationOptions } from "../../Models/Actions/ActivatableDeactivationOptions"
|
||||
import { EntryRating } from "../../Models/Hero/heroTypeHelpers"
|
||||
import { ActiveActivatable, ActiveActivatableA_ } from "../../Models/View/ActiveActivatable"
|
||||
import { StaticDataRecord } from "../../Models/Wiki/WikiModel"
|
||||
import { pipe_ } from "../../Utilities/pipe"
|
||||
import { isActiveRated } from "../../Utilities/ratingUtils"
|
||||
import { ListView } from "../Universal/List"
|
||||
import { ListPlaceholder } from "../Universal/ListPlaceholder"
|
||||
import { Scroll } from "../Universal/Scroll"
|
||||
import { ActivatableRemoveListItem } from "./ActivatableRemoveListItem"
|
||||
|
||||
export interface ActivatableRemoveListProps {
|
||||
filterText: string
|
||||
hideGroup?: boolean
|
||||
list: Maybe<List<Record<ActiveActivatable>>>
|
||||
staticData: StaticDataRecord
|
||||
isRemovingEnabled: boolean
|
||||
rating?: Maybe<OrderedMap<string, EntryRating>>
|
||||
showRating?: boolean
|
||||
selectedForInfo: Maybe<string>
|
||||
setLevel (id: string, index: number, level: number): void
|
||||
removeFromList (args: Record<ActivatableDeactivationOptions>): void
|
||||
selectForInfo (id: string): void
|
||||
}
|
||||
|
||||
const AAA_ = ActiveActivatableA_
|
||||
|
||||
export function ActivatableRemoveList (props: ActivatableRemoveListProps) {
|
||||
const {
|
||||
filterText,
|
||||
list: mactives,
|
||||
staticData,
|
||||
rating,
|
||||
showRating: mshow_rating,
|
||||
isRemovingEnabled,
|
||||
selectedForInfo,
|
||||
setLevel,
|
||||
removeFromList,
|
||||
selectForInfo,
|
||||
hideGroup,
|
||||
} = props
|
||||
|
||||
if (all (fnull) (mactives)) {
|
||||
return (
|
||||
<ListPlaceholder
|
||||
staticData={staticData}
|
||||
noResults={filterText.length > 0}
|
||||
type="specialAbilities"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const normalizedRating = normalize (rating)
|
||||
|
||||
const isRatedWithRating = fmapF (Maybe (mshow_rating)) (flip (isActiveRated) (normalizedRating))
|
||||
|
||||
return (
|
||||
<Scroll>
|
||||
<ListView>
|
||||
{pipe_ (
|
||||
mactives,
|
||||
fromJust,
|
||||
map (item => {
|
||||
const isRatedForItem = fmapF (isRatedWithRating) (thrush (item))
|
||||
|
||||
return (
|
||||
<ActivatableRemoveListItem
|
||||
key={`${AAA_.id (item)}_${AAA_.index (item)}`}
|
||||
staticData={staticData}
|
||||
item={item}
|
||||
isImportant={or (fmapF (isRatedForItem) (thrush (EntryRating.Essential)))}
|
||||
isTypical={or (fmapF (isRatedForItem) (thrush (EntryRating.Common)))}
|
||||
isUntypical={or (fmapF (isRatedForItem) (thrush (EntryRating.Uncommon)))}
|
||||
isRemovingEnabled={isRemovingEnabled}
|
||||
selectedForInfo={selectedForInfo}
|
||||
setLevel={setLevel}
|
||||
removeFromList={removeFromList}
|
||||
selectForInfo={selectForInfo}
|
||||
hideGroup={hideGroup}
|
||||
/>
|
||||
)
|
||||
}),
|
||||
toArray
|
||||
)}
|
||||
</ListView>
|
||||
</Scroll>
|
||||
)
|
||||
}
|
||||
@@ -1,229 +0,0 @@
|
||||
import * as React from "react"
|
||||
import { isString } from "util"
|
||||
import { equals, notEquals } from "../../../Data/Eq"
|
||||
import { onF } from "../../../Data/Function"
|
||||
import { fmap, mapReplace } from "../../../Data/Functor"
|
||||
import { cons, flength, List } from "../../../Data/List"
|
||||
import { any, bindF, ensure, fromJust, fromMaybe, guard, INTERNAL_shallowEquals, isJust, Just, liftM2, listToMaybe, Maybe, maybe, orN } from "../../../Data/Maybe"
|
||||
import { max, min } from "../../../Data/Num"
|
||||
import { lookup } from "../../../Data/OrderedMap"
|
||||
import { Record } from "../../../Data/Record"
|
||||
import { SpecialAbilityId } from "../../Constants/Ids"
|
||||
import { ActivatableDeactivationOptions } from "../../Models/Actions/ActivatableDeactivationOptions"
|
||||
import { NumIdName } from "../../Models/NumIdName"
|
||||
import { ActiveActivatable, ActiveActivatableA_ } from "../../Models/View/ActiveActivatable"
|
||||
import { DropdownOption } from "../../Models/View/DropdownOption"
|
||||
import { SpecialAbility } from "../../Models/Wiki/SpecialAbility"
|
||||
import { StaticData, StaticDataRecord } from "../../Models/Wiki/WikiModel"
|
||||
import { classListMaybe } from "../../Utilities/CSS"
|
||||
import { translate } from "../../Utilities/I18n"
|
||||
import { getLevelElementsWithMin } from "../../Utilities/levelUtils"
|
||||
import { pipe, pipe_ } from "../../Utilities/pipe"
|
||||
import { renderMaybe } from "../../Utilities/ReactUtils"
|
||||
import { Dropdown } from "../Universal/Dropdown"
|
||||
import { IconButton } from "../Universal/IconButton"
|
||||
import { ListItem } from "../Universal/ListItem"
|
||||
import { ListItemButtons } from "../Universal/ListItemButtons"
|
||||
import { ListItemGroup } from "../Universal/ListItemGroup"
|
||||
import { ListItemName } from "../Universal/ListItemName"
|
||||
import { ListItemSelections } from "../Universal/ListItemSelections"
|
||||
import { ListItemSeparator } from "../Universal/ListItemSeparator"
|
||||
import { ListItemValues } from "../Universal/ListItemValues"
|
||||
|
||||
export interface ActivatableRemoveListItemProps {
|
||||
item: Record<ActiveActivatable>
|
||||
staticData: StaticDataRecord
|
||||
isRemovingEnabled: boolean
|
||||
hideGroup?: boolean
|
||||
isImportant?: boolean
|
||||
isTypical?: boolean
|
||||
isUntypical?: boolean
|
||||
selectedForInfo: Maybe<string>
|
||||
setLevel (id: string, index: number, level: number): void
|
||||
removeFromList (args: Record<ActivatableDeactivationOptions>): void
|
||||
selectForInfo (id: string): void
|
||||
}
|
||||
|
||||
const AAA = ActiveActivatable.A
|
||||
const AAA_ = ActiveActivatableA_
|
||||
const DOA = DropdownOption.A
|
||||
|
||||
const ActivatableRemoveListItem: React.FC<ActivatableRemoveListItemProps> = props => {
|
||||
const {
|
||||
isRemovingEnabled,
|
||||
hideGroup,
|
||||
item,
|
||||
isImportant,
|
||||
isTypical,
|
||||
isUntypical,
|
||||
staticData,
|
||||
selectForInfo,
|
||||
selectedForInfo,
|
||||
removeFromList,
|
||||
setLevel,
|
||||
} = props
|
||||
|
||||
const id = AAA_.id (item)
|
||||
|
||||
const handleSelectTier = React.useCallback (
|
||||
(mlevel: Maybe<number>) => {
|
||||
if (isJust (mlevel)) {
|
||||
const level = fromJust (mlevel)
|
||||
|
||||
setLevel (id, AAA_.index (item), level)
|
||||
}
|
||||
},
|
||||
[ item, setLevel, id ]
|
||||
)
|
||||
|
||||
const handleRemove = React.useCallback (
|
||||
() => removeFromList (pipe_ (
|
||||
item,
|
||||
AAA_.finalCost,
|
||||
cost => ActivatableDeactivationOptions ({
|
||||
id,
|
||||
index: AAA_.index (item),
|
||||
cost,
|
||||
})
|
||||
)),
|
||||
[ item, removeFromList, id ]
|
||||
)
|
||||
|
||||
const handleSelectForInfo =
|
||||
React.useCallback (
|
||||
() => selectForInfo (id),
|
||||
[ selectForInfo, id ]
|
||||
)
|
||||
|
||||
const mlevel_element =
|
||||
liftM2<number, number, JSX.Element | string>
|
||||
(levels => level => {
|
||||
const curr_min =
|
||||
isRemovingEnabled
|
||||
? pipe_ (item, AAA_.minLevel, maybe (1) (max (1)))
|
||||
: level
|
||||
|
||||
const curr_max = pipe_ (item, AAA_.maxLevel, maybe (levels) (min (levels)))
|
||||
|
||||
const levelOptions = getLevelElementsWithMin (curr_min) (curr_max)
|
||||
|
||||
const levelOptionsWithMotherTongue =
|
||||
id === SpecialAbilityId.Language
|
||||
&& (level === 4 || isRemovingEnabled)
|
||||
? cons (levelOptions)
|
||||
(DropdownOption ({
|
||||
id: Just (4),
|
||||
name: translate (staticData) ("specialabilities.nativetonguelevel"),
|
||||
}))
|
||||
: levelOptions
|
||||
|
||||
return flength (levelOptionsWithMotherTongue) > 1
|
||||
? (
|
||||
<Dropdown
|
||||
className="tiers"
|
||||
value={Just (level)}
|
||||
onChange={handleSelectTier}
|
||||
options={levelOptionsWithMotherTongue}
|
||||
/>
|
||||
)
|
||||
: pipe_ (
|
||||
levelOptions,
|
||||
listToMaybe,
|
||||
fmap ((o: Record<DropdownOption<number>>) => ` ${DOA.name (o)}`),
|
||||
renderMaybe
|
||||
)
|
||||
})
|
||||
(AAA_.levels (item))
|
||||
(AAA_.level (item))
|
||||
|
||||
const baseName = AAA_.baseName (item)
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
important={isImportant}
|
||||
recommended={isTypical}
|
||||
unrecommended={isUntypical}
|
||||
active={Maybe.elem (id) (selectedForInfo)}
|
||||
>
|
||||
<ListItemName
|
||||
name={
|
||||
any ((x: string | JSX.Element) => !isString (x)) (mlevel_element)
|
||||
? maybe (baseName)
|
||||
((add_str: string) =>
|
||||
`${baseName} (${add_str})`)
|
||||
(AAA_.addName (item))
|
||||
: AAA_.name (item)
|
||||
}
|
||||
onClick={handleSelectForInfo}
|
||||
/>
|
||||
<ListItemSelections>
|
||||
{pipe_ (
|
||||
mlevel_element,
|
||||
bindF (ensure ((l): l is JSX.Element => !isString (l))),
|
||||
fromMaybe (<></>)
|
||||
)}
|
||||
</ListItemSelections>
|
||||
<ListItemSeparator />
|
||||
{orN (hideGroup)
|
||||
? null
|
||||
: (
|
||||
<ListItemGroup
|
||||
text={pipe_ (
|
||||
staticData,
|
||||
StaticData.A.specialAbilityGroups,
|
||||
lookup (pipe_ (item, AAA.wikiEntry, SpecialAbility.AL.gr)),
|
||||
maybe ("") (NumIdName.A.name)
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<ListItemValues>
|
||||
<div
|
||||
className={classListMaybe (List (
|
||||
Just ("cost"),
|
||||
pipe_ (
|
||||
item,
|
||||
AAA_.customCost,
|
||||
bindF (pipe (notEquals (0), guard)),
|
||||
mapReplace ("custom-cost")
|
||||
)
|
||||
))}
|
||||
>
|
||||
{AAA_.isAutomatic (item) ? `(${AAA_.finalCost (item)})` : AAA_.finalCost (item)}
|
||||
</div>
|
||||
</ListItemValues>
|
||||
<ListItemButtons>
|
||||
{isRemovingEnabled
|
||||
? (
|
||||
<IconButton
|
||||
icon=""
|
||||
onClick={handleRemove}
|
||||
disabled={AAA_.disabled (item)}
|
||||
flat
|
||||
/>
|
||||
)
|
||||
: null}
|
||||
<IconButton icon="" onClick={handleSelectForInfo} flat />
|
||||
</ListItemButtons>
|
||||
</ListItem>
|
||||
)
|
||||
}
|
||||
|
||||
const ActivatableRemoveListItemM =
|
||||
React.memo (
|
||||
ActivatableRemoveListItem,
|
||||
(prevProps, nextProps) => {
|
||||
const curr_item = prevProps.item
|
||||
const next_item = nextProps.item
|
||||
|
||||
return onF (AAA_.level) (equals) (curr_item) (next_item)
|
||||
&& onF (AAA_.finalCost) (equals) (curr_item) (next_item)
|
||||
&& prevProps.isRemovingEnabled === nextProps.isRemovingEnabled
|
||||
&& onF (AAA_.minLevel) (equals) (curr_item) (next_item)
|
||||
&& onF (AAA_.maxLevel) (equals) (curr_item) (next_item)
|
||||
&& onF (AAA_.name) (equals) (curr_item) (next_item)
|
||||
&& onF (AAA_.disabled) (equals) (curr_item) (next_item)
|
||||
&& INTERNAL_shallowEquals (prevProps.selectedForInfo) (nextProps.selectedForInfo)
|
||||
}
|
||||
)
|
||||
|
||||
export { ActivatableRemoveListItemM as ActivatableRemoveListItem }
|
||||
-121
@@ -1,121 +0,0 @@
|
||||
/**
|
||||
* @module Data.Ix
|
||||
*
|
||||
* The `Ix` class is used to map a contiguous subrange of values in type onto
|
||||
* integers.
|
||||
*
|
||||
* @author Lukas Obermann
|
||||
*/
|
||||
|
||||
import { cons, List } from "./List"
|
||||
import { inc } from "./Num"
|
||||
import { show } from "./Show"
|
||||
import { fst, Pair, snd } from "./Tuple"
|
||||
|
||||
|
||||
// MODULE HELPER FUNCTIONS
|
||||
|
||||
const buildRangeList =
|
||||
(u: number) => (x: number) => (xs: List<number>): List<number> =>
|
||||
x > u ? xs : cons (buildRangeList (u) (inc (x)) (xs)) (x)
|
||||
|
||||
|
||||
/**
|
||||
* `range :: Int a => (a, a) -> [a]`
|
||||
*
|
||||
* The list of values in the subrange defined by a bounding pair.
|
||||
*
|
||||
* The first argument `(l,u)` is a pair specifying the lower and upper bounds of
|
||||
* a contiguous subrange of values.
|
||||
*/
|
||||
export const range =
|
||||
(b: Pair<number, number>): List<number> =>
|
||||
buildRangeList (snd (b)) (fst (b)) (List.empty)
|
||||
|
||||
|
||||
/**
|
||||
* `rangeN :: Int a => (a, a) -> [a]`
|
||||
*
|
||||
* The list of values in the subrange defined by a bounding pair.
|
||||
*
|
||||
* The first argument `(l,u)` is a pair specifying the lower and upper bounds of
|
||||
* a contiguous subrange of values.
|
||||
*
|
||||
* Native version of `range`.
|
||||
*/
|
||||
export const rangeN =
|
||||
(l: number, u: number): List<number> =>
|
||||
buildRangeList (u) (l) (List.empty)
|
||||
|
||||
|
||||
/**
|
||||
* `inRange :: Int a => (a, a) -> a -> Bool`
|
||||
*
|
||||
* Returns `True` the given subscript lies in the range defined the bounding
|
||||
* pair.
|
||||
*
|
||||
* The first argument `(l,u)` is a pair specifying the lower and upper bounds of
|
||||
* a contiguous subrange of values.
|
||||
*/
|
||||
export const inRange =
|
||||
(b: Pair<number, number>) => (x: number): boolean =>
|
||||
x >= fst (b) && x <= snd (b)
|
||||
|
||||
|
||||
/**
|
||||
* `index :: Int a => (a, a) -> a -> Int`
|
||||
*
|
||||
* The position of a subscript in the subrange.
|
||||
*
|
||||
* The first argument `(l,u)` is a pair specifying the lower and upper bounds of
|
||||
* a contiguous subrange of values.
|
||||
*/
|
||||
export const index =
|
||||
(b: Pair<number, number>) => (x: number): number => {
|
||||
if (inRange (b) (x)) {
|
||||
return x - fst (b)
|
||||
}
|
||||
|
||||
throw new RangeError (
|
||||
`Ix.index: Index (${show (x)}) out of range (${show (b)}`
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* `inRangeN :: Int a => (a, a) -> a -> Bool`
|
||||
*
|
||||
* Returns `True` the given subscript lies in the range defined the bounding
|
||||
* pair.
|
||||
*
|
||||
* The first argument `(l,u)` is a pair specifying the lower and upper bounds of
|
||||
* a contiguous subrange of values.
|
||||
*
|
||||
* Native version of `inRange`.
|
||||
*/
|
||||
export const inRangeN =
|
||||
(l: number, u: number) => (x: number): boolean =>
|
||||
x >= l && x <= u
|
||||
|
||||
|
||||
/**
|
||||
* `rangeSize :: Int a => (a, a) -> Int`
|
||||
*
|
||||
* The size of the subrange defined by a bounding pair.
|
||||
*
|
||||
* The first argument `(l,u)` is a pair specifying the lower and upper bounds of
|
||||
* a contiguous subrange of values.
|
||||
*/
|
||||
export const rangeSize =
|
||||
(b: Pair<number, number>): number =>
|
||||
fst (b) <= snd (b) ? snd (b) - fst (b) + 1 : 0
|
||||
|
||||
|
||||
// NAMESPACED FUNCTIONS
|
||||
|
||||
export const Ix = {
|
||||
range,
|
||||
index,
|
||||
inRange,
|
||||
rangeSize,
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
import { index, inRange, inRangeN, range, rangeN, rangeSize } from "../Ix"
|
||||
import { List } from "../List"
|
||||
import { Pair } from "../Tuple"
|
||||
|
||||
test ("range", () => {
|
||||
expect (range (Pair (1, 5))) .toEqual (List (1, 2, 3, 4, 5))
|
||||
expect (range (Pair (1, 1))) .toEqual (List (1))
|
||||
expect (range (Pair (1, -2))) .toEqual (List ())
|
||||
})
|
||||
|
||||
test ("rangeN", () => {
|
||||
expect (rangeN (1, 5)) .toEqual (List (1, 2, 3, 4, 5))
|
||||
expect (rangeN (1, 1)) .toEqual (List (1))
|
||||
expect (rangeN (1, -2)) .toEqual (List ())
|
||||
})
|
||||
|
||||
test ("index", () => {
|
||||
expect (index (Pair (1, 5)) (3)) .toEqual (2)
|
||||
expect (index (Pair (1, 5)) (1)) .toEqual (0)
|
||||
expect (() => index (Pair (1, 5)) (-1)) .toThrow ()
|
||||
})
|
||||
|
||||
test ("inRange", () => {
|
||||
expect (inRange (Pair (1, 5)) (3)) .toEqual (true)
|
||||
expect (inRange (Pair (1, 5)) (1)) .toEqual (true)
|
||||
expect (inRange (Pair (1, 5)) (-1)) .toEqual (false)
|
||||
})
|
||||
|
||||
test ("inRangeN", () => {
|
||||
expect (inRangeN (1, 5) (3)) .toEqual (true)
|
||||
expect (inRangeN (1, 5) (1)) .toEqual (true)
|
||||
expect (inRangeN (1, 5) (-1)) .toEqual (false)
|
||||
})
|
||||
|
||||
test ("rangeSize", () => {
|
||||
expect (rangeSize (Pair (1, 5))) .toEqual (5)
|
||||
expect (rangeSize (Pair (1, 1))) .toEqual (1)
|
||||
expect (rangeSize (Pair (1, -2))) .toEqual (0)
|
||||
})
|
||||
@@ -0,0 +1,223 @@
|
||||
import { ActionCreatorWithPayload } from "@reduxjs/toolkit"
|
||||
import { Advantage } from "optolith-database-schema/types/Advantage"
|
||||
import { Disadvantage } from "optolith-database-schema/types/Disadvantage"
|
||||
import { ActivatableIdentifier } from "optolith-database-schema/types/_IdentifierGroup"
|
||||
import { useCallback } from "react"
|
||||
import {
|
||||
DisplayedActiveActivatable,
|
||||
getCostDifferenceOnRemove,
|
||||
getCostOfInstance,
|
||||
} from "../../shared/domain/activatable/activatableActive.ts"
|
||||
import { ActivatableInstance } from "../../shared/domain/activatable/activatableEntry.ts"
|
||||
import { equalsIdentifier } from "../../shared/domain/identifier.ts"
|
||||
import { useTranslate } from "../../shared/hooks/translate.ts"
|
||||
import { removeIndex } from "../../shared/utils/array.ts"
|
||||
import { isOk } from "../../shared/utils/result.ts"
|
||||
import { selectGetSelectOptionsById } from "../selectors/activatableSelectors.ts"
|
||||
import { showAlert } from "../slices/alertsSlice.ts"
|
||||
import { selectMagicalAndBlessedAdvantagesAndDisadvantagesCache } from "../slices/databaseSlice.ts"
|
||||
import {
|
||||
AvailabilityCheckResult,
|
||||
RejectionReason,
|
||||
getAlertForReason,
|
||||
getSpecialAdventurePointsKind,
|
||||
useAreEnoughAdventurePointsAvailableToBuy,
|
||||
useAreEnoughAdventurePointsAvailableToBuyAnAdvantageOrDisadvantage,
|
||||
} from "./areEnoughAdventurePointsAvailableToBuy.ts"
|
||||
import { useAppDispatch, useAppSelector } from "./redux.ts"
|
||||
|
||||
/**
|
||||
* Returns a set of callbacks for interaction with inactive activatable entries.
|
||||
*/
|
||||
export const useInactiveActivatableActions = (
|
||||
createAddAction: ActionCreatorWithPayload<{ id: number; instance: ActivatableInstance }>,
|
||||
areEnoughAdventurePointsAvailableToBuy: (
|
||||
id: number,
|
||||
cost: number,
|
||||
) => AvailabilityCheckResult<RejectionReason>,
|
||||
) => {
|
||||
const dispatch = useAppDispatch()
|
||||
const translate = useTranslate()
|
||||
|
||||
const handleAdd = useCallback(
|
||||
(params: { id: number; instance: ActivatableInstance }, cost: number) => {
|
||||
const enoughAP = areEnoughAdventurePointsAvailableToBuy(params.id, cost)
|
||||
|
||||
if (isOk(enoughAP)) {
|
||||
dispatch(createAddAction(params))
|
||||
} else {
|
||||
dispatch(showAlert(getAlertForReason(enoughAP.error, translate)))
|
||||
}
|
||||
},
|
||||
[areEnoughAdventurePointsAvailableToBuy, createAddAction, dispatch, translate],
|
||||
)
|
||||
|
||||
return {
|
||||
handleAdd,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a set of callbacks for interaction with inactive special ability
|
||||
* entries.
|
||||
*/
|
||||
export const useInactiveSpecialAbilityActions = (
|
||||
createAddAction: ActionCreatorWithPayload<{ id: number; instance: ActivatableInstance }>,
|
||||
) => {
|
||||
const areEnoughAdventurePointsAvailableToBuy = useAreEnoughAdventurePointsAvailableToBuy()
|
||||
|
||||
return useInactiveActivatableActions(createAddAction, (_id, cost) =>
|
||||
areEnoughAdventurePointsAvailableToBuy(cost),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a set of callbacks for interaction with inactive advantage and
|
||||
* disadvantage entries.
|
||||
*/
|
||||
export const useInactiveAdvantageAndDisadvantageActions = (
|
||||
kind: "advantage" | "disadvantage",
|
||||
createAddAction: ActionCreatorWithPayload<{ id: number; instance: ActivatableInstance }>,
|
||||
) => {
|
||||
const areEnoughAdventurePointsAvailableToBuyAnAdvantageOrDisadvantage =
|
||||
useAreEnoughAdventurePointsAvailableToBuyAnAdvantageOrDisadvantage()
|
||||
|
||||
const magicalAndBlessedAdvantagesAndDisadvantagesCache = useAppSelector(
|
||||
selectMagicalAndBlessedAdvantagesAndDisadvantagesCache,
|
||||
)
|
||||
|
||||
return useInactiveActivatableActions(createAddAction, (id, cost) =>
|
||||
areEnoughAdventurePointsAvailableToBuyAnAdvantageOrDisadvantage(
|
||||
cost,
|
||||
getSpecialAdventurePointsKind(magicalAndBlessedAdvantagesAndDisadvantagesCache, kind, id),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
const useActiveActivatableActions = (
|
||||
createRemoveAction: ActionCreatorWithPayload<{ id: number; index: number }>,
|
||||
createChangeLevelAction: ActionCreatorWithPayload<{ id: number; index: number; level: number }>,
|
||||
createIdObject: (id: number) => ActivatableIdentifier,
|
||||
areEnoughAdventurePointsAvailableToBuy: (
|
||||
id: number,
|
||||
cost: number,
|
||||
) => AvailabilityCheckResult<RejectionReason>,
|
||||
) => {
|
||||
const dispatch = useAppDispatch()
|
||||
const translate = useTranslate()
|
||||
|
||||
const handleRemove = useCallback(
|
||||
(
|
||||
params: { id: number; index: number },
|
||||
activeEntry: DisplayedActiveActivatable<Advantage | Disadvantage>,
|
||||
) => {
|
||||
const previousLevelCostDifference = getCostDifferenceOnRemove(activeEntry.cost)
|
||||
const enoughAP = areEnoughAdventurePointsAvailableToBuy(
|
||||
params.id,
|
||||
previousLevelCostDifference,
|
||||
)
|
||||
|
||||
if (isOk(enoughAP)) {
|
||||
dispatch(createRemoveAction(params))
|
||||
return true
|
||||
} else {
|
||||
dispatch(showAlert(getAlertForReason(enoughAP.error, translate)))
|
||||
return false
|
||||
}
|
||||
},
|
||||
[areEnoughAdventurePointsAvailableToBuy, createRemoveAction, dispatch, translate],
|
||||
)
|
||||
|
||||
const getSelectOptionsById = useAppSelector(selectGetSelectOptionsById)
|
||||
|
||||
const handleChangeLevel = useCallback(
|
||||
(
|
||||
params: { id: number; index: number; level: number },
|
||||
activeEntry: DisplayedActiveActivatable<Advantage | Disadvantage>,
|
||||
): boolean => {
|
||||
const idObject = createIdObject(params.id)
|
||||
const previousLevelCostDifference = getCostDifferenceOnRemove(activeEntry.cost)
|
||||
const nextLevelCost = getCostOfInstance(
|
||||
idObject,
|
||||
activeEntry.static.ap_value,
|
||||
removeIndex(activeEntry.dynamic.instances, activeEntry.instanceIndex),
|
||||
activeEntry.dynamic.instances[activeEntry.instanceIndex]!,
|
||||
optionId => getSelectOptionsById(idObject)?.find(opt => equalsIdentifier(opt.id, optionId)),
|
||||
true,
|
||||
)
|
||||
|
||||
const enoughAP = areEnoughAdventurePointsAvailableToBuy(
|
||||
params.id,
|
||||
(nextLevelCost ?? 0) - previousLevelCostDifference,
|
||||
)
|
||||
|
||||
if (isOk(enoughAP)) {
|
||||
dispatch(createChangeLevelAction(params))
|
||||
return true
|
||||
} else {
|
||||
dispatch(showAlert(getAlertForReason(enoughAP.error, translate)))
|
||||
return false
|
||||
}
|
||||
},
|
||||
[
|
||||
areEnoughAdventurePointsAvailableToBuy,
|
||||
createChangeLevelAction,
|
||||
createIdObject,
|
||||
dispatch,
|
||||
getSelectOptionsById,
|
||||
translate,
|
||||
],
|
||||
)
|
||||
|
||||
return {
|
||||
handleRemove,
|
||||
handleChangeLevel,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a set of callbacks for interaction with active activatable entries.
|
||||
*/
|
||||
export const useActiveSpecialAbilityActions = (
|
||||
createRemoveAction: ActionCreatorWithPayload<{ id: number; index: number }>,
|
||||
createChangeLevelAction: ActionCreatorWithPayload<{ id: number; index: number; level: number }>,
|
||||
createIdObject: (id: number) => ActivatableIdentifier,
|
||||
) => {
|
||||
const areEnoughAdventurePointsAvailableToBuy = useAreEnoughAdventurePointsAvailableToBuy()
|
||||
|
||||
return useActiveActivatableActions(
|
||||
createRemoveAction,
|
||||
createChangeLevelAction,
|
||||
createIdObject,
|
||||
(_id, cost) => areEnoughAdventurePointsAvailableToBuy(cost),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a set of callbacks for interaction with inactive advantage and
|
||||
* disadvantage entries.
|
||||
*/
|
||||
export const useActiveAdvantageAndDisadvantageActions = (
|
||||
kind: "advantage" | "disadvantage",
|
||||
createRemoveAction: ActionCreatorWithPayload<{ id: number; index: number }>,
|
||||
createChangeLevelAction: ActionCreatorWithPayload<{ id: number; index: number; level: number }>,
|
||||
createIdObject: (id: number) => ActivatableIdentifier,
|
||||
) => {
|
||||
const areEnoughAdventurePointsAvailableToBuyAnAdvantageOrDisadvantage =
|
||||
useAreEnoughAdventurePointsAvailableToBuyAnAdvantageOrDisadvantage()
|
||||
|
||||
const magicalAndBlessedAdvantagesAndDisadvantagesCache = useAppSelector(
|
||||
selectMagicalAndBlessedAdvantagesAndDisadvantagesCache,
|
||||
)
|
||||
|
||||
return useActiveActivatableActions(
|
||||
createRemoveAction,
|
||||
createChangeLevelAction,
|
||||
createIdObject,
|
||||
(id, cost) =>
|
||||
areEnoughAdventurePointsAvailableToBuyAnAdvantageOrDisadvantage(
|
||||
cost,
|
||||
getSpecialAdventurePointsKind(magicalAndBlessedAdvantagesAndDisadvantagesCache, kind, id),
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,374 @@
|
||||
import { CacheMap } from "optolith-database-schema/config/cache"
|
||||
import { useCallback } from "react"
|
||||
import {
|
||||
maximumAdventurePointsForAdventagesAndDisadvantages,
|
||||
maximumAdventurePointsForBlessedAdventagesAndDisadvantages,
|
||||
} from "../../shared/domain/activatable/advantagesDisadvantages.ts"
|
||||
import {
|
||||
AdventurePointsCache,
|
||||
getTotalAdventurePointsFromCache,
|
||||
} from "../../shared/domain/adventurePoints/cache.ts"
|
||||
import { Result, combine, error, ok } from "../../shared/utils/result.ts"
|
||||
import { Translate } from "../../shared/utils/translate.ts"
|
||||
import { assertExhaustive } from "../../shared/utils/typeSafety.ts"
|
||||
import {
|
||||
selectAdventurePointsAvailable,
|
||||
selectAdventurePointsSpentOnAdvantages,
|
||||
selectAdventurePointsSpentOnBlessedAdvantages,
|
||||
selectAdventurePointsSpentOnBlessedDisadvantages,
|
||||
selectAdventurePointsSpentOnDisadvantages,
|
||||
selectAdventurePointsSpentOnMagicalAdvantages,
|
||||
selectAdventurePointsSpentOnMagicalDisadvantages,
|
||||
} from "../selectors/adventurePointSelectors.ts"
|
||||
import { selectMaximumAdventurePointsForMagicalAdvantagesAndDisadvantages } from "../selectors/traditionSelectors.ts"
|
||||
import { Alert } from "../slices/alertsSlice.ts"
|
||||
import { useAppSelector } from "./redux.ts"
|
||||
|
||||
type SpecialAdventurePointsKind =
|
||||
| "advantage"
|
||||
| "magicalAdvantage"
|
||||
| "blessedAdvantage"
|
||||
| "disadvantage"
|
||||
| "magicalDisadvantage"
|
||||
| "blessedDisadvantage"
|
||||
|
||||
/**
|
||||
* Result of an availability check. If the entry can be bought or increased,
|
||||
* the result is `Ok`. Otherwise, the result is `Err` and states the reason why
|
||||
* the entry cannot be bought or increased.
|
||||
*/
|
||||
export type AvailabilityCheckResult<T extends RejectionReason> = Result<number, T>
|
||||
|
||||
/**
|
||||
* Reason why the requested entry cannot be bought or increased. It states how
|
||||
* many adventure points are missing in which category.
|
||||
*/
|
||||
export type RejectionReason = GeneralRejectionReason | SpecialRejectionReason
|
||||
|
||||
/**
|
||||
* Reason: Not enough adventure points are available in general.
|
||||
*/
|
||||
export type GeneralRejectionReason = {
|
||||
kind: "general"
|
||||
missingValue: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Reason: Not enough adventure points are available for a specific advantage
|
||||
* or disadvantage category limit.
|
||||
*/
|
||||
export type SpecialRejectionReason = {
|
||||
kind: SpecialAdventurePointsKind
|
||||
limit: number
|
||||
missingValue: number
|
||||
}
|
||||
|
||||
const checkGeneral = (
|
||||
value: number,
|
||||
available: number,
|
||||
): AvailabilityCheckResult<GeneralRejectionReason> =>
|
||||
available >= value ? ok(value) : error({ kind: "general", missingValue: value - available })
|
||||
|
||||
const checkAvailable = (
|
||||
value: number,
|
||||
available: number,
|
||||
limit: number,
|
||||
kind: SpecialAdventurePointsKind,
|
||||
): AvailabilityCheckResult<SpecialRejectionReason> =>
|
||||
available >= value ? ok(value) : error({ kind, missingValue: value - available, limit })
|
||||
|
||||
const checkLimitAndSpent = (
|
||||
value: number,
|
||||
limit: number,
|
||||
spent: number,
|
||||
kind: SpecialAdventurePointsKind,
|
||||
): AvailabilityCheckResult<SpecialRejectionReason> =>
|
||||
checkAvailable(value, limit - spent, limit, kind)
|
||||
|
||||
const checkAdvantages = (
|
||||
value: number,
|
||||
spentOnAdvantages: AdventurePointsCache,
|
||||
): AvailabilityCheckResult<SpecialRejectionReason> =>
|
||||
checkLimitAndSpent(
|
||||
value,
|
||||
maximumAdventurePointsForAdventagesAndDisadvantages,
|
||||
getTotalAdventurePointsFromCache(spentOnAdvantages),
|
||||
"advantage",
|
||||
)
|
||||
|
||||
const checkMagicalAdvantages = (
|
||||
value: number,
|
||||
limit: number,
|
||||
spentOnMagicalAdvantages: AdventurePointsCache,
|
||||
): AvailabilityCheckResult<SpecialRejectionReason> =>
|
||||
checkLimitAndSpent(
|
||||
value,
|
||||
limit,
|
||||
getTotalAdventurePointsFromCache(spentOnMagicalAdvantages),
|
||||
"magicalAdvantage",
|
||||
)
|
||||
|
||||
const checkBlessedAdvantages = (
|
||||
value: number,
|
||||
spentOnBlessedAdvantages: AdventurePointsCache,
|
||||
): AvailabilityCheckResult<SpecialRejectionReason> =>
|
||||
checkLimitAndSpent(
|
||||
value,
|
||||
maximumAdventurePointsForBlessedAdventagesAndDisadvantages,
|
||||
getTotalAdventurePointsFromCache(spentOnBlessedAdvantages),
|
||||
"blessedAdvantage",
|
||||
)
|
||||
|
||||
const checkDisadvantages = (
|
||||
value: number,
|
||||
spentOnDisadvantages: AdventurePointsCache,
|
||||
): AvailabilityCheckResult<SpecialRejectionReason> =>
|
||||
checkLimitAndSpent(
|
||||
value,
|
||||
maximumAdventurePointsForAdventagesAndDisadvantages,
|
||||
-getTotalAdventurePointsFromCache(spentOnDisadvantages),
|
||||
"disadvantage",
|
||||
)
|
||||
|
||||
const checkMagicalDisadvantages = (
|
||||
value: number,
|
||||
limit: number,
|
||||
spentOnMagicalDisadvantages: AdventurePointsCache,
|
||||
): AvailabilityCheckResult<SpecialRejectionReason> =>
|
||||
checkLimitAndSpent(
|
||||
value,
|
||||
limit,
|
||||
-getTotalAdventurePointsFromCache(spentOnMagicalDisadvantages),
|
||||
"magicalDisadvantage",
|
||||
)
|
||||
|
||||
const checkBlessedDisadvantages = (
|
||||
value: number,
|
||||
spentOnBlessedDisadvantages: AdventurePointsCache,
|
||||
): AvailabilityCheckResult<SpecialRejectionReason> =>
|
||||
checkLimitAndSpent(
|
||||
value,
|
||||
maximumAdventurePointsForBlessedAdventagesAndDisadvantages,
|
||||
-getTotalAdventurePointsFromCache(spentOnBlessedDisadvantages),
|
||||
"blessedDisadvantage",
|
||||
)
|
||||
|
||||
/**
|
||||
* Returns an alert object for the given rejection reason.
|
||||
*/
|
||||
export const getAlertForReason = (reason: RejectionReason, translate: Translate): Alert => {
|
||||
switch (reason.kind) {
|
||||
case "general":
|
||||
return {
|
||||
title: translate("Not enough AP"),
|
||||
description: translate(
|
||||
"You are missing {0} Adventure Points to do this.",
|
||||
reason.missingValue,
|
||||
reason.missingValue,
|
||||
),
|
||||
}
|
||||
case "advantage":
|
||||
return {
|
||||
title: translate("Exceeding Adventure Points limit for advantages"),
|
||||
description: translate(
|
||||
"You cannot spend more than {0} AP on advantages. You would exceed this limit by {1} AP.",
|
||||
reason.limit,
|
||||
reason.missingValue,
|
||||
),
|
||||
}
|
||||
case "magicalAdvantage":
|
||||
return {
|
||||
title: translate("Exceeding Adventure Points limit for magical advantages"),
|
||||
description: translate(
|
||||
"You cannot spend more than {0} AP on magical advantages. You would exceed this limit by {1} AP.",
|
||||
reason.limit,
|
||||
reason.missingValue,
|
||||
),
|
||||
}
|
||||
case "blessedAdvantage":
|
||||
return {
|
||||
title: translate("Exceeding Adventure Points limit for blessed advantages"),
|
||||
description: translate(
|
||||
"You cannot spend more than {0} AP on blessed advantages. You would exceed this limit by {1} AP.",
|
||||
reason.limit,
|
||||
reason.missingValue,
|
||||
),
|
||||
}
|
||||
case "disadvantage":
|
||||
return {
|
||||
title: translate("Exceeding Adventure Points limit for disadvantages"),
|
||||
description: translate(
|
||||
"You cannot receive more than {0} AP from disadvantages. You would exceed this limit by {1} AP.",
|
||||
reason.limit,
|
||||
reason.missingValue,
|
||||
),
|
||||
}
|
||||
case "magicalDisadvantage":
|
||||
return {
|
||||
title: translate("Exceeding Adventure Points limit for magical disadvantages"),
|
||||
description: translate(
|
||||
"You cannot receive more than {0} AP from magical disadvantages. You would exceed this limit by {1} AP.",
|
||||
reason.limit,
|
||||
reason.missingValue,
|
||||
),
|
||||
}
|
||||
case "blessedDisadvantage":
|
||||
return {
|
||||
title: translate("Exceeding Adventure Points limit for blessed disadvantages"),
|
||||
description: translate(
|
||||
"You cannot receive more than {0} AP from blessed disadvantages. You would exceed this limit by {1} AP.",
|
||||
reason.limit,
|
||||
reason.missingValue,
|
||||
),
|
||||
}
|
||||
default:
|
||||
return assertExhaustive(reason)
|
||||
}
|
||||
}
|
||||
|
||||
const combineChecks = <T extends RejectionReason>(
|
||||
firstResult: AvailabilityCheckResult<T>,
|
||||
...nextResults: AvailabilityCheckResult<T>[]
|
||||
): AvailabilityCheckResult<T> =>
|
||||
nextResults.reduce(
|
||||
(acc, res) =>
|
||||
combine(
|
||||
acc,
|
||||
res,
|
||||
ok1 => ok1,
|
||||
err1 => err1,
|
||||
),
|
||||
firstResult,
|
||||
)
|
||||
|
||||
/**
|
||||
* Custom hook that provides a function that checks whether enough adventure
|
||||
* points are available to buy or increase an entry.
|
||||
*/
|
||||
export const useAreEnoughAdventurePointsAvailableToBuy = () => {
|
||||
const available = useAppSelector(selectAdventurePointsAvailable)
|
||||
|
||||
return useCallback(
|
||||
(value: number): AvailabilityCheckResult<GeneralRejectionReason> =>
|
||||
checkGeneral(value, available),
|
||||
[available],
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the special adventure points kind for the given advantage or
|
||||
* disadvantage identifier.
|
||||
*/
|
||||
export const getSpecialAdventurePointsKind = (
|
||||
cache: CacheMap["magicalAndBlessedAdvantagesAndDisadvantages"],
|
||||
kind: "advantage" | "disadvantage",
|
||||
id: number,
|
||||
): SpecialAdventurePointsKind => {
|
||||
switch (kind) {
|
||||
case "advantage":
|
||||
if (cache.advantages.magical.ids.includes(id)) {
|
||||
return "magicalAdvantage"
|
||||
} else if (cache.advantages.blessed.ids.includes(id)) {
|
||||
return "blessedAdvantage"
|
||||
} else {
|
||||
return "advantage"
|
||||
}
|
||||
case "disadvantage":
|
||||
if (cache.disadvantages.magical.ids.includes(id)) {
|
||||
return "magicalDisadvantage"
|
||||
} else if (cache.disadvantages.blessed.ids.includes(id)) {
|
||||
return "blessedDisadvantage"
|
||||
} else {
|
||||
return "disadvantage"
|
||||
}
|
||||
default:
|
||||
return assertExhaustive(kind)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom hook that provides a function that checks whether enough adventure
|
||||
* points are available to buy or sell an advantage or disadvantage entry.
|
||||
*/
|
||||
export const useAreEnoughAdventurePointsAvailableToBuyAnAdvantageOrDisadvantage = () => {
|
||||
const maximumForMagicalAdvantagesDisadvantages = useAppSelector(
|
||||
selectMaximumAdventurePointsForMagicalAdvantagesAndDisadvantages,
|
||||
)
|
||||
const available = useAppSelector(selectAdventurePointsAvailable)
|
||||
const spentOnAdvantages = useAppSelector(selectAdventurePointsSpentOnAdvantages)
|
||||
const spentOnMagicalAdvantages = useAppSelector(selectAdventurePointsSpentOnMagicalAdvantages)
|
||||
const spentOnBlessedAdvantages = useAppSelector(selectAdventurePointsSpentOnBlessedAdvantages)
|
||||
const spentOnDisadvantages = useAppSelector(selectAdventurePointsSpentOnDisadvantages)
|
||||
const spentOnMagicalDisadvantages = useAppSelector(
|
||||
selectAdventurePointsSpentOnMagicalDisadvantages,
|
||||
)
|
||||
const spentOnBlessedDisadvantages = useAppSelector(
|
||||
selectAdventurePointsSpentOnBlessedDisadvantages,
|
||||
)
|
||||
|
||||
return useCallback(
|
||||
(
|
||||
value: number,
|
||||
kind?: SpecialAdventurePointsKind,
|
||||
): AvailabilityCheckResult<RejectionReason> => {
|
||||
switch (kind) {
|
||||
case undefined:
|
||||
return checkGeneral(value, available)
|
||||
case "advantage":
|
||||
return combineChecks<RejectionReason>(
|
||||
checkGeneral(value, available),
|
||||
checkAdvantages(value, spentOnAdvantages),
|
||||
)
|
||||
case "magicalAdvantage":
|
||||
return combineChecks<RejectionReason>(
|
||||
checkGeneral(value, available),
|
||||
checkAdvantages(value, spentOnAdvantages),
|
||||
checkMagicalAdvantages(
|
||||
value,
|
||||
maximumForMagicalAdvantagesDisadvantages,
|
||||
spentOnMagicalAdvantages,
|
||||
),
|
||||
)
|
||||
case "blessedAdvantage":
|
||||
return combineChecks<RejectionReason>(
|
||||
checkGeneral(value, available),
|
||||
checkAdvantages(value, spentOnAdvantages),
|
||||
checkBlessedAdvantages(value, spentOnBlessedAdvantages),
|
||||
)
|
||||
case "disadvantage":
|
||||
return combineChecks<RejectionReason>(
|
||||
checkGeneral(value, available),
|
||||
checkDisadvantages(-value, spentOnDisadvantages),
|
||||
)
|
||||
case "magicalDisadvantage":
|
||||
return combineChecks<RejectionReason>(
|
||||
checkGeneral(value, available),
|
||||
checkDisadvantages(-value, spentOnDisadvantages),
|
||||
checkMagicalDisadvantages(
|
||||
-value,
|
||||
maximumForMagicalAdvantagesDisadvantages,
|
||||
spentOnMagicalDisadvantages,
|
||||
),
|
||||
)
|
||||
case "blessedDisadvantage":
|
||||
return combineChecks<RejectionReason>(
|
||||
checkGeneral(value, available),
|
||||
checkDisadvantages(-value, spentOnDisadvantages),
|
||||
checkBlessedDisadvantages(-value, spentOnBlessedDisadvantages),
|
||||
)
|
||||
default:
|
||||
return assertExhaustive(kind)
|
||||
}
|
||||
},
|
||||
[
|
||||
available,
|
||||
maximumForMagicalAdvantagesDisadvantages,
|
||||
spentOnAdvantages,
|
||||
spentOnBlessedAdvantages,
|
||||
spentOnBlessedDisadvantages,
|
||||
spentOnDisadvantages,
|
||||
spentOnMagicalAdvantages,
|
||||
spentOnMagicalDisadvantages,
|
||||
],
|
||||
)
|
||||
}
|
||||
@@ -103,8 +103,12 @@ export const InlineLibraryRouter: FC<Props> = ({ id }) => {
|
||||
return <InlineLibraryPlaceholder />
|
||||
case "FatePointSexSpecialAbility":
|
||||
return <InlineLibraryPlaceholder />
|
||||
case "FatePointSpecialAbility":
|
||||
return <InlineLibraryPlaceholder />
|
||||
case "FocusRule":
|
||||
return <InlineLibraryFocusRule id={id.focus_rule} />
|
||||
case "FoolsHatEnchantment":
|
||||
return <InlineLibraryPlaceholder />
|
||||
case "GemOrPreciousStone":
|
||||
return <InlineLibraryPlaceholder />
|
||||
case "GeneralSpecialAbility":
|
||||
@@ -145,6 +149,10 @@ export const InlineLibraryRouter: FC<Props> = ({ id }) => {
|
||||
return <InlineLibraryPlaceholder />
|
||||
case "MagicalMelody":
|
||||
return <InlineLibraryPlaceholder />
|
||||
case "MagicalRune":
|
||||
return <InlineLibraryPlaceholder />
|
||||
case "MagicalSign":
|
||||
return <InlineLibraryPlaceholder />
|
||||
case "MagicalSpecialAbility":
|
||||
return <InlineLibraryPlaceholder />
|
||||
case "MagicalTradition":
|
||||
@@ -187,8 +195,14 @@ export const InlineLibraryRouter: FC<Props> = ({ id }) => {
|
||||
return <InlineLibraryPlaceholder />
|
||||
case "Skill":
|
||||
return <InlineLibrarySkill id={id.skill} />
|
||||
case "SkillStyleSpecialAbility":
|
||||
return <InlineLibraryPlaceholder />
|
||||
case "Spell":
|
||||
return <InlineLibraryPlaceholder />
|
||||
case "SpellSwordEnchantment":
|
||||
return <InlineLibraryPlaceholder />
|
||||
case "StaffEnchantment":
|
||||
return <InlineLibraryPlaceholder />
|
||||
case "ThievesTool":
|
||||
return <InlineLibraryPlaceholder />
|
||||
case "ToolOfTheTrade":
|
||||
@@ -199,6 +213,8 @@ export const InlineLibraryRouter: FC<Props> = ({ id }) => {
|
||||
return <InlineLibraryPlaceholder />
|
||||
case "Trinkhornzauber":
|
||||
return <InlineLibraryPlaceholder />
|
||||
case "VampiricGift":
|
||||
return <InlineLibraryPlaceholder />
|
||||
case "Vehicle":
|
||||
return <InlineLibraryPlaceholder />
|
||||
case "Vision":
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { FC } from "react"
|
||||
import {
|
||||
maximumAdventurePointsForAdventagesAndDisadvantages,
|
||||
maximumAdventurePointsForBlessedAdventagesAndDisadvantages,
|
||||
} from "../../shared/domain/activatable/advantagesDisadvantages.ts"
|
||||
import { useTranslate } from "../../shared/hooks/translate.ts"
|
||||
import { useAppSelector } from "../hooks/redux.ts"
|
||||
import {
|
||||
@@ -78,7 +82,7 @@ export const AdventurePointsTooltip: FC = () => {
|
||||
{translate(
|
||||
"{0}/{1} AP spent on advantages",
|
||||
spentOnAdvantages.general + spentOnAdvantages.bound,
|
||||
80,
|
||||
maximumAdventurePointsForAdventagesAndDisadvantages,
|
||||
)}
|
||||
</span>
|
||||
<span>
|
||||
@@ -95,32 +99,32 @@ export const AdventurePointsTooltip: FC = () => {
|
||||
? translate(
|
||||
"Thereof {0}/{1} on blessed advantages",
|
||||
spentOnBlessedAdvantages.general + spentOnBlessedAdvantages.bound,
|
||||
50,
|
||||
maximumAdventurePointsForBlessedAdventagesAndDisadvantages,
|
||||
)
|
||||
: null}
|
||||
</span>
|
||||
<span>
|
||||
{translate(
|
||||
"{0}/{1} AP received from disadvantages",
|
||||
spentOnDisadvantages.general + spentOnDisadvantages.bound,
|
||||
80,
|
||||
Math.abs(spentOnDisadvantages.general + spentOnDisadvantages.bound),
|
||||
maximumAdventurePointsForAdventagesAndDisadvantages,
|
||||
)}
|
||||
</span>
|
||||
<span>
|
||||
{spentOnMagicalDisadvantages.general + spentOnMagicalDisadvantages.bound > 0
|
||||
{spentOnMagicalDisadvantages.general + spentOnMagicalDisadvantages.bound < 0
|
||||
? translate(
|
||||
"Thereof {0}/{1} from magic disadvantages",
|
||||
spentOnMagicalDisadvantages.general + spentOnMagicalDisadvantages.bound,
|
||||
Math.abs(spentOnMagicalDisadvantages.general + spentOnMagicalDisadvantages.bound),
|
||||
maximumForMagicalAdvantagesDisadvantages,
|
||||
)
|
||||
: null}
|
||||
</span>
|
||||
<span>
|
||||
{spentOnBlessedDisadvantages.general + spentOnBlessedDisadvantages.bound > 0
|
||||
{spentOnBlessedDisadvantages.general + spentOnBlessedDisadvantages.bound < 0
|
||||
? translate(
|
||||
"Thereof {0}/{1} from blessed disadvantages",
|
||||
spentOnBlessedDisadvantages.general + spentOnBlessedDisadvantages.bound,
|
||||
50,
|
||||
Math.abs(spentOnBlessedDisadvantages.general + spentOnBlessedDisadvantages.bound),
|
||||
maximumAdventurePointsForBlessedAdventagesAndDisadvantages,
|
||||
)
|
||||
: null}
|
||||
</span>
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { FC, useCallback } from "react"
|
||||
import { Dialog } from "../../shared/components/dialog/Dialog.tsx"
|
||||
import { useTranslate } from "../../shared/hooks/translate.ts"
|
||||
import { useAppDispatch, useAppSelector } from "../hooks/redux.ts"
|
||||
import { dismissAlert, selectCurrentAlert } from "../slices/alertsSlice.ts"
|
||||
|
||||
/**
|
||||
* Displays the current alert, if any.
|
||||
*/
|
||||
export const Alerts: FC = () => {
|
||||
const dispatch = useAppDispatch()
|
||||
const translate = useTranslate()
|
||||
const currentAlert = useAppSelector(selectCurrentAlert)
|
||||
const close = useCallback(() => {
|
||||
dispatch(dismissAlert())
|
||||
}, [dispatch])
|
||||
|
||||
if (currentAlert === undefined) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog close={close} isOpen title={currentAlert.title} buttons={[{ label: translate("OK") }]}>
|
||||
{currentAlert.description}
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { classList } from "../../shared/utils/classList.ts"
|
||||
import { ExternalAPI } from "../external.ts"
|
||||
import { useAppSelector } from "../hooks/redux.ts"
|
||||
import { selectAreAnimationsEnabled, selectLocale } from "../slices/settingsSlice.ts"
|
||||
import { Alerts } from "./Alerts.tsx"
|
||||
import { NavigationBar } from "./NavigationBar.tsx"
|
||||
import "./Root.scss"
|
||||
import { Router } from "./Router.tsx"
|
||||
@@ -33,7 +34,7 @@ export const Root: FC = () => {
|
||||
<img src={backgroundImg} alt="" />
|
||||
</div>
|
||||
|
||||
{/* <AlertsContainer /> */}
|
||||
<Alerts />
|
||||
<TitleBar
|
||||
platform={ExternalAPI.platform}
|
||||
maximizeEvents={ExternalAPI}
|
||||
|
||||
+205
@@ -0,0 +1,205 @@
|
||||
import { UI } from "optolith-database-schema/types/UI"
|
||||
import { ActivatableIdentifier } from "optolith-database-schema/types/_IdentifierGroup"
|
||||
import { LocaleMap } from "optolith-database-schema/types/_LocaleMap"
|
||||
import { ReactNode, memo, useCallback, useState } from "react"
|
||||
import { Dropdown } from "../../../../../shared/components/dropdown/Dropdown.tsx"
|
||||
import { DropdownOption } from "../../../../../shared/components/dropdown/DropdownItem.tsx"
|
||||
import { IconButton } from "../../../../../shared/components/iconButton/IconButton.tsx"
|
||||
import { ListItem } from "../../../../../shared/components/list/ListItem.tsx"
|
||||
import { ListItemButtons } from "../../../../../shared/components/list/ListItemButtons.tsx"
|
||||
import { ListItemGroup } from "../../../../../shared/components/list/ListItemGroup.tsx"
|
||||
import { ListItemLeft } from "../../../../../shared/components/list/ListItemLeft.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 {
|
||||
DisplayedActiveActivatable,
|
||||
nameChunkToString,
|
||||
} from "../../../../../shared/domain/activatable/activatableActive.ts"
|
||||
import { equalsIdentifier } from "../../../../../shared/domain/identifier.ts"
|
||||
import { useTranslate } from "../../../../../shared/hooks/translate.ts"
|
||||
import { useTranslateMap } from "../../../../../shared/hooks/translateMap.ts"
|
||||
import { classList } from "../../../../../shared/utils/classList.ts"
|
||||
import { range } from "../../../../../shared/utils/range.ts"
|
||||
import { romanize } from "../../../../../shared/utils/roman.ts"
|
||||
import { PickOfType } from "../../../../../shared/utils/type.ts"
|
||||
import { assertExhaustive } from "../../../../../shared/utils/typeSafety.ts"
|
||||
import { useAppDispatch, useAppSelector } from "../../../../hooks/redux.ts"
|
||||
import {
|
||||
changeInlineLibraryEntry,
|
||||
selectInlineLibraryEntryId,
|
||||
} from "../../../../slices/inlineWikiSlice.ts"
|
||||
|
||||
type Props<T> = {
|
||||
activatable: DisplayedActiveActivatable<T>
|
||||
groupNameKey?: PickOfType<UI, string>
|
||||
isCustomCostAvailable?: boolean
|
||||
changeLevel: (
|
||||
parameters: { id: number; index: number; level: number },
|
||||
activeEntry: DisplayedActiveActivatable<T>,
|
||||
) => boolean
|
||||
remove: (
|
||||
parameters: { id: number; index: number },
|
||||
activeEntry: DisplayedActiveActivatable<T>,
|
||||
) => void
|
||||
createActivatableIdentifierObject: (id: number) => ActivatableIdentifier
|
||||
}
|
||||
|
||||
const getLevelKey = (option: DropdownOption<number>) => option.id
|
||||
|
||||
const ActiveActivatablesListItem = <
|
||||
T extends {
|
||||
id: number
|
||||
levels?: number
|
||||
translations: LocaleMap<{ name: string }>
|
||||
},
|
||||
>(
|
||||
props: Props<T>,
|
||||
): ReactNode => {
|
||||
const {
|
||||
activatable,
|
||||
groupNameKey,
|
||||
isCustomCostAvailable = false,
|
||||
changeLevel,
|
||||
remove,
|
||||
createActivatableIdentifierObject,
|
||||
} = props
|
||||
|
||||
const {
|
||||
static: { id, levels },
|
||||
dynamic,
|
||||
instanceIndex,
|
||||
isRemovable,
|
||||
cost,
|
||||
name,
|
||||
minLevel,
|
||||
maxLevel,
|
||||
} = activatable
|
||||
|
||||
const translate = useTranslate()
|
||||
const translateMap = useTranslateMap()
|
||||
const dispatch = useAppDispatch()
|
||||
const inlineLibraryEntryId = useAppSelector(selectInlineLibraryEntryId)
|
||||
|
||||
const [selectedLevel, setSelectedLevel] = useState(dynamic.instances[instanceIndex]!.level)
|
||||
|
||||
const handleLevel = useCallback(
|
||||
(level: number) => {
|
||||
if (changeLevel({ id, index: instanceIndex, level }, activatable)) {
|
||||
setSelectedLevel(level)
|
||||
}
|
||||
},
|
||||
[activatable, changeLevel, id, instanceIndex],
|
||||
)
|
||||
|
||||
const identifierObject = createActivatableIdentifierObject(id)
|
||||
|
||||
const handleSelectForInfo = useCallback(
|
||||
() => dispatch(changeInlineLibraryEntry(identifierObject)),
|
||||
[dispatch, identifierObject],
|
||||
)
|
||||
|
||||
const handleRemove = useCallback(() => {
|
||||
remove({ id, index: instanceIndex }, activatable)
|
||||
}, [activatable, id, instanceIndex, remove])
|
||||
|
||||
const levelElement =
|
||||
levels === undefined || selectedLevel === undefined || maxLevel === minLevel ? null : (
|
||||
<Dropdown
|
||||
className="level"
|
||||
options={range([minLevel ?? 1, maxLevel ?? levels]).map(l => ({
|
||||
id: l,
|
||||
name: romanize(l),
|
||||
}))}
|
||||
getKey={getLevelKey}
|
||||
value={selectedLevel}
|
||||
onChange={handleLevel}
|
||||
/>
|
||||
)
|
||||
|
||||
const costStr = (() => {
|
||||
// TODO
|
||||
// renderMaybeWith((x: number | string) => (IAA.isAutomatic(item) ? `(${x})` : x))(
|
||||
// PABSA.currentCost(snd(finalProps)),
|
||||
// )
|
||||
switch (cost.kind) {
|
||||
case "fix":
|
||||
return cost.fix.toString()
|
||||
case "variable":
|
||||
return `${cost.range[0]}–${cost.range[1]}`
|
||||
default:
|
||||
return assertExhaustive(cost)
|
||||
}
|
||||
})()
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
active={
|
||||
inlineLibraryEntryId !== undefined &&
|
||||
equalsIdentifier(inlineLibraryEntryId, identifierObject)
|
||||
}
|
||||
>
|
||||
<ListItemLeft>
|
||||
<ListItemName
|
||||
name={nameChunkToString(
|
||||
levelElement === null
|
||||
? name.full
|
||||
: Array.isArray(name.fullWithoutLevel)
|
||||
? name.fullWithoutLevel[0]
|
||||
: name.fullWithoutLevel,
|
||||
translate,
|
||||
translateMap,
|
||||
)}
|
||||
onClick={handleSelectForInfo}
|
||||
/>
|
||||
{levelElement}
|
||||
{levelElement !== null && Array.isArray(name.fullWithoutLevel) ? (
|
||||
<ListItemName
|
||||
name={nameChunkToString(name.fullWithoutLevel[1], translate, translateMap)}
|
||||
onClick={handleSelectForInfo}
|
||||
/>
|
||||
) : null}
|
||||
</ListItemLeft>
|
||||
<ListItemSeparator />
|
||||
{groupNameKey === undefined ? null : <ListItemGroup text={translate(groupNameKey)} />}
|
||||
<ListItemValues>
|
||||
{isCustomCostAvailable ? (
|
||||
<button
|
||||
className={classList("cost", {
|
||||
"custom-cost":
|
||||
dynamic.instances[instanceIndex]!.customAdventurePointsValue !== undefined,
|
||||
})}
|
||||
>
|
||||
{costStr}
|
||||
</button>
|
||||
) : (
|
||||
<div className="cost">{costStr}</div>
|
||||
)}
|
||||
</ListItemValues>
|
||||
<ListItemButtons>
|
||||
<IconButton
|
||||
icon=""
|
||||
disabled={!isRemovable}
|
||||
onClick={handleRemove}
|
||||
flat
|
||||
label={translate("Remove")}
|
||||
/>
|
||||
<IconButton
|
||||
icon=""
|
||||
onClick={handleSelectForInfo}
|
||||
flat
|
||||
label={translate("Show details")}
|
||||
/>
|
||||
</ListItemButtons>
|
||||
</ListItem>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays an activatable entry that is currently active.
|
||||
*/
|
||||
const MemoActiveActivatablesListItem = memo(
|
||||
ActiveActivatablesListItem,
|
||||
) as typeof ActiveActivatablesListItem
|
||||
|
||||
export { MemoActiveActivatablesListItem as ActiveActivatablesListItem }
|
||||
+294
@@ -0,0 +1,294 @@
|
||||
import { UI } from "optolith-database-schema/types/UI"
|
||||
import { AdventurePointsValue } from "optolith-database-schema/types/_Activatable"
|
||||
import { ActivatableIdentifier } from "optolith-database-schema/types/_IdentifierGroup"
|
||||
import { LocaleMap } from "optolith-database-schema/types/_LocaleMap"
|
||||
import { ReactNode, memo, useCallback, useMemo, useState } from "react"
|
||||
import { BasicInputDialog } from "../../../../../shared/components/basicInputDialog/BasicInputDialog.tsx"
|
||||
import { Dropdown } from "../../../../../shared/components/dropdown/Dropdown.tsx"
|
||||
import { DropdownOption } from "../../../../../shared/components/dropdown/DropdownItem.tsx"
|
||||
import { IconButton } from "../../../../../shared/components/iconButton/IconButton.tsx"
|
||||
import { ListItem } from "../../../../../shared/components/list/ListItem.tsx"
|
||||
import { ListItemButtons } from "../../../../../shared/components/list/ListItemButtons.tsx"
|
||||
import { ListItemGroup } from "../../../../../shared/components/list/ListItemGroup.tsx"
|
||||
import { ListItemLeft } from "../../../../../shared/components/list/ListItemLeft.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 { getCostOfInstance } from "../../../../../shared/domain/activatable/activatableActive.ts"
|
||||
import {
|
||||
ActivatableInstance,
|
||||
ActivatableOption,
|
||||
} from "../../../../../shared/domain/activatable/activatableEntry.ts"
|
||||
import {
|
||||
DisplayedInactiveActivatable,
|
||||
getSelectOptionFromDisplayedOption,
|
||||
} from "../../../../../shared/domain/activatable/activatableInactive.ts"
|
||||
import { AdvantageIdentifier, equalsIdentifier } from "../../../../../shared/domain/identifier.ts"
|
||||
import { useTranslate } from "../../../../../shared/hooks/translate.ts"
|
||||
import { useTranslateMap } from "../../../../../shared/hooks/translateMap.ts"
|
||||
import { classList } from "../../../../../shared/utils/classList.ts"
|
||||
import { parseInt } from "../../../../../shared/utils/math.ts"
|
||||
import { ensure, isNotNullish } from "../../../../../shared/utils/nullable.ts"
|
||||
import { range } from "../../../../../shared/utils/range.ts"
|
||||
import { romanize } from "../../../../../shared/utils/roman.ts"
|
||||
import { isNonEmptyString } from "../../../../../shared/utils/string.ts"
|
||||
import { PickOfType } from "../../../../../shared/utils/type.ts"
|
||||
import { assertExhaustive } from "../../../../../shared/utils/typeSafety.ts"
|
||||
import { useModalState } from "../../../../hooks/modalState.ts"
|
||||
import { useAppDispatch, useAppSelector } from "../../../../hooks/redux.ts"
|
||||
import {
|
||||
changeInlineLibraryEntry,
|
||||
selectInlineLibraryEntryId,
|
||||
} from "../../../../slices/inlineWikiSlice.ts"
|
||||
import { InactiveActivatableListItemOption } from "./InactiveActivatableListItemOption.tsx"
|
||||
|
||||
type Props<T> = {
|
||||
activatable: DisplayedInactiveActivatable<T>
|
||||
groupNameKey?: PickOfType<UI, string>
|
||||
isCustomCostAvailable?: boolean
|
||||
add: (parameters: { id: number; instance: ActivatableInstance }, cost: number) => void
|
||||
createActivatableIdentifierObject: (id: number) => ActivatableIdentifier
|
||||
}
|
||||
|
||||
const getLevelKey = (option: DropdownOption<number>) => option.id
|
||||
|
||||
const InactiveActivatablesListItem = <
|
||||
T extends {
|
||||
id: number
|
||||
ap_value: AdventurePointsValue
|
||||
translations: LocaleMap<{ name: string }>
|
||||
},
|
||||
>(
|
||||
props: Props<T>,
|
||||
): ReactNode => {
|
||||
const {
|
||||
activatable: {
|
||||
static: { id, ap_value, translations },
|
||||
dynamic,
|
||||
isAvailable,
|
||||
options,
|
||||
level,
|
||||
},
|
||||
groupNameKey,
|
||||
isCustomCostAvailable = false,
|
||||
add,
|
||||
createActivatableIdentifierObject,
|
||||
} = props
|
||||
|
||||
const translate = useTranslate()
|
||||
const translateMap = useTranslateMap()
|
||||
const dispatch = useAppDispatch()
|
||||
const inlineLibraryEntryId = useAppSelector(selectInlineLibraryEntryId)
|
||||
|
||||
const { name = "" } = translateMap(translations) ?? {}
|
||||
|
||||
const [selectedLevel, setSelectedLevel] = useState(() => (level === undefined ? undefined : 1))
|
||||
const [selectedOptions, setSelectedOptions] = useState<(ActivatableOption | undefined)[]>([])
|
||||
const [customCost, setCustomCost] = useState<number | undefined>()
|
||||
const [customCostPreview, setCustomCostPreview] = useState<string>()
|
||||
|
||||
const {
|
||||
isOpen: showCustomCostDialog,
|
||||
open: openCustomCostDialog,
|
||||
close: closeCustomCostDialog,
|
||||
} = useModalState()
|
||||
|
||||
const handleChangeOption = useCallback((option: ActivatableOption | undefined, index: number) => {
|
||||
setSelectedOptions(oldSelectedOptions => {
|
||||
const newSelectedOptions = [...oldSelectedOptions]
|
||||
// TODO: Reset options with higher indices?
|
||||
// const newSelectedOptions = oldSelectedOptions.slice(0, index)
|
||||
newSelectedOptions[index] = option
|
||||
return newSelectedOptions
|
||||
})
|
||||
}, [])
|
||||
|
||||
const handleChangeLevel = useCallback((newLevel: number) => {
|
||||
setSelectedLevel(newLevel)
|
||||
// TODO: Reset options for specific entries?
|
||||
// if (DisadvantageIdentifier.Principles === id || DisadvantageIdentifier.Obligations === id) {
|
||||
// setSelectedOptions([])
|
||||
// }
|
||||
}, [])
|
||||
|
||||
const handleShowCustomCostDialog = useCallback(() => {
|
||||
if (isCustomCostAvailable) {
|
||||
openCustomCostDialog()
|
||||
setCustomCostPreview(customCost?.toString())
|
||||
}
|
||||
}, [isCustomCostAvailable, openCustomCostDialog, customCost])
|
||||
|
||||
const handleSetCustomCost = useCallback(
|
||||
() => setCustomCost(customCostPreview === undefined ? undefined : parseInt(customCostPreview)),
|
||||
[customCostPreview],
|
||||
)
|
||||
|
||||
const handleSetCustomCostPreview = useCallback(
|
||||
(newCustomCostPreview: string) =>
|
||||
setCustomCostPreview(ensure(newCustomCostPreview, isNonEmptyString)),
|
||||
[setCustomCostPreview],
|
||||
)
|
||||
|
||||
const handleDeleteCustomCost = useCallback(() => setCustomCost(undefined), [setCustomCost])
|
||||
|
||||
const idObject = useMemo(
|
||||
() => createActivatableIdentifierObject(id),
|
||||
[createActivatableIdentifierObject, id],
|
||||
)
|
||||
|
||||
const handleSelectForInfo = useCallback(
|
||||
() => dispatch(changeInlineLibraryEntry(idObject)),
|
||||
[dispatch, idObject],
|
||||
)
|
||||
|
||||
const cost = useMemo(
|
||||
() =>
|
||||
getCostOfInstance(
|
||||
idObject,
|
||||
ap_value,
|
||||
dynamic?.instances ?? [],
|
||||
{
|
||||
level: selectedLevel,
|
||||
options: selectedOptions.filter(isNotNullish),
|
||||
customAdventurePointsValue: customCost,
|
||||
},
|
||||
optionId => getSelectOptionFromDisplayedOption(options, optionId),
|
||||
true,
|
||||
),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[ap_value, customCost, options, selectedLevel, selectedOptions],
|
||||
)
|
||||
|
||||
const handleAdd = useCallback(() => {
|
||||
add(
|
||||
{
|
||||
id,
|
||||
instance: {
|
||||
level: selectedLevel,
|
||||
options: selectedOptions.filter(isNotNullish),
|
||||
customAdventurePointsValue: customCost,
|
||||
},
|
||||
},
|
||||
cost ?? 0,
|
||||
)
|
||||
setSelectedLevel(undefined)
|
||||
setSelectedOptions([])
|
||||
}, [add, cost, customCost, id, selectedLevel, selectedOptions])
|
||||
|
||||
if (equalsIdentifier({ tag: "Advantage", advantage: AdvantageIdentifier.HatredOf }, idObject)) {
|
||||
console.log("cost", cost)
|
||||
console.log("options", options)
|
||||
}
|
||||
|
||||
const isAddDisabled = useMemo(
|
||||
() =>
|
||||
!isAvailable ||
|
||||
selectedOptions.filter(isNotNullish).length !==
|
||||
options.reduce((optionsSum, option) => {
|
||||
switch (option.kind) {
|
||||
case "choice":
|
||||
return optionsSum + (option.count ?? 1)
|
||||
case "choiceOrCustomizableText":
|
||||
case "text":
|
||||
return optionsSum + 1
|
||||
default:
|
||||
return assertExhaustive(option)
|
||||
}
|
||||
}, 0) ||
|
||||
(level === undefined) !== (selectedLevel === undefined) ||
|
||||
cost === undefined,
|
||||
[cost, isAvailable, level, options, selectedLevel, selectedOptions],
|
||||
)
|
||||
|
||||
const levelElement =
|
||||
level === undefined || !isAvailable ? null : level.maximum > 1 ? (
|
||||
<Dropdown
|
||||
className="level"
|
||||
options={range([1, level.maximum]).map(l => ({ id: l, name: romanize(l) }))}
|
||||
getKey={getLevelKey}
|
||||
value={selectedLevel}
|
||||
onChange={handleChangeLevel}
|
||||
/>
|
||||
) : (
|
||||
<span className="level title">{romanize(1)}</span>
|
||||
)
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
active={
|
||||
inlineLibraryEntryId !== undefined && equalsIdentifier(inlineLibraryEntryId, idObject)
|
||||
}
|
||||
disabled={!isAvailable}
|
||||
>
|
||||
<ListItemLeft>
|
||||
<ListItemName name={name} onClick={handleSelectForInfo} />
|
||||
{level?.placement === "after" ? levelElement : null}
|
||||
{isAvailable
|
||||
? options.map((option, index) => (
|
||||
<InactiveActivatableListItemOption
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
key={index}
|
||||
index={index}
|
||||
onChange={handleChangeOption}
|
||||
option={option}
|
||||
value={selectedOptions[index]}
|
||||
/>
|
||||
))
|
||||
: null}
|
||||
{level?.placement === "before" ? levelElement : null}
|
||||
</ListItemLeft>
|
||||
<ListItemSeparator />
|
||||
{groupNameKey === undefined ? null : <ListItemGroup text={translate(groupNameKey)} />}
|
||||
<ListItemValues>
|
||||
{isCustomCostAvailable ? (
|
||||
<button
|
||||
className={classList("cost", {
|
||||
"custom-cost": customCost !== undefined,
|
||||
})}
|
||||
onClick={handleShowCustomCostDialog}
|
||||
>
|
||||
{cost ?? null}
|
||||
</button>
|
||||
) : (
|
||||
<div className="cost">{cost ?? null}</div>
|
||||
)}
|
||||
<BasicInputDialog
|
||||
id="custom-cost-dialog"
|
||||
isOpen={showCustomCostDialog}
|
||||
title={translate("Custom AP Cost")}
|
||||
description={translate("AP Cost for {0}", name)}
|
||||
value={customCostPreview}
|
||||
acceptLabel={translate("Done")}
|
||||
rejectLabel={translate("Delete")}
|
||||
rejectDisabled={customCost === undefined}
|
||||
onClose={closeCustomCostDialog}
|
||||
onAccept={handleSetCustomCost}
|
||||
onReject={handleDeleteCustomCost}
|
||||
onChange={handleSetCustomCostPreview}
|
||||
/>
|
||||
</ListItemValues>
|
||||
<ListItemButtons>
|
||||
<IconButton
|
||||
icon=""
|
||||
disabled={isAddDisabled}
|
||||
onClick={handleAdd}
|
||||
flat
|
||||
label={translate("Add")}
|
||||
/>
|
||||
<IconButton
|
||||
icon=""
|
||||
onClick={handleSelectForInfo}
|
||||
flat
|
||||
label={translate("Show details")}
|
||||
/>
|
||||
</ListItemButtons>
|
||||
</ListItem>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays an activatable entry that is currently inactive.
|
||||
*/
|
||||
const MemoInactiveActivatablesListItem = memo(InactiveActivatablesListItem)
|
||||
|
||||
export { MemoInactiveActivatablesListItem as InactiveActivatablesListItem }
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
import { SelectOptionIdentifier } from "optolith-database-schema/types/_IdentifierGroup"
|
||||
import { FC, useCallback } from "react"
|
||||
import { Dropdown } from "../../../../../shared/components/dropdown/Dropdown.tsx"
|
||||
import { DropdownOption } from "../../../../../shared/components/dropdown/DropdownItem.tsx"
|
||||
import { TextField } from "../../../../../shared/components/textField/TextField.tsx"
|
||||
import { ActivatableOption } from "../../../../../shared/domain/activatable/activatableEntry.ts"
|
||||
import { DisplayedInactiveActivatableOption } from "../../../../../shared/domain/activatable/activatableInactive.ts"
|
||||
import { identifierObjectToString } from "../../../../../shared/domain/identifier.ts"
|
||||
import { useLocaleCompare } from "../../../../../shared/hooks/localeCompare.ts"
|
||||
import { useTranslate } from "../../../../../shared/hooks/translate.ts"
|
||||
import { useTranslateMap } from "../../../../../shared/hooks/translateMap.ts"
|
||||
import { compareAt } from "../../../../../shared/utils/compare.ts"
|
||||
import { assertExhaustive } from "../../../../../shared/utils/typeSafety.ts"
|
||||
|
||||
type Props = {
|
||||
option: DisplayedInactiveActivatableOption
|
||||
value: ActivatableOption | undefined
|
||||
onChange: (option: ActivatableOption | undefined, index: number) => void
|
||||
index: number
|
||||
}
|
||||
|
||||
const getKey = (option: DropdownOption<SelectOptionIdentifier | undefined>) =>
|
||||
option.id === undefined ? "default" : identifierObjectToString(option.id)
|
||||
|
||||
/**
|
||||
* Displays controls for handling a single option of an activatable entry.
|
||||
*/
|
||||
export const InactiveActivatableListItemOption: FC<Props> = props => {
|
||||
const { option, value, onChange, index } = props
|
||||
const translate = useTranslate()
|
||||
const translateMap = useTranslateMap()
|
||||
const localeCompare = useLocaleCompare()
|
||||
|
||||
const handleDropdownChange = useCallback(
|
||||
(id: SelectOptionIdentifier) => {
|
||||
onChange({ type: "Predefined", id }, index)
|
||||
},
|
||||
[index, onChange],
|
||||
)
|
||||
|
||||
const handleTextfieldChange = useCallback(
|
||||
(newText: string) => {
|
||||
onChange(newText === "" ? undefined : { type: "Custom", value: newText }, index)
|
||||
},
|
||||
[index, onChange],
|
||||
)
|
||||
|
||||
switch (option.kind) {
|
||||
case "choice":
|
||||
return (
|
||||
<Dropdown
|
||||
options={option.options
|
||||
.map(o => ({
|
||||
id: o.id,
|
||||
name: translateMap(o.translations)?.name ?? "…",
|
||||
}))
|
||||
.toSorted(compareAt(opt => opt.name, localeCompare))}
|
||||
value={value?.type === "Custom" ? undefined : value?.id}
|
||||
onChange={handleDropdownChange}
|
||||
getKey={getKey}
|
||||
/>
|
||||
)
|
||||
case "choiceOrCustomizableText":
|
||||
return (
|
||||
<>
|
||||
<Dropdown
|
||||
key={index}
|
||||
options={
|
||||
value?.type === "Custom"
|
||||
? [{ id: undefined, name: translate("Custom option") }]
|
||||
: option.options
|
||||
.map(o => ({
|
||||
id: o.id,
|
||||
name: translateMap(o.translations)?.name ?? "…",
|
||||
}))
|
||||
.toSorted(compareAt(opt => opt.name, localeCompare))
|
||||
}
|
||||
value={value?.type === "Custom" ? undefined : value?.id}
|
||||
disabled={value?.type === "Custom"}
|
||||
onChange={handleDropdownChange}
|
||||
getKey={getKey}
|
||||
/>
|
||||
<TextField
|
||||
onChange={handleTextfieldChange}
|
||||
value={value?.type === "Custom" ? value.value : ""}
|
||||
hint={translateMap(option.textLabel) ?? translate("Custom option")}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
case "text":
|
||||
return (
|
||||
<TextField
|
||||
onChange={handleTextfieldChange}
|
||||
value={value?.type === "Custom" ? value.value : ""}
|
||||
hint={translateMap(option.textLabel)}
|
||||
/>
|
||||
)
|
||||
default:
|
||||
return assertExhaustive(option)
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
import { FC } from "react"
|
||||
import { DisplayedActiveAdvantage } from "../../../../../shared/domain/activatable/advantagesActive.ts"
|
||||
import { getCreateIdentifierObject } from "../../../../../shared/domain/identifier.ts"
|
||||
import { useActiveAdvantageAndDisadvantageActions } from "../../../../hooks/activatableActions.ts"
|
||||
import { changeAdvantageLevel, removeAdvantage } from "../../../../slices/advantagesSlice.ts"
|
||||
import { ActiveActivatablesListItem } from "../activatable/ActiveActivatableListItem.tsx"
|
||||
|
||||
type Props = {
|
||||
advantage: DisplayedActiveAdvantage
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays an advantage that is currently active.
|
||||
*/
|
||||
export const ActiveAdvantagesListItem: FC<Props> = props => {
|
||||
const { advantage } = props
|
||||
const { handleChangeLevel, handleRemove } = useActiveAdvantageAndDisadvantageActions(
|
||||
"advantage",
|
||||
removeAdvantage,
|
||||
changeAdvantageLevel,
|
||||
getCreateIdentifierObject("Advantage"),
|
||||
)
|
||||
|
||||
return (
|
||||
<ActiveActivatablesListItem
|
||||
activatable={advantage}
|
||||
changeLevel={handleChangeLevel}
|
||||
remove={handleRemove}
|
||||
createActivatableIdentifierObject={getCreateIdentifierObject("Advantage")}
|
||||
/>
|
||||
)
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import { FC } from "react"
|
||||
import { DisplayedActiveDisadvantage } from "../../../../../shared/domain/activatable/disadvantagesActive.ts"
|
||||
import { getCreateIdentifierObject } from "../../../../../shared/domain/identifier.ts"
|
||||
import { useActiveAdvantageAndDisadvantageActions } from "../../../../hooks/activatableActions.ts"
|
||||
import {
|
||||
changeDisadvantageLevel,
|
||||
removeDisadvantage,
|
||||
} from "../../../../slices/disadvantagesSlice.ts"
|
||||
import { ActiveActivatablesListItem } from "../activatable/ActiveActivatableListItem.tsx"
|
||||
|
||||
type Props = {
|
||||
disadvantage: DisplayedActiveDisadvantage
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a disadvantage that is currently active.
|
||||
*/
|
||||
export const ActiveDisadvantagesListItem: FC<Props> = props => {
|
||||
const { disadvantage } = props
|
||||
const { handleChangeLevel, handleRemove } = useActiveAdvantageAndDisadvantageActions(
|
||||
"disadvantage",
|
||||
removeDisadvantage,
|
||||
changeDisadvantageLevel,
|
||||
getCreateIdentifierObject("Disadvantage"),
|
||||
)
|
||||
|
||||
return (
|
||||
<ActiveActivatablesListItem
|
||||
activatable={disadvantage}
|
||||
changeLevel={handleChangeLevel}
|
||||
remove={handleRemove}
|
||||
createActivatableIdentifierObject={getCreateIdentifierObject("Disadvantage")}
|
||||
/>
|
||||
)
|
||||
}
|
||||
+109
-16
@@ -1,7 +1,8 @@
|
||||
import { FC, useCallback, useState } from "react"
|
||||
import { FC, useCallback, useMemo, useState } from "react"
|
||||
import { Button } from "../../../../../shared/components/button/Button.tsx"
|
||||
import { Checkbox } from "../../../../../shared/components/checkbox/Checkbox.tsx"
|
||||
import { Grid } from "../../../../../shared/components/grid/Grid.tsx"
|
||||
import { List } from "../../../../../shared/components/list/List.tsx"
|
||||
import { ListHeader } from "../../../../../shared/components/list/ListHeader.tsx"
|
||||
import { ListHeaderTag } from "../../../../../shared/components/list/ListHeaderTag.tsx"
|
||||
import { ListPlaceholder } from "../../../../../shared/components/list/ListPlaceholder.tsx"
|
||||
@@ -10,10 +11,19 @@ import { Options } from "../../../../../shared/components/options/Options.tsx"
|
||||
import { Page } from "../../../../../shared/components/page/Page.tsx"
|
||||
import { RecommendedReference } from "../../../../../shared/components/recommendedReference/RecommendedReference.tsx"
|
||||
import { Scroll } from "../../../../../shared/components/scroll/Scroll.tsx"
|
||||
import { Slidein } from "../../../../../shared/components/slidein/Slidein.tsx"
|
||||
import { TextField } from "../../../../../shared/components/textField/TextField.tsx"
|
||||
import { filterAndSortDisplayed } from "../../../../../shared/domain/activatable/advantagesDisadvantages.ts"
|
||||
import { useLocaleCompare } from "../../../../../shared/hooks/localeCompare.ts"
|
||||
import { useTranslate } from "../../../../../shared/hooks/translate.ts"
|
||||
import { useTranslateMap } from "../../../../../shared/hooks/translateMap.ts"
|
||||
import { useModalState } from "../../../../hooks/modalState.ts"
|
||||
import { useAppDispatch, useAppSelector } from "../../../../hooks/redux.ts"
|
||||
import { InlineLibrary } from "../../../../inlineLibrary/InlineLibrary.tsx"
|
||||
import {
|
||||
selectVisibleActiveAdvantages,
|
||||
selectVisibleInactiveAdvantages,
|
||||
} from "../../../../selectors/advantageDisadvantageSelectors.ts"
|
||||
import {
|
||||
selectCanRemove,
|
||||
selectIsInCharacterCreation,
|
||||
@@ -22,19 +32,23 @@ import {
|
||||
selectAdvantagesDisadvantagesCultureRatingVisibility,
|
||||
switchAdvantagesDisadvantagesCultureRatingVisibility,
|
||||
} from "../../../../slices/settingsSlice.ts"
|
||||
import { ActiveAdvantagesListItem } from "./ActiveAdvantagesListItem.tsx"
|
||||
import { AdventurePointsSpent } from "./AdventurePointsSpent.tsx"
|
||||
import { InactiveAdvantagesListItem } from "./InactiveAdvantagesListItem.tsx"
|
||||
|
||||
/**
|
||||
* Returns a page for managing advantages.
|
||||
*/
|
||||
export const Advantages: FC = () => {
|
||||
const translate = useTranslate()
|
||||
const translateMap = useTranslateMap()
|
||||
const dispatch = useAppDispatch()
|
||||
const localeCompare = useLocaleCompare()
|
||||
|
||||
const cultureRatingVisibility = useAppSelector(
|
||||
selectAdvantagesDisadvantagesCultureRatingVisibility,
|
||||
)
|
||||
const handlSwitchCultureRatingVisibility = useCallback(
|
||||
const handleSwitchCultureRatingVisibility = useCallback(
|
||||
() => dispatch(switchAdvantagesDisadvantagesCultureRatingVisibility()),
|
||||
[dispatch],
|
||||
)
|
||||
@@ -42,16 +56,92 @@ export const Advantages: FC = () => {
|
||||
const canRemove = useAppSelector(selectCanRemove)
|
||||
const isInCharacterCreation = useAppSelector(selectIsInCharacterCreation)
|
||||
|
||||
const [filterText, setFilterText] = useState("")
|
||||
const [activeFilterText, setActiveFilterText] = useState("")
|
||||
const [inactiveFilterText, setInactiveFilterText] = useState("")
|
||||
|
||||
const visibleInactiveAdvantages = useAppSelector(selectVisibleInactiveAdvantages)
|
||||
const visibleActiveAdvantages = useAppSelector(selectVisibleActiveAdvantages)
|
||||
|
||||
const inactiveList = useMemo(
|
||||
() =>
|
||||
filterAndSortDisplayed(
|
||||
visibleInactiveAdvantages,
|
||||
inactiveFilterText,
|
||||
translateMap,
|
||||
localeCompare,
|
||||
),
|
||||
[visibleInactiveAdvantages, inactiveFilterText, translateMap, localeCompare],
|
||||
)
|
||||
|
||||
const activeList = useMemo(
|
||||
() =>
|
||||
filterAndSortDisplayed(
|
||||
visibleActiveAdvantages,
|
||||
activeFilterText,
|
||||
translateMap,
|
||||
localeCompare,
|
||||
),
|
||||
[visibleActiveAdvantages, activeFilterText, translateMap, localeCompare],
|
||||
)
|
||||
|
||||
const { isOpen: isSlideinVisible, open: openSlidein, close: closeSlidein } = useModalState()
|
||||
|
||||
return (
|
||||
<Page id="advantages">
|
||||
<Slidein isOpen={isSlideinVisible} close={closeSlidein} className="adding-advantages">
|
||||
<Options>
|
||||
<TextField
|
||||
value={inactiveFilterText}
|
||||
onChange={setInactiveFilterText}
|
||||
hint={translate("Search")}
|
||||
/>
|
||||
<Grid size="medium">
|
||||
<Checkbox
|
||||
checked={cultureRatingVisibility}
|
||||
onClick={handleSwitchCultureRatingVisibility}
|
||||
>
|
||||
{translate("Common Advantages")}
|
||||
</Checkbox>
|
||||
{cultureRatingVisibility ? <RecommendedReference strongly /> : null}
|
||||
</Grid>
|
||||
{isInCharacterCreation && <AdventurePointsSpent />}
|
||||
{/* <Checkbox checked={enableActiveItemHints} onClick={switchActiveItemHints}>
|
||||
{translate(staticData)("general.filters.showactivatedentries")}
|
||||
</Checkbox> */}
|
||||
</Options>
|
||||
<Main classOnly>
|
||||
<ListHeader>
|
||||
<ListHeaderTag className="name">{translate("Name")}</ListHeaderTag>
|
||||
<ListHeaderTag className="cost" hint={translate("Adventure Points")}>
|
||||
{translate("AP")}
|
||||
</ListHeaderTag>
|
||||
<ListHeaderTag className="btn-placeholder" />
|
||||
<ListHeaderTag className="btn-placeholder" />
|
||||
</ListHeader>
|
||||
<Scroll>
|
||||
{inactiveList.length > 0 ? (
|
||||
<List>
|
||||
{inactiveList.map(x => (
|
||||
<InactiveAdvantagesListItem key={`advantage-${x.static.id}`} advantage={x} />
|
||||
))}
|
||||
</List>
|
||||
) : (
|
||||
<ListPlaceholder type="inactiveAdvantages" message={translate("No Results")} />
|
||||
)}
|
||||
</Scroll>
|
||||
</Main>
|
||||
<InlineLibrary />
|
||||
</Slidein>
|
||||
<Options>
|
||||
<TextField value={filterText} onChange={setFilterText} hint={translate("Search")} />
|
||||
<Button>{translate("Add")}</Button>
|
||||
<TextField
|
||||
value={activeFilterText}
|
||||
onChange={setActiveFilterText}
|
||||
hint={translate("Search")}
|
||||
/>
|
||||
<Button onClick={openSlidein}>{translate("Add")}</Button>
|
||||
<Grid size="medium">
|
||||
<Checkbox checked={cultureRatingVisibility} onClick={handlSwitchCultureRatingVisibility}>
|
||||
{translate("advantages.filters.commonadvantages")}
|
||||
<Checkbox checked={cultureRatingVisibility} onClick={handleSwitchCultureRatingVisibility}>
|
||||
{translate("Common Advantages")}
|
||||
</Checkbox>
|
||||
{cultureRatingVisibility ? <RecommendedReference strongly /> : null}
|
||||
</Grid>
|
||||
@@ -59,20 +149,23 @@ export const Advantages: FC = () => {
|
||||
</Options>
|
||||
<Main>
|
||||
<ListHeader>
|
||||
<ListHeaderTag className="name">
|
||||
{translate("advantagesdisadvantages.header.name")}
|
||||
</ListHeaderTag>
|
||||
<ListHeaderTag
|
||||
className="cost"
|
||||
hint={translate("advantagesdisadvantages.header.adventurepoints.tooltip")}
|
||||
>
|
||||
{translate("advantagesdisadvantages.header.adventurepoints")}
|
||||
<ListHeaderTag className="name">{translate("Name")}</ListHeaderTag>
|
||||
<ListHeaderTag className="cost" hint={translate("Adventure Points")}>
|
||||
{translate("AP")}
|
||||
</ListHeaderTag>
|
||||
{canRemove && <ListHeaderTag className="btn-placeholder" />}
|
||||
<ListHeaderTag className="btn-placeholder" />
|
||||
</ListHeader>
|
||||
<Scroll stable>
|
||||
<ListPlaceholder type="advantages" message={translate("No Results")} />
|
||||
{activeList.length > 0 ? (
|
||||
<List>
|
||||
{activeList.map(x => (
|
||||
<ActiveAdvantagesListItem key={`advantage-${x.static.id}`} advantage={x} />
|
||||
))}
|
||||
</List>
|
||||
) : (
|
||||
<ListPlaceholder type="advantages" message={translate("No Results")} />
|
||||
)}
|
||||
</Scroll>
|
||||
</Main>
|
||||
<InlineLibrary />
|
||||
|
||||
+5
-5
@@ -66,25 +66,25 @@ export const AdventurePointsSpent: FC = () => {
|
||||
<p>
|
||||
{translate(
|
||||
"{0}/{1} AP received from disadvantages",
|
||||
spentOnDisadvantages.general + spentOnDisadvantages.bound,
|
||||
Math.abs(spentOnDisadvantages.general + spentOnDisadvantages.bound),
|
||||
80,
|
||||
)}
|
||||
{spentOnMagicalDisadvantages.general + spentOnMagicalDisadvantages.bound > 0 ? (
|
||||
{spentOnMagicalDisadvantages.general + spentOnMagicalDisadvantages.bound < 0 ? (
|
||||
<>
|
||||
<br />
|
||||
{translate(
|
||||
"Thereof {0}/{1} from magic disadvantages",
|
||||
spentOnMagicalDisadvantages.general + spentOnMagicalDisadvantages.bound,
|
||||
Math.abs(spentOnMagicalDisadvantages.general + spentOnMagicalDisadvantages.bound),
|
||||
maximumForMagicalAdvantagesDisadvantages,
|
||||
)}
|
||||
</>
|
||||
) : null}
|
||||
{spentOnBlessedDisadvantages.general + spentOnBlessedDisadvantages.bound > 0 ? (
|
||||
{spentOnBlessedDisadvantages.general + spentOnBlessedDisadvantages.bound < 0 ? (
|
||||
<>
|
||||
<br />
|
||||
{translate(
|
||||
"Thereof {0}/{1} from blessed disadvantages",
|
||||
spentOnBlessedDisadvantages.general + spentOnBlessedDisadvantages.bound,
|
||||
Math.abs(spentOnBlessedDisadvantages.general + spentOnBlessedDisadvantages.bound),
|
||||
50,
|
||||
)}
|
||||
</>
|
||||
|
||||
+112
-16
@@ -1,7 +1,8 @@
|
||||
import { FC, useCallback, useState } from "react"
|
||||
import { FC, useCallback, useMemo, useState } from "react"
|
||||
import { Button } from "../../../../../shared/components/button/Button.tsx"
|
||||
import { Checkbox } from "../../../../../shared/components/checkbox/Checkbox.tsx"
|
||||
import { Grid } from "../../../../../shared/components/grid/Grid.tsx"
|
||||
import { List } from "../../../../../shared/components/list/List.tsx"
|
||||
import { ListHeader } from "../../../../../shared/components/list/ListHeader.tsx"
|
||||
import { ListHeaderTag } from "../../../../../shared/components/list/ListHeaderTag.tsx"
|
||||
import { ListPlaceholder } from "../../../../../shared/components/list/ListPlaceholder.tsx"
|
||||
@@ -10,10 +11,19 @@ import { Options } from "../../../../../shared/components/options/Options.tsx"
|
||||
import { Page } from "../../../../../shared/components/page/Page.tsx"
|
||||
import { RecommendedReference } from "../../../../../shared/components/recommendedReference/RecommendedReference.tsx"
|
||||
import { Scroll } from "../../../../../shared/components/scroll/Scroll.tsx"
|
||||
import { Slidein } from "../../../../../shared/components/slidein/Slidein.tsx"
|
||||
import { TextField } from "../../../../../shared/components/textField/TextField.tsx"
|
||||
import { filterAndSortDisplayed } from "../../../../../shared/domain/activatable/advantagesDisadvantages.ts"
|
||||
import { useLocaleCompare } from "../../../../../shared/hooks/localeCompare.ts"
|
||||
import { useTranslate } from "../../../../../shared/hooks/translate.ts"
|
||||
import { useTranslateMap } from "../../../../../shared/hooks/translateMap.ts"
|
||||
import { useModalState } from "../../../../hooks/modalState.ts"
|
||||
import { useAppDispatch, useAppSelector } from "../../../../hooks/redux.ts"
|
||||
import { InlineLibrary } from "../../../../inlineLibrary/InlineLibrary.tsx"
|
||||
import {
|
||||
selectVisibleActiveDisadvantages,
|
||||
selectVisibleInactiveDisadvantages,
|
||||
} from "../../../../selectors/advantageDisadvantageSelectors.ts"
|
||||
import {
|
||||
selectCanRemove,
|
||||
selectIsInCharacterCreation,
|
||||
@@ -22,19 +32,23 @@ import {
|
||||
selectAdvantagesDisadvantagesCultureRatingVisibility,
|
||||
switchAdvantagesDisadvantagesCultureRatingVisibility,
|
||||
} from "../../../../slices/settingsSlice.ts"
|
||||
import { ActiveDisadvantagesListItem } from "./ActiveDisadvantagesListItem.tsx"
|
||||
import { AdventurePointsSpent } from "./AdventurePointsSpent.tsx"
|
||||
import { InactiveDisadvantagesListItem } from "./InactiveDisadvantagesListItem.tsx"
|
||||
|
||||
/**
|
||||
* Returns a page for managing disadvantages.
|
||||
*/
|
||||
export const Disadvantages: FC = () => {
|
||||
const translate = useTranslate()
|
||||
const translateMap = useTranslateMap()
|
||||
const dispatch = useAppDispatch()
|
||||
const localeCompare = useLocaleCompare()
|
||||
|
||||
const cultureRatingVisibility = useAppSelector(
|
||||
selectAdvantagesDisadvantagesCultureRatingVisibility,
|
||||
)
|
||||
const handlSwitchCultureRatingVisibility = useCallback(
|
||||
const handleSwitchCultureRatingVisibility = useCallback(
|
||||
() => dispatch(switchAdvantagesDisadvantagesCultureRatingVisibility()),
|
||||
[dispatch],
|
||||
)
|
||||
@@ -42,16 +56,95 @@ export const Disadvantages: FC = () => {
|
||||
const canRemove = useAppSelector(selectCanRemove)
|
||||
const isInCharacterCreation = useAppSelector(selectIsInCharacterCreation)
|
||||
|
||||
const [filterText, setFilterText] = useState("")
|
||||
const [activeFilterText, setActiveFilterText] = useState("")
|
||||
const [inactiveFilterText, setInactiveFilterText] = useState("")
|
||||
|
||||
const visibleInactiveDisadvantages = useAppSelector(selectVisibleInactiveDisadvantages)
|
||||
const visibleActiveDisadvantages = useAppSelector(selectVisibleActiveDisadvantages)
|
||||
|
||||
const inactiveList = useMemo(
|
||||
() =>
|
||||
filterAndSortDisplayed(
|
||||
visibleInactiveDisadvantages,
|
||||
inactiveFilterText,
|
||||
translateMap,
|
||||
localeCompare,
|
||||
),
|
||||
[visibleInactiveDisadvantages, inactiveFilterText, translateMap, localeCompare],
|
||||
)
|
||||
|
||||
const activeList = useMemo(
|
||||
() =>
|
||||
filterAndSortDisplayed(
|
||||
visibleActiveDisadvantages,
|
||||
activeFilterText,
|
||||
translateMap,
|
||||
localeCompare,
|
||||
),
|
||||
[visibleActiveDisadvantages, activeFilterText, translateMap, localeCompare],
|
||||
)
|
||||
|
||||
const { isOpen: isSlideinVisible, open: openSlidein, close: closeSlidein } = useModalState()
|
||||
|
||||
return (
|
||||
<Page id="disadvantages">
|
||||
<Slidein isOpen={isSlideinVisible} close={closeSlidein} className="adding-disadvantages">
|
||||
<Options>
|
||||
<TextField
|
||||
value={inactiveFilterText}
|
||||
onChange={setInactiveFilterText}
|
||||
hint={translate("Search")}
|
||||
/>
|
||||
<Grid size="medium">
|
||||
<Checkbox
|
||||
checked={cultureRatingVisibility}
|
||||
onClick={handleSwitchCultureRatingVisibility}
|
||||
>
|
||||
{translate("Common Disadvantages")}
|
||||
</Checkbox>
|
||||
{cultureRatingVisibility ? <RecommendedReference strongly /> : null}
|
||||
</Grid>
|
||||
{isInCharacterCreation && <AdventurePointsSpent />}
|
||||
{/* <Checkbox checked={enableActiveItemHints} onClick={switchActiveItemHints}>
|
||||
{translate(staticData)("general.filters.showactivatedentries")}
|
||||
</Checkbox> */}
|
||||
</Options>
|
||||
<Main classOnly>
|
||||
<ListHeader>
|
||||
<ListHeaderTag className="name">{translate("Name")}</ListHeaderTag>
|
||||
<ListHeaderTag className="cost" hint={translate("Adventure Points")}>
|
||||
{translate("AP")}
|
||||
</ListHeaderTag>
|
||||
<ListHeaderTag className="btn-placeholder" />
|
||||
<ListHeaderTag className="btn-placeholder" />
|
||||
</ListHeader>
|
||||
<Scroll>
|
||||
{inactiveList.length > 0 ? (
|
||||
<List>
|
||||
{inactiveList.map(x => (
|
||||
<InactiveDisadvantagesListItem
|
||||
key={`disadvantage-${x.static.id}`}
|
||||
disadvantage={x}
|
||||
/>
|
||||
))}
|
||||
</List>
|
||||
) : (
|
||||
<ListPlaceholder type="inactiveDisadvantages" message={translate("No Results")} />
|
||||
)}
|
||||
</Scroll>
|
||||
</Main>
|
||||
<InlineLibrary />
|
||||
</Slidein>
|
||||
<Options>
|
||||
<TextField value={filterText} onChange={setFilterText} hint={translate("Search")} />
|
||||
<Button>{translate("Add")}</Button>
|
||||
<TextField
|
||||
value={activeFilterText}
|
||||
onChange={setActiveFilterText}
|
||||
hint={translate("Search")}
|
||||
/>
|
||||
<Button onClick={openSlidein}>{translate("Add")}</Button>
|
||||
<Grid size="medium">
|
||||
<Checkbox checked={cultureRatingVisibility} onClick={handlSwitchCultureRatingVisibility}>
|
||||
{translate("disadvantages.filters.commondisadvantages")}
|
||||
<Checkbox checked={cultureRatingVisibility} onClick={handleSwitchCultureRatingVisibility}>
|
||||
{translate("Common Disadvantages")}
|
||||
</Checkbox>
|
||||
{cultureRatingVisibility ? <RecommendedReference strongly /> : null}
|
||||
</Grid>
|
||||
@@ -59,20 +152,23 @@ export const Disadvantages: FC = () => {
|
||||
</Options>
|
||||
<Main>
|
||||
<ListHeader>
|
||||
<ListHeaderTag className="name">
|
||||
{translate("advantagesdisadvantages.header.name")}
|
||||
</ListHeaderTag>
|
||||
<ListHeaderTag
|
||||
className="cost"
|
||||
hint={translate("advantagesdisadvantages.header.adventurepoints.tooltip")}
|
||||
>
|
||||
{translate("advantagesdisadvantages.header.adventurepoints")}
|
||||
<ListHeaderTag className="name">{translate("Name")}</ListHeaderTag>
|
||||
<ListHeaderTag className="cost" hint={translate("Adventure Points")}>
|
||||
{translate("AP")}
|
||||
</ListHeaderTag>
|
||||
{canRemove && <ListHeaderTag className="btn-placeholder" />}
|
||||
<ListHeaderTag className="btn-placeholder" />
|
||||
</ListHeader>
|
||||
<Scroll stable>
|
||||
<ListPlaceholder type="disadvantages" message={translate("No Results")} />
|
||||
{activeList.length > 0 ? (
|
||||
<List>
|
||||
{activeList.map(x => (
|
||||
<ActiveDisadvantagesListItem key={`disadvantage-${x.static.id}`} disadvantage={x} />
|
||||
))}
|
||||
</List>
|
||||
) : (
|
||||
<ListPlaceholder type="disadvantages" message={translate("No Results")} />
|
||||
)}
|
||||
</Scroll>
|
||||
</Main>
|
||||
<InlineLibrary />
|
||||
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
import { FC } from "react"
|
||||
import { DisplayedInactiveAdvantage } from "../../../../../shared/domain/activatable/advantagesInactive.ts"
|
||||
import { getCreateIdentifierObject } from "../../../../../shared/domain/identifier.ts"
|
||||
import { useInactiveAdvantageAndDisadvantageActions } from "../../../../hooks/activatableActions.ts"
|
||||
import { addAdvantage } from "../../../../slices/advantagesSlice.ts"
|
||||
import { InactiveActivatablesListItem } from "../activatable/InactiveActivatableListItem.tsx"
|
||||
|
||||
type Props = {
|
||||
advantage: DisplayedInactiveAdvantage
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays an advantage that is currently inactive.
|
||||
*/
|
||||
export const InactiveAdvantagesListItem: FC<Props> = props => {
|
||||
const { advantage } = props
|
||||
const { handleAdd } = useInactiveAdvantageAndDisadvantageActions("advantage", addAdvantage)
|
||||
|
||||
return (
|
||||
<InactiveActivatablesListItem
|
||||
activatable={advantage}
|
||||
add={handleAdd}
|
||||
createActivatableIdentifierObject={getCreateIdentifierObject("Advantage")}
|
||||
/>
|
||||
)
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
import { FC } from "react"
|
||||
import { DisplayedInactiveDisadvantage } from "../../../../../shared/domain/activatable/disadvantagesInactive.ts"
|
||||
import { getCreateIdentifierObject } from "../../../../../shared/domain/identifier.ts"
|
||||
import { useInactiveAdvantageAndDisadvantageActions } from "../../../../hooks/activatableActions.ts"
|
||||
import { addDisadvantage } from "../../../../slices/disadvantagesSlice.ts"
|
||||
import { InactiveActivatablesListItem } from "../activatable/InactiveActivatableListItem.tsx"
|
||||
|
||||
type Props = {
|
||||
disadvantage: DisplayedInactiveDisadvantage
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays an disadvantage that is currently inactive.
|
||||
*/
|
||||
export const InactiveDisadvantagesListItem: FC<Props> = props => {
|
||||
const { disadvantage } = props
|
||||
const { handleAdd } = useInactiveAdvantageAndDisadvantageActions("disadvantage", addDisadvantage)
|
||||
|
||||
return (
|
||||
<InactiveActivatablesListItem
|
||||
activatable={disadvantage}
|
||||
add={handleAdd}
|
||||
createActivatableIdentifierObject={getCreateIdentifierObject("Disadvantage")}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -58,17 +58,10 @@ export const SpecialAbilities: FC = () => {
|
||||
</Options>
|
||||
<Main>
|
||||
<ListHeader>
|
||||
<ListHeaderTag className="name">
|
||||
{translate("specialabilities.header.name")}
|
||||
</ListHeaderTag>
|
||||
<ListHeaderTag className="group">
|
||||
{translate("specialabilities.header.group")}
|
||||
</ListHeaderTag>
|
||||
<ListHeaderTag
|
||||
className="cost"
|
||||
hint={translate("specialabilities.header.adventurepoints.tooltip")}
|
||||
>
|
||||
{translate("specialabilities.header.adventurepoints")}
|
||||
<ListHeaderTag className="name">{translate("Name")}</ListHeaderTag>
|
||||
<ListHeaderTag className="group">{translate("Group")}</ListHeaderTag>
|
||||
<ListHeaderTag className="cost" hint={translate("Adventure Points")}>
|
||||
{translate("AP")}
|
||||
</ListHeaderTag>
|
||||
{canRemove ? <ListHeaderTag className="btn-placeholder" /> : null}
|
||||
<ListHeaderTag className="btn-placeholder" />
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { createSelector } from "@reduxjs/toolkit"
|
||||
import { ResolvedSelectOption } from "optolith-database-schema/cache/activatableSelectOptions"
|
||||
import { ActivatableIdentifier } from "optolith-database-schema/types/_IdentifierGroup"
|
||||
import { getSelectOptionsFromCacheById } from "../../shared/domain/database.ts"
|
||||
import { selectDatabase } from "../slices/databaseSlice.ts"
|
||||
|
||||
/**
|
||||
* Selects a function that returns the select options for a given activatable
|
||||
*identifier.
|
||||
*/
|
||||
export const selectGetSelectOptionsById = createSelector(
|
||||
selectDatabase,
|
||||
database =>
|
||||
(id: ActivatableIdentifier): ResolvedSelectOption[] | undefined =>
|
||||
getSelectOptionsFromCacheById(database.cache.activatableSelectOptions, id),
|
||||
)
|
||||
@@ -0,0 +1,426 @@
|
||||
import { createSelector } from "@reduxjs/toolkit"
|
||||
import {
|
||||
countActivations,
|
||||
isTinyActivatableActive,
|
||||
} from "../../shared/domain/activatable/activatableEntry.ts"
|
||||
import {
|
||||
DisplayedActiveAdvantage,
|
||||
getActiveAdvantages,
|
||||
} from "../../shared/domain/activatable/advantagesActive.ts"
|
||||
import {
|
||||
DisplayedInactiveAdvantage,
|
||||
getInactiveAdvantages,
|
||||
} from "../../shared/domain/activatable/advantagesInactive.ts"
|
||||
import { getActiveDisadvantages } from "../../shared/domain/activatable/disadvantagesActive.ts"
|
||||
import { getInactiveDisadvantages } from "../../shared/domain/activatable/disadvantagesInactive.ts"
|
||||
import {
|
||||
isRatedActive,
|
||||
isRatedWithEnhancementsActive,
|
||||
} from "../../shared/domain/rated/ratedEntry.ts"
|
||||
import { count, sumWith } from "../../shared/utils/array.ts"
|
||||
import { selectGetSelectOptionsById } from "./activatableSelectors.ts"
|
||||
import { SelectAll, SelectGetById } from "./basicCapabilitySelectors.ts"
|
||||
import { selectFilterApplyingActivatableDependencies } from "./dependencySelectors.ts"
|
||||
import { selectStartExperienceLevel } from "./experienceLevelSelectors.ts"
|
||||
import { selectCapabilitiesForPrerequisitesOfAdvantageOrDisadvantage } from "./prerequisiteSelectors.ts"
|
||||
import { selectIsEntryAvailable } from "./publicationSelectors.ts"
|
||||
|
||||
/**
|
||||
* Returns all all advantages with their corresponding dynamic entries,
|
||||
* extended by whether the entry can be activated.
|
||||
*/
|
||||
export const selectVisibleInactiveAdvantages = createSelector(
|
||||
SelectAll.Static.Advantages,
|
||||
SelectGetById.Dynamic.Advantage,
|
||||
selectIsEntryAvailable,
|
||||
selectCapabilitiesForPrerequisitesOfAdvantageOrDisadvantage,
|
||||
selectGetSelectOptionsById,
|
||||
selectFilterApplyingActivatableDependencies,
|
||||
createSelector(SelectAll.Dynamic.Sermons, dynamicSermons =>
|
||||
sumWith(dynamicSermons, countActivations),
|
||||
),
|
||||
createSelector(SelectAll.Dynamic.Visions, dynamicVisions =>
|
||||
sumWith(dynamicVisions, countActivations),
|
||||
),
|
||||
createSelector(
|
||||
SelectAll.Dynamic.Spells,
|
||||
SelectAll.Dynamic.Rituals,
|
||||
(dynamicSpells, dynamicRituals) =>
|
||||
count(dynamicSpells, isRatedWithEnhancementsActive) +
|
||||
count(dynamicRituals, isRatedWithEnhancementsActive),
|
||||
),
|
||||
SelectGetById.Dynamic.Disadvantage,
|
||||
(
|
||||
staticAdvantages,
|
||||
getDynamicAdvantageById,
|
||||
isEntryAvailable,
|
||||
prerequisiteCapabilities,
|
||||
getSelectOptionsById,
|
||||
filterApplyingDependencies,
|
||||
activeSermonsCount,
|
||||
activeVisionsCount,
|
||||
activeSpellworksCount,
|
||||
getDynamicDisadvantageById,
|
||||
): DisplayedInactiveAdvantage[] =>
|
||||
getInactiveAdvantages(
|
||||
staticAdvantages,
|
||||
getDynamicAdvantageById,
|
||||
isEntryAvailable,
|
||||
prerequisiteCapabilities,
|
||||
getSelectOptionsById,
|
||||
filterApplyingDependencies,
|
||||
{
|
||||
activeSermonsCount,
|
||||
activeVisionsCount,
|
||||
activeSpellworksCount,
|
||||
getDynamicAdvantageById,
|
||||
getDynamicDisadvantageById,
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
/**
|
||||
* Returns all all disadvantages with their corresponding dynamic entries,
|
||||
* extended by whether the entry can be activated.
|
||||
*/
|
||||
export const selectVisibleInactiveDisadvantages = createSelector(
|
||||
SelectAll.Static.Disadvantages,
|
||||
SelectGetById.Dynamic.Disadvantage,
|
||||
selectIsEntryAvailable,
|
||||
selectCapabilitiesForPrerequisitesOfAdvantageOrDisadvantage,
|
||||
selectGetSelectOptionsById,
|
||||
selectFilterApplyingActivatableDependencies,
|
||||
createSelector(SelectAll.Dynamic.Sermons, dynamicSermons =>
|
||||
sumWith(dynamicSermons, countActivations),
|
||||
),
|
||||
createSelector(SelectAll.Dynamic.Visions, dynamicVisions =>
|
||||
sumWith(dynamicVisions, countActivations),
|
||||
),
|
||||
createSelector(
|
||||
SelectAll.Dynamic.Spells,
|
||||
SelectAll.Dynamic.Rituals,
|
||||
(dynamicSpells, dynamicRituals) =>
|
||||
count(dynamicSpells, isRatedWithEnhancementsActive) +
|
||||
count(dynamicRituals, isRatedWithEnhancementsActive),
|
||||
),
|
||||
SelectGetById.Dynamic.Advantage,
|
||||
(
|
||||
staticDisadvantages,
|
||||
getDynamicDisadvantageById,
|
||||
isEntryAvailable,
|
||||
prerequisiteCapabilities,
|
||||
getSelectOptionsById,
|
||||
filterApplyingDependencies,
|
||||
activeSermonsCount,
|
||||
activeVisionsCount,
|
||||
activeSpellworksCount,
|
||||
getDynamicAdvantageById,
|
||||
): DisplayedInactiveAdvantage[] =>
|
||||
getInactiveDisadvantages(
|
||||
staticDisadvantages,
|
||||
getDynamicDisadvantageById,
|
||||
isEntryAvailable,
|
||||
prerequisiteCapabilities,
|
||||
getSelectOptionsById,
|
||||
filterApplyingDependencies,
|
||||
{
|
||||
activeSermonsCount,
|
||||
activeVisionsCount,
|
||||
activeSpellworksCount,
|
||||
getDynamicAdvantageById,
|
||||
getDynamicDisadvantageById,
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
/**
|
||||
* Returns all all advantages with their corresponding dynamic entries,
|
||||
* extended by which activation/instance of the entry it represents.
|
||||
*/
|
||||
export const selectVisibleActiveAdvantages = createSelector(
|
||||
SelectGetById.Static.Advantage,
|
||||
SelectAll.Dynamic.Advantages,
|
||||
selectCapabilitiesForPrerequisitesOfAdvantageOrDisadvantage,
|
||||
selectGetSelectOptionsById,
|
||||
selectFilterApplyingActivatableDependencies,
|
||||
createSelector(SelectAll.Dynamic.Sermons, dynamicSermons =>
|
||||
sumWith(dynamicSermons, countActivations),
|
||||
),
|
||||
createSelector(SelectAll.Dynamic.Visions, dynamicVisions =>
|
||||
sumWith(dynamicVisions, countActivations),
|
||||
),
|
||||
createSelector(SelectAll.Dynamic.Cantrips, dynamicCantrips =>
|
||||
count(dynamicCantrips, isTinyActivatableActive),
|
||||
),
|
||||
createSelector(
|
||||
SelectAll.Dynamic.Spells,
|
||||
SelectAll.Dynamic.Rituals,
|
||||
(dynamicSpells, dynamicRituals) =>
|
||||
count(dynamicSpells, isRatedWithEnhancementsActive) +
|
||||
count(dynamicRituals, isRatedWithEnhancementsActive),
|
||||
),
|
||||
createSelector(
|
||||
SelectAll.Dynamic.Curses,
|
||||
SelectAll.Dynamic.ElvenMagicalSongs,
|
||||
SelectAll.Dynamic.DominationRituals,
|
||||
SelectAll.Dynamic.MagicalDances,
|
||||
SelectAll.Dynamic.MagicalMelodies,
|
||||
SelectAll.Dynamic.JesterTricks,
|
||||
SelectAll.Dynamic.AnimistPowers,
|
||||
SelectAll.Dynamic.GeodeRituals,
|
||||
SelectAll.Dynamic.ZibiljaRituals,
|
||||
(
|
||||
dynamicCurses,
|
||||
dynamicElvenMagicalSongs,
|
||||
dynamicDominationRituals,
|
||||
dynamicMagicalDances,
|
||||
dynamicMagicalMelodies,
|
||||
dynamicJesterTricks,
|
||||
dynamicAnimistPowers,
|
||||
dynamicGeodeRituals,
|
||||
dynamicZibiljaRituals,
|
||||
) =>
|
||||
count(dynamicCurses, isRatedActive) +
|
||||
count(dynamicElvenMagicalSongs, isRatedActive) +
|
||||
count(dynamicDominationRituals, isRatedActive) +
|
||||
count(dynamicMagicalDances, isRatedActive) +
|
||||
count(dynamicMagicalMelodies, isRatedActive) +
|
||||
count(dynamicJesterTricks, isRatedActive) +
|
||||
count(dynamicAnimistPowers, isRatedActive) +
|
||||
count(dynamicGeodeRituals, isRatedActive) +
|
||||
count(dynamicZibiljaRituals, isRatedActive),
|
||||
),
|
||||
createSelector(SelectAll.Dynamic.Blessings, dynamicBlessings =>
|
||||
count(dynamicBlessings, isTinyActivatableActive),
|
||||
),
|
||||
createSelector(SelectAll.Dynamic.LiturgicalChants, dynamicLiturgicalChants =>
|
||||
count(dynamicLiturgicalChants, isRatedWithEnhancementsActive),
|
||||
),
|
||||
createSelector(SelectAll.Dynamic.Ceremonies, dynamicCeremonies =>
|
||||
count(dynamicCeremonies, isRatedWithEnhancementsActive),
|
||||
),
|
||||
SelectGetById.Dynamic.Skill,
|
||||
SelectGetById.Dynamic.LiturgicalChant,
|
||||
SelectGetById.Dynamic.Ceremony,
|
||||
SelectGetById.Dynamic.Spell,
|
||||
SelectGetById.Dynamic.Ritual,
|
||||
SelectGetById.Dynamic.CloseCombatTechnique,
|
||||
SelectGetById.Dynamic.RangedCombatTechnique,
|
||||
selectStartExperienceLevel,
|
||||
(
|
||||
getStaticAdvantageById,
|
||||
dynamicAdvantages,
|
||||
prerequisiteCapabilities,
|
||||
getSelectOptionsById,
|
||||
filterApplyingDependencies,
|
||||
activeSermonsCount,
|
||||
activeVisionsCount,
|
||||
activeCantripsCount,
|
||||
activeSpellworksCount,
|
||||
activeMagicalActionsCount,
|
||||
activeBlessingsCount,
|
||||
activeLiturgicalChantsCount,
|
||||
activeCeremoniesCount,
|
||||
getDynamicSkillById,
|
||||
getDynamicLiturgicalChantById,
|
||||
getDynamicCeremonyById,
|
||||
getDynamicSpellById,
|
||||
getDynamicRitualById,
|
||||
getDynamicCloseCombatTechniqueById,
|
||||
getDynamicRangedCombatTechniqueById,
|
||||
startExperienceLevel,
|
||||
): DisplayedActiveAdvantage[] =>
|
||||
getActiveAdvantages(
|
||||
getStaticAdvantageById,
|
||||
dynamicAdvantages,
|
||||
prerequisiteCapabilities,
|
||||
getSelectOptionsById,
|
||||
filterApplyingDependencies,
|
||||
{
|
||||
startExperienceLevel,
|
||||
activeSermonsCount,
|
||||
activeVisionsCount,
|
||||
activeCantripsCount,
|
||||
activeSpellworksCount,
|
||||
activeMagicalActionsCount,
|
||||
activeBlessingsCount,
|
||||
activeLiturgicalChantsCount,
|
||||
activeCeremoniesCount,
|
||||
getDynamicSkillById,
|
||||
getDynamicLiturgicalChantById,
|
||||
getDynamicCeremonyById,
|
||||
getDynamicSpellById,
|
||||
getDynamicRitualById,
|
||||
getDynamicCloseCombatTechniqueById,
|
||||
getDynamicRangedCombatTechniqueById,
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
/**
|
||||
* Returns all all disadvantages with their corresponding dynamic entries,
|
||||
* extended by which activation/instance of the entry it represents.
|
||||
*/
|
||||
export const selectVisibleActiveDisadvantages = createSelector(
|
||||
SelectGetById.Static.Disadvantage,
|
||||
SelectAll.Dynamic.Disadvantages,
|
||||
selectCapabilitiesForPrerequisitesOfAdvantageOrDisadvantage,
|
||||
selectGetSelectOptionsById,
|
||||
selectFilterApplyingActivatableDependencies,
|
||||
createSelector(SelectAll.Dynamic.Sermons, dynamicSermons =>
|
||||
sumWith(dynamicSermons, countActivations),
|
||||
),
|
||||
createSelector(SelectAll.Dynamic.Visions, dynamicVisions =>
|
||||
sumWith(dynamicVisions, countActivations),
|
||||
),
|
||||
createSelector(SelectAll.Dynamic.Cantrips, dynamicCantrips =>
|
||||
count(dynamicCantrips, isTinyActivatableActive),
|
||||
),
|
||||
createSelector(
|
||||
SelectAll.Dynamic.Spells,
|
||||
SelectAll.Dynamic.Rituals,
|
||||
(dynamicSpells, dynamicRituals) =>
|
||||
count(dynamicSpells, isRatedWithEnhancementsActive) +
|
||||
count(dynamicRituals, isRatedWithEnhancementsActive),
|
||||
),
|
||||
createSelector(
|
||||
SelectAll.Dynamic.Curses,
|
||||
SelectAll.Dynamic.ElvenMagicalSongs,
|
||||
SelectAll.Dynamic.DominationRituals,
|
||||
SelectAll.Dynamic.MagicalDances,
|
||||
SelectAll.Dynamic.MagicalMelodies,
|
||||
SelectAll.Dynamic.JesterTricks,
|
||||
SelectAll.Dynamic.AnimistPowers,
|
||||
SelectAll.Dynamic.GeodeRituals,
|
||||
SelectAll.Dynamic.ZibiljaRituals,
|
||||
(
|
||||
dynamicCurses,
|
||||
dynamicElvenMagicalSongs,
|
||||
dynamicDominationRituals,
|
||||
dynamicMagicalDances,
|
||||
dynamicMagicalMelodies,
|
||||
dynamicJesterTricks,
|
||||
dynamicAnimistPowers,
|
||||
dynamicGeodeRituals,
|
||||
dynamicZibiljaRituals,
|
||||
) =>
|
||||
count(dynamicCurses, isRatedActive) +
|
||||
count(dynamicElvenMagicalSongs, isRatedActive) +
|
||||
count(dynamicDominationRituals, isRatedActive) +
|
||||
count(dynamicMagicalDances, isRatedActive) +
|
||||
count(dynamicMagicalMelodies, isRatedActive) +
|
||||
count(dynamicJesterTricks, isRatedActive) +
|
||||
count(dynamicAnimistPowers, isRatedActive) +
|
||||
count(dynamicGeodeRituals, isRatedActive) +
|
||||
count(dynamicZibiljaRituals, isRatedActive),
|
||||
),
|
||||
createSelector(SelectAll.Dynamic.Blessings, dynamicBlessings =>
|
||||
count(dynamicBlessings, isTinyActivatableActive),
|
||||
),
|
||||
createSelector(SelectAll.Dynamic.LiturgicalChants, dynamicLiturgicalChants =>
|
||||
count(dynamicLiturgicalChants, isRatedWithEnhancementsActive),
|
||||
),
|
||||
createSelector(SelectAll.Dynamic.Ceremonies, dynamicCeremonies =>
|
||||
count(dynamicCeremonies, isRatedWithEnhancementsActive),
|
||||
),
|
||||
SelectGetById.Dynamic.Skill,
|
||||
SelectGetById.Dynamic.LiturgicalChant,
|
||||
SelectGetById.Dynamic.Ceremony,
|
||||
SelectGetById.Dynamic.Spell,
|
||||
SelectGetById.Dynamic.Ritual,
|
||||
SelectGetById.Dynamic.CloseCombatTechnique,
|
||||
SelectGetById.Dynamic.RangedCombatTechnique,
|
||||
selectStartExperienceLevel,
|
||||
(
|
||||
getStaticDisadvantageById,
|
||||
dynamicDisadvantages,
|
||||
prerequisiteCapabilities,
|
||||
getSelectOptionsById,
|
||||
filterApplyingDependencies,
|
||||
activeSermonsCount,
|
||||
activeVisionsCount,
|
||||
activeCantripsCount,
|
||||
activeSpellworksCount,
|
||||
activeMagicalActionsCount,
|
||||
activeBlessingsCount,
|
||||
activeLiturgicalChantsCount,
|
||||
activeCeremoniesCount,
|
||||
getDynamicSkillById,
|
||||
getDynamicLiturgicalChantById,
|
||||
getDynamicCeremonyById,
|
||||
getDynamicSpellById,
|
||||
getDynamicRitualById,
|
||||
getDynamicCloseCombatTechniqueById,
|
||||
getDynamicRangedCombatTechniqueById,
|
||||
startExperienceLevel,
|
||||
): DisplayedActiveAdvantage[] =>
|
||||
getActiveDisadvantages(
|
||||
getStaticDisadvantageById,
|
||||
dynamicDisadvantages,
|
||||
prerequisiteCapabilities,
|
||||
getSelectOptionsById,
|
||||
filterApplyingDependencies,
|
||||
{
|
||||
startExperienceLevel,
|
||||
activeSermonsCount,
|
||||
activeVisionsCount,
|
||||
activeCantripsCount,
|
||||
activeSpellworksCount,
|
||||
activeMagicalActionsCount,
|
||||
activeBlessingsCount,
|
||||
activeLiturgicalChantsCount,
|
||||
activeCeremoniesCount,
|
||||
getDynamicSkillById,
|
||||
getDynamicLiturgicalChantById,
|
||||
getDynamicCeremonyById,
|
||||
getDynamicSpellById,
|
||||
getDynamicRitualById,
|
||||
getDynamicCloseCombatTechniqueById,
|
||||
getDynamicRangedCombatTechniqueById,
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
// export const getAdvantagesRating = createMaybeSelector (
|
||||
// getRace,
|
||||
// getCulture,
|
||||
// getProfession,
|
||||
// (mrace, mculture, mprofession) =>
|
||||
// liftM3 ((r: Record<Race>) => (c: Record<Culture>) => (p: Record<Profession>) =>
|
||||
// pipe_ (
|
||||
// OrderedMap.empty as RatingMap,
|
||||
|
||||
// flip (foldr (insertRating (EntryRating.Common)))
|
||||
// (Race.A.commonAdvantages (r)),
|
||||
|
||||
// flip (foldr (insertRating (EntryRating.Uncommon)))
|
||||
// (Race.A.uncommonAdvantages (r)),
|
||||
|
||||
// flip (foldr (insertRating (EntryRating.Common)))
|
||||
// (Culture.A.commonAdvantages (c)),
|
||||
|
||||
// flip (foldr (insertRating (EntryRating.Uncommon)))
|
||||
// (Culture.A.uncommonAdvantages (c)),
|
||||
|
||||
// flip (foldr (insertRating (EntryRating.Common)))
|
||||
// (Profession.A.suggestedAdvantages (p)),
|
||||
|
||||
// flip (foldr (insertRating (EntryRating.Uncommon)))
|
||||
// (Profession.A.unsuitableAdvantages (p)),
|
||||
|
||||
// flip (foldr (insertRating (EntryRating.Essential)))
|
||||
// (Race.A.stronglyRecommendedAdvantages (r))
|
||||
// ))
|
||||
// (mrace)
|
||||
// (mculture)
|
||||
// (mprofession)
|
||||
// )
|
||||
|
||||
// export const getAdvantagesForSheet = createMaybeSelector (
|
||||
// getActiveForView (Category.ADVANTAGES),
|
||||
// ident
|
||||
// )
|
||||
|
||||
// export const getAdvantagesForEditMap = getActiveForViewMap (Category.ADVANTAGES)
|
||||
|
||||
// export const getAdvantagesForEdit = mapCurrentHero (getAdvantagesForEditMap)
|
||||
@@ -3,6 +3,11 @@ import {
|
||||
TinyActivatable,
|
||||
isTinyActivatableActive,
|
||||
} from "../../shared/domain/activatable/activatableEntry.ts"
|
||||
import {
|
||||
AdventurePointsCache,
|
||||
addAdventurePointsCaches,
|
||||
emptyAdventurePointsCache,
|
||||
} from "../../shared/domain/adventurePoints/cache.ts"
|
||||
import {
|
||||
ImprovementCost,
|
||||
adventurePointsForRange,
|
||||
@@ -12,11 +17,13 @@ import { count } from "../../shared/utils/array.ts"
|
||||
import {
|
||||
selectCurrentCharacter,
|
||||
selectDerivedCharacteristics,
|
||||
selectDynamicAdvantages,
|
||||
selectDynamicAnimistPowers,
|
||||
selectDynamicAttributes,
|
||||
selectDynamicCeremonies,
|
||||
selectDynamicCloseCombatTechniques,
|
||||
selectDynamicCurses,
|
||||
selectDynamicDisadvantages,
|
||||
selectDynamicDominationRituals,
|
||||
selectDynamicElvenMagicalSongs,
|
||||
selectDynamicGeodeRituals,
|
||||
@@ -27,21 +34,14 @@ import {
|
||||
selectDynamicRangedCombatTechniques,
|
||||
selectDynamicRituals,
|
||||
selectDynamicSkills,
|
||||
selectDynamicSpecialAbilities,
|
||||
selectDynamicSpells,
|
||||
selectDynamicZibiljaRituals,
|
||||
selectTotalAdventurePoints,
|
||||
} from "../slices/characterSlice.ts"
|
||||
import { selectMagicalAndBlessedAdvantagesAndDisadvantagesCache } from "../slices/databaseSlice.ts"
|
||||
import { SelectAll } from "./basicCapabilitySelectors.ts"
|
||||
|
||||
/**
|
||||
* Adventure Points can either be spent from the main pool of adventure points
|
||||
* or from the pool bound to a specific entry.
|
||||
*/
|
||||
export type SpentAdventurePoints = {
|
||||
general: number
|
||||
bound: number
|
||||
}
|
||||
|
||||
const sumRatedMaps = (
|
||||
...ratedMaps: (Record<number, { cachedAdventurePoints: RatedAdventurePointsCache }> | undefined)[]
|
||||
): SpentAdventurePoints =>
|
||||
@@ -125,60 +125,102 @@ export const selectAdventurePointsSpentOnBlessings = createSelector(
|
||||
sumTinyActivatables,
|
||||
)
|
||||
|
||||
const sumActivatableMaps = (
|
||||
...activatableMaps: Record<number, { cachedAdventurePoints: AdventurePointsCache }>[]
|
||||
): AdventurePointsCache =>
|
||||
activatableMaps
|
||||
.flatMap(activatableMap => Object.values(activatableMap ?? {}))
|
||||
.reduce(
|
||||
(acc, rated) => addAdventurePointsCaches(acc, rated.cachedAdventurePoints),
|
||||
emptyAdventurePointsCache,
|
||||
)
|
||||
|
||||
const sumActivatables = (
|
||||
activatables: { cachedAdventurePoints: AdventurePointsCache }[],
|
||||
): AdventurePointsCache =>
|
||||
activatables.reduce(
|
||||
(acc, rated) => addAdventurePointsCaches(acc, rated.cachedAdventurePoints),
|
||||
emptyAdventurePointsCache,
|
||||
)
|
||||
|
||||
/**
|
||||
* Returns the adventure points spent on advantages.
|
||||
*/
|
||||
export const selectAdventurePointsSpentOnAdvantages = createSelector(
|
||||
selectCurrentCharacter,
|
||||
(): SpentAdventurePoints => ({ general: 0, bound: 0 }),
|
||||
selectDynamicAdvantages,
|
||||
(dynamicAdvantages): AdventurePointsCache => sumActivatableMaps(dynamicAdvantages ?? {}),
|
||||
)
|
||||
|
||||
/**
|
||||
* Returns the adventure points spent on magical advantages.
|
||||
*/
|
||||
export const selectAdventurePointsSpentOnMagicalAdvantages = createSelector(
|
||||
selectCurrentCharacter,
|
||||
(): SpentAdventurePoints => ({ general: 0, bound: 0 }),
|
||||
SelectAll.Dynamic.Advantages,
|
||||
selectMagicalAndBlessedAdvantagesAndDisadvantagesCache,
|
||||
(dynamicAdvantages, cache): AdventurePointsCache =>
|
||||
sumActivatables(
|
||||
dynamicAdvantages.filter(({ id }) => cache.advantages.magical.ids.includes(id)),
|
||||
),
|
||||
)
|
||||
|
||||
/**
|
||||
* Returns the adventure points spent on blessed advantages.
|
||||
*/
|
||||
export const selectAdventurePointsSpentOnBlessedAdvantages = createSelector(
|
||||
selectCurrentCharacter,
|
||||
(): SpentAdventurePoints => ({ general: 0, bound: 0 }),
|
||||
SelectAll.Dynamic.Advantages,
|
||||
selectMagicalAndBlessedAdvantagesAndDisadvantagesCache,
|
||||
(dynamicAdvantages, cache): AdventurePointsCache =>
|
||||
sumActivatables(
|
||||
dynamicAdvantages.filter(({ id }) => cache.advantages.blessed.ids.includes(id)),
|
||||
),
|
||||
)
|
||||
|
||||
/**
|
||||
* Returns the adventure points spent on disadvantages.
|
||||
*/
|
||||
export const selectAdventurePointsSpentOnDisadvantages = createSelector(
|
||||
selectCurrentCharacter,
|
||||
(): SpentAdventurePoints => ({ general: 0, bound: 0 }),
|
||||
selectDynamicDisadvantages,
|
||||
(dynamicDisadvantages): AdventurePointsCache => {
|
||||
const { general, bound } = sumActivatableMaps(dynamicDisadvantages ?? {})
|
||||
return { general: -general, bound: -bound }
|
||||
},
|
||||
)
|
||||
|
||||
/**
|
||||
* Returns the adventure points spent on magical disadvantages.
|
||||
*/
|
||||
export const selectAdventurePointsSpentOnMagicalDisadvantages = createSelector(
|
||||
selectCurrentCharacter,
|
||||
(): SpentAdventurePoints => ({ general: 0, bound: 0 }),
|
||||
SelectAll.Dynamic.Disadvantages,
|
||||
selectMagicalAndBlessedAdvantagesAndDisadvantagesCache,
|
||||
(dynamicDisadvantages, cache): AdventurePointsCache => {
|
||||
const { general, bound } = sumActivatables(
|
||||
dynamicDisadvantages.filter(({ id }) => cache.disadvantages.magical.ids.includes(id)),
|
||||
)
|
||||
return { general: -general, bound: -bound }
|
||||
},
|
||||
)
|
||||
|
||||
/**
|
||||
* Returns the adventure points spent on blessed disadvantages.
|
||||
*/
|
||||
export const selectAdventurePointsSpentOnBlessedDisadvantages = createSelector(
|
||||
selectCurrentCharacter,
|
||||
(): SpentAdventurePoints => ({ general: 0, bound: 0 }),
|
||||
SelectAll.Dynamic.Disadvantages,
|
||||
selectMagicalAndBlessedAdvantagesAndDisadvantagesCache,
|
||||
(dynamicDisadvantages, cache): AdventurePointsCache => {
|
||||
const { general, bound } = sumActivatables(
|
||||
dynamicDisadvantages.filter(({ id }) => cache.disadvantages.blessed.ids.includes(id)),
|
||||
)
|
||||
return { general: -general, bound: -bound }
|
||||
},
|
||||
)
|
||||
|
||||
/**
|
||||
* Returns the adventure points spent on special abilities.
|
||||
*/
|
||||
export const selectAdventurePointsSpentOnSpecialAbilities = createSelector(
|
||||
selectCurrentCharacter,
|
||||
(): SpentAdventurePoints => ({ general: 0, bound: 0 }),
|
||||
selectDynamicSpecialAbilities,
|
||||
(dynamicSpecialAbilities): AdventurePointsCache =>
|
||||
sumActivatableMaps(...Object.values(dynamicSpecialAbilities ?? {})),
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -227,17 +269,13 @@ export const selectAdventurePointsSpent = createSelector(
|
||||
selectAdventurePointsSpentOnCantrips,
|
||||
selectAdventurePointsSpentOnBlessings,
|
||||
selectAdventurePointsSpentOnAdvantages,
|
||||
selectAdventurePointsSpentOnMagicalAdvantages,
|
||||
selectAdventurePointsSpentOnBlessedAdvantages,
|
||||
selectAdventurePointsSpentOnDisadvantages,
|
||||
selectAdventurePointsSpentOnMagicalDisadvantages,
|
||||
selectAdventurePointsSpentOnBlessedDisadvantages,
|
||||
selectAdventurePointsSpentOnSpecialAbilities,
|
||||
selectAdventurePointsSpentOnEnergies,
|
||||
selectAdventurePointsSpentOnRace,
|
||||
selectAdventurePointsSpentOnProfession,
|
||||
(...spentCategories): SpentAdventurePoints =>
|
||||
spentCategories.reduce<SpentAdventurePoints>(
|
||||
(...spentCategories): AdventurePointsCache =>
|
||||
spentCategories.reduce<AdventurePointsCache>(
|
||||
(acc, spentCategory) => ({
|
||||
general:
|
||||
acc.general +
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
/* eslint-disable @typescript-eslint/no-namespace */
|
||||
import { createSelector } from "@reduxjs/toolkit"
|
||||
import { All, GetById } from "../../shared/domain/getTypes.ts"
|
||||
import { All, GetById, Singleton } from "../../shared/domain/getTypes.ts"
|
||||
import {
|
||||
selectDynamicAdvancedCombatSpecialAbilities,
|
||||
selectDynamicAdvancedKarmaSpecialAbilities,
|
||||
@@ -76,6 +76,8 @@ import {
|
||||
selectDynamicWandEnchantments,
|
||||
selectDynamicWeaponEnchantments,
|
||||
selectDynamicZibiljaRituals,
|
||||
selectPact,
|
||||
selectSex,
|
||||
} from "../slices/characterSlice.ts"
|
||||
import { selectDatabase } from "../slices/databaseSlice.ts"
|
||||
|
||||
@@ -349,6 +351,14 @@ export namespace SelectGetById {
|
||||
}
|
||||
}
|
||||
|
||||
// prettier-ignore
|
||||
export namespace SelectSingleton {
|
||||
export namespace Dynamic {
|
||||
export const Pact = createSelector(selectPact, (pact): Singleton.Dynamic.Pact => pact)
|
||||
export const Sex = createSelector(selectSex, (sex): Singleton.Dynamic.Sex => sex)
|
||||
}
|
||||
}
|
||||
|
||||
// prettier-ignore
|
||||
export namespace SelectAll {
|
||||
export namespace Static {
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { createSelector } from "@reduxjs/toolkit"
|
||||
import { filterApplyingRatedDependencies } from "../../shared/domain/dependencies/filterApplyingDependencies.ts"
|
||||
import {
|
||||
filterApplyingActivatableDependencies,
|
||||
filterApplyingRatedDependencies,
|
||||
} from "../../shared/domain/dependencies/filterApplyingDependencies.ts"
|
||||
import { checkMultipleDisjunctionPartsAreValid } from "../../shared/domain/prerequisites/disjunctionPrerequisiteMultipartValidation.ts"
|
||||
import { checkPreconditions } from "../../shared/domain/prerequisites/fullPrerequisiteValidationForType.ts"
|
||||
import { selectDatabase } from "../slices/databaseSlice.ts"
|
||||
import * as PrerequisiteCaps from "./prerequisiteSelectors.ts"
|
||||
|
||||
@@ -91,6 +95,7 @@ export const selectFilterApplyingRatedDependencies = createSelector(
|
||||
getStaticMagicalTraditionById: id => database.magicalTraditions[id],
|
||||
getStaticBlessedTraditionById: id => database.blessedTraditions[id],
|
||||
getStaticPactGiftById: id => database.pactGifts[id],
|
||||
getStaticVampiricGiftById: id => database.vampiricGifts[id],
|
||||
getStaticSikaryanDrainSpecialAbilityById: id => database.sikaryanDrainSpecialAbilities[id],
|
||||
getStaticLycantropicGiftById: id => database.lycantropicGifts[id],
|
||||
getStaticSkillStyleSpecialAbilityById: id => database.skillStyleSpecialAbilities[id],
|
||||
@@ -108,6 +113,7 @@ export const selectFilterApplyingRatedDependencies = createSelector(
|
||||
getStaticChronicleEnchantmentById: id => database.chronicleEnchantments[id],
|
||||
getStaticKrallenkettenzauberById: id => database.krallenkettenzauber[id],
|
||||
getStaticTrinkhornzauberById: id => database.trinkhornzauber[id],
|
||||
getStaticMagicalSignById: id => database.magicalSigns[id],
|
||||
getStaticSpellById: id => database.spells[id],
|
||||
getStaticRitualById: id => database.rituals[id],
|
||||
getStaticLiturgicalChantById: id => database.liturgicalChants[id],
|
||||
@@ -134,5 +140,145 @@ export const selectFilterApplyingRatedDependencies = createSelector(
|
||||
...statePrerequisiteCapabilities,
|
||||
...textPrerequisiteCapabilities,
|
||||
}),
|
||||
ratedMinimumNumberPrerequisiteCapabilities,
|
||||
),
|
||||
)
|
||||
|
||||
/**
|
||||
* Selects a filter function to filter unapplying dependencies of activatable
|
||||
* entries.
|
||||
*/
|
||||
export const selectFilterApplyingActivatableDependencies = createSelector(
|
||||
selectDatabase,
|
||||
PrerequisiteCaps.selectCapabilitiesForActivatablePrerequisite,
|
||||
PrerequisiteCaps.selectCapabilitiesForAncestorBloodPrerequisite,
|
||||
PrerequisiteCaps.selectCapabilitiesForBlessedTraditionPrerequisite,
|
||||
PrerequisiteCaps.selectCapabilitiesForCommonSuggestedByRCPPrerequisite,
|
||||
PrerequisiteCaps.selectCapabilitiesForCulturePrerequisite,
|
||||
PrerequisiteCaps.selectCapabilitiesForExternalEnhancementPrerequisite,
|
||||
PrerequisiteCaps.selectCapabilitiesForInfluencePrerequisite,
|
||||
PrerequisiteCaps.selectCapabilitiesForMagicalTraditionPrerequisite,
|
||||
PrerequisiteCaps.selectCapabilitiesForPactPrerequisite,
|
||||
PrerequisiteCaps.selectCapabilitiesForPrimaryAttributePrerequisite,
|
||||
PrerequisiteCaps.selectCapabilitiesForPublicationPrerequisite,
|
||||
PrerequisiteCaps.selectCapabilitiesForRacePrerequisite,
|
||||
PrerequisiteCaps.selectCapabilitiesForRatedMinimumNumberPrerequisite,
|
||||
PrerequisiteCaps.selectCapabilitiesForRatedPrerequisite,
|
||||
PrerequisiteCaps.selectCapabilitiesForRatedSumPrerequisite,
|
||||
PrerequisiteCaps.selectCapabilitiesForRulePrerequisite,
|
||||
PrerequisiteCaps.selectCapabilitiesForSexPrerequisite,
|
||||
PrerequisiteCaps.selectCapabilitiesForSexualCharacteristicPrerequisite,
|
||||
PrerequisiteCaps.selectCapabilitiesForSocialStatusPrerequisite,
|
||||
PrerequisiteCaps.selectCapabilitiesForStatePrerequisite,
|
||||
PrerequisiteCaps.selectCapabilitiesForTextPrerequisite,
|
||||
PrerequisiteCaps.selectCapabilitiesForPrecondition,
|
||||
(
|
||||
database,
|
||||
activatablePrerequisiteCapabilities,
|
||||
ancestorBloodPrerequisiteCapabilities,
|
||||
blessedTraditionPrerequisiteCapabilities,
|
||||
commonSuggestedByRCPPrerequisiteCapabilities,
|
||||
culturePrerequisiteCapabilities,
|
||||
externalEnhancementPrerequisiteCapabilities,
|
||||
influencePrerequisiteCapabilities,
|
||||
magicalTraditionPrerequisiteCapabilities,
|
||||
pactPrerequisiteCapabilities,
|
||||
primaryAttributePrerequisiteCapabilities,
|
||||
publicationPrerequisiteCapabilities,
|
||||
racePrerequisiteCapabilities,
|
||||
ratedMinimumNumberPrerequisiteCapabilities,
|
||||
ratedPrerequisiteCapabilities,
|
||||
ratedSumPrerequisiteCapabilities,
|
||||
rulePrerequisiteCapabilities,
|
||||
sexPrerequisiteCapabilities,
|
||||
sexualCharacteristicPrerequisiteCapabilities,
|
||||
socialStatusPrerequisiteCapabilities,
|
||||
statePrerequisiteCapabilities,
|
||||
textPrerequisiteCapabilities,
|
||||
preconditionCapabilities,
|
||||
) =>
|
||||
filterApplyingActivatableDependencies(
|
||||
checkMultipleDisjunctionPartsAreValid({
|
||||
getStaticDisadvantageById: id => database.disadvantages[id],
|
||||
getStaticGeneralSpecialAbilityById: id => database.generalSpecialAbilities[id],
|
||||
getStaticFatePointSpecialAbilityById: id => database.fatePointSpecialAbilities[id],
|
||||
getStaticCombatSpecialAbilityById: id => database.combatSpecialAbilities[id],
|
||||
getStaticMagicalSpecialAbilityById: id => database.magicalSpecialAbilities[id],
|
||||
getStaticStaffEnchantmentById: id => database.staffEnchantments[id],
|
||||
getStaticFamiliarSpecialAbilityById: id => database.familiarSpecialAbilities[id],
|
||||
getStaticKarmaSpecialAbilityById: id => database.karmaSpecialAbilities[id],
|
||||
getStaticProtectiveWardingCircleSpecialAbilityById: id =>
|
||||
database.protectiveWardingCircleSpecialAbilities[id],
|
||||
getStaticCombatStyleSpecialAbilityById: id => database.combatStyleSpecialAbilities[id],
|
||||
getStaticAdvancedCombatSpecialAbilityById: id =>
|
||||
database.advancedCombatSpecialAbilities[id],
|
||||
getStaticCommandSpecialAbilityById: id => database.commandSpecialAbilities[id],
|
||||
getStaticMagicStyleSpecialAbilityById: id => database.magicStyleSpecialAbilities[id],
|
||||
getStaticAdvancedMagicalSpecialAbilityById: id =>
|
||||
database.advancedMagicalSpecialAbilities[id],
|
||||
getStaticSpellSwordEnchantmentById: id => database.spellSwordEnchantments[id],
|
||||
getStaticDaggerRitualById: id => database.daggerRituals[id],
|
||||
getStaticInstrumentEnchantmentById: id => database.instrumentEnchantments[id],
|
||||
getStaticAttireEnchantmentById: id => database.attireEnchantments[id],
|
||||
getStaticOrbEnchantmentById: id => database.orbEnchantments[id],
|
||||
getStaticWandEnchantmentById: id => database.wandEnchantments[id],
|
||||
getStaticBrawlingSpecialAbilityById: id => database.brawlingSpecialAbilities[id],
|
||||
getStaticAncestorGlyphById: id => database.ancestorGlyphs[id],
|
||||
getStaticCeremonialItemSpecialAbilityById: id =>
|
||||
database.ceremonialItemSpecialAbilities[id],
|
||||
getStaticSermonById: id => database.sermons[id],
|
||||
getStaticLiturgicalStyleSpecialAbilityById: id =>
|
||||
database.liturgicalStyleSpecialAbilities[id],
|
||||
getStaticAdvancedKarmaSpecialAbilityById: id => database.advancedKarmaSpecialAbilities[id],
|
||||
getStaticVisionById: id => database.visions[id],
|
||||
getStaticMagicalTraditionById: id => database.magicalTraditions[id],
|
||||
getStaticBlessedTraditionById: id => database.blessedTraditions[id],
|
||||
getStaticPactGiftById: id => database.pactGifts[id],
|
||||
getStaticVampiricGiftById: id => database.vampiricGifts[id],
|
||||
getStaticSikaryanDrainSpecialAbilityById: id => database.sikaryanDrainSpecialAbilities[id],
|
||||
getStaticLycantropicGiftById: id => database.lycantropicGifts[id],
|
||||
getStaticSkillStyleSpecialAbilityById: id => database.skillStyleSpecialAbilities[id],
|
||||
getStaticAdvancedSkillSpecialAbilityById: id => database.advancedSkillSpecialAbilities[id],
|
||||
getStaticArcaneOrbEnchantmentById: id => database.arcaneOrbEnchantments[id],
|
||||
getStaticCauldronEnchantmentById: id => database.cauldronEnchantments[id],
|
||||
getStaticFoolsHatEnchantmentById: id => database.foolsHatEnchantments[id],
|
||||
getStaticToyEnchantmentById: id => database.toyEnchantments[id],
|
||||
getStaticBowlEnchantmentById: id => database.bowlEnchantments[id],
|
||||
getStaticFatePointSexSpecialAbilityById: id => database.fatePointSexSpecialAbilities[id],
|
||||
getStaticSexSpecialAbilityById: id => database.sexSpecialAbilities[id],
|
||||
getStaticWeaponEnchantmentById: id => database.weaponEnchantments[id],
|
||||
getStaticSickleRitualById: id => database.sickleRituals[id],
|
||||
getStaticRingEnchantmentById: id => database.ringEnchantments[id],
|
||||
getStaticChronicleEnchantmentById: id => database.chronicleEnchantments[id],
|
||||
getStaticKrallenkettenzauberById: id => database.krallenkettenzauber[id],
|
||||
getStaticTrinkhornzauberById: id => database.trinkhornzauber[id],
|
||||
getStaticMagicalSignById: id => database.magicalSigns[id],
|
||||
getStaticSpellById: id => database.spells[id],
|
||||
getStaticRitualById: id => database.rituals[id],
|
||||
getStaticLiturgicalChantById: id => database.liturgicalChants[id],
|
||||
getStaticCeremonyById: id => database.ceremonies[id],
|
||||
...activatablePrerequisiteCapabilities,
|
||||
...ancestorBloodPrerequisiteCapabilities,
|
||||
...blessedTraditionPrerequisiteCapabilities,
|
||||
...commonSuggestedByRCPPrerequisiteCapabilities,
|
||||
...culturePrerequisiteCapabilities,
|
||||
...externalEnhancementPrerequisiteCapabilities,
|
||||
...influencePrerequisiteCapabilities,
|
||||
...magicalTraditionPrerequisiteCapabilities,
|
||||
...pactPrerequisiteCapabilities,
|
||||
...primaryAttributePrerequisiteCapabilities,
|
||||
...publicationPrerequisiteCapabilities,
|
||||
...racePrerequisiteCapabilities,
|
||||
...ratedMinimumNumberPrerequisiteCapabilities,
|
||||
...ratedPrerequisiteCapabilities,
|
||||
...ratedSumPrerequisiteCapabilities,
|
||||
...rulePrerequisiteCapabilities,
|
||||
...sexPrerequisiteCapabilities,
|
||||
...sexualCharacteristicPrerequisiteCapabilities,
|
||||
...socialStatusPrerequisiteCapabilities,
|
||||
...statePrerequisiteCapabilities,
|
||||
...textPrerequisiteCapabilities,
|
||||
}),
|
||||
pre => checkPreconditions(pre, preconditionCapabilities),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -61,8 +61,8 @@ export const selectAvailableHairColorsIdDice = createSelector(
|
||||
selectCurrentRaceVariant,
|
||||
createPropertySelector(selectDynamicDisadvantages, DisadvantageIdentifier.Stigma),
|
||||
(currentRace, currentRaceVariant, stigma): number[] => {
|
||||
const isAlbino = isOptionActive(stigma, { type: "Generic", value: ALBINO })
|
||||
const isGreenHaired = isOptionActive(stigma, { type: "Generic", value: GREEN_HAIR })
|
||||
const isAlbino = isOptionActive(stigma, { tag: "General", general: ALBINO })
|
||||
const isGreenHaired = isOptionActive(stigma, { tag: "General", general: GREEN_HAIR })
|
||||
|
||||
if (isAlbino || isGreenHaired) {
|
||||
return filterNonNullable([
|
||||
@@ -103,7 +103,7 @@ export const selectAvailableEyeColorsIdDice = createSelector(
|
||||
selectCurrentRaceVariant,
|
||||
createPropertySelector(selectDynamicDisadvantages, DisadvantageIdentifier.Stigma),
|
||||
(currentRace, currentRaceVariant, stigma): number[] => {
|
||||
const isAlbino = isOptionActive(stigma, { type: "Generic", value: ALBINO })
|
||||
const isAlbino = isOptionActive(stigma, { tag: "General", general: ALBINO })
|
||||
|
||||
if (isAlbino) {
|
||||
const eyeColorIds = [EyeColorIdentifier.Red, EyeColorIdentifier.Purple]
|
||||
|
||||
@@ -127,6 +127,7 @@ export const selectCapabilitiesForActivatablePrerequisite = createSelector(
|
||||
getDynamicMagicalTraditionById: id => specialAbilities?.magicalTraditions?.[id],
|
||||
getDynamicBlessedTraditionById: id => specialAbilities?.blessedTraditions?.[id],
|
||||
getDynamicPactGiftById: id => specialAbilities?.pactGifts?.[id],
|
||||
getDynamicVampiricGiftById: id => specialAbilities?.vampiricGifts?.[id],
|
||||
getDynamicSikaryanDrainSpecialAbilityById: id =>
|
||||
specialAbilities?.sikaryanDrainSpecialAbilities?.[id],
|
||||
getDynamicLycantropicGiftById: id => specialAbilities?.lycantropicGifts?.[id],
|
||||
@@ -148,6 +149,7 @@ export const selectCapabilitiesForActivatablePrerequisite = createSelector(
|
||||
getDynamicChronicleEnchantmentById: id => specialAbilities?.chronicleEnchantments?.[id],
|
||||
getDynamicKrallenkettenzauberById: id => specialAbilities?.krallenkettenzauber?.[id],
|
||||
getDynamicTrinkhornzauberById: id => specialAbilities?.trinkhornzauber?.[id],
|
||||
getDynamicMagicalSignById: id => specialAbilities?.magicalSigns?.[id],
|
||||
checkPrecondition: pre => checkPrecondition(precondition, pre),
|
||||
}),
|
||||
)
|
||||
@@ -386,22 +388,8 @@ export const selectCapabilitiesForRatedPrerequisite = createSelector(
|
||||
*/
|
||||
export const selectCapabilitiesForRatedSumPrerequisite = createSelector(
|
||||
SelectGetById.Dynamic.Skill,
|
||||
SelectGetById.Dynamic.Spell,
|
||||
SelectGetById.Dynamic.Ritual,
|
||||
SelectGetById.Dynamic.LiturgicalChant,
|
||||
SelectGetById.Dynamic.Ceremony,
|
||||
(
|
||||
(getDynamicSkillById): Parameters<typeof checkRatedSumPrerequisite>[0] => ({
|
||||
getDynamicSkillById,
|
||||
getDynamicSpellById,
|
||||
getDynamicRitualById,
|
||||
getDynamicLiturgicalChantById,
|
||||
getDynamicCeremonyById,
|
||||
): Parameters<typeof checkRatedSumPrerequisite>[0] => ({
|
||||
getDynamicSkillById,
|
||||
getDynamicSpellById,
|
||||
getDynamicRitualById,
|
||||
getDynamicLiturgicalChantById,
|
||||
getDynamicCeremonyById,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { createSelector } from "@reduxjs/toolkit"
|
||||
import { isActive } from "../../shared/domain/activatable/activatableEntry.ts"
|
||||
import { getMaximumAdventurePointsForMagicalAdvantagesAndDisadvantages } from "../../shared/domain/activatable/advantagesDisadvantages.ts"
|
||||
import {
|
||||
CombinedActiveBlessedTradition,
|
||||
isBlessedOne,
|
||||
} from "../../shared/domain/activatable/blessedTradition.ts"
|
||||
import {
|
||||
CombinedActiveMagicalTradition,
|
||||
getMaximumAdventurePointsForMagicalAdvantagesAndDisadvantages,
|
||||
isSpellcaster,
|
||||
} from "../../shared/domain/activatable/magicalTradition.ts"
|
||||
import { AdvantageIdentifier } from "../../shared/domain/identifier.ts"
|
||||
|
||||
@@ -0,0 +1,293 @@
|
||||
import { ActionCreatorWithPayload, AnyAction, Draft, createAction } from "@reduxjs/toolkit"
|
||||
import { AdventurePointsValue } from "optolith-database-schema/types/_Activatable"
|
||||
import { ActivatableIdentifier } from "optolith-database-schema/types/_IdentifierGroup"
|
||||
import { getCost } from "../../shared/domain/activatable/activatableActive.ts"
|
||||
import {
|
||||
Activatable,
|
||||
ActivatableInstance,
|
||||
ActivatableMap,
|
||||
createEmptyDynamicActivatable,
|
||||
} from "../../shared/domain/activatable/activatableEntry.ts"
|
||||
import { getSelectOptionsFromCacheById } from "../../shared/domain/database.ts"
|
||||
import { RegistrationMethod } from "../../shared/domain/dependencies/registrationHelpers.ts"
|
||||
import { createIdentifierObject, equalsIdentifier } from "../../shared/domain/identifier.ts"
|
||||
import { hasAspectById } from "../../shared/domain/rated/liturgicalChant.ts"
|
||||
import { RangeBounds } from "../../shared/utils/range.ts"
|
||||
import { DraftReducer } from "../../shared/utils/redux.ts"
|
||||
import { CharacterState } from "./characterSlice.ts"
|
||||
import { DatabaseState } from "./databaseSlice.ts"
|
||||
|
||||
/**
|
||||
* Functions for working with rated entries that can be activated and have
|
||||
* enhancements.
|
||||
*/
|
||||
export type ActivatableSlice<N extends string, E extends string> = {
|
||||
/**
|
||||
* The actions that can be dispatched to modify the state.
|
||||
*/
|
||||
actions: {
|
||||
/**
|
||||
* Add the entry with the given id.
|
||||
*/
|
||||
addInstance: ActionCreatorWithPayload<
|
||||
{ id: number; instance: ActivatableInstance },
|
||||
`${N}/add${E}`
|
||||
>
|
||||
|
||||
/**
|
||||
* Change the level of the entry with the given id.
|
||||
*/
|
||||
changeInstanceLevel: ActionCreatorWithPayload<
|
||||
{ id: number; index: number; level: number },
|
||||
`${N}/change${E}Level`
|
||||
>
|
||||
|
||||
/**
|
||||
* Remove the entry with the given id.
|
||||
*/
|
||||
removeInstance: ActionCreatorWithPayload<{ id: number; index: number }, `${N}/remove${E}`>
|
||||
}
|
||||
|
||||
/**
|
||||
* The reducer that handles the actions.
|
||||
*/
|
||||
reducer: DraftReducer<CharacterState, AnyAction, [database: DatabaseState]>
|
||||
}
|
||||
|
||||
const tagToNamespace = {
|
||||
AdvancedCombatSpecialAbility: "advancedCombatSpecialAbilities",
|
||||
AdvancedKarmaSpecialAbility: "advancedKarmaSpecialAbilities",
|
||||
AdvancedMagicalSpecialAbility: "advancedMagicalSpecialAbilities",
|
||||
AdvancedSkillSpecialAbility: "advancedSkillSpecialAbilities",
|
||||
Advantage: "advantages",
|
||||
AncestorGlyph: "ancestorGlyphs",
|
||||
ArcaneOrbEnchantment: "arcaneOrbEnchantments",
|
||||
AttireEnchantment: "attireEnchantments",
|
||||
BlessedTradition: "blessedTraditions",
|
||||
BowlEnchantment: "bowlEnchantments",
|
||||
BrawlingSpecialAbility: "brawlingSpecialAbilities",
|
||||
CauldronEnchantment: "cauldronEnchantments",
|
||||
CeremonialItemSpecialAbility: "ceremonialItemSpecialAbilities",
|
||||
ChronicleEnchantment: "chronicleEnchantments",
|
||||
CombatSpecialAbility: "combatSpecialAbilities",
|
||||
CombatStyleSpecialAbility: "combatStyleSpecialAbilities",
|
||||
CommandSpecialAbility: "commandSpecialAbilities",
|
||||
DaggerRitual: "daggerRituals",
|
||||
Disadvantage: "disadvantages",
|
||||
FamiliarSpecialAbility: "familiarSpecialAbilities",
|
||||
FatePointSexSpecialAbility: "fatePointSexSpecialAbilities",
|
||||
FatePointSpecialAbility: "fatePointSpecialAbilities",
|
||||
FoolsHatEnchantment: "foolsHatEnchantments",
|
||||
GeneralSpecialAbility: "generalSpecialAbilities",
|
||||
InstrumentEnchantment: "instrumentEnchantments",
|
||||
KarmaSpecialAbility: "karmaSpecialAbilities",
|
||||
Krallenkettenzauber: "krallenkettenzauber",
|
||||
LiturgicalStyleSpecialAbility: "liturgicalStyleSpecialAbilities",
|
||||
LycantropicGift: "lycantropicGifts",
|
||||
MagicalRune: "magicalRunes",
|
||||
MagicalSign: "magicalSigns",
|
||||
MagicalSpecialAbility: "magicalSpecialAbilities",
|
||||
MagicalTradition: "magicalTraditions",
|
||||
MagicStyleSpecialAbility: "magicStyleSpecialAbilities",
|
||||
OrbEnchantment: "orbEnchantments",
|
||||
PactGift: "pactGifts",
|
||||
ProtectiveWardingCircleSpecialAbility: "protectiveWardingCircleSpecialAbilities",
|
||||
RingEnchantment: "ringEnchantments",
|
||||
Sermon: "sermons",
|
||||
SexSpecialAbility: "sexSpecialAbilities",
|
||||
SickleRitual: "sickleRituals",
|
||||
SikaryanDrainSpecialAbility: "sikaryanDrainSpecialAbilities",
|
||||
SkillStyleSpecialAbility: "skillStyleSpecialAbilities",
|
||||
SpellSwordEnchantment: "spellSwordEnchantments",
|
||||
StaffEnchantment: "staffEnchantments",
|
||||
ToyEnchantment: "toyEnchantments",
|
||||
Trinkhornzauber: "trinkhornzauber",
|
||||
VampiricGift: "vampiricGifts",
|
||||
Vision: "visions",
|
||||
WandEnchantment: "wandEnchantments",
|
||||
WeaponEnchantment: "weaponEnchantments",
|
||||
} as const
|
||||
|
||||
type RecordValue<R> = R extends Record<number, infer V> ? V : never
|
||||
|
||||
/**
|
||||
* Creates a slice for a map of rated entries.
|
||||
*/
|
||||
export const createActivatableSlice = <
|
||||
E extends ActivatableIdentifier["tag"],
|
||||
P,
|
||||
IdO extends { tag: string },
|
||||
N extends (typeof tagToNamespace)[E] = (typeof tagToNamespace)[E],
|
||||
>(config: {
|
||||
entityName: E
|
||||
getState: (state: Draft<CharacterState>) => Draft<ActivatableMap>
|
||||
getPrerequisites: (id: number, database: DatabaseState) => P[]
|
||||
getAdventurePointsValue: (staticEntry: RecordValue<DatabaseState[N]>) => AdventurePointsValue
|
||||
createIdentifierObject: (id: number) => IdO
|
||||
registerOrUnregisterPrerequisitesAsDependencies: (
|
||||
method: RegistrationMethod,
|
||||
character: Draft<CharacterState>,
|
||||
prerequisites: P[],
|
||||
sourceId: IdO,
|
||||
levelRange: RangeBounds,
|
||||
capabilities: {
|
||||
ancestorBloodAdvantageIds: number[]
|
||||
closeCombatTechniqueIds: number[]
|
||||
rangedCombatTechniqueIds: number[]
|
||||
blessedTraditionChurchIds: number[]
|
||||
blessedTraditionShamanisticIds: number[]
|
||||
magicalTraditionIds: number[]
|
||||
magicalTraditionIdsThatCanLearnRituals: number[]
|
||||
magicalTraditionIdsThatCanBindFamiliars: number[]
|
||||
getSpellIdsByPropertyId: (id: number) => number[]
|
||||
getRitualIdsByPropertyId: (id: number) => number[]
|
||||
getLiturgicalChantIdsByAspectId: (id: number) => number[]
|
||||
getCeremonyIdsByAspectId: (id: number) => number[]
|
||||
},
|
||||
) => void
|
||||
}): ActivatableSlice<N, E> => {
|
||||
const namespace = tagToNamespace[config.entityName] as N
|
||||
|
||||
const updateCachedAdventurePoints = (entry: Draft<Activatable>, database: DatabaseState) => {
|
||||
const staticEntry = database[namespace][entry.id]
|
||||
const apValue =
|
||||
staticEntry === undefined
|
||||
? undefined
|
||||
: config.getAdventurePointsValue(staticEntry as RecordValue<DatabaseState[N]>)
|
||||
|
||||
if (apValue !== undefined) {
|
||||
const id = createIdentifierObject(config.entityName, entry.id)
|
||||
entry.cachedAdventurePoints = {
|
||||
general: getCost(id, apValue, entry.instances, optionId =>
|
||||
getSelectOptionsFromCacheById(database.cache.activatableSelectOptions, id)?.find(opt =>
|
||||
equalsIdentifier(opt.id, optionId),
|
||||
),
|
||||
).total,
|
||||
bound: 0,
|
||||
}
|
||||
}
|
||||
return entry
|
||||
}
|
||||
|
||||
const addAction = createAction<{ id: number; instance: ActivatableInstance }, `${N}/add${E}`>(
|
||||
`${namespace}/add${config.entityName}`,
|
||||
)
|
||||
|
||||
const changeLevelAction = createAction<
|
||||
{ id: number; index: number; level: number },
|
||||
`${N}/change${E}Level`
|
||||
>(`${namespace}/change${config.entityName}Level`)
|
||||
|
||||
const removeAction = createAction<{ id: number; index: number }, `${N}/remove${E}`>(
|
||||
`${namespace}/remove${config.entityName}`,
|
||||
)
|
||||
|
||||
const getCapabilities = (
|
||||
database: DatabaseState,
|
||||
): Parameters<(typeof config)["registerOrUnregisterPrerequisitesAsDependencies"]>[5] => ({
|
||||
ancestorBloodAdvantageIds: database.cache.ancestorBloodAdvantages.ids,
|
||||
closeCombatTechniqueIds: Object.values(database.closeCombatTechniques).map(x => x.id),
|
||||
rangedCombatTechniqueIds: Object.values(database.rangedCombatTechniques).map(x => x.id),
|
||||
blessedTraditionChurchIds: Object.values(database.blessedTraditions)
|
||||
.filter(x => !x.is_shamanistic)
|
||||
.map(x => x.id),
|
||||
blessedTraditionShamanisticIds: Object.values(database.blessedTraditions)
|
||||
.filter(x => x.is_shamanistic)
|
||||
.map(x => x.id),
|
||||
magicalTraditionIds: Object.values(database.magicalTraditions).map(x => x.id),
|
||||
magicalTraditionIdsThatCanLearnRituals: Object.values(database.magicalTraditions)
|
||||
.filter(x => x.can_learn_rituals)
|
||||
.map(x => x.id),
|
||||
magicalTraditionIdsThatCanBindFamiliars: Object.values(database.magicalTraditions)
|
||||
.filter(x => x.can_bind_familiars)
|
||||
.map(x => x.id),
|
||||
getSpellIdsByPropertyId: id =>
|
||||
Object.values(database.spells)
|
||||
.filter(x => x.property.id.property === id)
|
||||
.map(x => x.id),
|
||||
getRitualIdsByPropertyId: id =>
|
||||
Object.values(database.rituals)
|
||||
.filter(x => x.property.id.property === id)
|
||||
.map(x => x.id),
|
||||
getLiturgicalChantIdsByAspectId: id =>
|
||||
Object.values(database.liturgicalChants)
|
||||
.filter(x => hasAspectById(id, x.traditions))
|
||||
.map(x => x.id),
|
||||
getCeremonyIdsByAspectId: id =>
|
||||
Object.values(database.ceremonies)
|
||||
.filter(x => hasAspectById(id, x.traditions))
|
||||
.map(x => x.id),
|
||||
})
|
||||
|
||||
const reducer: DraftReducer<CharacterState, AnyAction, [database: DatabaseState]> = (
|
||||
state,
|
||||
action,
|
||||
database,
|
||||
) => {
|
||||
if (addAction.match(action)) {
|
||||
const { id, instance } = action.payload
|
||||
const entry = (config.getState(state)[id] ??= createEmptyDynamicActivatable(id))
|
||||
entry.instances.push(instance)
|
||||
updateCachedAdventurePoints(entry, database)
|
||||
config.registerOrUnregisterPrerequisitesAsDependencies(
|
||||
RegistrationMethod.Remove,
|
||||
state,
|
||||
config.getPrerequisites(action.payload.id, database),
|
||||
config.createIdentifierObject(action.payload.id),
|
||||
[1, instance.level ?? 1],
|
||||
getCapabilities(database),
|
||||
)
|
||||
} else if (changeLevelAction.match(action)) {
|
||||
const { id, index, level } = action.payload
|
||||
const entry = config.getState(state)[id]
|
||||
const instance = entry?.instances[index]
|
||||
if (instance !== undefined && instance.level !== undefined && instance.level !== level) {
|
||||
instance.level = level
|
||||
updateCachedAdventurePoints(entry!, database)
|
||||
if (level < instance.level) {
|
||||
config.registerOrUnregisterPrerequisitesAsDependencies(
|
||||
RegistrationMethod.Remove,
|
||||
state,
|
||||
config.getPrerequisites(action.payload.id, database),
|
||||
config.createIdentifierObject(action.payload.id),
|
||||
[level + 1, instance.level],
|
||||
getCapabilities(database),
|
||||
)
|
||||
} else if (level > instance.level) {
|
||||
config.registerOrUnregisterPrerequisitesAsDependencies(
|
||||
RegistrationMethod.Add,
|
||||
state,
|
||||
config.getPrerequisites(action.payload.id, database),
|
||||
config.createIdentifierObject(action.payload.id),
|
||||
[instance.level + 1, level],
|
||||
getCapabilities(database),
|
||||
)
|
||||
}
|
||||
}
|
||||
} else if (removeAction.match(action)) {
|
||||
const { id, index } = action.payload
|
||||
const entry = config.getState(state)[id]
|
||||
if (entry !== undefined) {
|
||||
config.registerOrUnregisterPrerequisitesAsDependencies(
|
||||
RegistrationMethod.Remove,
|
||||
state,
|
||||
config.getPrerequisites(action.payload.id, database),
|
||||
config.createIdentifierObject(action.payload.id),
|
||||
[1, entry.instances[index]!.level ?? 1],
|
||||
getCapabilities(database),
|
||||
)
|
||||
entry.instances.splice(index, 1)
|
||||
updateCachedAdventurePoints(entry, database)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
actions: {
|
||||
addInstance: addAction,
|
||||
changeInstanceLevel: changeLevelAction,
|
||||
removeInstance: removeAction,
|
||||
},
|
||||
reducer,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { registerOrUnregisterPrerequisitesOfAdvantageOrDisadvantageAsDependencies } from "../../shared/domain/dependencies/fullPrerequisiteRegistrationAsDependencyForType.ts"
|
||||
import { createIdentifierObject } from "../../shared/domain/identifier.ts"
|
||||
import { createActivatableSlice } from "./activatableSlice.ts"
|
||||
|
||||
// eslint-disable-next-line jsdoc/require-jsdoc
|
||||
export const {
|
||||
actions: {
|
||||
addInstance: addAdvantage,
|
||||
changeInstanceLevel: changeAdvantageLevel,
|
||||
removeInstance: removeAdvantage,
|
||||
},
|
||||
reducer: advantagesReducer,
|
||||
} = createActivatableSlice({
|
||||
entityName: "Advantage",
|
||||
getState: state => state.advantages,
|
||||
getPrerequisites: (id, database) => database.advantages[id]?.prerequisites ?? [],
|
||||
getAdventurePointsValue: staticEntry => staticEntry.ap_value,
|
||||
createIdentifierObject: id => createIdentifierObject("Advantage", id),
|
||||
registerOrUnregisterPrerequisitesAsDependencies:
|
||||
registerOrUnregisterPrerequisitesOfAdvantageOrDisadvantageAsDependencies,
|
||||
})
|
||||
@@ -0,0 +1,32 @@
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
/* eslint-disable max-len */
|
||||
import { PayloadAction, createSlice } from "@reduxjs/toolkit"
|
||||
import { RootState } from "../store.ts"
|
||||
|
||||
export type Alert = {
|
||||
title: string
|
||||
description: string
|
||||
}
|
||||
|
||||
type AlertsState = Alert[]
|
||||
|
||||
const initialAlertsState: AlertsState = []
|
||||
|
||||
const alertsSlice = createSlice({
|
||||
name: "alerts",
|
||||
initialState: initialAlertsState,
|
||||
reducers: {
|
||||
showAlert: (state, action: PayloadAction<Alert>) => {
|
||||
state.push(action.payload)
|
||||
},
|
||||
dismissAlert: state => {
|
||||
state.splice(0, 1)
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export const { showAlert, dismissAlert } = alertsSlice.actions
|
||||
|
||||
export const selectCurrentAlert = (state: RootState) => state.alerts[0]
|
||||
|
||||
export const alertsReducer = alertsSlice.reducer
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from "../../shared/domain/identifier.ts"
|
||||
import { createImmerReducer, reduceReducers } from "../../shared/utils/redux.ts"
|
||||
import { RootState } from "../store.ts"
|
||||
import { advantagesReducer } from "./advantagesSlice.ts"
|
||||
import { attributesReducer } from "./attributesSlice.ts"
|
||||
import { blessingsReducer } from "./blessingsSlice.ts"
|
||||
import { cantripsReducer } from "./cantripsSlice.ts"
|
||||
@@ -15,6 +16,7 @@ import { ceremoniesReducer } from "./ceremoniesSlice.ts"
|
||||
import { closeCombatTechniquesReducer } from "./closeCombatTechniqueSlice.ts"
|
||||
import { DatabaseState } from "./databaseSlice.ts"
|
||||
import { derivedCharacteristicsReducer } from "./derivedCharacteristicsSlice.ts"
|
||||
import { disadvantagesReducer } from "./disadvantagesSlice.ts"
|
||||
import { liturgicalChantsReducer } from "./liturgicalChantsSlice.ts"
|
||||
import { animistPowersReducer } from "./magicalActions/animistPowersSlice.ts"
|
||||
import { cursesReducer } from "./magicalActions/cursesSlice.ts"
|
||||
@@ -74,14 +76,12 @@ const staticInitialState: Omit<CharacterState, "dateCreated" | "dateLastModified
|
||||
},
|
||||
},
|
||||
advantages: {
|
||||
[AdvantageIdentifier.Blessed]: {
|
||||
id: AdvantageIdentifier.Blessed,
|
||||
instances: [{}],
|
||||
},
|
||||
[AdvantageIdentifier.Spellcaster]: {
|
||||
id: AdvantageIdentifier.Spellcaster,
|
||||
instances: [{}],
|
||||
},
|
||||
[AdvantageIdentifier.Blessed]: createDynamicActivatable(AdvantageIdentifier.Blessed, [{}], 25),
|
||||
[AdvantageIdentifier.Spellcaster]: createDynamicActivatable(
|
||||
AdvantageIdentifier.Spellcaster,
|
||||
[{}],
|
||||
25,
|
||||
),
|
||||
},
|
||||
disadvantages: {},
|
||||
specialAbilities: {
|
||||
@@ -93,10 +93,11 @@ const staticInitialState: Omit<CharacterState, "dateCreated" | "dateLastModified
|
||||
arcaneOrbEnchantments: {},
|
||||
attireEnchantments: {},
|
||||
blessedTraditions: {
|
||||
[BlessedTraditionIdentifier.Praios]: {
|
||||
id: BlessedTraditionIdentifier.Praios,
|
||||
instances: [{}],
|
||||
},
|
||||
[BlessedTraditionIdentifier.Praios]: createDynamicActivatable(
|
||||
BlessedTraditionIdentifier.Praios,
|
||||
[{}],
|
||||
130,
|
||||
),
|
||||
},
|
||||
bowlEnchantments: {},
|
||||
brawlingSpecialAbilities: {},
|
||||
@@ -121,10 +122,11 @@ const staticInitialState: Omit<CharacterState, "dateCreated" | "dateLastModified
|
||||
magicalSigns: {},
|
||||
magicalSpecialAbilities: {},
|
||||
magicalTraditions: {
|
||||
[MagicalTraditionIdentifier.Witches]: {
|
||||
id: MagicalTraditionIdentifier.Witches,
|
||||
instances: [{}],
|
||||
},
|
||||
[MagicalTraditionIdentifier.Witches]: createDynamicActivatable(
|
||||
MagicalTraditionIdentifier.Witches,
|
||||
[{}],
|
||||
135,
|
||||
),
|
||||
},
|
||||
magicStyleSpecialAbilities: {},
|
||||
orbEnchantments: {},
|
||||
@@ -145,7 +147,8 @@ const staticInitialState: Omit<CharacterState, "dateCreated" | "dateLastModified
|
||||
wandEnchantments: {},
|
||||
weaponEnchantments: {},
|
||||
},
|
||||
attributes: {},
|
||||
magicalPrimaryAttributeDependencies: [],
|
||||
blessedPrimaryAttributeDependencies: [],
|
||||
derivedCharacteristics: {
|
||||
lifePoints: {
|
||||
purchased: 0,
|
||||
@@ -242,8 +245,6 @@ const staticInitialState: Omit<CharacterState, "dateCreated" | "dateLastModified
|
||||
value: 0,
|
||||
},
|
||||
},
|
||||
magicalPrimaryAttributeDependencies: [],
|
||||
blessedPrimaryAttributeDependencies: [],
|
||||
// items: {}
|
||||
// hitZoneArmors: {}
|
||||
purse: {
|
||||
@@ -1012,6 +1013,8 @@ export const characterReducer = reduceReducers<
|
||||
raceReducer,
|
||||
professionReducer,
|
||||
rulesReducer,
|
||||
advantagesReducer,
|
||||
disadvantagesReducer,
|
||||
skillsReducer,
|
||||
closeCombatTechniquesReducer,
|
||||
rangedCombatTechniquesReducer,
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { registerOrUnregisterPrerequisitesOfAdvantageOrDisadvantageAsDependencies } from "../../shared/domain/dependencies/fullPrerequisiteRegistrationAsDependencyForType.ts"
|
||||
import { createIdentifierObject } from "../../shared/domain/identifier.ts"
|
||||
import { createActivatableSlice } from "./activatableSlice.ts"
|
||||
|
||||
// eslint-disable-next-line jsdoc/require-jsdoc
|
||||
export const {
|
||||
actions: {
|
||||
addInstance: addDisadvantage,
|
||||
changeInstanceLevel: changeDisadvantageLevel,
|
||||
removeInstance: removeDisadvantage,
|
||||
},
|
||||
reducer: disadvantagesReducer,
|
||||
} = createActivatableSlice({
|
||||
entityName: "Disadvantage",
|
||||
getState: state => state.disadvantages,
|
||||
getPrerequisites: (id, database) => database.disadvantages[id]?.prerequisites ?? [],
|
||||
getAdventurePointsValue: staticEntry => staticEntry.ap_value,
|
||||
createIdentifierObject: id => createIdentifierObject("Disadvantage", id),
|
||||
registerOrUnregisterPrerequisitesAsDependencies:
|
||||
registerOrUnregisterPrerequisitesOfAdvantageOrDisadvantageAsDependencies,
|
||||
})
|
||||
@@ -50,8 +50,10 @@ export type DisplayableMainIdentifier =
|
||||
| ID.EquipmentOfBlessedOnesIdentifier
|
||||
| ID.ExperienceLevelIdentifier
|
||||
| ID.FamiliarSpecialAbilityIdentifier
|
||||
| ID.FatePointSpecialAbilityIdentifier
|
||||
| ID.FatePointSexSpecialAbilityIdentifier
|
||||
| ID.FocusRuleIdentifier
|
||||
| ID.FoolsHatEnchantmentIdentifier
|
||||
| ID.GemOrPreciousStoneIdentifier
|
||||
| ID.GeneralSpecialAbilityIdentifier
|
||||
| ID.GeodeRitualIdentifier
|
||||
@@ -72,6 +74,8 @@ export type DisplayableMainIdentifier =
|
||||
| ID.MagicalArtifactIdentifier
|
||||
| ID.MagicalDanceIdentifier
|
||||
| ID.MagicalMelodyIdentifier
|
||||
| ID.MagicalRuneIdentifier
|
||||
| ID.MagicalSignIdentifier
|
||||
| ID.MagicalSpecialAbilityIdentifier
|
||||
| ID.MagicalTraditionIdentifier
|
||||
| ID.MusicalInstrumentIdentifier
|
||||
@@ -93,12 +97,16 @@ export type DisplayableMainIdentifier =
|
||||
| ID.SickleRitualIdentifier
|
||||
| ID.SikaryanDrainSpecialAbilityIdentifier
|
||||
| ID.SkillIdentifier
|
||||
| ID.SkillStyleSpecialAbilityIdentifier
|
||||
| ID.SpellIdentifier
|
||||
| ID.SpellSwordEnchantmentIdentifier
|
||||
| ID.StaffEnchantmentIdentifier
|
||||
| ID.ThievesToolIdentifier
|
||||
| ID.ToolOfTheTradeIdentifier
|
||||
| ID.ToyEnchantmentIdentifier
|
||||
| ID.TravelGearOrToolIdentifier
|
||||
| ID.TrinkhornzauberIdentifier
|
||||
| ID.VampiricGiftIdentifier
|
||||
| ID.VehicleIdentifier
|
||||
| ID.VisionIdentifier
|
||||
| ID.WandEnchantmentIdentifier
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { combineReducers, configureStore } from "@reduxjs/toolkit"
|
||||
import { reduceReducers } from "../shared/utils/redux.ts"
|
||||
import { alertsReducer } from "./slices/alertsSlice.ts"
|
||||
import { charactersReducer } from "./slices/charactersSlice.ts"
|
||||
import { databaseReducer } from "./slices/databaseSlice.ts"
|
||||
import { globalReducer } from "./slices/global.ts"
|
||||
@@ -8,6 +9,7 @@ import { routeReducer } from "./slices/routeSlice.ts"
|
||||
import { settingsReducer } from "./slices/settingsSlice.ts"
|
||||
|
||||
const sliceReducer = combineReducers({
|
||||
alerts: alertsReducer,
|
||||
database: databaseReducer,
|
||||
characters: charactersReducer,
|
||||
inlineLibrary: inlineLibraryReducer,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,85 @@
|
||||
import {
|
||||
ActivatableIdentifier,
|
||||
RequirableSelectOptionIdentifier,
|
||||
} from "optolith-database-schema/types/_IdentifierGroup"
|
||||
import { Preconditions } from "optolith-database-schema/types/prerequisites/ConditionalPrerequisites"
|
||||
import { equalsIdentifier } from "../identifier.ts"
|
||||
import { ActivatableInstance, PredefinedActivatableOption } from "./activatableEntry.ts"
|
||||
|
||||
/**
|
||||
* A dependency on an activatable entry.
|
||||
*/
|
||||
export type ActivatableDependency = 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
|
||||
|
||||
/**
|
||||
* If the required entry should be required to be active or inactive.
|
||||
*/
|
||||
active: boolean
|
||||
|
||||
/**
|
||||
* The required minimum level of the entry.
|
||||
*/
|
||||
level?: number
|
||||
|
||||
/**
|
||||
* Required select options. Order is important. Typically, you only need the
|
||||
* first array index, though.
|
||||
*/
|
||||
options?: RequirableSelectOptionIdentifier[]
|
||||
|
||||
when?: Preconditions
|
||||
}>
|
||||
|
||||
/**
|
||||
* Returns whether the given dependency is a general dependency, i.e. it does
|
||||
* not require a specific level or options, just the entry in general.
|
||||
*/
|
||||
export const isGeneralDependency = (dependency: ActivatableDependency) =>
|
||||
dependency.level === undefined && dependency.options === undefined
|
||||
|
||||
/**
|
||||
* Returns whether the given dependency strictly, i.e. with exactly matching
|
||||
* options array, applies to the given activatable instance.
|
||||
*/
|
||||
export const appliesToInstanceStrict = (
|
||||
dependency: ActivatableDependency,
|
||||
instance: ActivatableInstance,
|
||||
) =>
|
||||
dependency.options === undefined ||
|
||||
dependency.options.every(
|
||||
(depOpt, i) =>
|
||||
instance.options?.[i]?.type === "Predefined" &&
|
||||
equalsIdentifier(depOpt, (instance.options![i] as PredefinedActivatableOption).id),
|
||||
)
|
||||
|
||||
/**
|
||||
* Returns whether the given dependency applies loosely, i.e. the options from
|
||||
* the options array are included in the instance options, to the given
|
||||
* activatable instance.
|
||||
*/
|
||||
export const appliesToInstanceLoose = (
|
||||
dependency: ActivatableDependency,
|
||||
instance: ActivatableInstance,
|
||||
) =>
|
||||
dependency.options === undefined ||
|
||||
dependency.options.every(
|
||||
depOpt =>
|
||||
instance.options?.some(
|
||||
opt => opt.type === "Predefined" && equalsIdentifier(depOpt, opt.id),
|
||||
) ?? false,
|
||||
)
|
||||
@@ -1,7 +1,9 @@
|
||||
import { RequirableSelectOptionIdentifier } from "optolith-database-schema/types/_IdentifierGroup"
|
||||
import { Equality } from "../../utils/compare.ts"
|
||||
import { SelectOptionIdentifier } from "optolith-database-schema/types/_IdentifierGroup"
|
||||
import { isNotNullish } from "../../utils/nullable.ts"
|
||||
import { assertExhaustive } from "../../utils/typeSafety.ts"
|
||||
import { BoundAdventurePointsForActivatable } from "../adventurePoints/activatableEntry.ts"
|
||||
import { AdventurePointsCache, emptyAdventurePointsCache } from "../adventurePoints/cache.ts"
|
||||
import { equalsIdentifier, splitIdentifierObject } from "../identifier.ts"
|
||||
import { ActivatableDependency } from "./activatableDependency.ts"
|
||||
|
||||
/**
|
||||
* An activated activatable identifier.
|
||||
@@ -27,7 +29,7 @@ export const isTinyActivatableActive = (activatable: TinyActivatable | undefined
|
||||
/**
|
||||
* An activatable entry.
|
||||
*/
|
||||
export type Activatable = {
|
||||
export type Activatable = Readonly<{
|
||||
/**
|
||||
* The activatable identifier.
|
||||
* @integer
|
||||
@@ -38,7 +40,25 @@ export type Activatable = {
|
||||
* One or multiple activations of the activatable.
|
||||
*/
|
||||
instances: ActivatableInstance[]
|
||||
}
|
||||
|
||||
/**
|
||||
* The accumulated used adventure points value of all instances.
|
||||
*/
|
||||
cachedAdventurePoints: AdventurePointsCache
|
||||
|
||||
/**
|
||||
* The activatable's dependencies.
|
||||
*/
|
||||
dependencies: ActivatableDependency[]
|
||||
|
||||
/**
|
||||
* A list of bound adventure points. Bound adventure points are granted by the
|
||||
* GM and can only be spent on the entry. They don’t affect the costs of
|
||||
* buying the activatable entry. They may specify for which configuration of
|
||||
* an entry they count, just like a prerequisite.
|
||||
*/
|
||||
boundAdventurePoints: BoundAdventurePointsForActivatable[]
|
||||
}>
|
||||
|
||||
/**
|
||||
* A single activation of an activatable entry.
|
||||
@@ -59,6 +79,11 @@ export type ActivatableInstance = {
|
||||
* If provided, a custom adventure points value has been set for this instance.
|
||||
*/
|
||||
customAdventurePointsValue?: number
|
||||
|
||||
/**
|
||||
* A list of used bound adventure points for this instance.
|
||||
*/
|
||||
usedBoundAdventurePoints?: BoundAdventurePointsForActivatable[]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,44 +100,16 @@ export type PredefinedActivatableOption = {
|
||||
/**
|
||||
* An identifier referencing a different entry.
|
||||
*/
|
||||
id: {
|
||||
/**
|
||||
* The entry type or `"Generic"` if it references a select option local to the entry.
|
||||
*/
|
||||
type:
|
||||
| "Generic"
|
||||
| "Blessing"
|
||||
| "Cantrip"
|
||||
| "TradeSecret"
|
||||
| "Script"
|
||||
| "AnimalShape"
|
||||
| "ArcaneBardTradition"
|
||||
| "ArcaneDancerTradition"
|
||||
| "SexPractice"
|
||||
| "Race"
|
||||
| "Culture"
|
||||
| "BlessedTradition"
|
||||
| "Element"
|
||||
| "Property"
|
||||
| "Aspect"
|
||||
| "Disease"
|
||||
| "Poison"
|
||||
| "Language"
|
||||
| "Skill"
|
||||
| "CloseCombatTechnique"
|
||||
| "RangedCombatTechnique"
|
||||
| "LiturgicalChant"
|
||||
| "Ceremony"
|
||||
| "Spell"
|
||||
| "Ritual"
|
||||
|
||||
/**
|
||||
* The numeric identifier.
|
||||
*/
|
||||
value: number
|
||||
}
|
||||
id: SelectOptionIdentifier
|
||||
}
|
||||
|
||||
/**
|
||||
* A predicate for a predefined option on an activatable entry.
|
||||
*/
|
||||
export const isPredefinedActivatableOption = (
|
||||
option: ActivatableOption,
|
||||
): option is PredefinedActivatableOption => option.type === "Predefined"
|
||||
|
||||
/**
|
||||
* A custom option for an activatable entry, handled as user-entered text.
|
||||
*/
|
||||
@@ -125,6 +122,19 @@ export type CustomActivatableOption = {
|
||||
value: string
|
||||
}
|
||||
|
||||
/**
|
||||
* A predicate for a custom option on an activatable entry.
|
||||
*/
|
||||
export const isCustomActivatableOption = (
|
||||
option: ActivatableOption,
|
||||
): option is CustomActivatableOption => option.type === "Custom"
|
||||
|
||||
/**
|
||||
* Returns whether a select option matches a given activatable option.
|
||||
*/
|
||||
export const matchesOptionId = (id: SelectOptionIdentifier, option: ActivatableOption) =>
|
||||
isPredefinedActivatableOption(option) && equalsIdentifier(option.id, id)
|
||||
|
||||
/**
|
||||
* A map of activatable entries.
|
||||
*/
|
||||
@@ -138,10 +148,32 @@ export type ActivatableMap = {
|
||||
export const createEmptyDynamicActivatable = (id: number): Activatable => ({
|
||||
id,
|
||||
instances: [],
|
||||
cachedAdventurePoints: emptyAdventurePointsCache,
|
||||
dependencies: [],
|
||||
boundAdventurePoints: [],
|
||||
})
|
||||
|
||||
const equalOptionId: Equality<PredefinedActivatableOption["id"]> = (a, b) =>
|
||||
a.type === b.type && a.value === b.value
|
||||
/**
|
||||
* Creates a dynamic activatable entry.
|
||||
*/
|
||||
export const createDynamicActivatable = (
|
||||
id: number,
|
||||
instances: ActivatableInstance[],
|
||||
cachedAdventurePoints: AdventurePointsCache | number,
|
||||
options: {
|
||||
dependencies?: ActivatableDependency[]
|
||||
boundAdventurePoints?: BoundAdventurePointsForActivatable[]
|
||||
} = {},
|
||||
): Activatable => ({
|
||||
id,
|
||||
instances,
|
||||
cachedAdventurePoints:
|
||||
typeof cachedAdventurePoints === "number"
|
||||
? { general: cachedAdventurePoints, bound: 0 }
|
||||
: cachedAdventurePoints,
|
||||
dependencies: options.dependencies ?? [],
|
||||
boundAdventurePoints: options.boundAdventurePoints ?? [],
|
||||
})
|
||||
|
||||
/**
|
||||
* Get the level of the first instance of a given activatable entry, if it is
|
||||
@@ -156,6 +188,12 @@ export const firstLevel = (activatable: Activatable | undefined) =>
|
||||
export const isActive = (activatable: Activatable | undefined): activatable is Activatable =>
|
||||
(activatable?.instances.length ?? 0) > 0
|
||||
|
||||
/**
|
||||
* Return whether the instance has a custom adventure points value.
|
||||
*/
|
||||
export const hasCustomAdventurePointsValue = (instance: ActivatableInstance): boolean =>
|
||||
instance.customAdventurePointsValue !== undefined
|
||||
|
||||
/**
|
||||
* Returns if a given option is active in any instance of a given activatable.
|
||||
* It defaults to the first option in the options array, but a different index
|
||||
@@ -168,23 +206,37 @@ export const isOptionActive = (
|
||||
): boolean =>
|
||||
activatable?.instances.some(instance => {
|
||||
const optionAtIndex = instance.options?.[atIndex]
|
||||
return optionAtIndex?.type === "Predefined" && equalOptionId(optionAtIndex.id, optionId)
|
||||
return optionAtIndex?.type === "Predefined" && equalsIdentifier(optionAtIndex.id, optionId)
|
||||
}) ?? false
|
||||
|
||||
/**
|
||||
* Returns the number of instances of a given activatable entry.
|
||||
*/
|
||||
export const countActivations = (activatable: Activatable | undefined): number =>
|
||||
activatable?.instances.length ?? 0
|
||||
|
||||
/**
|
||||
* Returns the number of instances of a given activatable entry that have a
|
||||
* given option active. It defaults to the first option in the options array,
|
||||
* but a different index can be specified.
|
||||
*/
|
||||
export const countOptions = (
|
||||
activatable: Activatable | undefined,
|
||||
optionId: PredefinedActivatableOption["id"],
|
||||
atIndex = 0,
|
||||
): number =>
|
||||
activatable?.instances.filter(instance => {
|
||||
const optionAtIndex = instance.options?.[atIndex]
|
||||
return optionAtIndex?.type === "Predefined" && equalOptionId(optionAtIndex.id, optionId)
|
||||
}).length ?? 0
|
||||
activatable: Activatable | undefined | ActivatableInstance[],
|
||||
optionId: SelectOptionIdentifier,
|
||||
options: { atIndex?: number; ignoreWithCustomAdventurePointsValue?: boolean } = {},
|
||||
): number => {
|
||||
const { atIndex = 0, ignoreWithCustomAdventurePointsValue = false } = options
|
||||
return (Array.isArray(activatable) ? activatable : activatable?.instances ?? []).filter(
|
||||
instance => {
|
||||
const optionAtIndex = instance.options?.[atIndex]
|
||||
return (
|
||||
optionAtIndex?.type === "Predefined" &&
|
||||
equalsIdentifier(optionAtIndex.id, optionId) &&
|
||||
(!ignoreWithCustomAdventurePointsValue || !hasCustomAdventurePointsValue(instance))
|
||||
)
|
||||
},
|
||||
).length
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the option value of the first instance of a given activatable entry.
|
||||
@@ -192,9 +244,10 @@ export const countOptions = (
|
||||
* can be specified.
|
||||
*/
|
||||
export const getFirstOption = (
|
||||
activatable: Activatable | undefined,
|
||||
activatable: Activatable | undefined | ActivatableInstance[],
|
||||
atIndex = 0,
|
||||
): ActivatableOption | undefined => activatable?.instances[0]?.options?.[atIndex]
|
||||
): ActivatableOption | undefined =>
|
||||
(Array.isArray(activatable) ? activatable : activatable?.instances ?? [])[0]?.options?.[atIndex]
|
||||
|
||||
/**
|
||||
* Returns the option value of the first instance of a given activatable entry
|
||||
@@ -202,13 +255,13 @@ export const getFirstOption = (
|
||||
* in the options array, but a different index can be specified.
|
||||
*/
|
||||
export const getFirstOptionOfType = (
|
||||
activatable: Activatable | undefined,
|
||||
type: PredefinedActivatableOption["id"]["type"],
|
||||
activatable: Activatable | undefined | ActivatableInstance[],
|
||||
tag: SelectOptionIdentifier["tag"],
|
||||
atIndex = 0,
|
||||
): number | undefined => {
|
||||
const firstOption = getFirstOption(activatable, atIndex)
|
||||
if (firstOption?.type === "Predefined" && firstOption.id.type === type) {
|
||||
return firstOption.id.value
|
||||
if (firstOption?.type === "Predefined" && firstOption.id.tag === tag) {
|
||||
return splitIdentifierObject(firstOption.id)[1]
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
@@ -219,36 +272,9 @@ export const getFirstOptionOfType = (
|
||||
* specified.
|
||||
*/
|
||||
export const getOptions = (
|
||||
activatable: Activatable | undefined,
|
||||
activatable: Activatable | undefined | ActivatableInstance[],
|
||||
atIndex = 0,
|
||||
): ActivatableOption[] =>
|
||||
activatable?.instances.map(instance => instance.options?.[atIndex]).filter(isNotNullish) ?? []
|
||||
|
||||
/**
|
||||
* Checks if a prerequisite option and an instance option are equal.
|
||||
*/
|
||||
export const equalsOptionPrerequisite = (
|
||||
instanceOption: PredefinedActivatableOption["id"],
|
||||
prerequisiteOption: RequirableSelectOptionIdentifier,
|
||||
): boolean => {
|
||||
switch (prerequisiteOption.tag) {
|
||||
case "General":
|
||||
return (
|
||||
instanceOption.type === "Generic" && prerequisiteOption.general === instanceOption.value
|
||||
)
|
||||
case "Skill":
|
||||
return instanceOption.type === "Skill" && prerequisiteOption.skill === instanceOption.value
|
||||
case "CloseCombatTechnique":
|
||||
return (
|
||||
instanceOption.type === "CloseCombatTechnique" &&
|
||||
prerequisiteOption.close_combat_technique === instanceOption.value
|
||||
)
|
||||
case "RangedCombatTechnique":
|
||||
return (
|
||||
instanceOption.type === "RangedCombatTechnique" &&
|
||||
prerequisiteOption.ranged_combat_technique === instanceOption.value
|
||||
)
|
||||
default:
|
||||
return assertExhaustive(prerequisiteOption)
|
||||
}
|
||||
}
|
||||
(Array.isArray(activatable) ? activatable : activatable?.instances ?? [])
|
||||
.map(instance => instance.options?.[atIndex])
|
||||
.filter(isNotNullish)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,61 @@
|
||||
import { ResolvedSelectOption } from "optolith-database-schema/cache/activatableSelectOptions"
|
||||
import { Advantage } from "optolith-database-schema/types/Advantage"
|
||||
import { ActivatableIdentifier } from "optolith-database-schema/types/_IdentifierGroup"
|
||||
import { FilterApplyingActivatableDependencies } from "../dependencies/filterApplyingDependencies.ts"
|
||||
import { All, GetById, Singleton } from "../getTypes.ts"
|
||||
import { getCreateIdentifierObject } from "../identifier.ts"
|
||||
import { checkPrerequisitesOfAdvantageOrDisadvantage } from "../prerequisites/fullPrerequisiteValidationForType.ts"
|
||||
import { DisplayedActiveActivatable, getActiveActivatables } from "./activatableActive.ts"
|
||||
|
||||
/**
|
||||
* A combination of a static advantage and its dynamic counterpart, extended by
|
||||
* which activation/instance of the entry it represents.
|
||||
*/
|
||||
export type DisplayedActiveAdvantage = DisplayedActiveActivatable<Advantage>
|
||||
|
||||
/**
|
||||
* Returns all advantages with their corresponding dynamic entry, extended by
|
||||
* which activation/instance of the entry it represents.
|
||||
*/
|
||||
export const getActiveAdvantages = (
|
||||
getStaticAdvantageById: GetById.Static.Advantage,
|
||||
dynamicAdvantages: All.Dynamic.Advantages,
|
||||
prerequisiteCapabilities: Parameters<typeof checkPrerequisitesOfAdvantageOrDisadvantage>[4],
|
||||
getSelectOptionsById: (id: ActivatableIdentifier) => ResolvedSelectOption[] | undefined,
|
||||
filterApplyingDependencies: FilterApplyingActivatableDependencies,
|
||||
caps: {
|
||||
startExperienceLevel?: Singleton.Static.ExperienceLevel
|
||||
activeSermonsCount: number
|
||||
activeVisionsCount: number
|
||||
activeCantripsCount: number
|
||||
activeSpellworksCount: number
|
||||
activeMagicalActionsCount: number
|
||||
activeBlessingsCount: number
|
||||
activeLiturgicalChantsCount: number
|
||||
activeCeremoniesCount: number
|
||||
getDynamicSkillById: GetById.Dynamic.Skill
|
||||
getDynamicLiturgicalChantById: GetById.Dynamic.LiturgicalChant
|
||||
getDynamicCeremonyById: GetById.Dynamic.Ceremony
|
||||
getDynamicSpellById: GetById.Dynamic.Spell
|
||||
getDynamicRitualById: GetById.Dynamic.Ritual
|
||||
getDynamicCloseCombatTechniqueById: GetById.Dynamic.CloseCombatTechnique
|
||||
getDynamicRangedCombatTechniqueById: GetById.Dynamic.RangedCombatTechnique
|
||||
},
|
||||
): DisplayedActiveAdvantage[] =>
|
||||
getActiveActivatables(
|
||||
getStaticAdvantageById,
|
||||
dynamicAdvantages,
|
||||
(prerequisites, level, id) =>
|
||||
checkPrerequisitesOfAdvantageOrDisadvantage(
|
||||
prerequisites,
|
||||
level,
|
||||
"Advantage",
|
||||
id,
|
||||
prerequisiteCapabilities,
|
||||
),
|
||||
getSelectOptionsById,
|
||||
getCreateIdentifierObject("Advantage"),
|
||||
filterApplyingDependencies,
|
||||
t10n => t10n.name,
|
||||
caps,
|
||||
)
|
||||
@@ -0,0 +1,60 @@
|
||||
import { Advantage } from "optolith-database-schema/types/Advantage"
|
||||
import { Disadvantage } from "optolith-database-schema/types/Disadvantage"
|
||||
import { MagicalTradition } from "optolith-database-schema/types/specialAbility/MagicalTradition"
|
||||
import { Compare, compareAt } from "../../utils/compare.ts"
|
||||
import { mapNullableDefault } from "../../utils/nullable.ts"
|
||||
import { TranslateMap } from "../../utils/translate.ts"
|
||||
import { DisplayedActiveActivatable } from "./activatableActive.ts"
|
||||
import { DisplayedInactiveActivatable } from "./activatableInactive.ts"
|
||||
|
||||
/**
|
||||
* Filters and sorts the displayed advantages or disadvantages.
|
||||
*/
|
||||
export const filterAndSortDisplayed = <
|
||||
T extends
|
||||
| DisplayedInactiveActivatable<Advantage | Disadvantage>
|
||||
| DisplayedActiveActivatable<Advantage | Disadvantage>,
|
||||
>(
|
||||
visibleDisAdvantages: T[],
|
||||
filterText: string,
|
||||
translateMap: TranslateMap,
|
||||
localeCompare: Compare<string>,
|
||||
) => {
|
||||
const getName = (c: T) => translateMap(c.static.translations)?.name ?? ""
|
||||
return (
|
||||
filterText === ""
|
||||
? visibleDisAdvantages
|
||||
: visibleDisAdvantages.filter(
|
||||
c => getName(c).toLowerCase().includes(filterText.toLowerCase()) ?? false,
|
||||
)
|
||||
).sort(compareAt(getName, localeCompare))
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum number of adventure points that can be spent for advantages and
|
||||
* disadvantages.
|
||||
*/
|
||||
export const maximumAdventurePointsForAdventagesAndDisadvantages = 80
|
||||
|
||||
/**
|
||||
* Returns the maximum number of adventure points that can be spent for magical
|
||||
* advantages and disadvantages.
|
||||
*/
|
||||
export const getMaximumAdventurePointsForMagicalAdvantagesAndDisadvantages = (
|
||||
activeMagicalTraditions: MagicalTradition[],
|
||||
): number =>
|
||||
activeMagicalTraditions.reduce(
|
||||
(currentMax, tradition) =>
|
||||
mapNullableDefault(
|
||||
tradition.alternative_magical_adventure_points_maximum,
|
||||
altMax => Math.min(altMax, currentMax),
|
||||
currentMax,
|
||||
),
|
||||
50,
|
||||
)
|
||||
|
||||
/**
|
||||
* The maximum number of adventure points that can be spent for blessed
|
||||
* advantages and disadvantages.
|
||||
*/
|
||||
export const maximumAdventurePointsForBlessedAdventagesAndDisadvantages = 50
|
||||
@@ -0,0 +1,58 @@
|
||||
import { ResolvedSelectOption } from "optolith-database-schema/cache/activatableSelectOptions"
|
||||
import { Advantage } from "optolith-database-schema/types/Advantage"
|
||||
import { ActivatableIdentifier } from "optolith-database-schema/types/_IdentifierGroup"
|
||||
import { PublicationRefs } from "optolith-database-schema/types/source/_PublicationRef"
|
||||
import { FilterApplyingActivatableDependencies } from "../dependencies/filterApplyingDependencies.ts"
|
||||
import { All, GetById } from "../getTypes.ts"
|
||||
import { getCreateIdentifierObject } from "../identifier.ts"
|
||||
import {
|
||||
checkPrerequisitesOfAdvantageOrDisadvantage,
|
||||
checkPrerequisitesOfGeneralEntryWithLevels,
|
||||
} from "../prerequisites/fullPrerequisiteValidationForType.ts"
|
||||
import { DisplayedInactiveActivatable, getInactiveActivatables } from "./activatableInactive.ts"
|
||||
|
||||
/**
|
||||
* A combination of a static advantage and its dynamic counterpart, extended by
|
||||
* whether the entry can be activated and configuration options.
|
||||
*/
|
||||
export type DisplayedInactiveAdvantage = DisplayedInactiveActivatable<Advantage>
|
||||
|
||||
/**
|
||||
* Returns all advantages with their corresponding dynamic entry, extended by
|
||||
* whether the entry can be activated and configuration options.
|
||||
*/
|
||||
export const getInactiveAdvantages = (
|
||||
staticAdvantages: All.Static.Advantages,
|
||||
getDynamicAdvantageById: GetById.Dynamic.Advantage,
|
||||
isEntryAvailable: (src: PublicationRefs) => boolean,
|
||||
prerequisiteCapabilities: Parameters<typeof checkPrerequisitesOfAdvantageOrDisadvantage>[4],
|
||||
getSelectOptionsById: (id: ActivatableIdentifier) => ResolvedSelectOption[] | undefined,
|
||||
filterApplyingDependencies: FilterApplyingActivatableDependencies,
|
||||
caps: {
|
||||
activeSermonsCount: number
|
||||
activeVisionsCount: number
|
||||
activeSpellworksCount: number
|
||||
getDynamicAdvantageById: GetById.Dynamic.Advantage
|
||||
getDynamicDisadvantageById: GetById.Dynamic.Disadvantage
|
||||
},
|
||||
): DisplayedInactiveAdvantage[] =>
|
||||
getInactiveActivatables(
|
||||
staticAdvantages,
|
||||
getDynamicAdvantageById,
|
||||
isEntryAvailable,
|
||||
(prerequisites, level, id) =>
|
||||
checkPrerequisitesOfAdvantageOrDisadvantage(
|
||||
prerequisites,
|
||||
level,
|
||||
"Advantage",
|
||||
id,
|
||||
prerequisiteCapabilities,
|
||||
),
|
||||
prerequisites =>
|
||||
checkPrerequisitesOfGeneralEntryWithLevels(prerequisites, 1, prerequisiteCapabilities),
|
||||
getSelectOptionsById,
|
||||
getCreateIdentifierObject("Advantage"),
|
||||
filterApplyingDependencies,
|
||||
1,
|
||||
caps,
|
||||
)
|
||||
@@ -0,0 +1,61 @@
|
||||
import { ResolvedSelectOption } from "optolith-database-schema/cache/activatableSelectOptions"
|
||||
import { Disadvantage } from "optolith-database-schema/types/Disadvantage"
|
||||
import { ActivatableIdentifier } from "optolith-database-schema/types/_IdentifierGroup"
|
||||
import { FilterApplyingActivatableDependencies } from "../dependencies/filterApplyingDependencies.ts"
|
||||
import { All, GetById, Singleton } from "../getTypes.ts"
|
||||
import { getCreateIdentifierObject } from "../identifier.ts"
|
||||
import { checkPrerequisitesOfAdvantageOrDisadvantage } from "../prerequisites/fullPrerequisiteValidationForType.ts"
|
||||
import { DisplayedActiveActivatable, getActiveActivatables } from "./activatableActive.ts"
|
||||
|
||||
/**
|
||||
* A combination of a static disadvantage and its dynamic counterpart, extended by
|
||||
* which activation/instance of the entry it represents.
|
||||
*/
|
||||
export type DisplayedActiveDisadvantage = DisplayedActiveActivatable<Disadvantage>
|
||||
|
||||
/**
|
||||
* Returns all disadvantages with their corresponding dynamic entry, extended by
|
||||
* which activation/instance of the entry it represents.
|
||||
*/
|
||||
export const getActiveDisadvantages = (
|
||||
getStaticDisadvantageById: GetById.Static.Disadvantage,
|
||||
dynamicDisadvantages: All.Dynamic.Disadvantages,
|
||||
prerequisiteCapabilities: Parameters<typeof checkPrerequisitesOfAdvantageOrDisadvantage>[4],
|
||||
getSelectOptionsById: (id: ActivatableIdentifier) => ResolvedSelectOption[] | undefined,
|
||||
filterApplyingDependencies: FilterApplyingActivatableDependencies,
|
||||
caps: {
|
||||
startExperienceLevel?: Singleton.Static.ExperienceLevel
|
||||
activeSermonsCount: number
|
||||
activeVisionsCount: number
|
||||
activeCantripsCount: number
|
||||
activeSpellworksCount: number
|
||||
activeMagicalActionsCount: number
|
||||
activeBlessingsCount: number
|
||||
activeLiturgicalChantsCount: number
|
||||
activeCeremoniesCount: number
|
||||
getDynamicSkillById: GetById.Dynamic.Skill
|
||||
getDynamicLiturgicalChantById: GetById.Dynamic.LiturgicalChant
|
||||
getDynamicCeremonyById: GetById.Dynamic.Ceremony
|
||||
getDynamicSpellById: GetById.Dynamic.Spell
|
||||
getDynamicRitualById: GetById.Dynamic.Ritual
|
||||
getDynamicCloseCombatTechniqueById: GetById.Dynamic.CloseCombatTechnique
|
||||
getDynamicRangedCombatTechniqueById: GetById.Dynamic.RangedCombatTechnique
|
||||
},
|
||||
): DisplayedActiveDisadvantage[] =>
|
||||
getActiveActivatables(
|
||||
getStaticDisadvantageById,
|
||||
dynamicDisadvantages,
|
||||
(prerequisites, level, id) =>
|
||||
checkPrerequisitesOfAdvantageOrDisadvantage(
|
||||
prerequisites,
|
||||
level,
|
||||
"Disadvantage",
|
||||
id,
|
||||
prerequisiteCapabilities,
|
||||
),
|
||||
getSelectOptionsById,
|
||||
getCreateIdentifierObject("Disadvantage"),
|
||||
filterApplyingDependencies,
|
||||
t10n => t10n.name,
|
||||
caps,
|
||||
)
|
||||
@@ -0,0 +1,58 @@
|
||||
import { ResolvedSelectOption } from "optolith-database-schema/cache/activatableSelectOptions"
|
||||
import { Disadvantage } from "optolith-database-schema/types/Disadvantage"
|
||||
import { ActivatableIdentifier } from "optolith-database-schema/types/_IdentifierGroup"
|
||||
import { PublicationRefs } from "optolith-database-schema/types/source/_PublicationRef"
|
||||
import { FilterApplyingActivatableDependencies } from "../dependencies/filterApplyingDependencies.ts"
|
||||
import { All, GetById } from "../getTypes.ts"
|
||||
import { getCreateIdentifierObject } from "../identifier.ts"
|
||||
import {
|
||||
checkPrerequisitesOfAdvantageOrDisadvantage,
|
||||
checkPrerequisitesOfGeneralEntryWithLevels,
|
||||
} from "../prerequisites/fullPrerequisiteValidationForType.ts"
|
||||
import { DisplayedInactiveActivatable, getInactiveActivatables } from "./activatableInactive.ts"
|
||||
|
||||
/**
|
||||
* A combination of a static disadvantage and its dynamic counterpart, extended
|
||||
* by whether the entry can be activated and configuration options.
|
||||
*/
|
||||
export type DisplayedInactiveDisadvantage = DisplayedInactiveActivatable<Disadvantage>
|
||||
|
||||
/**
|
||||
* Returns all disadvantages with their corresponding dynamic entry, extended by
|
||||
* whether the entry can be activated and configuration options.
|
||||
*/
|
||||
export const getInactiveDisadvantages = (
|
||||
staticDisadvantages: All.Static.Disadvantages,
|
||||
getDynamicDisadvantageById: GetById.Dynamic.Disadvantage,
|
||||
isEntryAvailable: (src: PublicationRefs) => boolean,
|
||||
prerequisiteCapabilities: Parameters<typeof checkPrerequisitesOfAdvantageOrDisadvantage>[4],
|
||||
getSelectOptionsById: (id: ActivatableIdentifier) => ResolvedSelectOption[] | undefined,
|
||||
filterApplyingDependencies: FilterApplyingActivatableDependencies,
|
||||
caps: {
|
||||
activeSermonsCount: number
|
||||
activeVisionsCount: number
|
||||
activeSpellworksCount: number
|
||||
getDynamicAdvantageById: GetById.Dynamic.Advantage
|
||||
getDynamicDisadvantageById: GetById.Dynamic.Disadvantage
|
||||
},
|
||||
): DisplayedInactiveDisadvantage[] =>
|
||||
getInactiveActivatables(
|
||||
staticDisadvantages,
|
||||
getDynamicDisadvantageById,
|
||||
isEntryAvailable,
|
||||
(prerequisites, level, id) =>
|
||||
checkPrerequisitesOfAdvantageOrDisadvantage(
|
||||
prerequisites,
|
||||
level,
|
||||
"Disadvantage",
|
||||
id,
|
||||
prerequisiteCapabilities,
|
||||
),
|
||||
prerequisites =>
|
||||
checkPrerequisitesOfGeneralEntryWithLevels(prerequisites, 1, prerequisiteCapabilities),
|
||||
getSelectOptionsById,
|
||||
getCreateIdentifierObject("Disadvantage"),
|
||||
filterApplyingDependencies,
|
||||
1,
|
||||
caps,
|
||||
)
|
||||
@@ -1,5 +1,4 @@
|
||||
import { MagicalTradition } from "optolith-database-schema/types/specialAbility/MagicalTradition"
|
||||
import { mapNullableDefault } from "../../utils/nullable.ts"
|
||||
import { Activatable, isActive } from "./activatableEntry.ts"
|
||||
|
||||
/**
|
||||
@@ -23,20 +22,3 @@ export const isSpellcaster = (
|
||||
spellcaster: Activatable | undefined,
|
||||
magicalTraditions: Activatable[],
|
||||
): boolean => isActive(spellcaster) && magicalTraditions.some(isActive)
|
||||
|
||||
/**
|
||||
* Returns the maximum number of adventure points that can be spent for magical
|
||||
* advantages and disadvantages.
|
||||
*/
|
||||
export const getMaximumAdventurePointsForMagicalAdvantagesAndDisadvantages = (
|
||||
activeMagicalTraditions: MagicalTradition[],
|
||||
): number =>
|
||||
activeMagicalTraditions.reduce(
|
||||
(currentMax, tradition) =>
|
||||
mapNullableDefault(
|
||||
tradition.alternative_magical_adventure_points_maximum,
|
||||
altMax => Math.min(altMax, currentMax),
|
||||
currentMax,
|
||||
),
|
||||
50,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { ActivatableOption } from "../activatable/activatableEntry.ts"
|
||||
|
||||
/**
|
||||
* Bound adventure points are granted by the GM and can only be spent on the
|
||||
* entry. They don’t affect the costs of buying the activatable entry. They may
|
||||
* specify for which configuration of an entry they count, just like a
|
||||
* prerequisite.
|
||||
*/
|
||||
export type BoundAdventurePointsForActivatable = {
|
||||
/**
|
||||
* The granted adventure points.
|
||||
*/
|
||||
adventurePoints: number
|
||||
|
||||
/**
|
||||
* The level for which the adventure points have been granted.
|
||||
*/
|
||||
level?: number
|
||||
|
||||
/**
|
||||
* The set of options for which the adventure points have been granted.
|
||||
*/
|
||||
options?: ActivatableOption[]
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* The accumulated used adventure points value for an entry. It is
|
||||
* split by used bound and used general adventure points.
|
||||
*/
|
||||
export type AdventurePointsCache = {
|
||||
/**
|
||||
* The used general adventure points.
|
||||
*/
|
||||
general: number
|
||||
|
||||
/**
|
||||
* The used bound adventure points.
|
||||
*/
|
||||
bound: number
|
||||
}
|
||||
|
||||
/**
|
||||
* An empty adventure points cache.
|
||||
*/
|
||||
export const emptyAdventurePointsCache: AdventurePointsCache = {
|
||||
general: 0,
|
||||
bound: 0,
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of adventure points from the cache.
|
||||
*/
|
||||
export const getTotalAdventurePointsFromCache = (cache: AdventurePointsCache): number =>
|
||||
cache.general + cache.bound
|
||||
|
||||
/**
|
||||
* Adds two adventure points caches.
|
||||
*/
|
||||
export const addAdventurePointsCaches = (
|
||||
a: AdventurePointsCache,
|
||||
b: AdventurePointsCache,
|
||||
): AdventurePointsCache => ({
|
||||
general: a.general + b.general,
|
||||
bound: a.bound + b.bound,
|
||||
})
|
||||
@@ -0,0 +1,71 @@
|
||||
import {
|
||||
ActivatableSelectOptionsCache,
|
||||
ResolvedSelectOption,
|
||||
} from "optolith-database-schema/cache/activatableSelectOptions"
|
||||
import { ActivatableIdentifier } from "optolith-database-schema/types/_IdentifierGroup"
|
||||
import { assertExhaustive } from "../utils/typeSafety.ts"
|
||||
|
||||
/**
|
||||
* Returns the select options for a given activatable identifier from the
|
||||
* given cache.
|
||||
*/
|
||||
export const getSelectOptionsFromCacheById = (
|
||||
cache: ActivatableSelectOptionsCache,
|
||||
id: ActivatableIdentifier,
|
||||
): ResolvedSelectOption[] | undefined => {
|
||||
// prettier-ignore
|
||||
switch (id.tag) {
|
||||
case "Advantage": return cache.advantages[id.advantage]
|
||||
case "Disadvantage": return cache.disadvantages[id.disadvantage]
|
||||
case "GeneralSpecialAbility": return cache.generalSpecialAbilities[id.general_special_ability]
|
||||
case "FatePointSpecialAbility": return cache.fatePointSpecialAbilities[id.fate_point_special_ability]
|
||||
case "CombatSpecialAbility": return cache.combatSpecialAbilities[id.combat_special_ability]
|
||||
case "MagicalSpecialAbility": return cache.magicalSpecialAbilities[id.magical_special_ability]
|
||||
case "StaffEnchantment": return cache.staffEnchantments[id.staff_enchantment]
|
||||
case "FamiliarSpecialAbility": return cache.familiarSpecialAbilities[id.familiar_special_ability]
|
||||
case "KarmaSpecialAbility": return cache.karmaSpecialAbilities[id.karma_special_ability]
|
||||
case "ProtectiveWardingCircleSpecialAbility": return cache.protectiveWardingCircleSpecialAbilities[id.protective_warding_circle_special_ability]
|
||||
case "CombatStyleSpecialAbility": return cache.combatStyleSpecialAbilities[id.combat_style_special_ability]
|
||||
case "AdvancedCombatSpecialAbility": return cache.advancedCombatSpecialAbilities[id.advanced_combat_special_ability]
|
||||
case "CommandSpecialAbility": return cache.commandSpecialAbilities[id.command_special_ability]
|
||||
case "MagicStyleSpecialAbility": return cache.magicStyleSpecialAbilities[id.magic_style_special_ability]
|
||||
case "AdvancedMagicalSpecialAbility": return cache.advancedMagicalSpecialAbilities[id.advanced_magical_special_ability]
|
||||
case "SpellSwordEnchantment": return cache.spellSwordEnchantments[id.spell_sword_enchantment]
|
||||
case "DaggerRitual": return cache.daggerRituals[id.dagger_ritual]
|
||||
case "InstrumentEnchantment": return cache.instrumentEnchantments[id.instrument_enchantment]
|
||||
case "AttireEnchantment": return cache.attireEnchantments[id.attire_enchantment]
|
||||
case "OrbEnchantment": return cache.orbEnchantments[id.orb_enchantment]
|
||||
case "WandEnchantment": return cache.wandEnchantments[id.wand_enchantment]
|
||||
case "BrawlingSpecialAbility": return cache.brawlingSpecialAbilities[id.brawling_special_ability]
|
||||
case "AncestorGlyph": return cache.ancestorGlyphs[id.ancestor_glyph]
|
||||
case "CeremonialItemSpecialAbility": return cache.ceremonialItemSpecialAbilities[id.ceremonial_item_special_ability]
|
||||
case "Sermon": return cache.sermons[id.sermon]
|
||||
case "LiturgicalStyleSpecialAbility": return cache.liturgicalStyleSpecialAbilities[id.liturgical_style_special_ability]
|
||||
case "AdvancedKarmaSpecialAbility": return cache.advancedKarmaSpecialAbilities[id.advanced_karma_special_ability]
|
||||
case "Vision": return cache.visions[id.vision]
|
||||
case "MagicalTradition": return cache.magicalTraditions[id.magical_tradition]
|
||||
case "BlessedTradition": return cache.blessedTraditions[id.blessed_tradition]
|
||||
case "PactGift": return cache.pactGifts[id.pact_gift]
|
||||
case "SikaryanDrainSpecialAbility": return cache.sikaryanDrainSpecialAbilities[id.sikaryan_drain_special_ability]
|
||||
case "VampiricGift": return cache.vampiricGifts[id.vampiric_gift]
|
||||
case "LycantropicGift": return cache.lycantropicGifts[id.lycantropic_gift]
|
||||
case "SkillStyleSpecialAbility": return cache.skillStyleSpecialAbilities[id.skill_style_special_ability]
|
||||
case "AdvancedSkillSpecialAbility": return cache.advancedSkillSpecialAbilities[id.advanced_skill_special_ability]
|
||||
case "ArcaneOrbEnchantment": return cache.arcaneOrbEnchantments[id.arcane_orb_enchantment]
|
||||
case "CauldronEnchantment": return cache.cauldronEnchantments[id.cauldron_enchantment]
|
||||
case "FoolsHatEnchantment": return cache.foolsHatEnchantments[id.fools_hat_enchantment]
|
||||
case "ToyEnchantment": return cache.toyEnchantments[id.toy_enchantment]
|
||||
case "BowlEnchantment": return cache.bowlEnchantments[id.bowl_enchantment]
|
||||
case "FatePointSexSpecialAbility": return cache.fatePointSexSpecialAbilities[id.fate_point_sex_special_ability]
|
||||
case "SexSpecialAbility": return cache.sexSpecialAbilities[id.sex_special_ability]
|
||||
case "WeaponEnchantment": return cache.weaponEnchantments[id.weapon_enchantment]
|
||||
case "SickleRitual": return cache.sickleRituals[id.sickle_ritual]
|
||||
case "RingEnchantment": return cache.ringEnchantments[id.ring_enchantment]
|
||||
case "ChronicleEnchantment": return cache.chronicleEnchantments[id.chronicle_enchantment]
|
||||
case "Krallenkettenzauber": return cache.krallenkettenzauber[id.krallenkettenzauber]
|
||||
case "Trinkhornzauber": return cache.trinkhornzauber[id.trinkhornzauber]
|
||||
// @ts-expect-error Magical Signs do not have select options; this is for forward compatibility
|
||||
case "MagicalSign": return cache.magicalSigns?.[id.magical_sign]
|
||||
default: assertExhaustive(id)
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,15 @@ import {
|
||||
ActivatableIdentifier,
|
||||
SkillWithEnhancementsIdentifier,
|
||||
} from "optolith-database-schema/types/_IdentifierGroup"
|
||||
import { Preconditions } from "optolith-database-schema/types/prerequisites/ConditionalPrerequisites"
|
||||
import { assertExhaustive } from "../../utils/typeSafety.ts"
|
||||
import { ActivatableDependency } from "../activatable/activatableDependency.ts"
|
||||
import { All, GetById } from "../getTypes.ts"
|
||||
import { countMatchingRatedMinimumNumberEntries } from "../prerequisites/single/ratedMinimumNumberPrerequisiteValidation.ts"
|
||||
import { sumMatchingRatedSumEntries } from "../prerequisites/single/ratedSumPrerequisiteValidation.ts"
|
||||
import { GetDynamicLiturgicalChantsByAspectCapability } from "../rated/liturgicalChant.ts"
|
||||
import { RatedDependency } from "../rated/ratedDependency.ts"
|
||||
import { GetDynamicSpellworksByPropertyCapability } from "../rated/spell.ts"
|
||||
|
||||
/**
|
||||
* A function that filters a list of dependencies by if they apply to the
|
||||
@@ -32,11 +40,11 @@ import { RatedDependency } from "../rated/ratedDependency.ts"
|
||||
export type FilterApplyingRatedDependencies = (dependencies: RatedDependency[]) => RatedDependency[]
|
||||
|
||||
/**
|
||||
* `flattenDependencies` flattens the list of dependencies to usable values.
|
||||
* That means, optional dependencies (objects) will be evaluated and will be
|
||||
* included in the resulting list, depending on whether it has to follow the
|
||||
* optional dependency or not. The result is a plain `List` of all non-optional
|
||||
* dependencies.
|
||||
* `filterApplyingRatedDependencies` flattens the list of dependencies to usable
|
||||
* values. That means, optional dependencies (objects) will be evaluated and
|
||||
* will be included in the resulting list, depending on whether it has to follow
|
||||
* the optional dependency or not. The result is a plain `List` of all
|
||||
* non-optional dependencies.
|
||||
*/
|
||||
export const filterApplyingRatedDependencies =
|
||||
(
|
||||
@@ -44,6 +52,15 @@ export const filterApplyingRatedDependencies =
|
||||
id: ActivatableIdentifier | SkillWithEnhancementsIdentifier,
|
||||
index: number,
|
||||
) => boolean,
|
||||
caps: {
|
||||
getDynamicSkillById: GetById.Dynamic.Skill
|
||||
dynamicCloseCombatTechniques: All.Dynamic.CloseCombatTechniques
|
||||
dynamicRangedCombatTechniques: All.Dynamic.RangedCombatTechniques
|
||||
getDynamicSpellsByProperty: GetDynamicSpellworksByPropertyCapability
|
||||
getDynamicRitualsByProperty: GetDynamicSpellworksByPropertyCapability
|
||||
getDynamicLiturgicalChantsByAspect: GetDynamicLiturgicalChantsByAspectCapability
|
||||
getDynamicCeremoniesByAspect: GetDynamicLiturgicalChantsByAspectCapability
|
||||
},
|
||||
): FilterApplyingRatedDependencies =>
|
||||
dependencies =>
|
||||
dependencies.filter(dep => {
|
||||
@@ -51,6 +68,56 @@ export const filterApplyingRatedDependencies =
|
||||
return checkMultipleDisjunctionPartsAreValid(dep.source, dep.index)
|
||||
}
|
||||
|
||||
switch (dep.value.tag) {
|
||||
case "Fixed":
|
||||
return true
|
||||
case "MinimumNumberAtMinimumValue":
|
||||
return (
|
||||
countMatchingRatedMinimumNumberEntries(
|
||||
dep.value.minimumValue,
|
||||
dep.value.target,
|
||||
caps,
|
||||
) <= dep.value.minimumCount
|
||||
)
|
||||
case "Sum":
|
||||
return sumMatchingRatedSumEntries(dep.value.targetIds, caps) <= dep.value.sum
|
||||
default:
|
||||
return assertExhaustive(dep.value)
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* A function that filters a list of dependencies by if they apply to the
|
||||
* entry, since some dependencies target multiple entries but only one of them
|
||||
* has to apply. And if other entries match the dependency, the dependency does
|
||||
* not need to be matched by this entry.
|
||||
*/
|
||||
export type FilterApplyingActivatableDependencies = (
|
||||
dependencies: ActivatableDependency[],
|
||||
) => ActivatableDependency[]
|
||||
|
||||
/**
|
||||
* `filterApplyingActivatableDependencies` flattens the list of dependencies to
|
||||
* usable values. That means, optional dependencies (objects) will be evaluated
|
||||
* and will be included in the resulting list, depending on whether it has to
|
||||
* follow the optional dependency or not. The result is a plain `List` of all
|
||||
* non-optional dependencies.
|
||||
*/
|
||||
export const filterApplyingActivatableDependencies =
|
||||
(
|
||||
checkMultipleDisjunctionPartsAreValid: (id: ActivatableIdentifier, index: number) => boolean,
|
||||
checkPreconditions: (prerequisites: Preconditions) => boolean,
|
||||
): FilterApplyingActivatableDependencies =>
|
||||
dependencies =>
|
||||
dependencies.filter(dep => {
|
||||
if (dep.when !== undefined && !checkPreconditions(dep.when)) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (dep.isPartOfDisjunction) {
|
||||
return checkMultipleDisjunctionPartsAreValid(dep.sourceId, dep.index)
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
|
||||
@@ -1,20 +1,189 @@
|
||||
import { Draft } from "@reduxjs/toolkit"
|
||||
import {
|
||||
AdvantageIdentifier,
|
||||
DisadvantageIdentifier,
|
||||
} from "optolith-database-schema/types/_Identifier"
|
||||
import {
|
||||
LiturgyIdentifier,
|
||||
SpecialAbilityIdentifier,
|
||||
SpellworkIdentifier,
|
||||
} from "optolith-database-schema/types/_IdentifierGroup"
|
||||
import {
|
||||
AdvantageDisadvantagePrerequisites,
|
||||
GeneralPrerequisites,
|
||||
LiturgyPrerequisites,
|
||||
PlainGeneralPrerequisites,
|
||||
SpellworkPrerequisites,
|
||||
} from "optolith-database-schema/types/_Prerequisite"
|
||||
import { RangeBounds } from "../../utils/range.ts"
|
||||
import { Character } from "../character.ts"
|
||||
import { registerOrUnregisterPlainPrerequisites } from "./prerequisiteCombinatorRegistrationAsDependency.ts"
|
||||
import {
|
||||
registerOrUnregisterPlainPrerequisites,
|
||||
registerOrUnregisterPrerequisitesForLevels,
|
||||
} from "./prerequisiteCombinatorRegistrationAsDependency.ts"
|
||||
import {
|
||||
registerOrUnregisterPrerequisiteOfAdvantageOrDisadvantageAsDependency,
|
||||
registerOrUnregisterPrerequisiteOfGeneralAsDependency,
|
||||
registerOrUnregisterPrerequisiteOfLiturgyAsDependency,
|
||||
registerOrUnregisterPrerequisiteOfSpellworkAsDependency,
|
||||
} from "./prerequisiteRegistrationAsDependencyForType.ts"
|
||||
import { RegistrationMethod } from "./registrationHelpers.ts"
|
||||
|
||||
/**
|
||||
* Register or unregister all prerequisites of a general entry as dependencies
|
||||
* on the character's draft.
|
||||
*/
|
||||
export const registerOrUnregisterPrerequisitesOfPlainGeneralAsDependencies = (
|
||||
method: RegistrationMethod,
|
||||
character: Draft<Character>,
|
||||
p: PlainGeneralPrerequisites,
|
||||
sourceId: SpecialAbilityIdentifier,
|
||||
capabilities: {
|
||||
closeCombatTechniqueIds: number[]
|
||||
rangedCombatTechniqueIds: number[]
|
||||
blessedTraditionChurchIds: number[]
|
||||
blessedTraditionShamanisticIds: number[]
|
||||
magicalTraditionIds: number[]
|
||||
magicalTraditionIdsThatCanLearnRituals: number[]
|
||||
magicalTraditionIdsThatCanBindFamiliars: number[]
|
||||
getSpellIdsByPropertyId: (id: number) => number[]
|
||||
getRitualIdsByPropertyId: (id: number) => number[]
|
||||
getLiturgicalChantIdsByAspectId: (id: number) => number[]
|
||||
getCeremonyIdsByAspectId: (id: number) => number[]
|
||||
},
|
||||
): void =>
|
||||
registerOrUnregisterPlainPrerequisites(
|
||||
method,
|
||||
character,
|
||||
p,
|
||||
sourceId,
|
||||
registerOrUnregisterPrerequisiteOfGeneralAsDependency,
|
||||
capabilities,
|
||||
)
|
||||
|
||||
/**
|
||||
* Register or unregister all prerequisites of a general entry as dependencies
|
||||
* on the character's draft.
|
||||
*/
|
||||
export const registerOrUnregisterPrerequisitesOfGeneralAsDependencies = (
|
||||
method: RegistrationMethod,
|
||||
character: Draft<Character>,
|
||||
p: GeneralPrerequisites,
|
||||
sourceId: SpecialAbilityIdentifier,
|
||||
levelRange: RangeBounds,
|
||||
capabilities: {
|
||||
closeCombatTechniqueIds: number[]
|
||||
rangedCombatTechniqueIds: number[]
|
||||
blessedTraditionChurchIds: number[]
|
||||
blessedTraditionShamanisticIds: number[]
|
||||
magicalTraditionIds: number[]
|
||||
magicalTraditionIdsThatCanLearnRituals: number[]
|
||||
magicalTraditionIdsThatCanBindFamiliars: number[]
|
||||
getSpellIdsByPropertyId: (id: number) => number[]
|
||||
getRitualIdsByPropertyId: (id: number) => number[]
|
||||
getLiturgicalChantIdsByAspectId: (id: number) => number[]
|
||||
getCeremonyIdsByAspectId: (id: number) => number[]
|
||||
},
|
||||
): void =>
|
||||
registerOrUnregisterPrerequisitesForLevels(
|
||||
method,
|
||||
character,
|
||||
p,
|
||||
levelRange,
|
||||
sourceId,
|
||||
registerOrUnregisterPrerequisiteOfGeneralAsDependency,
|
||||
capabilities,
|
||||
)
|
||||
|
||||
/**
|
||||
* Register or unregister all prerequisites of a profession as dependencies on
|
||||
* the character's draft.
|
||||
*/
|
||||
// export const registerOrUnregisterPrerequisitesOfProfessionAsDependencies = (
|
||||
// method: RegistrationMethod,
|
||||
// character: Draft<Character>,
|
||||
// p: ProfessionPrerequisites,
|
||||
// sourceId: SpellworkIdentifier,
|
||||
// ): void =>
|
||||
// registerOrUnregisterPlainPrerequisites(
|
||||
// method,
|
||||
// character,
|
||||
// p,
|
||||
// sourceId,
|
||||
// registerOrUnregisterPrerequisiteOfSpellworkAsDependency,
|
||||
// )
|
||||
|
||||
/**
|
||||
* Register or unregister all prerequisites of an advantage or disadvantage as
|
||||
* dependencies on the character's draft.
|
||||
*/
|
||||
export const registerOrUnregisterPrerequisitesOfAdvantageOrDisadvantageAsDependencies = (
|
||||
method: RegistrationMethod,
|
||||
character: Draft<Character>,
|
||||
p: AdvantageDisadvantagePrerequisites,
|
||||
sourceId: AdvantageIdentifier | DisadvantageIdentifier,
|
||||
levelRange: RangeBounds,
|
||||
capabilities: {
|
||||
ancestorBloodAdvantageIds: number[]
|
||||
closeCombatTechniqueIds: number[]
|
||||
rangedCombatTechniqueIds: number[]
|
||||
blessedTraditionChurchIds: number[]
|
||||
blessedTraditionShamanisticIds: number[]
|
||||
magicalTraditionIds: number[]
|
||||
magicalTraditionIdsThatCanLearnRituals: number[]
|
||||
magicalTraditionIdsThatCanBindFamiliars: number[]
|
||||
getSpellIdsByPropertyId: (id: number) => number[]
|
||||
getRitualIdsByPropertyId: (id: number) => number[]
|
||||
getLiturgicalChantIdsByAspectId: (id: number) => number[]
|
||||
getCeremonyIdsByAspectId: (id: number) => number[]
|
||||
},
|
||||
): void =>
|
||||
registerOrUnregisterPrerequisitesForLevels(
|
||||
method,
|
||||
character,
|
||||
p,
|
||||
levelRange,
|
||||
sourceId,
|
||||
registerOrUnregisterPrerequisiteOfAdvantageOrDisadvantageAsDependency,
|
||||
capabilities,
|
||||
)
|
||||
|
||||
/**
|
||||
* Register or unregister all prerequisites of an arcane tradition as
|
||||
* dependencies on the character's draft.
|
||||
*/
|
||||
// export const registerOrUnregisterPrerequisitesOfArcaneTraditionAsDependencies = (
|
||||
// method: RegistrationMethod,
|
||||
// character: Draft<Character>,
|
||||
// p: ArcaneTraditionPrerequisites,
|
||||
// sourceId: SpellworkIdentifier,
|
||||
// ): void =>
|
||||
// registerOrUnregisterPlainPrerequisites(
|
||||
// method,
|
||||
// character,
|
||||
// p,
|
||||
// sourceId,
|
||||
// registerOrUnregisterPrerequisiteOfSpellworkAsDependency,
|
||||
// )
|
||||
|
||||
/**
|
||||
* Register or unregister all prerequisites of a personality trait as
|
||||
* dependencies on the character's draft.
|
||||
*/
|
||||
// export const registerOrUnregisterPrerequisitesOfPersonalityTraitAsDependencies = (
|
||||
// method: RegistrationMethod,
|
||||
// character: Draft<Character>,
|
||||
// p: PersonalityTraitPrerequisites,
|
||||
// sourceId: SpellworkIdentifier,
|
||||
// ): void =>
|
||||
// registerOrUnregisterPlainPrerequisites(
|
||||
// method,
|
||||
// character,
|
||||
// p,
|
||||
// sourceId,
|
||||
// registerOrUnregisterPrerequisiteOfSpellworkAsDependency,
|
||||
// )
|
||||
|
||||
/**
|
||||
* Register or unregister all prerequisites of a spellwork as dependencies on
|
||||
* the character's draft.
|
||||
@@ -50,3 +219,93 @@ export const registerOrUnregisterPrerequisitesOfLiturgyAsDependencies = (
|
||||
sourceId,
|
||||
registerOrUnregisterPrerequisiteOfLiturgyAsDependency,
|
||||
)
|
||||
|
||||
/**
|
||||
* Register or unregister all prerequisites of an influence as dependencies on
|
||||
* the character's draft.
|
||||
*/
|
||||
// export const registerOrUnregisterPrerequisitesOfInfluenceAsDependencies = (
|
||||
// method: RegistrationMethod,
|
||||
// character: Draft<Character>,
|
||||
// p: InfluencePrerequisites,
|
||||
// sourceId: SpellworkIdentifier,
|
||||
// ): void =>
|
||||
// registerOrUnregisterPlainPrerequisites(
|
||||
// method,
|
||||
// character,
|
||||
// p,
|
||||
// sourceId,
|
||||
// registerOrUnregisterPrerequisiteOfSpellworkAsDependency,
|
||||
// )
|
||||
|
||||
/**
|
||||
* Register or unregister all prerequisites of a language as dependencies on the
|
||||
* character's draft.
|
||||
*/
|
||||
// export const registerOrUnregisterPrerequisitesOfLanguageAsDependencies = (
|
||||
// method: RegistrationMethod,
|
||||
// character: Draft<Character>,
|
||||
// p: LanguagePrerequisites,
|
||||
// sourceId: SpellworkIdentifier,
|
||||
// ): void =>
|
||||
// registerOrUnregisterPlainPrerequisites(
|
||||
// method,
|
||||
// character,
|
||||
// p,
|
||||
// sourceId,
|
||||
// registerOrUnregisterPrerequisiteOfSpellworkAsDependency,
|
||||
// )
|
||||
|
||||
/**
|
||||
* Register or unregister all prerequisites of an animist power as dependencies
|
||||
* on the character's draft.
|
||||
*/
|
||||
// export const registerOrUnregisterPrerequisitesOfAnimistPowerAsDependencies = (
|
||||
// method: RegistrationMethod,
|
||||
// character: Draft<Character>,
|
||||
// p: AnimistPowerPrerequisites,
|
||||
// sourceId: SpellworkIdentifier,
|
||||
// ): void =>
|
||||
// registerOrUnregisterPlainPrerequisites(
|
||||
// method,
|
||||
// character,
|
||||
// p,
|
||||
// sourceId,
|
||||
// registerOrUnregisterPrerequisiteOfSpellworkAsDependency,
|
||||
// )
|
||||
|
||||
/**
|
||||
* Register or unregister all prerequisites of a geode ritual as dependencies on
|
||||
* the character's draft.
|
||||
*/
|
||||
// export const registerOrUnregisterPrerequisitesOfGeodeRitualAsDependencies = (
|
||||
// method: RegistrationMethod,
|
||||
// character: Draft<Character>,
|
||||
// p: GeodeRitualPrerequisites,
|
||||
// sourceId: SpellworkIdentifier,
|
||||
// ): void =>
|
||||
// registerOrUnregisterPlainPrerequisites(
|
||||
// method,
|
||||
// character,
|
||||
// p,
|
||||
// sourceId,
|
||||
// registerOrUnregisterPrerequisiteOfSpellworkAsDependency,
|
||||
// )
|
||||
|
||||
/**
|
||||
* Register or unregister all prerequisites of an enhancement as dependencies on
|
||||
* the character's draft.
|
||||
*/
|
||||
// export const registerOrUnregisterPrerequisitesOfEnhancementAsDependencies = (
|
||||
// method: RegistrationMethod,
|
||||
// character: Draft<Character>,
|
||||
// p: EnhancementPrerequisites,
|
||||
// sourceId: SpellworkIdentifier,
|
||||
// ): void =>
|
||||
// registerOrUnregisterPlainPrerequisites(
|
||||
// method,
|
||||
// character,
|
||||
// p,
|
||||
// sourceId,
|
||||
// registerOrUnregisterPrerequisiteOfSpellworkAsDependency,
|
||||
// )
|
||||
|
||||
@@ -7,41 +7,57 @@ import {
|
||||
PrerequisitesElement,
|
||||
PrerequisitesForLevels,
|
||||
} from "optolith-database-schema/types/_Prerequisite"
|
||||
import { RangeBounds, isInRange } from "../../utils/range.ts"
|
||||
import { assertExhaustive } from "../../utils/typeSafety.ts"
|
||||
import { Character } from "../character.ts"
|
||||
import { RegistrationFunction, RegistrationMethod } from "./registrationHelpers.ts"
|
||||
|
||||
const registerOrUnregisterPrerequisiteGroup = <T, SourceId>(
|
||||
const registerOrUnregisterPrerequisiteGroup = <T, SourceId, C>(
|
||||
method: RegistrationMethod,
|
||||
character: Draft<Character>,
|
||||
group: PrerequisiteGroup<T>,
|
||||
sourceId: SourceId,
|
||||
index: number,
|
||||
registerOrUnregister: RegistrationFunction<T, SourceId>,
|
||||
registerOrUnregister: RegistrationFunction<T, SourceId, C>,
|
||||
...rest: C extends undefined ? [] : [capabilities: C]
|
||||
): void =>
|
||||
group.list.forEach(p => registerOrUnregister(method, character, p, sourceId, index, false))
|
||||
group.list.forEach(p =>
|
||||
registerOrUnregister(method, character, p, sourceId, index, false, ...rest),
|
||||
)
|
||||
|
||||
const registerOrUnregisterPrerequisiteDisjunction = <T, SourceId>(
|
||||
const registerOrUnregisterPrerequisiteDisjunction = <T, SourceId, C>(
|
||||
method: RegistrationMethod,
|
||||
character: Draft<Character>,
|
||||
disjunction: PrerequisitesDisjunction<T>,
|
||||
sourceId: SourceId,
|
||||
index: number,
|
||||
registerOrUnregister: RegistrationFunction<T, SourceId>,
|
||||
registerOrUnregister: RegistrationFunction<T, SourceId, C>,
|
||||
...rest: C extends undefined ? [] : [capabilities: C]
|
||||
): void =>
|
||||
disjunction.list.forEach(p => registerOrUnregister(method, character, p, sourceId, index, true))
|
||||
disjunction.list.forEach(p =>
|
||||
registerOrUnregister(method, character, p, sourceId, index, true, ...rest),
|
||||
)
|
||||
|
||||
const registerOrUnregisterPrerequisiteElement = <T, SourceId>(
|
||||
const registerOrUnregisterPrerequisiteElement = <T, SourceId, C>(
|
||||
method: RegistrationMethod,
|
||||
character: Draft<Character>,
|
||||
element: PrerequisitesElement<T>,
|
||||
sourceId: SourceId,
|
||||
index: number,
|
||||
registerOrUnregister: RegistrationFunction<T, SourceId>,
|
||||
registerOrUnregister: RegistrationFunction<T, SourceId, C>,
|
||||
...rest: C extends undefined ? [] : [capabilities: C]
|
||||
): void => {
|
||||
switch (element.tag) {
|
||||
case "Single":
|
||||
return registerOrUnregister(method, character, element.single, sourceId, index, false)
|
||||
return registerOrUnregister(
|
||||
method,
|
||||
character,
|
||||
element.single,
|
||||
sourceId,
|
||||
index,
|
||||
false,
|
||||
...rest,
|
||||
)
|
||||
case "Group":
|
||||
return registerOrUnregisterPrerequisiteGroup(
|
||||
method,
|
||||
@@ -50,6 +66,7 @@ const registerOrUnregisterPrerequisiteElement = <T, SourceId>(
|
||||
sourceId,
|
||||
index,
|
||||
registerOrUnregister,
|
||||
...rest,
|
||||
)
|
||||
case "Disjunction":
|
||||
return registerOrUnregisterPrerequisiteDisjunction(
|
||||
@@ -59,6 +76,7 @@ const registerOrUnregisterPrerequisiteElement = <T, SourceId>(
|
||||
sourceId,
|
||||
index,
|
||||
registerOrUnregister,
|
||||
...rest,
|
||||
)
|
||||
default:
|
||||
return assertExhaustive(element)
|
||||
@@ -68,12 +86,13 @@ const registerOrUnregisterPrerequisiteElement = <T, SourceId>(
|
||||
/**
|
||||
* Checks a plain list of prerequisites if they’re matched.
|
||||
*/
|
||||
export const registerOrUnregisterPlainPrerequisites = <T, SourceId>(
|
||||
export const registerOrUnregisterPlainPrerequisites = <T, SourceId, C = undefined>(
|
||||
method: RegistrationMethod,
|
||||
character: Draft<Character>,
|
||||
prerequisites: PlainPrerequisites<T>,
|
||||
sourceId: SourceId,
|
||||
registerOrUnregister: RegistrationFunction<T, SourceId>,
|
||||
registerOrUnregister: RegistrationFunction<T, SourceId, C>,
|
||||
...rest: C extends undefined ? [] : [capabilities: C]
|
||||
): void =>
|
||||
prerequisites.forEach((p, index) =>
|
||||
registerOrUnregisterPrerequisiteElement(
|
||||
@@ -83,19 +102,21 @@ export const registerOrUnregisterPlainPrerequisites = <T, SourceId>(
|
||||
sourceId,
|
||||
index,
|
||||
registerOrUnregister,
|
||||
...rest,
|
||||
),
|
||||
)
|
||||
|
||||
const registerOrUnregisterPrerequisiteForLevel = <T, SourceId>(
|
||||
const registerOrUnregisterPrerequisiteForLevel = <T, SourceId, C>(
|
||||
method: RegistrationMethod,
|
||||
character: Draft<Character>,
|
||||
prerequisiteForLevel: PrerequisiteForLevel<T>,
|
||||
level: number,
|
||||
levelRange: RangeBounds,
|
||||
sourceId: SourceId,
|
||||
index: number,
|
||||
registerOrUnregister: RegistrationFunction<T, SourceId>,
|
||||
registerOrUnregister: RegistrationFunction<T, SourceId, C>,
|
||||
...rest: C extends undefined ? [] : [capabilities: C]
|
||||
): void => {
|
||||
if (prerequisiteForLevel.level <= level) {
|
||||
if (isInRange(levelRange, prerequisiteForLevel.level)) {
|
||||
registerOrUnregisterPrerequisiteElement(
|
||||
method,
|
||||
character,
|
||||
@@ -103,6 +124,7 @@ const registerOrUnregisterPrerequisiteForLevel = <T, SourceId>(
|
||||
sourceId,
|
||||
index,
|
||||
registerOrUnregister,
|
||||
...rest,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -111,22 +133,24 @@ const registerOrUnregisterPrerequisiteForLevel = <T, SourceId>(
|
||||
* Checks a list of level-specific prerequisites if they’re matched up to a
|
||||
* given level.
|
||||
*/
|
||||
export const registerOrUnregisterPrerequisitesForLevels = <T, SourceId>(
|
||||
export const registerOrUnregisterPrerequisitesForLevels = <T, SourceId, C = undefined>(
|
||||
method: RegistrationMethod,
|
||||
character: Draft<Character>,
|
||||
prerequisitesForLevels: PrerequisitesForLevels<T>,
|
||||
level: number,
|
||||
levelRange: RangeBounds,
|
||||
sourceId: SourceId,
|
||||
registerOrUnregister: RegistrationFunction<T, SourceId>,
|
||||
registerOrUnregister: RegistrationFunction<T, SourceId, C>,
|
||||
...rest: C extends undefined ? [] : [capabilities: C]
|
||||
): void =>
|
||||
prerequisitesForLevels.forEach((p, index) =>
|
||||
registerOrUnregisterPrerequisiteForLevel(
|
||||
method,
|
||||
character,
|
||||
p,
|
||||
level,
|
||||
levelRange,
|
||||
sourceId,
|
||||
index,
|
||||
registerOrUnregister,
|
||||
...rest,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -1,15 +1,460 @@
|
||||
import {
|
||||
AdvantageIdentifier,
|
||||
DisadvantageIdentifier,
|
||||
} from "optolith-database-schema/types/_Identifier"
|
||||
import {
|
||||
LiturgyIdentifier,
|
||||
SpecialAbilityIdentifier,
|
||||
SpellworkIdentifier,
|
||||
} from "optolith-database-schema/types/_IdentifierGroup"
|
||||
import {
|
||||
AdvantageDisadvantagePrerequisiteGroup,
|
||||
GeneralPrerequisiteGroup,
|
||||
LiturgyPrerequisiteGroup,
|
||||
SpellworkPrerequisiteGroup,
|
||||
} from "optolith-database-schema/types/prerequisites/PrerequisiteGroups"
|
||||
import { assertExhaustive } from "../../utils/typeSafety.ts"
|
||||
import { RegistrationFunction } from "./registrationHelpers.ts"
|
||||
import { registerOrUnregisterActivatablePrerequisiteAsDependency } from "./single/activatablePrerequisiteRegistrationAsDependency.ts"
|
||||
import { registerOrUnregisterNoOtherAncestorBloodAdvantagePrerequisiteAsDependency } from "./single/ancestorBloodAdvantagePrerequisiteRegistrationAsDependency.ts"
|
||||
import { registerOrUnregisterCommonSuggestedByRCPPrerequisiteAsDependency } from "./single/commonSuggestedByRCPPrerequisiteRegistrationAsDependency.ts"
|
||||
import { registerOrUnregisterCulturePrerequisiteAsDependency } from "./single/culturePrerequisiteRegistrationAsDependency.ts"
|
||||
import { registerOrUnregisterExternalEnhancementPrerequisiteAsDependency } from "./single/enhancementPrerequisiteRegistrationAsDependency.ts"
|
||||
import { registerOrUnregisterPactPrerequisiteAsDependency } from "./single/pactPrerequisiteRegistrationAsDependency.ts"
|
||||
import { registerOrUnregisterPrimaryAttributePrerequisiteAsDependency } from "./single/primaryAttributePrerequisiteRegistrationAsDependency.ts"
|
||||
import { registerOrUnregisterRacePrerequisiteAsDependency } from "./single/racePrerequisiteRegistrationAsDependency.ts"
|
||||
import { registerOrUnregisterRatedMinimumNumberPrerequisiteAsDependency } from "./single/ratedMinimumNumberPrerequisiteRegistrationAsDependency.ts"
|
||||
import { registerOrUnregisterRatedPrerequisiteAsDependency } from "./single/ratedPrerequisiteRegistrationAsDependency.ts"
|
||||
import { registerOrUnregisterRatedSumPrerequisiteAsDependency } from "./single/ratedSumPrerequisiteRegistrationAsDependency.ts"
|
||||
import { registerOrUnregisterRulePrerequisiteAsDependency } from "./single/rulePrerequisiteRegistrationAsDependency.ts"
|
||||
import { registerOrUnregisterSexPrerequisiteAsDependency } from "./single/sexPrerequisiteRegistrationAsDependency.ts"
|
||||
import { registerOrUnregisterSexualCharacteristicPrerequisiteAsDependency } from "./single/sexualCharacteristicPrerequisiteRegistrationAsDependency.ts"
|
||||
import { registerOrUnregisterSocialStatusPrerequisiteAsDependency } from "./single/socialStatusPrerequisiteRegistrationAsDependency.ts"
|
||||
import { registerOrUnregisterStatePrerequisiteAsDependency } from "./single/statePrerequisiteRegistrationAsDependency.ts"
|
||||
import { registerOrUnregisterTextPrerequisiteAsDependency } from "./single/textPrerequisiteRegistrationAsDependency.ts"
|
||||
import {
|
||||
registerOrUnregisterBlessedTraditionPrerequisiteAsDependency,
|
||||
registerOrUnregisterMagicalTraditionPrerequisiteAsDependency,
|
||||
} from "./single/traditionPrerequisiteRegistrationAsDependency.ts"
|
||||
|
||||
/**
|
||||
* Registers or unregisters a prerequisite of a general entry as a dependency on
|
||||
* the character's draft.
|
||||
*/
|
||||
export const registerOrUnregisterPrerequisiteOfGeneralAsDependency: RegistrationFunction<
|
||||
GeneralPrerequisiteGroup,
|
||||
SpecialAbilityIdentifier,
|
||||
{
|
||||
closeCombatTechniqueIds: number[]
|
||||
rangedCombatTechniqueIds: number[]
|
||||
blessedTraditionChurchIds: number[]
|
||||
blessedTraditionShamanisticIds: number[]
|
||||
magicalTraditionIds: number[]
|
||||
magicalTraditionIdsThatCanLearnRituals: number[]
|
||||
magicalTraditionIdsThatCanBindFamiliars: number[]
|
||||
getSpellIdsByPropertyId: (id: number) => number[]
|
||||
getRitualIdsByPropertyId: (id: number) => number[]
|
||||
getLiturgicalChantIdsByAspectId: (id: number) => number[]
|
||||
getCeremonyIdsByAspectId: (id: number) => number[]
|
||||
}
|
||||
> = (method, character, p, sourceId, index, isPartOfDisjunction, capabilities): void => {
|
||||
switch (p.tag) {
|
||||
case "Sex":
|
||||
return registerOrUnregisterSexPrerequisiteAsDependency(
|
||||
method,
|
||||
character,
|
||||
p.sex,
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
)
|
||||
case "Race":
|
||||
return registerOrUnregisterRacePrerequisiteAsDependency(
|
||||
method,
|
||||
character,
|
||||
p.race,
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
)
|
||||
case "Culture":
|
||||
return registerOrUnregisterCulturePrerequisiteAsDependency(
|
||||
method,
|
||||
character,
|
||||
p.culture,
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
)
|
||||
case "Pact":
|
||||
return registerOrUnregisterPactPrerequisiteAsDependency(
|
||||
method,
|
||||
character,
|
||||
p.pact,
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
)
|
||||
case "SocialStatus":
|
||||
return registerOrUnregisterSocialStatusPrerequisiteAsDependency(
|
||||
method,
|
||||
character,
|
||||
p.social_status,
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
)
|
||||
case "State":
|
||||
return registerOrUnregisterStatePrerequisiteAsDependency(
|
||||
method,
|
||||
character,
|
||||
p.state,
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
)
|
||||
case "Rule":
|
||||
return registerOrUnregisterRulePrerequisiteAsDependency(
|
||||
method,
|
||||
character,
|
||||
p.rule,
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
)
|
||||
case "PrimaryAttribute":
|
||||
return registerOrUnregisterPrimaryAttributePrerequisiteAsDependency(
|
||||
method,
|
||||
character,
|
||||
p.primary_attribute,
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
)
|
||||
case "Activatable":
|
||||
return registerOrUnregisterActivatablePrerequisiteAsDependency(
|
||||
method,
|
||||
character,
|
||||
p.activatable,
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
)
|
||||
case "BlessedTradition":
|
||||
return registerOrUnregisterBlessedTraditionPrerequisiteAsDependency(
|
||||
method,
|
||||
character,
|
||||
p.blessed_tradition,
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
capabilities,
|
||||
)
|
||||
case "MagicalTradition":
|
||||
return registerOrUnregisterMagicalTraditionPrerequisiteAsDependency(
|
||||
method,
|
||||
character,
|
||||
p.magical_tradition,
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
capabilities,
|
||||
)
|
||||
case "Rated":
|
||||
return registerOrUnregisterRatedPrerequisiteAsDependency(
|
||||
method,
|
||||
character,
|
||||
p.rated,
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
)
|
||||
case "RatedMinimumNumber":
|
||||
return registerOrUnregisterRatedMinimumNumberPrerequisiteAsDependency(
|
||||
method,
|
||||
character,
|
||||
p.rated_minimum_number,
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
capabilities,
|
||||
)
|
||||
case "RatedSum":
|
||||
return registerOrUnregisterRatedSumPrerequisiteAsDependency(
|
||||
method,
|
||||
character,
|
||||
p.rated_sum,
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
)
|
||||
case "ExternalEnhancement":
|
||||
return registerOrUnregisterExternalEnhancementPrerequisiteAsDependency(
|
||||
method,
|
||||
character,
|
||||
p.external_enhancement,
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
)
|
||||
case "Text":
|
||||
return registerOrUnregisterTextPrerequisiteAsDependency(
|
||||
method,
|
||||
character,
|
||||
p.text,
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
)
|
||||
case "SexualCharacteristic":
|
||||
return registerOrUnregisterSexualCharacteristicPrerequisiteAsDependency(
|
||||
method,
|
||||
character,
|
||||
p.sexual_characteristic,
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
)
|
||||
default:
|
||||
return assertExhaustive(p)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers or unregisters a prerequisite of a profession as a dependency on
|
||||
* the character's draft.
|
||||
*/
|
||||
// export const registerOrUnregisterPrerequisiteOfProfessionAsDependency: RegistrationFunction<ProfessionPrerequisiteGroup, ProfessionIdentifier> = (method, character, p, sourceId, index, isPartOfDisjunction, capabilities): void => {
|
||||
// switch (p.tag) {
|
||||
// default:
|
||||
// return assertExhaustive(p)
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* Registers or unregisters a prerequisite of an advantage or disadvantage as a
|
||||
* dependency on the character's draft.
|
||||
*/
|
||||
export const registerOrUnregisterPrerequisiteOfAdvantageOrDisadvantageAsDependency: RegistrationFunction<
|
||||
AdvantageDisadvantagePrerequisiteGroup,
|
||||
AdvantageIdentifier | DisadvantageIdentifier,
|
||||
{
|
||||
ancestorBloodAdvantageIds: number[]
|
||||
closeCombatTechniqueIds: number[]
|
||||
rangedCombatTechniqueIds: number[]
|
||||
blessedTraditionChurchIds: number[]
|
||||
blessedTraditionShamanisticIds: number[]
|
||||
magicalTraditionIds: number[]
|
||||
magicalTraditionIdsThatCanLearnRituals: number[]
|
||||
magicalTraditionIdsThatCanBindFamiliars: number[]
|
||||
getSpellIdsByPropertyId: (id: number) => number[]
|
||||
getRitualIdsByPropertyId: (id: number) => number[]
|
||||
getLiturgicalChantIdsByAspectId: (id: number) => number[]
|
||||
getCeremonyIdsByAspectId: (id: number) => number[]
|
||||
}
|
||||
> = (method, character, p, sourceId, index, isPartOfDisjunction, capabilities): void => {
|
||||
switch (p.tag) {
|
||||
case "CommonSuggestedByRCP":
|
||||
return registerOrUnregisterCommonSuggestedByRCPPrerequisiteAsDependency(
|
||||
method,
|
||||
character,
|
||||
p.common_suggested_by_rcp,
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
)
|
||||
case "Sex":
|
||||
return registerOrUnregisterSexPrerequisiteAsDependency(
|
||||
method,
|
||||
character,
|
||||
p.sex,
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
)
|
||||
case "Race":
|
||||
return registerOrUnregisterRacePrerequisiteAsDependency(
|
||||
method,
|
||||
character,
|
||||
p.race,
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
)
|
||||
case "Culture":
|
||||
return registerOrUnregisterCulturePrerequisiteAsDependency(
|
||||
method,
|
||||
character,
|
||||
p.culture,
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
)
|
||||
case "Pact":
|
||||
return registerOrUnregisterPactPrerequisiteAsDependency(
|
||||
method,
|
||||
character,
|
||||
p.pact,
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
)
|
||||
case "SocialStatus":
|
||||
return registerOrUnregisterSocialStatusPrerequisiteAsDependency(
|
||||
method,
|
||||
character,
|
||||
p.social_status,
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
)
|
||||
case "State":
|
||||
return registerOrUnregisterStatePrerequisiteAsDependency(
|
||||
method,
|
||||
character,
|
||||
p.state,
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
)
|
||||
case "Rule":
|
||||
return registerOrUnregisterRulePrerequisiteAsDependency(
|
||||
method,
|
||||
character,
|
||||
p.rule,
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
)
|
||||
case "PrimaryAttribute":
|
||||
return registerOrUnregisterPrimaryAttributePrerequisiteAsDependency(
|
||||
method,
|
||||
character,
|
||||
p.primary_attribute,
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
)
|
||||
case "Activatable":
|
||||
return registerOrUnregisterActivatablePrerequisiteAsDependency(
|
||||
method,
|
||||
character,
|
||||
p.activatable,
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
)
|
||||
case "BlessedTradition":
|
||||
return registerOrUnregisterBlessedTraditionPrerequisiteAsDependency(
|
||||
method,
|
||||
character,
|
||||
p.blessed_tradition,
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
capabilities,
|
||||
)
|
||||
case "MagicalTradition":
|
||||
return registerOrUnregisterMagicalTraditionPrerequisiteAsDependency(
|
||||
method,
|
||||
character,
|
||||
p.magical_tradition,
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
capabilities,
|
||||
)
|
||||
case "Rated":
|
||||
return registerOrUnregisterRatedPrerequisiteAsDependency(
|
||||
method,
|
||||
character,
|
||||
p.rated,
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
)
|
||||
case "RatedMinimumNumber":
|
||||
return registerOrUnregisterRatedMinimumNumberPrerequisiteAsDependency(
|
||||
method,
|
||||
character,
|
||||
p.rated_minimum_number,
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
capabilities,
|
||||
)
|
||||
case "RatedSum":
|
||||
return registerOrUnregisterRatedSumPrerequisiteAsDependency(
|
||||
method,
|
||||
character,
|
||||
p.rated_sum,
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
)
|
||||
case "ExternalEnhancement":
|
||||
return registerOrUnregisterExternalEnhancementPrerequisiteAsDependency(
|
||||
method,
|
||||
character,
|
||||
p.external_enhancement,
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
)
|
||||
case "Text":
|
||||
return registerOrUnregisterTextPrerequisiteAsDependency(
|
||||
method,
|
||||
character,
|
||||
p.text,
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
)
|
||||
case "NoOtherAncestorBloodAdvantage":
|
||||
return registerOrUnregisterNoOtherAncestorBloodAdvantagePrerequisiteAsDependency(
|
||||
method,
|
||||
character,
|
||||
p.no_other_ancestor_blood_advantage,
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
capabilities,
|
||||
)
|
||||
case "SexualCharacteristic":
|
||||
return registerOrUnregisterSexualCharacteristicPrerequisiteAsDependency(
|
||||
method,
|
||||
character,
|
||||
p.sexual_characteristic,
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
)
|
||||
default:
|
||||
return assertExhaustive(p)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers or unregisters a prerequisite of a spellwork as a dependency on the
|
||||
* character's draft.
|
||||
*/
|
||||
// export const registerOrUnregisterPrerequisiteOfArcaneTraditionAsDependency: RegistrationFunction<ArcaneTraditionPrerequisiteGroup, ArcaneTraditionIdentifier> = (method, character, p, sourceId, index, isPartOfDisjunction, capabilities): void => {
|
||||
// switch (p.tag) {
|
||||
// default:
|
||||
// return assertExhaustive(p)
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* Registers or unregisters a prerequisite of a spellwork as a dependency on the
|
||||
* character's draft.
|
||||
*/
|
||||
// export const registerOrUnregisterPrerequisiteOfPersonalityTraitAsDependency: RegistrationFunction<PersonalityTraitPrerequisiteGroup, PersonalityTraitIdentifier> = (method, character, p, sourceId, index, isPartOfDisjunction, capabilities): void => {
|
||||
// switch (p.tag) {
|
||||
// default:
|
||||
// return assertExhaustive(p)
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* Registers or unregisters a prerequisite of a spellwork as a dependency on the
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import assert from "node:assert/strict"
|
||||
import { describe, it } from "node:test"
|
||||
import { RegistrationMethod, addOrRemoveDependency } from "./registrationHelpers.ts"
|
||||
import { RegistrationMethod, addOrRemoveDependencyInSlice } from "./registrationHelpers.ts"
|
||||
|
||||
describe("addOrRemoveDependency", () => {
|
||||
const createDefault = () => ({ dependencies: [] })
|
||||
@@ -9,7 +9,7 @@ describe("addOrRemoveDependency", () => {
|
||||
const slice: Record<string, { dependencies: { name: string }[] }> = {}
|
||||
const id = "testId"
|
||||
const dep = { name: "dependency" }
|
||||
addOrRemoveDependency(RegistrationMethod.Add, slice, id, createDefault, dep)
|
||||
addOrRemoveDependencyInSlice(RegistrationMethod.Add, slice, id, createDefault, dep)
|
||||
assert.deepEqual(slice[id]?.dependencies, [dep])
|
||||
})
|
||||
|
||||
@@ -21,7 +21,7 @@ describe("addOrRemoveDependency", () => {
|
||||
dependencies: [dep1, dep2],
|
||||
},
|
||||
}
|
||||
addOrRemoveDependency(RegistrationMethod.Remove, slice, "testId", createDefault, dep1)
|
||||
addOrRemoveDependencyInSlice(RegistrationMethod.Remove, slice, "testId", createDefault, dep1)
|
||||
assert.deepEqual(slice["testId"]?.dependencies, [dep2])
|
||||
})
|
||||
|
||||
@@ -33,7 +33,7 @@ describe("addOrRemoveDependency", () => {
|
||||
dependencies: [dep1],
|
||||
},
|
||||
}
|
||||
addOrRemoveDependency(RegistrationMethod.Remove, slice, "testId", createDefault, dep2)
|
||||
addOrRemoveDependencyInSlice(RegistrationMethod.Remove, slice, "testId", createDefault, dep2)
|
||||
assert.deepEqual(slice["testId"].dependencies, [dep1])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -12,34 +12,24 @@ export enum RegistrationMethod {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds or removes a dependency from a character slice. When adding a
|
||||
* dependency and no entry exists for the given id, a new entry is created
|
||||
* using the given `createDefault` function. Removing a dependency is done by
|
||||
* a deep comparison of the dependency objects.
|
||||
* Adds or removes a dependency from a dependency array. Removing a dependency
|
||||
* is done by a deep comparison of the dependency objects.
|
||||
*/
|
||||
export const addOrRemoveDependency = <
|
||||
K extends string | number | symbol,
|
||||
D,
|
||||
T extends { dependencies: D[] },
|
||||
>(
|
||||
export const addOrRemoveDependency = <D>(
|
||||
method: RegistrationMethod,
|
||||
slice: Record<K, T>,
|
||||
id: K,
|
||||
createDefault: (id: K) => T,
|
||||
dependencies: D[],
|
||||
dep: D,
|
||||
): void => {
|
||||
switch (method) {
|
||||
case RegistrationMethod.Add: {
|
||||
const entry = (slice[id] ??= createDefault(id))
|
||||
entry.dependencies.push(dep)
|
||||
dependencies.push(dep)
|
||||
break
|
||||
}
|
||||
|
||||
case RegistrationMethod.Remove: {
|
||||
const entry = slice[id]
|
||||
const index = entry?.dependencies.findIndex(d => deepEqual(d, dep)) ?? -1
|
||||
const index = dependencies.findIndex(d => deepEqual(d, dep)) ?? -1
|
||||
if (index > -1) {
|
||||
entry?.dependencies.splice(index, 1)
|
||||
dependencies.splice(index, 1)
|
||||
}
|
||||
break
|
||||
}
|
||||
@@ -49,15 +39,55 @@ export const addOrRemoveDependency = <
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds or removes a dependency from a character slice. When adding a
|
||||
* dependency and no entry exists for the given identifier, a new entry is
|
||||
* created using the given `createDefault` function. Removing a dependency is
|
||||
* done by a deep comparison of the dependency objects.
|
||||
*/
|
||||
export const addOrRemoveDependencyInSlice = <
|
||||
K extends string | number | symbol,
|
||||
D,
|
||||
T extends { dependencies: D[] },
|
||||
>(
|
||||
method: RegistrationMethod,
|
||||
slice: Record<K, T>,
|
||||
id: K,
|
||||
createDefault: (id: K) => T,
|
||||
dep: D,
|
||||
): void => {
|
||||
const entry = (slice[id] ??= createDefault(id))
|
||||
addOrRemoveDependency(method, entry.dependencies, dep)
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that registers or unregisters a prerequisite as a dependency on
|
||||
* the character's draft.
|
||||
*/
|
||||
export type RegistrationFunction<T, SourceId> = (
|
||||
export type RegistrationFunction<T, SourceId, C = undefined> = (
|
||||
method: RegistrationMethod,
|
||||
character: Draft<Character>,
|
||||
p: T,
|
||||
sourceId: SourceId,
|
||||
index: number,
|
||||
isPartOfDisjunction: boolean,
|
||||
...rest: C extends undefined ? [] : [capabilities: C]
|
||||
) => void
|
||||
|
||||
/**
|
||||
* Additional data some registration functions need.
|
||||
*/
|
||||
export type RegistrationFunctionCapabilities = {
|
||||
ancestorBloodAdvantageIds: number[]
|
||||
closeCombatTechniqueIds: number[]
|
||||
rangedCombatTechniqueIds: number[]
|
||||
blessedTraditionChurchIds: number[]
|
||||
blessedTraditionShamanisticIds: number[]
|
||||
magicalTraditionIds: number[]
|
||||
magicalTraditionIdsThatCanLearnRituals: number[]
|
||||
magicalTraditionIdsThatCanBindFamiliars: number[]
|
||||
getSpellIdsByPropertyId: (id: number) => number[]
|
||||
getRitualIdsByPropertyId: (id: number) => number[]
|
||||
getLiturgicalChantIdsByAspectId: (id: number) => number[]
|
||||
getCeremonyIdsByAspectId: (id: number) => number[]
|
||||
}
|
||||
|
||||
+430
@@ -0,0 +1,430 @@
|
||||
import { ActivatableIdentifier } from "optolith-database-schema/types/_IdentifierGroup"
|
||||
import { ActivatablePrerequisite } from "optolith-database-schema/types/prerequisites/single/ActivatablePrerequisite"
|
||||
import { assertExhaustive } from "../../../utils/typeSafety.ts"
|
||||
import { ActivatableDependency } from "../../activatable/activatableDependency.ts"
|
||||
import { createEmptyDynamicActivatable } from "../../activatable/activatableEntry.ts"
|
||||
import { RegistrationFunction, addOrRemoveDependencyInSlice } from "../registrationHelpers.ts"
|
||||
|
||||
/**
|
||||
* Registers or unregisters a {@link ActivatablePrerequisite} as a dependency on
|
||||
* the character's draft.
|
||||
*/
|
||||
export const registerOrUnregisterActivatablePrerequisiteAsDependency: RegistrationFunction<
|
||||
ActivatablePrerequisite,
|
||||
ActivatableIdentifier
|
||||
> = (method, character, p, sourceId, index, isPartOfDisjunction): void => {
|
||||
const dependency: ActivatableDependency = {
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
active: p.active,
|
||||
level: p.level,
|
||||
options: p.options,
|
||||
when: p.when,
|
||||
}
|
||||
|
||||
switch (p.id.tag) {
|
||||
case "Advantage":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.advantages,
|
||||
p.id.advantage,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "Disadvantage":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.disadvantages,
|
||||
p.id.disadvantage,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "GeneralSpecialAbility":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.generalSpecialAbilities,
|
||||
p.id.general_special_ability,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "FatePointSpecialAbility":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.fatePointSpecialAbilities,
|
||||
p.id.fate_point_special_ability,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "CombatSpecialAbility":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.combatSpecialAbilities,
|
||||
p.id.combat_special_ability,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "MagicalSpecialAbility":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.magicalSpecialAbilities,
|
||||
p.id.magical_special_ability,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "StaffEnchantment":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.staffEnchantments,
|
||||
p.id.staff_enchantment,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "FamiliarSpecialAbility":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.familiarSpecialAbilities,
|
||||
p.id.familiar_special_ability,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "KarmaSpecialAbility":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.karmaSpecialAbilities,
|
||||
p.id.karma_special_ability,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "ProtectiveWardingCircleSpecialAbility":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.protectiveWardingCircleSpecialAbilities,
|
||||
p.id.protective_warding_circle_special_ability,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "CombatStyleSpecialAbility":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.combatStyleSpecialAbilities,
|
||||
p.id.combat_style_special_ability,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "AdvancedCombatSpecialAbility":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.advancedCombatSpecialAbilities,
|
||||
p.id.advanced_combat_special_ability,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "CommandSpecialAbility":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.commandSpecialAbilities,
|
||||
p.id.command_special_ability,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "MagicStyleSpecialAbility":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.magicStyleSpecialAbilities,
|
||||
p.id.magic_style_special_ability,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "AdvancedMagicalSpecialAbility":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.advancedMagicalSpecialAbilities,
|
||||
p.id.advanced_magical_special_ability,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "SpellSwordEnchantment":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.spellSwordEnchantments,
|
||||
p.id.spell_sword_enchantment,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "DaggerRitual":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.daggerRituals,
|
||||
p.id.dagger_ritual,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "InstrumentEnchantment":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.instrumentEnchantments,
|
||||
p.id.instrument_enchantment,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "AttireEnchantment":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.attireEnchantments,
|
||||
p.id.attire_enchantment,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "OrbEnchantment":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.orbEnchantments,
|
||||
p.id.orb_enchantment,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "WandEnchantment":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.wandEnchantments,
|
||||
p.id.wand_enchantment,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "BrawlingSpecialAbility":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.brawlingSpecialAbilities,
|
||||
p.id.brawling_special_ability,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "AncestorGlyph":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.ancestorGlyphs,
|
||||
p.id.ancestor_glyph,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "CeremonialItemSpecialAbility":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.ceremonialItemSpecialAbilities,
|
||||
p.id.ceremonial_item_special_ability,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "Sermon":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.sermons,
|
||||
p.id.sermon,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "LiturgicalStyleSpecialAbility":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.liturgicalStyleSpecialAbilities,
|
||||
p.id.liturgical_style_special_ability,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "AdvancedKarmaSpecialAbility":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.advancedKarmaSpecialAbilities,
|
||||
p.id.advanced_karma_special_ability,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "Vision":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.visions,
|
||||
p.id.vision,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "MagicalTradition":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.magicalTraditions,
|
||||
p.id.magical_tradition,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "BlessedTradition":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.blessedTraditions,
|
||||
p.id.blessed_tradition,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "PactGift":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.pactGifts,
|
||||
p.id.pact_gift,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "VampiricGift":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.vampiricGifts,
|
||||
p.id.vampiric_gift,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "SikaryanDrainSpecialAbility":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.sikaryanDrainSpecialAbilities,
|
||||
p.id.sikaryan_drain_special_ability,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "LycantropicGift":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.lycantropicGifts,
|
||||
p.id.lycantropic_gift,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "SkillStyleSpecialAbility":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.skillStyleSpecialAbilities,
|
||||
p.id.skill_style_special_ability,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "AdvancedSkillSpecialAbility":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.advancedSkillSpecialAbilities,
|
||||
p.id.advanced_skill_special_ability,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "ArcaneOrbEnchantment":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.arcaneOrbEnchantments,
|
||||
p.id.arcane_orb_enchantment,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "CauldronEnchantment":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.cauldronEnchantments,
|
||||
p.id.cauldron_enchantment,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "FoolsHatEnchantment":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.foolsHatEnchantments,
|
||||
p.id.fools_hat_enchantment,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "ToyEnchantment":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.toyEnchantments,
|
||||
p.id.toy_enchantment,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "BowlEnchantment":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.bowlEnchantments,
|
||||
p.id.bowl_enchantment,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "FatePointSexSpecialAbility":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.fatePointSexSpecialAbilities,
|
||||
p.id.fate_point_sex_special_ability,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "SexSpecialAbility":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.sexSpecialAbilities,
|
||||
p.id.sex_special_ability,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "WeaponEnchantment":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.weaponEnchantments,
|
||||
p.id.weapon_enchantment,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "SickleRitual":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.sickleRituals,
|
||||
p.id.sickle_ritual,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "RingEnchantment":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.ringEnchantments,
|
||||
p.id.ring_enchantment,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "ChronicleEnchantment":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.chronicleEnchantments,
|
||||
p.id.chronicle_enchantment,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "Krallenkettenzauber":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.krallenkettenzauber,
|
||||
p.id.krallenkettenzauber,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "Trinkhornzauber":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.trinkhornzauber,
|
||||
p.id.trinkhornzauber,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
case "MagicalSign":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.magicalSigns,
|
||||
p.id.magical_sign,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
default:
|
||||
return assertExhaustive(p.id)
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
import { ActivatableIdentifier } from "optolith-database-schema/types/_IdentifierGroup"
|
||||
import { AncestorBloodPrerequisite } from "optolith-database-schema/types/prerequisites/single/AncestorBloodPrerequisite"
|
||||
import { ActivatableDependency } from "../../activatable/activatableDependency.ts"
|
||||
import { createEmptyDynamicActivatable } from "../../activatable/activatableEntry.ts"
|
||||
import { RegistrationFunction, addOrRemoveDependencyInSlice } from "../registrationHelpers.ts"
|
||||
|
||||
/**
|
||||
* Registers or unregisters a {@link AncestorBloodPrerequisite} as a dependency
|
||||
* on the character's draft.
|
||||
*/
|
||||
export const registerOrUnregisterNoOtherAncestorBloodAdvantagePrerequisiteAsDependency: RegistrationFunction<
|
||||
AncestorBloodPrerequisite,
|
||||
ActivatableIdentifier,
|
||||
{ ancestorBloodAdvantageIds: number[] }
|
||||
> = (
|
||||
method,
|
||||
character,
|
||||
_p,
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
{ ancestorBloodAdvantageIds },
|
||||
): void => {
|
||||
const dependency: ActivatableDependency = {
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
active: false,
|
||||
}
|
||||
|
||||
ancestorBloodAdvantageIds.forEach(id => {
|
||||
if (sourceId.tag !== "Advantage" || sourceId.advantage !== id) {
|
||||
addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.advantages,
|
||||
id,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import { AnimistPowerIdentifier } from "optolith-database-schema/types/_Identifier"
|
||||
import { AnimistPowerPrerequisite } from "optolith-database-schema/types/prerequisites/single/AnimistPowerPrerequisite"
|
||||
import { RegistrationFunction } from "../registrationHelpers.ts"
|
||||
|
||||
/**
|
||||
* Registers or unregisters a {@link AnimistPowerPrerequisite} as a dependency on the
|
||||
* character's draft.
|
||||
*/
|
||||
export const registerOrUnregisterAnimistPowerPrerequisiteAsDependency: RegistrationFunction<
|
||||
AnimistPowerPrerequisite,
|
||||
AnimistPowerIdentifier
|
||||
> = (): void => {
|
||||
// TODO: Missing implementation
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
import {
|
||||
AdvantageIdentifier,
|
||||
DisadvantageIdentifier,
|
||||
} from "optolith-database-schema/types/_Identifier"
|
||||
import { RegistrationFunction } from "../registrationHelpers.ts"
|
||||
|
||||
/**
|
||||
* Registers or unregisters a {@link CommonSuggestedByRCPPrerequisite} as a
|
||||
* dependency on the character's draft.
|
||||
*/
|
||||
export const registerOrUnregisterCommonSuggestedByRCPPrerequisiteAsDependency: RegistrationFunction<
|
||||
Record<string, never>,
|
||||
AdvantageIdentifier | DisadvantageIdentifier
|
||||
> = (): void => {
|
||||
// no op
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { ActivatableIdentifier } from "optolith-database-schema/types/_IdentifierGroup"
|
||||
import { CulturePrerequisite } from "optolith-database-schema/types/prerequisites/single/CulturePrerequisite"
|
||||
import { CultureDependency } from "../../culture.ts"
|
||||
import { RegistrationFunction, addOrRemoveDependency } from "../registrationHelpers.ts"
|
||||
|
||||
/**
|
||||
* Registers or unregisters a {@link CulturePrerequisite} as a dependency on the
|
||||
* character's draft.
|
||||
*/
|
||||
export const registerOrUnregisterCulturePrerequisiteAsDependency: RegistrationFunction<
|
||||
CulturePrerequisite,
|
||||
ActivatableIdentifier
|
||||
> = (method, character, p, sourceId, index, isPartOfDisjunction): void => {
|
||||
const dependency: CultureDependency = {
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
id: p.id.culture,
|
||||
}
|
||||
|
||||
addOrRemoveDependency(method, character.culture.dependencies, dependency)
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
import { ActivatableIdentifier } from "optolith-database-schema/types/_IdentifierGroup"
|
||||
import {
|
||||
ExternalEnhancementPrerequisite,
|
||||
InternalEnhancementPrerequisite,
|
||||
} from "optolith-database-schema/types/prerequisites/single/EnhancementPrerequisite"
|
||||
import { assertExhaustive } from "../../../utils/typeSafety.ts"
|
||||
import { EnhancementDependency } from "../../rated/enhancement.ts"
|
||||
import { createEmptyDynamicLiturgicalChant } from "../../rated/liturgicalChant.ts"
|
||||
import { createEmptyDynamicSpell } from "../../rated/spell.ts"
|
||||
import { RegistrationFunction, addOrRemoveDependency } from "../registrationHelpers.ts"
|
||||
|
||||
/**
|
||||
* Registers or unregisters an {@link ExternalEnhancementPrerequisite} as a
|
||||
* dependency on the character's draft.
|
||||
*/
|
||||
export const registerOrUnregisterExternalEnhancementPrerequisiteAsDependency: RegistrationFunction<
|
||||
ExternalEnhancementPrerequisite,
|
||||
ActivatableIdentifier
|
||||
> = (method, character, p, sourceId, index, isPartOfDisjunction): void => {
|
||||
const dependency: EnhancementDependency = {
|
||||
tag: "External",
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
}
|
||||
|
||||
const addOrRemoveDependencyInSlice = <
|
||||
K extends string | number | symbol,
|
||||
T extends { enhancements: Record<number, { dependencies: EnhancementDependency[] }> },
|
||||
>(
|
||||
slice: Record<K, T>,
|
||||
id: K,
|
||||
createDefault: (id: K) => T,
|
||||
): void => {
|
||||
const entry = (slice[id] ??= createDefault(id))
|
||||
if (Object.hasOwn(entry.enhancements, p.enhancement.id)) {
|
||||
addOrRemoveDependency(method, entry.enhancements[p.enhancement.id]!.dependencies, dependency)
|
||||
}
|
||||
}
|
||||
|
||||
switch (p.skill.id.tag) {
|
||||
case "Spell":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
character.spells,
|
||||
p.skill.id.spell,
|
||||
createEmptyDynamicSpell,
|
||||
)
|
||||
case "Ritual":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
character.rituals,
|
||||
p.skill.id.ritual,
|
||||
createEmptyDynamicSpell,
|
||||
)
|
||||
case "LiturgicalChant":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
character.liturgicalChants,
|
||||
p.skill.id.liturgical_chant,
|
||||
createEmptyDynamicLiturgicalChant,
|
||||
)
|
||||
case "Ceremony":
|
||||
return addOrRemoveDependencyInSlice(
|
||||
character.ceremonies,
|
||||
p.skill.id.ceremony,
|
||||
createEmptyDynamicLiturgicalChant,
|
||||
)
|
||||
default:
|
||||
return assertExhaustive(p.skill.id)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers or unregisters an {@link InternalEnhancementPrerequisite} as a
|
||||
* dependency on the character's draft.
|
||||
*/
|
||||
export const registerOrUnregisterInfluencePrerequisiteAsDependency: RegistrationFunction<
|
||||
InternalEnhancementPrerequisite,
|
||||
number
|
||||
> = (): void => {
|
||||
// TODO: Missing implementation
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import { InfluenceIdentifier } from "optolith-database-schema/types/_Identifier"
|
||||
import { InfluencePrerequisite } from "optolith-database-schema/types/prerequisites/single/InfluencePrerequisite"
|
||||
import { RegistrationFunction } from "../registrationHelpers.ts"
|
||||
|
||||
/**
|
||||
* Registers or unregisters a {@link InfluencePrerequisite} as a dependency on the
|
||||
* character's draft.
|
||||
*/
|
||||
export const registerOrUnregisterInfluencePrerequisiteAsDependency: RegistrationFunction<
|
||||
InfluencePrerequisite,
|
||||
InfluenceIdentifier
|
||||
> = (): void => {
|
||||
// TODO: Missing implementation
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { ActivatableIdentifier } from "optolith-database-schema/types/_IdentifierGroup"
|
||||
import { PactPrerequisite } from "optolith-database-schema/types/prerequisites/single/PactPrerequisite"
|
||||
import { PactDependency } from "../../pact.ts"
|
||||
import { RegistrationFunction, addOrRemoveDependency } from "../registrationHelpers.ts"
|
||||
|
||||
/**
|
||||
* Registers or unregisters a {@link PactPrerequisite} as a dependency on the
|
||||
* character's draft.
|
||||
*/
|
||||
export const registerOrUnregisterPactPrerequisiteAsDependency: RegistrationFunction<
|
||||
PactPrerequisite,
|
||||
ActivatableIdentifier
|
||||
> = (method, character, p, sourceId, index, isPartOfDisjunction): void => {
|
||||
const dependency: PactDependency = {
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
category: p.category,
|
||||
domain: p.domain_id,
|
||||
level: p.level,
|
||||
}
|
||||
|
||||
addOrRemoveDependency(method, character.pactDependencies, dependency)
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
import { ActivatableIdentifier } from "optolith-database-schema/types/_IdentifierGroup"
|
||||
import { PrimaryAttributePrerequisite } from "optolith-database-schema/types/prerequisites/single/PrimaryAttributePrerequisite"
|
||||
import { assertExhaustive } from "../../../utils/typeSafety.ts"
|
||||
import { PrimaryAttributeDependency } from "../../rated/primaryAttribute.ts"
|
||||
import { RegistrationFunction, addOrRemoveDependency } from "../registrationHelpers.ts"
|
||||
|
||||
/**
|
||||
* Registers or unregisters a {@link PrimaryAttributePrerequisite} as a
|
||||
* dependency on the character's draft.
|
||||
*/
|
||||
export const registerOrUnregisterPrimaryAttributePrerequisiteAsDependency: RegistrationFunction<
|
||||
PrimaryAttributePrerequisite,
|
||||
ActivatableIdentifier
|
||||
> = (method, character, p, sourceId, index, isPartOfDisjunction): void => {
|
||||
const dependency: PrimaryAttributeDependency = {
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
value: {
|
||||
tag: "Minimum",
|
||||
minimum: p.value,
|
||||
},
|
||||
}
|
||||
|
||||
switch (p.category) {
|
||||
case "Blessed":
|
||||
return addOrRemoveDependency(
|
||||
method,
|
||||
character.blessedPrimaryAttributeDependencies,
|
||||
dependency,
|
||||
)
|
||||
case "Magical":
|
||||
return addOrRemoveDependency(
|
||||
method,
|
||||
character.magicalPrimaryAttributeDependencies,
|
||||
dependency,
|
||||
)
|
||||
default:
|
||||
return assertExhaustive(p.category)
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
import { PublicationIdentifier } from "optolith-database-schema/types/_Identifier"
|
||||
import { PublicationPrerequisite } from "optolith-database-schema/types/prerequisites/single/PublicationPrerequisite"
|
||||
import { PublicationDependency } from "../../sources/publicationDependency.ts"
|
||||
import { RegistrationFunction, addOrRemoveDependency } from "../registrationHelpers.ts"
|
||||
|
||||
/**
|
||||
* Registers or unregisters a {@link PublicationPrerequisite} as a dependency on the
|
||||
* character's draft.
|
||||
*/
|
||||
export const registerOrUnregisterPublicationPrerequisiteAsDependency: RegistrationFunction<
|
||||
PublicationPrerequisite,
|
||||
PublicationIdentifier
|
||||
> = (method, character, p, sourceId, index, isPartOfDisjunction): void => {
|
||||
const dependency: PublicationDependency = {
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
id: p.id,
|
||||
}
|
||||
|
||||
addOrRemoveDependency(method, character.rules.publicationDependencies, dependency)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ActivatableIdentifier } from "optolith-database-schema/types/_IdentifierGroup"
|
||||
import { RacePrerequisite } from "optolith-database-schema/types/prerequisites/single/RacePrerequisite"
|
||||
import { RaceDependency } from "../../race.ts"
|
||||
import { RegistrationFunction, addOrRemoveDependency } from "../registrationHelpers.ts"
|
||||
|
||||
/**
|
||||
* Registers or unregisters a {@link RacePrerequisite} as a dependency on the
|
||||
* character's draft.
|
||||
*/
|
||||
export const registerOrUnregisterRacePrerequisiteAsDependency: RegistrationFunction<
|
||||
RacePrerequisite,
|
||||
ActivatableIdentifier
|
||||
> = (method, character, p, sourceId, index, isPartOfDisjunction): void => {
|
||||
const dependency: RaceDependency = {
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
id: p.id.race,
|
||||
active: p.active,
|
||||
}
|
||||
|
||||
addOrRemoveDependency(method, character.race.dependencies, dependency)
|
||||
}
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
import { ActivatableIdentifier } from "optolith-database-schema/types/_IdentifierGroup"
|
||||
import { RatedMinimumNumberPrerequisite } from "optolith-database-schema/types/prerequisites/single/RatedMinimumNumberPrerequisite"
|
||||
import { assertExhaustive } from "../../../utils/typeSafety.ts"
|
||||
import { createEmptyDynamicCombatTechnique } from "../../rated/combatTechnique.ts"
|
||||
import { createEmptyDynamicLiturgicalChant } from "../../rated/liturgicalChant.ts"
|
||||
import { RatedDependency } from "../../rated/ratedDependency.ts"
|
||||
import { createEmptyDynamicSkill } from "../../rated/skill.ts"
|
||||
import { createEmptyDynamicSpell } from "../../rated/spell.ts"
|
||||
import { RegistrationFunction, addOrRemoveDependencyInSlice } from "../registrationHelpers.ts"
|
||||
|
||||
/**
|
||||
* Registers or unregisters a {@link RatedMinimumNumberPrerequisite} as a dependency on the
|
||||
* character's draft.
|
||||
*/
|
||||
export const registerOrUnregisterRatedMinimumNumberPrerequisiteAsDependency: RegistrationFunction<
|
||||
RatedMinimumNumberPrerequisite,
|
||||
ActivatableIdentifier,
|
||||
{
|
||||
closeCombatTechniqueIds: number[]
|
||||
rangedCombatTechniqueIds: number[]
|
||||
getSpellIdsByPropertyId: (id: number) => number[]
|
||||
getRitualIdsByPropertyId: (id: number) => number[]
|
||||
getLiturgicalChantIdsByAspectId: (id: number) => number[]
|
||||
getCeremonyIdsByAspectId: (id: number) => number[]
|
||||
}
|
||||
> = (
|
||||
method,
|
||||
character,
|
||||
p,
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
{
|
||||
closeCombatTechniqueIds,
|
||||
rangedCombatTechniqueIds,
|
||||
getSpellIdsByPropertyId,
|
||||
getRitualIdsByPropertyId,
|
||||
getLiturgicalChantIdsByAspectId,
|
||||
getCeremonyIdsByAspectId,
|
||||
},
|
||||
): void => {
|
||||
const dependency: RatedDependency = {
|
||||
source: sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
value: {
|
||||
tag: "MinimumNumberAtMinimumValue",
|
||||
minimumCount: p.number,
|
||||
minimumValue: p.value,
|
||||
target: p.targets,
|
||||
},
|
||||
}
|
||||
|
||||
switch (p.targets.tag) {
|
||||
case "Skills":
|
||||
return p.targets.skills.list.forEach(ref =>
|
||||
addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.skills,
|
||||
ref.id.skill,
|
||||
createEmptyDynamicSkill,
|
||||
dependency,
|
||||
),
|
||||
)
|
||||
case "CombatTechniques":
|
||||
switch (p.targets.combat_techniques.group) {
|
||||
case "All":
|
||||
closeCombatTechniqueIds.forEach(id =>
|
||||
addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.combatTechniques.close,
|
||||
id,
|
||||
createEmptyDynamicCombatTechnique,
|
||||
dependency,
|
||||
),
|
||||
)
|
||||
rangedCombatTechniqueIds.forEach(id =>
|
||||
addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.combatTechniques.ranged,
|
||||
id,
|
||||
createEmptyDynamicCombatTechnique,
|
||||
dependency,
|
||||
),
|
||||
)
|
||||
return
|
||||
case "Close":
|
||||
return closeCombatTechniqueIds.forEach(id =>
|
||||
addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.combatTechniques.close,
|
||||
id,
|
||||
createEmptyDynamicCombatTechnique,
|
||||
dependency,
|
||||
),
|
||||
)
|
||||
case "Ranged":
|
||||
return rangedCombatTechniqueIds.forEach(id =>
|
||||
addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.combatTechniques.ranged,
|
||||
id,
|
||||
createEmptyDynamicCombatTechnique,
|
||||
dependency,
|
||||
),
|
||||
)
|
||||
default:
|
||||
return assertExhaustive(p.targets.combat_techniques.group)
|
||||
}
|
||||
case "Spellworks":
|
||||
getSpellIdsByPropertyId(p.targets.spellworks.id.property).forEach(id =>
|
||||
addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.spells,
|
||||
id,
|
||||
createEmptyDynamicSpell,
|
||||
dependency,
|
||||
),
|
||||
)
|
||||
getRitualIdsByPropertyId(p.targets.spellworks.id.property).forEach(id =>
|
||||
addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.rituals,
|
||||
id,
|
||||
createEmptyDynamicSpell,
|
||||
dependency,
|
||||
),
|
||||
)
|
||||
return
|
||||
case "Liturgies":
|
||||
getLiturgicalChantIdsByAspectId(p.targets.liturgies.id.aspect).forEach(id =>
|
||||
addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.liturgicalChants,
|
||||
id,
|
||||
createEmptyDynamicLiturgicalChant,
|
||||
dependency,
|
||||
),
|
||||
)
|
||||
getCeremonyIdsByAspectId(p.targets.liturgies.id.aspect).forEach(id =>
|
||||
addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.ceremonies,
|
||||
id,
|
||||
createEmptyDynamicLiturgicalChant,
|
||||
dependency,
|
||||
),
|
||||
)
|
||||
return
|
||||
default:
|
||||
return assertExhaustive(p.targets)
|
||||
}
|
||||
}
|
||||
+5
-5
@@ -17,7 +17,7 @@ describe(registerOrUnregisterRatedPrerequisiteAsDependency.name, () => {
|
||||
dependencies: [
|
||||
{
|
||||
source: { tag: "Spell", spell: 1 },
|
||||
value: { tag: "Minimum", minimum: 10 },
|
||||
value: { tag: "Fixed", value: { tag: "Minimum", minimum: 10 } },
|
||||
index: 0,
|
||||
isPartOfDisjunction: false,
|
||||
},
|
||||
@@ -60,7 +60,7 @@ describe(registerOrUnregisterRatedPrerequisiteAsDependency.name, () => {
|
||||
dependencies: [
|
||||
{
|
||||
source: { tag: "Spell", spell: 1 },
|
||||
value: { tag: "Minimum", minimum: 10 },
|
||||
value: { tag: "Fixed", value: { tag: "Minimum", minimum: 10 } },
|
||||
index: 0,
|
||||
isPartOfDisjunction: false,
|
||||
},
|
||||
@@ -93,13 +93,13 @@ describe(registerOrUnregisterRatedPrerequisiteAsDependency.name, () => {
|
||||
dependencies: [
|
||||
{
|
||||
source: { tag: "Spell", spell: 1 },
|
||||
value: { tag: "Minimum", minimum: 10 },
|
||||
value: { tag: "Fixed", value: { tag: "Minimum", minimum: 10 } },
|
||||
index: 0,
|
||||
isPartOfDisjunction: false,
|
||||
},
|
||||
{
|
||||
source: { tag: "Ritual", ritual: 1 },
|
||||
value: { tag: "Minimum", minimum: 11 },
|
||||
value: { tag: "Fixed", value: { tag: "Minimum", minimum: 11 } },
|
||||
index: 0,
|
||||
isPartOfDisjunction: false,
|
||||
},
|
||||
@@ -116,7 +116,7 @@ describe(registerOrUnregisterRatedPrerequisiteAsDependency.name, () => {
|
||||
dependencies: [
|
||||
{
|
||||
source: { tag: "Ritual", ritual: 1 },
|
||||
value: { tag: "Minimum", minimum: 11 },
|
||||
value: { tag: "Fixed", value: { tag: "Minimum", minimum: 11 } },
|
||||
index: 0,
|
||||
isPartOfDisjunction: false,
|
||||
},
|
||||
|
||||
+16
-12
@@ -10,10 +10,11 @@ import { createEmptyDynamicLiturgicalChant } from "../../rated/liturgicalChant.t
|
||||
import { RatedDependency } from "../../rated/ratedDependency.ts"
|
||||
import { createEmptyDynamicSkill } from "../../rated/skill.ts"
|
||||
import { createEmptyDynamicSpell } from "../../rated/spell.ts"
|
||||
import { RegistrationFunction, addOrRemoveDependency } from "../registrationHelpers.ts"
|
||||
import { RegistrationFunction, addOrRemoveDependencyInSlice } from "../registrationHelpers.ts"
|
||||
|
||||
/**
|
||||
* Registers or unregisters a {@link RatedPrerequisite} as a dependency on the character's draft.
|
||||
* Registers or unregisters a {@link RatedPrerequisite} as a dependency on the
|
||||
* character's draft.
|
||||
*/
|
||||
export const registerOrUnregisterRatedPrerequisiteAsDependency: RegistrationFunction<
|
||||
RatedPrerequisite,
|
||||
@@ -24,14 +25,17 @@ export const registerOrUnregisterRatedPrerequisiteAsDependency: RegistrationFunc
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
value: {
|
||||
tag: "Minimum",
|
||||
minimum: p.value,
|
||||
tag: "Fixed",
|
||||
value: {
|
||||
tag: "Minimum",
|
||||
minimum: p.value,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
switch (p.id.tag) {
|
||||
case "Attribute":
|
||||
return addOrRemoveDependency(
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.attributes,
|
||||
p.id.attribute,
|
||||
@@ -39,7 +43,7 @@ export const registerOrUnregisterRatedPrerequisiteAsDependency: RegistrationFunc
|
||||
dependency,
|
||||
)
|
||||
case "Skill":
|
||||
return addOrRemoveDependency(
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.skills,
|
||||
p.id.skill,
|
||||
@@ -47,7 +51,7 @@ export const registerOrUnregisterRatedPrerequisiteAsDependency: RegistrationFunc
|
||||
dependency,
|
||||
)
|
||||
case "CloseCombatTechnique":
|
||||
return addOrRemoveDependency(
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.combatTechniques.close,
|
||||
p.id.close_combat_technique,
|
||||
@@ -55,7 +59,7 @@ export const registerOrUnregisterRatedPrerequisiteAsDependency: RegistrationFunc
|
||||
dependency,
|
||||
)
|
||||
case "RangedCombatTechnique":
|
||||
return addOrRemoveDependency(
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.combatTechniques.ranged,
|
||||
p.id.ranged_combat_technique,
|
||||
@@ -63,7 +67,7 @@ export const registerOrUnregisterRatedPrerequisiteAsDependency: RegistrationFunc
|
||||
dependency,
|
||||
)
|
||||
case "Spell":
|
||||
return addOrRemoveDependency(
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.spells,
|
||||
p.id.spell,
|
||||
@@ -71,7 +75,7 @@ export const registerOrUnregisterRatedPrerequisiteAsDependency: RegistrationFunc
|
||||
dependency,
|
||||
)
|
||||
case "Ritual":
|
||||
return addOrRemoveDependency(
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.rituals,
|
||||
p.id.ritual,
|
||||
@@ -79,7 +83,7 @@ export const registerOrUnregisterRatedPrerequisiteAsDependency: RegistrationFunc
|
||||
dependency,
|
||||
)
|
||||
case "LiturgicalChant":
|
||||
return addOrRemoveDependency(
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.liturgicalChants,
|
||||
p.id.liturgical_chant,
|
||||
@@ -87,7 +91,7 @@ export const registerOrUnregisterRatedPrerequisiteAsDependency: RegistrationFunc
|
||||
dependency,
|
||||
)
|
||||
case "Ceremony":
|
||||
return addOrRemoveDependency(
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.ceremonies,
|
||||
p.id.ceremony,
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import {
|
||||
ActivatableIdentifier,
|
||||
SkillWithEnhancementsIdentifier,
|
||||
} from "optolith-database-schema/types/_IdentifierGroup"
|
||||
import { RatedSumPrerequisite } from "optolith-database-schema/types/prerequisites/single/RatedSumPrerequisite"
|
||||
import { RatedDependency } from "../../rated/ratedDependency.ts"
|
||||
import { createEmptyDynamicSkill } from "../../rated/skill.ts"
|
||||
import { RegistrationFunction, addOrRemoveDependencyInSlice } from "../registrationHelpers.ts"
|
||||
|
||||
/**
|
||||
* Registers or unregisters a {@link RatedSumPrerequisite} as a dependency on the
|
||||
* character's draft.
|
||||
*/
|
||||
export const registerOrUnregisterRatedSumPrerequisiteAsDependency: RegistrationFunction<
|
||||
RatedSumPrerequisite,
|
||||
ActivatableIdentifier | SkillWithEnhancementsIdentifier
|
||||
> = (method, character, p, sourceId, index, isPartOfDisjunction): void => {
|
||||
const dependency: RatedDependency = {
|
||||
source: sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
value: {
|
||||
tag: "Sum",
|
||||
sum: p.sum,
|
||||
targetIds: p.targets,
|
||||
},
|
||||
}
|
||||
|
||||
p.targets.forEach(id =>
|
||||
addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.skills,
|
||||
id.skill,
|
||||
createEmptyDynamicSkill,
|
||||
dependency,
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -7,10 +7,11 @@ import { assertExhaustive } from "../../../utils/typeSafety.ts"
|
||||
import { createFocusRule } from "../../rules/focusRule.ts"
|
||||
import { createOptionalRule } from "../../rules/optionalRule.ts"
|
||||
import { RuleDependency } from "../../rules/ruleDependency.ts"
|
||||
import { RegistrationFunction, addOrRemoveDependency } from "../registrationHelpers.ts"
|
||||
import { RegistrationFunction, addOrRemoveDependencyInSlice } from "../registrationHelpers.ts"
|
||||
|
||||
/**
|
||||
* Registers or unregisters a {@link RulePrerequisite} as a dependency on the character's draft.
|
||||
* Registers or unregisters a {@link RulePrerequisite} as a dependency on the
|
||||
* character's draft.
|
||||
*/
|
||||
export const registerOrUnregisterRulePrerequisiteAsDependency: RegistrationFunction<
|
||||
RulePrerequisite,
|
||||
@@ -24,7 +25,7 @@ export const registerOrUnregisterRulePrerequisiteAsDependency: RegistrationFunct
|
||||
|
||||
switch (p.id.tag) {
|
||||
case "FocusRule":
|
||||
return addOrRemoveDependency(
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.rules.focusRules,
|
||||
p.id.focus_rule,
|
||||
@@ -32,7 +33,7 @@ export const registerOrUnregisterRulePrerequisiteAsDependency: RegistrationFunct
|
||||
dependency,
|
||||
)
|
||||
case "OptionalRule":
|
||||
return addOrRemoveDependency(
|
||||
return addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.rules.optionalRules,
|
||||
p.id.optional_rule,
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ActivatableIdentifier } from "optolith-database-schema/types/_IdentifierGroup"
|
||||
import { SexPrerequisite } from "optolith-database-schema/types/prerequisites/single/SexPrerequisite"
|
||||
import { SexDependency } from "../../sex.ts"
|
||||
import { RegistrationFunction, addOrRemoveDependency } from "../registrationHelpers.ts"
|
||||
|
||||
/**
|
||||
* Registers or unregisters a {@link SexPrerequisite} as a dependency on the
|
||||
* character's draft.
|
||||
*/
|
||||
export const registerOrUnregisterSexPrerequisiteAsDependency: RegistrationFunction<
|
||||
SexPrerequisite,
|
||||
ActivatableIdentifier
|
||||
> = (method, character, p, sourceId, index, isPartOfDisjunction): void => {
|
||||
const dependency: SexDependency = {
|
||||
value: {
|
||||
kind: "BinarySex",
|
||||
value: p.id,
|
||||
},
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
}
|
||||
|
||||
addOrRemoveDependency(method, character.personalData.sexDependencies, dependency)
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import { ActivatableIdentifier } from "optolith-database-schema/types/_IdentifierGroup"
|
||||
import { SexualCharacteristicPrerequisite } from "optolith-database-schema/types/prerequisites/single/SexualCharacteristicPrerequisite"
|
||||
import { SexDependency } from "../../sex.ts"
|
||||
import { RegistrationFunction, addOrRemoveDependency } from "../registrationHelpers.ts"
|
||||
|
||||
/**
|
||||
* Registers or unregisters a {@link SexualCharacteristicPrerequisite} as a
|
||||
* dependency on the character's draft.
|
||||
*/
|
||||
export const registerOrUnregisterSexualCharacteristicPrerequisiteAsDependency: RegistrationFunction<
|
||||
SexualCharacteristicPrerequisite,
|
||||
ActivatableIdentifier
|
||||
> = (method, character, p, sourceId, index, isPartOfDisjunction): void => {
|
||||
const dependency: SexDependency = {
|
||||
value: {
|
||||
kind: "SexualCharacteristic",
|
||||
value: p.id,
|
||||
},
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
}
|
||||
|
||||
addOrRemoveDependency(method, character.personalData.sexDependencies, dependency)
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
import { ActivatableIdentifier } from "optolith-database-schema/types/_IdentifierGroup"
|
||||
import { SocialStatusPrerequisite } from "optolith-database-schema/types/prerequisites/single/SocialStatusPrerequisite"
|
||||
import { SocialStatusDependency } from "../../socialStatus.ts"
|
||||
import { RegistrationFunction, addOrRemoveDependency } from "../registrationHelpers.ts"
|
||||
|
||||
/**
|
||||
* Registers or unregisters a {@link SocialStatusPrerequisite} as a dependency
|
||||
* on the character's draft.
|
||||
*/
|
||||
export const registerOrUnregisterSocialStatusPrerequisiteAsDependency: RegistrationFunction<
|
||||
SocialStatusPrerequisite,
|
||||
ActivatableIdentifier
|
||||
> = (method, character, p, sourceId, index, isPartOfDisjunction): void => {
|
||||
const dependency: SocialStatusDependency = {
|
||||
id: p.id.social_status,
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
}
|
||||
|
||||
addOrRemoveDependency(method, character.personalData.socialStatus.dependencies, dependency)
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { ActivatableIdentifier } from "optolith-database-schema/types/_IdentifierGroup"
|
||||
import { StatePrerequisite } from "optolith-database-schema/types/prerequisites/single/StatePrerequisite"
|
||||
import { StateDependency, createState } from "../../state.ts"
|
||||
import { RegistrationFunction, addOrRemoveDependencyInSlice } from "../registrationHelpers.ts"
|
||||
|
||||
/**
|
||||
* Registers or unregisters a {@link StatePrerequisite} as a dependency on the
|
||||
* character's draft.
|
||||
*/
|
||||
export const registerOrUnregisterStatePrerequisiteAsDependency: RegistrationFunction<
|
||||
StatePrerequisite,
|
||||
ActivatableIdentifier
|
||||
> = (method, character, p, sourceId, index, isPartOfDisjunction): void => {
|
||||
const dependency: StateDependency = {
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction,
|
||||
}
|
||||
|
||||
addOrRemoveDependencyInSlice(method, character.states, p.id.state, createState, dependency)
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import {
|
||||
ActivatableIdentifier,
|
||||
SkillWithEnhancementsIdentifier,
|
||||
} from "optolith-database-schema/types/_IdentifierGroup"
|
||||
import { TextPrerequisite } from "optolith-database-schema/types/prerequisites/single/TextPrerequisite"
|
||||
import { RegistrationFunction } from "../registrationHelpers.ts"
|
||||
|
||||
/**
|
||||
* Registers or unregisters a {@link TextPrerequisite} as a dependency on the
|
||||
* character's draft.
|
||||
*/
|
||||
export const registerOrUnregisterTextPrerequisiteAsDependency: RegistrationFunction<
|
||||
TextPrerequisite,
|
||||
ActivatableIdentifier | SkillWithEnhancementsIdentifier
|
||||
> = (): void => {
|
||||
// no op
|
||||
}
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
import { ActivatableIdentifier } from "optolith-database-schema/types/_IdentifierGroup"
|
||||
import {
|
||||
BlessedTraditionPrerequisite,
|
||||
MagicalTraditionPrerequisite,
|
||||
} from "optolith-database-schema/types/prerequisites/single/TraditionPrerequisite"
|
||||
import { assertExhaustive } from "../../../utils/typeSafety.ts"
|
||||
import { ActivatableDependency } from "../../activatable/activatableDependency.ts"
|
||||
import { createEmptyDynamicActivatable } from "../../activatable/activatableEntry.ts"
|
||||
import { RegistrationFunction, addOrRemoveDependencyInSlice } from "../registrationHelpers.ts"
|
||||
|
||||
/**
|
||||
* Registers or unregisters a {@link BlessedTraditionPrerequisite} as a
|
||||
* dependency on the character's draft.
|
||||
*/
|
||||
export const registerOrUnregisterBlessedTraditionPrerequisiteAsDependency: RegistrationFunction<
|
||||
BlessedTraditionPrerequisite,
|
||||
ActivatableIdentifier,
|
||||
{
|
||||
blessedTraditionChurchIds: number[]
|
||||
blessedTraditionShamanisticIds: number[]
|
||||
}
|
||||
> = (
|
||||
method,
|
||||
character,
|
||||
p,
|
||||
sourceId,
|
||||
index,
|
||||
_isPartOfDisjunction,
|
||||
{ blessedTraditionChurchIds, blessedTraditionShamanisticIds },
|
||||
): void => {
|
||||
const dependency: ActivatableDependency = {
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction: true,
|
||||
active: true,
|
||||
}
|
||||
|
||||
switch (p.restriction) {
|
||||
case "Church":
|
||||
return blessedTraditionChurchIds.forEach(id =>
|
||||
addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.blessedTraditions,
|
||||
id,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
),
|
||||
)
|
||||
case "Shamanistic":
|
||||
return blessedTraditionShamanisticIds.forEach(id =>
|
||||
addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.blessedTraditions,
|
||||
id,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
),
|
||||
)
|
||||
case undefined:
|
||||
return [...blessedTraditionChurchIds, ...blessedTraditionShamanisticIds].forEach(id =>
|
||||
addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.blessedTraditions,
|
||||
id,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
),
|
||||
)
|
||||
default:
|
||||
return assertExhaustive(p.restriction)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers or unregisters a {@link MagicalTraditionPrerequisite} as a
|
||||
* dependency on the character's draft.
|
||||
*/
|
||||
export const registerOrUnregisterMagicalTraditionPrerequisiteAsDependency: RegistrationFunction<
|
||||
MagicalTraditionPrerequisite,
|
||||
ActivatableIdentifier,
|
||||
{
|
||||
magicalTraditionIds: number[]
|
||||
magicalTraditionIdsThatCanLearnRituals: number[]
|
||||
magicalTraditionIdsThatCanBindFamiliars: number[]
|
||||
}
|
||||
> = (
|
||||
method,
|
||||
character,
|
||||
p,
|
||||
sourceId,
|
||||
index,
|
||||
_isPartOfDisjunction,
|
||||
{
|
||||
magicalTraditionIds,
|
||||
magicalTraditionIdsThatCanLearnRituals,
|
||||
magicalTraditionIdsThatCanBindFamiliars,
|
||||
},
|
||||
): void => {
|
||||
const dependency: ActivatableDependency = {
|
||||
sourceId,
|
||||
index,
|
||||
isPartOfDisjunction: true,
|
||||
active: true,
|
||||
}
|
||||
|
||||
switch (p.restriction) {
|
||||
case "CanLearnRituals":
|
||||
return magicalTraditionIdsThatCanLearnRituals.forEach(id =>
|
||||
addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.magicalTraditions,
|
||||
id,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
),
|
||||
)
|
||||
case "CanBindFamiliars":
|
||||
return magicalTraditionIdsThatCanBindFamiliars.forEach(id =>
|
||||
addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.magicalTraditions,
|
||||
id,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
),
|
||||
)
|
||||
case undefined:
|
||||
return magicalTraditionIds.forEach(id =>
|
||||
addOrRemoveDependencyInSlice(
|
||||
method,
|
||||
character.specialAbilities.magicalTraditions,
|
||||
id,
|
||||
createEmptyDynamicActivatable,
|
||||
dependency,
|
||||
),
|
||||
)
|
||||
default:
|
||||
return assertExhaustive(p.restriction)
|
||||
}
|
||||
}
|
||||
@@ -153,9 +153,11 @@ import { config as animalShapePathConfig } from "optolith-database-schema/types/
|
||||
import { config as animalShapeSizeConfig } from "optolith-database-schema/types/traditionArtifacts/sub/AnimalShapeSize"
|
||||
import { config as brewConfig } from "optolith-database-schema/types/traditionArtifacts/sub/Brew"
|
||||
import { Activatable, TinyActivatable } from "./activatable/activatableEntry.ts"
|
||||
import { Pact as PactInstance } from "./pact.ts"
|
||||
import { ActivatableRated, ActivatableRatedWithEnhancements, Rated } from "./rated/ratedEntry.ts"
|
||||
import { FocusRuleInstance } from "./rules/focusRule.ts"
|
||||
import { OptionalRuleInstance } from "./rules/optionalRule.ts"
|
||||
import { Sex as SexInstance } from "./sex.ts"
|
||||
import { StateInstance } from "./state.ts"
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@@ -409,7 +411,7 @@ export namespace GetById {
|
||||
// export type MagicalArtifact = GetByIdDynamic<typeof magicalArtifactConfig, never>
|
||||
export type MagicalDance = GetByIdDynamic<typeof magicalDanceConfig, ActivatableRated>
|
||||
export type MagicalMelody = GetByIdDynamic<typeof magicalMelodyConfig, ActivatableRated>
|
||||
export type MagicalRune = GetByIdDynamic<typeof magicalRuneConfig, Activatable>
|
||||
export type MagicalRune = GetByIdDynamic<typeof magicalRuneConfig, ActivatableRated>
|
||||
export type MagicalSign = GetByIdDynamic<typeof magicalSignConfig, Activatable>
|
||||
export type MagicalSpecialAbility = GetByIdDynamic<typeof magicalSpecialAbilityConfig, Activatable>
|
||||
export type MagicalTradition = GetByIdDynamic<typeof magicalTraditionConfig, Activatable>
|
||||
@@ -456,6 +458,18 @@ export namespace GetById {
|
||||
}
|
||||
}
|
||||
|
||||
// prettier-ignore
|
||||
export namespace Singleton {
|
||||
export namespace Static {
|
||||
export type ExperienceLevel = TypeFromConfig<typeof experienceLevelConfig> | undefined
|
||||
}
|
||||
|
||||
export namespace Dynamic {
|
||||
export type Pact = PactInstance | undefined
|
||||
export type Sex = SexInstance
|
||||
}
|
||||
}
|
||||
|
||||
// prettier-ignore
|
||||
export namespace All {
|
||||
export namespace Static {
|
||||
@@ -679,7 +693,7 @@ export namespace All {
|
||||
// export type MagicalArtifacts = GetAllDynamic<typeof magicalArtifactConfig, never>
|
||||
export type MagicalDances = AllDynamic<typeof magicalDanceConfig, ActivatableRated>
|
||||
export type MagicalMelodies = AllDynamic<typeof magicalMelodyConfig, ActivatableRated>
|
||||
export type MagicalRunes = AllDynamic<typeof magicalRuneConfig, Activatable>
|
||||
export type MagicalRunes = AllDynamic<typeof magicalRuneConfig, ActivatableRated>
|
||||
export type MagicalSigns = AllDynamic<typeof magicalSignConfig, Activatable>
|
||||
export type MagicalSpecialAbilities = AllDynamic<typeof magicalSpecialAbilityConfig, Activatable>
|
||||
export type MagicalTraditions = AllDynamic<typeof magicalTraditionConfig, Activatable>
|
||||
|
||||
@@ -100,6 +100,7 @@ export const checkMultipleDisjunctionPartsAreValid =
|
||||
getStaticMagicalTraditionById: GetById.Static.MagicalTradition
|
||||
getStaticBlessedTraditionById: GetById.Static.BlessedTradition
|
||||
getStaticPactGiftById: GetById.Static.PactGift
|
||||
getStaticVampiricGiftById: GetById.Static.VampiricGift
|
||||
getStaticSikaryanDrainSpecialAbilityById: GetById.Static.SikaryanDrainSpecialAbility
|
||||
getStaticLycantropicGiftById: GetById.Static.LycantropicGift
|
||||
getStaticSkillStyleSpecialAbilityById: GetById.Static.SkillStyleSpecialAbility
|
||||
@@ -117,6 +118,7 @@ export const checkMultipleDisjunctionPartsAreValid =
|
||||
getStaticChronicleEnchantmentById: GetById.Static.ChronicleEnchantment
|
||||
getStaticKrallenkettenzauberById: GetById.Static.Krallenkettenzauber
|
||||
getStaticTrinkhornzauberById: GetById.Static.Trinkhornzauber
|
||||
getStaticMagicalSignById: GetById.Static.MagicalSign
|
||||
getStaticSpellById: GetById.Static.Spell
|
||||
getStaticRitualById: GetById.Static.Ritual
|
||||
getStaticLiturgicalChantById: GetById.Static.LiturgicalChant
|
||||
@@ -351,6 +353,13 @@ export const checkMultipleDisjunctionPartsAreValid =
|
||||
index,
|
||||
caps,
|
||||
)
|
||||
case "VampiricGift":
|
||||
return checkLevelPrerequisite(
|
||||
checkPrerequisiteOfGeneralEntry,
|
||||
caps.getStaticVampiricGiftById(source.vampiric_gift),
|
||||
index,
|
||||
caps,
|
||||
)
|
||||
case "SikaryanDrainSpecialAbility":
|
||||
return checkLevelPrerequisite(
|
||||
checkPrerequisiteOfGeneralEntry,
|
||||
@@ -470,6 +479,13 @@ export const checkMultipleDisjunctionPartsAreValid =
|
||||
index,
|
||||
caps,
|
||||
)
|
||||
case "MagicalSign":
|
||||
return checkLevelPrerequisite(
|
||||
checkPrerequisiteOfGeneralEntry,
|
||||
caps.getStaticMagicalSignById(source.magical_sign),
|
||||
index,
|
||||
caps,
|
||||
)
|
||||
case "Spell":
|
||||
return checkPlainPrerequisite(
|
||||
checkPrerequisiteOfSpellwork,
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
PublicationPrerequisites,
|
||||
SpellworkPrerequisites,
|
||||
} from "optolith-database-schema/types/_Prerequisite"
|
||||
import { Preconditions } from "optolith-database-schema/types/prerequisites/ConditionalPrerequisites"
|
||||
import {
|
||||
checkPlainPrerequisites,
|
||||
checkPrerequisitesForLevels,
|
||||
@@ -196,3 +197,11 @@ export const checkPrerequisitesOfEnhancement = (
|
||||
checkPlainPrerequisites(prerequisites, p =>
|
||||
SingleChecks.checkPrerequisiteOfEnhancement(capabilities, p),
|
||||
)
|
||||
|
||||
/**
|
||||
* Checks all prerequisites for an enhancement.
|
||||
*/
|
||||
export const checkPreconditions = (
|
||||
prerequisites: Preconditions,
|
||||
capabilities: Parameters<typeof SingleChecks.checkPrecondition>[0],
|
||||
): boolean => prerequisites.every(p => SingleChecks.checkPrecondition(capabilities, p))
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user