refactor: combat techniques, isolate domain logic, use prettier
This commit is contained in:
+4
-165
@@ -135,136 +135,6 @@ rules:
|
||||
no-shadow-restricted-names: 2
|
||||
# no-undef: 2
|
||||
|
||||
# Stylistic Issues
|
||||
array-bracket-spacing:
|
||||
- 2
|
||||
- always
|
||||
array-element-newline:
|
||||
- 2
|
||||
- consistent
|
||||
block-spacing: 2
|
||||
# capitalized-comments: 2
|
||||
comma-dangle:
|
||||
- 2
|
||||
- arrays: always-multiline
|
||||
objects: always-multiline
|
||||
imports: always-multiline
|
||||
exports: always-multiline
|
||||
functions: only-multiline
|
||||
comma-style: 2
|
||||
computed-property-spacing: 2
|
||||
eol-last: 2
|
||||
func-name-matching: 2
|
||||
func-names: 2
|
||||
func-style:
|
||||
- 2
|
||||
- declaration
|
||||
- allowArrowFunctions: true
|
||||
function-call-argument-newline:
|
||||
- 2
|
||||
- consistent
|
||||
function-paren-newline:
|
||||
- 2
|
||||
- multiline-arguments
|
||||
jsx-quotes: 2
|
||||
key-spacing: 2
|
||||
keyword-spacing: 2
|
||||
lines-around-comment:
|
||||
- 0
|
||||
# requires an empty line before block comments
|
||||
- beforeBlockComment: true
|
||||
# requires an empty line after block comments
|
||||
afterBlockComment: false
|
||||
# requires an empty line before line comments
|
||||
beforeLineComment: true
|
||||
# requires an empty line after line comments
|
||||
afterLineComment: false
|
||||
# allows comments to appear at the start of block statements
|
||||
allowBlockStart: true
|
||||
# allows comments to appear at the end of block statements
|
||||
allowBlockEnd: false
|
||||
# allows comments to appear at the start of object literals
|
||||
allowObjectStart: true
|
||||
# allows comments to appear at the end of object literals
|
||||
allowObjectEnd: false
|
||||
# allows comments to appear at the start of array literals
|
||||
allowArrayStart: true
|
||||
# allows comments to appear at the end of array literals
|
||||
allowArrayEnd: false
|
||||
# allows comments to appear at the start of classes
|
||||
allowClassStart: true
|
||||
# allows comments to appear at the end of classes
|
||||
allowClassEnd: false
|
||||
ignorePattern: "^ *@"
|
||||
lines-between-class-members: 2
|
||||
max-len:
|
||||
- 2
|
||||
- code: 100
|
||||
ignoreComments: true
|
||||
ignoreTemplateLiterals: true
|
||||
ignorePattern: "^import |^export \\{(.*?)\\}|^ \\* |^ *it ?\\(\"\\w+|^export const \\w+ = createAction(?:<.+?>)?\\(\"\\w+/\\w+\"\\)"
|
||||
max-statements-per-line: [2, { max: 2 }]
|
||||
multiline-ternary:
|
||||
- 2
|
||||
- always-multiline
|
||||
new-parens: 2
|
||||
newline-per-chained-call:
|
||||
- 2
|
||||
- ignoreChainWithDepth: 2
|
||||
no-bitwise: 2
|
||||
no-continue: 2
|
||||
no-lonely-if: 2
|
||||
no-mixed-operators:
|
||||
- 2
|
||||
- groups:
|
||||
- ["&", "|", "^", "~", "<<", ">>", ">>>"]
|
||||
- ["==", "!=", "===", "!==", ">", ">=", "<", "<="]
|
||||
- ["&&", "||"]
|
||||
- ["in", "instanceof"]
|
||||
allowSamePrecedence: true
|
||||
no-mixed-spaces-and-tabs: 2
|
||||
no-multiple-empty-lines: 2
|
||||
no-negated-condition: 2
|
||||
no-new-object: 2
|
||||
no-tabs: 2
|
||||
no-trailing-spaces: 2
|
||||
no-unneeded-ternary: 2
|
||||
nonblock-statement-body-position: 2
|
||||
# object-curly-newline:
|
||||
# - 2
|
||||
# - multiline: true
|
||||
object-curly-spacing:
|
||||
- 2
|
||||
- always
|
||||
operator-assignment: 2
|
||||
operator-linebreak:
|
||||
- 2
|
||||
- before
|
||||
- overrides:
|
||||
"=": ignore
|
||||
padded-blocks:
|
||||
- 2
|
||||
- blocks: never
|
||||
classes: never
|
||||
switches: never
|
||||
prefer-object-spread: 2
|
||||
quote-props:
|
||||
- 2
|
||||
- as-needed
|
||||
semi-spacing: 2
|
||||
semi-style: 2
|
||||
space-before-blocks: 2
|
||||
space-in-parens: 2
|
||||
space-infix-ops:
|
||||
- 2
|
||||
- int32Hint: true
|
||||
space-unary-ops: 2
|
||||
spaced-comment: 2
|
||||
switch-colon-spacing: 2
|
||||
template-tag-spacing:
|
||||
- 2
|
||||
- always
|
||||
|
||||
# ECMAScript 6
|
||||
arrow-body-style: 2
|
||||
arrow-parens:
|
||||
@@ -272,9 +142,7 @@ rules:
|
||||
- as-needed
|
||||
arrow-spacing: 2
|
||||
constructor-super: 2
|
||||
generator-star-spacing:
|
||||
- 2
|
||||
- after
|
||||
generator-star-spacing: error
|
||||
no-class-assign: 2
|
||||
no-new-symbol: 2
|
||||
no-this-before-super: 2
|
||||
@@ -354,46 +222,17 @@ rules:
|
||||
"@typescript-eslint/unified-signatures": 2
|
||||
|
||||
# TypeScript Extension
|
||||
brace-style: 0
|
||||
"@typescript-eslint/brace-style":
|
||||
- 2
|
||||
- stroustrup
|
||||
comma-spacing: 0
|
||||
"@typescript-eslint/comma-spacing": 2
|
||||
func-call-spacing: 0
|
||||
"@typescript-eslint/func-call-spacing": 2
|
||||
indent: 0
|
||||
"@typescript-eslint/indent": 0
|
||||
no-array-constructor: 0
|
||||
"@typescript-eslint/no-array-constructor": 2
|
||||
no-extra-parens: 0
|
||||
"@typescript-eslint/no-extra-parens": 0
|
||||
no-extra-semi: 0
|
||||
"@typescript-eslint/no-extra-semi": 2
|
||||
no-unused-expressions: 0
|
||||
"@typescript-eslint/no-redeclare": 2
|
||||
"@typescript-eslint/no-shadow": 2
|
||||
"@typescript-eslint/no-unused-expressions": 2
|
||||
"@typescript-eslint/no-unused-vars":
|
||||
- 2
|
||||
- argsIgnorePattern: "^_"
|
||||
varsIgnorePattern: "^_"
|
||||
"@typescript-eslint/no-use-before-define": 2
|
||||
no-useless-constructor: 0
|
||||
"@typescript-eslint/no-useless-constructor": 2
|
||||
quotes: 0
|
||||
"@typescript-eslint/quotes":
|
||||
- 2
|
||||
- double
|
||||
- avoidEscape: true
|
||||
allowTemplateLiterals: true
|
||||
no-return-await: 0
|
||||
"@typescript-eslint/return-await": 2
|
||||
semi: 0
|
||||
"@typescript-eslint/semi":
|
||||
- 2
|
||||
- never
|
||||
- beforeStatementContinuationChars: never
|
||||
|
||||
# React
|
||||
react/boolean-prop-naming: 2
|
||||
@@ -434,9 +273,9 @@ rules:
|
||||
- 2
|
||||
- never
|
||||
react/jsx-child-element-spacing: 2
|
||||
react/jsx-closing-bracket-location:
|
||||
- 2
|
||||
- props-aligned
|
||||
# react/jsx-closing-bracket-location:
|
||||
# - 2
|
||||
# - props-aligned
|
||||
react/jsx-closing-tag-location: 2
|
||||
react/jsx-curly-newline: 2
|
||||
react/jsx-curly-spacing: 2
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
arrowParens: avoid
|
||||
printWidth: 100
|
||||
semi: false
|
||||
tabWidth: 2
|
||||
trailingComma: all
|
||||
Vendored
+7
-1
@@ -17,5 +17,11 @@
|
||||
"eslint.lintTask.enable": true,
|
||||
"eslint.lintTask.options": "\"src/**\" -c \"./.eslintrc.yaml\" --ext=\"ts\" --ext=\"tsx\" --ignore-pattern=\"./src/App/Utilities/YAML/Schema/**\"",
|
||||
"editor.tabSize": 2,
|
||||
"editor.detectIndentation": false
|
||||
"editor.detectIndentation": false,
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[typescriptreact]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
import { connect } from "react-redux"
|
||||
import { fromJust, isJust, Maybe } from "../../Data/Maybe"
|
||||
import { ReduxDispatch } from "../Actions/Actions"
|
||||
import * as AttributesActions from "../Actions/AttributesActions"
|
||||
import * as SubwindowsActions from "../Actions/SubwindowsActions"
|
||||
import { EnergyId } from "../Constants/Ids"
|
||||
import { AppStateRecord } from "../Models/AppState"
|
||||
import { getAdjustmentValue, getAttributesForView, getAttributeSum, getAvailableAdjustmentIds } from "../Selectors/attributeSelectors"
|
||||
import { getDerivedCharacteristics } from "../Selectors/derivedCharacteristicsSelectors"
|
||||
import { getMaxTotalAttributeValues } from "../Selectors/elSelectors"
|
||||
import { getIsInCharacterCreation, getIsRemovingEnabled } from "../Selectors/phaseSelectors"
|
||||
import { getAddPermanentEnergy, getCurrentAttributeAdjustmentId, getEditPermanentEnergy } from "../Selectors/stateSelectors"
|
||||
import { Attributes, AttributesDispatchProps, AttributesOwnProps, AttributesStateProps } from "../Views/Attributes/Attributes"
|
||||
|
||||
const mapStateToProps =
|
||||
(state: AppStateRecord, ownProps: AttributesOwnProps): AttributesStateProps => ({
|
||||
adjustmentValue: getAdjustmentValue (state, ownProps),
|
||||
attributes: getAttributesForView (state, ownProps),
|
||||
availableAttributeIds: getAvailableAdjustmentIds (state, ownProps),
|
||||
currentAttributeId: getCurrentAttributeAdjustmentId (state),
|
||||
isInCharacterCreation: getIsInCharacterCreation (state),
|
||||
isRemovingEnabled: getIsRemovingEnabled (state),
|
||||
derived: getDerivedCharacteristics (state, ownProps),
|
||||
maxTotalAttributeValues: getMaxTotalAttributeValues (state),
|
||||
sum: getAttributeSum (state, ownProps),
|
||||
getEditPermanentEnergy: getEditPermanentEnergy (state),
|
||||
getAddPermanentEnergy: getAddPermanentEnergy (state),
|
||||
})
|
||||
|
||||
const mapDispatchToProps = (dispatch: ReduxDispatch): AttributesDispatchProps => ({
|
||||
addPoint: async (id: string) => {
|
||||
await dispatch (AttributesActions.addAttributePoint (id))
|
||||
},
|
||||
removePoint: (id: string) => {
|
||||
dispatch (AttributesActions.removeAttributePoint (id))
|
||||
},
|
||||
addLifePoint: async () => {
|
||||
await dispatch (AttributesActions.addLifePoint)
|
||||
},
|
||||
addArcaneEnergyPoint: async () => {
|
||||
await dispatch (AttributesActions.addArcaneEnergyPoint)
|
||||
},
|
||||
addKarmaPoint: async () => {
|
||||
await dispatch (AttributesActions.addKarmaPoint)
|
||||
},
|
||||
removeLifePoint: () => {
|
||||
dispatch (AttributesActions.removeLifePoint ())
|
||||
},
|
||||
removeArcaneEnergyPoint: () => {
|
||||
dispatch (AttributesActions.removeArcaneEnergyPoint ())
|
||||
},
|
||||
removeKarmaPoint: () => {
|
||||
dispatch (AttributesActions.removeKarmaPoint ())
|
||||
},
|
||||
addLostLPPoint: () => {
|
||||
dispatch (AttributesActions.addLostLPPoint ())
|
||||
},
|
||||
removeLostLPPoint: () => {
|
||||
dispatch (AttributesActions.removeLostLPPoint ())
|
||||
},
|
||||
addLostLPPoints: (value: number) => {
|
||||
dispatch (AttributesActions.addLostLPPoints (value))
|
||||
},
|
||||
addBoughtBackAEPoint: async () => {
|
||||
await dispatch (AttributesActions.addBoughtBackAEPoint)
|
||||
},
|
||||
removeBoughtBackAEPoint: () => {
|
||||
dispatch (AttributesActions.removeBoughtBackAEPoint ())
|
||||
},
|
||||
addLostAEPoint: () => {
|
||||
dispatch (AttributesActions.addLostAEPoint ())
|
||||
},
|
||||
removeLostAEPoint: () => {
|
||||
dispatch (AttributesActions.removeLostAEPoint ())
|
||||
},
|
||||
addLostAEPoints: (value: number) => {
|
||||
dispatch (AttributesActions.addLostAEPoints (value))
|
||||
},
|
||||
addBoughtBackKPPoint: async () => {
|
||||
await dispatch (AttributesActions.addBoughtBackKPPoint)
|
||||
},
|
||||
removeBoughtBackKPPoint: () => {
|
||||
dispatch (AttributesActions.removeBoughtBackKPPoint ())
|
||||
},
|
||||
addLostKPPoint: () => {
|
||||
dispatch (AttributesActions.addLostKPPoint ())
|
||||
},
|
||||
removeLostKPPoint: () => {
|
||||
dispatch (AttributesActions.removeLostKPPoint ())
|
||||
},
|
||||
addLostKPPoints: (value: number) => {
|
||||
dispatch (AttributesActions.addLostKPPoints (value))
|
||||
},
|
||||
openAddPermanentEnergyLoss: (energy: EnergyId) => {
|
||||
console.log (`openAddPermanentEnergyLoss (${energy})`)
|
||||
dispatch (SubwindowsActions.openAddPermanentEnergyLoss (energy))
|
||||
},
|
||||
closeAddPermanentEnergyLoss: () => {
|
||||
console.log (`closeAddPermanentEnergyLoss ()`)
|
||||
dispatch (SubwindowsActions.closeAddPermanentEnergyLoss ())
|
||||
},
|
||||
openEditPermanentEnergy: (energy: EnergyId) => {
|
||||
console.log (`openEditPermanentEnergy (${energy})`)
|
||||
dispatch (SubwindowsActions.openEditPermanentEnergy (energy))
|
||||
},
|
||||
closeEditPermanentEnergy: () => {
|
||||
console.log (`closeEditPermanentEnergy ()`)
|
||||
dispatch (SubwindowsActions.closeEditPermanentEnergy ())
|
||||
},
|
||||
setAdjustmentId: (id: Maybe<string>) => {
|
||||
if (isJust (id)) {
|
||||
dispatch (AttributesActions.setAdjustmentId (fromJust (id)))
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const connectAttributes =
|
||||
connect<AttributesStateProps, AttributesDispatchProps, AttributesOwnProps, AppStateRecord> (
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)
|
||||
|
||||
export const AttributesContainer = connectAttributes (Attributes)
|
||||
@@ -1,50 +0,0 @@
|
||||
import { connect } from "react-redux"
|
||||
import { ReduxDispatch } from "../Actions/Actions"
|
||||
import * as CombatTechniquesActions from "../Actions/CombatTechniquesActions"
|
||||
import { AppStateRecord } from "../Models/AppState"
|
||||
import { CombatTechniquesSortOptions } from "../Models/Config"
|
||||
import { getAttributesForSheet } from "../Selectors/attributeSelectors"
|
||||
import { getFilteredCombatTechniques } from "../Selectors/combatTechniquesSelectors"
|
||||
import { getIsRemovingEnabled } from "../Selectors/phaseSelectors"
|
||||
import { getCombatTechniquesFilterText } from "../Selectors/stateSelectors"
|
||||
import { getCombatTechniquesSortOrder } from "../Selectors/uisettingsSelectors"
|
||||
import { CombatTechniques, CombatTechniquesDispatchProps, CombatTechniquesOwnProps, CombatTechniquesStateProps } from "../Views/CombatTechniques/CombatTechniques"
|
||||
|
||||
const mapStateToProps = (
|
||||
state: AppStateRecord,
|
||||
ownProps: CombatTechniquesOwnProps
|
||||
): CombatTechniquesStateProps => ({
|
||||
attributes: getAttributesForSheet (state, ownProps),
|
||||
isRemovingEnabled: getIsRemovingEnabled (state),
|
||||
list: getFilteredCombatTechniques (state, ownProps),
|
||||
sortOrder: getCombatTechniquesSortOrder (state),
|
||||
filterText: getCombatTechniquesFilterText (state),
|
||||
})
|
||||
|
||||
const mapDispatchToProps = (dispatch: ReduxDispatch) => ({
|
||||
async addPoint (id: string) {
|
||||
await dispatch (CombatTechniquesActions.addCombatTechniquePoint (id))
|
||||
},
|
||||
removePoint (id: string) {
|
||||
dispatch (CombatTechniquesActions.removeCombatTechniquePoint (id))
|
||||
},
|
||||
setSortOrder (sortOrder: CombatTechniquesSortOptions) {
|
||||
dispatch (CombatTechniquesActions.setCombatTechniquesSortOrder (sortOrder))
|
||||
},
|
||||
setFilterText (filterText: string) {
|
||||
dispatch (CombatTechniquesActions.setCombatTechniquesFilterText (filterText))
|
||||
},
|
||||
})
|
||||
|
||||
const connectCombatTechniques =
|
||||
connect<
|
||||
CombatTechniquesStateProps,
|
||||
CombatTechniquesDispatchProps,
|
||||
CombatTechniquesOwnProps,
|
||||
AppStateRecord
|
||||
> (
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)
|
||||
|
||||
export const CombatTechniquesContainer = connectCombatTechniques (CombatTechniques)
|
||||
@@ -1,58 +0,0 @@
|
||||
import { ipcRenderer } from "electron"
|
||||
import { connect } from "react-redux"
|
||||
import { fromJust, isJust, Maybe } from "../../Data/Maybe"
|
||||
import { ReduxDispatch } from "../Actions/Actions"
|
||||
import * as ConfigActions from "../Actions/ConfigActions"
|
||||
import * as FallbackLocaleActions from "../Actions/FallbackLocaleActions"
|
||||
import * as IOActions from "../Actions/IOActions"
|
||||
import * as LocaleActions from "../Actions/LocaleActions"
|
||||
import { AppStateRecord } from "../Models/AppState"
|
||||
import { Locale, Theme } from "../Models/Config"
|
||||
import { getUserSelectableSupportedLanguages } from "../Selectors/localeSelectors"
|
||||
import { getFallbackLocaleId, getFallbackLocaleType, getLocaleId, getLocaleType } from "../Selectors/stateSelectors"
|
||||
import { areAnimationsEnabled, getIsEditingHeroAfterCreationPhaseEnabled, getTheme } from "../Selectors/uisettingsSelectors"
|
||||
import { IPCChannels } from "../Utilities/IPCChannels"
|
||||
import { Settings, SettingsDispatchProps, SettingsOwnProps, SettingsStateProps } from "../Views/Settings/Settings"
|
||||
|
||||
const mapStateToProps = (state: AppStateRecord): SettingsStateProps => ({
|
||||
localeString: getLocaleId (state),
|
||||
localeType: getLocaleType (state),
|
||||
fallbackLocaleString: getFallbackLocaleId (state),
|
||||
fallbackLocaleType: getFallbackLocaleType (state),
|
||||
isEditingHeroAfterCreationPhaseEnabled: getIsEditingHeroAfterCreationPhaseEnabled (state),
|
||||
areAnimationsEnabled: areAnimationsEnabled (state),
|
||||
theme: getTheme (state),
|
||||
languages: getUserSelectableSupportedLanguages (state),
|
||||
isCheckForUpdatesDisabled: !(ipcRenderer.sendSync (IPCChannels.IsUpdaterEnabled) as boolean),
|
||||
})
|
||||
|
||||
const mapDispatchToProps = (dispatch: ReduxDispatch): SettingsDispatchProps => ({
|
||||
setTheme (theme: Maybe<Theme>) {
|
||||
if (isJust (theme)) {
|
||||
dispatch (ConfigActions.setTheme (fromJust (theme)))
|
||||
}
|
||||
},
|
||||
switchEnableEditingHeroAfterCreationPhase () {
|
||||
dispatch (ConfigActions.switchEnableEditingHeroAfterCreationPhase ())
|
||||
},
|
||||
async saveConfig () {
|
||||
await dispatch (IOActions.requestConfigSave)
|
||||
},
|
||||
setLocale (id: Maybe<Locale>) {
|
||||
dispatch (LocaleActions.setLocale (id))
|
||||
},
|
||||
setFallbackLocale (id: Maybe<Locale>) {
|
||||
dispatch (FallbackLocaleActions.setFallbackLocale (id))
|
||||
},
|
||||
switchEnableAnimations () {
|
||||
dispatch (ConfigActions.switchEnableAnimations ())
|
||||
},
|
||||
})
|
||||
|
||||
const connectSettings =
|
||||
connect<SettingsStateProps, SettingsDispatchProps, SettingsOwnProps, AppStateRecord> (
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)
|
||||
|
||||
export const SettingsContainer = connectSettings (Settings)
|
||||
@@ -1,47 +0,0 @@
|
||||
import { connect } from "react-redux"
|
||||
import { ReduxDispatch } from "../Actions/Actions"
|
||||
import * as SkillActions from "../Actions/SkillActions"
|
||||
import { AppStateRecord } from "../Models/AppState"
|
||||
import { SkillsSortOptions } from "../Models/Config"
|
||||
import { getAttributesForSheet } from "../Selectors/attributeSelectors"
|
||||
import { getIsRemovingEnabled } from "../Selectors/phaseSelectors"
|
||||
import { getFilteredSkills, getSkillRating } from "../Selectors/skillsSelectors"
|
||||
import { getSkillsFilterText } from "../Selectors/stateSelectors"
|
||||
import { getSkillsCultureRatingVisibility, getSkillsSortOrder } from "../Selectors/uisettingsSelectors"
|
||||
import { Skills, SkillsDispatchProps, SkillsOwnProps, SkillsStateProps } from "../Views/Skills/Skills"
|
||||
|
||||
const mapStateToProps = (state: AppStateRecord, ownProps: SkillsOwnProps): SkillsStateProps => ({
|
||||
attributes: getAttributesForSheet (state, ownProps),
|
||||
isRemovingEnabled: getIsRemovingEnabled (state),
|
||||
list: getFilteredSkills (state),
|
||||
sortOrder: getSkillsSortOrder (state),
|
||||
filterText: getSkillsFilterText (state),
|
||||
ratingVisibility: getSkillsCultureRatingVisibility (state),
|
||||
skillRating: getSkillRating (state),
|
||||
})
|
||||
|
||||
const mapDispatchToProps = (dispatch: ReduxDispatch): SkillsDispatchProps => ({
|
||||
async addPoint (id: string) {
|
||||
await dispatch (SkillActions.addSkillPoint (id))
|
||||
},
|
||||
removePoint (id: string) {
|
||||
dispatch (SkillActions.removeSkillPoint (id))
|
||||
},
|
||||
setSortOrder (sortOrder: SkillsSortOptions) {
|
||||
dispatch (SkillActions.setSkillsSortOrder (sortOrder))
|
||||
},
|
||||
switchRatingVisibility () {
|
||||
dispatch (SkillActions.switchSkillRatingVisibility ())
|
||||
},
|
||||
setFilterText (filterText: string) {
|
||||
dispatch (SkillActions.setSkillsFilterText (filterText))
|
||||
},
|
||||
})
|
||||
|
||||
export const connectSkills =
|
||||
connect<SkillsStateProps, SkillsDispatchProps, SkillsOwnProps, AppStateRecord> (
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)
|
||||
|
||||
export const SkillsContainer = connectSkills (Skills)
|
||||
@@ -1,188 +0,0 @@
|
||||
import { ident, thrush } from "../../Data/Function"
|
||||
import { fmap, fmapF } from "../../Data/Functor"
|
||||
import { consF, filter, fnull, List, map } from "../../Data/List"
|
||||
import { Just, liftM2, Maybe, maybe, Nothing } from "../../Data/Maybe"
|
||||
import { add, divideBy, gt, max, subtractBy } from "../../Data/Num"
|
||||
import { findWithDefault, foldrWithKey, lookup } from "../../Data/OrderedMap"
|
||||
import { Record } from "../../Data/Record"
|
||||
import { uncurryN } from "../../Data/Tuple/Curry"
|
||||
import { IdPrefixes } from "../Constants/IdPrefixes"
|
||||
import { CombatTechniqueId, SpecialAbilityId } from "../Constants/Ids"
|
||||
import { ActivatableDependent } from "../Models/ActiveEntries/ActivatableDependent"
|
||||
import { createSkillDependentWithValue6, SkillDependent } from "../Models/ActiveEntries/SkillDependent"
|
||||
import { HeroModel, HeroModelRecord } from "../Models/Hero/HeroModel"
|
||||
import { CombatTechniqueWithAttackParryBase, CombatTechniqueWithAttackParryBaseA_ } from "../Models/View/CombatTechniqueWithAttackParryBase"
|
||||
import { CombatTechniqueWithRequirements } from "../Models/View/CombatTechniqueWithRequirements"
|
||||
import { CombatTechnique } from "../Models/Wiki/CombatTechnique"
|
||||
import { StaticData } from "../Models/Wiki/WikiModel"
|
||||
import { isMaybeActive } from "../Utilities/Activatable/isActive"
|
||||
import { createMaybeSelector } from "../Utilities/createMaybeSelector"
|
||||
import { filterAndSortRecordsBy } from "../Utilities/filterAndSortBy"
|
||||
import { compareLocale } from "../Utilities/I18n"
|
||||
import { prefixId } from "../Utilities/IDUtils"
|
||||
import { isDecreaseDisabled, isIncreaseDisabled } from "../Utilities/Increasable/combatTechniqueUtils"
|
||||
import { pipe, pipe_ } from "../Utilities/pipe"
|
||||
import { filterByAvailabilityAndPred, isEntryFromCoreBook } from "../Utilities/RulesUtils"
|
||||
import { comparingR, sortByMulti } from "../Utilities/sortBy"
|
||||
import { getMaxAttributeValueByID } from "./attributeSelectors"
|
||||
import { getRuleBooksEnabled } from "./rulesSelectors"
|
||||
import { getCombatTechniquesWithRequirementsSortOptions } from "./sortOptionsSelectors"
|
||||
import { getAttributes, getCombatTechniques, getCombatTechniquesFilterText, getCurrentHeroPresent, getSpecialAbilities, getWiki, getWikiCombatTechniques } from "./stateSelectors"
|
||||
|
||||
const CTA = CombatTechnique.A
|
||||
const SDA = SkillDependent.A
|
||||
const CTWAPBA = CombatTechniqueWithAttackParryBase.A
|
||||
const CTWRA = CombatTechniqueWithRequirements.A
|
||||
const ADA = ActivatableDependent.A
|
||||
|
||||
/**
|
||||
* Calculate the AT or PA mod by passing the current attributes' state as well
|
||||
* as the relevant ids.
|
||||
*/
|
||||
const getPrimaryAttrMod =
|
||||
(attributes: HeroModel["attributes"]) =>
|
||||
pipe (
|
||||
getMaxAttributeValueByID (attributes),
|
||||
subtractBy (8),
|
||||
divideBy (3),
|
||||
Math.floor,
|
||||
max (0)
|
||||
)
|
||||
|
||||
const getAttackBase =
|
||||
(attributes: HeroModel["attributes"]) =>
|
||||
(wiki_entry: Record<CombatTechnique>) =>
|
||||
(hero_entry: Record<SkillDependent>): number =>
|
||||
pipe_ (
|
||||
CTA.gr (wiki_entry) === 2
|
||||
? CTA.primary (wiki_entry)
|
||||
: List (prefixId (IdPrefixes.ATTRIBUTES) (1)),
|
||||
getPrimaryAttrMod (attributes),
|
||||
add (SDA.value (hero_entry))
|
||||
)
|
||||
|
||||
const getParryBase =
|
||||
(attributes: HeroModel["attributes"]) =>
|
||||
(wiki_entry: Record<CombatTechnique>) =>
|
||||
(hero_entry: Record<SkillDependent>): Maybe<number> => {
|
||||
const curr_id = CTA.id (wiki_entry)
|
||||
const curr_gr = CTA.gr (wiki_entry)
|
||||
|
||||
return curr_gr === 2
|
||||
|| curr_id === prefixId (IdPrefixes.COMBAT_TECHNIQUES) (6)
|
||||
|| curr_id === prefixId (IdPrefixes.COMBAT_TECHNIQUES) (8)
|
||||
? Nothing
|
||||
: Just (Math.round (SDA.value (hero_entry) / 2)
|
||||
+ getPrimaryAttrMod (attributes) (CTA.primary (wiki_entry)))
|
||||
}
|
||||
|
||||
export const getCombatTechniquesForView = createMaybeSelector (
|
||||
getWiki,
|
||||
getWikiCombatTechniques,
|
||||
getAttributes,
|
||||
getSpecialAbilities,
|
||||
getCombatTechniques,
|
||||
(staticData, wiki_combat_techniques, attributes, special_abilities, mcombatTechniques) =>
|
||||
fmapF (mcombatTechniques)
|
||||
((combatTechniques): List<Record<CombatTechniqueWithAttackParryBase>> =>
|
||||
pipe_ (
|
||||
wiki_combat_techniques,
|
||||
foldrWithKey ((id: string) => (wiki_entry: Record<CombatTechnique>) => {
|
||||
const hero_entry =
|
||||
findWithDefault (createSkillDependentWithValue6 (id))
|
||||
(id)
|
||||
(combatTechniques)
|
||||
|
||||
if (id === CombatTechniqueId.SpittingFire
|
||||
&& maybe (true)
|
||||
(pipe (ADA.active, fnull))
|
||||
(lookup<string> (SpecialAbilityId.Feuerschlucker)
|
||||
(special_abilities))) {
|
||||
// If SF Feuerschlucker is not active, do not
|
||||
// show CT Spitting Fire
|
||||
return ident as
|
||||
ident<List<Record<CombatTechniqueWithAttackParryBase>>>
|
||||
}
|
||||
|
||||
return consF (CombatTechniqueWithAttackParryBase ({
|
||||
at: getAttackBase (attributes) (wiki_entry) (hero_entry),
|
||||
pa: getParryBase (attributes) (wiki_entry) (hero_entry),
|
||||
stateEntry: hero_entry,
|
||||
wikiEntry: wiki_entry,
|
||||
}))
|
||||
})
|
||||
(List.empty),
|
||||
sortByMulti ([ comparingR (CombatTechniqueWithAttackParryBaseA_.name)
|
||||
(compareLocale (staticData)) ])
|
||||
))
|
||||
)
|
||||
|
||||
export const getCombatTechniquesForSheet = createMaybeSelector (
|
||||
getWiki,
|
||||
getCombatTechniquesForView,
|
||||
(staticData, combatTechniques) =>
|
||||
fmapF (combatTechniques)
|
||||
(filter (x => SDA.value (CTWAPBA.stateEntry (x)) > 6
|
||||
|| isEntryFromCoreBook (CTA.src)
|
||||
(StaticData.A.books (staticData))
|
||||
(CTWAPBA.wikiEntry (x))))
|
||||
)
|
||||
|
||||
const getGr = pipe (CTWAPBA.wikiEntry, CTA.gr)
|
||||
const getValue = pipe (CTWAPBA.stateEntry, SDA.value)
|
||||
type CTWAPB = CombatTechniqueWithAttackParryBase
|
||||
|
||||
export const getAllCombatTechniques = createMaybeSelector (
|
||||
getCombatTechniquesForView,
|
||||
getCurrentHeroPresent,
|
||||
getWiki,
|
||||
(mcombat_techniques, mhero, wiki) =>
|
||||
liftM2 ((combatTechniques: List<Record<CTWAPB>>) => (hero: HeroModelRecord) => {
|
||||
const hunter = lookup<string> (SpecialAbilityId.Hunter)
|
||||
(HeroModel.A.specialAbilities (hero))
|
||||
|
||||
const hunterRequiresMinimum =
|
||||
isMaybeActive (hunter)
|
||||
&& thrush (combatTechniques) (List.any (x => getGr (x) === 2 && getValue (x) >= 10))
|
||||
|
||||
return thrush (combatTechniques)
|
||||
(map (x =>
|
||||
CombatTechniqueWithRequirements ({
|
||||
at: CTWAPBA.at (x),
|
||||
pa: CTWAPBA.pa (x),
|
||||
isDecreasable: !isDecreaseDisabled (wiki)
|
||||
(hero)
|
||||
(CTWAPBA.wikiEntry (x))
|
||||
(CTWAPBA.stateEntry (x))
|
||||
(hunterRequiresMinimum),
|
||||
isIncreasable: !isIncreaseDisabled (wiki)
|
||||
(hero)
|
||||
(CTWAPBA.wikiEntry (x))
|
||||
(CTWAPBA.stateEntry (x)),
|
||||
stateEntry: CTWAPBA.stateEntry (x),
|
||||
wikiEntry: CTWAPBA.wikiEntry (x),
|
||||
})))
|
||||
})
|
||||
(mcombat_techniques)
|
||||
(mhero)
|
||||
)
|
||||
|
||||
export const getAvailableCombatTechniques = createMaybeSelector (
|
||||
getRuleBooksEnabled,
|
||||
getAllCombatTechniques,
|
||||
uncurryN (av => fmap (filterByAvailabilityAndPred (pipe (CTWRA.wikiEntry, CTA.src))
|
||||
(pipe (CTWRA.stateEntry, SDA.value, gt (6)))
|
||||
(av)))
|
||||
)
|
||||
|
||||
export const getFilteredCombatTechniques = createMaybeSelector (
|
||||
getAvailableCombatTechniques,
|
||||
getCombatTechniquesWithRequirementsSortOptions,
|
||||
getCombatTechniquesFilterText,
|
||||
(mcombat_techniques, sortOptions, filterText) =>
|
||||
fmapF (mcombat_techniques)
|
||||
(filterAndSortRecordsBy (0)
|
||||
([ pipe (CTWRA.wikiEntry, CTA.name) ])
|
||||
(sortOptions)
|
||||
(filterText))
|
||||
)
|
||||
@@ -1,14 +0,0 @@
|
||||
import { fmap } from "../../../Data/Functor"
|
||||
import { mapMaybe, maybe } from "../../../Data/Maybe"
|
||||
import { lookupF, OrderedMap } from "../../../Data/OrderedMap"
|
||||
import { Record } from "../../../Data/Record"
|
||||
import { AttributeDependent } from "../../Models/ActiveEntries/AttributeDependent"
|
||||
import { pipe } from "../pipe"
|
||||
|
||||
const ADA = AttributeDependent.A
|
||||
|
||||
export const getSkillCheckValues =
|
||||
(attributes: OrderedMap<string, Record<AttributeDependent>>) =>
|
||||
mapMaybe (pipe (lookupF (attributes), fmap (ADA.value)))
|
||||
|
||||
export const getAttributeValueWithDefault = maybe (8) (ADA.value)
|
||||
@@ -1,108 +0,0 @@
|
||||
import { fmap } from "../../../Data/Functor"
|
||||
import { cons, elem, foldl, List, maximum } from "../../../Data/List"
|
||||
import { guard, Just, Maybe, maybe, then } from "../../../Data/Maybe"
|
||||
import { add, divideBy, max, min } from "../../../Data/Num"
|
||||
import { lookupF } from "../../../Data/OrderedMap"
|
||||
import { Record } from "../../../Data/Record"
|
||||
import { CombatTechniqueGroupId } from "../../Constants/Groups"
|
||||
import { AdvantageId, AttrId } from "../../Constants/Ids"
|
||||
import { AttributeDependent } from "../../Models/ActiveEntries/AttributeDependent"
|
||||
import { SkillDependent } from "../../Models/ActiveEntries/SkillDependent"
|
||||
import { HeroModel, HeroModelRecord } from "../../Models/Hero/HeroModel"
|
||||
import { CombatTechnique } from "../../Models/Wiki/CombatTechnique"
|
||||
import { ExperienceLevel } from "../../Models/Wiki/ExperienceLevel"
|
||||
import { StaticData, StaticDataRecord } from "../../Models/Wiki/WikiModel"
|
||||
import { getActiveSelectionsMaybe } from "../Activatable/selectionUtils"
|
||||
import { flattenDependencies } from "../Dependencies/flattenDependencies"
|
||||
import { pipe } from "../pipe"
|
||||
|
||||
const ADA = AttributeDependent.A
|
||||
const SkDA = SkillDependent.A
|
||||
const CTA = CombatTechnique.A
|
||||
const HA = HeroModel.A
|
||||
const SDA = StaticData.A
|
||||
const ELA = ExperienceLevel.A
|
||||
|
||||
const getMaxPrimaryAttributeValueById =
|
||||
(state: HeroModelRecord) =>
|
||||
foldl<string, number> (currentMax => pipe (
|
||||
lookupF (HA.attributes (state)),
|
||||
maybe (currentMax) (pipe (ADA.value, max (currentMax)))
|
||||
))
|
||||
(8)
|
||||
|
||||
const calculatePrimaryAttributeMod = pipe (add (-8), divideBy (3), Math.floor, max (0))
|
||||
|
||||
export const getPrimaryAttributeMod =
|
||||
(state: HeroModelRecord) =>
|
||||
pipe (getMaxPrimaryAttributeValueById (state), calculatePrimaryAttributeMod)
|
||||
|
||||
const getCombatTechniqueRating = maybe (6) (SkDA.value)
|
||||
|
||||
export const getAttack =
|
||||
(state: HeroModelRecord) =>
|
||||
(wikiEntry: Record<CombatTechnique>) =>
|
||||
pipe (
|
||||
getCombatTechniqueRating,
|
||||
add (getPrimaryAttributeMod (state)
|
||||
(CTA.gr (wikiEntry) === CombatTechniqueGroupId.Ranged
|
||||
? CTA.primary (wikiEntry)
|
||||
: List (AttrId.Courage)))
|
||||
)
|
||||
|
||||
export const getParry =
|
||||
(state: HeroModelRecord) =>
|
||||
(wikiEntry: Record<CombatTechnique>) =>
|
||||
(maybeStateEntry: Maybe<Record<SkillDependent>>): Maybe<number> =>
|
||||
then (guard (CTA.gr (wikiEntry) !== CombatTechniqueGroupId.Ranged
|
||||
&& !CTA.hasNoParry (wikiEntry)))
|
||||
(Just (
|
||||
Math.round (getCombatTechniqueRating (maybeStateEntry) / 2)
|
||||
+ getPrimaryAttributeMod (state) (CTA.primary (wikiEntry))
|
||||
))
|
||||
|
||||
export const isIncreaseDisabled =
|
||||
(staticData: StaticDataRecord) =>
|
||||
(state: HeroModelRecord) =>
|
||||
(wikiEntry: Record<CombatTechnique>) =>
|
||||
(instance: Record<SkillDependent>): boolean => {
|
||||
const max_by_primary = getMaxPrimaryAttributeValueById (state) (CTA.primary (wikiEntry)) + 2
|
||||
|
||||
const mmax_by_el = then (guard (HA.phase (state) < 3))
|
||||
(fmap (ELA.maxCombatTechniqueRating)
|
||||
(lookupF (SDA.experienceLevels (staticData))
|
||||
(HA.experienceLevel (state))))
|
||||
|
||||
const base_max = maybe (max_by_primary) (min (max_by_primary)) (mmax_by_el)
|
||||
|
||||
const exceptionalSkill = lookupF (HA.advantages (state))
|
||||
(AdvantageId.ExceptionalCombatTechnique)
|
||||
|
||||
const bonus = pipe (
|
||||
getActiveSelectionsMaybe,
|
||||
fmap (elem<string | number> (SkDA.id (instance))),
|
||||
Maybe.elem<boolean> (true),
|
||||
x => x ? 1 : 0
|
||||
)
|
||||
(exceptionalSkill)
|
||||
|
||||
return SkDA.value (instance) >= base_max + bonus
|
||||
}
|
||||
|
||||
export const isDecreaseDisabled =
|
||||
(staticData: StaticDataRecord) =>
|
||||
(state: HeroModelRecord) =>
|
||||
(wikiEntry: Record<CombatTechnique>) =>
|
||||
(instance: Record<SkillDependent>) =>
|
||||
(onlyOneCombatTechniqueForHunter: boolean): boolean => {
|
||||
const disabledByHunter =
|
||||
onlyOneCombatTechniqueForHunter
|
||||
&& CTA.gr (wikiEntry) === 2
|
||||
&& SkDA.value (instance) === 10
|
||||
|
||||
return disabledByHunter
|
||||
|| SkDA.value (instance) <= maximum (cons (flattenDependencies (staticData)
|
||||
(state)
|
||||
(SkDA.dependencies (instance)))
|
||||
(6))
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
import * as React from "react"
|
||||
import { List, map, notNull, toArray } from "../../../Data/List"
|
||||
import { bindF, ensure, Just, Maybe, maybe, Nothing } from "../../../Data/Maybe"
|
||||
import { Record } from "../../../Data/Record"
|
||||
import { WikiInfoContainer } from "../../Containers/WikiInfoContainer"
|
||||
import { CombatTechniquesSortOptions } from "../../Models/Config"
|
||||
import { HeroModelRecord } from "../../Models/Hero/HeroModel"
|
||||
import { AttributeCombined } from "../../Models/View/AttributeCombined"
|
||||
import { CombatTechniqueWithRequirements, CombatTechniqueWithRequirementsA_ } from "../../Models/View/CombatTechniqueWithRequirements"
|
||||
import { StaticDataRecord } from "../../Models/Wiki/WikiModel"
|
||||
import { translate } from "../../Utilities/I18n"
|
||||
import { pipe, pipe_ } from "../../Utilities/pipe"
|
||||
import { ListView } from "../Universal/List"
|
||||
import { ListHeader } from "../Universal/ListHeader"
|
||||
import { ListHeaderTag } from "../Universal/ListHeaderTag"
|
||||
import { ListPlaceholder } from "../Universal/ListPlaceholder"
|
||||
import { MainContent } from "../Universal/MainContent"
|
||||
import { Options } from "../Universal/Options"
|
||||
import { Page } from "../Universal/Page"
|
||||
import { Scroll } from "../Universal/Scroll"
|
||||
import { SearchField } from "../Universal/SearchField"
|
||||
import { SortNames, SortOptions } from "../Universal/SortOptions"
|
||||
import { CombatTechniqueListItem } from "./CombatTechniquesListItem"
|
||||
|
||||
export interface CombatTechniquesOwnProps {
|
||||
staticData: StaticDataRecord
|
||||
hero: HeroModelRecord
|
||||
}
|
||||
|
||||
export interface CombatTechniquesStateProps {
|
||||
attributes: List<Record<AttributeCombined>>
|
||||
list: Maybe<List<Record<CombatTechniqueWithRequirements>>>
|
||||
isRemovingEnabled: boolean
|
||||
sortOrder: CombatTechniquesSortOptions
|
||||
filterText: string
|
||||
}
|
||||
|
||||
export interface CombatTechniquesDispatchProps {
|
||||
setSortOrder (sortOrder: SortNames): void
|
||||
addPoint (id: string): void
|
||||
removePoint (id: string): void
|
||||
setFilterText (filterText: string): void
|
||||
}
|
||||
|
||||
export type CombatTechniquesProps =
|
||||
CombatTechniquesStateProps
|
||||
& CombatTechniquesDispatchProps
|
||||
& CombatTechniquesOwnProps
|
||||
|
||||
export interface CombatTechniquesState {
|
||||
infoId: Maybe<string>
|
||||
}
|
||||
|
||||
const CTWRA_ = CombatTechniqueWithRequirementsA_
|
||||
|
||||
export const CombatTechniques: React.FC<CombatTechniquesProps> = props => {
|
||||
const {
|
||||
addPoint,
|
||||
attributes,
|
||||
list,
|
||||
staticData,
|
||||
isRemovingEnabled,
|
||||
removePoint,
|
||||
setSortOrder,
|
||||
sortOrder,
|
||||
filterText,
|
||||
setFilterText,
|
||||
} = props
|
||||
|
||||
const [ infoId, setInfoId ] = React.useState<Maybe<string>> (Nothing)
|
||||
|
||||
const showInfo = React.useCallback (
|
||||
(id: string) => setInfoId (Just (id)),
|
||||
[ setInfoId ]
|
||||
)
|
||||
|
||||
return (
|
||||
<Page id="combattechniques">
|
||||
<Options>
|
||||
<SearchField
|
||||
staticData={staticData}
|
||||
value={filterText}
|
||||
onChange={setFilterText}
|
||||
fullWidth
|
||||
/>
|
||||
<SortOptions
|
||||
sortOrder={sortOrder}
|
||||
sort={setSortOrder}
|
||||
staticData={staticData}
|
||||
options={List (SortNames.Name, SortNames.Group, SortNames.IC)}
|
||||
/>
|
||||
</Options>
|
||||
<MainContent>
|
||||
<ListHeader>
|
||||
<ListHeaderTag className="name">
|
||||
{translate (staticData) ("combattechniques.header.name")}
|
||||
</ListHeaderTag>
|
||||
<ListHeaderTag className="group">
|
||||
{translate (staticData) ("combattechniques.header.group")}
|
||||
</ListHeaderTag>
|
||||
<ListHeaderTag
|
||||
className="value"
|
||||
hint={translate (staticData) ("combattechniques.header.combattechniquerating.tooltip")}
|
||||
>
|
||||
{translate (staticData) ("combattechniques.header.combattechniquerating")}
|
||||
</ListHeaderTag>
|
||||
<ListHeaderTag
|
||||
className="ic"
|
||||
hint={translate (staticData) ("combattechniques.header.improvementcost.tooltip")}
|
||||
>
|
||||
{translate (staticData) ("combattechniques.header.improvementcost")}
|
||||
</ListHeaderTag>
|
||||
<ListHeaderTag
|
||||
className="primary"
|
||||
hint={translate (staticData) ("combattechniques.header.primaryattribute.tooltip")}
|
||||
>
|
||||
{translate (staticData) ("combattechniques.header.primaryattribute")}
|
||||
</ListHeaderTag>
|
||||
<ListHeaderTag
|
||||
className="at"
|
||||
hint={translate (staticData) ("combattechniques.header.attack.tooltip")}
|
||||
>
|
||||
{translate (staticData) ("combattechniques.header.attack")}
|
||||
</ListHeaderTag>
|
||||
<ListHeaderTag
|
||||
className="pa"
|
||||
hint={translate (staticData) ("combattechniques.header.parry.tooltip")}
|
||||
>
|
||||
{translate (staticData) ("combattechniques.header.parry")}
|
||||
</ListHeaderTag>
|
||||
{isRemovingEnabled ? <ListHeaderTag className="btn-placeholder" /> : null}
|
||||
<ListHeaderTag className="btn-placeholder" />
|
||||
<ListHeaderTag className="btn-placeholder" />
|
||||
</ListHeader>
|
||||
<Scroll>
|
||||
<ListView>
|
||||
{pipe_ (
|
||||
list,
|
||||
bindF (ensure (notNull)),
|
||||
maybe<JSX.Element | JSX.Element[]>
|
||||
(<ListPlaceholder staticData={staticData} type="combatTechniques" noResults />)
|
||||
(pipe (
|
||||
map (
|
||||
(x: Record<CombatTechniqueWithRequirements>) => (
|
||||
<CombatTechniqueListItem
|
||||
key={CTWRA_.id (x)}
|
||||
attributes={attributes}
|
||||
combatTechnique={x}
|
||||
currentInfoId={infoId}
|
||||
selectForInfo={showInfo}
|
||||
staticData={staticData}
|
||||
addPoint={addPoint}
|
||||
removePoint={removePoint}
|
||||
isRemovingEnabled={isRemovingEnabled}
|
||||
/>
|
||||
)
|
||||
),
|
||||
toArray
|
||||
))
|
||||
)}
|
||||
</ListView>
|
||||
</Scroll>
|
||||
</MainContent>
|
||||
<WikiInfoContainer currentId={infoId} />
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
import * as React from "react"
|
||||
import { equals } from "../../../Data/Eq"
|
||||
import { fmapF } from "../../../Data/Functor"
|
||||
import { find, flength, intercalate, List } from "../../../Data/List"
|
||||
import { fromMaybe, listToMaybe, mapMaybe, Maybe, maybe } from "../../../Data/Maybe"
|
||||
import { lookupF } from "../../../Data/OrderedMap"
|
||||
import { Record } from "../../../Data/Record"
|
||||
import { NumIdName } from "../../Models/NumIdName"
|
||||
import { AttributeCombined, AttributeCombinedA_ } from "../../Models/View/AttributeCombined"
|
||||
import { CombatTechniqueWithRequirements, CombatTechniqueWithRequirementsA_ } from "../../Models/View/CombatTechniqueWithRequirements"
|
||||
import { StaticData, StaticDataRecord } from "../../Models/Wiki/WikiModel"
|
||||
import { ndash } from "../../Utilities/Chars"
|
||||
import { pipe, pipe_ } from "../../Utilities/pipe"
|
||||
import { SkillListItem } from "../Skills/SkillListItem"
|
||||
|
||||
const ACA_ = AttributeCombinedA_
|
||||
const CTWRA = CombatTechniqueWithRequirements.A
|
||||
const CTWRA_ = CombatTechniqueWithRequirementsA_
|
||||
|
||||
export interface CombatTechniqueListItemProps {
|
||||
staticData: StaticDataRecord
|
||||
attributes: List<Record<AttributeCombined>>
|
||||
combatTechnique: Record<CombatTechniqueWithRequirements>
|
||||
currentInfoId: Maybe<string>
|
||||
isRemovingEnabled: boolean
|
||||
addPoint: (id: string) => void
|
||||
removePoint: (id: string) => void
|
||||
selectForInfo: (id: string) => void
|
||||
}
|
||||
|
||||
export const CombatTechniqueListItem: React.FC<CombatTechniqueListItemProps> = props => {
|
||||
const {
|
||||
attributes,
|
||||
currentInfoId,
|
||||
isRemovingEnabled,
|
||||
staticData,
|
||||
combatTechnique: ct,
|
||||
selectForInfo,
|
||||
addPoint,
|
||||
removePoint,
|
||||
} = props
|
||||
|
||||
const primary =
|
||||
pipe_ (
|
||||
CTWRA_.primary (ct),
|
||||
mapMaybe ((id: string) => fmapF (find (pipe (ACA_.id, equals (id)))
|
||||
(attributes))
|
||||
(ACA_.short)),
|
||||
intercalate ("/")
|
||||
)
|
||||
|
||||
const customClassName =
|
||||
flength (CTWRA_.primary (ct)) > 1
|
||||
? "ATTR_6_8"
|
||||
: fromMaybe ("") (listToMaybe (CTWRA_.primary (ct)))
|
||||
|
||||
const primaryClassName = `primary ${customClassName}`
|
||||
|
||||
return (
|
||||
<SkillListItem
|
||||
id={CTWRA_.id (ct)}
|
||||
name={CTWRA_.name (ct)}
|
||||
sr={CTWRA_.value (ct)}
|
||||
ic={CTWRA_.ic (ct)}
|
||||
checkDisabled
|
||||
addPoint={addPoint}
|
||||
addDisabled={!CTWRA.isIncreasable (ct)}
|
||||
removePoint={removePoint}
|
||||
removeDisabled={!isRemovingEnabled || !CTWRA.isDecreasable (ct)}
|
||||
addValues={List (
|
||||
{ className: primaryClassName, value: primary },
|
||||
{ className: "at", value: CTWRA.at (ct) },
|
||||
{ className: "atpa" },
|
||||
{
|
||||
className: "pa",
|
||||
value: fromMaybe<string | number> (ndash) (CTWRA.pa (ct)),
|
||||
}
|
||||
)}
|
||||
attributes={attributes}
|
||||
staticData={staticData}
|
||||
isRemovingEnabled={isRemovingEnabled}
|
||||
selectForInfo={selectForInfo}
|
||||
group={CTWRA_.gr (ct)}
|
||||
getGroupName={
|
||||
pipe (
|
||||
lookupF (StaticData.A.combatTechniqueGroups (staticData)),
|
||||
maybe (ndash) (NumIdName.A.name)
|
||||
)
|
||||
}
|
||||
selectedForInfo={currentInfoId}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import { LastChanges } from "./about/LastChanges.tsx"
|
||||
import { ThirdPartyLicenses } from "./about/ThirdPartyLicenses.tsx"
|
||||
import { Characters } from "./characters/Characters.tsx"
|
||||
import { Attributes } from "./characters/character/attributes/Attributes.tsx"
|
||||
import { CombatTechniques } from "./characters/character/combatTechniques/CombatTechniques.tsx"
|
||||
import { ProfileOverview } from "./characters/character/profile/ProfileOverview.tsx"
|
||||
import { Rules } from "./characters/character/rules/Rules.tsx"
|
||||
import { Skills } from "./characters/character/skills/Skills.tsx"
|
||||
@@ -34,7 +35,7 @@ export const Router: FC = () => {
|
||||
case "disadvantages": return null
|
||||
|
||||
case "skills": return <Skills />
|
||||
case "combat_techniques": return null
|
||||
case "combat_techniques": return <CombatTechniques />
|
||||
case "special_abilities": return null
|
||||
case "spells": return null
|
||||
case "liturgical_chants": return null
|
||||
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
// import * as React from "react"
|
||||
// import { equals } from "../../../Data/Eq"
|
||||
// import { fmapF } from "../../../Data/Functor"
|
||||
// import { find, flength, intercalate, List } from "../../../Data/List"
|
||||
// import { fromMaybe, listToMaybe, mapMaybe, Maybe, maybe } from "../../../Data/Maybe"
|
||||
// import { lookupF } from "../../../Data/OrderedMap"
|
||||
// import { Record } from "../../../Data/Record"
|
||||
// import { NumIdName } from "../../Models/NumIdName"
|
||||
// import { AttributeCombined, AttributeCombinedA_ } from "../../Models/View/AttributeCombined"
|
||||
// import { CombatTechniqueWithRequirements, CombatTechniqueWithRequirementsA_ } from "../../Models/View/CombatTechniqueWithRequirements"
|
||||
// import { StaticData, StaticDataRecord } from "../../Models/Wiki/WikiModel"
|
||||
// import { ndash } from "../../Utilities/Chars"
|
||||
// import { pipe, pipe_ } from "../../Utilities/pipe"
|
||||
// import { SkillListItem } from "../Skills/SkillListItem"
|
||||
|
||||
import { AttributeReference } from "optolith-database-schema/types/_SimpleReferences"
|
||||
import { FC, memo, useCallback } from "react"
|
||||
import { ListItem } from "../../../../../shared/components/list/ListItem.tsx"
|
||||
import { ListItemGroup } from "../../../../../shared/components/list/ListItemGroup.tsx"
|
||||
import { ListItemName } from "../../../../../shared/components/list/ListItemName.tsx"
|
||||
import { ListItemSeparator } from "../../../../../shared/components/list/ListItemSeparator.tsx"
|
||||
import { ListItemValues } from "../../../../../shared/components/list/ListItemValues.tsx"
|
||||
import { ImprovementCost } from "../../../../../shared/domain/adventurePoints/improvementCost.ts"
|
||||
import { useTranslate } from "../../../../../shared/hooks/translate.ts"
|
||||
import { useTranslateMap } from "../../../../../shared/hooks/translateMap.ts"
|
||||
import { useAppDispatch, useAppSelector } from "../../../../hooks/redux.ts"
|
||||
import { selectCanRemove } from "../../../../selectors/characterSelectors.ts"
|
||||
import { selectAttributes } from "../../../../slices/databaseSlice.ts"
|
||||
import {
|
||||
changeInlineLibraryEntry,
|
||||
selectInlineLibraryEntryId,
|
||||
} from "../../../../slices/inlineWikiSlice.ts"
|
||||
import { SkillAdditionalValues } from "../skills/SkillAdditionalValues.tsx"
|
||||
import { SkillButtons } from "../skills/SkillButtons.tsx"
|
||||
import { SkillImprovementCost } from "../skills/SkillImprovementCost.tsx"
|
||||
import { SkillRating } from "../skills/SkillRating.tsx"
|
||||
|
||||
type Props = {
|
||||
insertTopMargin?: boolean
|
||||
id: number
|
||||
name: string
|
||||
sr: number
|
||||
primary: AttributeReference[]
|
||||
ic: ImprovementCost
|
||||
addDisabled: boolean
|
||||
removeDisabled: boolean
|
||||
at: number
|
||||
pa?: number
|
||||
addPoint: (id: number) => void
|
||||
removePoint: (id: number) => void
|
||||
}
|
||||
|
||||
const CloseCombatTechniquesListItem: FC<Props> = props => {
|
||||
const {
|
||||
insertTopMargin,
|
||||
id,
|
||||
name,
|
||||
sr,
|
||||
primary,
|
||||
ic,
|
||||
addDisabled,
|
||||
removeDisabled,
|
||||
at,
|
||||
pa,
|
||||
addPoint,
|
||||
removePoint,
|
||||
} = props
|
||||
|
||||
const translate = useTranslate()
|
||||
const translateMap = useTranslateMap()
|
||||
const dispatch = useAppDispatch()
|
||||
const inlineLibraryEntryId = useAppSelector(selectInlineLibraryEntryId)
|
||||
const canRemove = useAppSelector(selectCanRemove)
|
||||
|
||||
const handleSelectForInfo = useCallback(
|
||||
() =>
|
||||
dispatch(
|
||||
changeInlineLibraryEntry({ tag: "CloseCombatTechnique", close_combat_technique: id }),
|
||||
),
|
||||
[dispatch, id],
|
||||
)
|
||||
|
||||
const attributes = useAppSelector(selectAttributes)
|
||||
|
||||
const primaryStr = primary
|
||||
.map(ref => translateMap(attributes[ref.id.attribute]?.translations)?.abbreviation ?? "")
|
||||
.join("/")
|
||||
|
||||
const customClassName = `attr--${primary.map(ref => ref.id.attribute).join("-")}`
|
||||
|
||||
const primaryClassName = `primary ${customClassName}`
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
insertTopMargin={insertTopMargin}
|
||||
active={
|
||||
inlineLibraryEntryId?.tag === "CloseCombatTechnique" &&
|
||||
inlineLibraryEntryId.close_combat_technique === id
|
||||
}
|
||||
>
|
||||
<ListItemName name={name} onClick={handleSelectForInfo} />
|
||||
<ListItemSeparator />
|
||||
<ListItemGroup text={translate("Close Combat")} />
|
||||
<ListItemValues>
|
||||
<SkillRating sr={sr} addPoint={addPoint} />
|
||||
<SkillImprovementCost ic={ic} />
|
||||
<SkillAdditionalValues
|
||||
addValues={[
|
||||
{ className: primaryClassName, value: primaryStr },
|
||||
{ className: "at", value: at },
|
||||
{ className: "atpa" },
|
||||
{
|
||||
className: "pa",
|
||||
value: pa ?? "—",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</ListItemValues>
|
||||
<SkillButtons
|
||||
addDisabled={addDisabled}
|
||||
ic={ic}
|
||||
id={id}
|
||||
removeDisabled={removeDisabled}
|
||||
sr={sr}
|
||||
addPoint={addPoint}
|
||||
removePoint={canRemove ? removePoint : undefined}
|
||||
selectForInfo={handleSelectForInfo}
|
||||
/>
|
||||
</ListItem>
|
||||
)
|
||||
}
|
||||
|
||||
const MemoCloseCombatTechniquesListItem = memo(CloseCombatTechniquesListItem)
|
||||
|
||||
export { MemoCloseCombatTechniquesListItem as CloseCombatTechniquesListItem }
|
||||
@@ -0,0 +1,256 @@
|
||||
// import * as React from "react"
|
||||
// import { List, map, notNull, toArray } from "../../../../../Data/List.ts"
|
||||
// import { bindF, ensure, Just, Maybe, maybe, Nothing } from "../../../../../Data/Maybe.ts"
|
||||
// import { Record } from "../../../../../Data/Record.ts"
|
||||
// import { WikiInfoContainer } from "../../../../../App/Containers/WikiInfoContainer.ts"
|
||||
// import { CombatTechniquesSortOptions } from "../../../../../App/Models/Config.ts"
|
||||
// import { HeroModelRecord } from "../../../../../App/Models/Hero/HeroModel.ts"
|
||||
// import { AttributeCombined } from "../../../../../App/Models/View/AttributeCombined.ts"
|
||||
// import { CombatTechniqueWithRequirements, CombatTechniqueWithRequirementsA_ } from "../../../../../App/Models/View/CombatTechniqueWithRequirements.ts"
|
||||
// import { StaticDataRecord } from "../../../../../App/Models/Wiki/WikiModel.ts"
|
||||
// import { translate } from "../../../../../App/Utilities/I18n.ts"
|
||||
// import { pipe, pipe_ } from "../../../../../App/Utilities/pipe.ts"
|
||||
// import { ListView } from "../Universal/List"
|
||||
// import { ListHeader } from "../Universal/ListHeader"
|
||||
// import { ListHeaderTag } from "../Universal/ListHeaderTag"
|
||||
// import { ListPlaceholder } from "../Universal/ListPlaceholder"
|
||||
// import { MainContent } from "../Universal/MainContent"
|
||||
// import { Options } from "../Universal/Options"
|
||||
// import { Page } from "../Universal/Page"
|
||||
// import { Scroll } from "../Universal/Scroll"
|
||||
// import { SearchField } from "../../../../../App/Views/Universal/SearchField.tsx"
|
||||
// import { SortNames, SortOptions } from "../../../../../App/Views/Universal/SortOptions.tsx"
|
||||
// import { CombatTechniqueListItem } from "./CombatTechniquesListItem"
|
||||
|
||||
import { FC, useCallback, useMemo, useState } from "react"
|
||||
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"
|
||||
import { Main } from "../../../../../shared/components/main/Main.tsx"
|
||||
import { Options } from "../../../../../shared/components/options/Options.tsx"
|
||||
import { Page } from "../../../../../shared/components/page/Page.tsx"
|
||||
import { RadioButtonGroup } from "../../../../../shared/components/radioButton/RadioButtonGroup.tsx"
|
||||
import { Scroll } from "../../../../../shared/components/scroll/Scroll.tsx"
|
||||
import { TextField } from "../../../../../shared/components/textField/TextField.tsx"
|
||||
import {
|
||||
compareImprovementCost,
|
||||
fromRaw,
|
||||
} from "../../../../../shared/domain/adventurePoints/improvementCost.ts"
|
||||
import { useLocaleCompare } from "../../../../../shared/hooks/localeCompare.ts"
|
||||
import { useTranslate } from "../../../../../shared/hooks/translate.ts"
|
||||
import { useTranslateMap } from "../../../../../shared/hooks/translateMap.ts"
|
||||
import { compareAt, numAsc, reduceCompare } from "../../../../../shared/utils/compare.ts"
|
||||
import { assertExhaustive } from "../../../../../shared/utils/typeSafety.ts"
|
||||
import { useAppDispatch, useAppSelector } from "../../../../hooks/redux.ts"
|
||||
import { InlineLibrary } from "../../../../inlineLibrary/InlineLibrary.tsx"
|
||||
import { selectCanRemove } from "../../../../selectors/characterSelectors.ts"
|
||||
import {
|
||||
DisplayedCombatTechnique,
|
||||
selectVisibleCombatTechniques,
|
||||
} from "../../../../selectors/combatTechniquesSelectors.ts"
|
||||
import {
|
||||
decrementCloseCombatTechnique,
|
||||
incrementCloseCombatTechnique,
|
||||
} from "../../../../slices/closeCombatTechniqueSlice.ts"
|
||||
import {
|
||||
decrementRangedCombatTechnique,
|
||||
incrementRangedCombatTechnique,
|
||||
} from "../../../../slices/rangedCombatTechniqueSlice.ts"
|
||||
import {
|
||||
CombatTechniquesSortOrder,
|
||||
changeCombatTechniquesSortOrder,
|
||||
selectCombatTechniquesSortOrder,
|
||||
} from "../../../../slices/settingsSlice.ts"
|
||||
import { CloseCombatTechniquesListItem } from "./CloseCombatTechniquesListItem.tsx"
|
||||
import { RangedCombatTechniquesListItem } from "./RangedCombatTechniquesListItem.tsx"
|
||||
|
||||
const isTopMarginNeeded = (
|
||||
sortOrder: CombatTechniquesSortOrder,
|
||||
curr: DisplayedCombatTechnique,
|
||||
mprev: DisplayedCombatTechnique | undefined,
|
||||
) => sortOrder === "group" && mprev !== undefined && curr.kind !== mprev.kind
|
||||
|
||||
export const CombatTechniques: FC = () => {
|
||||
const translate = useTranslate()
|
||||
const translateMap = useTranslateMap()
|
||||
const dispatch = useAppDispatch()
|
||||
const localeCompare = useLocaleCompare()
|
||||
|
||||
const canRemove = useAppSelector(selectCanRemove)
|
||||
|
||||
const [filterText, setFilterText] = useState("")
|
||||
const sortOrder = useAppSelector(selectCombatTechniquesSortOrder)
|
||||
const handleChangeSortOrder = useCallback(
|
||||
(id: CombatTechniquesSortOrder) => dispatch(changeCombatTechniquesSortOrder(id)),
|
||||
[dispatch],
|
||||
)
|
||||
const visibleCombatTechniques = useAppSelector(selectVisibleCombatTechniques)
|
||||
const list = useMemo(
|
||||
() =>
|
||||
visibleCombatTechniques
|
||||
.filter(
|
||||
c =>
|
||||
translateMap(c.static.translations)
|
||||
?.name.toLowerCase()
|
||||
.includes(filterText.toLowerCase()) ?? false,
|
||||
)
|
||||
.sort(
|
||||
(() => {
|
||||
switch (sortOrder) {
|
||||
case CombatTechniquesSortOrder.Name:
|
||||
return compareAt(
|
||||
c => translateMap(c.static.translations)?.name ?? "",
|
||||
localeCompare,
|
||||
)
|
||||
case CombatTechniquesSortOrder.Group:
|
||||
return reduceCompare(
|
||||
compareAt(c => (c.kind === "close" ? 1 : 2), numAsc),
|
||||
compareAt(c => translateMap(c.static.translations)?.name ?? "", localeCompare),
|
||||
)
|
||||
case CombatTechniquesSortOrder.ImprovementCost:
|
||||
return reduceCompare(
|
||||
compareAt(c => fromRaw(c.static.improvement_cost), compareImprovementCost),
|
||||
compareAt(c => translateMap(c.static.translations)?.name ?? "", localeCompare),
|
||||
)
|
||||
default:
|
||||
return assertExhaustive(sortOrder)
|
||||
}
|
||||
})(),
|
||||
),
|
||||
[filterText, localeCompare, sortOrder, translateMap, visibleCombatTechniques],
|
||||
)
|
||||
|
||||
const handleAddCloseCombatTechniquePoint = useCallback(
|
||||
(id: number) => dispatch(incrementCloseCombatTechnique(id)),
|
||||
[dispatch],
|
||||
)
|
||||
|
||||
const handleRemoveCloseCombatTechniquePoint = useCallback(
|
||||
(id: number) => dispatch(decrementCloseCombatTechnique(id)),
|
||||
[dispatch],
|
||||
)
|
||||
|
||||
const handleAddRangedCombatTechniquePoint = useCallback(
|
||||
(id: number) => dispatch(incrementRangedCombatTechnique(id)),
|
||||
[dispatch],
|
||||
)
|
||||
|
||||
const handleRemoveRangedCombatTechniquePoint = useCallback(
|
||||
(id: number) => dispatch(decrementRangedCombatTechnique(id)),
|
||||
[dispatch],
|
||||
)
|
||||
|
||||
return (
|
||||
<Page id="combat-techniques">
|
||||
<Options>
|
||||
<TextField value={filterText} onChange={setFilterText} hint={translate("Search")} />
|
||||
<RadioButtonGroup
|
||||
active={sortOrder}
|
||||
label={translate("Sort By")}
|
||||
array={[
|
||||
{
|
||||
name: translate("Name"),
|
||||
value: CombatTechniquesSortOrder.Name,
|
||||
},
|
||||
{
|
||||
name: translate("Group"),
|
||||
value: CombatTechniquesSortOrder.Group,
|
||||
},
|
||||
{
|
||||
name: translate("Improvement Cost"),
|
||||
value: CombatTechniquesSortOrder.ImprovementCost,
|
||||
},
|
||||
]}
|
||||
onClick={handleChangeSortOrder}
|
||||
/>
|
||||
</Options>
|
||||
<Main>
|
||||
<ListHeader>
|
||||
<ListHeaderTag className="name">
|
||||
{translate("combattechniques.header.name")}
|
||||
</ListHeaderTag>
|
||||
<ListHeaderTag className="group">
|
||||
{translate("combattechniques.header.group")}
|
||||
</ListHeaderTag>
|
||||
<ListHeaderTag
|
||||
className="value"
|
||||
hint={translate("combattechniques.header.combattechniquerating.tooltip")}
|
||||
>
|
||||
{translate("combattechniques.header.combattechniquerating")}
|
||||
</ListHeaderTag>
|
||||
<ListHeaderTag
|
||||
className="ic"
|
||||
hint={translate("combattechniques.header.improvementcost.tooltip")}
|
||||
>
|
||||
{translate("combattechniques.header.improvementcost")}
|
||||
</ListHeaderTag>
|
||||
<ListHeaderTag
|
||||
className="primary"
|
||||
hint={translate("combattechniques.header.primaryattribute.tooltip")}
|
||||
>
|
||||
{translate("combattechniques.header.primaryattribute")}
|
||||
</ListHeaderTag>
|
||||
<ListHeaderTag className="at" hint={translate("combattechniques.header.attack.tooltip")}>
|
||||
{translate("combattechniques.header.attack")}
|
||||
</ListHeaderTag>
|
||||
<ListHeaderTag className="pa" hint={translate("combattechniques.header.parry.tooltip")}>
|
||||
{translate("combattechniques.header.parry")}
|
||||
</ListHeaderTag>
|
||||
{canRemove ? <ListHeaderTag className="btn-placeholder" /> : null}
|
||||
<ListHeaderTag className="btn-placeholder" />
|
||||
<ListHeaderTag className="btn-placeholder" />
|
||||
</ListHeader>
|
||||
<Scroll>
|
||||
{list.length > 0 ? (
|
||||
<List>
|
||||
{list.map((x, i) => {
|
||||
const translation = translateMap(x.static.translations)
|
||||
|
||||
if (x.kind === "close") {
|
||||
return (
|
||||
<CloseCombatTechniquesListItem
|
||||
key={`close--${x.static.id}`}
|
||||
insertTopMargin={isTopMarginNeeded(sortOrder, x, list[i - 1])}
|
||||
id={x.static.id}
|
||||
name={translation?.name ?? ""}
|
||||
sr={x.dynamic.value}
|
||||
primary={x.static.primary_attribute}
|
||||
ic={fromRaw(x.static.improvement_cost)}
|
||||
addDisabled={!x.isIncreasable}
|
||||
removeDisabled={!x.isDecreasable}
|
||||
at={x.attackBase}
|
||||
pa={x.parryBase}
|
||||
addPoint={handleAddCloseCombatTechniquePoint}
|
||||
removePoint={handleRemoveCloseCombatTechniquePoint}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<RangedCombatTechniquesListItem
|
||||
key={`ranged--${x.static.id}`}
|
||||
insertTopMargin={isTopMarginNeeded(sortOrder, x, list[i - 1])}
|
||||
id={x.static.id}
|
||||
name={translation?.name ?? ""}
|
||||
sr={x.dynamic.value}
|
||||
primary={x.static.primary_attribute}
|
||||
ic={fromRaw(x.static.improvement_cost)}
|
||||
addDisabled={!x.isIncreasable}
|
||||
removeDisabled={!x.isDecreasable}
|
||||
at={x.attackBase}
|
||||
addPoint={handleAddRangedCombatTechniquePoint}
|
||||
removePoint={handleRemoveRangedCombatTechniquePoint}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</List>
|
||||
) : (
|
||||
<ListPlaceholder type="combatTechniques" message={translate("No Results")} />
|
||||
)}
|
||||
</Scroll>
|
||||
</Main>
|
||||
<InlineLibrary />
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
// import * as React from "react"
|
||||
// import { equals } from "../../../Data/Eq"
|
||||
// import { fmapF } from "../../../Data/Functor"
|
||||
// import { find, flength, intercalate, List } from "../../../Data/List"
|
||||
// import { fromMaybe, listToMaybe, mapMaybe, Maybe, maybe } from "../../../Data/Maybe"
|
||||
// import { lookupF } from "../../../Data/OrderedMap"
|
||||
// import { Record } from "../../../Data/Record"
|
||||
// import { NumIdName } from "../../Models/NumIdName"
|
||||
// import { AttributeCombined, AttributeCombinedA_ } from "../../Models/View/AttributeCombined"
|
||||
// import { CombatTechniqueWithRequirements, CombatTechniqueWithRequirementsA_ } from "../../Models/View/CombatTechniqueWithRequirements"
|
||||
// import { StaticData, StaticDataRecord } from "../../Models/Wiki/WikiModel"
|
||||
// import { ndash } from "../../Utilities/Chars"
|
||||
// import { pipe, pipe_ } from "../../Utilities/pipe"
|
||||
// import { SkillListItem } from "../Skills/SkillListItem"
|
||||
|
||||
import { AttributeReference } from "optolith-database-schema/types/_SimpleReferences"
|
||||
import { FC, memo, useCallback } from "react"
|
||||
import { ListItem } from "../../../../../shared/components/list/ListItem.tsx"
|
||||
import { ListItemGroup } from "../../../../../shared/components/list/ListItemGroup.tsx"
|
||||
import { ListItemName } from "../../../../../shared/components/list/ListItemName.tsx"
|
||||
import { ListItemSeparator } from "../../../../../shared/components/list/ListItemSeparator.tsx"
|
||||
import { ListItemValues } from "../../../../../shared/components/list/ListItemValues.tsx"
|
||||
import { ImprovementCost } from "../../../../../shared/domain/adventurePoints/improvementCost.ts"
|
||||
import { useTranslate } from "../../../../../shared/hooks/translate.ts"
|
||||
import { useTranslateMap } from "../../../../../shared/hooks/translateMap.ts"
|
||||
import { useAppDispatch, useAppSelector } from "../../../../hooks/redux.ts"
|
||||
import { selectCanRemove } from "../../../../selectors/characterSelectors.ts"
|
||||
import { selectAttributes } from "../../../../slices/databaseSlice.ts"
|
||||
import { changeInlineLibraryEntry, selectInlineLibraryEntryId } from "../../../../slices/inlineWikiSlice.ts"
|
||||
import { SkillAdditionalValues } from "../skills/SkillAdditionalValues.tsx"
|
||||
import { SkillButtons } from "../skills/SkillButtons.tsx"
|
||||
import { SkillImprovementCost } from "../skills/SkillImprovementCost.tsx"
|
||||
import { SkillRating } from "../skills/SkillRating.tsx"
|
||||
|
||||
type Props = {
|
||||
insertTopMargin?: boolean
|
||||
id: number
|
||||
name: string
|
||||
sr: number
|
||||
primary: AttributeReference[]
|
||||
ic: ImprovementCost
|
||||
addDisabled: boolean
|
||||
removeDisabled: boolean
|
||||
at: number
|
||||
addPoint: (id: number) => void
|
||||
removePoint: (id: number) => void
|
||||
}
|
||||
|
||||
const RangedCombatTechniquesListItem: FC<Props> = props => {
|
||||
const {
|
||||
insertTopMargin,
|
||||
id,
|
||||
name,
|
||||
sr,
|
||||
primary,
|
||||
ic,
|
||||
addDisabled,
|
||||
removeDisabled,
|
||||
at,
|
||||
addPoint,
|
||||
removePoint,
|
||||
} = props
|
||||
|
||||
const translate = useTranslate()
|
||||
const translateMap = useTranslateMap()
|
||||
const dispatch = useAppDispatch()
|
||||
const inlineLibraryEntryId = useAppSelector(selectInlineLibraryEntryId)
|
||||
const canRemove = useAppSelector(selectCanRemove)
|
||||
|
||||
const handleSelectForInfo =
|
||||
useCallback(
|
||||
() => dispatch(changeInlineLibraryEntry({ tag: "RangedCombatTechnique", ranged_combat_technique: id })),
|
||||
[ dispatch, id ]
|
||||
)
|
||||
|
||||
const attributes = useAppSelector(selectAttributes)
|
||||
|
||||
const primaryStr = primary
|
||||
.map(ref => translateMap(attributes[ref.id.attribute]?.translations)?.abbreviation ?? "")
|
||||
.join("/")
|
||||
|
||||
const customClassName = `attr--${primary.map(ref => ref.id.attribute).join("-")}`
|
||||
|
||||
const primaryClassName = `primary ${customClassName}`
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
insertTopMargin={insertTopMargin}
|
||||
active={inlineLibraryEntryId?.tag === "RangedCombatTechnique" && inlineLibraryEntryId.ranged_combat_technique === id}
|
||||
>
|
||||
<ListItemName name={name} onClick={handleSelectForInfo} />
|
||||
<ListItemSeparator />
|
||||
<ListItemGroup text={translate("Ranged Combat")} />
|
||||
<ListItemValues>
|
||||
<SkillRating
|
||||
sr={sr}
|
||||
addPoint={addPoint}
|
||||
/>
|
||||
<SkillImprovementCost ic={ic} />
|
||||
<SkillAdditionalValues
|
||||
addValues={[
|
||||
{ className: primaryClassName, value: primaryStr },
|
||||
{ className: "at", value: at },
|
||||
{ className: "atpa" },
|
||||
{
|
||||
className: "pa",
|
||||
value: "—",
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</ListItemValues>
|
||||
<SkillButtons
|
||||
addDisabled={addDisabled}
|
||||
ic={ic}
|
||||
id={id}
|
||||
removeDisabled={removeDisabled}
|
||||
sr={sr}
|
||||
addPoint={addPoint}
|
||||
removePoint={canRemove ? removePoint : undefined}
|
||||
selectForInfo={handleSelectForInfo}
|
||||
/>
|
||||
</ListItem>
|
||||
)
|
||||
}
|
||||
|
||||
const MemoRangedCombatTechniquesListItem = memo(RangedCombatTechniquesListItem)
|
||||
|
||||
export { MemoRangedCombatTechniquesListItem as RangedCombatTechniquesListItem }
|
||||
@@ -1,4 +1,7 @@
|
||||
import { SkillCheckPenalty, SkillCheck as SkillCheckType } from "optolith-database-schema/types/_SkillCheck"
|
||||
import {
|
||||
SkillCheckPenalty,
|
||||
SkillCheck as SkillCheckType,
|
||||
} from "optolith-database-schema/types/_SkillCheck"
|
||||
import { memo, useCallback } from "react"
|
||||
import { ListItem } from "../../../../../shared/components/list/ListItem.tsx"
|
||||
import { ListItemName } from "../../../../../shared/components/list/ListItemName.tsx"
|
||||
@@ -7,7 +10,10 @@ import { ListItemValues } from "../../../../../shared/components/list/ListItemVa
|
||||
import { ImprovementCost } from "../../../../../shared/domain/adventurePoints/improvementCost.ts"
|
||||
import { useAppDispatch, useAppSelector } from "../../../../hooks/redux.ts"
|
||||
import { selectCanRemove } from "../../../../selectors/characterSelectors.ts"
|
||||
import { changeInlineLibraryEntry, selectInlineLibraryEntryId } from "../../../../slices/inlineWikiSlice.ts"
|
||||
import {
|
||||
changeInlineLibraryEntry,
|
||||
selectInlineLibraryEntryId,
|
||||
} from "../../../../slices/inlineWikiSlice.ts"
|
||||
import { AdditionalValue, SkillAdditionalValues } from "./SkillAdditionalValues.tsx"
|
||||
import { SkillButtons } from "./SkillButtons.tsx"
|
||||
import { SkillCheck } from "./SkillCheck.tsx"
|
||||
@@ -73,11 +79,10 @@ const SkillListItem: React.FC<Props> = props => {
|
||||
const inlineLibraryEntryId = useAppSelector(selectInlineLibraryEntryId)
|
||||
const canRemove = useAppSelector(selectCanRemove)
|
||||
|
||||
const handleSelectForInfo =
|
||||
useCallback(
|
||||
() => dispatch(changeInlineLibraryEntry({ tag: "Skill", skill: id })),
|
||||
[ dispatch, id ]
|
||||
)
|
||||
const handleSelectForInfo = useCallback(
|
||||
() => dispatch(changeInlineLibraryEntry({ tag: "Skill", skill: id })),
|
||||
[dispatch, id],
|
||||
)
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
@@ -86,38 +91,23 @@ const SkillListItem: React.FC<Props> = props => {
|
||||
unrecommended={untyp}
|
||||
insertTopMargin={insertTopMargin}
|
||||
active={inlineLibraryEntryId?.tag === "Skill" && inlineLibraryEntryId.skill === id}
|
||||
>
|
||||
>
|
||||
<ListItemName name={name} onClick={handleSelectForInfo} />
|
||||
<ListItemSeparator />
|
||||
<SkillGroup
|
||||
addText={addText}
|
||||
group={group}
|
||||
getGroupName={getGroupName}
|
||||
/>
|
||||
<SkillGroup addText={addText} group={group} getGroupName={getGroupName} />
|
||||
<ListItemValues>
|
||||
<SkillRating
|
||||
isNotActive={isNotActive}
|
||||
noIncrease={noIncrease}
|
||||
sr={sr}
|
||||
addPoint={addPoint}
|
||||
/>
|
||||
{checkDisabled !== true && check !== undefined
|
||||
? (
|
||||
<SkillCheck
|
||||
check={check}
|
||||
checkPenalty={checkmod}
|
||||
/>
|
||||
)
|
||||
: null}
|
||||
<SkillFill
|
||||
addFillElement={addFillElement}
|
||||
/>
|
||||
<SkillImprovementCost
|
||||
ic={ic}
|
||||
/>
|
||||
<SkillAdditionalValues
|
||||
addValues={addValues}
|
||||
/>
|
||||
/>
|
||||
{checkDisabled !== true && check !== undefined ? (
|
||||
<SkillCheck check={check} checkPenalty={checkmod} />
|
||||
) : null}
|
||||
<SkillFill addFillElement={addFillElement} />
|
||||
<SkillImprovementCost ic={ic} />
|
||||
<SkillAdditionalValues addValues={addValues} />
|
||||
</ListItemValues>
|
||||
<SkillButtons
|
||||
activateDisabled={activateDisabled}
|
||||
@@ -131,7 +121,7 @@ const SkillListItem: React.FC<Props> = props => {
|
||||
addPoint={addPoint}
|
||||
removePoint={canRemove ? removePoint : undefined}
|
||||
selectForInfo={handleSelectForInfo}
|
||||
/>
|
||||
/>
|
||||
</ListItem>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -12,7 +12,10 @@ import { RadioButtonGroup } from "../../../../../shared/components/radioButton/R
|
||||
import { RecommendedReference } from "../../../../../shared/components/recommendedReference/RecommendedReference.tsx"
|
||||
import { Scroll } from "../../../../../shared/components/scroll/Scroll.tsx"
|
||||
import { TextField } from "../../../../../shared/components/textField/TextField.tsx"
|
||||
import { compareImprovementCost, fromRaw } from "../../../../../shared/domain/adventurePoints/improvementCost.ts"
|
||||
import {
|
||||
compareImprovementCost,
|
||||
fromRaw,
|
||||
} from "../../../../../shared/domain/adventurePoints/improvementCost.ts"
|
||||
import { useLocaleCompare } from "../../../../../shared/hooks/localeCompare.ts"
|
||||
import { useTranslate } from "../../../../../shared/hooks/translate.ts"
|
||||
import { useTranslateMap } from "../../../../../shared/hooks/translateMap.ts"
|
||||
@@ -23,16 +26,25 @@ import { InlineLibrary } from "../../../../inlineLibrary/InlineLibrary.tsx"
|
||||
import { selectCanRemove } from "../../../../selectors/characterSelectors.ts"
|
||||
import { DisplayedSkill, selectVisibleSkills } from "../../../../selectors/skillsSelectors.ts"
|
||||
import { selectSkillGroups } from "../../../../slices/databaseSlice.ts"
|
||||
import { SkillsSortOrder, changeSkillsSortOrder, selectSkillsCultureRatingVisibility, selectSkillsSortOrder, switchSkillsCultureRatingVisibility } from "../../../../slices/settingsSlice.ts"
|
||||
import {
|
||||
SkillsSortOrder,
|
||||
changeSkillsSortOrder,
|
||||
selectSkillsCultureRatingVisibility,
|
||||
selectSkillsSortOrder,
|
||||
switchSkillsCultureRatingVisibility,
|
||||
} from "../../../../slices/settingsSlice.ts"
|
||||
import { decrementSkill, incrementSkill } from "../../../../slices/skillsSlice.ts"
|
||||
import { SkillListItem } from "./SkillListItem.tsx"
|
||||
import "./Skills.scss"
|
||||
|
||||
const isTopMarginNeeded =
|
||||
(sortOrder: SkillsSortOrder, curr: DisplayedSkill, mprev: DisplayedSkill | undefined) =>
|
||||
sortOrder === "group"
|
||||
&& mprev !== undefined
|
||||
&& curr.static.group.id.skill_group !== mprev.static.group.id.skill_group
|
||||
const isTopMarginNeeded = (
|
||||
sortOrder: SkillsSortOrder,
|
||||
curr: DisplayedSkill,
|
||||
mprev: DisplayedSkill | undefined,
|
||||
) =>
|
||||
sortOrder === "group" &&
|
||||
mprev !== undefined &&
|
||||
curr.static.group.id.skill_group !== mprev.static.group.id.skill_group
|
||||
|
||||
export const Skills: FC = () => {
|
||||
const translate = useTranslate()
|
||||
@@ -43,67 +55,67 @@ export const Skills: FC = () => {
|
||||
const cultureRatingVisibility = useAppSelector(selectSkillsCultureRatingVisibility)
|
||||
const handlSwitchCultureRatingVisibility = useCallback(
|
||||
() => dispatch(switchSkillsCultureRatingVisibility()),
|
||||
[ dispatch ]
|
||||
[dispatch],
|
||||
)
|
||||
|
||||
const canRemove = useAppSelector(selectCanRemove)
|
||||
const skillGroups = useAppSelector(selectSkillGroups)
|
||||
|
||||
const [ filterText, setFilterText ] = useState("")
|
||||
const [filterText, setFilterText] = useState("")
|
||||
const sortOrder = useAppSelector(selectSkillsSortOrder)
|
||||
const handleChangeSortOrder = useCallback(
|
||||
(id: SkillsSortOrder) => dispatch(changeSkillsSortOrder(id)),
|
||||
[ dispatch ]
|
||||
[dispatch],
|
||||
)
|
||||
const visibleSkills = useAppSelector(selectVisibleSkills)
|
||||
const list = useMemo(
|
||||
() => visibleSkills
|
||||
.filter(c => translateMap(c.static.translations)?.name.toLowerCase()
|
||||
.includes(filterText.toLowerCase()) ?? false)
|
||||
.sort((() => {
|
||||
switch (sortOrder) {
|
||||
case SkillsSortOrder.Name:
|
||||
return compareAt(c => translateMap(c.static.translations)?.name ?? "", localeCompare)
|
||||
case SkillsSortOrder.Group:
|
||||
return reduceCompare(
|
||||
compareAt(c => c.static.group.id.skill_group, numAsc),
|
||||
compareAt(c => translateMap(c.static.translations)?.name ?? "", localeCompare),
|
||||
)
|
||||
case SkillsSortOrder.ImprovementCost:
|
||||
return reduceCompare(
|
||||
compareAt(c => fromRaw(c.static.improvement_cost), compareImprovementCost),
|
||||
compareAt(c => translateMap(c.static.translations)?.name ?? "", localeCompare),
|
||||
)
|
||||
default:
|
||||
return assertExhaustive(sortOrder)
|
||||
}
|
||||
})()),
|
||||
[ filterText, localeCompare, sortOrder, translateMap, visibleSkills ]
|
||||
() =>
|
||||
visibleSkills
|
||||
.filter(
|
||||
c =>
|
||||
translateMap(c.static.translations)
|
||||
?.name.toLowerCase()
|
||||
.includes(filterText.toLowerCase()) ?? false,
|
||||
)
|
||||
.sort(
|
||||
(() => {
|
||||
switch (sortOrder) {
|
||||
case SkillsSortOrder.Name:
|
||||
return compareAt(
|
||||
c => translateMap(c.static.translations)?.name ?? "",
|
||||
localeCompare,
|
||||
)
|
||||
case SkillsSortOrder.Group:
|
||||
return reduceCompare(
|
||||
compareAt(c => c.static.group.id.skill_group, numAsc),
|
||||
compareAt(c => translateMap(c.static.translations)?.name ?? "", localeCompare),
|
||||
)
|
||||
case SkillsSortOrder.ImprovementCost:
|
||||
return reduceCompare(
|
||||
compareAt(c => fromRaw(c.static.improvement_cost), compareImprovementCost),
|
||||
compareAt(c => translateMap(c.static.translations)?.name ?? "", localeCompare),
|
||||
)
|
||||
default:
|
||||
return assertExhaustive(sortOrder)
|
||||
}
|
||||
})(),
|
||||
),
|
||||
[filterText, localeCompare, sortOrder, translateMap, visibleSkills],
|
||||
)
|
||||
|
||||
const handleAdd = useCallback(
|
||||
(id: number) => dispatch(incrementSkill(id)),
|
||||
[ dispatch ]
|
||||
)
|
||||
const handleAdd = useCallback((id: number) => dispatch(incrementSkill(id)), [dispatch])
|
||||
|
||||
const handleRemove = useCallback(
|
||||
(id: number) => dispatch(decrementSkill(id)),
|
||||
[ dispatch ]
|
||||
)
|
||||
const handleRemove = useCallback((id: number) => dispatch(decrementSkill(id)), [dispatch])
|
||||
|
||||
const getGroupName = useCallback(
|
||||
(id: number) => translateMap(skillGroups[id]?.translations)?.name ?? "",
|
||||
[ skillGroups, translateMap ]
|
||||
[skillGroups, translateMap],
|
||||
)
|
||||
|
||||
return (
|
||||
<Page id="skills">
|
||||
<Options>
|
||||
<TextField
|
||||
value={filterText}
|
||||
onChange={setFilterText}
|
||||
hint={translate("Search")}
|
||||
/>
|
||||
<TextField value={filterText} onChange={setFilterText} hint={translate("Search")} />
|
||||
<RadioButtonGroup
|
||||
active={sortOrder}
|
||||
label={translate("Sort By")}
|
||||
@@ -122,12 +134,9 @@ export const Skills: FC = () => {
|
||||
},
|
||||
]}
|
||||
onClick={handleChangeSortOrder}
|
||||
/>
|
||||
/>
|
||||
<Grid size="medium">
|
||||
<Checkbox
|
||||
checked={cultureRatingVisibility}
|
||||
onClick={handlSwitchCultureRatingVisibility}
|
||||
>
|
||||
<Checkbox checked={cultureRatingVisibility} onClick={handlSwitchCultureRatingVisibility}>
|
||||
{translate("skills.commonskills")}
|
||||
</Checkbox>
|
||||
{cultureRatingVisibility ? <RecommendedReference /> : null}
|
||||
@@ -135,25 +144,13 @@ export const Skills: FC = () => {
|
||||
</Options>
|
||||
<Main>
|
||||
<ListHeader>
|
||||
<ListHeaderTag className="name">
|
||||
{translate("skills.header.name")}
|
||||
</ListHeaderTag>
|
||||
<ListHeaderTag className="group">
|
||||
{translate("skills.header.group")}
|
||||
</ListHeaderTag>
|
||||
<ListHeaderTag
|
||||
className="value"
|
||||
hint={translate("skills.header.skillrating.tooltip")}
|
||||
>
|
||||
<ListHeaderTag className="name">{translate("skills.header.name")}</ListHeaderTag>
|
||||
<ListHeaderTag className="group">{translate("skills.header.group")}</ListHeaderTag>
|
||||
<ListHeaderTag className="value" hint={translate("skills.header.skillrating.tooltip")}>
|
||||
{translate("skills.header.skillrating")}
|
||||
</ListHeaderTag>
|
||||
<ListHeaderTag className="check">
|
||||
{translate("skills.header.check")}
|
||||
</ListHeaderTag>
|
||||
<ListHeaderTag
|
||||
className="ic"
|
||||
hint={translate("skills.header.improvementcost.tooltip")}
|
||||
>
|
||||
<ListHeaderTag className="check">{translate("skills.header.check")}</ListHeaderTag>
|
||||
<ListHeaderTag className="ic" hint={translate("skills.header.improvementcost.tooltip")}>
|
||||
{translate("skills.header.improvementcost")}
|
||||
</ListHeaderTag>
|
||||
{canRemove ? <ListHeaderTag className="btn-placeholder" /> : null}
|
||||
@@ -161,37 +158,35 @@ export const Skills: FC = () => {
|
||||
<ListHeaderTag className="btn-placeholder" />
|
||||
</ListHeader>
|
||||
<Scroll>
|
||||
{
|
||||
list.length > 0
|
||||
? (
|
||||
<List>
|
||||
{list.map((x, i) => {
|
||||
const translation = translateMap(x.static.translations)
|
||||
return (
|
||||
<SkillListItem
|
||||
key={x.static.id}
|
||||
id={x.static.id}
|
||||
typ={cultureRatingVisibility && x.commonness === "common"}
|
||||
untyp={cultureRatingVisibility && x.commonness === "uncommon"}
|
||||
name={translation?.name ?? ""}
|
||||
sr={x.dynamic.value}
|
||||
check={x.static.check}
|
||||
ic={fromRaw(x.static.improvement_cost)}
|
||||
addDisabled={!x.isIncreasable}
|
||||
addPoint={handleAdd}
|
||||
removeDisabled={!x.isDecreasable}
|
||||
removePoint={handleRemove}
|
||||
addFillElement
|
||||
insertTopMargin={isTopMarginNeeded(sortOrder, x, list[i - 1])}
|
||||
group={x.static.group.id.skill_group}
|
||||
getGroupName={getGroupName}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</List>
|
||||
)
|
||||
: <ListPlaceholder type="skills" message={translate("No Results")} />
|
||||
}
|
||||
{list.length > 0 ? (
|
||||
<List>
|
||||
{list.map((x, i) => {
|
||||
const translation = translateMap(x.static.translations)
|
||||
return (
|
||||
<SkillListItem
|
||||
key={x.static.id}
|
||||
id={x.static.id}
|
||||
typ={cultureRatingVisibility && x.commonness === "common"}
|
||||
untyp={cultureRatingVisibility && x.commonness === "uncommon"}
|
||||
name={translation?.name ?? ""}
|
||||
sr={x.dynamic.value}
|
||||
check={x.static.check}
|
||||
ic={fromRaw(x.static.improvement_cost)}
|
||||
addDisabled={!x.isIncreasable}
|
||||
addPoint={handleAdd}
|
||||
removeDisabled={!x.isDecreasable}
|
||||
removePoint={handleRemove}
|
||||
addFillElement
|
||||
insertTopMargin={isTopMarginNeeded(sortOrder, x, list[i - 1])}
|
||||
group={x.static.group.id.skill_group}
|
||||
getGroupName={getGroupName}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</List>
|
||||
) : (
|
||||
<ListPlaceholder type="skills" message={translate("No Results")} />
|
||||
)}
|
||||
</Scroll>
|
||||
</Main>
|
||||
<InlineLibrary />
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
import { createSelector } from "@reduxjs/toolkit"
|
||||
import { ImprovementCost, adventurePointsForRange } from "../../shared/domain/adventurePoints/improvementCost.ts"
|
||||
import { RatedMap, selectAttributes, selectCurrentCharacter, selectDerivedCharacteristics, selectSkills, selectTotalAdventurePoints } from "../slices/characterSlice.ts"
|
||||
import {
|
||||
ImprovementCost,
|
||||
adventurePointsForRange,
|
||||
} from "../../shared/domain/adventurePoints/improvementCost.ts"
|
||||
import { RatedMap } from "../../shared/domain/ratedEntry.ts"
|
||||
import {
|
||||
selectAttributes,
|
||||
selectCurrentCharacter,
|
||||
selectDerivedCharacteristics,
|
||||
selectSkills,
|
||||
selectTotalAdventurePoints,
|
||||
} from "../slices/characterSlice.ts"
|
||||
|
||||
export type SpentAdventurePoints = {
|
||||
general: number
|
||||
@@ -13,72 +23,66 @@ const sumRatedMap = (ratedMap: RatedMap): SpentAdventurePoints =>
|
||||
general: acc.general + rated.cachedAdventurePoints.general,
|
||||
bound: acc.bound + rated.cachedAdventurePoints.bound,
|
||||
}),
|
||||
{ general: 0, bound: 0 }
|
||||
{ general: 0, bound: 0 },
|
||||
)
|
||||
|
||||
export const selectAdventurePointsSpentOnAttributes = createSelector(
|
||||
selectAttributes,
|
||||
sumRatedMap
|
||||
)
|
||||
export const selectAdventurePointsSpentOnAttributes = createSelector(selectAttributes, sumRatedMap)
|
||||
|
||||
export const selectAdventurePointsSpentOnSkills = createSelector(
|
||||
selectSkills,
|
||||
sumRatedMap
|
||||
)
|
||||
export const selectAdventurePointsSpentOnSkills = createSelector(selectSkills, sumRatedMap)
|
||||
|
||||
export const selectAdventurePointsSpentOnCombatTechniques = createSelector(
|
||||
selectCurrentCharacter,
|
||||
(): SpentAdventurePoints => ({ general: 0, bound: 0 })
|
||||
(): SpentAdventurePoints => ({ general: 0, bound: 0 }),
|
||||
)
|
||||
|
||||
export const selectAdventurePointsSpentOnSpells = createSelector(
|
||||
selectCurrentCharacter,
|
||||
(): SpentAdventurePoints => ({ general: 0, bound: 0 })
|
||||
(): SpentAdventurePoints => ({ general: 0, bound: 0 }),
|
||||
)
|
||||
|
||||
export const selectAdventurePointsSpentOnLiturgicalChants = createSelector(
|
||||
selectCurrentCharacter,
|
||||
(): SpentAdventurePoints => ({ general: 0, bound: 0 })
|
||||
(): SpentAdventurePoints => ({ general: 0, bound: 0 }),
|
||||
)
|
||||
|
||||
export const selectAdventurePointsSpentOnCantrips = createSelector(
|
||||
selectCurrentCharacter,
|
||||
(): SpentAdventurePoints => ({ general: 0, bound: 0 })
|
||||
(): SpentAdventurePoints => ({ general: 0, bound: 0 }),
|
||||
)
|
||||
|
||||
export const selectAdventurePointsSpentOnBlessings = createSelector(
|
||||
selectCurrentCharacter,
|
||||
(): SpentAdventurePoints => ({ general: 0, bound: 0 })
|
||||
(): SpentAdventurePoints => ({ general: 0, bound: 0 }),
|
||||
)
|
||||
|
||||
export const selectAdventurePointsSpentOnAdvantages = createSelector(
|
||||
selectCurrentCharacter,
|
||||
(): SpentAdventurePoints => ({ general: 0, bound: 0 })
|
||||
(): SpentAdventurePoints => ({ general: 0, bound: 0 }),
|
||||
)
|
||||
|
||||
export const selectAdventurePointsSpentOnMagicalAdvantages = createSelector(
|
||||
selectCurrentCharacter,
|
||||
(): SpentAdventurePoints => ({ general: 0, bound: 0 })
|
||||
(): SpentAdventurePoints => ({ general: 0, bound: 0 }),
|
||||
)
|
||||
|
||||
export const selectAdventurePointsSpentOnBlessedAdvantages = createSelector(
|
||||
selectCurrentCharacter,
|
||||
(): SpentAdventurePoints => ({ general: 0, bound: 0 })
|
||||
(): SpentAdventurePoints => ({ general: 0, bound: 0 }),
|
||||
)
|
||||
|
||||
export const selectAdventurePointsSpentOnDisadvantages = createSelector(
|
||||
selectCurrentCharacter,
|
||||
(): SpentAdventurePoints => ({ general: 0, bound: 0 })
|
||||
(): SpentAdventurePoints => ({ general: 0, bound: 0 }),
|
||||
)
|
||||
|
||||
export const selectAdventurePointsSpentOnMagicalDisadvantages = createSelector(
|
||||
selectCurrentCharacter,
|
||||
(): SpentAdventurePoints => ({ general: 0, bound: 0 })
|
||||
(): SpentAdventurePoints => ({ general: 0, bound: 0 }),
|
||||
)
|
||||
|
||||
export const selectAdventurePointsSpentOnBlessedDisadvantages = createSelector(
|
||||
selectCurrentCharacter,
|
||||
(): SpentAdventurePoints => ({ general: 0, bound: 0 })
|
||||
(): SpentAdventurePoints => ({ general: 0, bound: 0 }),
|
||||
)
|
||||
|
||||
// export const getMagicalAdvantagesDisadvantagesAdventurePointsMaximum = createMaybeSelector(
|
||||
@@ -88,7 +92,7 @@ export const selectAdventurePointsSpentOnBlessedDisadvantages = createSelector(
|
||||
|
||||
export const selectAdventurePointsSpentOnSpecialAbilities = createSelector(
|
||||
selectCurrentCharacter,
|
||||
(): SpentAdventurePoints => ({ general: 0, bound: 0 })
|
||||
(): SpentAdventurePoints => ({ general: 0, bound: 0 }),
|
||||
)
|
||||
|
||||
export const selectAdventurePointsSpentOnEnergies = createSelector(
|
||||
@@ -100,20 +104,20 @@ export const selectAdventurePointsSpentOnEnergies = createSelector(
|
||||
derivedCharacteristics.karmaPoints.purchased,
|
||||
].reduce(
|
||||
(acc, purchased) => acc + adventurePointsForRange(ImprovementCost.D, 0, purchased),
|
||||
0
|
||||
)
|
||||
+ derivedCharacteristics.arcaneEnergy.permanentlyLostBoughtBack * 2
|
||||
+ derivedCharacteristics.karmaPoints.permanentlyLostBoughtBack * 2
|
||||
0,
|
||||
) +
|
||||
derivedCharacteristics.arcaneEnergy.permanentlyLostBoughtBack * 2 +
|
||||
derivedCharacteristics.karmaPoints.permanentlyLostBoughtBack * 2,
|
||||
)
|
||||
|
||||
export const selectAdventurePointsSpentOnRace = createSelector(
|
||||
selectCurrentCharacter,
|
||||
(): number => 0
|
||||
(): number => 0,
|
||||
)
|
||||
|
||||
export const selectAdventurePointsSpentOnProfession = createSelector(
|
||||
selectCurrentCharacter,
|
||||
(): number | undefined => undefined
|
||||
(): number | undefined => undefined,
|
||||
)
|
||||
|
||||
export const selectAdventurePointsSpent = createSelector(
|
||||
@@ -134,34 +138,33 @@ export const selectAdventurePointsSpent = createSelector(
|
||||
selectAdventurePointsSpentOnEnergies,
|
||||
selectAdventurePointsSpentOnRace,
|
||||
selectAdventurePointsSpentOnProfession,
|
||||
(...spentCategories): SpentAdventurePoints => spentCategories.reduce<SpentAdventurePoints>(
|
||||
(acc, spentCategory) => ({
|
||||
general:
|
||||
acc.general
|
||||
+ (
|
||||
typeof spentCategory === "number"
|
||||
? spentCategory
|
||||
: typeof spentCategory === "object"
|
||||
? spentCategory.general
|
||||
: 0),
|
||||
bound:
|
||||
acc.general
|
||||
+ (
|
||||
typeof spentCategory === "number"
|
||||
? 0
|
||||
: typeof spentCategory === "object"
|
||||
? spentCategory.bound
|
||||
: 0),
|
||||
}),
|
||||
{ general: 0, bound: 0 }
|
||||
)
|
||||
(...spentCategories): SpentAdventurePoints =>
|
||||
spentCategories.reduce<SpentAdventurePoints>(
|
||||
(acc, spentCategory) => ({
|
||||
general:
|
||||
acc.general +
|
||||
(typeof spentCategory === "number"
|
||||
? spentCategory
|
||||
: typeof spentCategory === "object"
|
||||
? spentCategory.general
|
||||
: 0),
|
||||
bound:
|
||||
acc.general +
|
||||
(typeof spentCategory === "number"
|
||||
? 0
|
||||
: typeof spentCategory === "object"
|
||||
? spentCategory.bound
|
||||
: 0),
|
||||
}),
|
||||
{ general: 0, bound: 0 },
|
||||
),
|
||||
)
|
||||
|
||||
export const selectAdventurePointsAvailable = createSelector(
|
||||
selectTotalAdventurePoints,
|
||||
selectAdventurePointsSpent,
|
||||
(totalAdventurePoints = 0, { general: spentAdventurePoints }) =>
|
||||
totalAdventurePoints - spentAdventurePoints
|
||||
totalAdventurePoints - spentAdventurePoints,
|
||||
)
|
||||
|
||||
// export const getHasCurrentNoAddedAP = createMaybeSelector (
|
||||
|
||||
@@ -1,18 +1,140 @@
|
||||
import { createSelector } from "@reduxjs/toolkit"
|
||||
import { Attribute } from "optolith-database-schema/types/Attribute"
|
||||
import { getAttributeMaximum, getAttributeMinimum, isAttributeDecreasable, isAttributeIncreasable } from "../../shared/domain/attribute.ts"
|
||||
import {
|
||||
getAttributeMaximum,
|
||||
getAttributeMinimum,
|
||||
isAttributeDecreasable,
|
||||
isAttributeIncreasable,
|
||||
} from "../../shared/domain/attribute.ts"
|
||||
import { filterApplyingRatedDependencies } from "../../shared/domain/dependencies/filterApplyingDependencies.ts"
|
||||
import { OptionalRuleIdentifier } from "../../shared/domain/identifier.ts"
|
||||
import { Rated } from "../../shared/domain/ratedEntry.ts"
|
||||
import { isNotNullish } from "../../shared/utils/nullable.ts"
|
||||
import { createPropertySelector } from "../../shared/utils/redux.ts"
|
||||
import { attributeValue, createInitialDynamicAttribute } from "../slices/attributesSlice.ts"
|
||||
import { selectActiveOptionalRules, selectAttributeAdjustmentId, selectDerivedCharacteristics, selectAttributes as selectDynamicAttributes } from "../slices/characterSlice.ts"
|
||||
import {
|
||||
selectActiveOptionalRules,
|
||||
selectAttributeAdjustmentId,
|
||||
selectCeremonies,
|
||||
selectCloseCombatTechniques,
|
||||
selectDerivedCharacteristics,
|
||||
selectAttributes as selectDynamicAttributes,
|
||||
selectLiturgicalChants,
|
||||
selectRangedCombatTechniques,
|
||||
selectRituals,
|
||||
selectSkills,
|
||||
selectSpells,
|
||||
} from "../slices/characterSlice.ts"
|
||||
import { selectAttributes as selectStaticAttributes } from "../slices/databaseSlice.ts"
|
||||
import { selectIsInCharacterCreation } from "./characterSelectors.ts"
|
||||
import { selectCurrentExperienceLevel, selectMaximumTotalAttributePoints, selectStartExperienceLevel } from "./experienceLevelSelectors.ts"
|
||||
import { selectActiveBlessedTradition, selectActiveMagicalTraditions } from "./magicalTraditionSelectors.ts"
|
||||
import {
|
||||
selectCurrentExperienceLevel,
|
||||
selectMaximumTotalAttributePoints,
|
||||
selectStartExperienceLevel,
|
||||
} from "./experienceLevelSelectors.ts"
|
||||
import {
|
||||
selectActiveBlessedTradition,
|
||||
selectActiveMagicalTraditions,
|
||||
} from "./magicalTraditionSelectors.ts"
|
||||
import { selectCurrentRace } from "./raceSelectors.ts"
|
||||
|
||||
export type DisplayedPrimaryAttribute = {
|
||||
static: Attribute
|
||||
dynamic: Rated
|
||||
}
|
||||
|
||||
export type DisplayedMagicalPrimaryAttributes = {
|
||||
list: DisplayedPrimaryAttribute[]
|
||||
halfed: boolean
|
||||
}
|
||||
|
||||
export const selectHighestMagicalPrimaryAttributes = createSelector(
|
||||
selectActiveMagicalTraditions,
|
||||
selectStaticAttributes,
|
||||
selectDynamicAttributes,
|
||||
(
|
||||
activeMagicalTraditions,
|
||||
staticAttributes,
|
||||
dynamicAttributes,
|
||||
): DisplayedMagicalPrimaryAttributes => {
|
||||
const { map, halfed } = activeMagicalTraditions.reduce<{
|
||||
map: Map<number, DisplayedPrimaryAttribute>
|
||||
halfed: boolean
|
||||
}>(
|
||||
(currentlyHighest, magicalTradition) => {
|
||||
const staticPrimaryAttribute = magicalTradition.static.primary
|
||||
|
||||
if (staticPrimaryAttribute === undefined) {
|
||||
return currentlyHighest
|
||||
} else {
|
||||
const {
|
||||
id: { attribute: id },
|
||||
use_half_for_arcane_energy,
|
||||
} = staticPrimaryAttribute
|
||||
const staticAttribute = staticAttributes[id]
|
||||
const dynamicAttribute = dynamicAttributes[id] ?? createInitialDynamicAttribute(id)
|
||||
|
||||
if (staticAttribute === undefined) {
|
||||
return currentlyHighest
|
||||
} else if (
|
||||
currentlyHighest.map.size === 0 ||
|
||||
[...currentlyHighest.map.values()][0]!.dynamic.value < dynamicAttribute.value
|
||||
) {
|
||||
return {
|
||||
map: new Map([[id, { static: staticAttribute, dynamic: dynamicAttribute }]]),
|
||||
halfed: currentlyHighest.halfed || use_half_for_arcane_energy,
|
||||
}
|
||||
} else if (
|
||||
[...currentlyHighest.map.values()][0]!.dynamic.value === dynamicAttribute.value
|
||||
) {
|
||||
return {
|
||||
map: currentlyHighest.map.set(id, {
|
||||
static: staticAttribute,
|
||||
dynamic: dynamicAttribute,
|
||||
}),
|
||||
halfed: currentlyHighest.halfed || use_half_for_arcane_energy,
|
||||
}
|
||||
} else {
|
||||
return currentlyHighest
|
||||
}
|
||||
}
|
||||
},
|
||||
{ map: new Map(), halfed: false },
|
||||
)
|
||||
|
||||
return {
|
||||
list: [...map.values()],
|
||||
halfed,
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
export const selectBlessedPrimaryAttribute = createSelector(
|
||||
selectActiveBlessedTradition,
|
||||
selectStaticAttributes,
|
||||
selectDynamicAttributes,
|
||||
(
|
||||
activeBlessedTradition,
|
||||
staticAttributes,
|
||||
dynamicAttributes,
|
||||
): DisplayedPrimaryAttribute | undefined => {
|
||||
const id = activeBlessedTradition?.static.primary?.id.attribute
|
||||
|
||||
if (id === undefined) {
|
||||
return undefined
|
||||
} else {
|
||||
const staticAttribute = staticAttributes[id]
|
||||
const dynamicAttribute = dynamicAttributes[id] ?? createInitialDynamicAttribute(id)
|
||||
|
||||
if (staticAttribute === undefined) {
|
||||
return undefined
|
||||
} else {
|
||||
return { static: staticAttribute, dynamic: dynamicAttribute }
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
export type DisplayedAttribute = {
|
||||
static: Attribute
|
||||
dynamic: Rated
|
||||
@@ -26,10 +148,7 @@ export const selectTotalPoints = createSelector(
|
||||
selectStaticAttributes,
|
||||
selectDynamicAttributes,
|
||||
(attributes, dynamicAttributes): number =>
|
||||
Object.values(attributes).reduce(
|
||||
(sum, { id }) => sum + (dynamicAttributes[id]?.value ?? 8),
|
||||
0
|
||||
)
|
||||
Object.values(attributes).reduce((sum, { id }) => sum + (dynamicAttributes[id]?.value ?? 8), 0),
|
||||
)
|
||||
|
||||
export const selectVisibleAttributes = createSelector(
|
||||
@@ -44,6 +163,15 @@ export const selectVisibleAttributes = createSelector(
|
||||
createPropertySelector(selectActiveOptionalRules, OptionalRuleIdentifier.MaximumAttributeScores),
|
||||
selectAttributeAdjustmentId,
|
||||
selectDerivedCharacteristics,
|
||||
selectSkills,
|
||||
selectCloseCombatTechniques,
|
||||
selectRangedCombatTechniques,
|
||||
selectSpells,
|
||||
selectRituals,
|
||||
selectLiturgicalChants,
|
||||
selectCeremonies,
|
||||
selectHighestMagicalPrimaryAttributes,
|
||||
selectBlessedPrimaryAttribute,
|
||||
(
|
||||
attributes,
|
||||
dynamicAttributes,
|
||||
@@ -56,16 +184,49 @@ export const selectVisibleAttributes = createSelector(
|
||||
maximumAttributeScores,
|
||||
attributeAdjustmentId,
|
||||
derivedCharacteristics,
|
||||
): DisplayedAttribute[] =>
|
||||
Object.values(attributes)
|
||||
skills,
|
||||
closeCombatTechniques,
|
||||
rangedCombatTechniques,
|
||||
spells,
|
||||
rituals,
|
||||
liturgicalChants,
|
||||
ceremonies,
|
||||
highestMagicalPrimaryAttributes,
|
||||
blessedPrimaryAttribute,
|
||||
): DisplayedAttribute[] => {
|
||||
const filterApplyingDependencies = filterApplyingRatedDependencies({
|
||||
attributes: dynamicAttributes,
|
||||
skills,
|
||||
closeCombatTechniques,
|
||||
rangedCombatTechniques,
|
||||
spells,
|
||||
rituals,
|
||||
liturgicalChants,
|
||||
ceremonies,
|
||||
})
|
||||
|
||||
const singleHighestMagicalPrimaryAttribute =
|
||||
highestMagicalPrimaryAttributes.list.length === 1
|
||||
? highestMagicalPrimaryAttributes.list[0]
|
||||
: undefined
|
||||
|
||||
return Object.values(attributes)
|
||||
.sort((a, b) => a.id - b.id)
|
||||
.map(attribute => {
|
||||
const dynamicAttribute =
|
||||
dynamicAttributes[attribute.id] ?? createInitialDynamicAttribute(attribute.id)
|
||||
|
||||
const minimum = getAttributeMinimum(
|
||||
derivedCharacteristics,
|
||||
derivedCharacteristics.lifePoints,
|
||||
derivedCharacteristics.arcaneEnergy,
|
||||
derivedCharacteristics.karmaPoints,
|
||||
dynamicAttribute,
|
||||
singleHighestMagicalPrimaryAttribute?.static.id,
|
||||
[], // TODO: Replace
|
||||
blessedPrimaryAttribute?.static.id,
|
||||
[], // TODO: Replace
|
||||
filterApplyingDependencies,
|
||||
_id => undefined, // TODO: Replace
|
||||
)
|
||||
|
||||
const maximum = getAttributeMaximum(
|
||||
@@ -83,21 +244,17 @@ export const selectVisibleAttributes = createSelector(
|
||||
dynamic: dynamicAttribute,
|
||||
minimum,
|
||||
maximum,
|
||||
isDecreasable:
|
||||
isAttributeDecreasable(
|
||||
dynamicAttribute,
|
||||
minimum
|
||||
),
|
||||
isIncreasable:
|
||||
isAttributeIncreasable(
|
||||
dynamicAttribute,
|
||||
maximum,
|
||||
totalPoints,
|
||||
maxTotalPoints,
|
||||
isInCharacterCreation,
|
||||
),
|
||||
isDecreasable: isAttributeDecreasable(dynamicAttribute, minimum),
|
||||
isIncreasable: isAttributeIncreasable(
|
||||
dynamicAttribute,
|
||||
maximum,
|
||||
totalPoints,
|
||||
maxTotalPoints,
|
||||
isInCharacterCreation,
|
||||
),
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
// const getAddedEnergies = createMaybeSelector (
|
||||
@@ -120,104 +277,6 @@ export const selectVisibleAttributes = createSelector(
|
||||
// maximum
|
||||
// )
|
||||
|
||||
export type DisplayedPrimaryAttribute = {
|
||||
static: Attribute
|
||||
dynamic: Rated
|
||||
}
|
||||
|
||||
export type DisplayedMagicalPrimaryAttributes = {
|
||||
list: DisplayedPrimaryAttribute[]
|
||||
halfed: boolean
|
||||
}
|
||||
|
||||
export const selectHighestMagicalPrimaryAttributes = createSelector(
|
||||
selectActiveMagicalTraditions,
|
||||
selectStaticAttributes,
|
||||
selectDynamicAttributes,
|
||||
(
|
||||
activeMagicalTraditions,
|
||||
staticAttributes,
|
||||
dynamicAttributes
|
||||
): DisplayedMagicalPrimaryAttributes => {
|
||||
const { map, halfed } = activeMagicalTraditions
|
||||
.reduce<{ map: Map<number, DisplayedPrimaryAttribute>; halfed: boolean }>(
|
||||
(currentlyHighest, magicalTradition) => {
|
||||
const staticPrimaryAttribute = magicalTradition.static.primary
|
||||
|
||||
if (staticPrimaryAttribute === undefined) {
|
||||
return currentlyHighest
|
||||
}
|
||||
else {
|
||||
const { id: { attribute: id }, use_half_for_arcane_energy } = staticPrimaryAttribute
|
||||
const staticAttribute = staticAttributes[id]
|
||||
const dynamicAttribute = dynamicAttributes[id] ?? createInitialDynamicAttribute(id)
|
||||
|
||||
if (staticAttribute === undefined) {
|
||||
return currentlyHighest
|
||||
}
|
||||
else if (
|
||||
currentlyHighest.map.size === 0
|
||||
|| [ ...currentlyHighest.map.values() ][0]!.dynamic.value < dynamicAttribute.value
|
||||
) {
|
||||
return {
|
||||
map: new Map([
|
||||
[
|
||||
id,
|
||||
{ static: staticAttribute, dynamic: dynamicAttribute },
|
||||
],
|
||||
]),
|
||||
halfed: currentlyHighest.halfed || use_half_for_arcane_energy,
|
||||
}
|
||||
}
|
||||
else {
|
||||
return {
|
||||
map: currentlyHighest.map.set(
|
||||
id,
|
||||
{ static: staticAttribute, dynamic: dynamicAttribute }
|
||||
),
|
||||
halfed: currentlyHighest.halfed || use_half_for_arcane_energy,
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{ map: new Map(), halfed: false }
|
||||
)
|
||||
|
||||
return {
|
||||
list: [ ...map.values() ],
|
||||
halfed,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
export const selectBlessedPrimaryAttribute = createSelector(
|
||||
selectActiveBlessedTradition,
|
||||
selectStaticAttributes,
|
||||
selectDynamicAttributes,
|
||||
(
|
||||
activeBlessedTradition,
|
||||
staticAttributes,
|
||||
dynamicAttributes,
|
||||
): DisplayedPrimaryAttribute | undefined => {
|
||||
const id = activeBlessedTradition?.static.primary?.id.attribute
|
||||
|
||||
if (id === undefined) {
|
||||
return undefined
|
||||
}
|
||||
else {
|
||||
const staticAttribute = staticAttributes[id]
|
||||
const dynamicAttribute = dynamicAttributes[id] ?? createInitialDynamicAttribute(id)
|
||||
|
||||
if (staticAttribute === undefined) {
|
||||
return undefined
|
||||
}
|
||||
else {
|
||||
return { static: staticAttribute, dynamic: dynamicAttribute }
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// export const getPrimaryMagicalAttributes = createMaybeSelector (
|
||||
// getWikiAttributes,
|
||||
// getAttributes,
|
||||
@@ -269,7 +328,7 @@ export const selectBlessedPrimaryAttribute = createSelector(
|
||||
|
||||
export const selectCarryingCapacity = createSelector(
|
||||
selectDynamicAttributes,
|
||||
(attributes): number => (attributes[8]?.value ?? 8) * 2
|
||||
(attributes): number => (attributes[8]?.value ?? 8) * 2,
|
||||
)
|
||||
|
||||
export const selectAvailableAdjustments = createSelector(
|
||||
@@ -277,25 +336,24 @@ export const selectAvailableAdjustments = createSelector(
|
||||
selectAttributeAdjustmentId,
|
||||
selectVisibleAttributes,
|
||||
(race, currentId, attributes) => {
|
||||
const selectableAdjustment = race?.attribute_adjustments.find(pair => pair.list.length > 1)
|
||||
const selectableAdjustment = race?.attribute_adjustments?.selectable?.[0]
|
||||
|
||||
if (selectableAdjustment === undefined) {
|
||||
return undefined
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
const current = attributes.find(attr => attr.static.id === currentId)
|
||||
|
||||
const canNotSwitch = current !== undefined
|
||||
&& current.maximum !== undefined
|
||||
&& current.maximum - selectableAdjustment.value < attributeValue(current.dynamic)
|
||||
const canNotSwitch =
|
||||
current !== undefined &&
|
||||
current.maximum !== undefined &&
|
||||
current.maximum - selectableAdjustment.value < attributeValue(current.dynamic)
|
||||
|
||||
if (canNotSwitch) {
|
||||
return {
|
||||
value: selectableAdjustment.value,
|
||||
list: [ current ],
|
||||
list: [current],
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return {
|
||||
value: selectableAdjustment.value,
|
||||
list: selectableAdjustment.list
|
||||
@@ -304,5 +362,5 @@ export const selectAvailableAdjustments = createSelector(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -0,0 +1,368 @@
|
||||
import { createSelector } from "@reduxjs/toolkit"
|
||||
import { CloseCombatTechnique } from "optolith-database-schema/types/CombatTechnique_Close"
|
||||
import { RangedCombatTechnique } from "optolith-database-schema/types/CombatTechnique_Ranged"
|
||||
// import { CombatTechniqueId, SpecialAbilityId } from "../../App/Constants/Ids.ts"
|
||||
// import { createSkillDependentWithValue6 } from "../../App/Models/ActiveEntries/SkillDependent.ts"
|
||||
// import { HeroModel, HeroModelRecord } from "../../App/Models/Hero/HeroModel.ts"
|
||||
// import {
|
||||
// CombatTechniqueWithAttackParryBase,
|
||||
// CombatTechniqueWithAttackParryBaseA_,
|
||||
// } from "../../App/Models/View/CombatTechniqueWithAttackParryBase.ts"
|
||||
// import { CombatTechniqueWithRequirements } from "../../App/Models/View/CombatTechniqueWithRequirements.ts"
|
||||
// import { CombatTechnique } from "../../App/Models/Wiki/CombatTechnique.ts"
|
||||
// import { StaticData } from "../../App/Models/Wiki/WikiModel.ts"
|
||||
// import { getRuleBooksEnabled } from "../../App/Selectors/rulesSelectors.ts"
|
||||
// import { getCombatTechniquesWithRequirementsSortOptions } from "../../App/Selectors/sortOptionsSelectors.ts"
|
||||
// import {
|
||||
// getAttributes,
|
||||
// getCombatTechniques,
|
||||
// getCombatTechniquesFilterText,
|
||||
// getCurrentHeroPresent,
|
||||
// getSpecialAbilities,
|
||||
// getWiki,
|
||||
// getWikiCombatTechniques,
|
||||
// } from "../../App/Selectors/stateSelectors.ts"
|
||||
// import { isMaybeActive } from "../../App/Utilities/Activatable/isActive.ts"
|
||||
// import { compareLocale } from "../../App/Utilities/I18n.ts"
|
||||
// import {
|
||||
// isDecreaseDisabled,
|
||||
// isIncreaseDisabled,
|
||||
// } from "../../App/Utilities/Increasable/combatTechniqueUtils.ts"
|
||||
// import { filterByAvailabilityAndPred, isEntryFromCoreBook } from "../../App/Utilities/RulesUtils.ts"
|
||||
// import { createMaybeSelector } from "../../App/Utilities/createMaybeSelector.ts"
|
||||
// import { filterAndSortRecordsBy } from "../../App/Utilities/filterAndSortBy.ts"
|
||||
// import { pipe, pipe_ } from "../../App/Utilities/pipe.ts"
|
||||
// import { comparingR, sortByMulti } from "../../App/Utilities/sortBy.ts"
|
||||
// import { ident, thrush } from "../../Data/Function.ts"
|
||||
// import { fmap, fmapF } from "../../Data/Functor.ts"
|
||||
// import { List, consF, filter, fnull, map } from "../../Data/List.ts"
|
||||
// import { liftM2, maybe } from "../../Data/Maybe.ts"
|
||||
// import { gt } from "../../Data/Num.ts"
|
||||
// import { findWithDefault, foldrWithKey, lookup } from "../../Data/OrderedMap.ts"
|
||||
// import { Record } from "../../Data/Record.ts"
|
||||
// import { uncurryN } from "../../Data/Tuple/Curry.ts"
|
||||
import { isActive } from "../../shared/domain/activatableEntry.ts"
|
||||
import {
|
||||
getAttackBaseForClose,
|
||||
getAttackBaseForRanged,
|
||||
getCombatTechniqueMaximum,
|
||||
getCombatTechniqueMinimum,
|
||||
getParryBaseForClose,
|
||||
isCombatTechniqueDecreasable,
|
||||
isCombatTechniqueIncreasable,
|
||||
} from "../../shared/domain/combatTechnique.ts"
|
||||
import { filterApplyingRatedDependencies } from "../../shared/domain/dependencies/filterApplyingDependencies.ts"
|
||||
import {
|
||||
AdvantageIdentifier,
|
||||
GeneralSpecialAbilityIdentifier,
|
||||
RangedCombatTechniqueIdentifier,
|
||||
} from "../../shared/domain/identifier.ts"
|
||||
import { Rated } from "../../shared/domain/ratedEntry.ts"
|
||||
import { isNotNullish } from "../../shared/utils/nullable.ts"
|
||||
import { createPropertySelector } from "../../shared/utils/redux.ts"
|
||||
import {
|
||||
selectAdvantages,
|
||||
selectAttributes,
|
||||
selectCeremonies,
|
||||
selectCloseCombatTechniques as selectDynamicCloseCombatTechniques,
|
||||
selectRangedCombatTechniques as selectDynamicRangedCombatTechniques,
|
||||
selectGeneralSpecialAbilities,
|
||||
selectLiturgicalChants,
|
||||
selectRituals,
|
||||
selectSkills,
|
||||
selectSpells,
|
||||
} from "../slices/characterSlice.ts"
|
||||
import { createInitialDynamicCloseCombatTechnique } from "../slices/closeCombatTechniqueSlice.ts"
|
||||
import {
|
||||
selectCloseCombatTechniques as selectStaticCloseCombatTechniques,
|
||||
selectRangedCombatTechniques as selectStaticRangedCombatTechniques,
|
||||
} from "../slices/databaseSlice.ts"
|
||||
import { createInitialDynamicRangedCombatTechnique } from "../slices/rangedCombatTechniqueSlice.ts"
|
||||
import { selectCanRemove, selectIsInCharacterCreation } from "./characterSelectors.ts"
|
||||
import { selectStartExperienceLevel } from "./experienceLevelSelectors.ts"
|
||||
|
||||
export type DisplayedCloseCombatTechnique = {
|
||||
kind: "close"
|
||||
static: CloseCombatTechnique
|
||||
dynamic: Rated
|
||||
minimum: number
|
||||
maximum: number
|
||||
isIncreasable: boolean
|
||||
isDecreasable: boolean
|
||||
attackBase: number
|
||||
parryBase?: number
|
||||
}
|
||||
|
||||
export type DisplayedRangedCombatTechnique = {
|
||||
kind: "ranged"
|
||||
static: RangedCombatTechnique
|
||||
dynamic: Rated
|
||||
minimum: number
|
||||
maximum: number
|
||||
isIncreasable: boolean
|
||||
isDecreasable: boolean
|
||||
attackBase: number
|
||||
}
|
||||
|
||||
export type DisplayedCombatTechnique =
|
||||
| DisplayedCloseCombatTechnique
|
||||
| DisplayedRangedCombatTechnique
|
||||
|
||||
export const selectVisibleCloseCombatTechniques = createSelector(
|
||||
selectStaticCloseCombatTechniques,
|
||||
selectDynamicCloseCombatTechniques,
|
||||
selectIsInCharacterCreation,
|
||||
selectStartExperienceLevel,
|
||||
selectCanRemove,
|
||||
createPropertySelector(selectAdvantages, AdvantageIdentifier.ExceptionalSkill),
|
||||
createPropertySelector(selectGeneralSpecialAbilities, GeneralSpecialAbilityIdentifier.Hunter),
|
||||
selectAttributes,
|
||||
selectSkills,
|
||||
selectDynamicRangedCombatTechniques,
|
||||
selectSpells,
|
||||
selectRituals,
|
||||
selectLiturgicalChants,
|
||||
selectCeremonies,
|
||||
(
|
||||
staticCloseCombatTechniques,
|
||||
dynamicCloseCombatTechniques,
|
||||
isInCharacterCreation,
|
||||
startExperienceLevel,
|
||||
canRemove,
|
||||
exceptionalSkill,
|
||||
hunter,
|
||||
attributes,
|
||||
skills,
|
||||
rangedCombatTechniques,
|
||||
spells,
|
||||
rituals,
|
||||
liturgicalChants,
|
||||
ceremonies,
|
||||
): DisplayedCloseCombatTechnique[] => {
|
||||
const filterApplyingDependencies = filterApplyingRatedDependencies({
|
||||
attributes,
|
||||
skills,
|
||||
closeCombatTechniques: dynamicCloseCombatTechniques,
|
||||
rangedCombatTechniques,
|
||||
spells,
|
||||
rituals,
|
||||
liturgicalChants,
|
||||
ceremonies,
|
||||
})
|
||||
|
||||
return Object.values(staticCloseCombatTechniques).map(combatTechnique => {
|
||||
const dynamicCloseCombatTechnique =
|
||||
dynamicCloseCombatTechniques[combatTechnique.id] ??
|
||||
createInitialDynamicCloseCombatTechnique(combatTechnique.id)
|
||||
|
||||
const minimum = getCombatTechniqueMinimum(
|
||||
rangedCombatTechniques,
|
||||
{ tag: "CloseCombatTechnique", closeCombatTechnique: combatTechnique },
|
||||
dynamicCloseCombatTechnique,
|
||||
hunter,
|
||||
filterApplyingDependencies,
|
||||
)
|
||||
|
||||
const maximum = getCombatTechniqueMaximum(
|
||||
attributes,
|
||||
{ tag: "CloseCombatTechnique", closeCombatTechnique: combatTechnique },
|
||||
isInCharacterCreation,
|
||||
startExperienceLevel,
|
||||
exceptionalSkill,
|
||||
)
|
||||
|
||||
return {
|
||||
kind: "close",
|
||||
static: combatTechnique,
|
||||
dynamic: dynamicCloseCombatTechnique,
|
||||
minimum,
|
||||
maximum,
|
||||
isDecreasable: isCombatTechniqueDecreasable(
|
||||
dynamicCloseCombatTechnique,
|
||||
minimum,
|
||||
canRemove,
|
||||
),
|
||||
isIncreasable: isCombatTechniqueIncreasable(dynamicCloseCombatTechnique, maximum),
|
||||
attackBase: getAttackBaseForClose(attributes, dynamicCloseCombatTechnique),
|
||||
parryBase: getParryBaseForClose(attributes, combatTechnique, dynamicCloseCombatTechnique),
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
export const selectVisibleRangedCombatTechniques = createSelector(
|
||||
selectStaticRangedCombatTechniques,
|
||||
selectDynamicRangedCombatTechniques,
|
||||
selectIsInCharacterCreation,
|
||||
selectStartExperienceLevel,
|
||||
selectCanRemove,
|
||||
createPropertySelector(selectAdvantages, AdvantageIdentifier.ExceptionalSkill),
|
||||
createPropertySelector(selectGeneralSpecialAbilities, GeneralSpecialAbilityIdentifier.Hunter),
|
||||
createPropertySelector(selectGeneralSpecialAbilities, GeneralSpecialAbilityIdentifier.FireEater),
|
||||
selectAttributes,
|
||||
selectSkills,
|
||||
selectDynamicCloseCombatTechniques,
|
||||
selectSpells,
|
||||
selectRituals,
|
||||
selectLiturgicalChants,
|
||||
selectCeremonies,
|
||||
(
|
||||
staticRangedCombatTechniques,
|
||||
dynamicRangedCombatTechniques,
|
||||
isInCharacterCreation,
|
||||
startExperienceLevel,
|
||||
canRemove,
|
||||
exceptionalSkill,
|
||||
hunter,
|
||||
fireEater,
|
||||
attributes,
|
||||
skills,
|
||||
closeCombatTechniques,
|
||||
spells,
|
||||
rituals,
|
||||
liturgicalChants,
|
||||
ceremonies,
|
||||
): DisplayedRangedCombatTechnique[] => {
|
||||
const filterApplyingDependencies = filterApplyingRatedDependencies({
|
||||
attributes,
|
||||
skills,
|
||||
closeCombatTechniques,
|
||||
rangedCombatTechniques: dynamicRangedCombatTechniques,
|
||||
spells,
|
||||
rituals,
|
||||
liturgicalChants,
|
||||
ceremonies,
|
||||
})
|
||||
|
||||
const isFireEaterActive = isActive(fireEater)
|
||||
|
||||
return Object.values(staticRangedCombatTechniques)
|
||||
.map(combatTechnique => {
|
||||
if (
|
||||
combatTechnique.id === RangedCombatTechniqueIdentifier.SpittingFire &&
|
||||
!isFireEaterActive
|
||||
) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const dynamicRangedCombatTechnique =
|
||||
dynamicRangedCombatTechniques[combatTechnique.id] ??
|
||||
createInitialDynamicRangedCombatTechnique(combatTechnique.id)
|
||||
|
||||
const minimum = getCombatTechniqueMinimum(
|
||||
dynamicRangedCombatTechniques,
|
||||
{ tag: "RangedCombatTechnique", rangedCombatTechnique: combatTechnique },
|
||||
dynamicRangedCombatTechnique,
|
||||
hunter,
|
||||
filterApplyingDependencies,
|
||||
)
|
||||
|
||||
const maximum = getCombatTechniqueMaximum(
|
||||
attributes,
|
||||
{ tag: "RangedCombatTechnique", rangedCombatTechnique: combatTechnique },
|
||||
isInCharacterCreation,
|
||||
startExperienceLevel,
|
||||
exceptionalSkill,
|
||||
)
|
||||
|
||||
return {
|
||||
kind: "ranged" as const,
|
||||
static: combatTechnique,
|
||||
dynamic: dynamicRangedCombatTechnique,
|
||||
minimum,
|
||||
maximum,
|
||||
isDecreasable: isCombatTechniqueDecreasable(
|
||||
dynamicRangedCombatTechnique,
|
||||
minimum,
|
||||
canRemove,
|
||||
),
|
||||
isIncreasable: isCombatTechniqueIncreasable(dynamicRangedCombatTechnique, maximum),
|
||||
attackBase: getAttackBaseForRanged(
|
||||
attributes,
|
||||
combatTechnique,
|
||||
dynamicRangedCombatTechnique,
|
||||
),
|
||||
}
|
||||
})
|
||||
.filter(isNotNullish)
|
||||
},
|
||||
)
|
||||
|
||||
export const selectVisibleCombatTechniques = createSelector(
|
||||
selectVisibleCloseCombatTechniques,
|
||||
selectVisibleRangedCombatTechniques,
|
||||
(closeCombatTechniques, rangedCombatTechniques): DisplayedCombatTechnique[] => [
|
||||
...closeCombatTechniques,
|
||||
...rangedCombatTechniques,
|
||||
],
|
||||
)
|
||||
|
||||
// export const getCombatTechniquesForSheet = createMaybeSelector(
|
||||
// getWiki,
|
||||
// getCombatTechniquesForView,
|
||||
// (staticData, combatTechniques) =>
|
||||
// fmapF(combatTechniques)(
|
||||
// filter(
|
||||
// x =>
|
||||
// SDA.value(CTWAPBA.stateEntry(x)) > 6 ||
|
||||
// isEntryFromCoreBook(CTA.src)(StaticData.A.books(staticData))(CTWAPBA.wikiEntry(x)),
|
||||
// ),
|
||||
// ),
|
||||
// )
|
||||
|
||||
// const getGr = pipe(CTWAPBA.wikiEntry, CTA.gr)
|
||||
// const getValue = pipe(CTWAPBA.stateEntry, SDA.value)
|
||||
// type CTWAPB = CombatTechniqueWithAttackParryBase
|
||||
|
||||
// export const getAllCombatTechniques = createMaybeSelector(
|
||||
// getCombatTechniquesForView,
|
||||
// getCurrentHeroPresent,
|
||||
// getWiki,
|
||||
// (mcombat_techniques, mhero, wiki) =>
|
||||
// liftM2((combatTechniques: List<Record<CTWAPB>>) => (hero: HeroModelRecord) => {
|
||||
// const hunter = lookup<string>(SpecialAbilityId.Hunter)(HeroModel.A.specialAbilities(hero))
|
||||
|
||||
// const hunterRequiresMinimum =
|
||||
// isMaybeActive(hunter) &&
|
||||
// thrush(combatTechniques)(List.any(x => getGr(x) === 2 && getValue(x) >= 10))
|
||||
|
||||
// return thrush(combatTechniques)(
|
||||
// map(x =>
|
||||
// CombatTechniqueWithRequirements({
|
||||
// at: CTWAPBA.at(x),
|
||||
// pa: CTWAPBA.pa(x),
|
||||
// isDecreasable: !isDecreaseDisabled(wiki)(hero)(CTWAPBA.wikiEntry(x))(
|
||||
// CTWAPBA.stateEntry(x),
|
||||
// )(hunterRequiresMinimum),
|
||||
// isIncreasable: !isIncreaseDisabled(wiki)(hero)(CTWAPBA.wikiEntry(x))(
|
||||
// CTWAPBA.stateEntry(x),
|
||||
// ),
|
||||
// stateEntry: CTWAPBA.stateEntry(x),
|
||||
// wikiEntry: CTWAPBA.wikiEntry(x),
|
||||
// }),
|
||||
// ),
|
||||
// )
|
||||
// })(mcombat_techniques)(mhero),
|
||||
// )
|
||||
|
||||
// export const getAvailableCombatTechniques = createMaybeSelector(
|
||||
// getRuleBooksEnabled,
|
||||
// getAllCombatTechniques,
|
||||
// uncurryN(av =>
|
||||
// fmap(
|
||||
// filterByAvailabilityAndPred(pipe(CTWRA.wikiEntry, CTA.src))(
|
||||
// pipe(CTWRA.stateEntry, SDA.value, gt(6)),
|
||||
// )(av),
|
||||
// ),
|
||||
// ),
|
||||
// )
|
||||
|
||||
// export const getFilteredCombatTechniques = createMaybeSelector(
|
||||
// getAvailableCombatTechniques,
|
||||
// getCombatTechniquesWithRequirementsSortOptions,
|
||||
// getCombatTechniquesFilterText,
|
||||
// (mcombat_techniques, sortOptions, filterText) =>
|
||||
// fmapF(mcombat_techniques)(
|
||||
// filterAndSortRecordsBy(0)([pipe(CTWRA.wikiEntry, CTA.name)])(sortOptions)(filterText),
|
||||
// ),
|
||||
// )
|
||||
@@ -1,10 +1,16 @@
|
||||
import { createSelector } from "@reduxjs/toolkit"
|
||||
import { BlessedTradition } from "optolith-database-schema/types/specialAbility/BlessedTradition"
|
||||
import { MagicalTradition } from "optolith-database-schema/types/specialAbility/MagicalTradition"
|
||||
import { isActive } from "../../shared/domain/activatableEntry.ts"
|
||||
import { Activatable, isActive } from "../../shared/domain/activatableEntry.ts"
|
||||
import { isNotNullish } from "../../shared/utils/nullable.ts"
|
||||
import { Activatable, selectBlessedTraditions as selectDynamicBlessedTraditions, selectMagicalTraditions as selectDynamicMagicalTraditions } from "../slices/characterSlice.ts"
|
||||
import { selectBlessedTraditions as selectStaticBlessedTraditions, selectMagicalTraditions as selectStaticMagicalTraditions } from "../slices/databaseSlice.ts"
|
||||
import {
|
||||
selectBlessedTraditions as selectDynamicBlessedTraditions,
|
||||
selectMagicalTraditions as selectDynamicMagicalTraditions,
|
||||
} from "../slices/characterSlice.ts"
|
||||
import {
|
||||
selectBlessedTraditions as selectStaticBlessedTraditions,
|
||||
selectMagicalTraditions as selectStaticMagicalTraditions,
|
||||
} from "../slices/databaseSlice.ts"
|
||||
|
||||
export type CombinedActiveMagicalTradition = {
|
||||
static: MagicalTradition
|
||||
@@ -28,7 +34,7 @@ export const selectActiveMagicalTraditions = createSelector(
|
||||
|
||||
return undefined
|
||||
})
|
||||
.filter(isNotNullish)
|
||||
.filter(isNotNullish),
|
||||
)
|
||||
|
||||
export type CombinedActiveBlessedTradition = {
|
||||
@@ -53,5 +59,5 @@ export const selectActiveBlessedTradition = createSelector(
|
||||
|
||||
return undefined
|
||||
})
|
||||
.find(isNotNullish)
|
||||
.find(isNotNullish),
|
||||
)
|
||||
|
||||
@@ -4,7 +4,11 @@ import { HairColor } from "optolith-database-schema/types/HairColor"
|
||||
import { Height, Weight } from "optolith-database-schema/types/Race"
|
||||
import { SocialStatus } from "optolith-database-schema/types/SocialStatus"
|
||||
import { isOptionActive } from "../../shared/domain/activatableEntry.ts"
|
||||
import { DisadvantageIdentifier, EyeColorIdentifier, HairColorIdentifier } from "../../shared/domain/identifier.ts"
|
||||
import {
|
||||
DisadvantageIdentifier,
|
||||
EyeColorIdentifier,
|
||||
HairColorIdentifier,
|
||||
} from "../../shared/domain/identifier.ts"
|
||||
import { filterNonNullable, unique } from "../../shared/utils/array.ts"
|
||||
import { compareAt, numAsc } from "../../shared/utils/compare.ts"
|
||||
import { createPropertySelector } from "../../shared/utils/redux.ts"
|
||||
@@ -20,15 +24,17 @@ export const selectAvailableSocialStatuses = createSelector(
|
||||
(currentCulture, socialStatusDependencies, socialStatuses): SocialStatus[] => {
|
||||
const minimumSocialStatus =
|
||||
socialStatusDependencies.length === 0
|
||||
? 1
|
||||
: Math.max(...socialStatusDependencies.map(dep => dep.id))
|
||||
? 1
|
||||
: Math.max(...socialStatusDependencies.map(dep => dep.id))
|
||||
|
||||
return Object.values(socialStatuses)
|
||||
.sort(compareAt(status => status.id, numAsc))
|
||||
.filter(status =>
|
||||
status.id >= minimumSocialStatus
|
||||
&& currentCulture?.social_status.some(ref => ref.id.social_status === status.id))
|
||||
}
|
||||
.filter(
|
||||
status =>
|
||||
status.id >= minimumSocialStatus &&
|
||||
currentCulture?.social_status.some(ref => ref.id.social_status === status.id),
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
const ALBINO = 1
|
||||
@@ -47,26 +53,24 @@ export const selectAvailableHairColorsIdDice = createSelector(
|
||||
isAlbino ? HairColorIdentifier.White : undefined,
|
||||
isGreenHaired ? HairColorIdentifier.Green : undefined,
|
||||
])
|
||||
}
|
||||
else if (currentRace === undefined) {
|
||||
} else if (currentRace === undefined) {
|
||||
return []
|
||||
} else {
|
||||
return (
|
||||
currentRaceVariant?.hair_color?.map(ref => ref.id.hair_color) ??
|
||||
(currentRace.variants.length === 1
|
||||
? currentRace.variants[0]!.hair_color.map(ref => ref.id.hair_color)
|
||||
: [])
|
||||
)
|
||||
}
|
||||
else {
|
||||
return currentRaceVariant?.hair_color?.map(ref => ref.id.hair_color)
|
||||
?? (
|
||||
currentRace.variant_dependent.tag === "Plain"
|
||||
? currentRace.variant_dependent.plain.hair_color.map(ref => ref.id.hair_color)
|
||||
: []
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
export const selectAvailableHairColors = createSelector(
|
||||
selectAvailableHairColorsIdDice,
|
||||
selectHairColors,
|
||||
(hairColorIds, hairColors): HairColor[] =>
|
||||
filterNonNullable(unique(hairColorIds).map(id => hairColors[id]))
|
||||
filterNonNullable(unique(hairColorIds).map(id => hairColors[id])),
|
||||
)
|
||||
|
||||
export const selectAvailableEyeColorsIdDice = createSelector(
|
||||
@@ -77,43 +81,39 @@ export const selectAvailableEyeColorsIdDice = createSelector(
|
||||
const isAlbino = isOptionActive(stigma, { type: "Generic", value: ALBINO })
|
||||
|
||||
if (isAlbino) {
|
||||
const eyeColorIds = [ EyeColorIdentifier.Red, EyeColorIdentifier.Purple ]
|
||||
const eyeColorIds = [EyeColorIdentifier.Red, EyeColorIdentifier.Purple]
|
||||
return eyeColorIds
|
||||
}
|
||||
else if (currentRace === undefined) {
|
||||
} else if (currentRace === undefined) {
|
||||
return []
|
||||
} else {
|
||||
return (
|
||||
currentRaceVariant?.eye_color?.map(ref => ref.id.eye_color) ??
|
||||
(currentRace.variants.length === 1
|
||||
? currentRace.variants[0]!.eye_color.map(ref => ref.id.eye_color)
|
||||
: [])
|
||||
)
|
||||
}
|
||||
else {
|
||||
return currentRaceVariant?.eye_color?.map(ref => ref.id.eye_color)
|
||||
?? (
|
||||
currentRace.variant_dependent.tag === "Plain"
|
||||
? currentRace.variant_dependent.plain.eye_color.map(ref => ref.id.eye_color)
|
||||
: []
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
export const selectAvailableEyeColors = createSelector(
|
||||
selectAvailableEyeColorsIdDice,
|
||||
selectEyeColors,
|
||||
(eyeColorIds, eyeColors): EyeColor[] =>
|
||||
filterNonNullable(unique(eyeColorIds).map(id => eyeColors[id]))
|
||||
filterNonNullable(unique(eyeColorIds).map(id => eyeColors[id])),
|
||||
)
|
||||
|
||||
export const selectRandomHeightCalculation = createSelector(
|
||||
selectCurrentRace,
|
||||
selectCurrentRaceVariant,
|
||||
(currentRace, currentRaceVariant): Height =>
|
||||
currentRaceVariant?.height
|
||||
?? (
|
||||
currentRace?.variant_dependent.tag === "Plain"
|
||||
? currentRace.variant_dependent.plain.height
|
||||
: { base: 0, random: [] }
|
||||
)
|
||||
currentRaceVariant?.height ??
|
||||
(currentRace?.variants.length === 1
|
||||
? currentRace.variants[0]!.height
|
||||
: { base: 0, random: [] }),
|
||||
)
|
||||
|
||||
export const selectRandomWeightCalculation = createSelector(
|
||||
selectCurrentRace,
|
||||
(currentRace): Weight => currentRace?.weight ?? { base: 0, random: [] }
|
||||
(currentRace): Weight => currentRace?.weight ?? { base: 0, random: [] },
|
||||
)
|
||||
|
||||
@@ -1,10 +1,31 @@
|
||||
import { createSelector } from "@reduxjs/toolkit"
|
||||
import { Skill } from "optolith-database-schema/types/Skill"
|
||||
import { AdvantageIdentifier } from "../../shared/domain/identifier.ts"
|
||||
import { filterApplyingRatedDependencies } from "../../shared/domain/dependencies/filterApplyingDependencies.ts"
|
||||
import {
|
||||
AdvantageIdentifier,
|
||||
GeneralSpecialAbilityIdentifier,
|
||||
} from "../../shared/domain/identifier.ts"
|
||||
import { Rated } from "../../shared/domain/ratedEntry.ts"
|
||||
import { getSkillCommonness, getSkillMaximum, getSkillMinimum, isSkillDecreasable, isSkillIncreasable } from "../../shared/domain/skill.ts"
|
||||
import {
|
||||
getSkillCommonness,
|
||||
getSkillMaximum,
|
||||
getSkillMinimum,
|
||||
isSkillDecreasable,
|
||||
isSkillIncreasable,
|
||||
} from "../../shared/domain/skill.ts"
|
||||
import { createPropertySelector } from "../../shared/utils/redux.ts"
|
||||
import { selectAdvantages, selectAttributes, selectSkills as selectDynamicSkills } from "../slices/characterSlice.ts"
|
||||
import {
|
||||
selectAdvantages,
|
||||
selectAttributes,
|
||||
selectCeremonies,
|
||||
selectCloseCombatTechniques,
|
||||
selectSkills as selectDynamicSkills,
|
||||
selectGeneralSpecialAbilities,
|
||||
selectLiturgicalChants,
|
||||
selectRangedCombatTechniques,
|
||||
selectRituals,
|
||||
selectSpells,
|
||||
} from "../slices/characterSlice.ts"
|
||||
import { selectSkills as selectStaticSkills } from "../slices/databaseSlice.ts"
|
||||
import { createInitialDynamicSkill } from "../slices/skillsSlice.ts"
|
||||
import { selectCanRemove, selectIsInCharacterCreation } from "./characterSelectors.ts"
|
||||
@@ -29,7 +50,17 @@ export const selectVisibleSkills = createSelector(
|
||||
selectStartExperienceLevel,
|
||||
selectCanRemove,
|
||||
createPropertySelector(selectAdvantages, AdvantageIdentifier.ExceptionalSkill),
|
||||
createPropertySelector(
|
||||
selectGeneralSpecialAbilities,
|
||||
GeneralSpecialAbilityIdentifier.CraftInstruments,
|
||||
),
|
||||
selectCurrentCulture,
|
||||
selectCloseCombatTechniques,
|
||||
selectRangedCombatTechniques,
|
||||
selectSpells,
|
||||
selectRituals,
|
||||
selectLiturgicalChants,
|
||||
selectCeremonies,
|
||||
(
|
||||
skills,
|
||||
dynamicSkills,
|
||||
@@ -38,41 +69,53 @@ export const selectVisibleSkills = createSelector(
|
||||
startExperienceLevel,
|
||||
canRemove,
|
||||
exceptionalSkill,
|
||||
craftInstruments,
|
||||
culture,
|
||||
): DisplayedSkill[] =>
|
||||
Object.values(skills)
|
||||
.sort((a, b) => a.id - b.id)
|
||||
.map(skill => {
|
||||
const dynamicSkill =
|
||||
dynamicSkills[skill.id] ?? createInitialDynamicSkill(skill.id)
|
||||
closeCombatTechniques,
|
||||
rangedCombatTechniques,
|
||||
spells,
|
||||
rituals,
|
||||
liturgicalChants,
|
||||
ceremonies,
|
||||
): DisplayedSkill[] => {
|
||||
const filterApplyingDependencies = filterApplyingRatedDependencies({
|
||||
attributes,
|
||||
skills: dynamicSkills,
|
||||
closeCombatTechniques,
|
||||
rangedCombatTechniques,
|
||||
spells,
|
||||
rituals,
|
||||
liturgicalChants,
|
||||
ceremonies,
|
||||
})
|
||||
|
||||
const minimum = getSkillMinimum()
|
||||
return Object.values(skills).map(skill => {
|
||||
const dynamicSkill = dynamicSkills[skill.id] ?? createInitialDynamicSkill(skill.id)
|
||||
|
||||
const maximum = getSkillMaximum(
|
||||
attributes,
|
||||
skill,
|
||||
isInCharacterCreation,
|
||||
startExperienceLevel,
|
||||
exceptionalSkill,
|
||||
)
|
||||
const minimum = getSkillMinimum(
|
||||
dynamicSkills,
|
||||
dynamicSkill,
|
||||
craftInstruments,
|
||||
filterApplyingDependencies,
|
||||
)
|
||||
|
||||
return {
|
||||
static: skill,
|
||||
dynamic: dynamicSkill,
|
||||
minimum,
|
||||
maximum,
|
||||
isDecreasable:
|
||||
isSkillDecreasable(
|
||||
dynamicSkill,
|
||||
minimum,
|
||||
canRemove,
|
||||
),
|
||||
isIncreasable:
|
||||
isSkillIncreasable(
|
||||
dynamicSkill,
|
||||
maximum,
|
||||
),
|
||||
commonness: culture === undefined ? undefined : getSkillCommonness(culture, skill),
|
||||
}
|
||||
})
|
||||
const maximum = getSkillMaximum(
|
||||
attributes,
|
||||
skill,
|
||||
isInCharacterCreation,
|
||||
startExperienceLevel,
|
||||
exceptionalSkill,
|
||||
)
|
||||
|
||||
return {
|
||||
static: skill,
|
||||
dynamic: dynamicSkill,
|
||||
minimum,
|
||||
maximum,
|
||||
isDecreasable: isSkillDecreasable(dynamicSkill, minimum, canRemove),
|
||||
isIncreasable: isSkillIncreasable(dynamicSkill, maximum),
|
||||
commonness: culture === undefined ? undefined : getSkillCommonness(culture, skill),
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
/* eslint-disable max-len */
|
||||
import { AnyAction, createAction } from "@reduxjs/toolkit"
|
||||
import { Draft } from "immer"
|
||||
import { ActivatableRated, ActivatableRatedWithEnhancements, Rated } from "../../shared/domain/ratedEntry.ts"
|
||||
import { ActivatableMap } from "../../shared/domain/activatableEntry.ts"
|
||||
import { ActivatableRatedMap, ActivatableRatedWithEnhancementsMap, RatedMap } from "../../shared/domain/ratedEntry.ts"
|
||||
import { Sex } from "../../shared/domain/sex.ts"
|
||||
import { createImmerReducer, reduceReducers } from "../../shared/utils/redux.ts"
|
||||
import { RootState } from "../store.ts"
|
||||
import { attributesReducer } from "./attributesSlice.ts"
|
||||
import { closeCombatTechniquesReducer } from "./closeCombatTechniqueSlice.ts"
|
||||
import { DatabaseState } from "./databaseSlice.ts"
|
||||
import { derivedCharacteristicsReducer } from "./derivedCharacteristicsSlice.ts"
|
||||
import { personalDataReducer } from "./personalDataSlice.ts"
|
||||
import { professionReducer } from "./professionSlice.ts"
|
||||
import { raceReducer } from "./raceSlice.ts"
|
||||
import { rangedCombatTechniquesReducer } from "./rangedCombatTechniqueSlice.ts"
|
||||
import { rulesReducer } from "./rulesSlice.ts"
|
||||
import { skillsReducer } from "./skillsSlice.ts"
|
||||
|
||||
@@ -355,65 +358,6 @@ export type ActiveOptionalRule = {
|
||||
options?: number[]
|
||||
}
|
||||
|
||||
/**
|
||||
* The character's sex. It does not have to be binary, although it always must be specified how to handle it in the context of binary sex prerequisites. You can also provide a custom sex with a custom name.
|
||||
*/
|
||||
export type Sex =
|
||||
| BinarySex
|
||||
| NonBinarySex
|
||||
| CustomSex
|
||||
|
||||
/**
|
||||
* A binary sex option.
|
||||
*/
|
||||
export type BinarySex = {
|
||||
type: "Male" | "Female"
|
||||
}
|
||||
|
||||
/**
|
||||
* A non-binary sex option.
|
||||
*/
|
||||
export type NonBinarySex ={
|
||||
type: "BalThani" | "Tsajana"
|
||||
|
||||
/**
|
||||
* Defines how a non-binary sex should be treated when checking prerequisites.
|
||||
*/
|
||||
binaryHandling: BinaryHandling
|
||||
}
|
||||
|
||||
/**
|
||||
* A custom non-binary sex option.
|
||||
*/
|
||||
export type CustomSex = {
|
||||
type: "Custom"
|
||||
|
||||
/**
|
||||
* The custom sex name.
|
||||
*/
|
||||
name: string
|
||||
|
||||
/**
|
||||
* Defines how a non-binary sex should be treated when checking prerequisites.
|
||||
*/
|
||||
binaryHandling: BinaryHandling
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines how a non-binary sex should be treated when checking prerequisites.
|
||||
*/
|
||||
export type BinaryHandling = {
|
||||
/**
|
||||
* Defines if the sex should be treated as male when checking prerequisites.
|
||||
*/
|
||||
asMale: boolean
|
||||
|
||||
/**
|
||||
* Defines if the sex should be treated as female when checking prerequisites.
|
||||
*/
|
||||
asFemale: boolean
|
||||
}
|
||||
|
||||
export type SocialStatusDependency = {
|
||||
id: number
|
||||
}
|
||||
@@ -475,18 +419,6 @@ export type EnergyWithBuyBack = {
|
||||
permanentlyLostBoughtBack: number
|
||||
}
|
||||
|
||||
export type RatedMap = {
|
||||
[id: number]: Rated
|
||||
}
|
||||
|
||||
export type ActivatableRatedMap = {
|
||||
[id: number]: ActivatableRated
|
||||
}
|
||||
|
||||
export type ActivatableRatedWithEnhancementsMap = {
|
||||
[id: number]: ActivatableRatedWithEnhancements
|
||||
}
|
||||
|
||||
export type TinyActivatableSet = number[]
|
||||
|
||||
/**
|
||||
@@ -523,96 +455,6 @@ export type Purse = {
|
||||
ducats: number
|
||||
}
|
||||
|
||||
export type Activatable = {
|
||||
/**
|
||||
* The activatable identifier.
|
||||
* @integer
|
||||
*/
|
||||
id: number
|
||||
|
||||
/**
|
||||
* One or multiple activations of the activatable.
|
||||
*/
|
||||
instances: {
|
||||
/**
|
||||
* One or multiple options for the activatable. The meaning depends on the activatable.
|
||||
* @minItems 1
|
||||
*/
|
||||
options?: ActivatableOption[]
|
||||
|
||||
/**
|
||||
* The instance level (if the activatable has levels).
|
||||
*/
|
||||
level?: number
|
||||
|
||||
/**
|
||||
* If provided, a custom adventure points value has been set for this instance.
|
||||
*/
|
||||
customAdventurePointsValue?: number
|
||||
}[]
|
||||
}
|
||||
|
||||
export type ActivatableOption =
|
||||
| PredefinedActivatableOption
|
||||
| CustomActivatableOption
|
||||
|
||||
export type PredefinedActivatableOption = {
|
||||
type: "Predefined"
|
||||
|
||||
/**
|
||||
* 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"
|
||||
| "MeleeCombatTechnique"
|
||||
| "RangedCombatTechnique"
|
||||
| "LiturgicalChant"
|
||||
| "Ceremony"
|
||||
| "Spell"
|
||||
| "Ritual"
|
||||
|
||||
/**
|
||||
* The numeric identifier.
|
||||
*/
|
||||
value: number
|
||||
}
|
||||
}
|
||||
|
||||
export type CustomActivatableOption = {
|
||||
type: "Custom"
|
||||
|
||||
/**
|
||||
* A user-entered text.
|
||||
*/
|
||||
value: string
|
||||
}
|
||||
|
||||
export type ActivatableMap = {
|
||||
[id: number]: Activatable
|
||||
}
|
||||
|
||||
const staticInitialState: Omit<CharacterState, "dateCreated" | "dateLastModified"> = {
|
||||
id: "550e8400-e29b-11d4-a716-446655440000",
|
||||
version: undefined,
|
||||
@@ -867,6 +709,12 @@ export const selectPurchasedKarmaPoints = (state: RootState) => selectCurrentCha
|
||||
export const selectKarmaPointsPermanentlyLost = (state: RootState) => selectCurrentCharacter(state)?.derivedCharacteristics.karmaPoints.permanentlyLost ?? 0
|
||||
export const selectKarmaPointsPermanentlyLostBoughtBack = (state: RootState) => selectCurrentCharacter(state)?.derivedCharacteristics.karmaPoints.permanentlyLostBoughtBack ?? 0
|
||||
export const selectSkills = (state: RootState) => selectCurrentCharacter(state)?.skills ?? {}
|
||||
export const selectCloseCombatTechniques = (state: RootState) => selectCurrentCharacter(state)?.combatTechniques.close ?? {}
|
||||
export const selectRangedCombatTechniques = (state: RootState) => selectCurrentCharacter(state)?.combatTechniques.ranged ?? {}
|
||||
export const selectSpells = (state: RootState) => selectCurrentCharacter(state)?.spells ?? {}
|
||||
export const selectRituals = (state: RootState) => selectCurrentCharacter(state)?.rituals ?? {}
|
||||
export const selectLiturgicalChants = (state: RootState) => selectCurrentCharacter(state)?.liturgicalChants ?? {}
|
||||
export const selectCeremonies = (state: RootState) => selectCurrentCharacter(state)?.ceremonies ?? {}
|
||||
|
||||
export const setName = createAction<string>("character/setName")
|
||||
export const setAvatar = createAction<string>("character/setAvatar")
|
||||
@@ -900,4 +748,6 @@ export const characterReducer =
|
||||
professionReducer,
|
||||
rulesReducer,
|
||||
skillsReducer,
|
||||
closeCombatTechniquesReducer,
|
||||
rangedCombatTechniquesReducer,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { ImprovementCost, fromRaw } from "../../shared/domain/adventurePoints/improvementCost.ts"
|
||||
import { createRatedSlice } from "./ratedSlice.ts"
|
||||
|
||||
const {
|
||||
create,
|
||||
createInitial,
|
||||
getValue,
|
||||
actions: { incrementAction, decrementAction },
|
||||
reducer,
|
||||
} = createRatedSlice({
|
||||
namespace: "combatTechniques/close",
|
||||
entityName: "CloseCombatTechnique",
|
||||
getState: state => state.combatTechniques.close,
|
||||
minValue: 6,
|
||||
getImprovementCost: (id, database) =>
|
||||
fromRaw(database.closeCombatTechniques[id]?.improvement_cost) ?? ImprovementCost.D,
|
||||
})
|
||||
|
||||
export {
|
||||
reducer as closeCombatTechniquesReducer,
|
||||
create as createDynamicCloseCombatTechnique,
|
||||
createInitial as createInitialDynamicCloseCombatTechnique,
|
||||
decrementAction as decrementCloseCombatTechnique,
|
||||
getValue as getCloseCombatTechniqueValue,
|
||||
incrementAction as incrementCloseCombatTechnique,
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { ImprovementCost, fromRaw } from "../../shared/domain/adventurePoints/improvementCost.ts"
|
||||
import { createRatedSlice } from "./ratedSlice.ts"
|
||||
|
||||
const {
|
||||
create,
|
||||
createInitial,
|
||||
getValue,
|
||||
actions: { incrementAction, decrementAction },
|
||||
reducer,
|
||||
} = createRatedSlice({
|
||||
namespace: "combatTechniques/ranged",
|
||||
entityName: "RangedCombatTechnique",
|
||||
getState: state => state.combatTechniques.ranged,
|
||||
minValue: 6,
|
||||
getImprovementCost: (id, database) =>
|
||||
fromRaw(database.rangedCombatTechniques[id]?.improvement_cost) ?? ImprovementCost.D,
|
||||
})
|
||||
|
||||
export {
|
||||
create as createDynamicRangedCombatTechnique,
|
||||
createInitial as createInitialDynamicRangedCombatTechnique,
|
||||
decrementAction as decrementRangedCombatTechnique,
|
||||
getValue as getRangedCombatTechniqueValue,
|
||||
incrementAction as incrementRangedCombatTechnique,
|
||||
reducer as rangedCombatTechniquesReducer,
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
import { ActionCreatorWithPayload, AnyAction, Draft, createAction } from "@reduxjs/toolkit"
|
||||
import { ImprovementCost } from "../../shared/domain/adventurePoints/improvementCost.ts"
|
||||
import { BoundAdventurePoints, cachedAdventurePoints } from "../../shared/domain/adventurePoints/ratedEntry.ts"
|
||||
import { Dependency, Rated, RatedValue } from "../../shared/domain/ratedEntry.ts"
|
||||
import {
|
||||
BoundAdventurePoints,
|
||||
cachedAdventurePoints,
|
||||
} from "../../shared/domain/adventurePoints/ratedEntry.ts"
|
||||
import { Dependency, Rated, RatedMap, RatedValue } from "../../shared/domain/ratedEntry.ts"
|
||||
import { Reducer, createImmerReducer } from "../../shared/utils/redux.ts"
|
||||
import { CharacterState, RatedMap } from "./characterSlice.ts"
|
||||
import { CharacterState } from "./characterSlice.ts"
|
||||
import { DatabaseState } from "./databaseSlice.ts"
|
||||
|
||||
export type RatedSlice<N extends string, E extends string> = {
|
||||
@@ -80,60 +83,59 @@ export const createRatedSlice = <N extends string, E extends string>(config: {
|
||||
database,
|
||||
id,
|
||||
value,
|
||||
{
|
||||
dependencies = [],
|
||||
boundAdventurePoints = [],
|
||||
} = {},
|
||||
{ dependencies = [], boundAdventurePoints = [] } = {},
|
||||
) =>
|
||||
updateCachedAdventurePoints({
|
||||
id,
|
||||
value: Math.max(config.minValue, value),
|
||||
cachedAdventurePoints: {
|
||||
general: 0,
|
||||
bound: 0,
|
||||
updateCachedAdventurePoints(
|
||||
{
|
||||
id,
|
||||
value: Math.max(config.minValue, value),
|
||||
cachedAdventurePoints: {
|
||||
general: 0,
|
||||
bound: 0,
|
||||
},
|
||||
dependencies,
|
||||
boundAdventurePoints,
|
||||
},
|
||||
dependencies,
|
||||
boundAdventurePoints,
|
||||
}, database)
|
||||
database,
|
||||
)
|
||||
|
||||
const createInitial: RatedSlice<N, E>["createInitial"] = (
|
||||
id,
|
||||
{
|
||||
dependencies = [],
|
||||
boundAdventurePoints = [],
|
||||
} = {},
|
||||
) =>
|
||||
({
|
||||
id,
|
||||
value: config.minValue,
|
||||
cachedAdventurePoints: {
|
||||
general: 0,
|
||||
bound: 0,
|
||||
},
|
||||
dependencies,
|
||||
boundAdventurePoints,
|
||||
})
|
||||
{ dependencies = [], boundAdventurePoints = [] } = {},
|
||||
) => ({
|
||||
id,
|
||||
value: config.minValue,
|
||||
cachedAdventurePoints: {
|
||||
general: 0,
|
||||
bound: 0,
|
||||
},
|
||||
dependencies,
|
||||
boundAdventurePoints,
|
||||
})
|
||||
|
||||
const getValue: RatedSlice<N, E>["getValue"] = entry => entry?.value ?? config.minValue
|
||||
|
||||
const incrementAction = createAction<number, `${N}/increment${E}`>(`${config.namespace}/increment${config.entityName}`)
|
||||
const decrementAction = createAction<number, `${N}/decrement${E}`>(`${config.namespace}/decrement${config.entityName}`)
|
||||
const incrementAction = createAction<number, `${N}/increment${E}`>(
|
||||
`${config.namespace}/increment${config.entityName}`,
|
||||
)
|
||||
const decrementAction = createAction<number, `${N}/decrement${E}`>(
|
||||
`${config.namespace}/decrement${config.entityName}`,
|
||||
)
|
||||
|
||||
const reducer = createImmerReducer(
|
||||
(state: Draft<CharacterState>, action, database: DatabaseState) => {
|
||||
if (incrementAction.match(action)) {
|
||||
const entry = config.getState(state)[action.payload] ??= createInitial(action.payload)
|
||||
const entry = (config.getState(state)[action.payload] ??= createInitial(action.payload))
|
||||
entry.value++
|
||||
updateCachedAdventurePoints(entry, database)
|
||||
}
|
||||
else if (decrementAction.match(action)) {
|
||||
} else if (decrementAction.match(action)) {
|
||||
const entry = config.getState(state)[action.payload]
|
||||
if (entry !== undefined && entry.value > config.minValue) {
|
||||
entry.value--
|
||||
updateCachedAdventurePoints(entry, database)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,6 +1,93 @@
|
||||
import { Activatable, PredefinedActivatableOption } from "../../main_window/slices/characterSlice.ts"
|
||||
import { Equality } from "../utils/compare.ts"
|
||||
|
||||
export type Activatable = {
|
||||
/**
|
||||
* The activatable identifier.
|
||||
* @integer
|
||||
*/
|
||||
id: number
|
||||
|
||||
/**
|
||||
* One or multiple activations of the activatable.
|
||||
*/
|
||||
instances: {
|
||||
/**
|
||||
* One or multiple options for the activatable. The meaning depends on the activatable.
|
||||
* @minItems 1
|
||||
*/
|
||||
options?: ActivatableOption[]
|
||||
|
||||
/**
|
||||
* The instance level (if the activatable has levels).
|
||||
*/
|
||||
level?: number
|
||||
|
||||
/**
|
||||
* If provided, a custom adventure points value has been set for this instance.
|
||||
*/
|
||||
customAdventurePointsValue?: number
|
||||
}[]
|
||||
}
|
||||
|
||||
export type ActivatableOption = PredefinedActivatableOption | CustomActivatableOption
|
||||
|
||||
export type PredefinedActivatableOption = {
|
||||
type: "Predefined"
|
||||
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
}
|
||||
|
||||
export type CustomActivatableOption = {
|
||||
type: "Custom"
|
||||
|
||||
/**
|
||||
* A user-entered text.
|
||||
*/
|
||||
value: string
|
||||
}
|
||||
|
||||
export type ActivatableMap = {
|
||||
[id: number]: Activatable
|
||||
}
|
||||
|
||||
const equalOptionId: Equality<PredefinedActivatableOption["id"]> = (a, b) =>
|
||||
a.type === b.type && a.value === b.value
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Activatable } from "../../main_window/slices/characterSlice.ts"
|
||||
import { firstLevel, isActive } from "./activatableEntry.ts"
|
||||
import { Activatable, firstLevel, isActive } from "./activatableEntry.ts"
|
||||
|
||||
/**
|
||||
* There are pairs of entries that are mutually exclusive and modify a certain
|
||||
@@ -9,8 +8,7 @@ import { firstLevel, isActive } from "./activatableEntry.ts"
|
||||
export const modifierByLevel = (
|
||||
incrementor: Activatable | undefined,
|
||||
decrementor: Activatable | undefined,
|
||||
): number =>
|
||||
firstLevel(incrementor) - firstLevel(decrementor)
|
||||
): number => firstLevel(incrementor) - firstLevel(decrementor)
|
||||
|
||||
/**
|
||||
* There are pairs of entries that are mutually exclusive and modify a certain
|
||||
@@ -21,8 +19,7 @@ export const modifierByLevel = (
|
||||
export const modifierByIsActive = (
|
||||
incrementor: Activatable | undefined,
|
||||
decrementor: Activatable | undefined,
|
||||
): number =>
|
||||
isActive(incrementor) ? 1 : isActive(decrementor) ? -1 : 0
|
||||
): number => (isActive(incrementor) ? 1 : isActive(decrementor) ? -1 : 0)
|
||||
|
||||
const countActive = (activatables: (Activatable | undefined)[]) =>
|
||||
activatables.reduce((acc, entry) => acc + (isActive(entry) ? 1 : 0), 0)
|
||||
@@ -35,5 +32,4 @@ const countActive = (activatables: (Activatable | undefined)[]) =>
|
||||
export const modifierByIsActives = (
|
||||
incrementors: (Activatable | undefined)[],
|
||||
decrementors: (Activatable | undefined)[],
|
||||
): number =>
|
||||
countActive(incrementors) - countActive(decrementors)
|
||||
): number => countActive(incrementors) - countActive(decrementors)
|
||||
|
||||
@@ -13,12 +13,18 @@ export enum ImprovementCost {
|
||||
|
||||
const adventureCostBase = (ic: ImprovementCost): number => {
|
||||
switch (ic) {
|
||||
case ImprovementCost.A: return 1
|
||||
case ImprovementCost.B: return 2
|
||||
case ImprovementCost.C: return 3
|
||||
case ImprovementCost.D: return 4
|
||||
case ImprovementCost.E: return 15
|
||||
default: return assertExhaustive(ic)
|
||||
case ImprovementCost.A:
|
||||
return 1
|
||||
case ImprovementCost.B:
|
||||
return 2
|
||||
case ImprovementCost.C:
|
||||
return 3
|
||||
case ImprovementCost.D:
|
||||
return 4
|
||||
case ImprovementCost.E:
|
||||
return 15
|
||||
default:
|
||||
return assertExhaustive(ic)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,9 +33,12 @@ const constantThresholdValue = (ic: ImprovementCost): number => {
|
||||
case ImprovementCost.A:
|
||||
case ImprovementCost.B:
|
||||
case ImprovementCost.C:
|
||||
case ImprovementCost.D: return 12
|
||||
case ImprovementCost.E: return 14
|
||||
default: return assertExhaustive(ic)
|
||||
case ImprovementCost.D:
|
||||
return 12
|
||||
case ImprovementCost.E:
|
||||
return 14
|
||||
default:
|
||||
return assertExhaustive(ic)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +56,7 @@ const adventurePointsValue = (ic: ImprovementCost, value: number): number =>
|
||||
export const adventurePointsForRange = (
|
||||
ic: ImprovementCost,
|
||||
fromValue: number,
|
||||
toValue: number
|
||||
toValue: number,
|
||||
): number => {
|
||||
if (fromValue === toValue) {
|
||||
return 0
|
||||
@@ -89,12 +98,18 @@ export const adventurePointsForActivation = adventureCostBase
|
||||
export const compareImprovementCost = (ic1: ImprovementCost, ic2: ImprovementCost): number => {
|
||||
const toInt = (ic: ImprovementCost): number => {
|
||||
switch (ic) {
|
||||
case ImprovementCost.A: return 1
|
||||
case ImprovementCost.B: return 2
|
||||
case ImprovementCost.C: return 3
|
||||
case ImprovementCost.D: return 4
|
||||
case ImprovementCost.E: return 5
|
||||
default: return assertExhaustive(ic)
|
||||
case ImprovementCost.A:
|
||||
return 1
|
||||
case ImprovementCost.B:
|
||||
return 2
|
||||
case ImprovementCost.C:
|
||||
return 3
|
||||
case ImprovementCost.D:
|
||||
return 4
|
||||
case ImprovementCost.E:
|
||||
return 5
|
||||
default:
|
||||
return assertExhaustive(ic)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,24 +126,36 @@ export const equals = (ic1: ImprovementCost, ic2: ImprovementCost): boolean => i
|
||||
*/
|
||||
export const toString = (ic: ImprovementCost): string => {
|
||||
switch (ic) {
|
||||
case ImprovementCost.A: return "A"
|
||||
case ImprovementCost.B: return "B"
|
||||
case ImprovementCost.C: return "C"
|
||||
case ImprovementCost.D: return "D"
|
||||
case ImprovementCost.E: return "E"
|
||||
default: return assertExhaustive(ic)
|
||||
case ImprovementCost.A:
|
||||
return "A"
|
||||
case ImprovementCost.B:
|
||||
return "B"
|
||||
case ImprovementCost.C:
|
||||
return "C"
|
||||
case ImprovementCost.D:
|
||||
return "D"
|
||||
case ImprovementCost.E:
|
||||
return "E"
|
||||
default:
|
||||
return assertExhaustive(ic)
|
||||
}
|
||||
}
|
||||
|
||||
export const fromRaw = <T extends RawImprovementCost | undefined>(
|
||||
ic: T
|
||||
ic: T,
|
||||
): ImprovementCost | Nullish<T> => {
|
||||
switch (ic) {
|
||||
case "A": return ImprovementCost.A
|
||||
case "B": return ImprovementCost.B
|
||||
case "C": return ImprovementCost.C
|
||||
case "D": return ImprovementCost.D
|
||||
case undefined: return undefined as Nullish<T>
|
||||
default: return assertExhaustive(ic)
|
||||
case "A":
|
||||
return ImprovementCost.A
|
||||
case "B":
|
||||
return ImprovementCost.B
|
||||
case "C":
|
||||
return ImprovementCost.C
|
||||
case "D":
|
||||
return ImprovementCost.D
|
||||
case undefined:
|
||||
return undefined as Nullish<T>
|
||||
default:
|
||||
return assertExhaustive(ic)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,15 +36,12 @@ export type RatedAdventurePointsCache = {
|
||||
}
|
||||
|
||||
const groupBoundAdventurePointsByRating = (
|
||||
boundAdventurePoints: BoundAdventurePoints[]
|
||||
boundAdventurePoints: BoundAdventurePoints[],
|
||||
): ReadonlyMap<number | "activation", number> =>
|
||||
boundAdventurePoints.reduce(
|
||||
(map, { rating: boundRating, adventurePoints }) => {
|
||||
const key = boundRating ?? "activation"
|
||||
return map.set(key, (map.get(key) ?? 0) + adventurePoints)
|
||||
},
|
||||
new Map<number | "activation", number>(),
|
||||
)
|
||||
boundAdventurePoints.reduce((map, { rating: boundRating, adventurePoints }) => {
|
||||
const key = boundRating ?? "activation"
|
||||
return map.set(key, (map.get(key) ?? 0) + adventurePoints)
|
||||
}, new Map<number | "activation", number>())
|
||||
|
||||
const accumulateCache = (
|
||||
startValue: number,
|
||||
@@ -59,9 +56,8 @@ const accumulateCache = (
|
||||
const usedBoundForStep = Math.min(costForStep, acc.remainingApplicableBound)
|
||||
const usedGeneralForStep = costForStep - usedBoundForStep
|
||||
|
||||
const newRemainingBound = acc.remainingApplicableBound
|
||||
- usedBoundForStep
|
||||
+ (boundByValue.get(currentValue) ?? 0)
|
||||
const newRemainingBound =
|
||||
acc.remainingApplicableBound - usedBoundForStep + (boundByValue.get(currentValue) ?? 0)
|
||||
|
||||
return {
|
||||
usedGeneral: acc.usedGeneral + usedGeneralForStep,
|
||||
@@ -72,8 +68,8 @@ const accumulateCache = (
|
||||
{
|
||||
usedGeneral: 0,
|
||||
usedBound: 0,
|
||||
remainingApplicableBound: (boundByValue.get(initialApplicableBoundKey) ?? 0),
|
||||
}
|
||||
remainingApplicableBound: boundByValue.get(initialApplicableBoundKey) ?? 0,
|
||||
},
|
||||
)
|
||||
|
||||
return {
|
||||
@@ -98,8 +94,7 @@ export const cachedAdventurePoints = (
|
||||
general: 0,
|
||||
bound: 0,
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
const boundByValue = groupBoundAdventurePointsByRating(boundAdventurePoints)
|
||||
return accumulateCache(minValue + 1, value, minValue, boundByValue, ic)
|
||||
}
|
||||
@@ -120,8 +115,7 @@ export const cachedAdventurePointsForActivatable = (
|
||||
general: 0,
|
||||
bound: 0,
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
const boundByValue = groupBoundAdventurePointsByRating(boundAdventurePoints)
|
||||
return accumulateCache(0, value, "activation", boundByValue, ic)
|
||||
}
|
||||
|
||||
@@ -1,58 +1,43 @@
|
||||
import { ExperienceLevel } from "optolith-database-schema/types/ExperienceLevel"
|
||||
import { Race } from "optolith-database-schema/types/Race"
|
||||
import { CharacterState } from "../../main_window/slices/characterSlice.ts"
|
||||
import { filterNonNullable } from "../utils/array.ts"
|
||||
import { mapNullable } from "../utils/nullable.ts"
|
||||
import { Energy, EnergyWithBuyBack } from "./energy.ts"
|
||||
import { AttributeIdentifier } from "./identifier.ts"
|
||||
import { Rated } from "./ratedEntry.ts"
|
||||
import { Dependency, Rated, flattenMinimumRestrictions } from "./ratedEntry.ts"
|
||||
|
||||
export const getAttributeValue = (dynamic: Rated | undefined): number => dynamic?.value ?? 8
|
||||
|
||||
export const getAttributeMinimum = (
|
||||
characterDerivedCharacteristics: CharacterState["derivedCharacteristics"],
|
||||
dynamic: Rated,
|
||||
lifePoints: Energy,
|
||||
arcaneEnergy: EnergyWithBuyBack,
|
||||
karmaPoints: EnergyWithBuyBack,
|
||||
dynamicAttribute: Rated,
|
||||
singleHighestMagicalPrimaryAttributeId: number | undefined,
|
||||
magicalPrimaryAttributeDependencies: Dependency[],
|
||||
blessedPrimaryAttributeId: number | undefined,
|
||||
blessedPrimaryAttributeDependencies: Dependency[],
|
||||
filterApplyingDependencies: (dependencies: Dependency[]) => Dependency[],
|
||||
getSkillCheckAttributeMinimum: (id: number) => number | undefined,
|
||||
): number => {
|
||||
// (wiki: StaticDataRecord) =>
|
||||
// (hero: HeroModelRecord) =>
|
||||
// (mblessed_primary_attr: Maybe<Record<AttributeCombined>>) =>
|
||||
// (mhighest_magical_primary_attr: Maybe<Record<AttributeCombined>>) =>
|
||||
const isConstitution = dynamicAttribute.id === AttributeIdentifier.Constitution
|
||||
const isHighestMagicalPrimaryAttribute =
|
||||
dynamicAttribute.id === singleHighestMagicalPrimaryAttributeId
|
||||
const isBlessedPrimaryAttribute = dynamicAttribute.id === blessedPrimaryAttributeId
|
||||
|
||||
const isConstitution = dynamic.id === AttributeIdentifier.Constitution
|
||||
const minimumValues: number[][] = [
|
||||
[8],
|
||||
flattenMinimumRestrictions(filterApplyingDependencies(dynamicAttribute.dependencies)),
|
||||
isConstitution ? [lifePoints.purchased] : [],
|
||||
isHighestMagicalPrimaryAttribute
|
||||
? [arcaneEnergy.purchased, ...flattenMinimumRestrictions(magicalPrimaryAttributeDependencies)]
|
||||
: [],
|
||||
isBlessedPrimaryAttribute
|
||||
? [karmaPoints.purchased, ...flattenMinimumRestrictions(blessedPrimaryAttributeDependencies)]
|
||||
: [],
|
||||
mapNullable(getSkillCheckAttributeMinimum(dynamicAttribute.id), min => [min]) ?? [],
|
||||
]
|
||||
|
||||
// const isHighestMagicalPrimaryAttribute =
|
||||
// Maybe.elem (AtDA.id (hero_entry)) (fmap (ACA_.id) (mhighest_magical_primary_attr))
|
||||
|
||||
// const isBlessedPrimaryAttribute =
|
||||
// Maybe.elem (AtDA.id (hero_entry)) (fmap (ACA_.id) (mblessed_primary_attr))
|
||||
|
||||
// const blessedPrimaryAttributeDependencies = HA.blessedPrimaryAttributeDependencies (hero)
|
||||
|
||||
// const magicalPrimaryAttributeDependencies = HA.magicalPrimaryAttributeDependencies (hero)
|
||||
|
||||
const minimumValues = filterNonNullable([
|
||||
8,
|
||||
// ...flattenDependencies (wiki) (hero) (AtDA.dependencies (hero_entry)),
|
||||
isConstitution ? characterDerivedCharacteristics.lifePoints.purchased : undefined,
|
||||
// ...(isHighestMagicalPrimaryAttribute
|
||||
// ? [ sel2 (added), ...magicalPrimaryAttributeDependencies.map (x => x.minValue) ]
|
||||
// : []),
|
||||
// ...(isBlessedPrimaryAttribute
|
||||
// ? [ sel3 (added), ...blessedPrimaryAttributeDependencies.map (x => x.minValue) ]
|
||||
// : []),
|
||||
// fromMaybe (8)
|
||||
// (getSkillCheckAttributeMinimum (
|
||||
// SDA.skills (wiki),
|
||||
// SDA.combatTechniques (wiki),
|
||||
// SDA.spells (wiki),
|
||||
// SDA.liturgicalChants (wiki),
|
||||
// HA.attributes (hero),
|
||||
// HA.skills (hero),
|
||||
// HA.combatTechniques (hero),
|
||||
// HA.spells (hero),
|
||||
// HA.liturgicalChants (hero),
|
||||
// HA.skillCheckAttributeCache (hero),
|
||||
// AtDA.id (hero_entry),
|
||||
// )),
|
||||
])
|
||||
|
||||
return Math.max(...minimumValues)
|
||||
return Math.max(...minimumValues.flat())
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,16 +45,14 @@ export const getAttributeMinimum = (
|
||||
* race `race`
|
||||
*/
|
||||
const getModIfSelectedAdjustment = (id: number, race: Race) =>
|
||||
race.attribute_adjustments
|
||||
.find(adjustment =>
|
||||
adjustment.list.length > 1
|
||||
&& adjustment.list.some(attribute => attribute.id.attribute === id))
|
||||
?.value ?? 0
|
||||
race.attribute_adjustments.selectable?.find(adjustment =>
|
||||
adjustment.list.some(attribute => attribute.id.attribute === id),
|
||||
)?.value ?? 0
|
||||
|
||||
const getModIfStaticAdjustment = (id: number, race: Race) =>
|
||||
race.attribute_adjustments
|
||||
.filter(adjustment => adjustment.list.length === 1 && adjustment.list[0]!.id.attribute === id)
|
||||
.reduce((acc, adjustment) => acc + adjustment.value, 0)
|
||||
race.attribute_adjustments.fixed
|
||||
?.filter(adjustment => adjustment.id.attribute === id)
|
||||
.reduce((acc, adjustment) => acc + adjustment.value, 0) ?? 0
|
||||
|
||||
export const getAttributeMaximum = (
|
||||
isInCharacterCreation: boolean,
|
||||
@@ -94,10 +77,7 @@ export const getAttributeMaximum = (
|
||||
return undefined
|
||||
}
|
||||
|
||||
export const isAttributeDecreasable = (
|
||||
dynamic: Rated,
|
||||
min: number,
|
||||
) => min < dynamic.value
|
||||
export const isAttributeDecreasable = (dynamic: Rated, min: number) => min < dynamic.value
|
||||
|
||||
export const isAttributeIncreasable = (
|
||||
dynamic: Rated,
|
||||
@@ -106,5 +86,5 @@ export const isAttributeIncreasable = (
|
||||
maxTotalPoints: number,
|
||||
isInCharacterCreation: boolean,
|
||||
) =>
|
||||
(!isInCharacterCreation || totalPoints < maxTotalPoints)
|
||||
&& (max === undefined || dynamic.value < max)
|
||||
(!isInCharacterCreation || totalPoints < maxTotalPoints) &&
|
||||
(max === undefined || dynamic.value < max)
|
||||
|
||||
@@ -9,9 +9,10 @@ export const isPublicationEnabled = (
|
||||
publication: Publication,
|
||||
includeAllPublications: boolean,
|
||||
includePublications: number[],
|
||||
) => publication.category === "CoreRules"
|
||||
|| (!publication.contains_adult_content && includeAllPublications)
|
||||
|| includePublications.includes(publication.id)
|
||||
) =>
|
||||
publication.category === "CoreRules" ||
|
||||
(!publication.contains_adult_content && includeAllPublications) ||
|
||||
includePublications.includes(publication.id)
|
||||
|
||||
/**
|
||||
* Checks if an entry from one or more publications is available, based on if
|
||||
@@ -25,6 +26,8 @@ export const isEntryAvailable = (
|
||||
) =>
|
||||
sourceReferences.some(ref => {
|
||||
const publication = publications[ref.id.publication]
|
||||
return publication !== undefined
|
||||
&& isPublicationEnabled(publication, includeAllPublications, includePublications)
|
||||
return (
|
||||
publication !== undefined &&
|
||||
isPublicationEnabled(publication, includeAllPublications, includePublications)
|
||||
)
|
||||
})
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
import { CloseCombatTechnique } from "optolith-database-schema/types/CombatTechnique_Close"
|
||||
import { RangedCombatTechnique } from "optolith-database-schema/types/CombatTechnique_Ranged"
|
||||
import { ExperienceLevel } from "optolith-database-schema/types/ExperienceLevel"
|
||||
import { filterNonNullable } from "../utils/array.ts"
|
||||
import {
|
||||
Activatable,
|
||||
PredefinedActivatableOption,
|
||||
countOptions,
|
||||
isActive,
|
||||
} from "./activatableEntry.ts"
|
||||
import { getAttributeValue } from "./attribute.ts"
|
||||
import { AttributeIdentifier } from "./identifier.ts"
|
||||
import { Dependency, Rated, RatedMap, flattenMinimumRestrictions } from "./ratedEntry.ts"
|
||||
|
||||
export const getCombatTechniqueValue = (dynamic: Rated | undefined): number => dynamic?.value ?? 6
|
||||
|
||||
export type CombatTechnique =
|
||||
| { tag: "CloseCombatTechnique"; closeCombatTechnique: CloseCombatTechnique }
|
||||
| { tag: "RangedCombatTechnique"; rangedCombatTechnique: RangedCombatTechnique }
|
||||
|
||||
const getPrimaryAttributeModifier = (dynamicAttributes: RatedMap, ids: number[]): number => {
|
||||
const primaryAttributeValues = ids.map(id => getAttributeValue(dynamicAttributes[id]))
|
||||
// return +1 for every 3 points above 8
|
||||
return Math.max(Math.floor((Math.max(...primaryAttributeValues) - 8) / 3), 0)
|
||||
}
|
||||
|
||||
const getAttackBase = (
|
||||
dynamicAttributes: RatedMap,
|
||||
primaryAttributeIds: number[],
|
||||
dynamicEntry: Rated | undefined,
|
||||
): number =>
|
||||
getPrimaryAttributeModifier(dynamicAttributes, primaryAttributeIds) +
|
||||
getCombatTechniqueValue(dynamicEntry)
|
||||
|
||||
/**
|
||||
* Returns the attack base for a close combat technique.
|
||||
*/
|
||||
export const getAttackBaseForClose = (
|
||||
dynamicAttributes: RatedMap,
|
||||
dynamicEntry: Rated | undefined,
|
||||
): number => getAttackBase(dynamicAttributes, [AttributeIdentifier.Courage], dynamicEntry)
|
||||
|
||||
/**
|
||||
* Returns the attack base for a ranged combat technique.
|
||||
*/
|
||||
export const getAttackBaseForRanged = (
|
||||
dynamicAttributes: RatedMap,
|
||||
staticEntry: RangedCombatTechnique,
|
||||
dynamicEntry: Rated | undefined,
|
||||
): number =>
|
||||
getAttackBase(
|
||||
dynamicAttributes,
|
||||
staticEntry.primary_attribute.map(ref => ref.id.attribute),
|
||||
dynamicEntry,
|
||||
)
|
||||
|
||||
/**
|
||||
* Returns the parry base for a close combat technique.
|
||||
*/
|
||||
export const getParryBaseForClose = (
|
||||
dynamicAttributes: RatedMap,
|
||||
staticEntry: CloseCombatTechnique,
|
||||
dynamicEntry: Rated | undefined,
|
||||
): number | undefined => {
|
||||
if (staticEntry.special.can_parry) {
|
||||
const primaryAttributeModifier = getPrimaryAttributeModifier(
|
||||
dynamicAttributes,
|
||||
staticEntry.primary_attribute.map(ref => ref.id.attribute),
|
||||
)
|
||||
const combatTechniqueRating = getCombatTechniqueValue(dynamicEntry)
|
||||
return primaryAttributeModifier + Math.round(combatTechniqueRating / 2)
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
export const getCombatTechniqueMinimum = (
|
||||
rangedCombatTechniques: RatedMap,
|
||||
staticCombatTechnique: CombatTechnique,
|
||||
dynamicCombatTechnique: Rated,
|
||||
hunter: Activatable | undefined,
|
||||
filterApplyingDependencies: (dependencies: Dependency[]) => Dependency[],
|
||||
): number => {
|
||||
const minimumValues: number[][] = [
|
||||
[6],
|
||||
flattenMinimumRestrictions(filterApplyingDependencies(dynamicCombatTechnique.dependencies)),
|
||||
isActive(hunter) &&
|
||||
staticCombatTechnique.tag === "RangedCombatTechnique" &&
|
||||
dynamicCombatTechnique.value >= 10 &&
|
||||
Object.values(rangedCombatTechniques)
|
||||
.map(getCombatTechniqueValue)
|
||||
.filter(value => value >= 10).length === 1
|
||||
? [10]
|
||||
: [],
|
||||
]
|
||||
|
||||
return Math.max(...minimumValues.flat())
|
||||
}
|
||||
|
||||
export const getCombatTechniqueMaximum = (
|
||||
attributes: RatedMap,
|
||||
combatTechnique: CombatTechnique,
|
||||
isInCharacterCreation: boolean,
|
||||
startExperienceLevel: ExperienceLevel | undefined,
|
||||
exceptionalSkill: Activatable | undefined,
|
||||
): number => {
|
||||
const idObject: PredefinedActivatableOption["id"] =
|
||||
combatTechnique.tag === "CloseCombatTechnique"
|
||||
? { type: "CloseCombatTechnique", value: combatTechnique.closeCombatTechnique.id }
|
||||
: { type: "RangedCombatTechnique", value: combatTechnique.rangedCombatTechnique.id }
|
||||
|
||||
const primaryAttribute =
|
||||
combatTechnique.tag === "CloseCombatTechnique"
|
||||
? combatTechnique.closeCombatTechnique.primary_attribute
|
||||
: combatTechnique.rangedCombatTechnique.primary_attribute
|
||||
|
||||
const maximumValues = filterNonNullable([
|
||||
Math.max(...primaryAttribute.map(ref => getAttributeValue(attributes[ref.id.attribute]))) + 2,
|
||||
isInCharacterCreation && startExperienceLevel !== undefined
|
||||
? startExperienceLevel.max_combat_technique_rating
|
||||
: undefined,
|
||||
])
|
||||
|
||||
const exceptionalSkillBonus = countOptions(exceptionalSkill, idObject)
|
||||
|
||||
return Math.min(...maximumValues) + exceptionalSkillBonus
|
||||
}
|
||||
|
||||
export const isCombatTechniqueDecreasable = (dynamic: Rated, min: number, canRemove: boolean) =>
|
||||
min < dynamic.value && canRemove
|
||||
|
||||
export const isCombatTechniqueIncreasable = (dynamic: Rated, max: number) => dynamic.value < max
|
||||
@@ -1,13 +1,8 @@
|
||||
import { Culture } from "optolith-database-schema/types/Culture"
|
||||
import { TranslateMap } from "../utils/translate.ts"
|
||||
|
||||
export const getCulture = (
|
||||
cultures: Record<number, Culture>,
|
||||
id: number,
|
||||
): Culture | undefined => cultures[id]
|
||||
export const getCulture = (cultures: Record<number, Culture>, id: number): Culture | undefined =>
|
||||
cultures[id]
|
||||
|
||||
export const getFullCultureName = (
|
||||
translateMap: TranslateMap,
|
||||
culture: Culture,
|
||||
): string =>
|
||||
export const getFullCultureName = (translateMap: TranslateMap, culture: Culture): string =>
|
||||
translateMap(culture.translations)?.name ?? ""
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
// import { fmap } from "../../../Data/Functor"
|
||||
// import { elem, filter, find, foldl, isList, List, map, maximumNonNegative } from "../../../Data/List"
|
||||
// import { bindF, Maybe, Nothing, or, sum } from "../../../Data/Maybe"
|
||||
// import { gt, gte, inc } from "../../../Data/Num"
|
||||
// import { isRecord, Record } from "../../../Data/Record"
|
||||
// import { HeroModelRecord } from "../../Models/Hero/HeroModel"
|
||||
// import { ValueBasedDependent } from "../../Models/Hero/heroTypeHelpers"
|
||||
// import { SkillOptionalDependency } from "../../Models/Hero/SkillOptionalDependency"
|
||||
// import { Advantage } from "../../Models/Wiki/Advantage"
|
||||
// import { RequireActivatable } from "../../Models/Wiki/prerequisites/ActivatableRequirement"
|
||||
// import { SocialPrerequisite } from "../../Models/Wiki/prerequisites/SocialPrerequisite"
|
||||
// import { StaticDataRecord } from "../../Models/Wiki/WikiModel"
|
||||
// import { AbilityRequirement, Activatable } from "../../Models/Wiki/wikiTypeHelpers"
|
||||
// import { getHeroStateItem } from "../heroStateUtils"
|
||||
// import { pipe } from "../pipe"
|
||||
// import { flattenPrerequisites } from "../Prerequisites/flattenPrerequisites"
|
||||
// import { isNumber } from "../typeCheckUtils"
|
||||
// import { getWikiEntry } from "../WikiUtils"
|
||||
|
||||
import { assertExhaustive } from "../../utils/typeSafety.ts"
|
||||
import { getAttributeValue } from "../attribute.ts"
|
||||
import { getCombatTechniqueValue } from "../combatTechnique.ts"
|
||||
import { getLiturgicalChantValue } from "../liturgicalChant.ts"
|
||||
import {
|
||||
ActivatableRatedWithEnhancementsMap,
|
||||
Dependency as RatedDependency,
|
||||
RatedMap,
|
||||
compareWithRestriction,
|
||||
} from "../ratedEntry.ts"
|
||||
import { getSkillValue } from "../skill.ts"
|
||||
import { getSpellValue } from "../spell.ts"
|
||||
|
||||
/**
|
||||
* `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.
|
||||
* @param wiki The full wiki.
|
||||
* @param state The current hero.
|
||||
* @param dependencies The list of dependencies to flatten.
|
||||
*/
|
||||
export const filterApplyingRatedDependencies =
|
||||
(ratedMaps: {
|
||||
attributes: RatedMap
|
||||
skills: RatedMap
|
||||
closeCombatTechniques: RatedMap
|
||||
rangedCombatTechniques: RatedMap
|
||||
spells: ActivatableRatedWithEnhancementsMap
|
||||
rituals: ActivatableRatedWithEnhancementsMap
|
||||
liturgicalChants: ActivatableRatedWithEnhancementsMap
|
||||
ceremonies: ActivatableRatedWithEnhancementsMap
|
||||
}) =>
|
||||
(dependencies: RatedDependency[]): RatedDependency[] =>
|
||||
dependencies.filter(dep => {
|
||||
if (dep.otherTargets) {
|
||||
return dep.otherTargets.some(target => {
|
||||
switch (target.tag) {
|
||||
case "Attribute":
|
||||
return compareWithRestriction(
|
||||
dep.value,
|
||||
getAttributeValue(ratedMaps.attributes[target.attribute]),
|
||||
)
|
||||
case "Skill":
|
||||
return compareWithRestriction(
|
||||
dep.value,
|
||||
getSkillValue(ratedMaps.skills[target.skill]),
|
||||
)
|
||||
case "CloseCombatTechnique":
|
||||
return compareWithRestriction(
|
||||
dep.value,
|
||||
getCombatTechniqueValue(
|
||||
ratedMaps.closeCombatTechniques[target.close_combat_technique],
|
||||
),
|
||||
)
|
||||
case "RangedCombatTechnique":
|
||||
return compareWithRestriction(
|
||||
dep.value,
|
||||
getCombatTechniqueValue(
|
||||
ratedMaps.rangedCombatTechniques[target.ranged_combat_technique],
|
||||
),
|
||||
)
|
||||
case "Spell":
|
||||
return compareWithRestriction(
|
||||
dep.value,
|
||||
getSpellValue(ratedMaps.spells[target.spell]),
|
||||
)
|
||||
case "Ritual":
|
||||
return compareWithRestriction(
|
||||
dep.value,
|
||||
getSpellValue(ratedMaps.rituals[target.ritual]),
|
||||
)
|
||||
case "LiturgicalChant":
|
||||
return compareWithRestriction(
|
||||
dep.value,
|
||||
getLiturgicalChantValue(ratedMaps.liturgicalChants[target.liturgical_chant]),
|
||||
)
|
||||
case "Ceremony":
|
||||
return compareWithRestriction(
|
||||
dep.value,
|
||||
getLiturgicalChantValue(ratedMaps.ceremonies[target.ceremony]),
|
||||
)
|
||||
default:
|
||||
return assertExhaustive(target)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
// (wiki: StaticDataRecord) =>
|
||||
// (state: HeroModelRecord) =>
|
||||
// <T extends number | boolean>
|
||||
// (dependencies: List<T | Record<SkillOptionalDependency>>) =>
|
||||
// map<T | Record<SkillOptionalDependency>, T>
|
||||
// (e => isRecord (e)
|
||||
// ? pipe (
|
||||
// getWikiEntry (wiki) as (id: string) => Maybe<Activatable>,
|
||||
// bindF (pipe (
|
||||
// prerequisites,
|
||||
// flattenPrerequisites (Nothing) (Nothing),
|
||||
// find ((r): r is AbilityRequirement =>
|
||||
// r !== "RCP"
|
||||
// && !SocialPrerequisite.is (r)
|
||||
// && isList (id (r))
|
||||
// && elem (origin (e)) (id (r) as List<string>))
|
||||
// )),
|
||||
// fmap (pipe (
|
||||
// id as (r: AbilityRequirement) => List<string>,
|
||||
// foldl<string, number>
|
||||
// (acc => pipe (
|
||||
// getHeroStateItem (state) as (id: string) => Maybe<ValueBasedDependent>,
|
||||
// fmap (pipe (value, gte (value (e)))),
|
||||
// or,
|
||||
// x => x ? inc (acc) : acc
|
||||
// ))
|
||||
// (0),
|
||||
// gt (1),
|
||||
// x => x ? 0 : value (e)
|
||||
// )),
|
||||
// sum
|
||||
// )
|
||||
// (origin (e)) as T
|
||||
// : e)
|
||||
// (dependencies)
|
||||
|
||||
// /**
|
||||
// * Filters the list of dependencies of `ActivatableSkillDependent`s and returns
|
||||
// * the maximum. Minimum: `0`.
|
||||
// */
|
||||
// export const filterAndMaximumNonNegative = pipe(
|
||||
// filter<number | boolean, number>(isNumber),
|
||||
// maximumNonNegative,
|
||||
// )
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* The instance of an energy entry where points can be purchased but also
|
||||
* (permanently) spent.
|
||||
*/
|
||||
export type Energy = {
|
||||
/**
|
||||
* The number of points purchased.
|
||||
*/
|
||||
readonly purchased: number
|
||||
|
||||
/**
|
||||
* The number of points permanently lost.
|
||||
*/
|
||||
readonly permanentlyLost: number
|
||||
}
|
||||
|
||||
/**
|
||||
* The instance of an energy entry where points can be purchased but also
|
||||
* (permanently) spent and bought back.
|
||||
*/
|
||||
export type EnergyWithBuyBack = {
|
||||
/**
|
||||
* The number of points purchased.
|
||||
*/
|
||||
readonly purchased: number
|
||||
|
||||
/**
|
||||
* The number of points permanently lost.
|
||||
*/
|
||||
readonly permanentlyLost: number
|
||||
|
||||
/**
|
||||
* The number of permanently lost points that have been bought back.
|
||||
*/
|
||||
readonly permanentlyLostBoughtBack: number
|
||||
}
|
||||
@@ -2,21 +2,21 @@ import { ActivatableIdentifier } from "optolith-database-schema/types/_Identifie
|
||||
|
||||
export type EnhancementDependency =
|
||||
| {
|
||||
tag: "Internal"
|
||||
tag: "Internal"
|
||||
|
||||
/**
|
||||
* The depending enhancement.
|
||||
*/
|
||||
id: number
|
||||
}
|
||||
/**
|
||||
* The depending enhancement.
|
||||
*/
|
||||
id: number
|
||||
}
|
||||
| {
|
||||
tag: "External"
|
||||
tag: "External"
|
||||
|
||||
/**
|
||||
* The depending activatable.
|
||||
*/
|
||||
id: ActivatableIdentifier
|
||||
}
|
||||
/**
|
||||
* The depending activatable.
|
||||
*/
|
||||
id: ActivatableIdentifier
|
||||
}
|
||||
|
||||
export type Enhancement = {
|
||||
id: number
|
||||
|
||||
@@ -8,8 +8,6 @@ export const getCurrentExperienceLevel = (
|
||||
.sort((a, b) => a.adventure_points - b.adventure_points)
|
||||
.reduce(
|
||||
(acc, experienceLevel) =>
|
||||
experienceLevel.adventure_points <= totalAdventurePoints
|
||||
? experienceLevel
|
||||
: acc,
|
||||
experienceLevels[0]
|
||||
experienceLevel.adventure_points <= totalAdventurePoints ? experienceLevel : acc,
|
||||
experienceLevels[0],
|
||||
)
|
||||
|
||||
@@ -52,6 +52,68 @@ export type EnergyIdentifier =
|
||||
| DerivedCharacteristicIdentifier.ArcaneEnergy
|
||||
| DerivedCharacteristicIdentifier.KarmaPoints
|
||||
|
||||
export enum SkillIdentifier {
|
||||
Flying = 1,
|
||||
Gaukelei = 2,
|
||||
Climbing = 3,
|
||||
BodyControl = 4,
|
||||
FeatOfStrength = 5,
|
||||
Riding = 6,
|
||||
Swimming = 7,
|
||||
SelfControl = 8,
|
||||
Singing = 9,
|
||||
Perception = 10,
|
||||
Dancing = 11,
|
||||
Pickpocket = 12,
|
||||
Stealth = 13,
|
||||
Carousing = 14,
|
||||
Persuasion = 15,
|
||||
Seduction = 16,
|
||||
Intimidation = 17,
|
||||
Etiquette = 18,
|
||||
Streetwise = 19,
|
||||
Empathy = 20,
|
||||
FastTalk = 21,
|
||||
Disguise = 22,
|
||||
Willpower = 23,
|
||||
Tracking = 24,
|
||||
Ropes = 25,
|
||||
Fishing = 26,
|
||||
Orienting = 27,
|
||||
PlantLore = 28,
|
||||
AnimalLore = 29,
|
||||
Survival = 30,
|
||||
Gambling = 31,
|
||||
Geography = 32,
|
||||
History = 33,
|
||||
Religions = 34,
|
||||
Warfare = 35,
|
||||
MagicalLore = 36,
|
||||
Mechanics = 37,
|
||||
Math = 38,
|
||||
Law = 39,
|
||||
MythsAndLegends = 40,
|
||||
SphereLore = 41,
|
||||
Astronomy = 42,
|
||||
Alchemy = 43,
|
||||
Sailing = 44,
|
||||
Driving = 45,
|
||||
Commerce = 46,
|
||||
TreatPoison = 47,
|
||||
TreatDisease = 48,
|
||||
TreatSoul = 49,
|
||||
TreatWounds = 50,
|
||||
Woodworking = 51,
|
||||
PrepareFood = 52,
|
||||
Leatherworking = 53,
|
||||
ArtisticAbility = 54,
|
||||
Metalworking = 55,
|
||||
Music = 56,
|
||||
PickLocks = 57,
|
||||
Earthencraft = 58,
|
||||
Clothworking = 59,
|
||||
}
|
||||
|
||||
export enum AdvantageIdentifier {
|
||||
CustomAdvantage = 0,
|
||||
Aptitude = 4, // Begabung
|
||||
@@ -113,10 +175,20 @@ export enum DisadvantageIdentifier {
|
||||
WenigeVisionen = 73,
|
||||
}
|
||||
|
||||
export enum RangedCombatTechniqueIdentifier {
|
||||
SpittingFire = 4,
|
||||
}
|
||||
|
||||
export enum CombatSpecialAbilityIdentifier {
|
||||
CombatReflexes = 12,
|
||||
}
|
||||
|
||||
export enum GeneralSpecialAbilityIdentifier {
|
||||
CraftInstruments = 17,
|
||||
Hunter = 18,
|
||||
FireEater = 53,
|
||||
}
|
||||
|
||||
export enum MagicalSpecialAbilityIdentifier {
|
||||
GrosseMeditation = 12,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import { ActivatableRated } from "./ratedEntry.ts"
|
||||
|
||||
export const getLiturgicalChantValue = (
|
||||
dynamic: ActivatableRated | undefined,
|
||||
): number | undefined => dynamic?.value
|
||||
+52
-55
@@ -1,39 +1,41 @@
|
||||
import { Page, PageRange as RawPageRange, SimpleOccurrence } from "optolith-database-schema/types/source/_PublicationRef"
|
||||
import {
|
||||
Page,
|
||||
PageRange as RawPageRange,
|
||||
SimpleOccurrence,
|
||||
} from "optolith-database-schema/types/source/_PublicationRef"
|
||||
import { range } from "../utils/array.ts"
|
||||
import { Compare } from "../utils/compare.ts"
|
||||
import { assertExhaustive } from "../utils/typeSafety.ts"
|
||||
|
||||
export const comparePage: Compare<Page> = (a, b) => {
|
||||
switch (a.tag) {
|
||||
case "InsideCoverFront": return b.tag === "InsideCoverFront"
|
||||
? 0
|
||||
: -1
|
||||
case "InsideCoverBack": return b.tag === "InsideCoverBack"
|
||||
? 0
|
||||
: 1
|
||||
case "Numbered": return b.tag === "Numbered"
|
||||
? a.numbered - b.numbered
|
||||
: b.tag === "InsideCoverFront"
|
||||
? 1
|
||||
: -1
|
||||
default: return assertExhaustive(a)
|
||||
case "InsideCoverFront":
|
||||
return b.tag === "InsideCoverFront" ? 0 : -1
|
||||
case "InsideCoverBack":
|
||||
return b.tag === "InsideCoverBack" ? 0 : 1
|
||||
case "Numbered":
|
||||
return b.tag === "Numbered" ? a.numbered - b.numbered : b.tag === "InsideCoverFront" ? 1 : -1
|
||||
default:
|
||||
return assertExhaustive(a)
|
||||
}
|
||||
}
|
||||
|
||||
export const equalsPage = (a: Page, b: Page): boolean =>
|
||||
comparePage(a, b) === 0
|
||||
export const equalsPage = (a: Page, b: Page): boolean => comparePage(a, b) === 0
|
||||
|
||||
export const succ = (page: Page): Page => {
|
||||
switch (page.tag) {
|
||||
case "InsideCoverFront": return { tag: "Numbered", numbered: 1 }
|
||||
case "InsideCoverBack": return { tag: "InsideCoverFront", inside_cover_front: {} }
|
||||
case "Numbered": return { tag: "Numbered", numbered: page.numbered + 1 }
|
||||
default: return assertExhaustive(page)
|
||||
case "InsideCoverFront":
|
||||
return { tag: "Numbered", numbered: 1 }
|
||||
case "InsideCoverBack":
|
||||
return { tag: "InsideCoverFront", inside_cover_front: {} }
|
||||
case "Numbered":
|
||||
return { tag: "Numbered", numbered: page.numbered + 1 }
|
||||
default:
|
||||
return assertExhaustive(page)
|
||||
}
|
||||
}
|
||||
|
||||
export const numberToPage = (number: number): Page =>
|
||||
({ tag: "Numbered", numbered: number })
|
||||
export const numberToPage = (number: number): Page => ({ tag: "Numbered", numbered: number })
|
||||
|
||||
export type PageRange = {
|
||||
firstPage: Page
|
||||
@@ -42,51 +44,46 @@ export type PageRange = {
|
||||
|
||||
export const numberRangeToPageRange = (numberRange: SimpleOccurrence): PageRange =>
|
||||
numberRange.last_page === undefined
|
||||
? ({ firstPage: numberToPage(numberRange.first_page) })
|
||||
: ({
|
||||
firstPage: numberToPage(numberRange.first_page),
|
||||
lastPage: numberToPage(numberRange.last_page),
|
||||
})
|
||||
? { firstPage: numberToPage(numberRange.first_page) }
|
||||
: {
|
||||
firstPage: numberToPage(numberRange.first_page),
|
||||
lastPage: numberToPage(numberRange.last_page),
|
||||
}
|
||||
|
||||
export const fromRawPageRange = (pageRange: RawPageRange): PageRange =>
|
||||
pageRange.last_page === undefined
|
||||
? ({ firstPage: pageRange.first_page })
|
||||
: ({
|
||||
firstPage: pageRange.first_page,
|
||||
lastPage: pageRange.last_page,
|
||||
})
|
||||
? { firstPage: pageRange.first_page }
|
||||
: {
|
||||
firstPage: pageRange.first_page,
|
||||
lastPage: pageRange.last_page,
|
||||
}
|
||||
|
||||
export const normalizePageRanges = (
|
||||
ranges: PageRange[],
|
||||
): PageRange[] =>
|
||||
export const normalizePageRanges = (ranges: PageRange[]): PageRange[] =>
|
||||
ranges
|
||||
.flatMap(({ firstPage, lastPage = firstPage }): Page[] => {
|
||||
if (firstPage.tag === "Numbered" && lastPage.tag === "Numbered") {
|
||||
return range(firstPage.numbered, lastPage.numbered)
|
||||
.map(numbered => ({ tag: "Numbered", numbered }))
|
||||
}
|
||||
else {
|
||||
return [ firstPage, lastPage ]
|
||||
return range(firstPage.numbered, lastPage.numbered).map(numbered => ({
|
||||
tag: "Numbered",
|
||||
numbered,
|
||||
}))
|
||||
} else {
|
||||
return [firstPage, lastPage]
|
||||
}
|
||||
})
|
||||
.filter((page, i, pages) =>
|
||||
pages.findIndex(p => equalsPage(page, p)) === i)
|
||||
.filter((page, i, pages) => pages.findIndex(p => equalsPage(page, p)) === i)
|
||||
.sort(comparePage)
|
||||
.reduce<PageRange[]>(
|
||||
(acc, page): PageRange[] => {
|
||||
const lastRange = acc[acc.length - 1]
|
||||
.reduce<PageRange[]>((acc, page): PageRange[] => {
|
||||
const lastRange = acc[acc.length - 1]
|
||||
|
||||
if (lastRange === undefined) {
|
||||
return [ { firstPage: page } ]
|
||||
}
|
||||
if (lastRange === undefined) {
|
||||
return [{ firstPage: page }]
|
||||
}
|
||||
|
||||
const { firstPage, lastPage = firstPage } = lastRange
|
||||
const { firstPage, lastPage = firstPage } = lastRange
|
||||
|
||||
if (equalsPage(page, succ(lastPage))) {
|
||||
return [ ...acc.slice(0, -1), { firstPage, lastPage: page } ]
|
||||
}
|
||||
if (equalsPage(page, succ(lastPage))) {
|
||||
return [...acc.slice(0, -1), { firstPage, lastPage: page }]
|
||||
}
|
||||
|
||||
return [ ...acc, { firstPage: page } ]
|
||||
},
|
||||
[]
|
||||
)
|
||||
return [...acc, { firstPage: page }]
|
||||
}, [])
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
import { ExperienceLevel } from "optolith-database-schema/types/ExperienceLevel"
|
||||
import { Profession, ProfessionName, ProfessionPackage, ProfessionTranslation, ProfessionVariant, ProfessionVersion } from "optolith-database-schema/types/Profession"
|
||||
import {
|
||||
Profession,
|
||||
ProfessionName,
|
||||
ProfessionPackage,
|
||||
ProfessionTranslation,
|
||||
ProfessionVariant,
|
||||
ProfessionVersion,
|
||||
} from "optolith-database-schema/types/Profession"
|
||||
import { LocaleMap } from "optolith-database-schema/types/_LocaleMap"
|
||||
import { Sex } from "../../main_window/slices/characterSlice.ts"
|
||||
import { Nullish, isNotNullish } from "../utils/nullable.ts"
|
||||
import { TranslateMap } from "../utils/translate.ts"
|
||||
import { assertExhaustive } from "../utils/typeSafety.ts"
|
||||
import { ProfessionIdentifier } from "./identifier.ts"
|
||||
import { Sex } from "./sex.ts"
|
||||
|
||||
export type ProfessionParts = {
|
||||
base: Profession
|
||||
@@ -23,30 +30,31 @@ export const getProfessionParts = (
|
||||
const selectedProfession = professions[id]
|
||||
if (selectedProfession === undefined) {
|
||||
return undefined
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
const selectedInstance = selectedProfession.versions.find(x =>
|
||||
x.tag === "Experienced"
|
||||
? x.experienced.id === instanceId
|
||||
: x.by_experience_level.id === instanceId)
|
||||
? x.experienced.id === instanceId
|
||||
: x.by_experience_level.id === instanceId,
|
||||
)
|
||||
|
||||
if (selectedInstance === undefined) {
|
||||
return undefined
|
||||
}
|
||||
else {
|
||||
const selectedTranslations = selectedInstance.tag === "Experienced"
|
||||
? selectedInstance.experienced.translations
|
||||
: selectedInstance?.by_experience_level.translations
|
||||
} else {
|
||||
const selectedTranslations =
|
||||
selectedInstance.tag === "Experienced"
|
||||
? selectedInstance.experienced.translations
|
||||
: selectedInstance?.by_experience_level.translations
|
||||
|
||||
const selectedPackage = selectedInstance.tag === "Experienced"
|
||||
? selectedInstance.experienced.package
|
||||
: selectedInstance.by_experience_level.packages_map
|
||||
.find(x => x.experience_level_id === startExperienceLevel.id)?.package
|
||||
const selectedPackage =
|
||||
selectedInstance.tag === "Experienced"
|
||||
? selectedInstance.experienced.package
|
||||
: selectedInstance.by_experience_level.packages_map.find(
|
||||
x => x.experience_level_id === startExperienceLevel.id,
|
||||
)?.package
|
||||
|
||||
if (selectedPackage === undefined) {
|
||||
return undefined
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return {
|
||||
base: selectedProfession,
|
||||
instance: selectedInstance,
|
||||
@@ -64,27 +72,29 @@ export const getProfessionVariant = (
|
||||
): ProfessionVariant | undefined => {
|
||||
if (variantId === undefined) {
|
||||
return undefined
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return profession.package.variants?.list.find(x => x.id === variantId)
|
||||
}
|
||||
}
|
||||
|
||||
export const professionNameToString = <T extends ProfessionName | undefined>(
|
||||
sex: Sex,
|
||||
professionName: T
|
||||
professionName: T,
|
||||
): string | Nullish<T> => {
|
||||
if (professionName === undefined || typeof professionName === "string") {
|
||||
return professionName as string | Nullish<T>
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
switch (sex.type) {
|
||||
case "Male": return professionName.male
|
||||
case "Female": return professionName.female
|
||||
case "Male":
|
||||
return professionName.male
|
||||
case "Female":
|
||||
return professionName.female
|
||||
case "BalThani":
|
||||
case "Tsajana":
|
||||
case "Custom": return professionName.default
|
||||
default: return assertExhaustive(sex)
|
||||
case "Custom":
|
||||
return professionName.default
|
||||
default:
|
||||
return assertExhaustive(sex)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -104,9 +114,8 @@ export const getFullProfessionNameParts = (
|
||||
customName?: string,
|
||||
): FullProfessionNameParts => {
|
||||
if (profession.base.id === ProfessionIdentifier.OwnProfession) {
|
||||
const name = customName
|
||||
?? professionNameToString(sex, translateMap(profession.translations)?.name)
|
||||
?? ""
|
||||
const name =
|
||||
customName ?? professionNameToString(sex, translateMap(profession.translations)?.name) ?? ""
|
||||
|
||||
return {
|
||||
name,
|
||||
@@ -114,15 +123,18 @@ export const getFullProfessionNameParts = (
|
||||
}
|
||||
}
|
||||
|
||||
const professionName =
|
||||
professionNameToString(sex, translateMap(profession.translations)?.name)
|
||||
const professionSubName =
|
||||
professionNameToString(sex, translateMap(profession.translations)?.specification)
|
||||
const professionVariantName =
|
||||
professionNameToString(sex, translateMap(professionVariant?.translations)?.name)
|
||||
const professionName = professionNameToString(sex, translateMap(profession.translations)?.name)
|
||||
const professionSubName = professionNameToString(
|
||||
sex,
|
||||
translateMap(profession.translations)?.specification,
|
||||
)
|
||||
const professionVariantName = professionNameToString(
|
||||
sex,
|
||||
translateMap(professionVariant?.translations)?.name,
|
||||
)
|
||||
|
||||
if (professionSubName !== undefined || professionVariantName !== undefined) {
|
||||
const specifications = [ professionSubName, professionVariantName ]
|
||||
const specifications = [professionSubName, professionVariantName]
|
||||
.filter(isNotNullish)
|
||||
.join(", ")
|
||||
|
||||
@@ -132,8 +144,7 @@ export const getFullProfessionNameParts = (
|
||||
variantName: professionVariantName,
|
||||
fullName: `${professionName ?? ""} (${specifications})`,
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return {
|
||||
name: professionName ?? "",
|
||||
fullName: professionName ?? "",
|
||||
|
||||
@@ -2,28 +2,18 @@ import { Race, RaceVariant } from "optolith-database-schema/types/Race"
|
||||
import { mapNullableDefault } from "../utils/nullable.ts"
|
||||
import { TranslateMap } from "../utils/translate.ts"
|
||||
|
||||
export const getRace = (
|
||||
races: Record<number, Race>,
|
||||
id: number,
|
||||
): Race | undefined => races[id]
|
||||
export const getRace = (races: Record<number, Race>, id: number): Race | undefined => races[id]
|
||||
|
||||
export const getRaceVariant = (
|
||||
race: Race,
|
||||
id: number | undefined,
|
||||
): RaceVariant | undefined =>
|
||||
id === undefined || race.variant_dependent.tag !== "HasVariants"
|
||||
? undefined
|
||||
: race.variant_dependent.has_variants.find(variant => variant.id === id)
|
||||
export const getRaceVariant = (race: Race, id: number | undefined): RaceVariant | undefined =>
|
||||
id === undefined ? undefined : race.variants.find(variant => variant.id === id)
|
||||
|
||||
export const getFullRaceName = (
|
||||
translateMap: TranslateMap,
|
||||
race: Race,
|
||||
raceVariant?: RaceVariant,
|
||||
): string =>
|
||||
`${translateMap(race.translations)?.name ?? ""}${
|
||||
mapNullableDefault(
|
||||
translateMap(raceVariant?.translations)?.name,
|
||||
str => ` (${str})`,
|
||||
""
|
||||
)
|
||||
}`
|
||||
`${translateMap(race.translations)?.name ?? ""}${mapNullableDefault(
|
||||
translateMap(raceVariant?.translations)?.name,
|
||||
str => ` (${str})`,
|
||||
"",
|
||||
)}`
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
import { ActivatableIdentifier, SkillWithEnhancementsIdentifier } from "optolith-database-schema/types/_IdentifierGroup"
|
||||
import {
|
||||
ActivatableIdentifier,
|
||||
RatedIdentifier,
|
||||
SkillWithEnhancementsIdentifier,
|
||||
} from "optolith-database-schema/types/_IdentifierGroup"
|
||||
import { ImprovementCost } from "./adventurePoints/improvementCost.ts"
|
||||
import { BoundAdventurePoints, RatedAdventurePointsCache, cachedAdventurePoints, cachedAdventurePointsForActivatable } from "./adventurePoints/ratedEntry.ts"
|
||||
import {
|
||||
BoundAdventurePoints,
|
||||
RatedAdventurePointsCache,
|
||||
cachedAdventurePoints,
|
||||
cachedAdventurePointsForActivatable,
|
||||
} from "./adventurePoints/ratedEntry.ts"
|
||||
import { Enhancement } from "./enhancement.ts"
|
||||
|
||||
/**
|
||||
* A required value from a prerequisite. Can either require a minimum or a
|
||||
* maximum value.
|
||||
*/
|
||||
export type ValueRestriction =
|
||||
| MinimumValueRestriction
|
||||
| MaximumValueRestriction
|
||||
export type ValueRestriction = MinimumValueRestriction | MaximumValueRestriction
|
||||
|
||||
export type MinimumValueRestriction = {
|
||||
readonly tag: "Minimum"
|
||||
@@ -27,6 +34,18 @@ export const isMinimumRestriction = (x: ValueRestriction): x is MinimumValueRest
|
||||
export const isMaximumRestriction = (x: ValueRestriction): x is MaximumValueRestriction =>
|
||||
x.tag === "Maximum"
|
||||
|
||||
export const compareWithRestriction = (
|
||||
restriction: ValueRestriction,
|
||||
value: number | undefined,
|
||||
): boolean =>
|
||||
isMinimumRestriction(restriction)
|
||||
? value === undefined
|
||||
? false
|
||||
: value >= restriction.minimum
|
||||
: value === undefined
|
||||
? true
|
||||
: value <= restriction.maximum
|
||||
|
||||
/**
|
||||
* Describes a dependency on a certain rated entry.
|
||||
*/
|
||||
@@ -40,7 +59,7 @@ export type Dependency = {
|
||||
* If the source prerequisite targets multiple entries, the other entries are
|
||||
* listed here.
|
||||
*/
|
||||
readonly otherTargets?: ActivatableIdentifier | SkillWithEnhancementsIdentifier
|
||||
readonly otherTargets?: RatedIdentifier[]
|
||||
|
||||
/**
|
||||
* The required value.
|
||||
@@ -48,6 +67,18 @@ export type Dependency = {
|
||||
readonly value: ValueRestriction
|
||||
}
|
||||
|
||||
/**
|
||||
* Flattens the minimum restrictions of a list of dependencies.
|
||||
*/
|
||||
export const flattenMinimumRestrictions = (dependencies: Dependency[]): number[] =>
|
||||
dependencies.flatMap(dep => (isMinimumRestriction(dep.value) ? [dep.value.minimum] : []))
|
||||
|
||||
/**
|
||||
* Flattens the maximum restrictions of a list of dependencies.
|
||||
*/
|
||||
export const flattenMaximumRestrictions = (dependencies: Dependency[]): number[] =>
|
||||
dependencies.flatMap(dep => (isMaximumRestriction(dep.value) ? [dep.value.maximum] : []))
|
||||
|
||||
/**
|
||||
* The current value.
|
||||
*/
|
||||
@@ -86,6 +117,10 @@ export type Rated = {
|
||||
readonly boundAdventurePoints: BoundAdventurePoints[]
|
||||
}
|
||||
|
||||
export type RatedMap = {
|
||||
[id: number]: Rated
|
||||
}
|
||||
|
||||
export type RatedHelpers = {
|
||||
/**
|
||||
* Creates a new entry with an initial value if active. The initial
|
||||
@@ -124,22 +159,18 @@ export const createRatedHelpers = (config: {
|
||||
|
||||
const updateCachedAdventurePoints = (entry: Rated): Rated => ({
|
||||
...entry,
|
||||
cachedAdventurePoints:
|
||||
cachedAdventurePoints(
|
||||
entry.value,
|
||||
minValue,
|
||||
entry.boundAdventurePoints,
|
||||
getImprovementCost(entry.id),
|
||||
),
|
||||
cachedAdventurePoints: cachedAdventurePoints(
|
||||
entry.value,
|
||||
minValue,
|
||||
entry.boundAdventurePoints,
|
||||
getImprovementCost(entry.id),
|
||||
),
|
||||
})
|
||||
|
||||
const create: RatedHelpers["create"] = (
|
||||
id,
|
||||
value = minValue,
|
||||
{
|
||||
dependencies = [],
|
||||
boundAdventurePoints = [],
|
||||
} = {},
|
||||
{ dependencies = [], boundAdventurePoints = [] } = {},
|
||||
) =>
|
||||
updateCachedAdventurePoints({
|
||||
id,
|
||||
@@ -205,6 +236,10 @@ export type ActivatableRated = {
|
||||
readonly boundAdventurePoints: BoundAdventurePoints[]
|
||||
}
|
||||
|
||||
export type ActivatableRatedMap = {
|
||||
[id: number]: ActivatableRated
|
||||
}
|
||||
|
||||
export type ActivatableRatedHelpers = {
|
||||
/**
|
||||
* Creates a new entry with an initial value if active. The initial
|
||||
@@ -246,21 +281,17 @@ export const createActivatableRatedHelpers = (config: {
|
||||
|
||||
const updateCachedAdventurePoints = (entry: ActivatableRated): ActivatableRated => ({
|
||||
...entry,
|
||||
cachedAdventurePoints:
|
||||
cachedAdventurePointsForActivatable(
|
||||
entry.value,
|
||||
entry.boundAdventurePoints,
|
||||
getImprovementCost(entry.id),
|
||||
),
|
||||
cachedAdventurePoints: cachedAdventurePointsForActivatable(
|
||||
entry.value,
|
||||
entry.boundAdventurePoints,
|
||||
getImprovementCost(entry.id),
|
||||
),
|
||||
})
|
||||
|
||||
const create: ActivatableRatedHelpers["create"] = (
|
||||
id,
|
||||
value,
|
||||
{
|
||||
dependencies = [],
|
||||
boundAdventurePoints = [],
|
||||
} = {},
|
||||
{ dependencies = [], boundAdventurePoints = [] } = {},
|
||||
) =>
|
||||
updateCachedAdventurePoints({
|
||||
id,
|
||||
@@ -330,3 +361,7 @@ export type ActivatableRatedWithEnhancements = {
|
||||
[id: number]: Enhancement
|
||||
}
|
||||
}
|
||||
|
||||
export type ActivatableRatedWithEnhancementsMap = {
|
||||
[id: number]: ActivatableRatedWithEnhancements
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* The character's sex. It does not have to be binary, although it always must be specified how to handle it in the context of binary sex prerequisites. You can also provide a custom sex with a custom name.
|
||||
*/
|
||||
export type Sex = BinarySex | NonBinarySex | CustomSex
|
||||
|
||||
/**
|
||||
* A binary sex option.
|
||||
*/
|
||||
export type BinarySex = {
|
||||
type: "Male" | "Female"
|
||||
}
|
||||
|
||||
/**
|
||||
* A non-binary sex option.
|
||||
*/
|
||||
export type NonBinarySex = {
|
||||
type: "BalThani" | "Tsajana"
|
||||
|
||||
/**
|
||||
* Defines how a non-binary sex should be treated when checking prerequisites.
|
||||
*/
|
||||
binaryHandling: BinaryHandling
|
||||
}
|
||||
|
||||
/**
|
||||
* A custom non-binary sex option.
|
||||
*/
|
||||
export type CustomSex = {
|
||||
type: "Custom"
|
||||
|
||||
/**
|
||||
* The custom sex name.
|
||||
*/
|
||||
name: string
|
||||
|
||||
/**
|
||||
* Defines how a non-binary sex should be treated when checking prerequisites.
|
||||
*/
|
||||
binaryHandling: BinaryHandling
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines how a non-binary sex should be treated when checking prerequisites.
|
||||
*/
|
||||
export type BinaryHandling = {
|
||||
/**
|
||||
* Defines if the sex should be treated as male when checking prerequisites.
|
||||
*/
|
||||
asMale: boolean
|
||||
|
||||
/**
|
||||
* Defines if the sex should be treated as female when checking prerequisites.
|
||||
*/
|
||||
asFemale: boolean
|
||||
}
|
||||
+48
-66
@@ -1,64 +1,53 @@
|
||||
import { Culture } from "optolith-database-schema/types/Culture"
|
||||
import { ExperienceLevel } from "optolith-database-schema/types/ExperienceLevel"
|
||||
import { Skill } from "optolith-database-schema/types/Skill"
|
||||
import { Activatable, RatedMap } from "../../main_window/slices/characterSlice.ts"
|
||||
import { filterNonNullable } from "../utils/array.ts"
|
||||
import { countOptions } from "./activatableEntry.ts"
|
||||
import { Rated } from "./ratedEntry.ts"
|
||||
import { mapNullable } from "../utils/nullable.ts"
|
||||
import { Activatable, countOptions, isActive } from "./activatableEntry.ts"
|
||||
import { SkillIdentifier } from "./identifier.ts"
|
||||
import { Dependency, Rated, RatedMap, flattenMinimumRestrictions } from "./ratedEntry.ts"
|
||||
import { getSkillCheckValues } from "./skillCheck.ts"
|
||||
|
||||
// const getMinSRByCraftInstruments = (state : HeroModelRecord) =>
|
||||
// (entry : Record<SkillCombined>) : Maybe<number> => {
|
||||
// const id = SCA_.id (entry)
|
||||
// const { CraftInstruments } = SpecialAbilityId
|
||||
export const getSkillValue = (dynamic: Rated | undefined): number => dynamic?.value ?? 0
|
||||
|
||||
// if ((id === SkillId.Woodworking || id === SkillId.Metalworking)
|
||||
// && isMaybeActive (lookupF (HA.specialAbilities (state))
|
||||
// (CraftInstruments))) {
|
||||
// // Sum of Woodworking and Metalworking must be at least 12.
|
||||
// const MINIMUM_SUM = 12
|
||||
const getSkillMinimumByCraftInstruments = (
|
||||
skills: RatedMap,
|
||||
dynamicSkill: Rated,
|
||||
craftInstruments: Activatable | undefined,
|
||||
) => {
|
||||
if (
|
||||
(dynamicSkill.id === SkillIdentifier.Woodworking ||
|
||||
dynamicSkill.id === SkillIdentifier.Metalworking) &&
|
||||
isActive(craftInstruments)
|
||||
) {
|
||||
// Sum of Woodworking and Metalworking must be at least 12.
|
||||
const MINIMUM_SUM = 12
|
||||
const otherSkillId =
|
||||
dynamicSkill.id === SkillIdentifier.Woodworking
|
||||
? SkillIdentifier.Metalworking
|
||||
: SkillIdentifier.Woodworking
|
||||
const otherSkillRating = getSkillValue(skills[otherSkillId])
|
||||
return MINIMUM_SUM - otherSkillRating
|
||||
}
|
||||
|
||||
// const otherSkillId = id === SkillId.Woodworking
|
||||
// ? SkillId.Metalworking
|
||||
// : SkillId.Woodworking
|
||||
return undefined
|
||||
}
|
||||
|
||||
// const otherSkillRating = pipe_ (
|
||||
// state,
|
||||
// HA.skills,
|
||||
// lookup <string> (otherSkillId),
|
||||
// fmap (SDA.value),
|
||||
// sum
|
||||
// )
|
||||
export const getSkillMinimum = (
|
||||
skills: RatedMap,
|
||||
dynamicSkill: Rated,
|
||||
craftInstruments: Activatable | undefined,
|
||||
filterApplyingDependencies: (dependencies: Dependency[]) => Dependency[],
|
||||
): number => {
|
||||
const minimumValues: number[][] = [
|
||||
[0],
|
||||
flattenMinimumRestrictions(filterApplyingDependencies(dynamicSkill.dependencies)),
|
||||
mapNullable(getSkillMinimumByCraftInstruments(skills, dynamicSkill, craftInstruments), min => [
|
||||
min,
|
||||
]) ?? [],
|
||||
]
|
||||
|
||||
// return Just (MINIMUM_SUM - otherSkillRating)
|
||||
// }
|
||||
|
||||
// return Nothing
|
||||
// }
|
||||
|
||||
|
||||
// /**
|
||||
// * Check if the dependencies allow the passed skill to be decreased.
|
||||
// */
|
||||
// const getMinSRByDeps = (staticData : StaticDataRecord) =>
|
||||
// (hero : HeroModelRecord) =>
|
||||
// (entry : Record<SkillCombined>) : Maybe<number> =>
|
||||
// pipe_ (
|
||||
// entry,
|
||||
// SCA_.dependencies,
|
||||
// flattenDependencies (staticData) (hero),
|
||||
// ensure (notNull),
|
||||
// fmap (maximum)
|
||||
// )
|
||||
|
||||
export const getSkillMinimum = (): number => {
|
||||
const minimumValues = filterNonNullable([
|
||||
0,
|
||||
// TODO: getMinSRByDeps (staticData) (hero) (entry),
|
||||
// TODO: getMinSRByCraftInstruments (hero) (entry)
|
||||
])
|
||||
|
||||
return Math.max(...minimumValues)
|
||||
return Math.max(...minimumValues.flat())
|
||||
}
|
||||
|
||||
export const getSkillMaximum = (
|
||||
@@ -80,24 +69,17 @@ export const getSkillMaximum = (
|
||||
return Math.min(...maximumValues) + exceptionalSkillBonus
|
||||
}
|
||||
|
||||
export const isSkillDecreasable = (
|
||||
dynamic: Rated,
|
||||
min: number,
|
||||
canRemove: boolean,
|
||||
) => min < dynamic.value && canRemove
|
||||
export const isSkillDecreasable = (dynamic: Rated, min: number, canRemove: boolean) =>
|
||||
min < dynamic.value && canRemove
|
||||
|
||||
export const isSkillIncreasable = (
|
||||
dynamic: Rated,
|
||||
max: number,
|
||||
) =>
|
||||
dynamic.value < max
|
||||
export const isSkillIncreasable = (dynamic: Rated, max: number) => dynamic.value < max
|
||||
|
||||
export const getSkillCommonness = (
|
||||
culture: Culture,
|
||||
skill: Skill
|
||||
skill: Skill,
|
||||
): "common" | "uncommon" | undefined =>
|
||||
culture.common_skills.some(({ id: { skill: id } }) => skill.id === id)
|
||||
? "common"
|
||||
: culture.uncommon_skills?.some(({ id: { skill: id } }) => skill.id === id) ?? false
|
||||
? "uncommon"
|
||||
: undefined
|
||||
? "common"
|
||||
: culture.uncommon_skills?.some(({ id: { skill: id } }) => skill.id === id) ?? false
|
||||
? "uncommon"
|
||||
: undefined
|
||||
|
||||
@@ -1,40 +1,44 @@
|
||||
import { Attribute } from "optolith-database-schema/types/Attribute"
|
||||
import { SkillCheck } from "optolith-database-schema/types/_SkillCheck"
|
||||
import { attributeValue } from "../../main_window/slices/attributesSlice.ts"
|
||||
import { RatedMap } from "../../main_window/slices/characterSlice.ts"
|
||||
import { getAttributeValue } from "./attribute.ts"
|
||||
import { RatedMap } from "./ratedEntry.ts"
|
||||
|
||||
type Triple<T> = [T, T, T]
|
||||
|
||||
const zipTriple = <T, U, V>(
|
||||
[ a, b, c ]: Triple<T>,
|
||||
[ x, y, z ]: Triple<U>,
|
||||
[a, b, c]: Triple<T>,
|
||||
[x, y, z]: Triple<U>,
|
||||
f: (x: T, y: U) => V,
|
||||
): Triple<V> => [ f(a, x), f(b, y), f(c, z) ]
|
||||
): Triple<V> => [f(a, x), f(b, y), f(c, z)]
|
||||
|
||||
type SkillCheckAttributes = Triple<Attribute>
|
||||
type SkillCheckValues = Triple<number>
|
||||
type DisplayedSkillCheck = Triple<{ attribute: Attribute; value: number}>
|
||||
type DisplayedSkillCheck = Triple<{ attribute: Attribute; value: number }>
|
||||
|
||||
/**
|
||||
* Returns the static attributes of a skill check.
|
||||
*/
|
||||
export const getSkillCheckAttributes = (
|
||||
attributes: Record<number, Attribute>,
|
||||
check: SkillCheck,
|
||||
): SkillCheckAttributes =>
|
||||
[
|
||||
attributes[check[0].id.attribute]!,
|
||||
attributes[check[1].id.attribute]!,
|
||||
attributes[check[2].id.attribute]!,
|
||||
]
|
||||
): SkillCheckAttributes => [
|
||||
attributes[check[0].id.attribute]!,
|
||||
attributes[check[1].id.attribute]!,
|
||||
attributes[check[2].id.attribute]!,
|
||||
]
|
||||
|
||||
export const getSkillCheckValues = (
|
||||
attributes: RatedMap,
|
||||
check: SkillCheck,
|
||||
): SkillCheckValues =>
|
||||
[
|
||||
attributeValue(attributes[check[0].id.attribute]),
|
||||
attributeValue(attributes[check[1].id.attribute]),
|
||||
attributeValue(attributes[check[2].id.attribute]),
|
||||
]
|
||||
/**
|
||||
* Returns the attribute values of a skill check.
|
||||
*/
|
||||
export const getSkillCheckValues = (attributes: RatedMap, check: SkillCheck): SkillCheckValues => [
|
||||
getAttributeValue(attributes[check[0].id.attribute]),
|
||||
getAttributeValue(attributes[check[1].id.attribute]),
|
||||
getAttributeValue(attributes[check[2].id.attribute]),
|
||||
]
|
||||
|
||||
/**
|
||||
* Returns the attributes of a skill check, paired with their values.
|
||||
*/
|
||||
export const getDisplayedSkillCheck = (
|
||||
staticAttributes: Record<number, Attribute>,
|
||||
dynamicAttributes: RatedMap,
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import { ActivatableRated } from "./ratedEntry.ts"
|
||||
|
||||
export const getSpellValue = (dynamic: ActivatableRated | undefined): number | undefined =>
|
||||
dynamic?.value
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": false,
|
||||
"noUnusedParameters": true,
|
||||
"outDir": "app/",
|
||||
"removeComments": true,
|
||||
"resolveJsonModule": true,
|
||||
|
||||
Reference in New Issue
Block a user