From 2840c39ffaba3a6d75f613a86960bb36c6ea0203 Mon Sep 17 00:00:00 2001 From: frzyc Date: Sun, 3 Nov 2024 00:46:52 -0400 Subject: [PATCH 01/11] preliminary targeted conditional solution --- .../database/src/lib/DataManagerBase.ts | 3 +- libs/common/util/src/lib/array.ts | 17 + .../src/components/ConditionalDisplay.tsx | 413 ++++++++++++++++++ .../src/components/DocumentDisplay.tsx | 300 +------------ .../ui-sheet/src/components/FieldDisplay.tsx | 7 +- .../ui-sheet/src/components/HeaderDisplay.tsx | 21 + libs/pando/ui-sheet/src/components/index.ts | 1 + libs/pando/ui-sheet/src/context/TagContext.ts | 4 + libs/pando/ui-sheet/src/context/index.ts | 1 + libs/pando/ui-sheet/src/types/conditional.ts | 1 + .../Database/DataManagers/TeamDataManager.ts | 48 +- .../sr/formula-ui/src/char/sheets/RuanMei.tsx | 10 +- libs/sr/formula-ui/src/index.ts | 1 + .../WatchmakerMasterOfDreamMachinations.tsx | 14 +- libs/sr/formula-ui/src/relic/cavern/index.ts | 7 - libs/sr/formula-ui/src/relic/index.ts | 7 + .../WatchmakerMasterOfDreamMachinations.ts | 4 +- libs/sr/page-team/src/RelicSheetDisplay.tsx | 68 +++ libs/sr/page-team/src/RelicSheetsDisplay.tsx | 21 + libs/sr/page-team/src/TalentContent.tsx | 32 +- libs/sr/page-team/src/TeamCalcProvider.tsx | 33 +- libs/sr/page-team/src/TeammateDisplay.tsx | 171 ++++---- libs/sr/page-team/src/index.tsx | 167 +++++-- libs/sr/ui/src/Character/CharacterCard.tsx | 29 +- libs/sr/ui/src/Character/CharacterTrans.tsx | 12 + libs/sr/ui/src/Character/index.tsx | 1 + libs/sr/ui/src/Hook/useSrCalcContext.ts | 9 +- libs/sr/ui/src/Relic/index.tsx | 1 + libs/sr/util/src/relic.ts | 21 +- 29 files changed, 912 insertions(+), 512 deletions(-) create mode 100644 libs/pando/ui-sheet/src/components/ConditionalDisplay.tsx create mode 100644 libs/pando/ui-sheet/src/components/HeaderDisplay.tsx create mode 100644 libs/pando/ui-sheet/src/context/TagContext.ts delete mode 100644 libs/sr/formula-ui/src/relic/cavern/index.ts create mode 100644 libs/sr/formula-ui/src/relic/index.ts create mode 100644 libs/sr/page-team/src/RelicSheetDisplay.tsx create mode 100644 libs/sr/page-team/src/RelicSheetsDisplay.tsx create mode 100644 libs/sr/ui/src/Character/CharacterTrans.tsx diff --git a/libs/common/database/src/lib/DataManagerBase.ts b/libs/common/database/src/lib/DataManagerBase.ts index 1fe418efea..be90371a69 100644 --- a/libs/common/database/src/lib/DataManagerBase.ts +++ b/libs/common/database/src/lib/DataManagerBase.ts @@ -75,7 +75,7 @@ export class DataManagerBase< key: CacheKey, valueOrFunc: | Partial - | ((v: StorageValue) => Partial | void), + | ((v: StorageValue) => Partial | void | false), notify = true ): boolean { const old = this.getStorage(key) @@ -85,6 +85,7 @@ export class DataManagerBase< } const value = typeof valueOrFunc === 'function' ? valueOrFunc(old) ?? old : valueOrFunc + if (value === false) return false const validated = this.validate({ ...(old ?? {}), ...value }, key) if (!validated) { this.trigger(key, 'invalid', value) diff --git a/libs/common/util/src/lib/array.ts b/libs/common/util/src/lib/array.ts index e4a9e6101a..216f146a62 100644 --- a/libs/common/util/src/lib/array.ts +++ b/libs/common/util/src/lib/array.ts @@ -89,3 +89,20 @@ export function pruneOrPadArray(array: T[], length: number, value: T) { else array.push(...new Array(length - array.length).fill(value)) return array } + +/** + * Move an element in the array to the front, if it exists. + * @param arr + * @param key + * @returns + */ +export function moveToFront(arr: T[], key: T): T[] { + const index = arr.indexOf(key) + if (index > -1) { + // Remove the element from its current position + const [element] = arr.splice(index, 1) + // Add the element to the front of the array + arr.unshift(element) + } + return arr +} diff --git a/libs/pando/ui-sheet/src/components/ConditionalDisplay.tsx b/libs/pando/ui-sheet/src/components/ConditionalDisplay.tsx new file mode 100644 index 0000000000..5d7e1dc206 --- /dev/null +++ b/libs/pando/ui-sheet/src/components/ConditionalDisplay.tsx @@ -0,0 +1,413 @@ +'use client' +import type { + IListConditionalData, + INumConditionalData, +} from '@genshin-optimizer/common/formula' +import type { CardBackgroundColor } from '@genshin-optimizer/common/ui' +import { + CardThemed, + DropdownButton, + NumberInputLazy, + SqBadge, +} from '@genshin-optimizer/common/ui' +import { evalIfFunc } from '@genshin-optimizer/common/util' +import ArrowRightAltIcon from '@mui/icons-material/ArrowRightAlt' +import CheckBoxIcon from '@mui/icons-material/CheckBox' +import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank' +import type { SliderProps } from '@mui/material' +import { + Box, + Button, + Divider, + MenuItem, + Slider, + Stack, + Typography, +} from '@mui/material' +import type { ReactNode } from 'react' +import { + createContext, + memo, + useCallback, + useContext, + useMemo, + useState, +} from 'react' +import { CalcContext, TagContext } from '../context' +import type { Conditional } from '../types' +import { FieldsDisplay } from './FieldDisplay' +import { HeaderDisplay } from './HeaderDisplay' + +export const ConditionalValuesContext = createContext>([]) +type CondValue = { + sheet: string + condKey: string + condValue: number + src: string + dst: string +} +export function ConditionalsDisplay({ + conditional, + bgt, +}: { + bgt?: CardBackgroundColor + conditional: Conditional +}) { + const { srcDisplay, dstDisplay } = useContext(SrcDstDisplayContext) + const setConditional = useContext(SetConditionalContext) + const { + metadata: { sheet, name }, + targeted, + } = conditional + const conditionals = useContext(ConditionalValuesContext) + // Allowing showing an "empty" conditional UI for user to add new conditionals + + const filteredConditionals = useMemo( + () => + conditionals.filter( + ({ condValue, sheet: s, condKey }) => + condValue && s === sheet && condKey === name + ), + [conditionals, sheet, name] + ) + + const hasExisting = useCallback( + (src: string, dst: string) => + filteredConditionals.some(({ src: s, dst: d }) => s === src && d === dst), + [filteredConditionals] + ) + + const [src, setSrc] = useState(Object.keys(srcDisplay)[0]) + const [dst, setDst] = useState(Object.keys(dstDisplay)[0]) + return ( + + {filteredConditionals.map(({ src, dst, condValue }) => ( + setConditional(sheet, name, src, dst, v)} + bgt={bgt} + /> + ))} + {/* // empty default conditional UI */} + {(targeted || !filteredConditionals.length) && ( + setConditional(sheet, name, src, dst, v)} + disabled={hasExisting(src, dst)} + /> + )} + + ) +} + +export const SrcDstDisplayContext = createContext<{ + srcDisplay: Record + dstDisplay: Record +}>({ + srcDisplay: {}, + dstDisplay: {}, +}) +export type SetConditionalFunc = ( + sheet: string, + condKey: string, + src: string, + dst: string, + value: number +) => void +export const SetConditionalContext = createContext(() => + console.warn('SetConditional NOT IMPLEMENTED') +) +const ConditionalDisplay = memo(function ConditionalDisplay({ + conditional, + src, + setSrc, + dst, + setDst, + value, + setValue, + bgt = 'normal', + disabled, +}: { + conditional: Conditional + src: string + setSrc?: (src: string) => void + dst: string + setDst?: (dst: string) => void + value: number + setValue: (value: number) => void + bgt?: CardBackgroundColor + disabled?: boolean +}) { + const { header, fields, targeted } = conditional + const { srcDisplay, dstDisplay } = useContext(SrcDstDisplayContext) + const tag = useContext(TagContext) + const newTag = useMemo( + () => ({ + ...tag, + src, + dst, + }), + [tag, src, dst] + ) + return ( + + {!!header && } + {targeted && ( + + )} + + {!!fields && ( + + + + )} + + ) +}) +type ConditionalProps = { + conditional: Conditional + setValue: (value: number) => void + value: number + disabled?: boolean +} +function ConditionalSelector(props: ConditionalProps) { + switch (props.conditional.metadata.type) { + case 'bool': + return + case 'list': + return + case 'num': + return + default: + return null + } +} +function Badge({ children }: { children: ReactNode }) { + if (!children) return null + return {children} +} + +function BoolConditional({ + conditional, + setValue, + value, + disabled, +}: ConditionalProps) { + const calc = useContext(CalcContext) + const { label, badge } = conditional + const { sheet: sheetKey, name: condKey } = conditional.metadata + if (!sheetKey || !condKey) throw new Error('metadata missing') + if (!calc) return null + + const labelEle = evalIfFunc(label, calc, value) + const badgeEle = evalIfFunc(badge, calc, value) + return ( + + ) +} +function ListConditional({ + conditional, + setValue, + value: value, + disabled, +}: ConditionalProps) { + const calc = useContext(CalcContext) + const { label, badge } = conditional + const { + sheet: sheetKey, + name: condKey, + list, + } = conditional.metadata as IListConditionalData + if (!sheetKey || !condKey) throw new Error('metadata missing') + if (!calc) return null + + return ( + + {evalIfFunc(label, calc, value)}{' '} + {evalIfFunc(badge, calc, value)} + + } + disabled={disabled} + > + + {['0', ...list].map((val, ind) => ( + setValue(ind)} + selected={value === ind} + disabled={value === ind} + > + {evalIfFunc(label, calc, ind)} + {evalIfFunc(badge, calc, ind)} + + ))} + + ) +} + +function NumConditional({ + conditional, + setValue, + value, + disabled, +}: ConditionalProps) { + const calc = useContext(CalcContext) + const { label, badge } = conditional + const { + sheet: sheetKey, + name: condKey, + int_only, + min, + max, + } = conditional.metadata as INumConditionalData + if (!sheetKey || !condKey) throw new Error('metadata missing') + if (!calc) return null + + const labelEle = evalIfFunc(label, calc, value) + const badgeEle = evalIfFunc(badge, calc, value) + if (typeof min === 'undefined' || typeof max === 'undefined') + return ( + {labelEle}, + endAdornment: {evalIfFunc(badge, calc, value)}, + }} + value={value} + onChange={(newVal) => setValue(newVal)} + disabled={disabled} + /> + ) + return ( + + {(labelEle || badge) && ( + + {labelEle} {{badgeEle}} + + )} + setInnerValue(v as number)} + onChangeCommitted={(_e, v) => setValue(v as number)} + valueLabelDisplay="auto" + disabled={disabled} + /> + + ) +} +function CondSlider(props: Omit) { + const [innerValue, setInnerValue] = useState(props.value) + return ( + setInnerValue(v as number)} + value={innerValue} + /> + ) +} + +function CondSrcDst({ + src, + srcDisplay, + setSrc, + dst, + dstDisplay, + setDst, +}: { + src: S + srcDisplay: Record + setSrc?: (src: S) => void + dst: D + dstDisplay: Record + setDst?: (dst: D) => void +}) { + if (!Object.keys(srcDisplay).length || !Object.keys(dstDisplay).length) + return null + return ( + + {setSrc ? ( + + ) : ( + srcDisplay[src] + )} + + {setDst ? ( + + ) : ( + dstDisplay[dst] + )} + + ) +} + +function SrcDstDropDown({ + target, + targetMap, + onChange, +}: { + target: K + targetMap: Record + onChange: (target: K) => void +}) { + const onlyOption = Object.keys(targetMap).length === 1 + return ( + + {Object.entries(targetMap).map(([key, display]) => ( + onChange(key as K)}> + {display} + + ))} + + ) +} diff --git a/libs/pando/ui-sheet/src/components/DocumentDisplay.tsx b/libs/pando/ui-sheet/src/components/DocumentDisplay.tsx index 28f272c97d..50a1db7e55 100644 --- a/libs/pando/ui-sheet/src/components/DocumentDisplay.tsx +++ b/libs/pando/ui-sheet/src/components/DocumentDisplay.tsx @@ -1,61 +1,24 @@ 'use client' -import type { - IListConditionalData, - INumConditionalData, -} from '@genshin-optimizer/common/formula' import type { CardBackgroundColor } from '@genshin-optimizer/common/ui' -import { - CardHeaderCustom, - CardThemed, - DropdownButton, - NumberInputLazy, - SqBadge, -} from '@genshin-optimizer/common/ui' +import { CardThemed } from '@genshin-optimizer/common/ui' import { evalIfFunc } from '@genshin-optimizer/common/util' -import type { Calculator } from '@genshin-optimizer/pando/engine' -import { read } from '@genshin-optimizer/pando/engine' -import CheckBoxIcon from '@mui/icons-material/CheckBox' -import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank' import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown' -import type { SliderProps } from '@mui/material' -import { - Box, - Button, - Collapse, - Divider, - MenuItem, - Slider, - Typography, -} from '@mui/material' -import type { ReactNode } from 'react' +import { Box, Collapse } from '@mui/material' import { useContext, useState } from 'react' import { CalcContext } from '../context' -import type { - Conditional, - Document, - FieldsDocument, - Header, - TextDocument, -} from '../types' +import type { Document, FieldsDocument, TextDocument } from '../types' +import { ConditionalsDisplay } from './ConditionalDisplay' import { FieldsDisplay } from './FieldDisplay' - -type SetConditionalFunc = ( - srcKey: string, - sheetKey: string, - condKey: string, - value: number -) => void +import { HeaderDisplay } from './HeaderDisplay' export function DocumentDisplay({ document, bgt = 'normal', collapse = false, - setConditional, }: { document: Document bgt?: CardBackgroundColor collapse?: boolean - setConditional: SetConditionalFunc }) { switch (document.type) { case 'fields': @@ -68,9 +31,8 @@ export function DocumentDisplay({ ) case 'conditional': return ( - ) } - -export function HeaderDisplay({ - header, - hideDivider, -}: { - header: Header - hideDivider?: boolean | ((section: Document) => boolean) -}) { - const { icon, text: title, additional: action } = header - - return ( - <> - - {!hideDivider && } - - ) -} - -function ConditionalDisplay({ - conditional, - bgt = 'normal', - setConditional, -}: { - conditional: Conditional - bgt?: CardBackgroundColor - setConditional: SetConditionalFunc -}) { - const { header, fields } = conditional - return ( - - {!!header && } - - {!!fields && } - - ) -} -function ConditionalSelector({ - conditional, - setConditional, -}: { - conditional: Conditional - setConditional: SetConditionalFunc -}) { - switch (conditional.metadata.type) { - case 'bool': - return ( - - ) - case 'list': - return ( - - ) - case 'num': - return ( - - ) - default: - return null - } -} -function Badge({ children }: { children: ReactNode }) { - if (!children) return null - return {children} -} -function getConditionalValue( - calc: Calculator, - sheetKey: string, - condKey: string, - srcKey: string -) { - return calc.compute( - read( - { - et: 'own', - qt: 'cond', - sheet: sheetKey, - q: condKey, - src: srcKey, - dst: calc.cache.tag.src, - }, - 'max' - ) - ).val -} -function BoolConditional({ - conditional, - setConditional, -}: { - conditional: Conditional - setConditional: SetConditionalFunc -}) { - const calc = useContext(CalcContext) - const { label, badge } = conditional - const { sheet: sheetKey, name: condKey } = conditional.metadata - if (!sheetKey || !condKey) throw new Error('metadata missing') - if (!calc) return null - const srcKey = 'all' - const conditionalValue = getConditionalValue(calc, sheetKey, condKey, srcKey) - const labelEle = evalIfFunc(label, calc, conditionalValue) - const badgeEle = evalIfFunc(badge, calc, conditionalValue) - return ( - - ) -} -function ListConditional({ - conditional, - setConditional, -}: { - conditional: Conditional - setConditional: SetConditionalFunc -}) { - const calc = useContext(CalcContext) - const { label, badge } = conditional - const { - sheet: sheetKey, - name: condKey, - list, - } = conditional.metadata as IListConditionalData - if (!sheetKey || !condKey) throw new Error('metadata missing') - if (!calc) return null - const srcKey = 'all' - - const conditionalValue = getConditionalValue(calc, sheetKey, condKey, srcKey) - - return ( - - {evalIfFunc(label, calc, conditionalValue)}{' '} - {evalIfFunc(badge, calc, conditionalValue)} - - } - // disabled={disabled} - > - - {['0', ...list].map((val, ind) => ( - setConditional(srcKey, sheetKey, condKey, ind)} - selected={conditionalValue === ind} - disabled={conditionalValue === ind} - > - {evalIfFunc(label, calc, ind)} - {evalIfFunc(badge, calc, ind)} - - ))} - - ) -} - -function NumConditional({ - conditional, - setConditional, -}: { - conditional: Conditional - setConditional: SetConditionalFunc -}) { - const calc = useContext(CalcContext) - const { label, badge } = conditional - const { - sheet: sheetKey, - name: condKey, - int_only, - min, - max, - } = conditional.metadata as INumConditionalData - if (!sheetKey || !condKey) throw new Error('metadata missing') - if (!calc) return null - const srcKey = 'all' - - const conditionalValue = getConditionalValue(calc, sheetKey, condKey, srcKey) - const labelEle = evalIfFunc(label, calc, conditionalValue) - const badgeEle = evalIfFunc(badge, calc, conditionalValue) - if (typeof min === 'undefined' || typeof max === 'undefined') - return ( - {labelEle}, - endAdornment: ( - {evalIfFunc(badge, calc, conditionalValue)} - ), - }} - value={conditionalValue} - onChange={(newVal) => setConditional(srcKey, sheetKey, condKey, newVal)} - /> - ) - return ( - - {(labelEle || badge) && ( - - {labelEle} {{badgeEle}} - - )} - setInnerValue(v as number)} - onChangeCommitted={(_e, v) => - setConditional(srcKey, sheetKey, condKey, v as number) - } - valueLabelDisplay="auto" - /> - - ) -} -function CondSlider(props: Omit) { - const [innerValue, setInnerValue] = useState(props.value) - return ( - setInnerValue(v as number)} - value={innerValue} - /> - ) -} diff --git a/libs/pando/ui-sheet/src/components/FieldDisplay.tsx b/libs/pando/ui-sheet/src/components/FieldDisplay.tsx index 0ddae7c1aa..9e0ce1b9b8 100644 --- a/libs/pando/ui-sheet/src/components/FieldDisplay.tsx +++ b/libs/pando/ui-sheet/src/components/FieldDisplay.tsx @@ -10,7 +10,7 @@ import type { ListProps, Palette, PaletteColor } from '@mui/material' import { Box, List, ListItem, Typography, styled } from '@mui/material' import type { ReactNode } from 'react' import React, { useContext } from 'react' -import { CalcContext } from '../context' +import { CalcContext, TagContext } from '../context' import type { Field, TagField, TextField } from '../types' export function FieldsDisplay({ @@ -88,12 +88,15 @@ export function TagFieldDisplay({ showZero?: boolean }) { const calc = useContext(CalcContext) + const tag = useContext(TagContext) // const compareCalc: null | Calculator = null //TODO: compare calcs if (!calc) return null // if (!calc && !compareCalc) return null //TODO: undefined: we assume "unique" accumulator - const valueCalcRes = calc.compute(read(field.fieldRef, undefined)) + const valueCalcRes = calc + .withTag(tag) + .compute(read(field.fieldRef, undefined)) // const compareValueCalcRes: CalcResult | null = null // const { setFormulaData } = useContext(FormulaDataContext) diff --git a/libs/pando/ui-sheet/src/components/HeaderDisplay.tsx b/libs/pando/ui-sheet/src/components/HeaderDisplay.tsx new file mode 100644 index 0000000000..ab87fc6392 --- /dev/null +++ b/libs/pando/ui-sheet/src/components/HeaderDisplay.tsx @@ -0,0 +1,21 @@ +'use client' +import { CardHeaderCustom } from '@genshin-optimizer/common/ui' +import { Divider } from '@mui/material' +import type { Document, Header } from '../types' + +export function HeaderDisplay({ + header, + hideDivider, +}: { + header: Header + hideDivider?: boolean | ((section: Document) => boolean) +}) { + const { icon, text: title, additional: action } = header + + return ( + <> + + {!hideDivider && } + + ) +} diff --git a/libs/pando/ui-sheet/src/components/index.ts b/libs/pando/ui-sheet/src/components/index.ts index c60240978f..33f8106ccc 100644 --- a/libs/pando/ui-sheet/src/components/index.ts +++ b/libs/pando/ui-sheet/src/components/index.ts @@ -1,2 +1,3 @@ +export * from './ConditionalDisplay' export * from './DocumentDisplay' export * from './FieldDisplay' diff --git a/libs/pando/ui-sheet/src/context/TagContext.ts b/libs/pando/ui-sheet/src/context/TagContext.ts new file mode 100644 index 0000000000..5f3ccd8d14 --- /dev/null +++ b/libs/pando/ui-sheet/src/context/TagContext.ts @@ -0,0 +1,4 @@ +import type { Tag } from '@genshin-optimizer/pando/engine' +import { createContext } from 'react' + +export const TagContext = createContext({} as Tag) diff --git a/libs/pando/ui-sheet/src/context/index.ts b/libs/pando/ui-sheet/src/context/index.ts index fcbf0a1487..bf72341ce7 100644 --- a/libs/pando/ui-sheet/src/context/index.ts +++ b/libs/pando/ui-sheet/src/context/index.ts @@ -1 +1,2 @@ export * from './CalcContext' +export * from './TagContext' diff --git a/libs/pando/ui-sheet/src/types/conditional.ts b/libs/pando/ui-sheet/src/types/conditional.ts index 5c0c12bc33..c7715c71e0 100644 --- a/libs/pando/ui-sheet/src/types/conditional.ts +++ b/libs/pando/ui-sheet/src/types/conditional.ts @@ -10,4 +10,5 @@ export type Conditional = { badge?: ReactNode | ((calc: Calculator, value: number) => ReactNode) header?: Header fields?: Field[] + targeted?: boolean } diff --git a/libs/sr/db/src/Database/DataManagers/TeamDataManager.ts b/libs/sr/db/src/Database/DataManagers/TeamDataManager.ts index 93d6ae5b60..7e884685a4 100644 --- a/libs/sr/db/src/Database/DataManagers/TeamDataManager.ts +++ b/libs/sr/db/src/Database/DataManagers/TeamDataManager.ts @@ -74,7 +74,7 @@ export class TeamDataManager extends DataManager { teamMetadata, lastEdit, frames, - conditionals: conditional, + conditionals, bonusStats, statConstraints, } = obj as Team @@ -166,20 +166,22 @@ export class TeamDataManager extends DataManager { frames = frames.filter(validateTag) const framesLength = frames.length if (!framesLength) { - conditional = [] + conditionals = [] bonusStats = [] } else { - if (!Array.isArray(conditional)) conditional = [] - if (!Array.isArray(bonusStats)) bonusStats = [] - if (!Array.isArray(statConstraints)) statConstraints = [] - conditional = conditional.filter(({ condValues }) => { + if (!Array.isArray(conditionals)) conditionals = [] + conditionals = conditionals.filter(({ src, dst, condValues }) => { // TODO: validate conditionals src dst condKey + if (!allCharacterKeys.includes(src as CharacterKey)) return false + if (!allCharacterKeys.includes(dst as CharacterKey)) return false if (!Array.isArray(condValues)) return false pruneOrPadArray(condValues, framesLength, 0) // If all values are false, remove the conditional if (condValues.every((v) => !v)) return false return true }) + + if (!Array.isArray(bonusStats)) bonusStats = [] bonusStats = bonusStats.filter(({ tag, values }) => { if (!validateTag(tag)) return false if (!Array.isArray(values)) return false @@ -187,6 +189,7 @@ export class TeamDataManager extends DataManager { return true }) + if (!Array.isArray(statConstraints)) statConstraints = [] statConstraints = statConstraints.filter(({ tag, values, isMaxs }) => { if (!validateTag(tag)) return false if (!Array.isArray(values)) return false @@ -206,7 +209,7 @@ export class TeamDataManager extends DataManager { teamMetadata: teamMetadata, lastEdit, frames, - conditionals: conditional, + conditionals, bonusStats, statConstraints, } @@ -373,18 +376,26 @@ export class TeamDataManager extends DataManager { setConditional( teamId: string, sheet: Sheet, + condKey: string, src: Member | 'all', dst: Member | 'all', - condKey: string, condValue: number, frameIndex: number ) { this.set(teamId, (team) => { + if (frameIndex > team.frames.length) return false const condIndex = team.conditionals.findIndex( - (c) => c.src === src && c.dst === dst && c.condKey === condKey + (c) => + c.condKey === condKey && + c.sheet === sheet && + c.src === src && + c.dst === dst ) - if (frameIndex > team.frames.length) return + console.log({ + condIndex, + }) if (condIndex === -1) { + console.log('creating new conditional') const condValues = new Array(team.frames.length).fill(0) condValues[frameIndex] = condValue team.conditionals.push({ @@ -395,8 +406,23 @@ export class TeamDataManager extends DataManager { condValues, }) } else { - team.conditionals[condIndex].condValues[frameIndex] = condValue + const cond = team.conditionals[condIndex] + // Check if the value is the same, return false to not propagate the update. + if ( + cond.sheet === sheet && + cond.src === src && + cond.dst === dst && + cond.condKey === condKey && + cond.condValues[frameIndex] === condValue + ) + return false + cond.sheet = sheet + cond.src = src + cond.dst = dst + cond.condKey = condKey + cond.condValues[frameIndex] = condValue } + return team }) } /** diff --git a/libs/sr/formula-ui/src/char/sheets/RuanMei.tsx b/libs/sr/formula-ui/src/char/sheets/RuanMei.tsx index eceb991336..366c74d7c1 100644 --- a/libs/sr/formula-ui/src/char/sheets/RuanMei.tsx +++ b/libs/sr/formula-ui/src/char/sheets/RuanMei.tsx @@ -2,6 +2,7 @@ import { ColorText, ImgIcon, SqBadge } from '@genshin-optimizer/common/ui' import type { UISheet } from '@genshin-optimizer/pando/ui-sheet' import { characterAsset } from '@genshin-optimizer/sr/assets' import type { CharacterKey } from '@genshin-optimizer/sr/consts' +import type { Tag } from '@genshin-optimizer/sr/formula' import { buffs, conditionals, @@ -20,6 +21,7 @@ const formula = formulas.RuanMei const cond = conditionals.RuanMei const buff = buffs.RuanMei const dm = mappedStats.char[key] +const selfTag: Tag = { src: key, dst: key } const sheet: UISheet = { basic: { title: chg('abilities.basic.0.name'), @@ -38,7 +40,7 @@ const sheet: UISheet = { getInterpolateObject( key, 'basic', - calculator.compute(own.char.basic).val + calculator.withTag(selfTag).compute(own.char.basic).val ) ), }, @@ -70,7 +72,7 @@ const sheet: UISheet = { getInterpolateObject( key, 'skill', - calculator.compute(own.char.skill).val + calculator.withTag(selfTag).compute(own.char.skill).val ) ), }, @@ -119,7 +121,7 @@ const sheet: UISheet = { getInterpolateObject( key, 'ult', - calculator.compute(own.char.ult).val + calculator.withTag(selfTag).compute(own.char.ult).val ) ), }, @@ -174,7 +176,7 @@ const sheet: UISheet = { getInterpolateObject( key, 'talent', - calculator.compute(own.char.talent).val + calculator.withTag(selfTag).compute(own.char.talent).val ) ), }, diff --git a/libs/sr/formula-ui/src/index.ts b/libs/sr/formula-ui/src/index.ts index 22fe72f72c..27ea9be52f 100644 --- a/libs/sr/formula-ui/src/index.ts +++ b/libs/sr/formula-ui/src/index.ts @@ -1 +1,2 @@ export * from './char' +export * from './relic' diff --git a/libs/sr/formula-ui/src/relic/cavern/WatchmakerMasterOfDreamMachinations.tsx b/libs/sr/formula-ui/src/relic/cavern/WatchmakerMasterOfDreamMachinations.tsx index 5a9f51187b..987335c6af 100644 --- a/libs/sr/formula-ui/src/relic/cavern/WatchmakerMasterOfDreamMachinations.tsx +++ b/libs/sr/formula-ui/src/relic/cavern/WatchmakerMasterOfDreamMachinations.tsx @@ -5,17 +5,18 @@ import type { RelicSetKey } from '@genshin-optimizer/sr/consts' import { buffs, conditionals } from '@genshin-optimizer/sr/formula' import { mappedStats } from '@genshin-optimizer/sr/stats' import { StatDisplay } from '@genshin-optimizer/sr/ui' +import { getDefaultRelicSlot } from '@genshin-optimizer/sr/util' import { trans } from '../../util' const key: RelicSetKey = 'WatchmakerMasterOfDreamMachinations' const [chg, _ch] = trans('relic', key) const dm = mappedStats.relic[key] -const headAsset = relicAsset(key, 'head') -const cond = conditionals[key] // TODO: no conditionals for relic in meta.ts -const buff = buffs[key] // TODO: no buffs for relic in meta.ts +const icon = relicAsset(key, getDefaultRelicSlot(key)) +const cond = conditionals[key] +const buff = buffs[key] const sheet: UISheet<'2' | '4'> = { 2: { title: '2-Set', // TODO: L10n - img: headAsset, + img: icon, documents: [ { type: 'text', @@ -34,7 +35,7 @@ const sheet: UISheet<'2' | '4'> = { }, 4: { title: '4-Set', // TODO: L10n - img: relicAsset(key, 'head'), + img: icon, documents: [ { type: 'text', @@ -43,8 +44,9 @@ const sheet: UISheet<'2' | '4'> = { { type: 'conditional', conditional: { + targeted: true, header: { - icon: , + icon: , text: 'Use Their Ult on an ally', // TODO: L10n additional: 4-Set, // TODO: L10n }, diff --git a/libs/sr/formula-ui/src/relic/cavern/index.ts b/libs/sr/formula-ui/src/relic/cavern/index.ts deleted file mode 100644 index 1611004520..0000000000 --- a/libs/sr/formula-ui/src/relic/cavern/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { UISheet } from '@genshin-optimizer/pando/ui-sheet' -import type { RelicSetKey } from '@genshin-optimizer/sr/consts' -import WatchmakerMasterOfDreamMachinations from './WatchmakerMasterOfDreamMachinations' - -export const uiSheets: Partial>> = { - WatchmakerMasterOfDreamMachinations, -} diff --git a/libs/sr/formula-ui/src/relic/index.ts b/libs/sr/formula-ui/src/relic/index.ts new file mode 100644 index 0000000000..1a84ba0e81 --- /dev/null +++ b/libs/sr/formula-ui/src/relic/index.ts @@ -0,0 +1,7 @@ +import type { UISheet } from '@genshin-optimizer/pando/ui-sheet' +import type { RelicSetKey } from '@genshin-optimizer/sr/consts' +import WatchmakerMasterOfDreamMachinations from './cavern/WatchmakerMasterOfDreamMachinations' + +export const relicUiSheets: Partial>> = { + WatchmakerMasterOfDreamMachinations, +} diff --git a/libs/sr/formula/src/data/relic/sheets/WatchmakerMasterOfDreamMachinations.ts b/libs/sr/formula/src/data/relic/sheets/WatchmakerMasterOfDreamMachinations.ts index 697c28fd42..dbe38fcba8 100644 --- a/libs/sr/formula/src/data/relic/sheets/WatchmakerMasterOfDreamMachinations.ts +++ b/libs/sr/formula/src/data/relic/sheets/WatchmakerMasterOfDreamMachinations.ts @@ -4,9 +4,9 @@ import { allStats, mappedStats } from '@genshin-optimizer/sr/stats' import { allBoolConditionals, own, - ownBuff, register, registerBuff, + teamBuff, } from '../../util' import { entriesForRelic } from '../util' @@ -26,7 +26,7 @@ const sheet = register( registerBuff( 'set4_brEffect_', - ownBuff.premod.brEffect_.add( + teamBuff.premod.brEffect_.add( useUltimateOnAlly.ifOn(cmpGE(relicCount, 4, dm[4].brEffect_)) ) ) diff --git a/libs/sr/page-team/src/RelicSheetDisplay.tsx b/libs/sr/page-team/src/RelicSheetDisplay.tsx new file mode 100644 index 0000000000..0b706455bd --- /dev/null +++ b/libs/sr/page-team/src/RelicSheetDisplay.tsx @@ -0,0 +1,68 @@ +import { CardThemed, NextImage, SqBadge } from '@genshin-optimizer/common/ui' +import type { UISheetElement } from '@genshin-optimizer/pando/ui-sheet' +import { DocumentDisplay } from '@genshin-optimizer/pando/ui-sheet' +import { relicAsset } from '@genshin-optimizer/sr/assets' +import type { RelicSetKey } from '@genshin-optimizer/sr/consts' +import { relicUiSheets } from '@genshin-optimizer/sr/formula-ui' +import { RelicSetName } from '@genshin-optimizer/sr/ui' +import { getDefaultRelicSlot, isCavernRelic } from '@genshin-optimizer/sr/util' +import { + Box, + CardContent, + CardHeader, + Divider, + Stack, + Typography, +} from '@mui/material' + +export function RelicSheetDisplay({ setKey }: { setKey: RelicSetKey }) { + const relicSheet = relicUiSheets[setKey] + if (!relicSheet) return null + return ( + + + + + + + {/* TODO: translate */} + + {isCavernRelic(setKey) ? 'Cavern' : 'Planar'} + + + + + {Object.entries(relicSheet).map(([key, uiSheetElement]) => ( + + ))} + + + + ) +} +export function RelicUiSheetElement({ + uiSheetElement, +}: { + uiSheetElement: UISheetElement +}) { + const { documents, title } = uiSheetElement + return ( + + + + + {documents.map((doc, i) => ( + + ))} + + + ) +} diff --git a/libs/sr/page-team/src/RelicSheetsDisplay.tsx b/libs/sr/page-team/src/RelicSheetsDisplay.tsx new file mode 100644 index 0000000000..f0c72c73a3 --- /dev/null +++ b/libs/sr/page-team/src/RelicSheetsDisplay.tsx @@ -0,0 +1,21 @@ +import { CardThemed } from '@genshin-optimizer/common/ui' +import type { RelicSetKey } from '@genshin-optimizer/sr/consts' +import { CardContent, Grid } from '@mui/material' +import { RelicSheetDisplay } from './RelicSheetDisplay' + +const sets: RelicSetKey[] = ['WatchmakerMasterOfDreamMachinations'] as const +export function RelicSheetsDisplay() { + return ( + + + + {sets.map((setKey) => ( + + + + ))} + + + + ) +} diff --git a/libs/sr/page-team/src/TalentContent.tsx b/libs/sr/page-team/src/TalentContent.tsx index ffa345fffd..cc9598b2bd 100644 --- a/libs/sr/page-team/src/TalentContent.tsx +++ b/libs/sr/page-team/src/TalentContent.tsx @@ -13,7 +13,6 @@ import { useCharacterContext, useDatabaseContext, } from '@genshin-optimizer/sr/db-ui' -import type { Member, Sheet } from '@genshin-optimizer/sr/formula' import { own } from '@genshin-optimizer/sr/formula' import { isTalentKey, @@ -32,9 +31,9 @@ import { useTheme, } from '@mui/material' import type { ReactNode } from 'react' -import { useCallback, useContext, useMemo } from 'react' +import { useCallback, useMemo } from 'react' import { useTranslation } from 'react-i18next' -import { PresetContext, TeamContext, useTeamContext } from './context' +import { useTeamContext } from './context' const talentSpacing = { xs: 12, @@ -127,7 +126,7 @@ export default function CharacterTalentPane() { lg={3} sx={{ display: 'flex', flexDirection: 'column', gap: 1 }} > - + {eidolonCards && eidolonCards.map((c, i) => ( = i + 1 ? 1 : 0.5 }}> @@ -188,7 +187,7 @@ export default function CharacterTalentPane() { {!grlg && ( - + {/* {constellationCards.map((c, i) => ( - // assume dst key to be the current character - database.teams.setConditional( - teamId, - sheetKey as Sheet, - srcKey as Member, - characterKey, - condKey, - condValue, - presetIndex - ), - - [teamId, database.teams, characterKey, presetIndex] - ) return ( @@ -342,7 +324,6 @@ function SkillDisplayCard({ document={doc} collapse // hideHeader={hideHeader} - setConditional={setConditional} /> ))} @@ -350,15 +331,12 @@ function SkillDisplayCard({ ) } -export function EidolonDropdown() { +export function EidolonDropdown({ eidolon }: { eidolon: number }) { const { t } = useTranslation('characters_gen') - const calc = useSrCalcContext() const { teammateDatum: { characterKey }, } = useTeamContext() const { database } = useDatabaseContext() - if (!calc) return null - const eidolon = calc.compute(own.char.eidolon).val return ( condValues.flatMap((condValue, frameIndex) => - withPreset( - `preset${frameIndex}` as Preset, - conditionalEntries(sheet, src, dst)(condKey, condValue) - ) + condValue + ? withPreset( + `preset${frameIndex}` as Preset, + conditionalEntries(sheet, src, dst)(condKey, condValue) + ) + : [] ) ), ...team.bonusStats.flatMap(({ tag, values }) => @@ -105,21 +102,7 @@ export function TeamCalcProvider({ [team, member0, member1, member2, member3] ) - const calcWithTag = useMemo( - () => - (currentChar && - calc?.withTag({ - src: currentChar, - dst: currentChar, - preset: `preset${presetIndex}` as Preset, - })) ?? - null, - [calc, currentChar, presetIndex] - ) - - return ( - {children} - ) + return {children} } function useCharacterAndEquipment( diff --git a/libs/sr/page-team/src/TeammateDisplay.tsx b/libs/sr/page-team/src/TeammateDisplay.tsx index 887ca29f0f..36a8bd08bc 100644 --- a/libs/sr/page-team/src/TeammateDisplay.tsx +++ b/libs/sr/page-team/src/TeammateDisplay.tsx @@ -30,6 +30,7 @@ import { BuildGridBase, BuildsDisplay } from './BuildsDisplay' import { ComboEditor } from './ComboEditor' import { useTeamContext } from './context' import Optimize from './Optimize' +import { RelicSheetsDisplay } from './RelicSheetsDisplay' import CharacterTalentPane from './TalentContent' export default function TeammateDisplay() { @@ -37,7 +38,6 @@ export default function TeammateDisplay() { teammateDatum: { characterKey }, } = useTeamContext() const character = useCharacterContext() - const calc = useSrCalcContext() const [editorKey, setCharacterKey] = useState( undefined ) @@ -66,84 +66,8 @@ export default function TeammateDisplay() { Edit Character - - }> - All target listings - - - - {calc - ?.listFormulas(own.listing.formulas) - .map((read, index) => { - const computed = calc.compute(read) - const name = read.tag.name || read.tag.q - return ( - - - {name}: {computed.val} - - - }> - debug for {name} - - - conds:{' '} - {JSON.stringify( - computed.meta.conds, - undefined, - 2 - )} - - {JSON.stringify( - calc.toDebug().compute(read), - undefined, - 2 - )} - - - - - ) - })} - - - - - }> - All target buffs - - - - {calc?.listFormulas(own.listing.buffs).map((read, index) => { - const computed = calc.compute(read) - const name = read.tag.name || read.tag.q - return ( - - - {name}: {computed.val} - - - }> - debug for {name} - - - conds:{' '} - {JSON.stringify(computed.meta.conds, undefined, 2)} - - {JSON.stringify( - calc.toDebug().compute(read), - undefined, - 2 - )} - - - - - ) - })} - - - + + @@ -199,3 +123,92 @@ function BuildsModal({ ) } +function RelicConditionals() { + const [show, onShow, onHide] = useBoolState() + return ( + <> + {/* TODO: translation */} + + + + + + ) +} +function CalcDebug() { + const calc = useSrCalcContext() + return ( + <> + + }> + All target listings + + + + {calc?.listFormulas(own.listing.formulas).map((read, index) => { + const computed = calc.compute(read) + const name = read.tag.name || read.tag.q + return ( + + + {name}: {computed.val} + + + }> + debug for {name} + + + conds: {JSON.stringify(computed.meta.conds, undefined, 2)} + + {JSON.stringify( + calc.toDebug().compute(read), + undefined, + 2 + )} + + + + + ) + })} + + + + + }> + All target buffs + + + + {calc?.listFormulas(own.listing.buffs).map((read, index) => { + const computed = calc.compute(read) + const name = read.tag.name || read.tag.q + return ( + + + {name}: {computed.val} + + + }> + debug for {name} + + + conds: {JSON.stringify(computed.meta.conds, undefined, 2)} + + {JSON.stringify( + calc.toDebug().compute(read), + undefined, + 2 + )} + + + + + ) + })} + + + + + ) +} diff --git a/libs/sr/page-team/src/index.tsx b/libs/sr/page-team/src/index.tsx index 16d884b1d9..f141be711a 100644 --- a/libs/sr/page-team/src/index.tsx +++ b/libs/sr/page-team/src/index.tsx @@ -1,12 +1,27 @@ import { CardThemed, useTitle } from '@genshin-optimizer/common/ui' +import { + moveToFront, + notEmpty, + objKeyMap, +} from '@genshin-optimizer/common/util' +import type { SetConditionalFunc } from '@genshin-optimizer/pando/ui-sheet' +import { + ConditionalValuesContext, + SetConditionalContext, + SrcDstDisplayContext, + TagContext, +} from '@genshin-optimizer/pando/ui-sheet' +import type { CharacterKey } from '@genshin-optimizer/sr/consts' import { CharacterContext, useCharacter, useDatabaseContext, useTeam, } from '@genshin-optimizer/sr/db-ui' +import type { Member, Preset, Sheet, Tag } from '@genshin-optimizer/sr/formula' +import { CharacterName } from '@genshin-optimizer/sr/ui' import { Box, Skeleton } from '@mui/material' -import { Suspense, useEffect, useMemo, useState } from 'react' +import { Suspense, useCallback, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { Navigate, @@ -48,6 +63,7 @@ export default function PageTeam() { function Page({ teamId }: { teamId: string }) { const navigate = useNavigate() + const { database } = useDatabaseContext() const [presetIndex, setPresetIndex] = useState(0) const presetObj = useMemo( () => @@ -105,51 +121,112 @@ function Page({ teamId }: { teamId: string }) { teammateDatum, } }, [teammateDatum, team, teamId]) - + const srcDstDisplayContextValue = useMemo(() => { + const charDisplay = objKeyMap( + moveToFront( + team.teamMetadata + .filter(notEmpty) + .map(({ characterKey }) => characterKey), + characterKey as CharacterKey + ), + (ck) => + ) + return { srcDisplay: charDisplay, dstDisplay: charDisplay } + }, [team.teamMetadata, characterKey]) + const conditionals = useMemo( + () => + team.conditionals.map(({ sheet, src, dst, condKey, condValues }) => ({ + sheet, + src, + dst, + condKey, + condValue: condValues[presetIndex], + })), + [presetIndex, team.conditionals] + ) + const setConditional = useCallback( + ( + sheet: string, + condKey: string, + src: string, + dst: string, + condValue: number + ) => { + database.teams.setConditional( + teamId, + sheet as Sheet, + condKey, + src as Member | 'all', + dst as Member | 'all', + condValue, + presetIndex + ) + }, + [database.teams, presetIndex, teamId] + ) + const tag = useMemo( + () => ({ + src: characterKey, + dst: characterKey, + preset: `preset${presetIndex}` as Preset, + }), + [characterKey, presetIndex] + ) return ( - - - - - - - { - // const elementKey = characterKey && allStats.char[characterKey] - // if (!elementKey) return {} - // const hex = theme.palette[elementKey].main as string - // const color = hexToColor(hex) - // if (!color) return {} - // const rgba = colorToRgbaString(color, 0.1) - // return { - // background: `linear-gradient(to bottom, ${rgba} 0%, rgba(0,0,0,0)) 25%`, - // } - // }} - > - {teamContextObj && ( - - - - )} - - - - + + + + + + + + + + + { + // const elementKey = characterKey && allStats.char[characterKey] + // if (!elementKey) return {} + // const hex = theme.palette[elementKey].main as string + // const color = hexToColor(hex) + // if (!color) return {} + // const rgba = colorToRgbaString(color, 0.1) + // return { + // background: `linear-gradient(to bottom, ${rgba} 0%, rgba(0,0,0,0)) 25%`, + // } + // }} + > + {teamContextObj && ( + + + + )} + + + + + + + + ) } diff --git a/libs/sr/ui/src/Character/CharacterCard.tsx b/libs/sr/ui/src/Character/CharacterCard.tsx index 9326b38612..d9dd2fb0c4 100644 --- a/libs/sr/ui/src/Character/CharacterCard.tsx +++ b/libs/sr/ui/src/Character/CharacterCard.tsx @@ -1,5 +1,6 @@ import { CardThemed } from '@genshin-optimizer/common/ui' import { getUnitStr, toPercent } from '@genshin-optimizer/common/util' +import type { CharacterKey } from '@genshin-optimizer/sr/consts' import type { ICachedCharacter } from '@genshin-optimizer/sr/db' import type { Calculator } from '@genshin-optimizer/sr/formula' import { @@ -16,6 +17,7 @@ import { Typography, } from '@mui/material' import { useSrCalcContext } from '../Hook' +import { CharacterName } from './CharacterTrans' import { StatDisplay } from './StatDisplay' const stats = [ @@ -40,23 +42,33 @@ export function CharacterCard({ }) { const calc = useSrCalcContext() ?? srCalculatorWithEntries(charData(character)) + return ( {onClick ? ( - {character.key} + + + ) : ( - {character.key} + + + )} Eidolon: {character.eidolon} Level: {character.level} {stats.map((statKey) => ( - + ))} @@ -64,9 +76,11 @@ export function CharacterCard({ ) } function StatLine({ + characterKey, calc, statKey, }: { + characterKey: CharacterKey calc: Calculator statKey: (typeof stats)[number] }) { @@ -81,9 +95,12 @@ function StatLine({ > - {toPercent(calc.compute(own.final[statKey]).val, statKey).toFixed( - statToFixed(statKey) - )} + {toPercent( + calc + .withTag({ src: characterKey, dst: characterKey }) + .compute(own.final[statKey]).val, + statKey + ).toFixed(statToFixed(statKey))} {getUnitStr(statKey)} diff --git a/libs/sr/ui/src/Character/CharacterTrans.tsx b/libs/sr/ui/src/Character/CharacterTrans.tsx new file mode 100644 index 0000000000..8068501e50 --- /dev/null +++ b/libs/sr/ui/src/Character/CharacterTrans.tsx @@ -0,0 +1,12 @@ +'use client' +// use client due to hydration difference between client rendering and server in translation +import type { CharacterKey } from '@genshin-optimizer/sr/consts' +import { Translate } from '@genshin-optimizer/sr/i18n' + +export function CharacterName({ + characterKey, +}: { + characterKey: CharacterKey +}) { + return +} diff --git a/libs/sr/ui/src/Character/index.tsx b/libs/sr/ui/src/Character/index.tsx index 343ac6a248..31260d869a 100644 --- a/libs/sr/ui/src/Character/index.tsx +++ b/libs/sr/ui/src/Character/index.tsx @@ -3,5 +3,6 @@ export * from './CharacterAutocomplete' export * from './CharacterCard' export * from './CharacterEditor' export * from './CharacterInventory' +export * from './CharacterTrans' export * from './LocationAutocomplete' export * from './StatDisplay' diff --git a/libs/sr/ui/src/Hook/useSrCalcContext.ts b/libs/sr/ui/src/Hook/useSrCalcContext.ts index f80dd00532..8e965339a5 100644 --- a/libs/sr/ui/src/Hook/useSrCalcContext.ts +++ b/libs/sr/ui/src/Hook/useSrCalcContext.ts @@ -1,7 +1,10 @@ -import { CalcContext } from '@genshin-optimizer/pando/ui-sheet' +import { CalcContext, TagContext } from '@genshin-optimizer/pando/ui-sheet' import type { Calculator } from '@genshin-optimizer/sr/formula' -import { useContext } from 'react' +import { useContext, useMemo } from 'react' export function useSrCalcContext() { - return useContext(CalcContext) as Calculator | null + const _calc = useContext(CalcContext) as Calculator | null + const tag = useContext(TagContext) + + return useMemo(() => _calc?.withTag(tag), [_calc, tag]) } diff --git a/libs/sr/ui/src/Relic/index.tsx b/libs/sr/ui/src/Relic/index.tsx index 98875c8570..e1956983b8 100644 --- a/libs/sr/ui/src/Relic/index.tsx +++ b/libs/sr/ui/src/Relic/index.tsx @@ -2,4 +2,5 @@ export * from './EmptyRelicCard' export * from './RelicCard' export * from './RelicEditor' export * from './RelicInventory' +export * from './RelicTrans' export * from './util' diff --git a/libs/sr/util/src/relic.ts b/libs/sr/util/src/relic.ts index 7c5e691481..01606a6491 100644 --- a/libs/sr/util/src/relic.ts +++ b/libs/sr/util/src/relic.ts @@ -5,8 +5,14 @@ import { range, toPercent, } from '@genshin-optimizer/common/util' -import type { RelicSlotKey } from '@genshin-optimizer/sr/consts' +import type { + RelicCavernSetKey, + RelicPlanarSetKey, + RelicSetKey, + RelicSlotKey, +} from '@genshin-optimizer/sr/consts' import { + allRelicCavernSetKeys, allRelicCavernSlotKeys, allRelicPlanarSetKeys, allRelicPlanarSlotKeys, @@ -165,3 +171,16 @@ export function getSubstatValuesPercent( console.log('getSubstatValuesPercent', substatKey, rarity) return [] } + +export function getDefaultRelicSlot(setKey: RelicSetKey) { + if (allRelicCavernSetKeys.includes(setKey as RelicCavernSetKey)) + return allRelicCavernSlotKeys[0] + if (allRelicPlanarSetKeys.includes(setKey as RelicPlanarSetKey)) + return allRelicPlanarSlotKeys[0] + // noopt + return allRelicCavernSlotKeys[0] +} + +export function isCavernRelic(setKey: RelicSetKey) { + return allRelicCavernSetKeys.includes(setKey as RelicCavernSetKey) +} From 8a49b2fd41310905d2007a8fea05a6b4e3888f19 Mon Sep 17 00:00:00 2001 From: Van Nguyen <36019388+nguyentvan7@users.noreply.github.com> Date: Mon, 4 Nov 2024 21:52:00 -0700 Subject: [PATCH 02/11] Fix conditional src being null --- libs/sr/formula/src/data/util/tag.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/sr/formula/src/data/util/tag.ts b/libs/sr/formula/src/data/util/tag.ts index f365f12ea3..35eac66792 100644 --- a/libs/sr/formula/src/data/util/tag.ts +++ b/libs/sr/formula/src/data/util/tag.ts @@ -227,7 +227,7 @@ const condMeta = Symbol.for('condMeta') type CondIgnored = 'both' | 'src' | 'dst' | 'none' function allConditionals( sheet: Sheet, - shared: CondIgnored = 'src', + shared: CondIgnored = 'none', meta: IBaseConditionalData, transform: (r: Read, q: string) => T ): Record { From 0ddf395eb0051aecf5b47efe5c87d5827dd7cb03 Mon Sep 17 00:00:00 2001 From: Van Nguyen <36019388+nguyentvan7@users.noreply.github.com> Date: Mon, 4 Nov 2024 21:52:17 -0700 Subject: [PATCH 03/11] Add TODO and duct tape DB bug --- libs/sr/page-team/src/TeamCalcProvider.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/sr/page-team/src/TeamCalcProvider.tsx b/libs/sr/page-team/src/TeamCalcProvider.tsx index a10ace7026..053cb9a9ba 100644 --- a/libs/sr/page-team/src/TeamCalcProvider.tsx +++ b/libs/sr/page-team/src/TeamCalcProvider.tsx @@ -110,9 +110,10 @@ function useCharacterAndEquipment( ): CharacterFullData | undefined { const character = useCharacter(meta?.characterKey) // TODO: Handle tc build + // TODO: Handle equipped build const build = useBuild(meta?.buildId) const lightCone = useLightCone(build?.lightConeId) - const relics = useRelics(build?.relicIds) + const relics = useRelics(build?.relicIds ?? character?.equippedRelics) // TODO: Temporary for testing return useMemo( () => ({ character, From 9a5104f78b3d5f0981a8c343ab25aad09195a22d Mon Sep 17 00:00:00 2001 From: frzyc Date: Tue, 5 Nov 2024 01:35:35 -0500 Subject: [PATCH 04/11] fix team calc provider --- libs/sr/page-team/src/TeamCalcProvider.tsx | 113 ++++++++------------- 1 file changed, 45 insertions(+), 68 deletions(-) diff --git a/libs/sr/page-team/src/TeamCalcProvider.tsx b/libs/sr/page-team/src/TeamCalcProvider.tsx index 053cb9a9ba..cc7f643f54 100644 --- a/libs/sr/page-team/src/TeamCalcProvider.tsx +++ b/libs/sr/page-team/src/TeamCalcProvider.tsx @@ -1,15 +1,7 @@ import { constant } from '@genshin-optimizer/pando/engine' import { CalcContext } from '@genshin-optimizer/pando/ui-sheet' -import type { - RelicSlotKey, - RelicSubStatKey, -} from '@genshin-optimizer/sr/consts' -import type { - ICachedCharacter, - ICachedLightCone, - ICachedRelic, - TeammateDatum, -} from '@genshin-optimizer/sr/db' +import type { RelicSubStatKey } from '@genshin-optimizer/sr/consts' +import type { ICachedRelic, TeammateDatum } from '@genshin-optimizer/sr/db' import { useBuild, useCharacter, @@ -17,11 +9,7 @@ import { useRelics, useTeam, } from '@genshin-optimizer/sr/db-ui' -import type { - Member, - Preset, - TagMapNodeEntries, -} from '@genshin-optimizer/sr/formula' +import type { Member, Preset } from '@genshin-optimizer/sr/formula' import { charData, conditionalEntries, @@ -38,12 +26,6 @@ import { import type { ReactNode } from 'react' import { useMemo } from 'react' -type CharacterFullData = { - character: ICachedCharacter | undefined - lightCone: ICachedLightCone | undefined - relics: Record -} - export function TeamCalcProvider({ teamId, children, @@ -69,11 +51,11 @@ export function TeamCalcProvider({ .filter((m): m is Member => !!m) ), // Add actual member data - ...(member0 ? createMember(member0) : []), - ...(member1 ? createMember(member1) : []), - ...(member2 ? createMember(member2) : []), - ...(member3 ? createMember(member3) : []), - // TODO: Get these from db + ...member0, + ...member1, + ...member2, + ...member3, + // TODO: Get enemy values from db enemyDebuff.common.lvl.add(80), enemyDebuff.common.res.add(0.1), enemyDebuff.common.isBroken.add(0), @@ -105,51 +87,46 @@ export function TeamCalcProvider({ return {children} } -function useCharacterAndEquipment( - meta: TeammateDatum | undefined -): CharacterFullData | undefined { +function useCharacterAndEquipment(meta: TeammateDatum | undefined) { const character = useCharacter(meta?.characterKey) // TODO: Handle tc build - // TODO: Handle equipped build const build = useBuild(meta?.buildId) - const lightCone = useLightCone(build?.lightConeId) - const relics = useRelics(build?.relicIds ?? character?.equippedRelics) // TODO: Temporary for testing - return useMemo( - () => ({ - character, - lightCone, - relics, - }), - [character, lightCone, relics] + const lightCone = useLightCone( + meta?.buildType === 'equipped' + ? character?.equippedLightCone + : meta?.buildType === 'real' + ? build?.lightConeId + : undefined ) -} - -function createMember({ - character, - lightCone, - relics, -}: CharacterFullData): TagMapNodeEntries { - if (!character) return [] - - return withMember( - character.key, - ...charData(character), - ...lightConeData(lightCone), - ...relicsData( - Object.values(relics) - .filter((relic): relic is ICachedRelic => !!relic) - .map((relic) => ({ - set: relic.setKey, - stats: [ - ...relic.substats - .filter(({ key }) => key !== '') - .map((substat) => ({ - key: substat.key as RelicSubStatKey, // Safe because of the above filter - value: substat.accurateValue, - })), - { key: relic.mainStatKey, value: relic.mainStatVal }, - ], - })) - ) + const relics = useRelics( + meta?.buildType === 'equipped' + ? character?.equippedRelics + : meta?.buildType === 'real' + ? build?.relicIds + : undefined ) + return useMemo(() => { + if (!character) return [] + return withMember( + character.key, + ...charData(character), + ...lightConeData(lightCone), + ...relicsData( + Object.values(relics) + .filter((relic): relic is ICachedRelic => !!relic) + .map((relic) => ({ + set: relic.setKey, + stats: [ + ...relic.substats + .filter(({ key }) => key !== '') + .map((substat) => ({ + key: substat.key as RelicSubStatKey, // Safe because of the above filter + value: substat.accurateValue, + })), + { key: relic.mainStatKey, value: relic.mainStatVal }, + ], + })) + ) + ) + }, [character, lightCone, relics]) } From 6089b5f3727a172d426cda8d8153439b551decb8 Mon Sep 17 00:00:00 2001 From: frzyc Date: Tue, 5 Nov 2024 01:47:47 -0500 Subject: [PATCH 05/11] refactor TeammateDisplay --- libs/sr/page-team/src/ComboEditor.tsx | 33 +++- libs/sr/page-team/src/TeammateDisplay.tsx | 205 ++++++++++------------ 2 files changed, 125 insertions(+), 113 deletions(-) diff --git a/libs/sr/page-team/src/ComboEditor.tsx b/libs/sr/page-team/src/ComboEditor.tsx index 02d7f12660..f4322f825e 100644 --- a/libs/sr/page-team/src/ComboEditor.tsx +++ b/libs/sr/page-team/src/ComboEditor.tsx @@ -1,16 +1,25 @@ -import { CardThemed, ConditionalWrapper } from '@genshin-optimizer/common/ui' +import { useBoolState } from '@genshin-optimizer/common/react-util' +import { + CardThemed, + ConditionalWrapper, + ModalWrapper, +} from '@genshin-optimizer/common/ui' import { useDatabaseContext } from '@genshin-optimizer/sr/db-ui' import type { Tag } from '@genshin-optimizer/sr/formula' import { Box, + Button, CardActionArea, CardContent, CardHeader, Divider, + Stack, } from '@mui/material' import { useContext } from 'react' +import { BonusStats } from './BonusStats' import { PresetContext, useTeamContext } from './context' import { OptimizationTargetSelector } from './Optimize/OptimizationTargetSelector' +import { RelicSheetsDisplay } from './RelicSheetsDisplay' export function ComboEditor() { const { database } = useDatabaseContext() @@ -92,8 +101,28 @@ function Team({ - + + + + + ) } + +function RelicConditionals() { + const [show, onShow, onHide] = useBoolState() + return ( + <> + {/* TODO: translation */} + + + + + + ) +} diff --git a/libs/sr/page-team/src/TeammateDisplay.tsx b/libs/sr/page-team/src/TeammateDisplay.tsx index 36a8bd08bc..89a6152ebe 100644 --- a/libs/sr/page-team/src/TeammateDisplay.tsx +++ b/libs/sr/page-team/src/TeammateDisplay.tsx @@ -25,12 +25,10 @@ import { Typography, } from '@mui/material' import { useMemo, useState } from 'react' -import { BonusStats } from './BonusStats' import { BuildGridBase, BuildsDisplay } from './BuildsDisplay' import { ComboEditor } from './ComboEditor' import { useTeamContext } from './context' import Optimize from './Optimize' -import { RelicSheetsDisplay } from './RelicSheetsDisplay' import CharacterTalentPane from './TalentContent' export default function TeammateDisplay() { @@ -43,37 +41,29 @@ export default function TeammateDisplay() { ) return ( - - setCharacterKey(undefined)} - /> + + setCharacterKey(undefined)} + /> + - - - - - - - - - - - - - - - + + + + + ) } function CurrentBuildDisplay() { @@ -123,92 +113,85 @@ function BuildsModal({ ) } -function RelicConditionals() { - const [show, onShow, onHide] = useBoolState() - return ( - <> - {/* TODO: translation */} - - - - - - ) -} + function CalcDebug() { const calc = useSrCalcContext() return ( - <> - - }> - All target listings - - - - {calc?.listFormulas(own.listing.formulas).map((read, index) => { - const computed = calc.compute(read) - const name = read.tag.name || read.tag.q - return ( - - - {name}: {computed.val} - - - }> - debug for {name} - - - conds: {JSON.stringify(computed.meta.conds, undefined, 2)} - - {JSON.stringify( - calc.toDebug().compute(read), - undefined, - 2 - )} - - - - - ) - })} - - - - - }> - All target buffs - - - - {calc?.listFormulas(own.listing.buffs).map((read, index) => { - const computed = calc.compute(read) - const name = read.tag.name || read.tag.q - return ( - - - {name}: {computed.val} - - - }> - debug for {name} - - - conds: {JSON.stringify(computed.meta.conds, undefined, 2)} - - {JSON.stringify( - calc.toDebug().compute(read), - undefined, - 2 - )} - - - - - ) - })} - - - - + + + + }> + All target listings + + + + {calc?.listFormulas(own.listing.formulas).map((read, index) => { + const computed = calc.compute(read) + const name = read.tag.name || read.tag.q + return ( + + + {name}: {computed.val} + + + }> + debug for {name} + + + conds:{' '} + {JSON.stringify(computed.meta.conds, undefined, 2)} + + {JSON.stringify( + calc.toDebug().compute(read), + undefined, + 2 + )} + + + + + ) + })} + + + + + }> + All target buffs + + + + {calc?.listFormulas(own.listing.buffs).map((read, index) => { + const computed = calc.compute(read) + const name = read.tag.name || read.tag.q + return ( + + + {name}: {computed.val} + + + }> + debug for {name} + + + conds:{' '} + {JSON.stringify(computed.meta.conds, undefined, 2)} + + {JSON.stringify( + calc.toDebug().compute(read), + undefined, + 2 + )} + + + + + ) + })} + + + + + ) } From 17efd9edd008693f92ccb3e86a6a96ae33f3214d Mon Sep 17 00:00:00 2001 From: frzyc Date: Tue, 5 Nov 2024 02:17:31 -0500 Subject: [PATCH 06/11] validate conditional value --- .../Database/DataManagers/TeamDataManager.ts | 66 +++++++++++++++---- 1 file changed, 55 insertions(+), 11 deletions(-) diff --git a/libs/sr/db/src/Database/DataManagers/TeamDataManager.ts b/libs/sr/db/src/Database/DataManagers/TeamDataManager.ts index 7e884685a4..6b397516b3 100644 --- a/libs/sr/db/src/Database/DataManagers/TeamDataManager.ts +++ b/libs/sr/db/src/Database/DataManagers/TeamDataManager.ts @@ -1,3 +1,4 @@ +import type { IConditionalData } from '@genshin-optimizer/common/formula' import { objKeyMap, pruneOrPadArray, @@ -9,7 +10,12 @@ import { allCharacterKeys, allRelicSlotKeys, } from '@genshin-optimizer/sr/consts' -import type { Member, Sheet, Tag } from '@genshin-optimizer/sr/formula' +import { + conditionals, + type Member, + type Sheet, + type Tag, +} from '@genshin-optimizer/sr/formula' import type { RelicIds } from '../../Types' import { DataManager } from '../DataManager' import type { SroDatabase } from '../Database' @@ -170,16 +176,30 @@ export class TeamDataManager extends DataManager { bonusStats = [] } else { if (!Array.isArray(conditionals)) conditionals = [] - conditionals = conditionals.filter(({ src, dst, condValues }) => { - // TODO: validate conditionals src dst condKey - if (!allCharacterKeys.includes(src as CharacterKey)) return false - if (!allCharacterKeys.includes(dst as CharacterKey)) return false - if (!Array.isArray(condValues)) return false - pruneOrPadArray(condValues, framesLength, 0) - // If all values are false, remove the conditional - if (condValues.every((v) => !v)) return false - return true - }) + const hashList: string[] = [] // a hash to ensure sheet:condKey:src:dst is unique + conditionals = conditionals.filter( + ({ sheet, condKey, src, dst, condValues }) => { + const cond = getConditional(sheet, condKey) + if (!cond) return false + // TODO: handle src/dst 'all' + if (!allCharacterKeys.includes(src as CharacterKey)) return false + if (!allCharacterKeys.includes(dst as CharacterKey)) return false + + // validate uniqueness + const hash = `${sheet}:${condKey}:${src}:${dst}` + if (hashList.includes(hash)) return false + hashList.push(hash) + + // validate values + if (!Array.isArray(condValues)) return false + pruneOrPadArray(condValues, framesLength, 0) + condValues = condValues.map((v) => correctConditionalValue(cond, v)) + // If all values are false, remove the conditional + if (condValues.every((v) => !v)) return false + + return true + } + ) if (!Array.isArray(bonusStats)) bonusStats = [] bonusStats = bonusStats.filter(({ tag, values }) => { @@ -489,3 +509,27 @@ export class TeamDataManager extends DataManager { }) } } + +export function getConditional(sheet: Sheet, condKey: string) { + return (conditionals as any)[sheet]?.[condKey] as IConditionalData | undefined +} +function correctConditionalValue(conditional: IConditionalData, value: number) { + if (conditional.type === 'bool') { + return +!!value + } else if (conditional.type === 'num') { + if (conditional.int_only && !Number.isInteger(value)) { + value = Math.round(value) + } + if (conditional.min !== undefined && value < conditional.min) + value = conditional.min + if (conditional.max !== undefined && value > conditional.max) + value = conditional.max + } else if (conditional.type === 'list') { + if (!Number.isInteger(value)) { + value = Math.round(value) + } + if (value < 0) value = 0 + if (value > conditional.list.length - 1) value = conditional.list.length - 1 + } + return value +} From 8b880a7561d755a75a39fa5e2ba9f70002fac316 Mon Sep 17 00:00:00 2001 From: frzyc Date: Tue, 5 Nov 2024 02:25:21 -0500 Subject: [PATCH 07/11] fix gi-frontend --- apps/gi-frontend/src/app/teams/[teamId]/TalentContent.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/gi-frontend/src/app/teams/[teamId]/TalentContent.tsx b/apps/gi-frontend/src/app/teams/[teamId]/TalentContent.tsx index 7d92a29b20..4452a505c5 100644 --- a/apps/gi-frontend/src/app/teams/[teamId]/TalentContent.tsx +++ b/apps/gi-frontend/src/app/teams/[teamId]/TalentContent.tsx @@ -299,7 +299,6 @@ function SkillDisplayCard({ document={doc} collapse // hideHeader={hideHeader} - setConditional={() => {}} // TODO: frzyc setConditional /> ))} From 0c4f5ac15139c7e380cfea60bce65e0a4a3209d9 Mon Sep 17 00:00:00 2001 From: frzyc Date: Tue, 5 Nov 2024 10:47:56 -0500 Subject: [PATCH 08/11] address comments --- .../src/components/ConditionalDisplay.tsx | 6 +++--- libs/sr/db/src/Types/conditional.ts | 18 ------------------ libs/sr/page-team/src/RelicSheetsDisplay.tsx | 1 + libs/sr/page-team/src/index.tsx | 16 +++++++--------- 4 files changed, 11 insertions(+), 30 deletions(-) delete mode 100644 libs/sr/db/src/Types/conditional.ts diff --git a/libs/pando/ui-sheet/src/components/ConditionalDisplay.tsx b/libs/pando/ui-sheet/src/components/ConditionalDisplay.tsx index 5d7e1dc206..daae95281c 100644 --- a/libs/pando/ui-sheet/src/components/ConditionalDisplay.tsx +++ b/libs/pando/ui-sheet/src/components/ConditionalDisplay.tsx @@ -241,7 +241,7 @@ function BoolConditional({ function ListConditional({ conditional, setValue, - value: value, + value, disabled, }: ConditionalProps) { const calc = useContext(CalcContext) @@ -370,7 +370,7 @@ function CondSrcDst({ {setSrc ? ( @@ -380,7 +380,7 @@ function CondSrcDst({ {setDst ? ( diff --git a/libs/sr/db/src/Types/conditional.ts b/libs/sr/db/src/Types/conditional.ts deleted file mode 100644 index 6f20136e13..0000000000 --- a/libs/sr/db/src/Types/conditional.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { - CharacterKey, - LightConeKey, - RelicSetKey, -} from '@genshin-optimizer/sr/consts' - -type SheetKey = CharacterKey | RelicSetKey | LightConeKey -// Stored per-char loadout, so the dst is assumed to be the owning char -// CharKey|'all' is the srcKey -// SheetKey is the SheetKey -// condkey is the condKey -// value is the condValue -export type ConditionalValues = Partial< - Record< - CharacterKey | 'all', - Partial> - > -> diff --git a/libs/sr/page-team/src/RelicSheetsDisplay.tsx b/libs/sr/page-team/src/RelicSheetsDisplay.tsx index f0c72c73a3..4346a81698 100644 --- a/libs/sr/page-team/src/RelicSheetsDisplay.tsx +++ b/libs/sr/page-team/src/RelicSheetsDisplay.tsx @@ -3,6 +3,7 @@ import type { RelicSetKey } from '@genshin-optimizer/sr/consts' import { CardContent, Grid } from '@mui/material' import { RelicSheetDisplay } from './RelicSheetDisplay' +// TODO: hardcoded to RelicSheetsDisplay.tsx for now. Can be expanded to all relic sets with sheets const sets: RelicSetKey[] = ['WatchmakerMasterOfDreamMachinations'] as const export function RelicSheetsDisplay() { return ( diff --git a/libs/sr/page-team/src/index.tsx b/libs/sr/page-team/src/index.tsx index f141be711a..c1c1370f4a 100644 --- a/libs/sr/page-team/src/index.tsx +++ b/libs/sr/page-team/src/index.tsx @@ -122,15 +122,13 @@ function Page({ teamId }: { teamId: string }) { } }, [teammateDatum, team, teamId]) const srcDstDisplayContextValue = useMemo(() => { - const charDisplay = objKeyMap( - moveToFront( - team.teamMetadata - .filter(notEmpty) - .map(({ characterKey }) => characterKey), - characterKey as CharacterKey - ), - (ck) => - ) + const charList = team.teamMetadata + .filter(notEmpty) + .map(({ characterKey }) => characterKey) + if (characterKey) moveToFront(charList, characterKey as CharacterKey) + const charDisplay = objKeyMap(charList, (ck) => ( + + )) return { srcDisplay: charDisplay, dstDisplay: charDisplay } }, [team.teamMetadata, characterKey]) const conditionals = useMemo( From ad5b7a09272f62f586e4e47e4b8e111fc25c1be6 Mon Sep 17 00:00:00 2001 From: frzyc Date: Tue, 5 Nov 2024 10:54:44 -0500 Subject: [PATCH 09/11] update --- libs/sr/db/src/Types/index.ts | 1 - libs/sr/formula/src/util.ts | 21 --------------------- 2 files changed, 22 deletions(-) diff --git a/libs/sr/db/src/Types/index.ts b/libs/sr/db/src/Types/index.ts index 8437e95dda..55c82de417 100644 --- a/libs/sr/db/src/Types/index.ts +++ b/libs/sr/db/src/Types/index.ts @@ -1,2 +1 @@ -export * from './conditional' export * from './relics' diff --git a/libs/sr/formula/src/util.ts b/libs/sr/formula/src/util.ts index cf2332d66e..1d05605674 100644 --- a/libs/sr/formula/src/util.ts +++ b/libs/sr/formula/src/util.ts @@ -5,10 +5,8 @@ import { allStatBoostKeys, } from '@genshin-optimizer/sr/consts' import type { ICharacter, ILightCone } from '@genshin-optimizer/sr/srod' -import type { SrcCondInfo } from './calculator' import type { Member, Preset, TagMapNodeEntries } from './data/util' import { - conditionalEntries, convert, getStatFromStatKey, own, @@ -173,22 +171,3 @@ export function teamData(members: readonly Member[]): TagMapNodeEntries { members.map((src) => teamEntry.add(reader.withTag({ src, et: 'own' }).sum)), ].flat() } - -/** - * Generate conditional TagMapNodeEntry for calculator. Should be provided outside of any member data, in order to preserve specified 'src' - * @param dst member to apply conditionals to - * @param data conditional data in `Src: { Sheet: { CondKey: value } }` format. Src can be 'all', unless the buff is possibly duplicated (e.g. relic team buff). In that case, you should specify the src member, if you want to select which one to apply. - * @returns - */ -export function conditionalData( - dst: Member | 'all', - data: SrcCondInfo | undefined -) { - if (!data) return [] - return Object.entries(data).flatMap(([src, entries]) => - Object.entries(entries).flatMap(([sheet, entries]) => { - const conds = conditionalEntries(sheet, src, dst) - return Object.entries(entries).map(([k, v]) => conds(k, v)) - }) - ) -} From 4d87cfe27c3768688ca2be4b095c87dee59d0c8b Mon Sep 17 00:00:00 2001 From: frzyc Date: Tue, 5 Nov 2024 20:04:23 -0500 Subject: [PATCH 10/11] update typeguards and remove 'all' from Member --- .../Database/DataManagers/TeamDataManager.ts | 19 ++++++---------- libs/sr/formula/src/calculator.ts | 9 ++++---- libs/sr/formula/src/conditionalUtil.ts | 7 ++++++ libs/sr/formula/src/data/util/listing.ts | 7 ++++++ libs/sr/formula/src/data/util/tag.ts | 7 ++---- libs/sr/formula/src/index.ts | 1 + libs/sr/page-team/src/index.tsx | 22 ++++++++++++++----- 7 files changed, 45 insertions(+), 27 deletions(-) create mode 100644 libs/sr/formula/src/conditionalUtil.ts diff --git a/libs/sr/db/src/Database/DataManagers/TeamDataManager.ts b/libs/sr/db/src/Database/DataManagers/TeamDataManager.ts index 6b397516b3..641edb2939 100644 --- a/libs/sr/db/src/Database/DataManagers/TeamDataManager.ts +++ b/libs/sr/db/src/Database/DataManagers/TeamDataManager.ts @@ -11,7 +11,8 @@ import { allRelicSlotKeys, } from '@genshin-optimizer/sr/consts' import { - conditionals, + getConditional, + isMember, type Member, type Sheet, type Tag, @@ -46,8 +47,8 @@ export interface Team { frames: Array conditionals: Array<{ sheet: Sheet - src: Member | 'all' - dst: Member | 'all' + src: Member + dst: Member condKey: string condValues: number[] // should be the same length as `frames` }> @@ -179,11 +180,9 @@ export class TeamDataManager extends DataManager { const hashList: string[] = [] // a hash to ensure sheet:condKey:src:dst is unique conditionals = conditionals.filter( ({ sheet, condKey, src, dst, condValues }) => { + if (!isMember(src) || !isMember(dst)) return false const cond = getConditional(sheet, condKey) if (!cond) return false - // TODO: handle src/dst 'all' - if (!allCharacterKeys.includes(src as CharacterKey)) return false - if (!allCharacterKeys.includes(dst as CharacterKey)) return false // validate uniqueness const hash = `${sheet}:${condKey}:${src}:${dst}` @@ -397,8 +396,8 @@ export class TeamDataManager extends DataManager { teamId: string, sheet: Sheet, condKey: string, - src: Member | 'all', - dst: Member | 'all', + src: Member, + dst: Member, condValue: number, frameIndex: number ) { @@ -415,7 +414,6 @@ export class TeamDataManager extends DataManager { condIndex, }) if (condIndex === -1) { - console.log('creating new conditional') const condValues = new Array(team.frames.length).fill(0) condValues[frameIndex] = condValue team.conditionals.push({ @@ -510,9 +508,6 @@ export class TeamDataManager extends DataManager { } } -export function getConditional(sheet: Sheet, condKey: string) { - return (conditionals as any)[sheet]?.[condKey] as IConditionalData | undefined -} function correctConditionalValue(conditional: IConditionalData, value: number) { if (conditional.type === 'bool') { return +!!value diff --git a/libs/sr/formula/src/calculator.ts b/libs/sr/formula/src/calculator.ts index 288ae89571..f178c5f2be 100644 --- a/libs/sr/formula/src/calculator.ts +++ b/libs/sr/formula/src/calculator.ts @@ -11,7 +11,7 @@ import { reader, tagStr } from './data/util' const emptyInfo: Info = Object.freeze({ conds: Object.freeze({}) }) const { arithmetic } = calculation -type MemRec = Partial> +type MemRec = Partial> type SingleCondInfo = Partial>> export type SrcCondInfo = MemRec /// conds[dst][src][sheet][name] @@ -102,9 +102,10 @@ export class Calculator extends Base { if (tag.qt === 'cond') { const { src, dst, sheet, q } = tag - meta.conds = { - [dst ?? 'all']: { [src ?? 'all']: { [sheet!]: { [q!]: val } } }, - } + if (src && dst && sheet && q) + meta.conds = { + [dst]: { [src]: { [sheet!]: { [q!]: val } } }, + } dirty = true } Object.freeze(meta) diff --git a/libs/sr/formula/src/conditionalUtil.ts b/libs/sr/formula/src/conditionalUtil.ts new file mode 100644 index 0000000000..5f945f3615 --- /dev/null +++ b/libs/sr/formula/src/conditionalUtil.ts @@ -0,0 +1,7 @@ +import type { IConditionalData } from '@genshin-optimizer/common/formula' +import type { Sheet } from './data/util' +import { conditionals } from './meta' + +export function getConditional(sheet: Sheet, condKey: string) { + return (conditionals as any)[sheet]?.[condKey] as IConditionalData | undefined +} diff --git a/libs/sr/formula/src/data/util/listing.ts b/libs/sr/formula/src/data/util/listing.ts index e4fc447eed..4817e9ea85 100644 --- a/libs/sr/formula/src/data/util/listing.ts +++ b/libs/sr/formula/src/data/util/listing.ts @@ -113,3 +113,10 @@ export type EntryType = (typeof entryTypes)[number] export type Sheet = (typeof sheets)[number] export type Member = (typeof members)[number] export type Preset = (typeof presets)[number] + +export function isMember(x: string): x is Member { + return members.includes(x as Member) +} +export function isSheet(x: string): x is Sheet { + return sheets.includes(x as Sheet) +} diff --git a/libs/sr/formula/src/data/util/tag.ts b/libs/sr/formula/src/data/util/tag.ts index 35eac66792..6729188120 100644 --- a/libs/sr/formula/src/data/util/tag.ts +++ b/libs/sr/formula/src/data/util/tag.ts @@ -214,11 +214,8 @@ export const allNumConditionals = ( ) => allConditionals(sheet, ignored, { type: 'num', int_only, min, max }, (r) => r) -type MemAll = Member | 'all' -export const conditionalEntries = (sheet: Sheet, src: MemAll, dst: MemAll) => { - let tag: Tag = { sheet, qt: 'cond' } - if (src !== 'all') tag = { ...tag, src } - if (dst !== 'all') tag = { ...tag, dst } +export const conditionalEntries = (sheet: Sheet, src: Member, dst: Member) => { + const tag: Tag = { sheet, qt: 'cond', src, dst } const base = own.withTag(tag).withAll('q', []) return (name: string, val: string | number) => base[name].add(val) } diff --git a/libs/sr/formula/src/index.ts b/libs/sr/formula/src/index.ts index a5ee844a67..d182b84cef 100644 --- a/libs/sr/formula/src/index.ts +++ b/libs/sr/formula/src/index.ts @@ -13,6 +13,7 @@ import { keys, values } from './data' import type { Tag } from './data/util' export { Calculator } from './calculator' export type { SrcCondInfo } from './calculator' +export * from './conditionalUtil' export * from './data/util' export * from './meta' export * from './util' diff --git a/libs/sr/page-team/src/index.tsx b/libs/sr/page-team/src/index.tsx index 3bca17139e..2274b524c9 100644 --- a/libs/sr/page-team/src/index.tsx +++ b/libs/sr/page-team/src/index.tsx @@ -12,14 +12,20 @@ import { TagContext, } from '@genshin-optimizer/pando/ui-sheet' import { characterKeyToGenderedKey } from '@genshin-optimizer/sr/assets' -import type { CharacterKey } from '@genshin-optimizer/sr/consts' import { CharacterContext, useCharacter, useDatabaseContext, useTeam, } from '@genshin-optimizer/sr/db-ui' -import type { Member, Preset, Sheet, Tag } from '@genshin-optimizer/sr/formula' +import { + getConditional, + isMember, + isSheet, + type Preset, + type Sheet, + type Tag, +} from '@genshin-optimizer/sr/formula' import { CharacterName } from '@genshin-optimizer/sr/ui' import { Box, Skeleton } from '@mui/material' import { Suspense, useCallback, useEffect, useMemo, useState } from 'react' @@ -126,7 +132,7 @@ function Page({ teamId }: { teamId: string }) { const charList = team.teamMetadata .filter(notEmpty) .map(({ characterKey }) => characterKey) - if (characterKey) moveToFront(charList, characterKey as CharacterKey) + if (characterKey) moveToFront(charList, characterKey) const charDisplay = objKeyMap(charList, (ck) => ( )) @@ -151,12 +157,16 @@ function Page({ teamId }: { teamId: string }) { dst: string, condValue: number ) => { + if (!isSheet(sheet) || !isMember(src) || !isMember(dst)) return + const cond = getConditional(sheet as Sheet, condKey) + if (!cond) return + database.teams.setConditional( teamId, - sheet as Sheet, + sheet, condKey, - src as Member | 'all', - dst as Member | 'all', + src, + dst, condValue, presetIndex ) From 0be3038d0cfc70a0d2efed3a1dcd796ca6294267 Mon Sep 17 00:00:00 2001 From: frzyc Date: Tue, 5 Nov 2024 20:48:16 -0500 Subject: [PATCH 11/11] remove log, remove sheet tags --- .../sr/db/src/Database/DataManagers/TeamDataManager.ts | 3 --- libs/sr/formula-ui/src/char/sheets/RuanMei.tsx | 10 ++++------ 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/libs/sr/db/src/Database/DataManagers/TeamDataManager.ts b/libs/sr/db/src/Database/DataManagers/TeamDataManager.ts index 641edb2939..e3f62086c3 100644 --- a/libs/sr/db/src/Database/DataManagers/TeamDataManager.ts +++ b/libs/sr/db/src/Database/DataManagers/TeamDataManager.ts @@ -410,9 +410,6 @@ export class TeamDataManager extends DataManager { c.src === src && c.dst === dst ) - console.log({ - condIndex, - }) if (condIndex === -1) { const condValues = new Array(team.frames.length).fill(0) condValues[frameIndex] = condValue diff --git a/libs/sr/formula-ui/src/char/sheets/RuanMei.tsx b/libs/sr/formula-ui/src/char/sheets/RuanMei.tsx index 366c74d7c1..eceb991336 100644 --- a/libs/sr/formula-ui/src/char/sheets/RuanMei.tsx +++ b/libs/sr/formula-ui/src/char/sheets/RuanMei.tsx @@ -2,7 +2,6 @@ import { ColorText, ImgIcon, SqBadge } from '@genshin-optimizer/common/ui' import type { UISheet } from '@genshin-optimizer/pando/ui-sheet' import { characterAsset } from '@genshin-optimizer/sr/assets' import type { CharacterKey } from '@genshin-optimizer/sr/consts' -import type { Tag } from '@genshin-optimizer/sr/formula' import { buffs, conditionals, @@ -21,7 +20,6 @@ const formula = formulas.RuanMei const cond = conditionals.RuanMei const buff = buffs.RuanMei const dm = mappedStats.char[key] -const selfTag: Tag = { src: key, dst: key } const sheet: UISheet = { basic: { title: chg('abilities.basic.0.name'), @@ -40,7 +38,7 @@ const sheet: UISheet = { getInterpolateObject( key, 'basic', - calculator.withTag(selfTag).compute(own.char.basic).val + calculator.compute(own.char.basic).val ) ), }, @@ -72,7 +70,7 @@ const sheet: UISheet = { getInterpolateObject( key, 'skill', - calculator.withTag(selfTag).compute(own.char.skill).val + calculator.compute(own.char.skill).val ) ), }, @@ -121,7 +119,7 @@ const sheet: UISheet = { getInterpolateObject( key, 'ult', - calculator.withTag(selfTag).compute(own.char.ult).val + calculator.compute(own.char.ult).val ) ), }, @@ -176,7 +174,7 @@ const sheet: UISheet = { getInterpolateObject( key, 'talent', - calculator.withTag(selfTag).compute(own.char.talent).val + calculator.compute(own.char.talent).val ) ), },