diff --git a/libs/gi/sheets/src/Artifacts/NoblesseOblige/index.tsx b/libs/gi/sheets/src/Artifacts/NoblesseOblige/index.tsx index 874d8a1215..0e1f0a033c 100644 --- a/libs/gi/sheets/src/Artifacts/NoblesseOblige/index.tsx +++ b/libs/gi/sheets/src/Artifacts/NoblesseOblige/index.tsx @@ -1,7 +1,13 @@ import type { ArtifactSetKey } from '@genshin-optimizer/gi/consts' import type { Data } from '@genshin-optimizer/gi/wr' -import { equal, greaterEq, input, percent } from '@genshin-optimizer/gi/wr' -import { cond, st, stg } from '../../SheetUtil' +import { + equalStr, + greaterEq, + greaterEqStr, + input, + percent, +} from '@genshin-optimizer/gi/wr' +import { cond, nonStackBuff, st, stg } from '../../SheetUtil' import { ArtifactSheet, setHeaderTemplate } from '../ArtifactSheet' import type { SetEffectSheet } from '../IArtifactSheet' import { dataObjForArtifactSheet } from '../dataUtil' @@ -12,11 +18,12 @@ const setHeader = setHeaderTemplate(key) const set2 = greaterEq(input.artSet.NoblesseOblige, 2, percent(0.2)) const [condSet4Path, condSet4] = cond(key, 'set4') -const set4 = greaterEq( +const set4TallyWrite = greaterEqStr( input.artSet.NoblesseOblige, 4, - equal(condSet4, 'on', percent(0.2)) + equalStr(condSet4, 'on', input.charKey) ) +const [set4, set4Inactive] = nonStackBuff('no4', 'atk_', percent(0.2)) export const data: Data = dataObjForArtifactSheet(key, { premod: { @@ -26,6 +33,9 @@ export const data: Data = dataObjForArtifactSheet(key, { premod: { atk_: set4, }, + nonStacking: { + no4: set4TallyWrite, + }, }, }) @@ -45,6 +55,9 @@ const sheet: SetEffectSheet = { { node: set4, }, + { + node: set4Inactive, + }, { text: stg('duration'), value: 12, diff --git a/libs/gi/sheets/src/SheetUtil.tsx b/libs/gi/sheets/src/SheetUtil.tsx index cc3105ade2..6341840c66 100644 --- a/libs/gi/sheets/src/SheetUtil.tsx +++ b/libs/gi/sheets/src/SheetUtil.tsx @@ -5,12 +5,20 @@ import type { WeaponKey, } from '@genshin-optimizer/gi/consts' import { Translate } from '@genshin-optimizer/gi/i18n' -import type { Info, NumNode, ReadNode, StrNode } from '@genshin-optimizer/gi/wr' +import type { + Info, + NonStackBuff, + NumNode, + ReadNode, + StrNode, +} from '@genshin-optimizer/gi/wr' import { customStringRead, equal, infoMut, input, + nonStacking, + unequal, } from '@genshin-optimizer/gi/wr' import type { ReactNode } from 'react' @@ -80,3 +88,18 @@ export function activeCharBuff( equal(input.activeCharKey, buffTargetKey, node), ] } + +export function nonStackBuff( + buffName: NonStackBuff, + path: string, + buffNode: NumNode +) { + return [ + equal(nonStacking[buffName], input.charKey, buffNode), + unequal(nonStacking[buffName], input.charKey, buffNode, { + path, + isTeamBuff: true, + strikethrough: true, + }), + ] +} diff --git a/libs/gi/ui/src/components/FieldDisplay.tsx b/libs/gi/ui/src/components/FieldDisplay.tsx index d58afc9371..78e6b2c45b 100644 --- a/libs/gi/ui/src/components/FieldDisplay.tsx +++ b/libs/gi/ui/src/components/FieldDisplay.tsx @@ -129,7 +129,7 @@ export function NodeFieldDisplay({ [setFormulaData, data, calcRes] ) if (!calcRes && !compareCalcRes) return null - const { multi } = calcRes?.info ?? compareCalcRes?.info ?? {} + const { multi, strikethrough } = calcRes?.info ?? compareCalcRes?.info ?? {} const multiDisplay = multi && {multi}× const calcValue = calcRes?.value ?? 0 @@ -198,6 +198,7 @@ export function NodeFieldDisplay({ gap: 1, boxShadow: emphasize ? '0px 0px 0px 2px red inset' : undefined, py: 0.25, + textDecoration: strikethrough ? 'line-through' : undefined, }} component={component} > diff --git a/libs/gi/ui/src/util/getCalcDisplay.tsx b/libs/gi/ui/src/util/getCalcDisplay.tsx index 531740096d..67a1dbf775 100644 --- a/libs/gi/ui/src/util/getCalcDisplay.tsx +++ b/libs/gi/ui/src/util/getCalcDisplay.tsx @@ -13,6 +13,7 @@ import { import { Translate } from '@genshin-optimizer/gi/i18n' import type { CalcResult } from '@genshin-optimizer/gi/uidata' import type { Info, InfoExtra, KeyMapPrefix } from '@genshin-optimizer/gi/wr' +import { Typography } from '@mui/material' import { useContext, type ReactNode } from 'react' import { SillyContext } from '../context' import { resolveInfo } from './resolveInfo' @@ -220,11 +221,15 @@ function computeFormulaDisplay( components.filter((c) => c) result.formula = ( - <> + {components.map((x, i) => ( {x} ))} - + ) return result diff --git a/libs/gi/uidata/src/uiData.ts b/libs/gi/uidata/src/uiData.ts index 14914244c6..23203ddd04 100644 --- a/libs/gi/uidata/src/uiData.ts +++ b/libs/gi/uidata/src/uiData.ts @@ -29,6 +29,7 @@ import { deepNodeClone, input, mergeData, + nonStacking, resetData, setReadNodeKeys, tally, @@ -537,6 +538,8 @@ export function uiDataForTeam( const newNode = customRead(path) if (path[0] === 'teamBuff' && path[1] === 'tally') newNode.accu = objPathValue(tally, path.slice(2))?.accu + if (path[0] === 'teamBuff' && path[1] === 'nonStacking') + newNode.accu = objPathValue(nonStacking, path.slice(2))?.accu layeredAssignment(customReadNodes, path, newNode) return newNode } diff --git a/libs/gi/wr/src/api.ts b/libs/gi/wr/src/api.ts index 70adcce29a..f69ef98f25 100644 --- a/libs/gi/wr/src/api.ts +++ b/libs/gi/wr/src/api.ts @@ -21,7 +21,7 @@ import type { } from '@genshin-optimizer/gi/db' import type { ICharacter } from '@genshin-optimizer/gi/good' import { getMainStatValue } from '@genshin-optimizer/gi/util' -import { input, tally } from './formula' +import { input, nonStacking, tally } from './formula' import type { Data, Info, NumNode, ReadNode, StrNode } from './type' import { constant, data, infoMut, none, percent, prod, sum } from './utils' @@ -41,7 +41,7 @@ export function inferInfoMut(data: Data, source?: Info['source']): Data { | undefined if (reference) x.info = { ...x.info, ...reference.info, prefix: undefined, source } - else if (path[0] !== 'tally') + else if (path[0] !== 'tally' && path[0] !== 'nonStacking') console.error( `Detect ${source} buff into non-existant key path ${path}` ) @@ -246,7 +246,13 @@ export function mergeData(data: Data[]): Data { if (data.length <= 1) return data[0] if (data[0].operation) { if (path[0] === 'teamBuff') path = path.slice(1) - const base = path[0] === 'tally' ? ((path = path.slice(1)), tally) : input + const base = + path[0] === 'tally' + ? tally + : path[0] === 'nonStacking' + ? nonStacking + : input + if (path[0] === 'tally' || path[0] === 'nonStacking') path = path.slice(1) /*eslint prefer-const: ["error", {"destructuring": "all"}]*/ let { accu, type } = (objPathValue(base, path) as ReadNode | undefined) ?? diff --git a/libs/gi/wr/src/formula.ts b/libs/gi/wr/src/formula.ts index 3f0a59bb94..246e72c32f 100644 --- a/libs/gi/wr/src/formula.ts +++ b/libs/gi/wr/src/formula.ts @@ -46,6 +46,8 @@ const asConst = true as const, const allElements = allElementWithPhyKeys const allTalents = ['auto', 'skill', 'burst'] as const +const allNonstackBuffs = ['no4'] as const +export type NonStackBuff = (typeof allNonstackBuffs)[number] const allMoves = [ 'normal', 'charged', @@ -587,6 +589,11 @@ const tally = { ele: sum(...allElements.map((ele) => min(_tally[ele], 1))), } +const nonStacking = setReadNodeKeys( + objKeyMap(allNonstackBuffs, () => stringRead('small')), + ['nonStacking'] +) + /** * List of `input` nodes, rearranged to conform to the needs of the * UI code. This is a separate list so that the evolution of the UIs @@ -604,4 +611,4 @@ export const infusionNode = stringPrio( input.infusion.overridableSelf ) -export { common, customBonus, input, tally, target, uiInput } +export { common, customBonus, input, nonStacking, tally, target, uiInput } diff --git a/libs/gi/wr/src/type.d.ts b/libs/gi/wr/src/type.d.ts index be2489e22d..5e90c35b6f 100644 --- a/libs/gi/wr/src/type.d.ts +++ b/libs/gi/wr/src/type.d.ts @@ -9,7 +9,7 @@ import type { AmplifyingReactionsKey, TransformativeReactionsKey, } from '@genshin-optimizer/gi/keymap' -import type { input, uiInput } from './formula' +import type { input, NonStackBuff, uiInput } from './formula' export type NumNode = | ComputeNode @@ -54,6 +54,7 @@ export type Info = { fixed?: number isTeamBuff?: boolean multi?: number + strikethrough?: boolean } export type Variant = | ElementWithPhyKey @@ -179,7 +180,10 @@ interface DynamicNumInput { [key: string]: DisplaySub } conditional?: NodeData - teamBuff?: Input & { tally?: NodeData } + teamBuff?: Input & { + tally?: NodeData + nonStacking?: Record + } } export interface NodeData { [key: string]: typeof key extends 'operation' ? never : NodeData | T