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