From cb0e5fd8c44044a247fe2986593f15a1475c60e8 Mon Sep 17 00:00:00 2001 From: eeeqeee <103794572+eeeqeee@users.noreply.github.com> Date: Mon, 21 Nov 2022 17:51:40 -0500 Subject: [PATCH 01/61] wip: tc-opt --- src/Formula/optimization.test.ts | 6 +- src/Formula/optimization.ts | 19 +++- .../Tabs/TabOptimize/index.tsx | 4 +- .../Tabs/TabTheorycraft/index.tsx | 103 +++++++++++++++++- src/Types/character.d.ts | 1 + 5 files changed, 120 insertions(+), 13 deletions(-) diff --git a/src/Formula/optimization.test.ts b/src/Formula/optimization.test.ts index 0510e4a9a0..8648154609 100644 --- a/src/Formula/optimization.test.ts +++ b/src/Formula/optimization.test.ts @@ -75,14 +75,14 @@ describe("optimization", () => { const output1 = sum(1, r1, r2), output2 = prod(r2, r3), output3 = sum(output1, output2) const compute = precompute([output1] as OptNode[], {}, x => x.path[0], 1) - expect([...compute([{ id: "", values: { 0: 32, 1: 77 } }]).slice(0, 1)]).toEqual([1 + 32 + 77]) + expect([...compute([{ values: { 0: 32, 1: 77 } }]).slice(0, 1)]).toEqual([1 + 32 + 77]) }) test("Output is read node", () => { const r1 = inputs[0], r2 = inputs[1], r3 = inputs[2] const output1 = sum(1, r1, r2), output2 = prod(r2, r3), output3 = sum(output1, output2) const compute = precompute([r1], {}, x => x.path[0], 1) - expect([...compute([{ id: "", values: { 0: 32 } }]).slice(0, 1)]).toEqual([32]) + expect([...compute([{ values: { 0: 32 } }]).slice(0, 1)]).toEqual([32]) }) test("Output is constant node", () => { const r1 = inputs[0], r2 = inputs[1], r3 = inputs[2] @@ -96,7 +96,7 @@ describe("optimization", () => { const output1 = sum(1, r1, r2), output2 = prod(r2, r3), output3 = sum(output1, output2) const compute = precompute([output3, output3] as OptNode[], {}, x => x.path[0], 1) - expect([...compute([{ id: "", values: { 0: 2, 1: 44, 2: 7 } }]).slice(0, 2)]).toEqual([(1 + 2 + 44) + (44 * 7), (1 + 2 + 44) + (44 * 7)]) + expect([...compute([{ values: { 0: 2, 1: 44, 2: 7 } }]).slice(0, 2)]).toEqual([(1 + 2 + 44) + (44 * 7), (1 + 2 + 44) + (44 * 7)]) }) }) }) diff --git a/src/Formula/optimization.ts b/src/Formula/optimization.ts index efa654ce9f..d1f81f0469 100644 --- a/src/Formula/optimization.ts +++ b/src/Formula/optimization.ts @@ -1,4 +1,4 @@ -import type { ArtifactBuildData } from "../PageCharacter/CharacterDisplay/Tabs/TabOptimize/common" +import type { ArtifactBuildData, DynStat } from "../PageCharacter/CharacterDisplay/Tabs/TabOptimize/common" import { assertUnreachable, objPathValue } from "../Util/Util" import { customMapFormula, forEachNodes, mapFormulas } from "./internal" import { AnyNode, CommutativeMonoidOperation, ComputeNode, ConstantNode, Data, NumNode, Operation, ReadNode, StrNode, StrPrioNode, ThresholdNode } from "./type" @@ -31,7 +31,19 @@ export function optimize(formulas: NumNode[], topLevelData: Data, shouldFold = ( opts = flatten(opts) return deduplicate(opts) } -export function precompute(formulas: OptNode[], initial: ArtifactBuildData["values"], binding: (readNode: ReadNode | ReadNode) => string, slotCount: number): (_: ArtifactBuildData[]) => number[] { + +/** + * Compile an array of `formulas` into a JS `Function` + * + * The nodes in the array should be automatically deduped by the JS engine + * + * @param formulas + * @param initial base stats for the formula + * @param binding + * @param slotCount the number of slots in the build (usually 5) + * @returns + */ +export function precompute(formulas: OptNode[], initial: DynStat, binding: (readNode: ReadNode) => string, slotCount: number): (_: { values: DynStat }[]) => number[] { let body = ` "use strict"; // copied from the code above @@ -70,6 +82,9 @@ const x0=0`; // making sure `const` has at least one entry default: assertUnreachable(operation) } + if (operation !== "const") { + body += "\n" + } }) body += `;\nreturn [${formulas.map(f => names.get(f)!)}]` return new (Function as any)(`b`, body) diff --git a/src/PageCharacter/CharacterDisplay/Tabs/TabOptimize/index.tsx b/src/PageCharacter/CharacterDisplay/Tabs/TabOptimize/index.tsx index 1c143d3905..6f1e8370e5 100644 --- a/src/PageCharacter/CharacterDisplay/Tabs/TabOptimize/index.tsx +++ b/src/PageCharacter/CharacterDisplay/Tabs/TabOptimize/index.tsx @@ -19,7 +19,7 @@ import { mergeData, uiDataForTeam } from '../../../../Formula/api'; import { uiInput as input } from '../../../../Formula/index'; import { optimize } from '../../../../Formula/optimization'; import { NumNode } from '../../../../Formula/type'; -import { NodeDisplay, UIData } from '../../../../Formula/uiData'; +import { UIData } from '../../../../Formula/uiData'; import useCharacterReducer from '../../../../ReactHooks/useCharacterReducer'; import useCharSelectionCallback from '../../../../ReactHooks/useCharSelectionCallback'; import useForceUpdate from '../../../../ReactHooks/useForceUpdate'; @@ -113,7 +113,7 @@ export default function TabBuild() { const teamData = await getTeamData(database, characterKey, mainStatAssumptionLevel, []) if (!teamData) return - const workerData = uiDataForTeam(teamData.teamData, characterKey)[characterKey as CharacterKey]?.target.data![0] + const workerData = uiDataForTeam(teamData.teamData, characterKey)[characterKey]?.target.data![0] if (!workerData) return Object.assign(workerData, mergeData([workerData, dynamicData])) // Mark art fields as dynamic let unoptimizedOptimizationTargetNode = objPathValue(workerData.display ?? {}, optimizationTarget) as NumNode | undefined diff --git a/src/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx b/src/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx index 10f400bdf8..1cf457713e 100644 --- a/src/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx +++ b/src/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx @@ -33,19 +33,24 @@ import WeaponSheet from "../../../../Data/Weapons/WeaponSheet"; import { initCharTC } from "../../../../Database/DataManagers/CharacterTCData"; import { DatabaseContext } from "../../../../Database/Database"; import { uiInput as input } from "../../../../Formula"; -import { computeUIData, dataObjForWeapon } from "../../../../Formula/api"; +import { computeUIData, dataObjForWeapon, mergeData } from "../../../../Formula/api"; import { constant, percent } from "../../../../Formula/utils"; import KeyMap, { cacheValueString } from "../../../../KeyMap"; import useBoolState from "../../../../ReactHooks/useBoolState"; import usePromise from "../../../../ReactHooks/usePromise"; import useTeamData from "../../../../ReactHooks/useTeamData"; -import { ICachedArtifact, MainStatKey, SubstatKey } from "../../../../Types/artifact"; +import { allSubstatKeys, ICachedArtifact, MainStatKey, SubstatKey } from "../../../../Types/artifact"; import { ICharTC, ICharTCArtifactSlot } from "../../../../Types/character"; -import { allSlotKeys, ArtifactRarity, ArtifactSetKey, SetNum, SlotKey, SubstatType, substatType, WeaponTypeKey } from "../../../../Types/consts"; +import { allArtifactSets, allSlotKeys, ArtifactRarity, ArtifactSetKey, SetNum, SlotKey, SubstatType, substatType, WeaponTypeKey } from "../../../../Types/consts"; import { ICachedWeapon } from "../../../../Types/weapon"; -import { deepClone, objectMap } from "../../../../Util/Util"; +import { deepClone, objectMap, objPathValue } from "../../../../Util/Util"; import { defaultInitialWeaponKey } from "../../../../Util/WeaponUtil"; import useCharTC from "./useCharTC"; +import OptimizationTargetSelector from "../TabOptimize/Components/OptimizationTargetSelector"; +import { optimize, precompute } from "../../../../Formula/optimization"; +import { NumNode } from "../../../../Formula/type"; +import { dynamicData } from "../TabOptimize/foreground"; +import { mapFormulas } from "../../../../Formula/internal"; const WeaponSelectionModal = React.lazy(() => import('../../../../Components/Weapon/WeaponSelectionModal')) type ISet = Partial> @@ -90,7 +95,7 @@ export default function TabTheorycraft() { value === 3 ? 2 : value === 5 ? 4 : value === 1 && !(key as string).startsWith("PrayersFor") ? 0 : value - ]).filter(([key, value]) => value)) + ]).filter(([, value]) => value)) setData(newData) }, [data, setData], @@ -177,6 +182,82 @@ export default function TabTheorycraft() { oldData: compareData ? oldData : undefined, } }, [dataContextValue, compareData, oldData]) + + // Fixme: persist across page reloads + const [optimizationTarget, setOptimizationTarget] = useState(undefined); + + // This is mostly copied from TabOptimize/index.tsx except where noted and where i forgot to note + const optimizeSubstats = useCallback(async () => { + if (!characterKey || !optimizationTarget) return + if (!teamData) return + // Fixme: why is data an array? + const workerData = teamData[characterKey]?.target.data[0] + if (!workerData) return + Object.assign(workerData, mergeData([workerData, dynamicData])) // Mark art fields as dynamic + const unoptimizedOptimizationTargetNode = objPathValue(workerData.display ?? {}, optimizationTarget) as NumNode | undefined + if (!unoptimizedOptimizationTargetNode) return + let unoptimizedNodes = [unoptimizedOptimizationTargetNode] + let nodes = optimize(unoptimizedNodes, workerData, ({ path: [p] }) => p !== "dyn") + // Const fold the artifact set + nodes = mapFormulas(nodes, f => { + if (f.operation === "read" && f.path[0] === "dyn") { + const a = data.artifact.sets[f.path[1]]; + if (a) { + return constant(a) + } else if (allArtifactSets.includes(f.path[1] as any)) { + return constant(0) + } + } + return f + }, f => f) + nodes = optimize(nodes, {}, _ => false) + + // xd + const subs = new Set() + let compute = precompute(nodes, {}, f => { + subs.add(f.path[1]) + return f.path[1] + }, 3) + const realSubs = [...subs].filter(x => allSubstatKeys.includes(x as any)) + const comp = (statKey: string) => statKey.endsWith("_") ? 100 : 1 + + // KQMC + // Fixme: inputs + const freeSubs = 20 + // Fixme: -2 per main stat + const maxSubs = Object.fromEntries(allSubstatKeys.map(x => [x, 10])) + + let max = -Infinity + const buffer = Object.fromEntries([...subs].map(x => [x, 0])) + let maxBuffer: typeof buffer | undefined; + const bufferMain = objectMap(data.artifact.slots, ({ statKey, rarity, level }) => Artifact.mainStatValue(statKey, rarity, level) / comp(statKey)) + const bufferSubs = objectMap(data.artifact.substats.stats, (v, k) => v / comp(k)) + const g = (freeSubs: number, [x, ...xs]: string[]) => { + if (xs.length === 0) { + if (freeSubs > maxSubs[x]) + return + buffer[x] = Artifact.substatValue(x as SubstatKey, 5, data.artifact.substats.type) / comp(x) * freeSubs; + const [result] = compute([{ values: bufferMain }, { values: bufferSubs }, { values: buffer }]); + if (result > max) { + max = result + maxBuffer = JSON.parse(JSON.stringify(buffer)) + } + return + } + for (let i = 0; i <= Math.min(maxSubs[x], freeSubs); i++) { + buffer[x] = Artifact.substatValue(x as SubstatKey, 5, data.artifact.substats.type) / comp(x) * i; + g(freeSubs - i, xs) + } + } + g(freeSubs, realSubs) + console.log(maxBuffer) + console.log(objectMap(maxBuffer!, (v, x) => + allSubstatKeys.includes(x as any) ? + v / (Artifact.substatValue(x as SubstatKey, 5, data.artifact.substats.type) / comp(x)) : + v + )) + }, [characterKey, data.artifact.sets, data.artifact.slots, data.artifact.substats.stats, data.artifact.substats.type, optimizationTarget, teamData]) + return @@ -200,6 +281,16 @@ export default function TabTheorycraft() { s.statKey)} /> + setOptimizationTarget(target)} + /> + : } {dataContextValueWithOld ? @@ -236,7 +327,7 @@ function WeaponEditorCard({ weapon, setWeapon, weaponTypeKey }: { weapon: ICache {weaponUIData && - {[input.weapon.main, input.weapon.sub, input.weapon.sub2].map((node, i) => { + {[input.weapon.main, input.weapon.sub, input.weapon.sub2].map((node) => { const n = weaponUIData.get(node) if (n.isEmpty || !n.value) return null return diff --git a/src/Types/character.d.ts b/src/Types/character.d.ts index 522385d7cd..81e59b1a5c 100644 --- a/src/Types/character.d.ts +++ b/src/Types/character.d.ts @@ -3,6 +3,7 @@ import { EleEnemyResKey } from "../KeyMap"; import { MainStatKey } from "./artifact"; import { AdditiveReactionKey, AmpReactionKey, ArtifactRarity, Ascension, CharacterKey, HitModeKey, InfusionAuraElements, SlotKey, SubstatType } from "./consts"; import { IConditionalValues } from "./IConditional"; +import { SubstatKey } from "./artifact"; export interface CustomTarget { weight: number, From 3ed54a9d925b7dad917d7189700a6c539e68ca2b Mon Sep 17 00:00:00 2001 From: eeeqeee <103794572+eeeqeee@users.noreply.github.com> Date: Wed, 23 Nov 2022 15:18:52 -0500 Subject: [PATCH 02/61] fixes --- src/Components/CustomNumberInput.tsx | 3 +- src/Database/DataManagers/CharacterTCData.ts | 32 +++- src/Formula/optimization.ts | 2 +- src/KeyMap/index.tsx | 2 +- .../Components/ArtifactSetConfig.tsx | 2 +- .../CharacterDisplay/Tabs/TabTeambuffs.tsx | 2 +- .../Tabs/TabTheorycraft/index.tsx | 169 +++++++++++++++--- src/Types/character.d.ts | 11 +- 8 files changed, 187 insertions(+), 36 deletions(-) diff --git a/src/Components/CustomNumberInput.tsx b/src/Components/CustomNumberInput.tsx index aa9e250f66..6c4a8a7ae2 100644 --- a/src/Components/CustomNumberInput.tsx +++ b/src/Components/CustomNumberInput.tsx @@ -38,7 +38,7 @@ export function CustomNumberInputButtonGroupWrapper({ children, disableRipple, d return {children} } -export default function CustomNumberInput({ value = 0, onChange, disabled = false, float = false, ...props }: CustomNumberInputProps) { +export default function CustomNumberInput({ value = 0, onChange, float = false, ...props }: CustomNumberInputProps) { const { inputProps = {}, ...restProps } = props const [number, setNumber] = useState(value) @@ -74,7 +74,6 @@ export default function CustomNumberInput({ value = 0, onChange, disabled = fals onChange={onInputChange} onBlur={onBlur} onFocus={onFocus} - disabled={disabled} onKeyDown={onKeyDown} {...restProps} /> diff --git a/src/Database/DataManagers/CharacterTCData.ts b/src/Database/DataManagers/CharacterTCData.ts index 2bf0508b0c..c4135b8688 100644 --- a/src/Database/DataManagers/CharacterTCData.ts +++ b/src/Database/DataManagers/CharacterTCData.ts @@ -20,7 +20,9 @@ export class CharacterTCDataManager extends DataManager 0) }, sets: {} + }, + optimization: { + target: undefined, + distributedSubstats: 20, + maxSubstats: initCharTcOptimizationMaxSubstats() } } } @@ -85,3 +92,26 @@ function validateCharTCArtifactSlots(slots: any): ICharTC["artifact"]["slots"] | if (Object.keys(slots).length !== allSlotKeys.length || Object.keys(slots).some(s => !allSlotKeys.includes(s as any))) return initCharTCArtifactSlots() return slots } +function validateCharTcOptimization(optimization: any): ICharTC["optimization"] | undefined { + if (typeof optimization !== "object") return + let { target, distributedSubstats, maxSubstats } = optimization + if (!Array.isArray(target)) target = undefined + if (typeof distributedSubstats !== "number") distributedSubstats = 20 + if (typeof maxSubstats !== "object") maxSubstats = initCharTcOptimizationMaxSubstats() + maxSubstats = objectKeyMap([...allSubstatKeys, "useMaxOff", "max", "offset",], + k => { + if (k === "useMaxOff") + return typeof maxSubstats[k] === "boolean" ? maxSubstats[k] : true + else + return typeof maxSubstats[k] === "number" ? maxSubstats[k] : 0 + }) + return { target, distributedSubstats, maxSubstats } +} +function initCharTcOptimizationMaxSubstats(): ICharTC["optimization"]["maxSubstats"] { + return { + ...objectKeyMap(allSubstatKeys, () => 0), + useMaxOff: true, + max: 10, + offset: 2 + } +} diff --git a/src/Formula/optimization.ts b/src/Formula/optimization.ts index d1f81f0469..7490b65c82 100644 --- a/src/Formula/optimization.ts +++ b/src/Formula/optimization.ts @@ -1,4 +1,4 @@ -import type { ArtifactBuildData, DynStat } from "../PageCharacter/CharacterDisplay/Tabs/TabOptimize/common" +import type { DynStat } from "../PageCharacter/CharacterDisplay/Tabs/TabOptimize/common" import { assertUnreachable, objPathValue } from "../Util/Util" import { customMapFormula, forEachNodes, mapFormulas } from "./internal" import { AnyNode, CommutativeMonoidOperation, ComputeNode, ConstantNode, Data, NumNode, Operation, ReadNode, StrNode, StrPrioNode, ThresholdNode } from "./type" diff --git a/src/KeyMap/index.tsx b/src/KeyMap/index.tsx index 438bfdc119..490e6cf22d 100644 --- a/src/KeyMap/index.tsx +++ b/src/KeyMap/index.tsx @@ -240,7 +240,7 @@ export function valueString(value: number, unit: Unit = "", fixed = -1): string } export function cacheValueString(value: number, unit: Unit): string { - switch (unit as any) { + switch (unit) { case "%": return (Math.round(value * 10) / 10).toFixed(1) // TODO: % conversion default: return Math.round(value).toFixed(0) } diff --git a/src/PageCharacter/CharacterDisplay/Tabs/TabOptimize/Components/ArtifactSetConfig.tsx b/src/PageCharacter/CharacterDisplay/Tabs/TabOptimize/Components/ArtifactSetConfig.tsx index bda4e5b367..476e29967d 100644 --- a/src/PageCharacter/CharacterDisplay/Tabs/TabOptimize/Components/ArtifactSetConfig.tsx +++ b/src/PageCharacter/CharacterDisplay/Tabs/TabOptimize/Components/ArtifactSetConfig.tsx @@ -64,7 +64,7 @@ export default function ArtifactSetConfig({ disabled }: { disabled?: boolean, }) const exclude2 = artKeysByRarity.length - allow2, exclude4 = artKeysByRarity.length - allow4 const artifactCondCount = useMemo(() => (Object.keys(conditional)).filter(k => - allArtifactSets.includes(k as ArtifactSetKey) && Object.keys(conditional[k]).length !== 0).length + allArtifactSets.includes(k as ArtifactSetKey) && Object.keys(conditional[k]!).length !== 0).length , [conditional]) const fakeDataContextObj = useMemo(() => ({ ...dataContext, diff --git a/src/PageCharacter/CharacterDisplay/Tabs/TabTeambuffs.tsx b/src/PageCharacter/CharacterDisplay/Tabs/TabTeambuffs.tsx index 7bf9e5570b..8396abe867 100644 --- a/src/PageCharacter/CharacterDisplay/Tabs/TabTeambuffs.tsx +++ b/src/PageCharacter/CharacterDisplay/Tabs/TabTeambuffs.tsx @@ -102,7 +102,7 @@ function TeammateDisplay({ index }: { index: number }) { if (!teamMateKey) return if (!("conditional" in state)) return const { conditional } = state - characterDispatch({ type: "teamConditional", teamMateKey: teamMateKey, conditional }) + characterDispatch({ type: "teamConditional", teamMateKey: teamMateKey, conditional: conditional! }) } }, [active, teamMateKey, dataBundle, characterDispatch]) const teamMateDataContext: dataContextObj | undefined = useMemo(() => dataBundle && { diff --git a/src/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx b/src/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx index 1cf457713e..8c061514a4 100644 --- a/src/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx +++ b/src/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx @@ -43,7 +43,7 @@ import { allSubstatKeys, ICachedArtifact, MainStatKey, SubstatKey } from "../../ import { ICharTC, ICharTCArtifactSlot } from "../../../../Types/character"; import { allArtifactSets, allSlotKeys, ArtifactRarity, ArtifactSetKey, SetNum, SlotKey, SubstatType, substatType, WeaponTypeKey } from "../../../../Types/consts"; import { ICachedWeapon } from "../../../../Types/weapon"; -import { deepClone, objectMap, objPathValue } from "../../../../Util/Util"; +import { deepClone, objectKeyMap, objectMap, objPathValue } from "../../../../Util/Util"; import { defaultInitialWeaponKey } from "../../../../Util/WeaponUtil"; import useCharTC from "./useCharTC"; import OptimizationTargetSelector from "../TabOptimize/Components/OptimizationTargetSelector"; @@ -183,14 +183,35 @@ export default function TabTheorycraft() { } }, [dataContextValue, compareData, oldData]) - // Fixme: persist across page reloads - const [optimizationTarget, setOptimizationTarget] = useState(undefined); + const optimizationTarget = data.optimization.target + const setOptimizationTarget = useCallback((optimizationTarget: ICharTC["optimization"]["target"]) => { + const data_ = deepClone(data) + data_.optimization.target = optimizationTarget + setData(data_) + }, [data, setData]) + + const distributedSubstats = data.optimization.distributedSubstats + const setDistributedSubstats = (distributedSubstats: ICharTC["optimization"]["distributedSubstats"]) => { + const data_ = deepClone(data) + data_.optimization.distributedSubstats = distributedSubstats + setData(data_) + } + const maxSubstats = useMemo(() => { + let result: Record + let maxSubstats = data.optimization.maxSubstats; + if (maxSubstats.useMaxOff) { + const { max, offset } = maxSubstats; + result = objectKeyMap(allSubstatKeys, (k) => max - offset * Object.values(data.artifact.slots).reduce((p, s) => p + +(s.statKey === k), 0)); + } else { + result = maxSubstats + } + return result; + }, [data.artifact.slots, data.optimization.maxSubstats]) // This is mostly copied from TabOptimize/index.tsx except where noted and where i forgot to note - const optimizeSubstats = useCallback(async () => { + const optimizeSubstats = useCallback((apply: boolean) => () => { if (!characterKey || !optimizationTarget) return if (!teamData) return - // Fixme: why is data an array? const workerData = teamData[characterKey]?.target.data[0] if (!workerData) return Object.assign(workerData, mergeData([workerData, dynamicData])) // Mark art fields as dynamic @@ -218,45 +239,48 @@ export default function TabTheorycraft() { subs.add(f.path[1]) return f.path[1] }, 3) - const realSubs = [...subs].filter(x => allSubstatKeys.includes(x as any)) + let realSubs = [...subs].filter(x => allSubstatKeys.includes(x as any)) + if (realSubs.reduce((p, x) => p + maxSubstats[x], 0) < distributedSubstats) + realSubs.push("__unused__") const comp = (statKey: string) => statKey.endsWith("_") ? 100 : 1 - // KQMC - // Fixme: inputs - const freeSubs = 20 - // Fixme: -2 per main stat - const maxSubs = Object.fromEntries(allSubstatKeys.map(x => [x, 10])) - let max = -Infinity const buffer = Object.fromEntries([...subs].map(x => [x, 0])) let maxBuffer: typeof buffer | undefined; const bufferMain = objectMap(data.artifact.slots, ({ statKey, rarity, level }) => Artifact.mainStatValue(statKey, rarity, level) / comp(statKey)) const bufferSubs = objectMap(data.artifact.substats.stats, (v, k) => v / comp(k)) - const g = (freeSubs: number, [x, ...xs]: string[]) => { + const permute = (distributedSubstats: number, [x, ...xs]: string[]) => { if (xs.length === 0) { - if (freeSubs > maxSubs[x]) + if (distributedSubstats > maxSubstats[x]) return - buffer[x] = Artifact.substatValue(x as SubstatKey, 5, data.artifact.substats.type) / comp(x) * freeSubs; + if (x !== "__unused__") + buffer[x] = Artifact.substatValue(x as SubstatKey, 5, data.artifact.substats.type) / comp(x) * distributedSubstats; const [result] = compute([{ values: bufferMain }, { values: bufferSubs }, { values: buffer }]); if (result > max) { max = result - maxBuffer = JSON.parse(JSON.stringify(buffer)) + maxBuffer = structuredClone(buffer) } return } - for (let i = 0; i <= Math.min(maxSubs[x], freeSubs); i++) { + for (let i = 0; i <= Math.min(maxSubstats[x], distributedSubstats); i++) { buffer[x] = Artifact.substatValue(x as SubstatKey, 5, data.artifact.substats.type) / comp(x) * i; - g(freeSubs - i, xs) + permute(distributedSubstats - i, xs) } } - g(freeSubs, realSubs) + permute(distributedSubstats, realSubs) console.log(maxBuffer) console.log(objectMap(maxBuffer!, (v, x) => allSubstatKeys.includes(x as any) ? v / (Artifact.substatValue(x as SubstatKey, 5, data.artifact.substats.type) / comp(x)) : v )) - }, [characterKey, data.artifact.sets, data.artifact.slots, data.artifact.substats.stats, data.artifact.substats.type, optimizationTarget, teamData]) + + if (apply) { + const data_ = deepClone(data) + data_.artifact.substats.stats = objectMap(data.artifact.substats.stats, (v, k) => v + (maxBuffer![k] ?? 0) * comp(k)) + setData(data_) + } + }, [characterKey, data, distributedSubstats, maxSubstats, optimizationTarget, setData, teamData]) return @@ -277,8 +301,36 @@ export default function TabTheorycraft() { - - s.statKey)} /> + + s.statKey)} + distributedSubstats={distributedSubstats} setDistributedSubstats={setDistributedSubstats} + maxSubstats={maxSubstats} setMaxSubstats={(k: SubstatKey) => (v: number) => { + if (data.optimization.maxSubstats[k] === v) return + const data_ = deepClone(data) + data_.optimization.maxSubstats.useMaxOff = false + data_.optimization.maxSubstats[k] = v + setData(data_) + }} + max={data.optimization.maxSubstats.max} + setMax={(v) => { + if (data.optimization.maxSubstats.max === v) return + const data_ = deepClone(data) + data_.optimization.maxSubstats.useMaxOff = true + data_.optimization.maxSubstats.max = v + setData(data_) + }} + offset={data.optimization.maxSubstats.offset} + setOffset={(v) => { + if (data.optimization.maxSubstats.offset === v) return + const data_ = deepClone(data) + data_.optimization.maxSubstats.useMaxOff = true + data_.optimization.maxSubstats.offset = v + setData(data_) + }} + disableMaxSubstats={data.optimization.maxSubstats.useMaxOff} /> setOptimizationTarget(target)} /> + : } {dataContextValueWithOld ? : } - } @@ -477,14 +534,24 @@ function ArtifactSetEditor({ setKey, value, setValue, deleteValue, remaining }: } } -function ArtifactSubCard({ substats, setSubstats, substatsType, setSubstatsType, mainStatKeys }: { substats: Record, setSubstats: (substats: Record) => void, substatsType: SubstatType, setSubstatsType: (t: SubstatType) => void, mainStatKeys: MainStatKey[] }) { +function ArtifactSubCard({ substats, setSubstats, substatsType, setSubstatsType, mainStatKeys, distributedSubstats, setDistributedSubstats, maxSubstats, setMaxSubstats, disableMaxSubstats, max, setMax, offset, setOffset +}: { + substats: Record, setSubstats: (substats: Record) => void, + substatsType: SubstatType, setSubstatsType: (t: SubstatType) => void, + mainStatKeys: MainStatKey[], + distributedSubstats: number, setDistributedSubstats: (f: number) => void, + maxSubstats: Record, setMaxSubstats: (k: SubstatKey) => (v: number) => void, + max: number, setMax: (v: number) => void, + offset: number, setOffset: (v: number) => void, + disableMaxSubstats: boolean +}) { const setValue = useCallback((key: SubstatKey) => (v: number) => setSubstats({ ...substats, [key]: v }), [substats, setSubstats]) const { t } = useTranslation("page_character") const rv = Object.entries(substats).reduce((t, [k, v]) => t + (v / Artifact.substatValue(k)), 0) * 100 const rolls = Object.entries(substats).reduce((t, [k, v]) => t + (v / Artifact.substatValue(k, undefined, substatsType)), 0) return - + {substatType.map(st => setSubstatsType(st)}>{t(`tabTheorycraft.substatType.${st}`)})} {t`tabTheorycraft.maxTotalRolls`}} placement="top"> @@ -493,13 +560,53 @@ function ArtifactSubCard({ substats, setSubstats, substatsType, setSubstatsType, 45 ? "warning" : undefined} >RV: {rv.toFixed(1)}% + v !== undefined && setDistributedSubstats(v)} + endAdornment={"Distributed Substats"} + sx={{ borderRadius: 1, px: 1, width: "50%" }} + inputProps={{ sx: { textAlign: "right", px: 1, width: "20%" }, min: 0 }} + /> + v !== undefined && setMax(v)} + endAdornment={"Max"} + color={!disableMaxSubstats ? "error" : "success"} + sx={{ borderRadius: 1, px: 1 }} + inputProps={{ sx: { textAlign: "right", px: 1 }, min: 0 }} + /> + v !== undefined && setOffset(v)} + endAdornment={"Offset"} + color={!disableMaxSubstats ? "error" : "success"} + sx={{ borderRadius: 1, px: 1 }} + inputProps={{ sx: { textAlign: "right", px: 1 }, min: 0 }} + /> - {Object.entries(substats).map(([k, v]) => )} + {Object.entries(substats).map(([k, v]) => + )} } -function ArtifactSubstatEditor({ statKey, value, setValue, substatsType, mainStatKeys }: { statKey: SubstatKey, value: number, setValue: (v: number) => void, substatsType: SubstatType, mainStatKeys: MainStatKey[] }) { +function ArtifactSubstatEditor({ statKey, value, setValue, substatsType, mainStatKeys, maxSubstat, setMaxSubstat, disableMaxSubstats }: { + statKey: SubstatKey, + value: number, setValue: (v: number) => void, + substatsType: SubstatType, + mainStatKeys: MainStatKey[], + maxSubstat: number, setMaxSubstat: (v: number) => void, + disableMaxSubstats: boolean, +}) { const { t } = useTranslation("page_character") const substatValue = Artifact.substatValue(statKey, 5, substatsType) const [rolls, setRolls] = useState(() => value / substatValue) @@ -555,6 +662,12 @@ function ArtifactSubstatEditor({ statKey, value, setValue, substatsType, mainSta onChange={v => v !== undefined && setValue(v * substatValue)} sx={{ borderRadius: 1, px: 1, my: 0, height: "100%", width: "7em" }} inputProps={{ sx: { textAlign: "right", pr: 0.5, }, min: 0, step: 1 }} /> + v !== undefined && setMaxSubstat(v)} + color={disableMaxSubstats ? "error" : "success"} + sx={{ borderRadius: 1, px: 1, my: 0, height: "100%", width: "7em" }} + inputProps={{ sx: { textAlign: "right", pr: 0.5, }, min: 0, step: 1 }} /> } diff --git a/src/Types/character.d.ts b/src/Types/character.d.ts index 81e59b1a5c..621885137a 100644 --- a/src/Types/character.d.ts +++ b/src/Types/character.d.ts @@ -2,7 +2,7 @@ import { input } from "../Formula"; import { EleEnemyResKey } from "../KeyMap"; import { MainStatKey } from "./artifact"; import { AdditiveReactionKey, AmpReactionKey, ArtifactRarity, Ascension, CharacterKey, HitModeKey, InfusionAuraElements, SlotKey, SubstatType } from "./consts"; -import { IConditionalValues } from "./IConditional"; +import { IConditionalValues } from "./sheet"; import { SubstatKey } from "./artifact"; export interface CustomTarget { @@ -66,4 +66,13 @@ export type ICharTC = { }, sets: Partial> } + optimization: { + target?: string[] + distributedSubstats: number + maxSubstats: Record & { + useMaxOff: boolean + max: number + offset: number + } + } } From 3aed95f27397ecda7b838f4f0d09e91b4abe2413 Mon Sep 17 00:00:00 2001 From: eeeqeee <103794572+eeeqeee@users.noreply.github.com> Date: Mon, 9 Jan 2023 14:34:23 -0500 Subject: [PATCH 03/61] add comments --- src/Formula/optimization.ts | 3 --- .../CharacterDisplay/Tabs/TabTheorycraft/index.tsx | 6 ++++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Formula/optimization.ts b/src/Formula/optimization.ts index 7490b65c82..78d064e5e6 100644 --- a/src/Formula/optimization.ts +++ b/src/Formula/optimization.ts @@ -82,9 +82,6 @@ const x0=0`; // making sure `const` has at least one entry default: assertUnreachable(operation) } - if (operation !== "const") { - body += "\n" - } }) body += `;\nreturn [${formulas.map(f => names.get(f)!)}]` return new (Function as any)(`b`, body) diff --git a/src/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx b/src/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx index 8c061514a4..6cdf9f75c7 100644 --- a/src/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx +++ b/src/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx @@ -208,7 +208,10 @@ export default function TabTheorycraft() { return result; }, [data.artifact.slots, data.optimization.maxSubstats]) - // This is mostly copied from TabOptimize/index.tsx except where noted and where i forgot to note + // This solves + // $\argmax_{x\in N^k, \sum x <= n, x <= x_max} f(x)$ without assumptions on the properties of $f$ + // We brute force iterate over all substats in the graph and compute the maximum + // n.b. some substat combinations may not be materializable into real artifacts const optimizeSubstats = useCallback((apply: boolean) => () => { if (!characterKey || !optimizationTarget) return if (!teamData) return @@ -233,7 +236,6 @@ export default function TabTheorycraft() { }, f => f) nodes = optimize(nodes, {}, _ => false) - // xd const subs = new Set() let compute = precompute(nodes, {}, f => { subs.add(f.path[1]) From 2629306d217e731cf483ff5c7545826d01465b1b Mon Sep 17 00:00:00 2001 From: eeeqeee <103794572+eeeqeee@users.noreply.github.com> Date: Mon, 20 Feb 2023 17:59:06 -0500 Subject: [PATCH 04/61] fix hysteresis --- .../CharacterDisplay/Tabs/TabTheorycraft/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx index 76f85a3018..61b9649bbb 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx @@ -216,9 +216,9 @@ export default function TabTheorycraft() { const optimizeSubstats = useCallback((apply: boolean) => () => { if (!characterKey || !optimizationTarget) return if (!teamData) return - const workerData = teamData[characterKey]?.target.data[0] + let workerData = teamData[characterKey]?.target.data[0] if (!workerData) return - Object.assign(workerData, mergeData([workerData, dynamicData])) // Mark art fields as dynamic + workerData = { ...workerData, ...mergeData([workerData, dynamicData]) } // Mark art fields as dynamic const unoptimizedOptimizationTargetNode = objPathValue(workerData.display ?? {}, optimizationTarget) as NumNode | undefined if (!unoptimizedOptimizationTargetNode) return let unoptimizedNodes = [unoptimizedOptimizationTargetNode] From 44f1e384cb3aa3532dac8b522b5064695675b404 Mon Sep 17 00:00:00 2001 From: eeeqeee <103794572+eeeqeee@users.noreply.github.com> Date: Mon, 20 Feb 2023 18:56:44 -0500 Subject: [PATCH 05/61] eslint fixes + touchups --- apps/frontend/src/app/Formula/optimization.ts | 22 +++++++++---------- .../Tabs/TabTheorycraft/index.tsx | 12 +++++----- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/apps/frontend/src/app/Formula/optimization.ts b/apps/frontend/src/app/Formula/optimization.ts index b3f1eaaac1..72c700c836 100644 --- a/apps/frontend/src/app/Formula/optimization.ts +++ b/apps/frontend/src/app/Formula/optimization.ts @@ -35,8 +35,6 @@ export function optimize(formulas: NumNode[], topLevelData: Data, shouldFold = ( /** * Compile an array of `formulas` into a JS `Function` * - * The nodes in the array should be automatically deduped by the JS engine - * * @param formulas * @param initial base stats for the formula * @param binding @@ -44,9 +42,9 @@ export function optimize(formulas: NumNode[], topLevelData: Data, shouldFold = ( * @returns */ export function precompute(formulas: OptNode[], initial: DynStat, binding: (readNode: ReadNode) => string, slotCount: number): (_: { values: DynStat }[]) => number[] { + // res copied from the code above let body = ` "use strict"; -// copied from the code above function res(res) { if (res < 0) return 1 - res / 2 else if (res >= 0.75) return 1 / (res * 4 + 1) @@ -56,7 +54,7 @@ const x0=0`; // making sure `const` has at least one entry let i = 1; const names = new Map() - forEachNodes(formulas, _ => { }, f => { + forEachNodes(formulas, _ => {/* */ }, f => { const { operation, operands } = f, name = `x${i++}`, operandNames = operands.map((x: OptNode) => names.get(x)!) names.set(f, name) switch (operation) { @@ -66,19 +64,19 @@ const x0=0`; // making sure `const` has at least one entry if (initial[key] && initial[key] !== 0) { arr = [initial[key].toString(), ...arr] } - body += `,${name}=${arr.join('+')}` + body += `,${name}=${arr.join('+')}\n` break } case "const": names.set(f, `(${f.value})`); break - case "add": case "mul": body += `,${name}=${operandNames.join(operation === "add" ? "+" : "*")}`; break - case "min": case "max": body += `,${name}=Math.${operation}(${operandNames})`; break + case "add": case "mul": body += `,${name}=${operandNames.join(operation === "add" ? "+" : "*")}\n`; break + case "min": case "max": body += `,${name}=Math.${operation}(${operandNames})\n`; break case "threshold": { const [value, threshold, pass, fail] = operandNames - body += `,${name}=(${value}>=${threshold})?${pass}:${fail}` + body += `,${name}=(${value}>=${threshold})?${pass}:${fail}\n` break } - case "res": body += `,${name}=res(${operandNames[0]})`; break - case "sum_frac": body += `,${name}=${operandNames[0]}/(${operandNames[0]}+${operandNames[1]})`; break + case "res": body += `,${name}=res(${operandNames[0]})\n`; break + case "sum_frac": body += `,${name}=${operandNames[0]}/(${operandNames[0]}+${operandNames[1]})\n`; break default: assertUnreachable(operation) } @@ -121,12 +119,12 @@ function deduplicate(formulas: OptNode[]): OptNode[] { } } - while (true) { + for (; ;) { let next: typeof wrap.common | undefined const factored: ComputeNode = { operation: wrap.common.operation, operands: arrayFromCounts(wrap.common.counts) } - let candidatesByOperation = new Map, Map][]>() + const candidatesByOperation = new Map, Map][]>() for (const operation of Object.keys(allCommutativeMonoidOperations)) candidatesByOperation.set(operation, []) diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx index 61b9649bbb..557d459f79 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx @@ -199,7 +199,7 @@ export default function TabTheorycraft() { } const maxSubstats = useMemo(() => { let result: Record - let maxSubstats = data.optimization.maxSubstats; + const maxSubstats = data.optimization.maxSubstats; if (maxSubstats.useMaxOff) { const { max, offset } = maxSubstats; result = objectKeyMap(allSubstatKeys, (k) => max - offset * Object.values(data.artifact.slots).reduce((p, s) => p + +(s.statKey === k), 0)); @@ -221,7 +221,7 @@ export default function TabTheorycraft() { workerData = { ...workerData, ...mergeData([workerData, dynamicData]) } // Mark art fields as dynamic const unoptimizedOptimizationTargetNode = objPathValue(workerData.display ?? {}, optimizationTarget) as NumNode | undefined if (!unoptimizedOptimizationTargetNode) return - let unoptimizedNodes = [unoptimizedOptimizationTargetNode] + const unoptimizedNodes = [unoptimizedOptimizationTargetNode] let nodes = optimize(unoptimizedNodes, workerData, ({ path: [p] }) => p !== "dyn") // Const fold the artifact set nodes = mapFormulas(nodes, f => { @@ -238,11 +238,11 @@ export default function TabTheorycraft() { nodes = optimize(nodes, {}, _ => false) const subs = new Set() - let compute = precompute(nodes, {}, f => { + const compute = precompute(nodes, {}, f => { subs.add(f.path[1]) return f.path[1] }, 3) - let realSubs = [...subs].filter(x => allSubstatKeys.includes(x as any)) + const realSubs = [...subs].filter(x => allSubstatKeys.includes(x as any)) if (realSubs.reduce((p, x) => p + maxSubstats[x], 0) < distributedSubstats) realSubs.push("__unused__") const comp = (statKey: string) => statKey.endsWith("_") ? 100 : 1 @@ -250,7 +250,9 @@ export default function TabTheorycraft() { let max = -Infinity const buffer = Object.fromEntries([...subs].map(x => [x, 0])) let maxBuffer: typeof buffer | undefined; - const bufferMain = objectMap(data.artifact.slots, ({ statKey, rarity, level }) => Artifact.mainStatValue(statKey, rarity, level) / comp(statKey)) + const bufferMain = Object.entries(data.artifact.slots).map(( + [_, { statKey, rarity, level }]) => [statKey, Artifact.mainStatValue(statKey, rarity, level) / comp(statKey)] as const + ).reduce>>((acc, [k, v]) => ((acc[k] = (acc[k] ?? 0) + v, acc)), {}) const bufferSubs = objectMap(data.artifact.substats.stats, (v, k) => v / comp(k)) const permute = (distributedSubstats: number, [x, ...xs]: string[]) => { if (xs.length === 0) { From 1737d695d294f8888b9e3388edb5ae26969b4e48 Mon Sep 17 00:00:00 2001 From: eeeqeee <103794572+eeeqeee@users.noreply.github.com> Date: Tue, 28 Feb 2023 21:34:35 -0500 Subject: [PATCH 06/61] remove max+offset from display --- apps/frontend/src/app/Formula/optimization.ts | 2 +- .../Tabs/TabTheorycraft/index.tsx | 23 +++++++++++-------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/apps/frontend/src/app/Formula/optimization.ts b/apps/frontend/src/app/Formula/optimization.ts index 72c700c836..7d66679b01 100644 --- a/apps/frontend/src/app/Formula/optimization.ts +++ b/apps/frontend/src/app/Formula/optimization.ts @@ -60,7 +60,7 @@ const x0=0`; // making sure `const` has at least one entry switch (operation) { case "read": { const key = binding(f) - let arr = new Array(slotCount).fill(null).map((x, i) => `(b[${i}].values["${key}"] ?? 0)`) + let arr = slotCount ? new Array(slotCount).fill(null).map((_, i) => `(b[${i}].values["${key}"] ?? 0)`) : ['0'] if (initial[key] && initial[key] !== 0) { arr = [initial[key].toString(), ...arr] } diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx index 557d459f79..70433d07a4 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx @@ -33,12 +33,12 @@ import { getWeaponSheet } from "../../../../Data/Weapons"; import { DatabaseContext } from "../../../../Database/Database"; import { initCharTC } from "../../../../Database/DataManagers/CharacterTCData"; import { uiInput as input } from "../../../../Formula"; -import { computeUIData, dataObjForWeapon, mergeData } from "../../../../Formula/api"; +import { computeUIData, dataObjForWeapon, mergeData, uiDataForTeam } from "../../../../Formula/api"; import { constant, percent } from "../../../../Formula/utils"; import KeyMap, { cacheValueString } from "../../../../KeyMap"; import StatIcon from "../../../../KeyMap/StatIcon"; import useBoolState from "../../../../ReactHooks/useBoolState"; -import useTeamData from "../../../../ReactHooks/useTeamData"; +import useTeamData, { getTeamData } from "../../../../ReactHooks/useTeamData"; import { iconInlineProps } from "../../../../SVGIcons"; import { allSubstatKeys, ICachedArtifact, MainStatKey, SubstatKey } from "../../../../Types/artifact"; import { ICharTC, ICharTCArtifactSlot } from "../../../../Types/character"; @@ -52,6 +52,7 @@ import { optimize, precompute } from "../../../../Formula/optimization"; import { NumNode } from "../../../../Formula/type"; import OptimizationTargetSelector from "../TabOptimize/Components/OptimizationTargetSelector"; import { dynamicData } from "../TabOptimize/foreground"; +import useDBMeta from "../../../../ReactHooks/useDBMeta"; const WeaponSelectionModal = React.lazy(() => import('../../../../Components/Weapon/WeaponSelectionModal')) type ISet = Partial> @@ -192,11 +193,11 @@ export default function TabTheorycraft() { }, [data, setData]) const distributedSubstats = data.optimization.distributedSubstats - const setDistributedSubstats = (distributedSubstats: ICharTC["optimization"]["distributedSubstats"]) => { + const setDistributedSubstats = useCallback((distributedSubstats: ICharTC["optimization"]["distributedSubstats"]) => { const data_ = deepClone(data) data_.optimization.distributedSubstats = distributedSubstats setData(data_) - } + }, [data, setData]) const maxSubstats = useMemo(() => { let result: Record const maxSubstats = data.optimization.maxSubstats; @@ -209,16 +210,19 @@ export default function TabTheorycraft() { return result; }, [data.artifact.slots, data.optimization.maxSubstats]) + const { gender } = useDBMeta() + // This solves // $\argmax_{x\in N^k, \sum x <= n, x <= x_max} f(x)$ without assumptions on the properties of $f$ // We brute force iterate over all substats in the graph and compute the maximum // n.b. some substat combinations may not be materializable into real artifacts const optimizeSubstats = useCallback((apply: boolean) => () => { if (!characterKey || !optimizationTarget) return + const teamData = getTeamData(database, characterKey) if (!teamData) return - let workerData = teamData[characterKey]?.target.data[0] + const workerData = uiDataForTeam(teamData.teamData, gender, characterKey)[characterKey]?.target.data![0] if (!workerData) return - workerData = { ...workerData, ...mergeData([workerData, dynamicData]) } // Mark art fields as dynamic + Object.assign(workerData, mergeData([workerData, dynamicData])) // Mark art fields as dynamic const unoptimizedOptimizationTargetNode = objPathValue(workerData.display ?? {}, optimizationTarget) as NumNode | undefined if (!unoptimizedOptimizationTargetNode) return const unoptimizedNodes = [unoptimizedOptimizationTargetNode] @@ -284,8 +288,9 @@ export default function TabTheorycraft() { const data_ = deepClone(data) data_.artifact.substats.stats = objectMap(data.artifact.substats.stats, (v, k) => v + (maxBuffer![k] ?? 0) * comp(k)) setData(data_) + setDistributedSubstats(0) } - }, [characterKey, data, distributedSubstats, maxSubstats, optimizationTarget, setData, teamData]) + }, [characterKey, data, database, distributedSubstats, gender, maxSubstats, optimizationTarget, setData, setDistributedSubstats]) return @@ -559,7 +564,7 @@ function ArtifactSubCard({ substats, setSubstats, substatsType, setSubstatsType, sx={{ borderRadius: 1, px: 1, width: "50%" }} inputProps={{ sx: { textAlign: "right", px: 1, width: "20%" }, min: 0 }} /> - v !== undefined && setMax(v)} endAdornment={"Max"} @@ -574,7 +579,7 @@ function ArtifactSubCard({ substats, setSubstats, substatsType, setSubstatsType, color={!disableMaxSubstats ? "error" : "success"} sx={{ borderRadius: 1, px: 1 }} inputProps={{ sx: { textAlign: "right", px: 1 }, min: 0 }} - /> + /> */} {Object.entries(substats).map(([k, v]) => From 743428483126dee00fc61751a16e17302e5a7005 Mon Sep 17 00:00:00 2001 From: eeeqeee <103794572+eeeqeee@users.noreply.github.com> Date: Thu, 2 Mar 2023 01:43:40 -0500 Subject: [PATCH 07/61] Fix apply not working & set max default to 30 subs --- .../src/app/Database/DataManagers/CharacterTCData.ts | 4 ++-- .../CharacterDisplay/Tabs/TabTheorycraft/index.tsx | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/frontend/src/app/Database/DataManagers/CharacterTCData.ts b/apps/frontend/src/app/Database/DataManagers/CharacterTCData.ts index 9b40505043..20ee282804 100644 --- a/apps/frontend/src/app/Database/DataManagers/CharacterTCData.ts +++ b/apps/frontend/src/app/Database/DataManagers/CharacterTCData.ts @@ -109,8 +109,8 @@ function validateCharTcOptimization(optimization: any): ICharTC["optimization"] } function initCharTcOptimizationMaxSubstats(): ICharTC["optimization"]["maxSubstats"] { return { - ...objectKeyMap(allSubstatKeys, () => 0), - useMaxOff: true, + ...objectKeyMap(allSubstatKeys, () => 30), + useMaxOff: false, max: 10, offset: 2 } diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx index 70433d07a4..18103d3f3c 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx @@ -287,10 +287,10 @@ export default function TabTheorycraft() { if (apply) { const data_ = deepClone(data) data_.artifact.substats.stats = objectMap(data.artifact.substats.stats, (v, k) => v + (maxBuffer![k] ?? 0) * comp(k)) + data_.optimization.distributedSubstats = 0 setData(data_) - setDistributedSubstats(0) } - }, [characterKey, data, database, distributedSubstats, gender, maxSubstats, optimizationTarget, setData, setDistributedSubstats]) + }, [characterKey, data, database, distributedSubstats, gender, maxSubstats, optimizationTarget, setData]) return @@ -347,17 +347,17 @@ export default function TabTheorycraft() { optimizationTarget={optimizationTarget} setTarget={target => setOptimizationTarget(target)} /> - + Log Optimized Substats + } : } From c2aebcfffcc89e47c913aedc8b9971fdd77eaf68 Mon Sep 17 00:00:00 2001 From: eeeqeee <> Date: Fri, 3 Mar 2023 01:14:26 -0500 Subject: [PATCH 08/61] wtf is teamData i think it should call `uiDataForTeam` through - teamData - useTeamData - getTeamDataCalc - uiDataForTeam --- .../CharacterDisplay/Tabs/TabTheorycraft/index.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx index 18103d3f3c..a6e56102df 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx @@ -33,12 +33,12 @@ import { getWeaponSheet } from "../../../../Data/Weapons"; import { DatabaseContext } from "../../../../Database/Database"; import { initCharTC } from "../../../../Database/DataManagers/CharacterTCData"; import { uiInput as input } from "../../../../Formula"; -import { computeUIData, dataObjForWeapon, mergeData, uiDataForTeam } from "../../../../Formula/api"; +import { computeUIData, dataObjForWeapon, mergeData } from "../../../../Formula/api"; import { constant, percent } from "../../../../Formula/utils"; import KeyMap, { cacheValueString } from "../../../../KeyMap"; import StatIcon from "../../../../KeyMap/StatIcon"; import useBoolState from "../../../../ReactHooks/useBoolState"; -import useTeamData, { getTeamData } from "../../../../ReactHooks/useTeamData"; +import useTeamData from "../../../../ReactHooks/useTeamData"; import { iconInlineProps } from "../../../../SVGIcons"; import { allSubstatKeys, ICachedArtifact, MainStatKey, SubstatKey } from "../../../../Types/artifact"; import { ICharTC, ICharTCArtifactSlot } from "../../../../Types/character"; @@ -218,11 +218,10 @@ export default function TabTheorycraft() { // n.b. some substat combinations may not be materializable into real artifacts const optimizeSubstats = useCallback((apply: boolean) => () => { if (!characterKey || !optimizationTarget) return - const teamData = getTeamData(database, characterKey) if (!teamData) return - const workerData = uiDataForTeam(teamData.teamData, gender, characterKey)[characterKey]?.target.data![0] + let workerData = teamData[characterKey]?.target.data[0] if (!workerData) return - Object.assign(workerData, mergeData([workerData, dynamicData])) // Mark art fields as dynamic + workerData = { ...workerData, ...mergeData([workerData, dynamicData]) } // Mark art fields as dynamic const unoptimizedOptimizationTargetNode = objPathValue(workerData.display ?? {}, optimizationTarget) as NumNode | undefined if (!unoptimizedOptimizationTargetNode) return const unoptimizedNodes = [unoptimizedOptimizationTargetNode] From f6e571355997938185a3b921598e9b28b172c648 Mon Sep 17 00:00:00 2001 From: frzyc Date: Sat, 4 Mar 2023 04:07:22 -0500 Subject: [PATCH 09/61] cleanup max+offset --- .../Database/DataManagers/CharacterTCData.ts | 73 ++++++++-------- .../Tabs/TabTheorycraft/index.tsx | 84 ++++--------------- apps/frontend/src/app/Types/character.d.ts | 6 +- 3 files changed, 53 insertions(+), 110 deletions(-) diff --git a/apps/frontend/src/app/Database/DataManagers/CharacterTCData.ts b/apps/frontend/src/app/Database/DataManagers/CharacterTCData.ts index 20ee282804..b2299ade35 100644 --- a/apps/frontend/src/app/Database/DataManagers/CharacterTCData.ts +++ b/apps/frontend/src/app/Database/DataManagers/CharacterTCData.ts @@ -1,7 +1,8 @@ +import { allArtifactSlotKeys, allWeaponKeys, ArtifactSlotKey, CharacterKey, WeaponKey } from "@genshin-optimizer/consts"; import { validateLevelAsc } from "../../Data/LevelData"; import { allSubstatKeys, MainStatKey } from "../../Types/artifact"; import { ICharTC } from "../../Types/character"; -import { allSlotKeys, allWeaponKeys, ArtifactRarity, CharacterKey, substatType, WeaponKey } from "../../Types/consts"; +import { ArtifactRarity, substatType } from "../../Types/consts"; import { objectKeyMap } from "../../Util/Util"; import { ArtCharDatabase } from "../Database"; import { DataManager } from "../DataManager"; @@ -14,15 +15,17 @@ export class CharacterTCDataManager extends DataManager ({ + return objectKeyMap(allArtifactSlotKeys, s => ({ level: 20, rarity: 5 as ArtifactRarity, statKey: (s === "flower" ? "hp" : s === "plume" ? "atk" : "atk_") as MainStatKey, })) } -function validateCharTCWeapon(weapon: any): ICharTC["weapon"] | undefined { +function validateCharTCWeapon(weapon: unknown): ICharTC["weapon"] | undefined { if (typeof weapon !== "object") return - let { key, level: rawLevel, ascension: rawAscension, refinement } = weapon - if (!allWeaponKeys.includes(weapon.key)) return + const { key, } = weapon as ICharTC["weapon"] + let { level, ascension, refinement } = weapon as ICharTC["weapon"] + if (!allWeaponKeys.includes(key)) return if (typeof refinement !== "number" || refinement < 1 || refinement > 5) refinement = 1 - const { level, ascension } = validateLevelAsc(rawLevel, rawAscension) + const { level: _level, ascension: _ascension } = validateLevelAsc(level, ascension); + [level, ascension] = [_level, _ascension] return { key, level, ascension, refinement } } -function validateCharTCArtifact(artifact: any): ICharTC["artifact"] | undefined { +function validateCharTCArtifact(artifact: unknown): ICharTC["artifact"] | undefined { if (typeof artifact !== "object") return - let { slots, substats: { type, stats }, sets } = artifact - slots = validateCharTCArtifactSlots(slots) - if (!slots) return + let { slots, substats: { type, stats }, sets } = artifact as ICharTC["artifact"] + const _slots = validateCharTCArtifactSlots(slots) + if (!_slots) return + slots = _slots if (!substatType.includes(type)) type = "max" if (typeof stats !== "object") stats = objectKeyMap(allSubstatKeys, () => 0) stats = objectKeyMap(allSubstatKeys, k => typeof stats[k] === "number" ? stats[k] : 0) + + if (typeof sets !== "object") sets = {} + // TODO: validate sets + return { slots, substats: { type, stats }, sets } } -function validateCharTCArtifactSlots(slots: any): ICharTC["artifact"]["slots"] | undefined { +function validateCharTCArtifactSlots(slots: unknown): ICharTC["artifact"]["slots"] | undefined { if (typeof slots !== "object") return initCharTCArtifactSlots() - if (Object.keys(slots).length !== allSlotKeys.length || Object.keys(slots).some(s => !allSlotKeys.includes(s as any))) return initCharTCArtifactSlots() - return slots + if (Object.keys(slots as ICharTC["artifact"]["slots"]).length !== allArtifactSlotKeys.length || Object.keys(slots as ICharTC["artifact"]["slots"]).some(s => !allArtifactSlotKeys.includes(s as ArtifactSlotKey))) return initCharTCArtifactSlots() + return slots as ICharTC["artifact"]["slots"] } -function validateCharTcOptimization(optimization: any): ICharTC["optimization"] | undefined { +function validateCharTcOptimization(optimization: unknown): ICharTC["optimization"] | undefined { if (typeof optimization !== "object") return - let { target, distributedSubstats, maxSubstats } = optimization + let { target, distributedSubstats, maxSubstats } = optimization as ICharTC["optimization"] if (!Array.isArray(target)) target = undefined if (typeof distributedSubstats !== "number") distributedSubstats = 20 if (typeof maxSubstats !== "object") maxSubstats = initCharTcOptimizationMaxSubstats() - maxSubstats = objectKeyMap([...allSubstatKeys, "useMaxOff", "max", "offset",], - k => { - if (k === "useMaxOff") - return typeof maxSubstats[k] === "boolean" ? maxSubstats[k] : true - else - return typeof maxSubstats[k] === "number" ? maxSubstats[k] : 0 - }) + maxSubstats = objectKeyMap([...allSubstatKeys], k => typeof maxSubstats[k] === "number" ? maxSubstats[k] : 0) return { target, distributedSubstats, maxSubstats } } function initCharTcOptimizationMaxSubstats(): ICharTC["optimization"]["maxSubstats"] { - return { - ...objectKeyMap(allSubstatKeys, () => 30), - useMaxOff: false, - max: 10, - offset: 2 - } + return objectKeyMap(allSubstatKeys, () => 30) } diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx index a099182a42..ca3bf2120b 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx @@ -34,6 +34,9 @@ import { DatabaseContext } from "../../../../Database/Database"; import { initCharTC } from "../../../../Database/DataManagers/CharacterTCData"; import { uiInput as input } from "../../../../Formula"; import { computeUIData, dataObjForWeapon, mergeData } from "../../../../Formula/api"; +import { mapFormulas } from "../../../../Formula/internal"; +import { optimize, precompute } from "../../../../Formula/optimization"; +import { NumNode } from "../../../../Formula/type"; import { constant, percent } from "../../../../Formula/utils"; import KeyMap, { cacheValueString } from "../../../../KeyMap"; import StatIcon from "../../../../KeyMap/StatIcon"; @@ -44,15 +47,11 @@ import { allSubstatKeys, ICachedArtifact, MainStatKey, SubstatKey } from "../../ import { ICharTC, ICharTCArtifactSlot } from "../../../../Types/character"; import { ArtifactRarity, SetNum, SubstatType, substatType } from "../../../../Types/consts"; import { ICachedWeapon } from "../../../../Types/weapon"; -import { deepClone, objectKeyMap, objectMap, objPathValue } from "../../../../Util/Util"; +import { deepClone, objectMap, objPathValue } from "../../../../Util/Util"; import { defaultInitialWeaponKey } from "../../../../Util/WeaponUtil"; -import useCharTC from "./useCharTC"; -import { mapFormulas } from "../../../../Formula/internal"; -import { optimize, precompute } from "../../../../Formula/optimization"; -import { NumNode } from "../../../../Formula/type"; import OptimizationTargetSelector from "../TabOptimize/Components/OptimizationTargetSelector"; import { dynamicData } from "../TabOptimize/foreground"; -import useDBMeta from "../../../../ReactHooks/useDBMeta"; +import useCharTC from "./useCharTC"; const WeaponSelectionModal = React.lazy(() => import('../../../../Components/Weapon/WeaponSelectionModal')) type ISet = Partial> @@ -198,19 +197,7 @@ export default function TabTheorycraft() { data_.optimization.distributedSubstats = distributedSubstats setData(data_) }, [data, setData]) - const maxSubstats = useMemo(() => { - let result: Record - const maxSubstats = data.optimization.maxSubstats; - if (maxSubstats.useMaxOff) { - const { max, offset } = maxSubstats; - result = objectKeyMap(allSubstatKeys, (k) => max - offset * Object.values(data.artifact.slots).reduce((p, s) => p + +(s.statKey === k), 0)); - } else { - result = maxSubstats - } - return result; - }, [data.artifact.slots, data.optimization.maxSubstats]) - - const { gender } = useDBMeta() + const maxSubstats = data.optimization.maxSubstats // This solves // $\argmax_{x\in N^k, \sum x <= n, x <= x_max} f(x)$ without assumptions on the properties of $f$ @@ -289,7 +276,7 @@ export default function TabTheorycraft() { data_.optimization.distributedSubstats = 0 setData(data_) } - }, [characterKey, data, database, distributedSubstats, gender, maxSubstats, optimizationTarget, setData]) + }, [teamData, characterKey, data, distributedSubstats, maxSubstats, optimizationTarget, setData]) return @@ -319,27 +306,9 @@ export default function TabTheorycraft() { maxSubstats={maxSubstats} setMaxSubstats={(k: SubstatKey) => (v: number) => { if (data.optimization.maxSubstats[k] === v) return const data_ = deepClone(data) - data_.optimization.maxSubstats.useMaxOff = false data_.optimization.maxSubstats[k] = v setData(data_) - }} - max={data.optimization.maxSubstats.max} - setMax={(v) => { - if (data.optimization.maxSubstats.max === v) return - const data_ = deepClone(data) - data_.optimization.maxSubstats.useMaxOff = true - data_.optimization.maxSubstats.max = v - setData(data_) - }} - offset={data.optimization.maxSubstats.offset} - setOffset={(v) => { - if (data.optimization.maxSubstats.offset === v) return - const data_ = deepClone(data) - data_.optimization.maxSubstats.useMaxOff = true - data_.optimization.maxSubstats.offset = v - setData(data_) - }} - disableMaxSubstats={data.optimization.maxSubstats.useMaxOff} /> + }} /> } } -function ArtifactSubCard({ substats, setSubstats, substatsType, setSubstatsType, mainStatKeys, distributedSubstats, setDistributedSubstats, maxSubstats, setMaxSubstats, disableMaxSubstats, max, setMax, offset, setOffset }: { +function ArtifactSubCard({ substats, setSubstats, substatsType, setSubstatsType, mainStatKeys, distributedSubstats, setDistributedSubstats, maxSubstats, setMaxSubstats }: { substats: Record, setSubstats: (substats: Record) => void, substatsType: SubstatType, setSubstatsType: (t: SubstatType) => void, mainStatKeys: MainStatKey[], distributedSubstats: number, setDistributedSubstats: (f: number) => void, maxSubstats: Record, setMaxSubstats: (k: SubstatKey) => (v: number) => void, - max: number, setMax: (v: number) => void, - offset: number, setOffset: (v: number) => void, - disableMaxSubstats: boolean }) { const setValue = useCallback((key: SubstatKey) => (v: number) => setSubstats({ ...substats, [key]: v }), [substats, setSubstats]) const { t } = useTranslation("page_character") @@ -563,22 +529,6 @@ function ArtifactSubCard({ substats, setSubstats, substatsType, setSubstatsType, sx={{ borderRadius: 1, px: 1, width: "50%" }} inputProps={{ sx: { textAlign: "right", px: 1, width: "20%" }, min: 0 }} /> - {/* v !== undefined && setMax(v)} - endAdornment={"Max"} - color={!disableMaxSubstats ? "error" : "success"} - sx={{ borderRadius: 1, px: 1 }} - inputProps={{ sx: { textAlign: "right", px: 1 }, min: 0 }} - /> - v !== undefined && setOffset(v)} - endAdornment={"Offset"} - color={!disableMaxSubstats ? "error" : "success"} - sx={{ borderRadius: 1, px: 1 }} - inputProps={{ sx: { textAlign: "right", px: 1 }, min: 0 }} - /> */} {Object.entries(substats).map(([k, v]) => @@ -589,19 +539,17 @@ function ArtifactSubCard({ substats, setSubstats, substatsType, setSubstatsType, substatsType={substatsType} mainStatKeys={mainStatKeys} maxSubstat={maxSubstats[k]} - disableMaxSubstats={disableMaxSubstats} setMaxSubstat={setMaxSubstats(k)} />)} } -function ArtifactSubstatEditor({ statKey, value, setValue, substatsType, mainStatKeys, maxSubstat, setMaxSubstat, disableMaxSubstats }: { +function ArtifactSubstatEditor({ statKey, value, setValue, substatsType, mainStatKeys, maxSubstat, setMaxSubstat }: { statKey: SubstatKey, value: number, setValue: (v: number) => void, substatsType: SubstatType, mainStatKeys: MainStatKey[], maxSubstat: number, setMaxSubstat: (v: number) => void, - disableMaxSubstats: boolean, }) { const { t } = useTranslation("page_character") const substatValue = Artifact.substatValue(statKey, 5, substatsType) @@ -623,11 +571,11 @@ function ArtifactSubstatEditor({ statKey, value, setValue, substatsType, mainSta {KeyMap.getStr(statKey)}{KeyMap.unit(statKey)} - {t(numMains ? `tabTheorycraft.maxRollsMain` : `tabTheorycraft.maxRolls`, { value: maxRolls })}} placement="top"> - - RV: {rv.toFixed(1)}% - - + {/* {t(numMains ? `tabTheorycraft.maxRollsMain` : `tabTheorycraft.maxRolls`, { value: maxRolls })}} placement="top"> */} + + RV: {rv.toFixed(1)}% + + {/* */} v !== undefined && setMaxSubstat(v)} - color={disableMaxSubstats ? "error" : "success"} + color={maxSubstat > 30 ? "warning" : "success"} sx={{ borderRadius: 1, px: 1, my: 0, height: "100%", width: "7em" }} inputProps={{ sx: { textAlign: "right", pr: 0.5, }, min: 0, step: 1 }} /> diff --git a/apps/frontend/src/app/Types/character.d.ts b/apps/frontend/src/app/Types/character.d.ts index 8b0d7fa848..92fa40a2f5 100644 --- a/apps/frontend/src/app/Types/character.d.ts +++ b/apps/frontend/src/app/Types/character.d.ts @@ -68,10 +68,6 @@ export type ICharTC = { optimization: { target?: string[] distributedSubstats: number - maxSubstats: Record & { - useMaxOff: boolean - max: number - offset: number - } + maxSubstats: Record } } From 40afe56977d41191b386aee290bc5755ef041178 Mon Sep 17 00:00:00 2001 From: frzyc Date: Sat, 4 Mar 2023 13:00:29 -0500 Subject: [PATCH 10/61] add UI to mass edit substat rolls/max --- apps/frontend/src/app/Database/DataManager.ts | 5 +- .../Tabs/TabTheorycraft/index.tsx | 190 ++++++++++++------ apps/frontend/src/app/Util/Util.ts | 2 +- 3 files changed, 138 insertions(+), 59 deletions(-) diff --git a/apps/frontend/src/app/Database/DataManager.ts b/apps/frontend/src/app/Database/DataManager.ts index e7bcad0bea..a493ffb6e2 100644 --- a/apps/frontend/src/app/Database/DataManager.ts +++ b/apps/frontend/src/app/Database/DataManager.ts @@ -1,4 +1,4 @@ -import { deepFreeze } from "../Util/Util" +import { deepClone, deepFreeze } from "../Util/Util" import { ArtCharDatabase } from "./Database" import { IGO, IGOOD, ImportResult } from "./exim" export class DataManager { @@ -49,8 +49,9 @@ export class DataManager, notify = true): boolean { + set(key: CacheKey, value: Partial | ((v: StorageValue) => Partial), notify = true): boolean { const old = this.getStorage(key) + if (typeof value === "function") value = value(deepClone(old)) const validated = this.validate({ ...(old ?? {}), ...value }, key) if (!validated) { this.trigger(key, "invalid", value) diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx index ca3bf2120b..cce9a45411 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx @@ -3,7 +3,7 @@ import { weaponAsset } from "@genshin-optimizer/g-assets"; import { CopyAll, DeleteForever, Info, Refresh } from "@mui/icons-material"; import StarRoundedIcon from '@mui/icons-material/StarRounded'; import { Box, Button, ButtonGroup, CardHeader, Divider, Grid, ListItem, MenuItem, Skeleton, Slider, Stack, ToggleButton, Typography } from "@mui/material"; -import React, { Suspense, useCallback, useContext, useDeferredValue, useEffect, useMemo, useState } from "react"; +import React, { createContext, Suspense, useCallback, useContext, useDeferredValue, useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { useLocation } from "react-router-dom"; import ArtifactSetAutocomplete from "../../../../Components/Artifact/ArtifactSetAutocomplete"; @@ -55,26 +55,34 @@ import useCharTC from "./useCharTC"; const WeaponSelectionModal = React.lazy(() => import('../../../../Components/Weapon/WeaponSelectionModal')) type ISet = Partial> +type SetCharTCAction = Partial | ((v: ICharTC) => Partial) +type CharTCContexObj = { + charTC: ICharTC, + setCharTC: (action: SetCharTCAction) => void +} +const CharTCContext = createContext({} as CharTCContexObj) + export default function TabTheorycraft() { const { database } = useContext(DatabaseContext) const { data: oldData } = useContext(DataContext) const { character, character: { key: characterKey, compareData }, characterSheet, characterDispatch } = useContext(CharacterContext) - const data = useCharTC(characterKey, defaultInitialWeaponKey(characterSheet.weaponTypeKey)) - const setData = useCallback((data: ICharTC) => database.charTCs.set(characterKey, data), [characterKey, database]) + const charTC = useCharTC(characterKey, defaultInitialWeaponKey(characterSheet.weaponTypeKey)) + const setCharTC = useCallback((data: SetCharTCAction) => { database.charTCs.set(characterKey, data) }, [characterKey, database]) + const valueCharTCContext = useMemo(() => ({ charTC, setCharTC }), [charTC, setCharTC]) const resetData = useCallback(() => { - setData(initCharTC(defaultInitialWeaponKey(characterSheet.weaponTypeKey))) - }, [setData, characterSheet]) + setCharTC(initCharTC(defaultInitialWeaponKey(characterSheet.weaponTypeKey))) + }, [setCharTC, characterSheet]) const setWeapon = useCallback( (action: Partial) => { - setData({ ...data, weapon: { ...data.weapon, ...action } }) + setCharTC({ ...charTC, weapon: { ...charTC.weapon, ...action } }) }, - [setData, data], + [setCharTC, charTC], ) const copyFrom = useCallback( (eWeapon: ICachedWeapon, build: ICachedArtifact[]) => { const newData = initCharTC(eWeapon.key) - newData.artifact.substats.type = data.artifact.substats.type + newData.artifact.substats.type = charTC.artifact.substats.type newData.weapon.level = eWeapon.level newData.weapon.ascension = eWeapon.ascension @@ -97,9 +105,9 @@ export default function TabTheorycraft() { value === 5 ? 4 : value === 1 && !(key as string).startsWith("PrayersFor") ? 0 : value ]).filter(([, value]) => value)) - setData(newData) + setCharTC(newData) }, - [data, setData], + [charTC, setCharTC], ) const location = useLocation() const { build: locBuild } = (location.state as { build: string[] } | undefined) ?? { build: undefined } @@ -121,31 +129,31 @@ export default function TabTheorycraft() { const weapon: ICachedWeapon = useMemo(() => { return { - ...data.weapon, + ...charTC.weapon, location: "", lock: false, id: "" } - }, [data]) + }, [charTC]) const setArtifact = useCallback((artifact: ICharTC["artifact"]) => { - const data_ = deepClone(data) + const data_ = deepClone(charTC) data_.artifact = artifact - setData(data_) - }, [data, setData]) + setCharTC(data_) + }, [charTC, setCharTC]) const setSubstatsType = useCallback((t: SubstatType) => { - const data_ = deepClone(data) + const data_ = deepClone(charTC) data_.artifact.substats.type = t - setData(data_) - }, [data, setData]) + setCharTC(data_) + }, [charTC, setCharTC]) const setSubstats = useCallback((setSubstats: Record) => { - const data_ = deepClone(data) + const data_ = deepClone(charTC) data_.artifact.substats.stats = setSubstats - setData(data_) - }, [data, setData]) + setCharTC(data_) + }, [charTC, setCharTC]) - const deferredData = useDeferredValue(data) + const deferredData = useDeferredValue(charTC) const overriderArtData = useMemo(() => { const stats = { ...deferredData.artifact.substats.stats } Object.values(deferredData.artifact.slots).forEach(({ statKey, rarity, level }) => @@ -159,12 +167,12 @@ export default function TabTheorycraft() { const overrideWeapon: ICachedWeapon = useMemo(() => ({ id: "", location: "", - key: data.weapon.key, - level: data.weapon.level, - ascension: data.weapon.ascension, - refinement: data.weapon.refinement, + key: charTC.weapon.key, + level: charTC.weapon.level, + ascension: charTC.weapon.ascension, + refinement: charTC.weapon.refinement, lock: false - }), [data]) + }), [charTC]) const teamData = useTeamData(characterKey, 0, overriderArtData, overrideWeapon) const { target: charUIData } = teamData?.[characterKey] ?? {} @@ -184,20 +192,20 @@ export default function TabTheorycraft() { } }, [dataContextValue, compareData, oldData]) - const optimizationTarget = data.optimization.target + const optimizationTarget = charTC.optimization.target const setOptimizationTarget = useCallback((optimizationTarget: ICharTC["optimization"]["target"]) => { - const data_ = deepClone(data) + const data_ = deepClone(charTC) data_.optimization.target = optimizationTarget - setData(data_) - }, [data, setData]) + setCharTC(data_) + }, [charTC, setCharTC]) - const distributedSubstats = data.optimization.distributedSubstats + const distributedSubstats = charTC.optimization.distributedSubstats const setDistributedSubstats = useCallback((distributedSubstats: ICharTC["optimization"]["distributedSubstats"]) => { - const data_ = deepClone(data) + const data_ = deepClone(charTC) data_.optimization.distributedSubstats = distributedSubstats - setData(data_) - }, [data, setData]) - const maxSubstats = data.optimization.maxSubstats + setCharTC(data_) + }, [charTC, setCharTC]) + const maxSubstats = charTC.optimization.maxSubstats // This solves // $\argmax_{x\in N^k, \sum x <= n, x <= x_max} f(x)$ without assumptions on the properties of $f$ @@ -216,7 +224,7 @@ export default function TabTheorycraft() { // Const fold the artifact set nodes = mapFormulas(nodes, f => { if (f.operation === "read" && f.path[0] === "dyn") { - const a = data.artifact.sets[f.path[1]]; + const a = charTC.artifact.sets[f.path[1]]; if (a) { return constant(a) } else if (allArtifactSetKeys.includes(f.path[1] as any)) { @@ -240,16 +248,16 @@ export default function TabTheorycraft() { let max = -Infinity const buffer = Object.fromEntries([...subs].map(x => [x, 0])) let maxBuffer: typeof buffer | undefined; - const bufferMain = Object.entries(data.artifact.slots).map(( + const bufferMain = Object.entries(charTC.artifact.slots).map(( [_, { statKey, rarity, level }]) => [statKey, Artifact.mainStatValue(statKey, rarity, level) / comp(statKey)] as const ).reduce>>((acc, [k, v]) => ((acc[k] = (acc[k] ?? 0) + v, acc)), {}) - const bufferSubs = objectMap(data.artifact.substats.stats, (v, k) => v / comp(k)) + const bufferSubs = objectMap(charTC.artifact.substats.stats, (v, k) => v / comp(k)) const permute = (distributedSubstats: number, [x, ...xs]: string[]) => { if (xs.length === 0) { if (distributedSubstats > maxSubstats[x]) return if (x !== "__unused__") - buffer[x] = Artifact.substatValue(x as SubstatKey, 5, data.artifact.substats.type) / comp(x) * distributedSubstats; + buffer[x] = Artifact.substatValue(x as SubstatKey, 5, charTC.artifact.substats.type) / comp(x) * distributedSubstats; const [result] = compute([{ values: bufferMain }, { values: bufferSubs }, { values: buffer }]); if (result > max) { max = result @@ -258,7 +266,7 @@ export default function TabTheorycraft() { return } for (let i = 0; i <= Math.min(maxSubstats[x], distributedSubstats); i++) { - buffer[x] = Artifact.substatValue(x as SubstatKey, 5, data.artifact.substats.type) / comp(x) * i; + buffer[x] = Artifact.substatValue(x as SubstatKey, 5, charTC.artifact.substats.type) / comp(x) * i; permute(distributedSubstats - i, xs) } } @@ -266,19 +274,19 @@ export default function TabTheorycraft() { console.log(maxBuffer) console.log(objectMap(maxBuffer!, (v, x) => allSubstatKeys.includes(x as any) ? - v / (Artifact.substatValue(x as SubstatKey, 5, data.artifact.substats.type) / comp(x)) : + v / (Artifact.substatValue(x as SubstatKey, 5, charTC.artifact.substats.type) / comp(x)) : v )) if (apply) { - const data_ = deepClone(data) - data_.artifact.substats.stats = objectMap(data.artifact.substats.stats, (v, k) => v + (maxBuffer![k] ?? 0) * comp(k)) + const data_ = deepClone(charTC) + data_.artifact.substats.stats = objectMap(charTC.artifact.substats.stats, (v, k) => v + (maxBuffer![k] ?? 0) * comp(k)) data_.optimization.distributedSubstats = 0 - setData(data_) + setCharTC(data_) } - }, [teamData, characterKey, data, distributedSubstats, maxSubstats, optimizationTarget, setData]) + }, [teamData, characterKey, charTC, distributedSubstats, maxSubstats, optimizationTarget, setCharTC]) - return + return @@ -295,19 +303,19 @@ export default function TabTheorycraft() { - + s.statKey)} + substats={charTC.artifact.substats.stats} setSubstats={setSubstats} + substatsType={charTC.artifact.substats.type} setSubstatsType={setSubstatsType} + mainStatKeys={Object.values(charTC.artifact.slots).map(s => s.statKey)} distributedSubstats={distributedSubstats} setDistributedSubstats={setDistributedSubstats} maxSubstats={maxSubstats} setMaxSubstats={(k: SubstatKey) => (v: number) => { - if (data.optimization.maxSubstats[k] === v) return - const data_ = deepClone(data) + if (charTC.optimization.maxSubstats[k] === v) return + const data_ = deepClone(charTC) data_.optimization.maxSubstats[k] = v - setData(data_) + setCharTC(data_) }} /> @@ -333,7 +341,7 @@ export default function TabTheorycraft() { : } - + } function WeaponEditorCard({ weapon, setWeapon, weaponTypeKey }: { weapon: ICachedWeapon, weaponTypeKey: WeaponTypeKey, setWeapon: (action: Partial) => void }) { @@ -531,6 +539,7 @@ function ArtifactSubCard({ substats, setSubstats, substatsType, setSubstatsType, /> + {Object.entries(substats).map(([k, v]) => v !== undefined && setMaxSubstat(v)} - color={maxSubstat > 30 ? "warning" : "success"} + color={maxSubstat > maxRolls ? "warning" : "success"} sx={{ borderRadius: 1, px: 1, my: 0, height: "100%", width: "7em" }} inputProps={{ sx: { textAlign: "right", pr: 0.5, }, min: 0, step: 1 }} /> } + +function ArtifactAllSubstatEditor() { + const [rolls, setRolls] = useState(undefined as undefined | number) + const [maxSubstat, setMaxSubstat] = useState(undefined as undefined | number) + const { setCharTC } = useContext(CharTCContext) + + const rollsDeferred = useDeferredValue(rolls) + useEffect(() => { + if (rollsDeferred === undefined) return + setCharTC(charTC => { + const stats = charTC.artifact.substats.stats + const substatsType = charTC.artifact.substats.type + charTC.artifact.substats.stats = objectMap(stats, (val, statKey) => { + const substatValue = Artifact.substatValue(statKey, 5, substatsType) + return substatValue * rollsDeferred + }) + return charTC + }) + }, [setCharTC, rollsDeferred]) + + const maxSubstatDeferred = useDeferredValue(maxSubstat) + useEffect(() => { + if (maxSubstatDeferred === undefined) return + setCharTC(charTC => { + charTC.optimization.maxSubstats = objectMap(charTC.optimization.maxSubstats, (val, statKey) => maxSubstatDeferred) + return charTC + }) + }, [setCharTC, maxSubstatDeferred]) + + const maxRolls = 30 + // 0.0001 to nudge float comparasion + const invalid = (rolls ?? 0 - 0.0001) > maxRolls + + + return + + Change all Substats + + + setRolls(v as number)} + onChangeCommitted={(e, v) => setRolls(v as number)} + /> + + All Rolls} + value={parseFloat((rolls ?? 0).toFixed(2))} + onChange={v => v !== undefined && setRolls(v)} + sx={{ borderRadius: 1, px: 1, my: 0, height: "100%", width: "7em" }} + inputProps={{ sx: { textAlign: "right", pr: 0.5, }, min: 0, step: 1 }} /> + All Max} + onChange={v => v !== undefined && setMaxSubstat(v)} + color={(maxSubstat ?? 0) > maxRolls ? "warning" : "success"} + sx={{ borderRadius: 1, px: 1, my: 0, height: "100%", width: "7em" }} + inputProps={{ sx: { textAlign: "right", pr: 0.5, }, min: 0, step: 1 }} /> + +} diff --git a/apps/frontend/src/app/Util/Util.ts b/apps/frontend/src/app/Util/Util.ts index dcc5dfa350..a2e04df632 100644 --- a/apps/frontend/src/app/Util/Util.ts +++ b/apps/frontend/src/app/Util/Util.ts @@ -127,7 +127,7 @@ export function objectMap(obj: Partial>, fn export function objectMap(obj: Partial>, fn: (value: V, key: `${K}`, index: number) => T): Partial> { return Object.fromEntries(Object.entries(obj).map( ([k, v], i) => [k, fn(v, k, i)] - )) as any + )) as Partial> } const rangeGen = function* (from: number, to: number): Iterable { From d35c15dbc886016a11d6b99ca55ea537edadea10 Mon Sep 17 00:00:00 2001 From: eeeqeee <103794572+eeeqeee@users.noreply.github.com> Date: Wed, 8 Mar 2023 22:49:37 -0500 Subject: [PATCH 11/61] fix bugs --- apps/frontend/src/app/Formula/optimization.ts | 2 +- .../Tabs/TabTheorycraft/index.tsx | 42 ++++++++++--------- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/apps/frontend/src/app/Formula/optimization.ts b/apps/frontend/src/app/Formula/optimization.ts index 4dbd5b51f7..ede7751c32 100644 --- a/apps/frontend/src/app/Formula/optimization.ts +++ b/apps/frontend/src/app/Formula/optimization.ts @@ -41,7 +41,7 @@ export function optimize(formulas: NumNode[], topLevelData: Data, shouldFold = ( * @param slotCount the number of slots in the build (usually 5) * @returns */ -export function precompute(formulas: OptNode[], initial: DynStat, binding: (readNode: ReadNode) => string, slotCount: number): (_: { values: DynStat }[]) => number[] { +export function precompute(formulas: OptNode[], initial: DynStat, binding: (readNode: ReadNode) => string, slotCount: C): (_: readonly { readonly values: Readonly }[] & { length: C }) => number[] { // res copied from the code above let body = ` "use strict"; diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx index cce9a45411..e4a2cd3603 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx @@ -33,7 +33,7 @@ import { getWeaponSheet } from "../../../../Data/Weapons"; import { DatabaseContext } from "../../../../Database/Database"; import { initCharTC } from "../../../../Database/DataManagers/CharacterTCData"; import { uiInput as input } from "../../../../Formula"; -import { computeUIData, dataObjForWeapon, mergeData } from "../../../../Formula/api"; +import { computeUIData, dataObjForWeapon, mergeData, uiDataForTeam } from "../../../../Formula/api"; import { mapFormulas } from "../../../../Formula/internal"; import { optimize, precompute } from "../../../../Formula/optimization"; import { NumNode } from "../../../../Formula/type"; @@ -41,7 +41,7 @@ import { constant, percent } from "../../../../Formula/utils"; import KeyMap, { cacheValueString } from "../../../../KeyMap"; import StatIcon from "../../../../KeyMap/StatIcon"; import useBoolState from "../../../../ReactHooks/useBoolState"; -import useTeamData from "../../../../ReactHooks/useTeamData"; +import useTeamData, { getTeamData } from "../../../../ReactHooks/useTeamData"; import { iconInlineProps } from "../../../../SVGIcons"; import { allSubstatKeys, ICachedArtifact, MainStatKey, SubstatKey } from "../../../../Types/artifact"; import { ICharTC, ICharTCArtifactSlot } from "../../../../Types/character"; @@ -52,6 +52,7 @@ import { defaultInitialWeaponKey } from "../../../../Util/WeaponUtil"; import OptimizationTargetSelector from "../TabOptimize/Components/OptimizationTargetSelector"; import { dynamicData } from "../TabOptimize/foreground"; import useCharTC from "./useCharTC"; +import useDBMeta from "../../../../ReactHooks/useDBMeta"; const WeaponSelectionModal = React.lazy(() => import('../../../../Components/Weapon/WeaponSelectionModal')) type ISet = Partial> @@ -127,14 +128,13 @@ export default function TabTheorycraft() { [database, character.equippedArtifacts, character.equippedWeapon, copyFrom], ) - const weapon: ICachedWeapon = useMemo(() => { - return { - ...charTC.weapon, - location: "", - lock: false, - id: "" - } - }, [charTC]) + const weapon: ICachedWeapon = useMemo(() => ({ + ...charTC.weapon, + location: "", + lock: false, + id: "" + }), [charTC]) + const setArtifact = useCallback((artifact: ICharTC["artifact"]) => { const data_ = deepClone(charTC) data_.artifact = artifact @@ -207,16 +207,20 @@ export default function TabTheorycraft() { }, [charTC, setCharTC]) const maxSubstats = charTC.optimization.maxSubstats + const { gender } = useDBMeta() + // This solves // $\argmax_{x\in N^k, \sum x <= n, x <= x_max} f(x)$ without assumptions on the properties of $f$ // We brute force iterate over all substats in the graph and compute the maximum // n.b. some substat combinations may not be materializable into real artifacts const optimizeSubstats = useCallback((apply: boolean) => () => { + const startTime = performance.now() if (!characterKey || !optimizationTarget) return + const teamData = getTeamData(database, characterKey, 0, overriderArtData, overrideWeapon) if (!teamData) return - let workerData = teamData[characterKey]?.target.data[0] + const workerData = uiDataForTeam(teamData.teamData, gender, characterKey)[characterKey]?.target.data![0] if (!workerData) return - workerData = { ...workerData, ...mergeData([workerData, dynamicData]) } // Mark art fields as dynamic + Object.assign(workerData, mergeData([workerData, dynamicData])) // Mark art fields as dynamic const unoptimizedOptimizationTargetNode = objPathValue(workerData.display ?? {}, optimizationTarget) as NumNode | undefined if (!unoptimizedOptimizationTargetNode) return const unoptimizedNodes = [unoptimizedOptimizationTargetNode] @@ -239,7 +243,7 @@ export default function TabTheorycraft() { const compute = precompute(nodes, {}, f => { subs.add(f.path[1]) return f.path[1] - }, 3) + }, 2) const realSubs = [...subs].filter(x => allSubstatKeys.includes(x as any)) if (realSubs.reduce((p, x) => p + maxSubstats[x], 0) < distributedSubstats) realSubs.push("__unused__") @@ -248,9 +252,6 @@ export default function TabTheorycraft() { let max = -Infinity const buffer = Object.fromEntries([...subs].map(x => [x, 0])) let maxBuffer: typeof buffer | undefined; - const bufferMain = Object.entries(charTC.artifact.slots).map(( - [_, { statKey, rarity, level }]) => [statKey, Artifact.mainStatValue(statKey, rarity, level) / comp(statKey)] as const - ).reduce>>((acc, [k, v]) => ((acc[k] = (acc[k] ?? 0) + v, acc)), {}) const bufferSubs = objectMap(charTC.artifact.substats.stats, (v, k) => v / comp(k)) const permute = (distributedSubstats: number, [x, ...xs]: string[]) => { if (xs.length === 0) { @@ -258,7 +259,7 @@ export default function TabTheorycraft() { return if (x !== "__unused__") buffer[x] = Artifact.substatValue(x as SubstatKey, 5, charTC.artifact.substats.type) / comp(x) * distributedSubstats; - const [result] = compute([{ values: bufferMain }, { values: bufferSubs }, { values: buffer }]); + const [result] = compute([{ values: bufferSubs }, { values: buffer }] as const); if (result > max) { max = result maxBuffer = structuredClone(buffer) @@ -271,6 +272,8 @@ export default function TabTheorycraft() { } } permute(distributedSubstats, realSubs) + if (process.env.NODE_ENV === "development") + console.log(`Took ${performance.now() - startTime} ms`) console.log(maxBuffer) console.log(objectMap(maxBuffer!, (v, x) => allSubstatKeys.includes(x as any) ? @@ -284,7 +287,7 @@ export default function TabTheorycraft() { data_.optimization.distributedSubstats = 0 setCharTC(data_) } - }, [teamData, characterKey, charTC, distributedSubstats, maxSubstats, optimizationTarget, setCharTC]) + }, [charTC, characterKey, database, distributedSubstats, gender, maxSubstats, optimizationTarget, overrideWeapon, overriderArtData, setCharTC]) return @@ -370,7 +373,7 @@ function WeaponEditorCard({ weapon, setWeapon, weaponTypeKey }: { weapon: ICache {weaponUIData && - {[input.weapon.main, input.weapon.sub, input.weapon.sub2].map((node, i) => { + {[input.weapon.main, input.weapon.sub, input.weapon.sub2].map((node) => { const n = weaponUIData.get(node) if (n.isEmpty || !n.value) return null return @@ -560,7 +563,6 @@ function ArtifactSubstatEditor({ statKey, value, setValue, substatsType, mainSta mainStatKeys: MainStatKey[], maxSubstat: number, setMaxSubstat: (v: number) => void, }) { - const { t } = useTranslation("page_character") const substatValue = Artifact.substatValue(statKey, 5, substatsType) const [rolls, setRolls] = useState(() => value / substatValue) useEffect(() => setRolls(value / substatValue), [value, substatValue]) From 70e81241855f37ba441e1093b3edd13e186ac954 Mon Sep 17 00:00:00 2001 From: eeeqeee <103794572+eeeqeee@users.noreply.github.com> Date: Sat, 11 Mar 2023 02:28:41 -0500 Subject: [PATCH 12/61] fix merge --- apps/frontend/src/app/Database/DataManager.ts | 37 +- .../Database/DataManagers/CharacterTCData.ts | 280 ++- .../src/app/Formula/optimization.test.ts | 285 ++- apps/frontend/src/app/Formula/optimization.ts | 897 +++++--- .../Tabs/TabTheorycraft/index.tsx | 2025 +++++++++++------ apps/frontend/src/app/Types/character.d.ts | 168 +- apps/frontend/src/app/Util/Util.ts | 381 ++-- 7 files changed, 2528 insertions(+), 1545 deletions(-) diff --git a/apps/frontend/src/app/Database/DataManager.ts b/apps/frontend/src/app/Database/DataManager.ts index dd6c1ce67c..033af39cf4 100644 --- a/apps/frontend/src/app/Database/DataManager.ts +++ b/apps/frontend/src/app/Database/DataManager.ts @@ -1,7 +1,12 @@ -import { deepClone, deepFreeze } from "../Util/Util" -import { ArtCharDatabase } from "./Database" -import { IGO, IGOOD, ImportResult } from "./exim" -export class DataManager { +import { deepClone, deepFreeze } from '../Util/Util' +import type { ArtCharDatabase } from './Database' +import type { IGO, IGOOD, ImportResult } from './exim' +export class DataManager< + CacheKey extends string, + GOKey extends string, + CacheValue extends StorageValue, + StorageValue +> { database: ArtCharDatabase /** * The "list name" when an DataManager is exported to GO data @@ -45,13 +50,25 @@ export class DataManager | ((v: StorageValue) => Partial), notify = true): boolean { + get keys() { + return Object.keys(this.data) + } + get values() { + return Object.values(this.data) + } + get(key: CacheKey | '' | undefined): CacheValue | undefined { + return key ? this.data[key] : undefined + } + getStorage(key: CacheKey): StorageValue { + return this.database.storage.get(this.toStorageKey(key)) + } + set( + key: CacheKey, + value: Partial | ((v: StorageValue) => Partial), + notify = true + ): boolean { const old = this.getStorage(key) - if (typeof value === "function") value = value(deepClone(old)) + if (typeof value === 'function') value = value(deepClone(old)) const validated = this.validate({ ...(old ?? {}), ...value }, key) if (!validated) { this.trigger(key, 'invalid', value) diff --git a/apps/frontend/src/app/Database/DataManagers/CharacterTCData.ts b/apps/frontend/src/app/Database/DataManagers/CharacterTCData.ts index b2299ade35..47db37e844 100644 --- a/apps/frontend/src/app/Database/DataManagers/CharacterTCData.ts +++ b/apps/frontend/src/app/Database/DataManagers/CharacterTCData.ts @@ -1,116 +1,164 @@ -import { allArtifactSlotKeys, allWeaponKeys, ArtifactSlotKey, CharacterKey, WeaponKey } from "@genshin-optimizer/consts"; -import { validateLevelAsc } from "../../Data/LevelData"; -import { allSubstatKeys, MainStatKey } from "../../Types/artifact"; -import { ICharTC } from "../../Types/character"; -import { ArtifactRarity, substatType } from "../../Types/consts"; -import { objectKeyMap } from "../../Util/Util"; -import { ArtCharDatabase } from "../Database"; -import { DataManager } from "../DataManager"; - -export class CharacterTCDataManager extends DataManager{ - constructor(database: ArtCharDatabase) { - super(database, "charTCs") - for (const key of this.database.storage.keys) { - if (key.startsWith("charTC_") && !this.set(key.split("charTC_")[1] as CharacterKey, {})) - database.storage.remove(key) - } - } - validate(obj: unknown): ICharTC | undefined { - if (typeof obj !== "object") return - const { weapon, artifact, optimization } = obj as ICharTC - const _weapon = validateCharTCWeapon(weapon) - if (!_weapon) return - - const _artifact = validateCharTCArtifact(artifact) - if (!_artifact) return - const _optimization = validateCharTcOptimization(optimization) - if (!_optimization) return - return { artifact: _artifact, weapon: _weapon, optimization: _optimization } - } - toStorageKey(key: CharacterKey): string { - return `charTC_${key}` - } - remove(key: CharacterKey) { - const char = this.get(key) - if (!char) return - super.remove(key) - } - getWithInit(key: CharacterKey, weaponKey: WeaponKey): ICharTC { - const charTc = key ? this.data[key] : undefined - return charTc ?? initCharTC(weaponKey) - } -} - -export function initCharTC(weaponKey: WeaponKey): ICharTC { - return { - weapon: { - key: weaponKey, - level: 1, - ascension: 0, - refinement: 1, - }, - artifact: { - slots: initCharTCArtifactSlots(), - substats: { - type: "max", - stats: objectKeyMap(allSubstatKeys, () => 0) - }, - sets: {} - }, - optimization: { - target: undefined, - distributedSubstats: 45, - maxSubstats: initCharTcOptimizationMaxSubstats() - } - } -} -function initCharTCArtifactSlots() { - return objectKeyMap(allArtifactSlotKeys, s => ({ - level: 20, - rarity: 5 as ArtifactRarity, - statKey: (s === "flower" ? "hp" : s === "plume" ? "atk" : "atk_") as MainStatKey, - })) -} - -function validateCharTCWeapon(weapon: unknown): ICharTC["weapon"] | undefined { - if (typeof weapon !== "object") return - const { key, } = weapon as ICharTC["weapon"] - let { level, ascension, refinement } = weapon as ICharTC["weapon"] - if (!allWeaponKeys.includes(key)) return - if (typeof refinement !== "number" || refinement < 1 || refinement > 5) refinement = 1 - const { level: _level, ascension: _ascension } = validateLevelAsc(level, ascension); - [level, ascension] = [_level, _ascension] - return { key, level, ascension, refinement } -} -function validateCharTCArtifact(artifact: unknown): ICharTC["artifact"] | undefined { - if (typeof artifact !== "object") return - let { slots, substats: { type, stats }, sets } = artifact as ICharTC["artifact"] - const _slots = validateCharTCArtifactSlots(slots) - if (!_slots) return - slots = _slots - if (!substatType.includes(type)) type = "max" - if (typeof stats !== "object") stats = objectKeyMap(allSubstatKeys, () => 0) - stats = objectKeyMap(allSubstatKeys, k => typeof stats[k] === "number" ? stats[k] : 0) - - if (typeof sets !== "object") sets = {} - // TODO: validate sets - - return { slots, substats: { type, stats }, sets } -} -function validateCharTCArtifactSlots(slots: unknown): ICharTC["artifact"]["slots"] | undefined { - if (typeof slots !== "object") return initCharTCArtifactSlots() - if (Object.keys(slots as ICharTC["artifact"]["slots"]).length !== allArtifactSlotKeys.length || Object.keys(slots as ICharTC["artifact"]["slots"]).some(s => !allArtifactSlotKeys.includes(s as ArtifactSlotKey))) return initCharTCArtifactSlots() - return slots as ICharTC["artifact"]["slots"] -} -function validateCharTcOptimization(optimization: unknown): ICharTC["optimization"] | undefined { - if (typeof optimization !== "object") return - let { target, distributedSubstats, maxSubstats } = optimization as ICharTC["optimization"] - if (!Array.isArray(target)) target = undefined - if (typeof distributedSubstats !== "number") distributedSubstats = 20 - if (typeof maxSubstats !== "object") maxSubstats = initCharTcOptimizationMaxSubstats() - maxSubstats = objectKeyMap([...allSubstatKeys], k => typeof maxSubstats[k] === "number" ? maxSubstats[k] : 0) - return { target, distributedSubstats, maxSubstats } -} -function initCharTcOptimizationMaxSubstats(): ICharTC["optimization"]["maxSubstats"] { - return objectKeyMap(allSubstatKeys, () => 30) -} +import type { + ArtifactSlotKey, + CharacterKey, + WeaponKey} from '@genshin-optimizer/consts'; +import { + allArtifactSlotKeys, + allWeaponKeys +} from '@genshin-optimizer/consts' +import { validateLevelAsc } from '../../Data/LevelData' +import type { MainStatKey } from '../../Types/artifact'; +import { allSubstatKeys } from '../../Types/artifact' +import type { ICharTC } from '../../Types/character' +import type { ArtifactRarity} from '../../Types/consts'; +import { substatType } from '../../Types/consts' +import { objectKeyMap } from '../../Util/Util' +import type { ArtCharDatabase } from '../Database' +import { DataManager } from '../DataManager' + +export class CharacterTCDataManager extends DataManager< + CharacterKey, + 'charTCs', + ICharTC, + ICharTC +> { + constructor(database: ArtCharDatabase) { + super(database, 'charTCs') + for (const key of this.database.storage.keys) { + if ( + key.startsWith('charTC_') && + !this.set(key.split('charTC_')[1] as CharacterKey, {}) + ) + database.storage.remove(key) + } + } + validate(obj: unknown): ICharTC | undefined { + if (typeof obj !== 'object') return + const { weapon, artifact, optimization } = obj as ICharTC + const _weapon = validateCharTCWeapon(weapon) + if (!_weapon) return + + const _artifact = validateCharTCArtifact(artifact) + if (!_artifact) return + const _optimization = validateCharTcOptimization(optimization) + if (!_optimization) return + return { artifact: _artifact, weapon: _weapon, optimization: _optimization } + } + toStorageKey(key: CharacterKey): string { + return `charTC_${key}` + } + remove(key: CharacterKey) { + const char = this.get(key) + if (!char) return + super.remove(key) + } + getWithInit(key: CharacterKey, weaponKey: WeaponKey): ICharTC { + const charTc = key ? this.data[key] : undefined + return charTc ?? initCharTC(weaponKey) + } +} + +export function initCharTC(weaponKey: WeaponKey): ICharTC { + return { + weapon: { + key: weaponKey, + level: 1, + ascension: 0, + refinement: 1, + }, + artifact: { + slots: initCharTCArtifactSlots(), + substats: { + type: 'max', + stats: objectKeyMap(allSubstatKeys, () => 0), + }, + sets: {}, + }, + optimization: { + target: undefined, + distributedSubstats: 45, + maxSubstats: initCharTcOptimizationMaxSubstats(), + }, + } +} +function initCharTCArtifactSlots() { + return objectKeyMap(allArtifactSlotKeys, (s) => ({ + level: 20, + rarity: 5 as ArtifactRarity, + statKey: (s === 'flower' + ? 'hp' + : s === 'plume' + ? 'atk' + : 'atk_') as MainStatKey, + })) +} + +function validateCharTCWeapon(weapon: unknown): ICharTC['weapon'] | undefined { + if (typeof weapon !== 'object') return + const { key } = weapon as ICharTC['weapon'] + let { level, ascension, refinement } = weapon as ICharTC['weapon'] + if (!allWeaponKeys.includes(key)) return + if (typeof refinement !== 'number' || refinement < 1 || refinement > 5) + refinement = 1 + const { level: _level, ascension: _ascension } = validateLevelAsc( + level, + ascension + ) + ;[level, ascension] = [_level, _ascension] + return { key, level, ascension, refinement } +} +function validateCharTCArtifact( + artifact: unknown +): ICharTC['artifact'] | undefined { + if (typeof artifact !== 'object') return + let { + slots, + substats: { type, stats }, + sets, + } = artifact as ICharTC['artifact'] + const _slots = validateCharTCArtifactSlots(slots) + if (!_slots) return + slots = _slots + if (!substatType.includes(type)) type = 'max' + if (typeof stats !== 'object') stats = objectKeyMap(allSubstatKeys, () => 0) + stats = objectKeyMap(allSubstatKeys, (k) => + typeof stats[k] === 'number' ? stats[k] : 0 + ) + + if (typeof sets !== 'object') sets = {} + // TODO: validate sets + + return { slots, substats: { type, stats }, sets } +} +function validateCharTCArtifactSlots( + slots: unknown +): ICharTC['artifact']['slots'] | undefined { + if (typeof slots !== 'object') return initCharTCArtifactSlots() + if ( + Object.keys(slots as ICharTC['artifact']['slots']).length !== + allArtifactSlotKeys.length || + Object.keys(slots as ICharTC['artifact']['slots']).some( + (s) => !allArtifactSlotKeys.includes(s as ArtifactSlotKey) + ) + ) + return initCharTCArtifactSlots() + return slots as ICharTC['artifact']['slots'] +} +function validateCharTcOptimization( + optimization: unknown +): ICharTC['optimization'] | undefined { + if (typeof optimization !== 'object') return + let { target, distributedSubstats, maxSubstats } = + optimization as ICharTC['optimization'] + if (!Array.isArray(target)) target = undefined + if (typeof distributedSubstats !== 'number') distributedSubstats = 20 + if (typeof maxSubstats !== 'object') + maxSubstats = initCharTcOptimizationMaxSubstats() + maxSubstats = objectKeyMap([...allSubstatKeys], (k) => + typeof maxSubstats[k] === 'number' ? maxSubstats[k] : 0 + ) + return { target, distributedSubstats, maxSubstats } +} +function initCharTcOptimizationMaxSubstats(): ICharTC['optimization']['maxSubstats'] { + return objectKeyMap(allSubstatKeys, () => 30) +} diff --git a/apps/frontend/src/app/Formula/optimization.test.ts b/apps/frontend/src/app/Formula/optimization.test.ts index 677c68b3b9..2e18455784 100644 --- a/apps/frontend/src/app/Formula/optimization.test.ts +++ b/apps/frontend/src/app/Formula/optimization.test.ts @@ -1,102 +1,183 @@ -import { forEachNodes } from "./internal" -import { OptNode, precompute, testing } from "./optimization" -import { AnyNode, ConstantNode, Data, Info } from "./type" -import { constant, customRead, data, infoMut, max, min, prod, read, resetData, setReadNodeKeys, sum } from "./utils" - -const { constantFold } = testing -const deduplicate = testing.deduplicate as any as (nodes: AnyNode[]) => AnyNode -const flatten = testing.flatten as any as (nodes: AnyNode[]) => AnyNode - -const inputs = setReadNodeKeys(Object.fromEntries([...Array(6).keys()].map(i => [i, read("add")]))) - -describe("optimization", () => { - describe("flatten", () => { - test("same formulas", () => { - const r1 = inputs[0], r2 = inputs[1], r3 = inputs[2] - const r4 = inputs[3], r5 = inputs[4], r6 = inputs[5] - - const f1 = sum(r1, r2, sum(r3, r4), r5, r6) - expect(flatten([f1])).toEqual([sum(r1, r2, r3, r4, r5, r6)]) - }) - test("nested formulas", () => { - const r1 = inputs[0], r2 = inputs[1], r3 = inputs[2] - const r4 = inputs[3], r5 = inputs[4], r6 = inputs[5] - - const f = sum(r1, sum(r2, sum(r3, sum(r4, sum(r5, r6))))) - expect(flatten([f])).toEqual([sum(r1, r2, r3, r4, r5, r6)]) - }) - }) - test("deduplicate common terms", () => { - const r1 = inputs[0], r2 = inputs[1], r3 = inputs[2] - const r4 = inputs[3], r5 = inputs[4], r6 = inputs[5] - - const f1 = sum(r1, r2, r3, r4, r5, r6), f2 = sum(r3, r4) - // TODO: Factoring process may reorder the term ( r3 + r4 ). May need to update the checking - expect(deduplicate([f1, f2])).toEqual([sum(r1, r2, r5, r6, sum(r3, r4)), sum(r3, r4)]) - }) - test("constant folding", () => { - const r1 = inputs[0], r2 = inputs[1], r3 = inputs[2] - - expect(constantFold([sum(1, -1, r1, r2, r3)], {})).toEqual([sum(r1, r2, r3)]) - expect(constantFold([prod(1, r1, r2, r3)], {})).toEqual([prod(r1, r2, r3)]) - expect(constantFold([min(Infinity, r1, r2, r3)], {})).toEqual([min(r1, r2, r3)]) - expect(constantFold([max(-Infinity, r1, r2, r3)], {})).toEqual([max(r1, r2, r3)]) - - // Degenerate case - expect(constantFold([prod(0, r1, r2, r3)], {})).toEqual([constant(0)]) - - // Remove wrapper for single-value formula - expect(constantFold([sum(1, -1, r1)], {})).toEqual([r1]) - - { - // Removing Info - const node = sum(1, -1, infoMut(sum(r1), {} as any), r2, r3) - let info: Info | undefined = undefined - forEachNodes([node], _ => _, f => info ||= f.info) - expect(info).toBeTruthy() - - info = undefined - forEachNodes(constantFold([node], {}), _ => _, f => info ||= f.info) - expect(info).toBeFalsy() - } - }) - test("data unpacking", () => { - const r1 = customRead(["aa"]) - r1.accu = "add" - const data0 = { aa: constant(66) } as any as Data - const data1 = { aa: constant(77) } as any as Data - const t1 = data(r1, data1) - - expect(constantFold([resetData(t1, {}), t1], data0).map(x => (x as ConstantNode).value)).toEqual([77, 66 + 77]) - }) - describe("precomputing", () => { - test("Base", () => { - const r1 = inputs[0], r2 = inputs[1], r3 = inputs[2] - const output1 = sum(1, r1, r2), output2 = prod(r2, r3), output3 = sum(output1, output2) - - const compute = precompute([output1] as OptNode[], {}, x => x.path[0], 1) - expect([...compute([{ values: { 0: 32, 1: 77 } }]).slice(0, 1)]).toEqual([1 + 32 + 77]) - }) - test("Output is read node", () => { - const r1 = inputs[0], r2 = inputs[1], r3 = inputs[2] - const output1 = sum(1, r1, r2), output2 = prod(r2, r3), output3 = sum(output1, output2) - - const compute = precompute([r1], {}, x => x.path[0], 1) - expect([...compute([{ values: { 0: 32 } }]).slice(0, 1)]).toEqual([32]) - }) - test("Output is constant node", () => { - const r1 = inputs[0], r2 = inputs[1], r3 = inputs[2] - const output1 = sum(1, r1, r2), output2 = prod(r2, r3), output3 = sum(output1, output2) - - const compute = precompute([constant(35)], {}, x => x.path[0], 0) - expect([...compute([]).slice(0, 1)]).toEqual([35]) - }) - test("Output is duplicated", () => { - const r1 = inputs[0], r2 = inputs[1], r3 = inputs[2] - const output1 = sum(1, r1, r2), output2 = prod(r2, r3), output3 = sum(output1, output2) - - const compute = precompute([output3, output3] as OptNode[], {}, x => x.path[0], 1) - expect([...compute([{ values: { 0: 2, 1: 44, 2: 7 } }]).slice(0, 2)]).toEqual([(1 + 2 + 44) + (44 * 7), (1 + 2 + 44) + (44 * 7)]) - }) - }) -}) +import { forEachNodes } from './internal' +import type { OptNode} from './optimization'; +import { precompute, testing } from './optimization' +import type { AnyNode, ConstantNode, Data, Info } from './type' +import { + constant, + customRead, + data, + infoMut, + max, + min, + prod, + read, + resetData, + setReadNodeKeys, + sum, +} from './utils' + +const { constantFold } = testing +const deduplicate = testing.deduplicate as any as (nodes: AnyNode[]) => AnyNode +const flatten = testing.flatten as any as (nodes: AnyNode[]) => AnyNode + +const inputs = setReadNodeKeys( + Object.fromEntries([...Array(6).keys()].map((i) => [i, read('add')])) +) + +describe('optimization', () => { + describe('flatten', () => { + test('same formulas', () => { + const r1 = inputs[0], + r2 = inputs[1], + r3 = inputs[2] + const r4 = inputs[3], + r5 = inputs[4], + r6 = inputs[5] + + const f1 = sum(r1, r2, sum(r3, r4), r5, r6) + expect(flatten([f1])).toEqual([sum(r1, r2, r3, r4, r5, r6)]) + }) + test('nested formulas', () => { + const r1 = inputs[0], + r2 = inputs[1], + r3 = inputs[2] + const r4 = inputs[3], + r5 = inputs[4], + r6 = inputs[5] + + const f = sum(r1, sum(r2, sum(r3, sum(r4, sum(r5, r6))))) + expect(flatten([f])).toEqual([sum(r1, r2, r3, r4, r5, r6)]) + }) + }) + test('deduplicate common terms', () => { + const r1 = inputs[0], + r2 = inputs[1], + r3 = inputs[2] + const r4 = inputs[3], + r5 = inputs[4], + r6 = inputs[5] + + const f1 = sum(r1, r2, r3, r4, r5, r6), + f2 = sum(r3, r4) + // TODO: Factoring process may reorder the term ( r3 + r4 ). May need to update the checking + expect(deduplicate([f1, f2])).toEqual([ + sum(r1, r2, r5, r6, sum(r3, r4)), + sum(r3, r4), + ]) + }) + test('constant folding', () => { + const r1 = inputs[0], + r2 = inputs[1], + r3 = inputs[2] + + expect(constantFold([sum(1, -1, r1, r2, r3)], {})).toEqual([ + sum(r1, r2, r3), + ]) + expect(constantFold([prod(1, r1, r2, r3)], {})).toEqual([prod(r1, r2, r3)]) + expect(constantFold([min(Infinity, r1, r2, r3)], {})).toEqual([ + min(r1, r2, r3), + ]) + expect(constantFold([max(-Infinity, r1, r2, r3)], {})).toEqual([ + max(r1, r2, r3), + ]) + + // Degenerate case + expect(constantFold([prod(0, r1, r2, r3)], {})).toEqual([constant(0)]) + + // Remove wrapper for single-value formula + expect(constantFold([sum(1, -1, r1)], {})).toEqual([r1]) + + { + // Removing Info + const node = sum(1, -1, infoMut(sum(r1), {} as any), r2, r3) + let info: Info | undefined = undefined + forEachNodes( + [node], + (_) => _, + (f) => (info ||= f.info) + ) + expect(info).toBeTruthy() + + info = undefined + forEachNodes( + constantFold([node], {}), + (_) => _, + (f) => (info ||= f.info) + ) + expect(info).toBeFalsy() + } + }) + test('data unpacking', () => { + const r1 = customRead(['aa']) + r1.accu = 'add' + const data0 = { aa: constant(66) } as any as Data + const data1 = { aa: constant(77) } as any as Data + const t1 = data(r1, data1) + + expect( + constantFold([resetData(t1, {}), t1], data0).map( + (x) => (x as ConstantNode).value + ) + ).toEqual([77, 66 + 77]) + }) + describe('precomputing', () => { + test('Base', () => { + const r1 = inputs[0], + r2 = inputs[1], + r3 = inputs[2] + const output1 = sum(1, r1, r2), + output2 = prod(r2, r3), + output3 = sum(output1, output2) + + const compute = precompute( + [output1] as OptNode[], + {}, + (x) => x.path[0], + 1 + ) + expect([...compute([{ values: { 0: 32, 1: 77 } }] as const).slice(0, 1)]).toEqual([ + 1 + 32 + 77, + ]) + }) + test('Output is read node', () => { + const r1 = inputs[0], + r2 = inputs[1], + r3 = inputs[2] + const output1 = sum(1, r1, r2), + output2 = prod(r2, r3), + output3 = sum(output1, output2) + + const compute = precompute([r1], {}, (x) => x.path[0], 1) + expect([...compute([{ values: { 0: 32 } }] as const).slice(0, 1)]).toEqual([32]) + }) + test('Output is constant node', () => { + const r1 = inputs[0], + r2 = inputs[1], + r3 = inputs[2] + const output1 = sum(1, r1, r2), + output2 = prod(r2, r3), + output3 = sum(output1, output2) + + const compute = precompute([constant(35)], {}, (x) => x.path[0], 0) + expect([...compute([] as const).slice(0, 1)]).toEqual([35]) + }) + test('Output is duplicated', () => { + const r1 = inputs[0], + r2 = inputs[1], + r3 = inputs[2] + const output1 = sum(1, r1, r2), + output2 = prod(r2, r3), + output3 = sum(output1, output2) + + const compute = precompute( + [output3, output3] as OptNode[], + {}, + (x) => x.path[0], + 1 + ) + expect([ + ...compute([{ values: { 0: 2, 1: 44, 2: 7 } }] as const).slice(0, 2), + ]).toEqual([1 + 2 + 44 + 44 * 7, 1 + 2 + 44 + 44 * 7]) + }) + }) +}) diff --git a/apps/frontend/src/app/Formula/optimization.ts b/apps/frontend/src/app/Formula/optimization.ts index ede7751c32..5d458f1121 100644 --- a/apps/frontend/src/app/Formula/optimization.ts +++ b/apps/frontend/src/app/Formula/optimization.ts @@ -1,376 +1,521 @@ -import type { DynStat } from "../Solver/common" -import { assertUnreachable, objPathValue } from "../Util/Util" -import { customMapFormula, forEachNodes, mapFormulas } from "./internal" -import { AnyNode, CommutativeMonoidOperation, ComputeNode, ConstantNode, Data, NumNode, Operation, ReadNode, StrNode, StrPrioNode, ThresholdNode } from "./type" -import { constant } from "./utils" - -export type OptNode = ComputeNode | ThresholdNode | - ReadNode | ConstantNode - -const allCommutativeMonoidOperations: StrictDict number> = { - min: (x: number[]): number => Math.min(...x), - max: (x: number[]): number => Math.max(...x), - add: (x: number[]): number => x.reduce((a, b) => a + b, 0), - mul: (x: number[]): number => x.reduce((a, b) => a * b, 1), -} -export const allOperations: StrictDict number> = { - ...allCommutativeMonoidOperations, - res: ([res]: number[]): number => { - if (res < 0) return 1 - res / 2 - else if (res >= 0.75) return 1 / (res * 4 + 1) - return 1 - res - }, - sum_frac: (x: number[]): number => x[0] / x.reduce((a, b) => a + b), - threshold: ([value, threshold, pass, fail]: number[]): number => value >= threshold ? pass : fail, -} - -const commutativeMonoidOperationSet = new Set(Object.keys(allCommutativeMonoidOperations) as (NumNode["operation"])[]) - -export function optimize(formulas: NumNode[], topLevelData: Data, shouldFold = (_formula: ReadNode) => false): OptNode[] { - let opts = constantFold(formulas, topLevelData, shouldFold) - opts = flatten(opts) - return deduplicate(opts) -} - -/** - * Compile an array of `formulas` into a JS `Function` - * - * @param formulas - * @param initial base stats for the formula - * @param binding - * @param slotCount the number of slots in the build (usually 5) - * @returns - */ -export function precompute(formulas: OptNode[], initial: DynStat, binding: (readNode: ReadNode) => string, slotCount: C): (_: readonly { readonly values: Readonly }[] & { length: C }) => number[] { - // res copied from the code above - let body = ` -"use strict"; -function res(res) { - if (res < 0) return 1 - res / 2 - else if (res >= 0.75) return 1 / (res * 4 + 1) - return 1 - res -} -const x0=0`; // making sure `const` has at least one entry - - let i = 1; - const names = new Map() - forEachNodes(formulas, _ => {/* */ }, f => { - const { operation, operands } = f, name = `x${i++}`, operandNames = operands.map((x: OptNode) => names.get(x)!) - names.set(f, name) - switch (operation) { - case "read": { - const key = binding(f) - let arr = slotCount ? new Array(slotCount).fill(null).map((_, i) => `(b[${i}].values["${key}"] ?? 0)`) : ['0'] - if (initial[key] && initial[key] !== 0) { - arr = [initial[key].toString(), ...arr] - } - body += `,${name}=${arr.join('+')}\n` - break - } - case "const": names.set(f, `(${f.value})`); break - case "add": case "mul": body += `,${name}=${operandNames.join(operation === "add" ? "+" : "*")}\n`; break - case "min": case "max": body += `,${name}=Math.${operation}(${operandNames})\n`; break - case "threshold": { - const [value, threshold, pass, fail] = operandNames - body += `,${name}=(${value}>=${threshold})?${pass}:${fail}\n` - break - } - case "res": body += `,${name}=res(${operandNames[0]})\n`; break - case "sum_frac": body += `,${name}=${operandNames[0]}/(${operandNames[0]}+${operandNames[1]})\n`; break - - default: assertUnreachable(operation) - } - }) - body += `;\nreturn [${formulas.map(f => names.get(f)!)}]` - return new (Function as any)(`b`, body) -} - -function flatten(formulas: OptNode[]): OptNode[] { - return mapFormulas(formulas, f => f, _formula => { - let result = _formula - if (commutativeMonoidOperationSet.has(_formula.operation as Operation)) { - const formula = _formula as ComputeNode - const { operation } = formula - - let flattened = false - const operands = formula.operands.flatMap(dep => - (dep.operation === operation) ? (flattened = true, dep.operands) : [dep]) - result = flattened ? { ...formula, operands } : formula - } - - return result - }) -} -function deduplicate(formulas: OptNode[]): OptNode[] { - function elementCounts(array: readonly T[]): Map { - const result = new Map() - for (const value of array) result.set(value, (result.get(value) ?? 0) + 1) - return result - } - function arrayFromCounts(counts: Map): T[] { - return [...counts].flatMap(([dep, count]) => Array(count).fill(dep)) - } - - const wrap = { - common: { - counts: new Map(), - formulas: new Set(), - operation: "add" as Operation - } - } - - for (; ;) { - let next: typeof wrap.common | undefined - - const factored: ComputeNode = { operation: wrap.common.operation, operands: arrayFromCounts(wrap.common.counts) } - - const candidatesByOperation = new Map, Map][]>() - for (const operation of Object.keys(allCommutativeMonoidOperations)) - candidatesByOperation.set(operation, []) - - formulas = mapFormulas(formulas, _formula => { - if (wrap.common.formulas.has(_formula)) { - const formula = _formula as ComputeNode - const remainingCounts = new Map(wrap.common.counts) - const operands = formula.operands.filter(dep => { - const count = remainingCounts.get(dep) - if (count) { - remainingCounts.set(dep, count - 1) - return false - } - return true - }) - - if (!operands.length) - return factored - operands.push(factored) - return { ...formula, operands } - } - return _formula - }, _formula => { - if (!commutativeMonoidOperationSet.has(_formula.operation as any)) return _formula - const formula = _formula as ComputeNode - - if (next) { - if (next.operation === formula.operation) { - const currentCounts = elementCounts(formula.operands), commonCounts = new Map() - const nextCounts = next.counts - let total = 0 - - for (const [dependency, currentCount] of currentCounts.entries()) { - const commonCount = Math.min(currentCount, nextCounts.get(dependency) ?? 0) - if (commonCount) { - commonCounts.set(dependency, commonCount) - total += commonCount - } else commonCounts.delete(dependency) - } - if (total > 1) { - next.counts = commonCounts - next.formulas.add(formula) - } - } - } else { - const candidates = candidatesByOperation.get(formula.operation)! - const counts = elementCounts(formula.operands) - - for (const [candidate, candidateCounts] of candidates) { - let total = 0 - - const commonCounts = new Map() - for (const [dependency, candidateCount] of candidateCounts.entries()) { - const count = Math.min(candidateCount, counts.get(dependency) ?? 0) - if (count) { - commonCounts.set(dependency, count) - total += count - } - } - if (total > 1) { - next = { - counts: commonCounts, - formulas: new Set([formula, candidate]), - operation: formula.operation - } - candidatesByOperation.clear() - break - } - } - if (!next) candidates.push([formula, counts]) - } - - return formula - }) - - if (next) wrap.common = next - else break - } - - return formulas -} - -/** - * Replace nodes with known values with appropriate constants, - * avoiding removal of any nodes that pass `isFixed` predicate - */ -export function constantFold(formulas: NumNode[], topLevelData: Data, shouldFold = (_formula: ReadNode) => false): OptNode[] { - type Context = { data: Data[], processed: Map } - const origin: Context = { data: [], processed: new Map() } - const nextContextMap = new Map([[origin, new Map()]]) - - const context = { data: [topLevelData], processed: new Map() } - nextContextMap.set(context, new Map()) - nextContextMap.get(origin)!.set(topLevelData, context) - return customMapFormula(formulas, context, (formula, context, map) => { - const { operation } = formula, fold = (x: NumNode, c: typeof context) => map(x, c) as OptNode - const foldStr = (x: StrNode, c: typeof context) => map(x, c) as StrNode - let result: OptNode | StrNode - switch (operation) { - case "const": result = formula; break - case "add": case "mul": case "max": case "min": - const f = allOperations[operation] - const numericOperands: number[] = [] - const formulaOperands: OptNode[] = formula.operands.filter(formula => { - const folded = fold(formula, context) - return (folded.operation === "const") - ? (numericOperands.push(folded.value), false) - : true - }).map(x => fold(x, context)) - const numericValue = f(numericOperands) - - // Fold degenerate cases. This may incorrectly compute NaN - // results, which shouldn't appear under expected usage. - // - zero - // - 0 * ... = 0 - // - infinity - // - max(infinity, ...) = infinity - // - infinity + ... = infinity - // - (-infinity) - // - min(-infinity, ...) - infinity - // - (-infinity) + ... = -infinity - // - NaN - // - operation(NaN, ...) = NaN - if (!isFinite(numericValue)) { - if ((operation !== "mul") && - (operation !== "max" || numericValue > 0) && - (operation !== "min" || numericValue < 0)) { - result = constant(numericValue) - break - } - } else if (operation === "mul" && numericValue === 0) { - result = constant(numericValue) - break - } - - if (numericValue !== f([])) // Skip vacuous values - formulaOperands.push(constant(numericValue)) - if (formulaOperands.length <= 1) result = formulaOperands[0] ?? constant(f([])) - else result = { operation, operands: formulaOperands } - break - case "res": case "sum_frac": { - const operands = formula.operands.map(x => fold(x, context)) - const f = allOperations[operation] - if (operands.every(x => x.operation === "const")) - result = constant(f(operands.map(x => (x as ConstantNode).value))) - else result = { ...formula, operands } - break - } - case "lookup": { - const index = foldStr(formula.operands[0], context) - if (index.operation === "const") { - const selected = formula.table[index.value!] ?? formula.operands[1] - if (selected) { - result = map(selected, context) - break - } - } - throw new Error(`Unsupported ${operation} node while folding`) - } - case "prio": { - const first = formula.operands.find(op => { - const folded = foldStr(op, context) - if (folded.operation !== "const") - throw new Error(`Unsupported ${operation} node while folding`) - return folded.value !== undefined - }) - result = first ? foldStr(first, context) : constant(undefined) - break - } - case "small": { - let smallest = undefined as ConstantNode | undefined - for (const operand of formula.operands) { - const folded = foldStr(operand, context) - if (folded.operation !== "const") - throw new Error(`Unsupported ${operation} node while folding`) - if (smallest?.value === undefined || (folded.value !== undefined && folded.value < smallest.value)) - smallest = folded - } - result = smallest ?? constant(undefined) - break - } - case "match": { - const [v1, v2, match, unmatch] = formula.operands.map((x: NumNode | StrNode) => map(x, context)) - if (v1.operation !== "const" || v2.operation !== "const") - throw new Error(`Unsupported ${operation} node while folding`) - result = (v1.value === v2.value) ? match : unmatch - break - } - case "threshold": { - const [value, threshold, pass, fail] = formula.operands.map(x => map(x, context) as OptNode) - if (pass.operation === "const" && fail.operation === "const" && pass.value === fail.value) - result = pass - else if (value.operation === "const" && threshold.operation === "const") - result = value.value >= threshold.value ? pass : fail - else - result = { ...formula, operands: [value, threshold, pass, fail] } - break - } - case "subscript": { - const index = fold(formula.operands[0], context) - if (index.operation !== "const") - throw new Error("Found non-constant subscript node while folding") - result = constant(formula.list[index.value]) - break - } - case "read": { - const operands = context.data - .map(x => objPathValue(x, formula.path) as (NumNode | StrNode)) - .filter(x => x) - - if (operands.length === 0) { - if (shouldFold(formula)) { - const { accu } = formula - if (accu === undefined || accu === "small") - result = formula.type === "string" ? constant(undefined) : constant(NaN) - else result = constant(allOperations[accu]([])) - } else result = formula - } else if (formula.accu === undefined || operands.length === 1) - result = map(operands[operands.length - 1], context) - else - result = map({ operation: formula.accu, operands } as ComputeNode | StrPrioNode, context) - break - } - case "data": { - if (formula.reset) context = origin - const nextMap = nextContextMap.get(context)! - let nextContext = nextMap.get(formula.data) - if (!nextContext) { - nextContext = { data: [...context.data, formula.data], processed: new Map() } - nextContextMap.set(nextContext, new Map()) - nextMap.set(formula.data, nextContext) - } - result = map(formula.operands[0], nextContext) - break - } - default: assertUnreachable(operation) - } - - if (result.info) { - result = { ...result } - delete result.info - } - return result - }) as OptNode[] -} - -export const testing = { - constantFold, flatten, deduplicate -} +import type { DynStat } from '../Solver/common' +import { assertUnreachable, objPathValue } from '../Util/Util' +import { customMapFormula, forEachNodes, mapFormulas } from './internal' +import type { + AnyNode, + CommutativeMonoidOperation, + ComputeNode, + ConstantNode, + Data, + NumNode, + Operation, + ReadNode, + StrNode, + StrPrioNode, + ThresholdNode, +} from './type' +import { constant } from './utils' + +export type OptNode = + | ComputeNode + | ThresholdNode + | ReadNode + | ConstantNode + +const allCommutativeMonoidOperations: StrictDict< + CommutativeMonoidOperation, + (_: number[]) => number +> = { + min: (x: number[]): number => Math.min(...x), + max: (x: number[]): number => Math.max(...x), + add: (x: number[]): number => x.reduce((a, b) => a + b, 0), + mul: (x: number[]): number => x.reduce((a, b) => a * b, 1), +} +export const allOperations: StrictDict< + Operation | 'threshold', + (_: number[]) => number +> = { + ...allCommutativeMonoidOperations, + res: ([res]: number[]): number => { + if (res < 0) return 1 - res / 2 + else if (res >= 0.75) return 1 / (res * 4 + 1) + return 1 - res + }, + sum_frac: (x: number[]): number => x[0] / x.reduce((a, b) => a + b), + threshold: ([value, threshold, pass, fail]: number[]): number => + value >= threshold ? pass : fail, +} + +const commutativeMonoidOperationSet = new Set( + Object.keys(allCommutativeMonoidOperations) as NumNode['operation'][] +) + +export function optimize( + formulas: NumNode[], + topLevelData: Data, + shouldFold = (_formula: ReadNode) => false +): OptNode[] { + let opts = constantFold(formulas, topLevelData, shouldFold) + opts = flatten(opts) + return deduplicate(opts) +} + +/** + * Compile an array of `formulas` into a JS `Function` + * + * @param formulas + * @param initial base stats for the formula + * @param binding + * @param slotCount the number of slots in the build (usually 5) + * @returns + */ +export function precompute( + formulas: OptNode[], + initial: DynStat, + binding: (readNode: ReadNode) => string, + slotCount: C +): ( + _: readonly { readonly values: Readonly }[] & { length: C } +) => number[] { + // res copied from the code above + let body = ` +"use strict"; +function res(res) { + if (res < 0) return 1 - res / 2 + else if (res >= 0.75) return 1 / (res * 4 + 1) + return 1 - res +} +const x0=0` // making sure `const` has at least one entry + + let i = 1 + const names = new Map() + forEachNodes( + formulas, + (_) => { + /* */ + }, + (f) => { + const { operation, operands } = f, + name = `x${i++}`, + operandNames = operands.map((x: OptNode) => names.get(x)!) + names.set(f, name) + switch (operation) { + case 'read': { + const key = binding(f) + let arr = slotCount + ? new Array(slotCount) + .fill(null) + .map((_, i) => `(b[${i}].values["${key}"] ?? 0)`) + : ['0'] + if (initial[key] && initial[key] !== 0) { + arr = [initial[key].toString(), ...arr] + } + body += `,${name}=${arr.join('+')}\n` + break + } + case 'const': + names.set(f, `(${f.value})`) + break + case 'add': + case 'mul': + body += `,${name}=${operandNames.join( + operation === 'add' ? '+' : '*' + )}\n` + break + case 'min': + case 'max': + body += `,${name}=Math.${operation}(${operandNames})\n` + break + case 'threshold': { + const [value, threshold, pass, fail] = operandNames + body += `,${name}=(${value}>=${threshold})?${pass}:${fail}\n` + break + } + case 'res': + body += `,${name}=res(${operandNames[0]})\n` + break + case 'sum_frac': + body += `,${name}=${operandNames[0]}/(${operandNames[0]}+${operandNames[1]})\n` + break + + default: + assertUnreachable(operation) + } + } + ) + body += `;\nreturn [${formulas.map((f) => names.get(f)!)}]` + return new (Function as any)(`b`, body) +} + +function flatten(formulas: OptNode[]): OptNode[] { + return mapFormulas( + formulas, + (f) => f, + (_formula) => { + let result = _formula + if (commutativeMonoidOperationSet.has(_formula.operation as Operation)) { + const formula = _formula as ComputeNode + const { operation } = formula + + let flattened = false + const operands = formula.operands.flatMap((dep) => + dep.operation === operation + ? ((flattened = true), dep.operands) + : [dep] + ) + result = flattened ? { ...formula, operands } : formula + } + + return result + } + ) +} +function deduplicate(formulas: OptNode[]): OptNode[] { + function elementCounts(array: readonly T[]): Map { + const result = new Map() + for (const value of array) result.set(value, (result.get(value) ?? 0) + 1) + return result + } + function arrayFromCounts(counts: Map): T[] { + return [...counts].flatMap(([dep, count]) => Array(count).fill(dep)) + } + + const wrap = { + common: { + counts: new Map(), + formulas: new Set(), + operation: 'add' as Operation, + }, + } + + for (; ;) { + let next: typeof wrap.common | undefined + + const factored: ComputeNode = { + operation: wrap.common.operation, + operands: arrayFromCounts(wrap.common.counts), + } + + const candidatesByOperation = new Map< + Operation, + [ComputeNode, Map][] + >() + for (const operation of Object.keys(allCommutativeMonoidOperations)) + candidatesByOperation.set(operation, []) + + formulas = mapFormulas( + formulas, + (_formula) => { + if (wrap.common.formulas.has(_formula)) { + const formula = _formula as ComputeNode + const remainingCounts = new Map(wrap.common.counts) + const operands = formula.operands.filter((dep) => { + const count = remainingCounts.get(dep) + if (count) { + remainingCounts.set(dep, count - 1) + return false + } + return true + }) + + if (!operands.length) return factored + operands.push(factored) + return { ...formula, operands } + } + return _formula + }, + (_formula) => { + if (!commutativeMonoidOperationSet.has(_formula.operation as any)) + return _formula + const formula = _formula as ComputeNode + + if (next) { + if (next.operation === formula.operation) { + const currentCounts = elementCounts(formula.operands), + commonCounts = new Map() + const nextCounts = next.counts + let total = 0 + + for (const [dependency, currentCount] of currentCounts.entries()) { + const commonCount = Math.min( + currentCount, + nextCounts.get(dependency) ?? 0 + ) + if (commonCount) { + commonCounts.set(dependency, commonCount) + total += commonCount + } else commonCounts.delete(dependency) + } + if (total > 1) { + next.counts = commonCounts + next.formulas.add(formula) + } + } + } else { + const candidates = candidatesByOperation.get(formula.operation)! + const counts = elementCounts(formula.operands) + + for (const [candidate, candidateCounts] of candidates) { + let total = 0 + + const commonCounts = new Map() + for (const [ + dependency, + candidateCount, + ] of candidateCounts.entries()) { + const count = Math.min( + candidateCount, + counts.get(dependency) ?? 0 + ) + if (count) { + commonCounts.set(dependency, count) + total += count + } + } + if (total > 1) { + next = { + counts: commonCounts, + formulas: new Set([formula, candidate]), + operation: formula.operation, + } + candidatesByOperation.clear() + break + } + } + if (!next) candidates.push([formula, counts]) + } + + return formula + } + ) + + if (next) wrap.common = next + else break + } + + return formulas +} + +/** + * Replace nodes with known values with appropriate constants, + * avoiding removal of any nodes that pass `isFixed` predicate + */ +export function constantFold( + formulas: NumNode[], + topLevelData: Data, + shouldFold = (_formula: ReadNode) => false +): OptNode[] { + type Context = { + data: Data[] + processed: Map + } + const origin: Context = { data: [], processed: new Map() } + const nextContextMap = new Map([[origin, new Map()]]) + + const context = { data: [topLevelData], processed: new Map() } + nextContextMap.set(context, new Map()) + nextContextMap.get(origin)!.set(topLevelData, context) + return customMapFormula( + formulas, + context, + (formula, context, map) => { + const { operation } = formula, + fold = (x: NumNode, c: typeof context) => map(x, c) as OptNode + const foldStr = (x: StrNode, c: typeof context) => map(x, c) as StrNode + let result: OptNode | StrNode + switch (operation) { + case 'const': + result = formula + break + case 'add': + case 'mul': + case 'max': + case 'min': { + const f = allOperations[operation] + const numericOperands: number[] = [] + const formulaOperands: OptNode[] = formula.operands + .filter((formula) => { + const folded = fold(formula, context) + return folded.operation === 'const' + ? (numericOperands.push(folded.value), false) + : true + }) + .map((x) => fold(x, context)) + const numericValue = f(numericOperands) + + // Fold degenerate cases. This may incorrectly compute NaN + // results, which shouldn't appear under expected usage. + // - zero + // - 0 * ... = 0 + // - infinity + // - max(infinity, ...) = infinity + // - infinity + ... = infinity + // - (-infinity) + // - min(-infinity, ...) - infinity + // - (-infinity) + ... = -infinity + // - NaN + // - operation(NaN, ...) = NaN + if (!isFinite(numericValue)) { + if ( + operation !== 'mul' && + (operation !== 'max' || numericValue > 0) && + (operation !== 'min' || numericValue < 0) + ) { + result = constant(numericValue) + break + } + } else if (operation === 'mul' && numericValue === 0) { + result = constant(numericValue) + break + } + + if (numericValue !== f([])) + // Skip vacuous values + formulaOperands.push(constant(numericValue)) + if (formulaOperands.length <= 1) + result = formulaOperands[0] ?? constant(f([])) + else result = { operation, operands: formulaOperands } + break + } + case 'res': + case 'sum_frac': { + const operands = formula.operands.map((x) => fold(x, context)) + const f = allOperations[operation] + if (operands.every((x) => x.operation === 'const')) + result = constant( + f(operands.map((x) => (x as ConstantNode).value)) + ) + else result = { ...formula, operands } + break + } + case 'lookup': { + const index = foldStr(formula.operands[0], context) + if (index.operation === 'const') { + const selected = formula.table[index.value!] ?? formula.operands[1] + if (selected) { + result = map(selected, context) + break + } + } + throw new Error(`Unsupported ${operation} node while folding`) + } + case 'prio': { + const first = formula.operands.find((op) => { + const folded = foldStr(op, context) + if (folded.operation !== 'const') + throw new Error(`Unsupported ${operation} node while folding`) + return folded.value !== undefined + }) + result = first ? foldStr(first, context) : constant(undefined) + break + } + case 'small': { + let smallest = undefined as + | ConstantNode + | undefined + for (const operand of formula.operands) { + const folded = foldStr(operand, context) + if (folded.operation !== 'const') + throw new Error(`Unsupported ${operation} node while folding`) + if ( + smallest?.value === undefined || + (folded.value !== undefined && folded.value < smallest.value) + ) + smallest = folded + } + result = smallest ?? constant(undefined) + break + } + case 'match': { + const [v1, v2, match, unmatch] = formula.operands.map( + (x: NumNode | StrNode) => map(x, context) + ) + if (v1.operation !== 'const' || v2.operation !== 'const') + throw new Error(`Unsupported ${operation} node while folding`) + result = v1.value === v2.value ? match : unmatch + break + } + case 'threshold': { + const [value, threshold, pass, fail] = formula.operands.map( + (x) => map(x, context) as OptNode + ) + if ( + pass.operation === 'const' && + fail.operation === 'const' && + pass.value === fail.value + ) + result = pass + else if ( + value.operation === 'const' && + threshold.operation === 'const' + ) + result = value.value >= threshold.value ? pass : fail + else result = { ...formula, operands: [value, threshold, pass, fail] } + break + } + case 'subscript': { + const index = fold(formula.operands[0], context) + if (index.operation !== 'const') + throw new Error('Found non-constant subscript node while folding') + result = constant(formula.list[index.value]) + break + } + case 'read': { + const operands = context.data + .map((x) => objPathValue(x, formula.path) as NumNode | StrNode) + .filter((x) => x) + + if (operands.length === 0) { + if (shouldFold(formula)) { + const { accu } = formula + if (accu === undefined || accu === 'small') + result = + formula.type === 'string' + ? constant(undefined) + : constant(NaN) + else result = constant(allOperations[accu]([])) + } else result = formula + } else if (formula.accu === undefined || operands.length === 1) + result = map(operands[operands.length - 1], context) + else + result = map( + { operation: formula.accu, operands } as + | ComputeNode + | StrPrioNode, + context + ) + break + } + case 'data': { + if (formula.reset) context = origin + const nextMap = nextContextMap.get(context)! + let nextContext = nextMap.get(formula.data) + if (!nextContext) { + nextContext = { + data: [...context.data, formula.data], + processed: new Map(), + } + nextContextMap.set(nextContext, new Map()) + nextMap.set(formula.data, nextContext) + } + result = map(formula.operands[0], nextContext) + break + } + default: + assertUnreachable(operation) + } + + if (result.info) { + result = { ...result } + delete result.info + } + return result + } + ) as OptNode[] +} + +export const testing = { + constantFold, + flatten, + deduplicate, +} diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx index e4a2cd3603..1d92c1be7b 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx @@ -1,698 +1,1327 @@ -import { allArtifactSetKeys, allArtifactSlotKeys, ArtifactSetKey, ArtifactSlotKey, WeaponTypeKey } from "@genshin-optimizer/consts"; -import { weaponAsset } from "@genshin-optimizer/g-assets"; -import { CopyAll, DeleteForever, Info, Refresh } from "@mui/icons-material"; -import StarRoundedIcon from '@mui/icons-material/StarRounded'; -import { Box, Button, ButtonGroup, CardHeader, Divider, Grid, ListItem, MenuItem, Skeleton, Slider, Stack, ToggleButton, Typography } from "@mui/material"; -import React, { createContext, Suspense, useCallback, useContext, useDeferredValue, useEffect, useMemo, useState } from "react"; -import { useTranslation } from "react-i18next"; -import { useLocation } from "react-router-dom"; -import ArtifactSetAutocomplete from "../../../../Components/Artifact/ArtifactSetAutocomplete"; -import ArtifactSetTooltip from "../../../../Components/Artifact/ArtifactSetTooltip"; -import SetEffectDisplay from "../../../../Components/Artifact/SetEffectDisplay"; -import SlotIcon from "../../../../Components/Artifact/SlotIcon"; -import BootstrapTooltip from "../../../../Components/BootstrapTooltip"; -import CardDark from "../../../../Components/Card/CardDark"; -import CardLight from "../../../../Components/Card/CardLight"; -import StatDisplayComponent from "../../../../Components/Character/StatDisplayComponent"; -import ColorText from "../../../../Components/ColoredText"; -import CustomNumberInput from "../../../../Components/CustomNumberInput"; -import DocumentDisplay from "../../../../Components/DocumentDisplay"; -import DropdownButton from "../../../../Components/DropdownMenu/DropdownButton"; -import { FieldDisplayList, NodeFieldDisplay } from "../../../../Components/FieldDisplay"; -import ImgIcon from "../../../../Components/Image/ImgIcon"; -import LevelSelect from "../../../../Components/LevelSelect"; -import RefinementDropdown from "../../../../Components/RefinementDropdown"; -import SolidToggleButtonGroup from "../../../../Components/SolidToggleButtonGroup"; -import { StatColoredWithUnit, StatWithUnit } from "../../../../Components/StatDisplay"; -import { CharacterContext } from "../../../../Context/CharacterContext"; -import { DataContext, dataContextObj } from "../../../../Context/DataContext"; -import { getArtSheet } from "../../../../Data/Artifacts"; -import Artifact, { maxArtifactLevel } from "../../../../Data/Artifacts/Artifact"; -import { artifactDefIcon } from "../../../../Data/Artifacts/ArtifactSheet"; -import { getWeaponSheet } from "../../../../Data/Weapons"; -import { DatabaseContext } from "../../../../Database/Database"; -import { initCharTC } from "../../../../Database/DataManagers/CharacterTCData"; -import { uiInput as input } from "../../../../Formula"; -import { computeUIData, dataObjForWeapon, mergeData, uiDataForTeam } from "../../../../Formula/api"; -import { mapFormulas } from "../../../../Formula/internal"; -import { optimize, precompute } from "../../../../Formula/optimization"; -import { NumNode } from "../../../../Formula/type"; -import { constant, percent } from "../../../../Formula/utils"; -import KeyMap, { cacheValueString } from "../../../../KeyMap"; -import StatIcon from "../../../../KeyMap/StatIcon"; -import useBoolState from "../../../../ReactHooks/useBoolState"; -import useTeamData, { getTeamData } from "../../../../ReactHooks/useTeamData"; -import { iconInlineProps } from "../../../../SVGIcons"; -import { allSubstatKeys, ICachedArtifact, MainStatKey, SubstatKey } from "../../../../Types/artifact"; -import { ICharTC, ICharTCArtifactSlot } from "../../../../Types/character"; -import { ArtifactRarity, SetNum, SubstatType, substatType } from "../../../../Types/consts"; -import { ICachedWeapon } from "../../../../Types/weapon"; -import { deepClone, objectMap, objPathValue } from "../../../../Util/Util"; -import { defaultInitialWeaponKey } from "../../../../Util/WeaponUtil"; -import OptimizationTargetSelector from "../TabOptimize/Components/OptimizationTargetSelector"; -import { dynamicData } from "../TabOptimize/foreground"; -import useCharTC from "./useCharTC"; -import useDBMeta from "../../../../ReactHooks/useDBMeta"; -const WeaponSelectionModal = React.lazy(() => import('../../../../Components/Weapon/WeaponSelectionModal')) - -type ISet = Partial> -type SetCharTCAction = Partial | ((v: ICharTC) => Partial) -type CharTCContexObj = { - charTC: ICharTC, - setCharTC: (action: SetCharTCAction) => void -} -const CharTCContext = createContext({} as CharTCContexObj) - -export default function TabTheorycraft() { - const { database } = useContext(DatabaseContext) - const { data: oldData } = useContext(DataContext) - const { character, character: { key: characterKey, compareData }, characterSheet, characterDispatch } = useContext(CharacterContext) - const charTC = useCharTC(characterKey, defaultInitialWeaponKey(characterSheet.weaponTypeKey)) - const setCharTC = useCallback((data: SetCharTCAction) => { database.charTCs.set(characterKey, data) }, [characterKey, database]) - const valueCharTCContext = useMemo(() => ({ charTC, setCharTC }), [charTC, setCharTC]) - const resetData = useCallback(() => { - setCharTC(initCharTC(defaultInitialWeaponKey(characterSheet.weaponTypeKey))) - }, [setCharTC, characterSheet]) - const setWeapon = useCallback( - (action: Partial) => { - setCharTC({ ...charTC, weapon: { ...charTC.weapon, ...action } }) - }, - [setCharTC, charTC], - ) - - const copyFrom = useCallback( - (eWeapon: ICachedWeapon, build: ICachedArtifact[]) => { - const newData = initCharTC(eWeapon.key) - newData.artifact.substats.type = charTC.artifact.substats.type - - newData.weapon.level = eWeapon.level - newData.weapon.ascension = eWeapon.ascension - newData.weapon.refinement = eWeapon.refinement - - const sets = {} - build.forEach(art => { - if (!art) return - const { slotKey, setKey, substats, mainStatKey, level, rarity } = art - newData.artifact.slots[slotKey].level = level - newData.artifact.slots[slotKey].statKey = mainStatKey - newData.artifact.slots[slotKey].rarity = rarity - sets[setKey] = (sets[setKey] ?? 0) + 1 - substats.forEach(substat => { - if (substat.key) newData.artifact.substats.stats[substat.key] = (newData.artifact.substats.stats[substat.key] ?? 0) + substat.accurateValue - }) - }) - newData.artifact.sets = Object.fromEntries(Object.entries(sets).map(([key, value]) => [key, - value === 3 ? 2 : - value === 5 ? 4 : - value === 1 && !(key as string).startsWith("PrayersFor") ? 0 : value - ]).filter(([, value]) => value)) - setCharTC(newData) - }, - [charTC, setCharTC], - ) - const location = useLocation() - const { build: locBuild } = (location.state as { build: string[] } | undefined) ?? { build: undefined } - useEffect(() => { - if (!locBuild) return - const eWeapon = database.weapons.get(character.equippedWeapon)! - copyFrom(eWeapon, locBuild.map(i => database.arts.get(i)!)) - // WARNING: if copyFrom is included, it will cause a render loop due to its setData <---> data - // eslint-disable-next-line - }, [locBuild, database]) - - const copyFromEquipped = useCallback( - () => { - const eWeapon = database.weapons.get(character.equippedWeapon)! - copyFrom(eWeapon, Object.values(character.equippedArtifacts).map(a => database.arts.get(a)!).filter(a => a)) - }, - [database, character.equippedArtifacts, character.equippedWeapon, copyFrom], - ) - - const weapon: ICachedWeapon = useMemo(() => ({ - ...charTC.weapon, - location: "", - lock: false, - id: "" - }), [charTC]) - - const setArtifact = useCallback((artifact: ICharTC["artifact"]) => { - const data_ = deepClone(charTC) - data_.artifact = artifact - setCharTC(data_) - }, [charTC, setCharTC]) - - const setSubstatsType = useCallback((t: SubstatType) => { - const data_ = deepClone(charTC) - data_.artifact.substats.type = t - setCharTC(data_) - }, [charTC, setCharTC]) - - const setSubstats = useCallback((setSubstats: Record) => { - const data_ = deepClone(charTC) - data_.artifact.substats.stats = setSubstats - setCharTC(data_) - }, [charTC, setCharTC]) - - const deferredData = useDeferredValue(charTC) - const overriderArtData = useMemo(() => { - const stats = { ...deferredData.artifact.substats.stats } - Object.values(deferredData.artifact.slots).forEach(({ statKey, rarity, level }) => - stats[statKey] = (stats[statKey] ?? 0) + Artifact.mainStatValue(statKey, rarity, level)) - return { - art: objectMap(stats, (v, k) => k.endsWith("_") ? percent(v / 100) : constant(v)), - artSet: objectMap(deferredData.artifact.sets, v => constant(v)), - } - }, [deferredData]) - - const overrideWeapon: ICachedWeapon = useMemo(() => ({ - id: "", - location: "", - key: charTC.weapon.key, - level: charTC.weapon.level, - ascension: charTC.weapon.ascension, - refinement: charTC.weapon.refinement, - lock: false - }), [charTC]) - const teamData = useTeamData(characterKey, 0, overriderArtData, overrideWeapon) - - const { target: charUIData } = teamData?.[characterKey] ?? {} - - const dataContextValue: dataContextObj | undefined = useMemo(() => { - if (!teamData || !charUIData) return undefined - return { - data: charUIData, - teamData, - } - }, [charUIData, teamData]) - const dataContextValueWithOld: dataContextObj | undefined = useMemo(() => { - if (!dataContextValue) return undefined - return { - ...dataContextValue, - oldData: compareData ? oldData : undefined, - } - }, [dataContextValue, compareData, oldData]) - - const optimizationTarget = charTC.optimization.target - const setOptimizationTarget = useCallback((optimizationTarget: ICharTC["optimization"]["target"]) => { - const data_ = deepClone(charTC) - data_.optimization.target = optimizationTarget - setCharTC(data_) - }, [charTC, setCharTC]) - - const distributedSubstats = charTC.optimization.distributedSubstats - const setDistributedSubstats = useCallback((distributedSubstats: ICharTC["optimization"]["distributedSubstats"]) => { - const data_ = deepClone(charTC) - data_.optimization.distributedSubstats = distributedSubstats - setCharTC(data_) - }, [charTC, setCharTC]) - const maxSubstats = charTC.optimization.maxSubstats - - const { gender } = useDBMeta() - - // This solves - // $\argmax_{x\in N^k, \sum x <= n, x <= x_max} f(x)$ without assumptions on the properties of $f$ - // We brute force iterate over all substats in the graph and compute the maximum - // n.b. some substat combinations may not be materializable into real artifacts - const optimizeSubstats = useCallback((apply: boolean) => () => { - const startTime = performance.now() - if (!characterKey || !optimizationTarget) return - const teamData = getTeamData(database, characterKey, 0, overriderArtData, overrideWeapon) - if (!teamData) return - const workerData = uiDataForTeam(teamData.teamData, gender, characterKey)[characterKey]?.target.data![0] - if (!workerData) return - Object.assign(workerData, mergeData([workerData, dynamicData])) // Mark art fields as dynamic - const unoptimizedOptimizationTargetNode = objPathValue(workerData.display ?? {}, optimizationTarget) as NumNode | undefined - if (!unoptimizedOptimizationTargetNode) return - const unoptimizedNodes = [unoptimizedOptimizationTargetNode] - let nodes = optimize(unoptimizedNodes, workerData, ({ path: [p] }) => p !== "dyn") - // Const fold the artifact set - nodes = mapFormulas(nodes, f => { - if (f.operation === "read" && f.path[0] === "dyn") { - const a = charTC.artifact.sets[f.path[1]]; - if (a) { - return constant(a) - } else if (allArtifactSetKeys.includes(f.path[1] as any)) { - return constant(0) - } - } - return f - }, f => f) - nodes = optimize(nodes, {}, _ => false) - - const subs = new Set() - const compute = precompute(nodes, {}, f => { - subs.add(f.path[1]) - return f.path[1] - }, 2) - const realSubs = [...subs].filter(x => allSubstatKeys.includes(x as any)) - if (realSubs.reduce((p, x) => p + maxSubstats[x], 0) < distributedSubstats) - realSubs.push("__unused__") - const comp = (statKey: string) => statKey.endsWith("_") ? 100 : 1 - - let max = -Infinity - const buffer = Object.fromEntries([...subs].map(x => [x, 0])) - let maxBuffer: typeof buffer | undefined; - const bufferSubs = objectMap(charTC.artifact.substats.stats, (v, k) => v / comp(k)) - const permute = (distributedSubstats: number, [x, ...xs]: string[]) => { - if (xs.length === 0) { - if (distributedSubstats > maxSubstats[x]) - return - if (x !== "__unused__") - buffer[x] = Artifact.substatValue(x as SubstatKey, 5, charTC.artifact.substats.type) / comp(x) * distributedSubstats; - const [result] = compute([{ values: bufferSubs }, { values: buffer }] as const); - if (result > max) { - max = result - maxBuffer = structuredClone(buffer) - } - return - } - for (let i = 0; i <= Math.min(maxSubstats[x], distributedSubstats); i++) { - buffer[x] = Artifact.substatValue(x as SubstatKey, 5, charTC.artifact.substats.type) / comp(x) * i; - permute(distributedSubstats - i, xs) - } - } - permute(distributedSubstats, realSubs) - if (process.env.NODE_ENV === "development") - console.log(`Took ${performance.now() - startTime} ms`) - console.log(maxBuffer) - console.log(objectMap(maxBuffer!, (v, x) => - allSubstatKeys.includes(x as any) ? - v / (Artifact.substatValue(x as SubstatKey, 5, charTC.artifact.substats.type) / comp(x)) : - v - )) - - if (apply) { - const data_ = deepClone(charTC) - data_.artifact.substats.stats = objectMap(charTC.artifact.substats.stats, (v, k) => v + (maxBuffer![k] ?? 0) * comp(k)) - data_.optimization.distributedSubstats = 0 - setCharTC(data_) - } - }, [charTC, characterKey, database, distributedSubstats, gender, maxSubstats, optimizationTarget, overrideWeapon, overriderArtData, setCharTC]) - - return - - - - - - - characterDispatch({ compareData: v })} size="small"> - Show TC stats - Compare vs. equipped - - - - {dataContextValue ? - - - - - - - s.statKey)} - distributedSubstats={distributedSubstats} setDistributedSubstats={setDistributedSubstats} - maxSubstats={maxSubstats} setMaxSubstats={(k: SubstatKey) => (v: number) => { - if (charTC.optimization.maxSubstats[k] === v) return - const data_ = deepClone(charTC) - data_.optimization.maxSubstats[k] = v - setCharTC(data_) - }} /> - - - setOptimizationTarget(target)} - /> - {process.env.NODE_ENV === "development" && } - - : } - - {dataContextValueWithOld ? - - : } - - -} - -function WeaponEditorCard({ weapon, setWeapon, weaponTypeKey }: { weapon: ICachedWeapon, weaponTypeKey: WeaponTypeKey, setWeapon: (action: Partial) => void }) { - const { key, level = 0, refinement = 1, ascension = 0 } = weapon - const weaponSheet = getWeaponSheet(key) - const [show, onShow, onHide] = useBoolState() - const { data } = useContext(DataContext) - const weaponUIData = useMemo(() => weapon && computeUIData([weaponSheet.data, dataObjForWeapon(weapon)]), [weaponSheet, weapon]) - return - setWeapon({ key: k })} weaponTypeFilter={weaponTypeKey} /> - - - = 2)} - sx={{ flexshrink: 1, flexBasis: 0, maxWidth: "30%", borderRadius: 1 }} - /> - - - {weaponSheet.hasRefinement && setWeapon({ refinement: r })} />} - - - - - - - {weaponUIData && - {[input.weapon.main, input.weapon.sub, input.weapon.sub2].map((node) => { - const n = weaponUIData.get(node) - if (n.isEmpty || !n.value) return null - return - })} - } - - {data && weaponSheet?.document && } - - -} - -function ArtifactMainLevelCard({ artifactData, setArtifactData }: - { artifactData: ICharTC["artifact"], setArtifactData: (a: ICharTC["artifact"]) => void }) { - const setSlot = useCallback((slotKey: ArtifactSlotKey) => (slot: ICharTCArtifactSlot) => { - const artifactData_ = deepClone(artifactData) - artifactData_.slots[slotKey] = slot - setArtifactData(artifactData_) - }, [artifactData, setArtifactData]) - - const setArtSet = useCallback((artSet: ISet) => { - const artifactData_ = deepClone(artifactData) - artifactData_.sets = artSet - setArtifactData(artifactData_) - }, [artifactData, setArtifactData]) - - return - - - {allArtifactSlotKeys.map(s => )} - - - }> - - - -} -function ArtifactMainLevelSlot({ slotKey, slot, setSlot: setSlotProp }: { slotKey: ArtifactSlotKey, slot: ICharTCArtifactSlot, setSlot: (s: ICharTCArtifactSlot) => void }) { - const { level, statKey, rarity } = slot - const keys = Artifact.slotMainStats(slotKey) - const setSlot = useCallback((action: Partial) => { - setSlotProp({ ...slot, ...action }) - }, [slot, setSlotProp]) - const setRarity = useCallback( - (r: ArtifactRarity) => { - const mLvl = maxArtifactLevel[r] ?? 0 - if (level > mLvl) setSlot({ rarity: r, level: mLvl }) - else setSlot({ rarity: r }) - }, [level, setSlot]) - - return - - - {keys.length === 1 ? - {KeyMap.getStr(keys[0])} : - } color={KeyMap.getVariant(statKey) ?? "success"}> - {keys.map(msk => - setSlot({ statKey: msk })}> - - )} - } - - {rarity} } > - {[5, 4, 3].map(r => - setRarity(r as ArtifactRarity)}> - {r} - )} - - l !== undefined && setSlot({ level: l })} sx={{ borderRadius: 1, pl: 1, my: 0, height: "100%" }} inputProps={{ sx: { pl: 0.5, width: "2em" }, max: 20, min: 0 }} /> - - {`${cacheValueString(Artifact.mainStatValue(statKey, rarity, level), KeyMap.unit(statKey))}${KeyMap.unit(statKey)}`} - - -} - -function ArtifactSetsEditor({ artSet, setArtSet }: { artSet: ISet, setArtSet(artSet: ISet) }) { - const setSet = useCallback((setKey: ArtifactSetKey | "") => { - if (!setKey) return - setArtSet({ ...artSet, [setKey]: parseInt(Object.keys(getArtSheet(setKey).setEffects)[0]) }) - }, [artSet, setArtSet,]) - - const setValue = useCallback((setKey: ArtifactSetKey) => (value: 1 | 2 | 4) => setArtSet({ ...artSet, [setKey]: value }), [artSet, setArtSet]) - const deleteValue = useCallback((setKey: ArtifactSetKey) => () => { - const { [setKey]: _, ...rest } = artSet - setArtSet({ ...rest }) - }, [artSet, setArtSet]) - - const remaining = 5 - Object.values(artSet).reduce((a, b) => a + b, 0) - - return - {Object.entries(artSet).map(([setKey, value]) => )} - - Object.keys(artSet).includes(key as ArtifactSetKey) || !key || Object.keys(getArtSheet(key).setEffects).every(n => parseInt(n) > remaining)} - /> - - - -} -function ArtifactSetEditor({ setKey, value, setValue, deleteValue, remaining }: { setKey: ArtifactSetKey, value: 1 | 2 | 4, setValue: (v: 1 | 2 | 4) => void, deleteValue: () => void, remaining: number }) { - const artifactSheet = getArtSheet(setKey) - - /* Assumes that all conditionals are from 4-Set. needs to change if there are 2-Set conditionals */ - const set4CondNums = useMemo(() => { - if (value < 4) return [] - return Object.keys(artifactSheet.setEffects).filter(setNumKey => artifactSheet.setEffects[setNumKey]?.document.some(doc => "states" in doc)) - }, [artifactSheet, value]) - - return - - - - - {artifactSheet.setName} - - - - - {value}-set}> - {Object.keys(artifactSheet.setEffects).map(setKey => parseInt(setKey)).map(setKey => - (remaining + value)} onClick={() => setValue(setKey as 1 | 2 | 4)}>{setKey}-set - )} - - - - - {!!set4CondNums.length && - {set4CondNums.map(setNumKey => - - )} - } - -} -function ArtifactSubCard({ substats, setSubstats, substatsType, setSubstatsType, mainStatKeys, distributedSubstats, setDistributedSubstats, maxSubstats, setMaxSubstats }: { - substats: Record, setSubstats: (substats: Record) => void, - substatsType: SubstatType, setSubstatsType: (t: SubstatType) => void, - mainStatKeys: MainStatKey[], - distributedSubstats: number, setDistributedSubstats: (f: number) => void, - maxSubstats: Record, setMaxSubstats: (k: SubstatKey) => (v: number) => void, -}) { - const setValue = useCallback((key: SubstatKey) => (v: number) => setSubstats({ ...substats, [key]: v }), [substats, setSubstats]) - const { t } = useTranslation("page_character") - const rv = Object.entries(substats).reduce((t, [k, v]) => t + (v / Artifact.substatValue(k)), 0) * 100 - const rolls = Object.entries(substats).reduce((t, [k, v]) => t + (v / Artifact.substatValue(k, undefined, substatsType)), 0) - return - - - {substatType.map(st => setSubstatsType(st)}>{t(`tabTheorycraft.substatType.${st}`)})} - - {t`tabTheorycraft.maxTotalRolls`}} placement="top"> - - 45 ? "warning" : undefined} >Rolls: {rolls.toFixed(0)} - 45 ? "warning" : undefined} >RV: {rv.toFixed(1)}% - - - v !== undefined && setDistributedSubstats(v)} - endAdornment={"Distributed Substats"} - sx={{ borderRadius: 1, px: 1, width: "50%" }} - inputProps={{ sx: { textAlign: "right", px: 1, width: "20%" }, min: 0 }} - /> - - - - {Object.entries(substats).map(([k, v]) => - )} - - -} -function ArtifactSubstatEditor({ statKey, value, setValue, substatsType, mainStatKeys, maxSubstat, setMaxSubstat }: { - statKey: SubstatKey, - value: number, setValue: (v: number) => void, - substatsType: SubstatType, - mainStatKeys: MainStatKey[], - maxSubstat: number, setMaxSubstat: (v: number) => void, -}) { - const substatValue = Artifact.substatValue(statKey, 5, substatsType) - const [rolls, setRolls] = useState(() => value / substatValue) - useEffect(() => setRolls(value / substatValue), [value, substatValue]) - - const unit = KeyMap.unit(statKey) - const displayValue = rolls * substatValue - - const rv = (rolls * substatValue) / Artifact.substatValue(statKey) * 100 - const numMains = mainStatKeys.reduce((t, ms) => t + (ms === statKey ? 1 : 0), 0) - const maxRolls = (5 - numMains) * 6 - // 0.0001 to nudge float comparasion - const invalid = (rolls - 0.0001) > maxRolls - const setRValue = useCallback((r: number) => setValue(r * substatValue), [setValue, substatValue]) - - return - - - {KeyMap.getStr(statKey)}{KeyMap.unit(statKey)} - - {/* {t(numMains ? `tabTheorycraft.maxRollsMain` : `tabTheorycraft.maxRolls`, { value: maxRolls })}} placement="top"> */} - - RV: {rv.toFixed(1)}% - - {/* */} - - - } - value={parseFloat(displayValue.toFixed(2))} - onChange={v => v !== undefined && setValue(v)} - sx={{ borderRadius: 1, px: 1, height: "100%", width: "6em" }} - inputProps={{ sx: { textAlign: "right" }, min: 0 }} /> - - setRolls(v as number)} - onChangeCommitted={(e, v) => setRValue(v as number)} - /> - - {cacheValueString(substatValue, unit)}{unit}x} - value={parseFloat(rolls.toFixed(2))} - onChange={v => v !== undefined && setValue(v * substatValue)} - sx={{ borderRadius: 1, px: 1, my: 0, height: "100%", width: "7em" }} - inputProps={{ sx: { textAlign: "right", pr: 0.5, }, min: 0, step: 1 }} /> - v !== undefined && setMaxSubstat(v)} - color={maxSubstat > maxRolls ? "warning" : "success"} - sx={{ borderRadius: 1, px: 1, my: 0, height: "100%", width: "7em" }} - inputProps={{ sx: { textAlign: "right", pr: 0.5, }, min: 0, step: 1 }} /> - - -} - -function ArtifactAllSubstatEditor() { - const [rolls, setRolls] = useState(undefined as undefined | number) - const [maxSubstat, setMaxSubstat] = useState(undefined as undefined | number) - const { setCharTC } = useContext(CharTCContext) - - const rollsDeferred = useDeferredValue(rolls) - useEffect(() => { - if (rollsDeferred === undefined) return - setCharTC(charTC => { - const stats = charTC.artifact.substats.stats - const substatsType = charTC.artifact.substats.type - charTC.artifact.substats.stats = objectMap(stats, (val, statKey) => { - const substatValue = Artifact.substatValue(statKey, 5, substatsType) - return substatValue * rollsDeferred - }) - return charTC - }) - }, [setCharTC, rollsDeferred]) - - const maxSubstatDeferred = useDeferredValue(maxSubstat) - useEffect(() => { - if (maxSubstatDeferred === undefined) return - setCharTC(charTC => { - charTC.optimization.maxSubstats = objectMap(charTC.optimization.maxSubstats, (val, statKey) => maxSubstatDeferred) - return charTC - }) - }, [setCharTC, maxSubstatDeferred]) - - const maxRolls = 30 - // 0.0001 to nudge float comparasion - const invalid = (rolls ?? 0 - 0.0001) > maxRolls - - - return - - Change all Substats - - - setRolls(v as number)} - onChangeCommitted={(e, v) => setRolls(v as number)} - /> - - All Rolls} - value={parseFloat((rolls ?? 0).toFixed(2))} - onChange={v => v !== undefined && setRolls(v)} - sx={{ borderRadius: 1, px: 1, my: 0, height: "100%", width: "7em" }} - inputProps={{ sx: { textAlign: "right", pr: 0.5, }, min: 0, step: 1 }} /> - All Max} - onChange={v => v !== undefined && setMaxSubstat(v)} - color={(maxSubstat ?? 0) > maxRolls ? "warning" : "success"} - sx={{ borderRadius: 1, px: 1, my: 0, height: "100%", width: "7em" }} - inputProps={{ sx: { textAlign: "right", pr: 0.5, }, min: 0, step: 1 }} /> - -} +import type { + ArtifactSetKey, + ArtifactSlotKey, + WeaponTypeKey} from '@genshin-optimizer/consts'; +import { + allArtifactSetKeys, + allArtifactSlotKeys +} from '@genshin-optimizer/consts' +import { weaponAsset } from '@genshin-optimizer/g-assets' +import { CopyAll, DeleteForever, Info, Refresh } from '@mui/icons-material' +import StarRoundedIcon from '@mui/icons-material/StarRounded' +import { + Box, + Button, + ButtonGroup, + CardHeader, + Divider, + Grid, + ListItem, + MenuItem, + Skeleton, + Slider, + Stack, + ToggleButton, + Typography, +} from '@mui/material' +import React, { + createContext, + Suspense, + useCallback, + useContext, + useDeferredValue, + useEffect, + useMemo, + useState, +} from 'react' +import { useTranslation } from 'react-i18next' +import { useLocation } from 'react-router-dom' +import ArtifactSetAutocomplete from '../../../../Components/Artifact/ArtifactSetAutocomplete' +import ArtifactSetTooltip from '../../../../Components/Artifact/ArtifactSetTooltip' +import SetEffectDisplay from '../../../../Components/Artifact/SetEffectDisplay' +import SlotIcon from '../../../../Components/Artifact/SlotIcon' +import BootstrapTooltip from '../../../../Components/BootstrapTooltip' +import CardDark from '../../../../Components/Card/CardDark' +import CardLight from '../../../../Components/Card/CardLight' +import StatDisplayComponent from '../../../../Components/Character/StatDisplayComponent' +import ColorText from '../../../../Components/ColoredText' +import CustomNumberInput from '../../../../Components/CustomNumberInput' +import DocumentDisplay from '../../../../Components/DocumentDisplay' +import DropdownButton from '../../../../Components/DropdownMenu/DropdownButton' +import { + FieldDisplayList, + NodeFieldDisplay, +} from '../../../../Components/FieldDisplay' +import ImgIcon from '../../../../Components/Image/ImgIcon' +import LevelSelect from '../../../../Components/LevelSelect' +import RefinementDropdown from '../../../../Components/RefinementDropdown' +import SolidToggleButtonGroup from '../../../../Components/SolidToggleButtonGroup' +import { + StatColoredWithUnit, + StatWithUnit, +} from '../../../../Components/StatDisplay' +import { CharacterContext } from '../../../../Context/CharacterContext' +import type { dataContextObj } from '../../../../Context/DataContext'; +import { DataContext } from '../../../../Context/DataContext' +import { getArtSheet } from '../../../../Data/Artifacts' +import Artifact, { maxArtifactLevel } from '../../../../Data/Artifacts/Artifact' +import { artifactDefIcon } from '../../../../Data/Artifacts/ArtifactSheet' +import { getWeaponSheet } from '../../../../Data/Weapons' +import { DatabaseContext } from '../../../../Database/Database' +import { initCharTC } from '../../../../Database/DataManagers/CharacterTCData' +import { uiInput as input } from '../../../../Formula' +import { + computeUIData, + dataObjForWeapon, + mergeData, + uiDataForTeam, +} from '../../../../Formula/api' +import { mapFormulas } from '../../../../Formula/internal' +import { optimize, precompute } from '../../../../Formula/optimization' +import type { NumNode } from '../../../../Formula/type' +import { constant, percent } from '../../../../Formula/utils' +import KeyMap, { cacheValueString } from '../../../../KeyMap' +import StatIcon from '../../../../KeyMap/StatIcon' +import useBoolState from '../../../../ReactHooks/useBoolState' +import useTeamData, { getTeamData } from '../../../../ReactHooks/useTeamData' +import { iconInlineProps } from '../../../../SVGIcons' +import type { + ICachedArtifact, + MainStatKey, + SubstatKey} from '../../../../Types/artifact'; +import { + allSubstatKeys +} from '../../../../Types/artifact' +import type { ICharTC, ICharTCArtifactSlot } from '../../../../Types/character' +import type { + ArtifactRarity, + SetNum, + SubstatType} from '../../../../Types/consts'; +import { + substatType, +} from '../../../../Types/consts' +import type { ICachedWeapon } from '../../../../Types/weapon' +import { deepClone, objectMap, objPathValue } from '../../../../Util/Util' +import { defaultInitialWeaponKey } from '../../../../Util/WeaponUtil' +import OptimizationTargetSelector from '../TabOptimize/Components/OptimizationTargetSelector' +import { dynamicData } from '../TabOptimize/foreground' +import useCharTC from './useCharTC' +import useDBMeta from '../../../../ReactHooks/useDBMeta' +const WeaponSelectionModal = React.lazy( + () => import('../../../../Components/Weapon/WeaponSelectionModal') +) + +type ISet = Partial> +type SetCharTCAction = Partial | ((v: ICharTC) => Partial) +type CharTCContexObj = { + charTC: ICharTC + setCharTC: (action: SetCharTCAction) => void +} +const CharTCContext = createContext({} as CharTCContexObj) + +export default function TabTheorycraft() { + const { database } = useContext(DatabaseContext) + const { data: oldData } = useContext(DataContext) + const { + character, + character: { key: characterKey, compareData }, + characterSheet, + characterDispatch, + } = useContext(CharacterContext) + const charTC = useCharTC( + characterKey, + defaultInitialWeaponKey(characterSheet.weaponTypeKey) + ) + const setCharTC = useCallback( + (data: SetCharTCAction) => { + database.charTCs.set(characterKey, data) + }, + [characterKey, database] + ) + const valueCharTCContext = useMemo( + () => ({ charTC, setCharTC }), + [charTC, setCharTC] + ) + const resetData = useCallback(() => { + setCharTC(initCharTC(defaultInitialWeaponKey(characterSheet.weaponTypeKey))) + }, [setCharTC, characterSheet]) + const setWeapon = useCallback( + (action: Partial) => { + setCharTC({ ...charTC, weapon: { ...charTC.weapon, ...action } }) + }, + [setCharTC, charTC] + ) + + const copyFrom = useCallback( + (eWeapon: ICachedWeapon, build: ICachedArtifact[]) => { + const newData = initCharTC(eWeapon.key) + newData.artifact.substats.type = charTC.artifact.substats.type + + newData.weapon.level = eWeapon.level + newData.weapon.ascension = eWeapon.ascension + newData.weapon.refinement = eWeapon.refinement + + const sets = {} + build.forEach((art) => { + if (!art) return + const { slotKey, setKey, substats, mainStatKey, level, rarity } = art + newData.artifact.slots[slotKey].level = level + newData.artifact.slots[slotKey].statKey = mainStatKey + newData.artifact.slots[slotKey].rarity = rarity + sets[setKey] = (sets[setKey] ?? 0) + 1 + substats.forEach((substat) => { + if (substat.key) + newData.artifact.substats.stats[substat.key] = + (newData.artifact.substats.stats[substat.key] ?? 0) + + substat.accurateValue + }) + }) + newData.artifact.sets = Object.fromEntries( + Object.entries(sets) + .map(([key, value]) => [ + key, + value === 3 + ? 2 + : value === 5 + ? 4 + : value === 1 && !(key as string).startsWith('PrayersFor') + ? 0 + : value, + ]) + .filter(([, value]) => value) + ) + setCharTC(newData) + }, + [charTC, setCharTC] + ) + const location = useLocation() + const { build: locBuild } = (location.state as + | { build: string[] } + | undefined) ?? { build: undefined } + useEffect(() => { + if (!locBuild) return + const eWeapon = database.weapons.get(character.equippedWeapon)! + copyFrom( + eWeapon, + locBuild.map((i) => database.arts.get(i)!) + ) + // WARNING: if copyFrom is included, it will cause a render loop due to its setData <---> data + // eslint-disable-next-line + }, [locBuild, database]) + + const copyFromEquipped = useCallback(() => { + const eWeapon = database.weapons.get(character.equippedWeapon)! + copyFrom( + eWeapon, + Object.values(character.equippedArtifacts) + .map((a) => database.arts.get(a)!) + .filter((a) => a) + ) + }, [ + database, + character.equippedArtifacts, + character.equippedWeapon, + copyFrom, + ]) + + const weapon: ICachedWeapon = useMemo( + () => ({ + ...charTC.weapon, + location: '', + lock: false, + id: '', + }), + [charTC] + ) + + const setArtifact = useCallback( + (artifact: ICharTC['artifact']) => { + const data_ = deepClone(charTC) + data_.artifact = artifact + setCharTC(data_) + }, + [charTC, setCharTC] + ) + + const setSubstatsType = useCallback( + (t: SubstatType) => { + const data_ = deepClone(charTC) + data_.artifact.substats.type = t + setCharTC(data_) + }, + [charTC, setCharTC] + ) + + const setSubstats = useCallback( + (setSubstats: Record) => { + const data_ = deepClone(charTC) + data_.artifact.substats.stats = setSubstats + setCharTC(data_) + }, + [charTC, setCharTC] + ) + + const deferredData = useDeferredValue(charTC) + const overriderArtData = useMemo(() => { + const stats = { ...deferredData.artifact.substats.stats } + Object.values(deferredData.artifact.slots).forEach( + ({ statKey, rarity, level }) => + (stats[statKey] = + (stats[statKey] ?? 0) + + Artifact.mainStatValue(statKey, rarity, level)) + ) + return { + art: objectMap(stats, (v, k) => + k.endsWith('_') ? percent(v / 100) : constant(v) + ), + artSet: objectMap(deferredData.artifact.sets, (v) => constant(v)), + } + }, [deferredData]) + + const overrideWeapon: ICachedWeapon = useMemo( + () => ({ + id: '', + location: '', + key: charTC.weapon.key, + level: charTC.weapon.level, + ascension: charTC.weapon.ascension, + refinement: charTC.weapon.refinement, + lock: false, + }), + [charTC] + ) + const teamData = useTeamData( + characterKey, + 0, + overriderArtData, + overrideWeapon + ) + + const { target: charUIData } = teamData?.[characterKey] ?? {} + + const dataContextValue: dataContextObj | undefined = useMemo(() => { + if (!teamData || !charUIData) return undefined + return { + data: charUIData, + teamData, + } + }, [charUIData, teamData]) + const dataContextValueWithOld: dataContextObj | undefined = useMemo(() => { + if (!dataContextValue) return undefined + return { + ...dataContextValue, + oldData: compareData ? oldData : undefined, + } + }, [dataContextValue, compareData, oldData]) + + const optimizationTarget = charTC.optimization.target + const setOptimizationTarget = useCallback( + (optimizationTarget: ICharTC['optimization']['target']) => { + const data_ = deepClone(charTC) + data_.optimization.target = optimizationTarget + setCharTC(data_) + }, + [charTC, setCharTC] + ) + + const distributedSubstats = charTC.optimization.distributedSubstats + const setDistributedSubstats = useCallback( + (distributedSubstats: ICharTC['optimization']['distributedSubstats']) => { + const data_ = deepClone(charTC) + data_.optimization.distributedSubstats = distributedSubstats + setCharTC(data_) + }, + [charTC, setCharTC] + ) + const maxSubstats = charTC.optimization.maxSubstats + + const { gender } = useDBMeta() + + // This solves + // $\argmax_{x\in N^k, \sum x <= n, x <= x_max} f(x)$ without assumptions on the properties of $f$ + // We brute force iterate over all substats in the graph and compute the maximum + // n.b. some substat combinations may not be materializable into real artifacts + const optimizeSubstats = useCallback( + (apply: boolean) => () => { + const startTime = performance.now() + if (!characterKey || !optimizationTarget) return + const teamData = getTeamData( + database, + characterKey, + 0, + overriderArtData, + overrideWeapon + ) + if (!teamData) return + const workerData = uiDataForTeam(teamData.teamData, gender, characterKey)[ + characterKey + ]?.target.data![0] + if (!workerData) return + Object.assign(workerData, mergeData([workerData, dynamicData])) // Mark art fields as dynamic + const unoptimizedOptimizationTargetNode = objPathValue( + workerData.display ?? {}, + optimizationTarget + ) as NumNode | undefined + if (!unoptimizedOptimizationTargetNode) return + const unoptimizedNodes = [unoptimizedOptimizationTargetNode] + let nodes = optimize( + unoptimizedNodes, + workerData, + ({ path: [p] }) => p !== 'dyn' + ) + // Const fold the artifact set + nodes = mapFormulas( + nodes, + (f) => { + if (f.operation === 'read' && f.path[0] === 'dyn') { + const a = charTC.artifact.sets[f.path[1]] + if (a) { + return constant(a) + } else if (allArtifactSetKeys.includes(f.path[1] as any)) { + return constant(0) + } + } + return f + }, + (f) => f + ) + nodes = optimize(nodes, {}, (_) => false) + + const subs = new Set() + const compute = precompute( + nodes, + {}, + (f) => { + subs.add(f.path[1]) + return f.path[1] + }, + 2 + ) + const realSubs = [...subs].filter((x) => + allSubstatKeys.includes(x as any) + ) + if ( + realSubs.reduce((p, x) => p + maxSubstats[x], 0) < distributedSubstats + ) + realSubs.push('__unused__') + const comp = (statKey: string) => (statKey.endsWith('_') ? 100 : 1) + + let max = -Infinity + const buffer = Object.fromEntries([...subs].map((x) => [x, 0])) + let maxBuffer: typeof buffer | undefined + const bufferSubs = objectMap( + charTC.artifact.substats.stats, + (v, k) => v / comp(k) + ) + const permute = (distributedSubstats: number, [x, ...xs]: string[]) => { + if (xs.length === 0) { + if (distributedSubstats > maxSubstats[x]) return + if (x !== '__unused__') + buffer[x] = + (Artifact.substatValue( + x as SubstatKey, + 5, + charTC.artifact.substats.type + ) / + comp(x)) * + distributedSubstats + const [result] = compute([ + { values: bufferSubs }, + { values: buffer }, + ] as const) + if (result > max) { + max = result + maxBuffer = structuredClone(buffer) + } + return + } + for ( + let i = 0; + i <= Math.min(maxSubstats[x], distributedSubstats); + i++ + ) { + buffer[x] = + (Artifact.substatValue( + x as SubstatKey, + 5, + charTC.artifact.substats.type + ) / + comp(x)) * + i + permute(distributedSubstats - i, xs) + } + } + permute(distributedSubstats, realSubs) + if (process.env.NODE_ENV === 'development') + console.log(`Took ${performance.now() - startTime} ms`) + console.log(maxBuffer) + console.log( + objectMap(maxBuffer!, (v, x) => + allSubstatKeys.includes(x as any) + ? v / + (Artifact.substatValue( + x as SubstatKey, + 5, + charTC.artifact.substats.type + ) / + comp(x)) + : v + ) + ) + + if (apply) { + const data_ = deepClone(charTC) + data_.artifact.substats.stats = objectMap( + charTC.artifact.substats.stats, + (v, k) => v + (maxBuffer![k] ?? 0) * comp(k) + ) + data_.optimization.distributedSubstats = 0 + setCharTC(data_) + } + }, + [ + charTC, + characterKey, + database, + distributedSubstats, + gender, + maxSubstats, + optimizationTarget, + overrideWeapon, + overriderArtData, + setCharTC, + ] + ) + + return ( + + + + + + + + + characterDispatch({ compareData: v })} + size="small" + > + + Show TC stats + + + Compare vs. equipped + + + + + {dataContextValue ? ( + + + + + + + + s.statKey + )} + distributedSubstats={distributedSubstats} + setDistributedSubstats={setDistributedSubstats} + maxSubstats={maxSubstats} + setMaxSubstats={(k: SubstatKey) => (v: number) => { + if (charTC.optimization.maxSubstats[k] === v) return + const data_ = deepClone(charTC) + data_.optimization.maxSubstats[k] = v + setCharTC(data_) + }} + /> + + + setOptimizationTarget(target)} + /> + {process.env.NODE_ENV === 'development' && ( + + )} + + + ) : ( + + )} + + {dataContextValueWithOld ? ( + + + + ) : ( + + )} + + + + ) +} + +function WeaponEditorCard({ + weapon, + setWeapon, + weaponTypeKey, +}: { + weapon: ICachedWeapon + weaponTypeKey: WeaponTypeKey + setWeapon: (action: Partial) => void +}) { + const { key, level = 0, refinement = 1, ascension = 0 } = weapon + const weaponSheet = getWeaponSheet(key) + const [show, onShow, onHide] = useBoolState() + const { data } = useContext(DataContext) + const weaponUIData = useMemo( + () => weapon && computeUIData([weaponSheet.data, dataObjForWeapon(weapon)]), + [weaponSheet, weapon] + ) + return ( + + setWeapon({ key: k })} + weaponTypeFilter={weaponTypeKey} + /> + + + = 2)} + sx={{ + flexshrink: 1, + flexBasis: 0, + maxWidth: '30%', + borderRadius: 1, + }} + /> + + + {weaponSheet.hasRefinement && ( + setWeapon({ refinement: r })} + /> + )} + + + + + + + {weaponUIData && ( + + {[input.weapon.main, input.weapon.sub, input.weapon.sub2].map( + (node) => { + const n = weaponUIData.get(node) + if (n.isEmpty || !n.value) return null + return ( + + ) + } + )} + + )} + + {data && weaponSheet?.document && ( + + )} + + + ) +} + +function ArtifactMainLevelCard({ + artifactData, + setArtifactData, +}: { + artifactData: ICharTC['artifact'] + setArtifactData: (a: ICharTC['artifact']) => void +}) { + const setSlot = useCallback( + (slotKey: ArtifactSlotKey) => (slot: ICharTCArtifactSlot) => { + const artifactData_ = deepClone(artifactData) + artifactData_.slots[slotKey] = slot + setArtifactData(artifactData_) + }, + [artifactData, setArtifactData] + ) + + const setArtSet = useCallback( + (artSet: ISet) => { + const artifactData_ = deepClone(artifactData) + artifactData_.sets = artSet + setArtifactData(artifactData_) + }, + [artifactData, setArtifactData] + ) + + return ( + + + + {allArtifactSlotKeys.map((s) => ( + + ))} + + + } + > + + + + ) +} +function ArtifactMainLevelSlot({ + slotKey, + slot, + setSlot: setSlotProp, +}: { + slotKey: ArtifactSlotKey + slot: ICharTCArtifactSlot + setSlot: (s: ICharTCArtifactSlot) => void +}) { + const { level, statKey, rarity } = slot + const keys = Artifact.slotMainStats(slotKey) + const setSlot = useCallback( + (action: Partial) => { + setSlotProp({ ...slot, ...action }) + }, + [slot, setSlotProp] + ) + const setRarity = useCallback( + (r: ArtifactRarity) => { + const mLvl = maxArtifactLevel[r] ?? 0 + if (level > mLvl) setSlot({ rarity: r, level: mLvl }) + else setSlot({ rarity: r }) + }, + [level, setSlot] + ) + + return ( + + + + {keys.length === 1 ? ( + + {' '} + {KeyMap.getStr(keys[0])} + + ) : ( + } + color={KeyMap.getVariant(statKey) ?? 'success'} + > + {keys.map((msk) => ( + setSlot({ statKey: msk })} + > + + + ))} + + )} + + + {rarity} + + } + > + {[5, 4, 3].map((r) => ( + setRarity(r as ArtifactRarity)} + > + + {r} + + + ))} + + l !== undefined && setSlot({ level: l })} + sx={{ borderRadius: 1, pl: 1, my: 0, height: '100%' }} + inputProps={{ sx: { pl: 0.5, width: '2em' }, max: 20, min: 0 }} + /> + + {`${cacheValueString( + Artifact.mainStatValue(statKey, rarity, level), + KeyMap.unit(statKey) + )}${KeyMap.unit(statKey)}`} + + + ) +} + +function ArtifactSetsEditor({ + artSet, + setArtSet, +}: { + artSet: ISet + setArtSet(artSet: ISet) +}) { + const setSet = useCallback( + (setKey: ArtifactSetKey | '') => { + if (!setKey) return + setArtSet({ + ...artSet, + [setKey]: parseInt(Object.keys(getArtSheet(setKey).setEffects)[0]), + }) + }, + [artSet, setArtSet] + ) + + const setValue = useCallback( + (setKey: ArtifactSetKey) => (value: 1 | 2 | 4) => + setArtSet({ ...artSet, [setKey]: value }), + [artSet, setArtSet] + ) + const deleteValue = useCallback( + (setKey: ArtifactSetKey) => () => { + const { [setKey]: _, ...rest } = artSet + setArtSet({ ...rest }) + }, + [artSet, setArtSet] + ) + + const remaining = 5 - Object.values(artSet).reduce((a, b) => a + b, 0) + + return ( + + {Object.entries(artSet).map(([setKey, value]) => ( + + ))} + + + Object.keys(artSet).includes(key as ArtifactSetKey) || + !key || + Object.keys(getArtSheet(key).setEffects).every( + (n) => parseInt(n) > remaining + ) + } + /> + + + ) +} +function ArtifactSetEditor({ + setKey, + value, + setValue, + deleteValue, + remaining, +}: { + setKey: ArtifactSetKey + value: 1 | 2 | 4 + setValue: (v: 1 | 2 | 4) => void + deleteValue: () => void + remaining: number +}) { + const artifactSheet = getArtSheet(setKey) + + /* Assumes that all conditionals are from 4-Set. needs to change if there are 2-Set conditionals */ + const set4CondNums = useMemo(() => { + if (value < 4) return [] + return Object.keys(artifactSheet.setEffects).filter((setNumKey) => + artifactSheet.setEffects[setNumKey]?.document.some( + (doc) => 'states' in doc + ) + ) + }, [artifactSheet, value]) + + return ( + + + + + + {artifactSheet.setName} + + + + + {value}-set} + > + {Object.keys(artifactSheet.setEffects) + .map((setKey) => parseInt(setKey)) + .map((setKey) => ( + remaining + value} + onClick={() => setValue(setKey as 1 | 2 | 4)} + > + {setKey}-set + + ))} + + + + + {!!set4CondNums.length && ( + + {set4CondNums.map((setNumKey) => ( + + ))} + + )} + + ) +} +function ArtifactSubCard({ + substats, + setSubstats, + substatsType, + setSubstatsType, + mainStatKeys, + distributedSubstats, + setDistributedSubstats, + maxSubstats, + setMaxSubstats, +}: { + substats: Record + setSubstats: (substats: Record) => void + substatsType: SubstatType + setSubstatsType: (t: SubstatType) => void + mainStatKeys: MainStatKey[] + distributedSubstats: number + setDistributedSubstats: (f: number) => void + maxSubstats: Record + setMaxSubstats: (k: SubstatKey) => (v: number) => void +}) { + const setValue = useCallback( + (key: SubstatKey) => (v: number) => setSubstats({ ...substats, [key]: v }), + [substats, setSubstats] + ) + const { t } = useTranslation('page_character') + const rv = + Object.entries(substats).reduce( + (t, [k, v]) => t + v / Artifact.substatValue(k), + 0 + ) * 100 + const rolls = Object.entries(substats).reduce( + (t, [k, v]) => t + v / Artifact.substatValue(k, undefined, substatsType), + 0 + ) + return ( + + + + {substatType.map((st) => ( + setSubstatsType(st)} + > + {t(`tabTheorycraft.substatType.${st}`)} + + ))} + + {t`tabTheorycraft.maxTotalRolls`}} + placement="top" + > + + 45 ? 'warning' : undefined}> + Rolls: {rolls.toFixed(0)} + + 45 ? 'warning' : undefined}> + RV: {rv.toFixed(1)}% + + + + v !== undefined && setDistributedSubstats(v)} + endAdornment={'Distributed Substats'} + sx={{ borderRadius: 1, px: 1, width: '50%' }} + inputProps={{ + sx: { textAlign: 'right', px: 1, width: '20%' }, + min: 0, + }} + /> + + + + {Object.entries(substats).map(([k, v]) => ( + + ))} + + + ) +} +function ArtifactSubstatEditor({ + statKey, + value, + setValue, + substatsType, + mainStatKeys, + maxSubstat, + setMaxSubstat, +}: { + statKey: SubstatKey + value: number + setValue: (v: number) => void + substatsType: SubstatType + mainStatKeys: MainStatKey[] + maxSubstat: number + setMaxSubstat: (v: number) => void +}) { + const substatValue = Artifact.substatValue(statKey, 5, substatsType) + const [rolls, setRolls] = useState(() => value / substatValue) + useEffect(() => setRolls(value / substatValue), [value, substatValue]) + + const unit = KeyMap.unit(statKey) + const displayValue = rolls * substatValue + + const rv = ((rolls * substatValue) / Artifact.substatValue(statKey)) * 100 + const numMains = mainStatKeys.reduce( + (t, ms) => t + (ms === statKey ? 1 : 0), + 0 + ) + const maxRolls = (5 - numMains) * 6 + // 0.0001 to nudge float comparasion + const invalid = rolls - 0.0001 > maxRolls + const setRValue = useCallback( + (r: number) => setValue(r * substatValue), + [setValue, substatValue] + ) + + return ( + + + + + {KeyMap.getStr(statKey)} + {KeyMap.unit(statKey)} + + {/* {t(numMains ? `tabTheorycraft.maxRollsMain` : `tabTheorycraft.maxRolls`, { value: maxRolls })}} placement="top"> */} + + + RV: {rv.toFixed(1)}% + + + {/* */} + + + + } + value={parseFloat(displayValue.toFixed(2))} + onChange={(v) => v !== undefined && setValue(v)} + sx={{ borderRadius: 1, px: 1, height: '100%', width: '6em' }} + inputProps={{ sx: { textAlign: 'right' }, min: 0 }} + /> + + setRolls(v as number)} + onChangeCommitted={(e, v) => setRValue(v as number)} + /> + + + + {cacheValueString(substatValue, unit)} + {unit} + + x + + } + value={parseFloat(rolls.toFixed(2))} + onChange={(v) => v !== undefined && setValue(v * substatValue)} + sx={{ borderRadius: 1, px: 1, my: 0, height: '100%', width: '7em' }} + inputProps={{ sx: { textAlign: 'right', pr: 0.5 }, min: 0, step: 1 }} + /> + v !== undefined && setMaxSubstat(v)} + color={maxSubstat > maxRolls ? 'warning' : 'success'} + sx={{ borderRadius: 1, px: 1, my: 0, height: '100%', width: '7em' }} + inputProps={{ sx: { textAlign: 'right', pr: 0.5 }, min: 0, step: 1 }} + /> + + + ) +} + +function ArtifactAllSubstatEditor() { + const [rolls, setRolls] = useState(undefined as undefined | number) + const [maxSubstat, setMaxSubstat] = useState(undefined as undefined | number) + const { setCharTC } = useContext(CharTCContext) + + const rollsDeferred = useDeferredValue(rolls) + useEffect(() => { + if (rollsDeferred === undefined) return + setCharTC((charTC) => { + const stats = charTC.artifact.substats.stats + const substatsType = charTC.artifact.substats.type + charTC.artifact.substats.stats = objectMap(stats, (val, statKey) => { + const substatValue = Artifact.substatValue(statKey, 5, substatsType) + return substatValue * rollsDeferred + }) + return charTC + }) + }, [setCharTC, rollsDeferred]) + + const maxSubstatDeferred = useDeferredValue(maxSubstat) + useEffect(() => { + if (maxSubstatDeferred === undefined) return + setCharTC((charTC) => { + charTC.optimization.maxSubstats = objectMap( + charTC.optimization.maxSubstats, + (_val, _statKey) => maxSubstatDeferred + ) + return charTC + }) + }, [setCharTC, maxSubstatDeferred]) + + const maxRolls = 30 + // 0.0001 to nudge float comparasion + const invalid = (rolls ?? 0 - 0.0001) > maxRolls + + return ( + + + Change all Substats + + + setRolls(v as number)} + onChangeCommitted={(e, v) => setRolls(v as number)} + /> + + All Rolls} + value={parseFloat((rolls ?? 0).toFixed(2))} + onChange={(v) => v !== undefined && setRolls(v)} + sx={{ borderRadius: 1, px: 1, my: 0, height: '100%', width: '7em' }} + inputProps={{ sx: { textAlign: 'right', pr: 0.5 }, min: 0, step: 1 }} + /> + All Max} + onChange={(v) => v !== undefined && setMaxSubstat(v)} + color={(maxSubstat ?? 0) > maxRolls ? 'warning' : 'success'} + sx={{ borderRadius: 1, px: 1, my: 0, height: '100%', width: '7em' }} + inputProps={{ sx: { textAlign: 'right', pr: 0.5 }, min: 0, step: 1 }} + /> + + ) +} diff --git a/apps/frontend/src/app/Types/character.d.ts b/apps/frontend/src/app/Types/character.d.ts index 92fa40a2f5..e37ff1c1ef 100644 --- a/apps/frontend/src/app/Types/character.d.ts +++ b/apps/frontend/src/app/Types/character.d.ts @@ -1,73 +1,95 @@ -import { InputPremodKey } from "../Formula"; -import { EleEnemyResKey } from "../KeyMap"; -import { MainStatKey, SubstatKey } from "./artifact"; -import { AdditiveReactionKey, AmpReactionKey, ArtifactRarity, ArtifactSetKey, Ascension, CharacterKey, HitModeKey, InfusionAuraElements, Refinement, SlotKey, SubstatType, WeaponKey } from "./consts"; -import { IConditionalValues } from "./sheet"; -export interface CustomTarget { - weight: number, - path: string[] - hitMode: HitModeKey, - reaction?: AmpReactionKey | AdditiveReactionKey, - infusionAura?: InfusionAuraElements, - bonusStats: Partial> -} -export interface CustomMultiTarget { - name: string, - description?: string, - targets: CustomTarget[] -} - -export interface ICharacter { - key: CharacterKey - level: number - constellation: number - ascension: Ascension - talent: { - auto: number - skill: number - burst: number - } - - // GO-specific - hitMode: HitModeKey - reaction?: AmpReactionKey | AdditiveReactionKey - conditional: IConditionalValues - bonusStats: Partial> - enemyOverride: Partial> - infusionAura: InfusionAuraElements | "" - compareData: boolean - customMultiTarget: CustomMultiTarget[] - team: [teammate1: CharacterKey | "", teammate2: CharacterKey | "", teammate3: CharacterKey | ""] - teamConditional: Partial> -} -export interface ICachedCharacter extends ICharacter { - equippedArtifacts: StrictDict - equippedWeapon: string -} - -export type ICharTCArtifactSlot = { - level: number, - statKey: MainStatKey, - rarity: ArtifactRarity -} -export type ICharTC = { - weapon: { - key: WeaponKey, - level: number, - ascension: Ascension, - refinement: Refinement, - }, - artifact: { - slots: Record - substats: { - type: SubstatType, - stats: Record - }, - sets: Partial> - } - optimization: { - target?: string[] - distributedSubstats: number - maxSubstats: Record - } -} +import type { InputPremodKey } from '../Formula' +import type { EleEnemyResKey } from '../KeyMap' +import type { MainStatKey, SubstatKey } from './artifact' +import type { + AdditiveReactionKey, + AmpReactionKey, + ArtifactRarity, + ArtifactSetKey, + Ascension, + CharacterKey, + HitModeKey, + InfusionAuraElements, + Refinement, + SlotKey, + SubstatType, + WeaponKey, +} from './consts' +import type { IConditionalValues } from './sheet' +export interface CustomTarget { + weight: number + path: string[] + hitMode: HitModeKey + reaction?: AmpReactionKey | AdditiveReactionKey + infusionAura?: InfusionAuraElements + bonusStats: Partial> +} +export interface CustomMultiTarget { + name: string + description?: string + targets: CustomTarget[] +} + +export interface ICharacter { + key: CharacterKey + level: number + constellation: number + ascension: Ascension + talent: { + auto: number + skill: number + burst: number + } + + // GO-specific + hitMode: HitModeKey + reaction?: AmpReactionKey | AdditiveReactionKey + conditional: IConditionalValues + bonusStats: Partial> + enemyOverride: Partial< + Record< + EleEnemyResKey | 'enemyLevel' | 'enemyDefRed_' | 'enemyDefIgn_', + number + > + > + infusionAura: InfusionAuraElements | '' + compareData: boolean + customMultiTarget: CustomMultiTarget[] + team: [ + teammate1: CharacterKey | '', + teammate2: CharacterKey | '', + teammate3: CharacterKey | '' + ] + teamConditional: Partial> +} +export interface ICachedCharacter extends ICharacter { + equippedArtifacts: StrictDict + equippedWeapon: string +} + +export type ICharTCArtifactSlot = { + level: number + statKey: MainStatKey + rarity: ArtifactRarity +} +export type ICharTC = { + weapon: { + key: WeaponKey + level: number + ascension: Ascension + refinement: Refinement + } + artifact: { + slots: Record + substats: { + type: SubstatType + stats: Record + } + sets: Partial> + } + optimization: { + target?: string[] + distributedSubstats: number + maxSubstats: Record + } +} diff --git a/apps/frontend/src/app/Util/Util.ts b/apps/frontend/src/app/Util/Util.ts index a2e04df632..4f74a38a7b 100644 --- a/apps/frontend/src/app/Util/Util.ts +++ b/apps/frontend/src/app/Util/Util.ts @@ -1,170 +1,211 @@ -export const getRandomElementFromArray = (array: readonly T[]): T => array[Math.floor(Math.random() * array.length)]; -export function getRandomInt(min, max) { - min = Math.ceil(min); - max = Math.floor(max); - return Math.floor(Math.random() * (max - min) + min); //The maximum is exclusive and the minimum is inclusive -} -export function getRandomIntInclusive(min, max) { - min = Math.ceil(min); - max = Math.floor(max); - return Math.floor(Math.random() * (max - min + 1) + min); //The maximum is inclusive and the minimum is inclusive -} -export function getRandomArbitrary(min, max) { - return Math.random() * (max - min) + min; -} - -/** - * Assumes that the object entries are all primitives + objects - * shallow copy the object, - * deep copy the - * @param obj - * @returns - */ -export function deepClone(obj: T): T { - if (!obj) return obj - if (!Object.keys(obj).length) return {} as T - const ret = { ...obj } - Object.entries(obj).forEach(([k, v]: any) => { - if (typeof v !== "object") return - ret[k] = JSON.parse(JSON.stringify(v)) - }) - return ret -} - -export const clamp = (val, low, high) => { - if (val < low) return low; - if (val > high) return high; - return val -} -export const getArrLastElement = (arr) => - arr.length ? arr[arr.length - 1] : null - -export const clamp01 = (val) => clamp(val, 0, 1) -export const clampPercent = (val) => clamp(val, 0, 100) - -//use to pretty print timestamps, or anything really. -export function strPadLeft(string, pad, length) { - return (new Array(length + 1).join(pad) + string).slice(-length); -} - -//fuzzy compare strings. longer the distance, the higher the mismatch. -export function hammingDistance(str1, str2) { - let dist = 0; - str1 = str1.toLowerCase(); - str2 = str2.toLowerCase(); - for (let i = 0, j = Math.max(str1.length, str2.length); i < j; i++) { - let match = true - if (!str1[i] || !str2[i] || str1[i] !== str2[i]) - match = false - if (str1[i - 1] === str2[i] || str1[i + 1] === str2[i]) - match = true - if (!match) dist++ - } - return dist; -} - -//multiplies every numerical value in the obj by a multiplier. -export function objMultiplication(obj, multi) { - if (multi === 1) return obj - Object.keys(obj).forEach((prop: any) => { - if (typeof obj[prop] === "object") objMultiplication(obj[prop], multi) - if (typeof obj[prop] === "number") obj[prop] = obj[prop] * multi - }) - return obj -} - -//assign obj.[keys...] = value -export function layeredAssignment(obj, keys: readonly string[], value) { - keys.reduce((accu, key, i, arr) => { - if (i === arr.length - 1) return (accu[key] = value) - if (!accu[key]) accu[key] = {} - return accu[key] - }, obj) - return obj -} -//get the value in a nested object, giving array of path -export function objPathValue(obj: object | undefined, keys: readonly string[]): any { - if (!obj || !keys) return undefined; - !Array.isArray(keys) && console.error(keys) - return keys.reduce((a, k) => a?.[k], obj) -} -//delete the value denoted by the path. Will also delete empty objects as well. -export function deletePropPath(obj, path) { - const tempPath = [...path] - const lastKey = tempPath.pop() - const objPathed = objPathValue(obj, tempPath) - delete objPathed?.[lastKey]; -} - -export function objClearEmpties(o) { - for (const k in o) { - if (typeof o[k] !== "object") continue - objClearEmpties(o[k]) - if (!Object.keys(o[k]).length) delete o[k]; - } -} -export function crawlObject(obj: any, keys: string[] = [], validate: (o: any, keys: string[]) => boolean, cb: (o: any, keys: string[]) => void) { - if (validate(obj, keys)) cb(obj, keys) - else obj && typeof obj === "object" && Object.entries(obj).forEach(([key, val]) => crawlObject(val, [...keys, key], validate, cb)) -} -// const getObjectKeysRecursive = (obj) => Object.values(obj).reduce((a, prop) => typeof prop === "object" ? [...a, ...getObjectKeysRecursive(prop)] : a, Object.keys(obj)) -export const getObjectKeysRecursive = (obj) => typeof obj === "object" ? Object.values(obj).flatMap(getObjectKeysRecursive).concat(Object.keys(obj)) : (typeof obj === "string" ? [obj] : []) - -export function evalIfFunc(value: T | ((arg: X) => T), arg: X): T { - return typeof value === "function" ? (value as any)(arg) : value -} -//fromEntries doesn't result in StrictDict, this is just a utility wrapper. -export function objectKeyMap(keys: readonly K[], map: (key: K, i: number) => V): StrictDict<`${K}`, V> { - return Object.fromEntries(keys.map((k, i) => [k, map(k, i)])) as any -} -//fromEntries doesn't result in StrictDict, this is just a utility wrapper. -export function objectKeyValueMap(items: readonly T[], map: (item: T, i: number) => [K, V]): StrictDict<`${K}`, V> { - return Object.fromEntries(items.map((t, i) => map(t, i))) as any -} - -export function objectMap(obj: Record>, fn: (value: V, key: `${K}`, index: number) => T): Record -export function objectMap(obj: Partial>, fn: (value: V, key: `${K}`, index: number) => T): Partial> -export function objectMap(obj: Partial>, fn: (value: V, key: `${K}`, index: number) => T): Partial> { - return Object.fromEntries(Object.entries(obj).map( - ([k, v], i) => [k, fn(v, k, i)] - )) as Partial> -} - -const rangeGen = function* (from: number, to: number): Iterable { - for (let i = from; i <= to; i++) yield i; -}; - -/** range of [from, to], inclusive */ -export function range(from: number, to: number): number[] { - return [...rangeGen(from, to)] -} - -export function assertUnreachable(value: never): never { - throw new Error(`Should not reach this with value ${value}`) -} - -/** Will change `arr` in-place */ -export function toggleInArr(arr: T[], value: T) { - const ind = arr.indexOf(value) - if (ind < 0) arr.push(value) - else arr.splice(ind, 1) - return arr -} - -export function toggleArr(arr: T[], value: T) { - return arr.includes(value) ? arr.filter(a => a !== value) : [...arr, value] -} - -export function deepFreeze(obj: T, layers = 5): T { - if (layers === 0) return obj - if (typeof obj === "object") - Object.values(Object.freeze(obj)).forEach(o => deepFreeze(o, layers--)) - return obj -} - -export function arrayMove(arr: T[], oldIndex: number, newIndex: number) { - if (newIndex < 0 || newIndex >= arr.length) return arr - if (oldIndex < 0 || oldIndex >= arr.length) return arr - arr.splice(newIndex, 0, arr.splice(oldIndex, 1)[0]); - return arr -} +export const getRandomElementFromArray = (array: readonly T[]): T => + array[Math.floor(Math.random() * array.length)] +export function getRandomInt(min, max) { + min = Math.ceil(min) + max = Math.floor(max) + return Math.floor(Math.random() * (max - min) + min) //The maximum is exclusive and the minimum is inclusive +} +export function getRandomIntInclusive(min, max) { + min = Math.ceil(min) + max = Math.floor(max) + return Math.floor(Math.random() * (max - min + 1) + min) //The maximum is inclusive and the minimum is inclusive +} +export function getRandomArbitrary(min, max) { + return Math.random() * (max - min) + min +} + +/** + * Assumes that the object entries are all primitives + objects + * shallow copy the object, + * deep copy the + * @param obj + * @returns + */ +export function deepClone(obj: T): T { + if (!obj) return obj + if (!Object.keys(obj).length) return {} as T + const ret = { ...obj } + Object.entries(obj).forEach(([k, v]: any) => { + if (typeof v !== 'object') return + ret[k] = JSON.parse(JSON.stringify(v)) + }) + return ret +} + +export const clamp = (val, low, high) => { + if (val < low) return low + if (val > high) return high + return val +} +export const getArrLastElement = (arr) => + arr.length ? arr[arr.length - 1] : null + +export const clamp01 = (val) => clamp(val, 0, 1) +export const clampPercent = (val) => clamp(val, 0, 100) + +//use to pretty print timestamps, or anything really. +export function strPadLeft(string, pad, length) { + return (new Array(length + 1).join(pad) + string).slice(-length) +} + +//fuzzy compare strings. longer the distance, the higher the mismatch. +export function hammingDistance(str1, str2) { + let dist = 0 + str1 = str1.toLowerCase() + str2 = str2.toLowerCase() + for (let i = 0, j = Math.max(str1.length, str2.length); i < j; i++) { + let match = true + if (!str1[i] || !str2[i] || str1[i] !== str2[i]) match = false + if (str1[i - 1] === str2[i] || str1[i + 1] === str2[i]) match = true + if (!match) dist++ + } + return dist +} + +//multiplies every numerical value in the obj by a multiplier. +export function objMultiplication(obj, multi) { + if (multi === 1) return obj + Object.keys(obj).forEach((prop: any) => { + if (typeof obj[prop] === 'object') objMultiplication(obj[prop], multi) + if (typeof obj[prop] === 'number') obj[prop] = obj[prop] * multi + }) + return obj +} + +//assign obj.[keys...] = value +export function layeredAssignment(obj, keys: readonly string[], value) { + keys.reduce((accu, key, i, arr) => { + if (i === arr.length - 1) return (accu[key] = value) + if (!accu[key]) accu[key] = {} + return accu[key] + }, obj) + return obj +} +//get the value in a nested object, giving array of path +export function objPathValue( + obj: object | undefined, + keys: readonly string[] +): any { + if (!obj || !keys) return undefined + !Array.isArray(keys) && console.error(keys) + return keys.reduce((a, k) => a?.[k], obj) +} +//delete the value denoted by the path. Will also delete empty objects as well. +export function deletePropPath(obj, path) { + const tempPath = [...path] + const lastKey = tempPath.pop() + const objPathed = objPathValue(obj, tempPath) + delete objPathed?.[lastKey] +} + +export function objClearEmpties(o) { + for (const k in o) { + if (typeof o[k] !== 'object') continue + objClearEmpties(o[k]) + if (!Object.keys(o[k]).length) delete o[k] + } +} +export function crawlObject( + obj: any, + keys: string[] = [], + validate: (o: any, keys: string[]) => boolean, + cb: (o: any, keys: string[]) => void +) { + if (validate(obj, keys)) cb(obj, keys) + else + obj && + typeof obj === 'object' && + Object.entries(obj).forEach(([key, val]) => + crawlObject(val, [...keys, key], validate, cb) + ) +} +// const getObjectKeysRecursive = (obj) => Object.values(obj).reduce((a, prop) => typeof prop === "object" ? [...a, ...getObjectKeysRecursive(prop)] : a, Object.keys(obj)) +export const getObjectKeysRecursive = (obj) => + typeof obj === 'object' + ? Object.values(obj) + .flatMap(getObjectKeysRecursive) + .concat(Object.keys(obj)) + : typeof obj === 'string' + ? [obj] + : [] + +export function evalIfFunc(value: T | ((arg: X) => T), arg: X): T { + return typeof value === 'function' ? (value as any)(arg) : value +} +//fromEntries doesn't result in StrictDict, this is just a utility wrapper. +export function objectKeyMap( + keys: readonly K[], + map: (key: K, i: number) => V +): StrictDict<`${K}`, V> { + return Object.fromEntries(keys.map((k, i) => [k, map(k, i)])) as any +} +//fromEntries doesn't result in StrictDict, this is just a utility wrapper. +export function objectKeyValueMap( + items: readonly T[], + map: (item: T, i: number) => [K, V] +): StrictDict<`${K}`, V> { + return Object.fromEntries(items.map((t, i) => map(t, i))) as any +} + +export function objectMap( + obj: Record>, + fn: (value: V, key: `${K}`, index: number) => T +): Record +export function objectMap( + obj: Partial>, + fn: (value: V, key: `${K}`, index: number) => T +): Partial> +export function objectMap( + obj: Partial>, + fn: (value: V, key: `${K}`, index: number) => T +): Partial> { + return Object.fromEntries( + Object.entries(obj).map(([k, v], i) => [k, fn(v, k, i)]) + ) as Partial> +} + +const rangeGen = function* (from: number, to: number): Iterable { + for (let i = from; i <= to; i++) yield i +} + +/** range of [from, to], inclusive */ +export function range(from: number, to: number): number[] { + return [...rangeGen(from, to)] +} + +export function assertUnreachable(value: never): never { + throw new Error(`Should not reach this with value ${value}`) +} + +// cartesian product of list of arrays +export function cartesian(...q: T[][]): T[][] { + return q.reduce((a, b) => a.flatMap((d) => b.map((e) => [d, [e]].flat())), [ + [], + ] as T[][]) +} + +/** Will change `arr` in-place */ +export function toggleInArr(arr: T[], value: T) { + const ind = arr.indexOf(value) + if (ind < 0) arr.push(value) + else arr.splice(ind, 1) + return arr +} + +export function toggleArr(arr: T[], value: T) { + return arr.includes(value) ? arr.filter((a) => a !== value) : [...arr, value] +} + +export function deepFreeze(obj: T, layers = 5): T { + if (layers === 0) return obj + if (typeof obj === 'object') + Object.values(Object.freeze(obj)).forEach((o) => deepFreeze(o, layers--)) + return obj +} + +export function arrayMove(arr: T[], oldIndex: number, newIndex: number) { + if (newIndex < 0 || newIndex >= arr.length) return arr + if (oldIndex < 0 || oldIndex >= arr.length) return arr + arr.splice(newIndex, 0, arr.splice(oldIndex, 1)[0]) + return arr +} From edd363a18fa35cb7c5d1b35480c913383bdb9e03 Mon Sep 17 00:00:00 2001 From: eeeqeee <103794572+eeeqeee@users.noreply.github.com> Date: Sun, 18 Jun 2023 17:15:07 -0400 Subject: [PATCH 13/61] guard printing --- .../Tabs/TabTheorycraft/index.tsx | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx index 1d92c1be7b..fff8abe919 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx @@ -1,10 +1,11 @@ import type { ArtifactSetKey, ArtifactSlotKey, - WeaponTypeKey} from '@genshin-optimizer/consts'; + WeaponTypeKey, +} from '@genshin-optimizer/consts' import { allArtifactSetKeys, - allArtifactSlotKeys + allArtifactSlotKeys, } from '@genshin-optimizer/consts' import { weaponAsset } from '@genshin-optimizer/g-assets' import { CopyAll, DeleteForever, Info, Refresh } from '@mui/icons-material' @@ -61,7 +62,7 @@ import { StatWithUnit, } from '../../../../Components/StatDisplay' import { CharacterContext } from '../../../../Context/CharacterContext' -import type { dataContextObj } from '../../../../Context/DataContext'; +import type { dataContextObj } from '../../../../Context/DataContext' import { DataContext } from '../../../../Context/DataContext' import { getArtSheet } from '../../../../Data/Artifacts' import Artifact, { maxArtifactLevel } from '../../../../Data/Artifacts/Artifact' @@ -88,18 +89,16 @@ import { iconInlineProps } from '../../../../SVGIcons' import type { ICachedArtifact, MainStatKey, - SubstatKey} from '../../../../Types/artifact'; -import { - allSubstatKeys + SubstatKey, } from '../../../../Types/artifact' +import { allSubstatKeys } from '../../../../Types/artifact' import type { ICharTC, ICharTCArtifactSlot } from '../../../../Types/character' import type { ArtifactRarity, SetNum, - SubstatType} from '../../../../Types/consts'; -import { - substatType, + SubstatType, } from '../../../../Types/consts' +import { substatType } from '../../../../Types/consts' import type { ICachedWeapon } from '../../../../Types/weapon' import { deepClone, objectMap, objPathValue } from '../../../../Util/Util' import { defaultInitialWeaponKey } from '../../../../Util/WeaponUtil' @@ -452,22 +451,23 @@ export default function TabTheorycraft() { } } permute(distributedSubstats, realSubs) - if (process.env.NODE_ENV === 'development') + if (process.env.NODE_ENV === 'development') { console.log(`Took ${performance.now() - startTime} ms`) - console.log(maxBuffer) - console.log( - objectMap(maxBuffer!, (v, x) => - allSubstatKeys.includes(x as any) - ? v / - (Artifact.substatValue( - x as SubstatKey, - 5, - charTC.artifact.substats.type - ) / - comp(x)) - : v + console.log(maxBuffer) + console.log( + objectMap(maxBuffer!, (v, x) => + allSubstatKeys.includes(x as any) + ? v / + (Artifact.substatValue( + x as SubstatKey, + 5, + charTC.artifact.substats.type + ) / + comp(x)) + : v + ) ) - ) + } if (apply) { const data_ = deepClone(charTC) From 0f47810edc0e7c0129edda75a294611fa1349f65 Mon Sep 17 00:00:00 2001 From: eeeqeee <103794572+eeeqeee@users.noreply.github.com> Date: Sun, 18 Jun 2023 17:35:31 -0400 Subject: [PATCH 14/61] fmt --- .../Database/DataManagers/CharacterTCData.ts | 10 ++++------ .../src/app/Formula/optimization.test.ts | 18 ++++++++++-------- apps/frontend/src/app/Formula/optimization.ts | 8 ++++---- apps/frontend/src/app/Util/Util.ts | 8 ++++---- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/apps/frontend/src/app/Database/DataManagers/CharacterTCData.ts b/apps/frontend/src/app/Database/DataManagers/CharacterTCData.ts index 47db37e844..16b4cbfa22 100644 --- a/apps/frontend/src/app/Database/DataManagers/CharacterTCData.ts +++ b/apps/frontend/src/app/Database/DataManagers/CharacterTCData.ts @@ -1,16 +1,14 @@ import type { ArtifactSlotKey, CharacterKey, - WeaponKey} from '@genshin-optimizer/consts'; -import { - allArtifactSlotKeys, - allWeaponKeys + WeaponKey, } from '@genshin-optimizer/consts' +import { allArtifactSlotKeys, allWeaponKeys } from '@genshin-optimizer/consts' import { validateLevelAsc } from '../../Data/LevelData' -import type { MainStatKey } from '../../Types/artifact'; +import type { MainStatKey } from '../../Types/artifact' import { allSubstatKeys } from '../../Types/artifact' import type { ICharTC } from '../../Types/character' -import type { ArtifactRarity} from '../../Types/consts'; +import type { ArtifactRarity } from '../../Types/consts' import { substatType } from '../../Types/consts' import { objectKeyMap } from '../../Util/Util' import type { ArtCharDatabase } from '../Database' diff --git a/apps/frontend/src/app/Formula/optimization.test.ts b/apps/frontend/src/app/Formula/optimization.test.ts index c8e1fd1f20..69b47e6bee 100644 --- a/apps/frontend/src/app/Formula/optimization.test.ts +++ b/apps/frontend/src/app/Formula/optimization.test.ts @@ -129,7 +129,7 @@ describe('optimization', () => { r3 = inputs[2] const output1 = sum(1, r1, r2), output2 = prod(r2, r3), - output3 = sum(output1, output2) + _output3 = sum(output1, output2) const compute = precompute( [output1] as OptNode[], @@ -137,9 +137,9 @@ describe('optimization', () => { (x) => x.path[1], 1 ) - expect([...compute([{ values: { 0: 32, 1: 77 } }] as const).slice(0, 1)]).toEqual([ - 1 + 32 + 77, - ]) + expect([ + ...compute([{ values: { 0: 32, 1: 77 } }] as const).slice(0, 1), + ]).toEqual([1 + 32 + 77]) }) test('Output is read node', () => { const r1 = inputs[0], @@ -147,10 +147,12 @@ describe('optimization', () => { r3 = inputs[2] const output1 = sum(1, r1, r2), output2 = prod(r2, r3), - output3 = sum(output1, output2) + _output3 = sum(output1, output2) - const compute = precompute([r1], {}, (x) => x.path[0], 1) - expect([...compute([{ values: { 0: 32 } }] as const).slice(0, 1)]).toEqual([32]) + const compute = precompute([r1], {}, (x) => x.path[1], 1) + expect([ + ...compute([{ values: { 0: 32 } }] as const).slice(0, 1), + ]).toEqual([32]) }) test('Output is constant node', () => { const r1 = inputs[0], @@ -158,7 +160,7 @@ describe('optimization', () => { r3 = inputs[2] const output1 = sum(1, r1, r2), output2 = prod(r2, r3), - output3 = sum(output1, output2) + _output3 = sum(output1, output2) const compute = precompute([constant(35)], {}, (x) => x.path[0], 0) expect([...compute([] as const).slice(0, 1)]).toEqual([35]) diff --git a/apps/frontend/src/app/Formula/optimization.ts b/apps/frontend/src/app/Formula/optimization.ts index 90ca5d3603..27f70d35fd 100644 --- a/apps/frontend/src/app/Formula/optimization.ts +++ b/apps/frontend/src/app/Formula/optimization.ts @@ -105,8 +105,8 @@ const x0=0` // making sure `const` has at least one entry const key = binding(f) let arr = slotCount ? new Array(slotCount) - .fill(null) - .map((_, i) => `(b[${i}].values["${key}"] ?? 0)`) + .fill(null) + .map((_, i) => `(b[${i}].values["${key}"] ?? 0)`) : ['0'] if (initial[key] && initial[key] !== 0) { arr = [initial[key].toString(), ...arr] @@ -487,8 +487,8 @@ export function constantFold( else result = map( { operation: formula.accu, operands } as - | ComputeNode - | StrPrioNode, + | ComputeNode + | StrPrioNode, context ) break diff --git a/apps/frontend/src/app/Util/Util.ts b/apps/frontend/src/app/Util/Util.ts index 2d49d45988..784cb056ed 100644 --- a/apps/frontend/src/app/Util/Util.ts +++ b/apps/frontend/src/app/Util/Util.ts @@ -123,11 +123,11 @@ export function crawlObject( export const getObjectKeysRecursive = (obj) => typeof obj === 'object' ? Object.values(obj) - .flatMap(getObjectKeysRecursive) - .concat(Object.keys(obj)) + .flatMap(getObjectKeysRecursive) + .concat(Object.keys(obj)) : typeof obj === 'string' - ? [obj] - : [] + ? [obj] + : [] export function evalIfFunc(value: T | ((arg: X) => T), arg: X): T { return typeof value === 'function' ? (value as any)(arg) : value From d34cafefb812cad2b63a44edbfa853a493f4aba4 Mon Sep 17 00:00:00 2001 From: eeeqeee <103794572+eeeqeee@users.noreply.github.com> Date: Sun, 18 Jun 2023 18:11:47 -0400 Subject: [PATCH 15/61] extract to function --- .../Tabs/TabTheorycraft/index.tsx | 174 ++---------------- .../Tabs/TabTheorycraft/optimizeTc.tsx | 166 +++++++++++++++++ 2 files changed, 186 insertions(+), 154 deletions(-) create mode 100644 apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.tsx diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx index fff8abe919..0182d8afec 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx @@ -3,10 +3,7 @@ import type { ArtifactSlotKey, WeaponTypeKey, } from '@genshin-optimizer/consts' -import { - allArtifactSetKeys, - allArtifactSlotKeys, -} from '@genshin-optimizer/consts' +import { allArtifactSlotKeys } from '@genshin-optimizer/consts' import { weaponAsset } from '@genshin-optimizer/g-assets' import { CopyAll, DeleteForever, Info, Refresh } from '@mui/icons-material' import StarRoundedIcon from '@mui/icons-material/StarRounded' @@ -71,27 +68,18 @@ import { getWeaponSheet } from '../../../../Data/Weapons' import { DatabaseContext } from '../../../../Database/Database' import { initCharTC } from '../../../../Database/DataManagers/CharacterTCData' import { uiInput as input } from '../../../../Formula' -import { - computeUIData, - dataObjForWeapon, - mergeData, - uiDataForTeam, -} from '../../../../Formula/api' -import { mapFormulas } from '../../../../Formula/internal' -import { optimize, precompute } from '../../../../Formula/optimization' -import type { NumNode } from '../../../../Formula/type' +import { computeUIData, dataObjForWeapon } from '../../../../Formula/api' import { constant, percent } from '../../../../Formula/utils' import KeyMap, { cacheValueString } from '../../../../KeyMap' import StatIcon from '../../../../KeyMap/StatIcon' import useBoolState from '../../../../ReactHooks/useBoolState' -import useTeamData, { getTeamData } from '../../../../ReactHooks/useTeamData' +import useTeamData from '../../../../ReactHooks/useTeamData' import { iconInlineProps } from '../../../../SVGIcons' import type { ICachedArtifact, MainStatKey, SubstatKey, } from '../../../../Types/artifact' -import { allSubstatKeys } from '../../../../Types/artifact' import type { ICharTC, ICharTCArtifactSlot } from '../../../../Types/character' import type { ArtifactRarity, @@ -100,18 +88,20 @@ import type { } from '../../../../Types/consts' import { substatType } from '../../../../Types/consts' import type { ICachedWeapon } from '../../../../Types/weapon' -import { deepClone, objectMap, objPathValue } from '../../../../Util/Util' +import { deepClone, objectMap } from '../../../../Util/Util' import { defaultInitialWeaponKey } from '../../../../Util/WeaponUtil' import OptimizationTargetSelector from '../TabOptimize/Components/OptimizationTargetSelector' -import { dynamicData } from '../TabOptimize/foreground' import useCharTC from './useCharTC' import useDBMeta from '../../../../ReactHooks/useDBMeta' +import { optimizeTc } from './optimizeTc' const WeaponSelectionModal = React.lazy( () => import('../../../../Components/Weapon/WeaponSelectionModal') ) type ISet = Partial> -type SetCharTCAction = Partial | ((v: ICharTC) => Partial) +export type SetCharTCAction = + | Partial + | ((v: ICharTC) => Partial) type CharTCContexObj = { charTC: ICharTC setCharTC: (action: SetCharTCAction) => void @@ -341,144 +331,20 @@ export default function TabTheorycraft() { // We brute force iterate over all substats in the graph and compute the maximum // n.b. some substat combinations may not be materializable into real artifacts const optimizeSubstats = useCallback( - (apply: boolean) => () => { - const startTime = performance.now() - if (!characterKey || !optimizationTarget) return - const teamData = getTeamData( - database, + (apply: boolean) => + optimizeTc( characterKey, - 0, + optimizationTarget, + database, overriderArtData, - overrideWeapon - ) - if (!teamData) return - const workerData = uiDataForTeam(teamData.teamData, gender, characterKey)[ - characterKey - ]?.target.data![0] - if (!workerData) return - Object.assign(workerData, mergeData([workerData, dynamicData])) // Mark art fields as dynamic - const unoptimizedOptimizationTargetNode = objPathValue( - workerData.display ?? {}, - optimizationTarget - ) as NumNode | undefined - if (!unoptimizedOptimizationTargetNode) return - const unoptimizedNodes = [unoptimizedOptimizationTargetNode] - let nodes = optimize( - unoptimizedNodes, - workerData, - ({ path: [p] }) => p !== 'dyn' - ) - // Const fold the artifact set - nodes = mapFormulas( - nodes, - (f) => { - if (f.operation === 'read' && f.path[0] === 'dyn') { - const a = charTC.artifact.sets[f.path[1]] - if (a) { - return constant(a) - } else if (allArtifactSetKeys.includes(f.path[1] as any)) { - return constant(0) - } - } - return f - }, - (f) => f - ) - nodes = optimize(nodes, {}, (_) => false) - - const subs = new Set() - const compute = precompute( - nodes, - {}, - (f) => { - subs.add(f.path[1]) - return f.path[1] - }, - 2 - ) - const realSubs = [...subs].filter((x) => - allSubstatKeys.includes(x as any) - ) - if ( - realSubs.reduce((p, x) => p + maxSubstats[x], 0) < distributedSubstats - ) - realSubs.push('__unused__') - const comp = (statKey: string) => (statKey.endsWith('_') ? 100 : 1) - - let max = -Infinity - const buffer = Object.fromEntries([...subs].map((x) => [x, 0])) - let maxBuffer: typeof buffer | undefined - const bufferSubs = objectMap( - charTC.artifact.substats.stats, - (v, k) => v / comp(k) - ) - const permute = (distributedSubstats: number, [x, ...xs]: string[]) => { - if (xs.length === 0) { - if (distributedSubstats > maxSubstats[x]) return - if (x !== '__unused__') - buffer[x] = - (Artifact.substatValue( - x as SubstatKey, - 5, - charTC.artifact.substats.type - ) / - comp(x)) * - distributedSubstats - const [result] = compute([ - { values: bufferSubs }, - { values: buffer }, - ] as const) - if (result > max) { - max = result - maxBuffer = structuredClone(buffer) - } - return - } - for ( - let i = 0; - i <= Math.min(maxSubstats[x], distributedSubstats); - i++ - ) { - buffer[x] = - (Artifact.substatValue( - x as SubstatKey, - 5, - charTC.artifact.substats.type - ) / - comp(x)) * - i - permute(distributedSubstats - i, xs) - } - } - permute(distributedSubstats, realSubs) - if (process.env.NODE_ENV === 'development') { - console.log(`Took ${performance.now() - startTime} ms`) - console.log(maxBuffer) - console.log( - objectMap(maxBuffer!, (v, x) => - allSubstatKeys.includes(x as any) - ? v / - (Artifact.substatValue( - x as SubstatKey, - 5, - charTC.artifact.substats.type - ) / - comp(x)) - : v - ) - ) - } - - if (apply) { - const data_ = deepClone(charTC) - data_.artifact.substats.stats = objectMap( - charTC.artifact.substats.stats, - (v, k) => v + (maxBuffer![k] ?? 0) * comp(k) - ) - data_.optimization.distributedSubstats = 0 - setCharTC(data_) - } - }, + overrideWeapon, + gender, + charTC, + maxSubstats, + distributedSubstats, + apply, + setCharTC + ), [ charTC, characterKey, diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.tsx new file mode 100644 index 0000000000..1be4003afe --- /dev/null +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.tsx @@ -0,0 +1,166 @@ +import type { ArtifactSetKey, CharacterKey } from '@genshin-optimizer/consts' +import { allArtifactSetKeys } from '@genshin-optimizer/consts' +import Artifact from '../../../../Data/Artifacts/Artifact' +import type { ArtCharDatabase } from '../../../../Database/Database' +import { mergeData, uiDataForTeam } from '../../../../Formula/api' +import { mapFormulas } from '../../../../Formula/internal' +import { optimize, precompute } from '../../../../Formula/optimization' +import type { NumNode } from '../../../../Formula/type' +import { constant } from '../../../../Formula/utils' +import { getTeamData } from '../../../../ReactHooks/useTeamData' +import type { SubstatKey } from '../../../../Types/artifact' +import { allSubstatKeys } from '../../../../Types/artifact' +import type { ICharTC } from '../../../../Types/character' +import type { Gender } from '../../../../Types/consts' +import type { ICachedWeapon } from '../../../../Types/weapon' +import { deepClone, objectMap, objPathValue } from '../../../../Util/Util' +import { dynamicData } from '../TabOptimize/foreground' +import type { SetCharTCAction } from '.' + +export function optimizeTc( + characterKey: CharacterKey, + optimizationTarget: string[] | undefined, + database: ArtCharDatabase, + overriderArtData: { + art: Record + artSet: Partial> + }, + overrideWeapon: ICachedWeapon, + gender: Gender, + charTC: ICharTC, + maxSubstats: Record, + distributedSubstats: number, + apply: boolean, + setCharTC: (data: SetCharTCAction) => void +): () => void { + return () => { + const startTime = performance.now() + if (!characterKey || !optimizationTarget) return + const teamData = getTeamData( + database, + characterKey, + 0, + overriderArtData, + overrideWeapon + ) + if (!teamData) return + const workerData = uiDataForTeam(teamData.teamData, gender, characterKey)[ + characterKey + ]?.target.data![0] + if (!workerData) return + Object.assign(workerData, mergeData([workerData, dynamicData])) // Mark art fields as dynamic + const unoptimizedOptimizationTargetNode = objPathValue( + workerData.display ?? {}, + optimizationTarget + ) as NumNode | undefined + if (!unoptimizedOptimizationTargetNode) return + const unoptimizedNodes = [unoptimizedOptimizationTargetNode] + let nodes = optimize( + unoptimizedNodes, + workerData, + ({ path: [p] }) => p !== 'dyn' + ) + // Const fold the artifact set + nodes = mapFormulas( + nodes, + (f) => { + if (f.operation === 'read' && f.path[0] === 'dyn') { + const a = charTC.artifact.sets[f.path[1]] + if (a) { + return constant(a) + } else if (allArtifactSetKeys.includes(f.path[1] as any)) { + return constant(0) + } + } + return f + }, + (f) => f + ) + nodes = optimize(nodes, {}, (_) => false) + + const subs = new Set() + const compute = precompute( + nodes, + {}, + (f) => { + subs.add(f.path[1]) + return f.path[1] + }, + 2 + ) + const realSubs = [...subs].filter((x) => allSubstatKeys.includes(x as any)) + if (realSubs.reduce((p, x) => p + maxSubstats[x], 0) < distributedSubstats) + realSubs.push('__unused__') + const comp = (statKey: string) => (statKey.endsWith('_') ? 100 : 1) + + let max = -Infinity + const buffer = Object.fromEntries([...subs].map((x) => [x, 0])) + let maxBuffer: typeof buffer | undefined + const bufferSubs = objectMap( + charTC.artifact.substats.stats, + (v, k) => v / comp(k) + ) + const permute = (distributedSubstats: number, [x, ...xs]: string[]) => { + if (xs.length === 0) { + if (distributedSubstats > maxSubstats[x]) return + if (x !== '__unused__') + buffer[x] = + (Artifact.substatValue( + x as SubstatKey, + 5, + charTC.artifact.substats.type + ) / + comp(x)) * + distributedSubstats + const [result] = compute([ + { values: bufferSubs }, + { values: buffer }, + ] as const) + if (result > max) { + max = result + maxBuffer = structuredClone(buffer) + } + return + } + for (let i = 0; i <= Math.min(maxSubstats[x], distributedSubstats); i++) { + buffer[x] = + (Artifact.substatValue( + x as SubstatKey, + 5, + charTC.artifact.substats.type + ) / + comp(x)) * + i + permute(distributedSubstats - i, xs) + } + } + permute(distributedSubstats, realSubs) + if (process.env.NODE_ENV === 'development') { + console.log(`Took ${performance.now() - startTime} ms`) + console.log(maxBuffer) + console.log( + objectMap(maxBuffer!, (v, x) => + allSubstatKeys.includes(x as any) + ? v / + (Artifact.substatValue( + x as SubstatKey, + 5, + charTC.artifact.substats.type + ) / + comp(x)) + : v + ) + ) + } + + if (apply) { + const data_ = deepClone(charTC) + data_.artifact.substats.stats = objectMap( + charTC.artifact.substats.stats, + (v, k) => v + (maxBuffer![k] ?? 0) * comp(k) + ) + data_.optimization.distributedSubstats = 0 + setCharTC(data_) + } + } +} From 1674edac818efcc9e209e721f9a04df81b6fa10e Mon Sep 17 00:00:00 2001 From: eeeqeee <103794572+eeeqeee@users.noreply.github.com> Date: Wed, 21 Jun 2023 20:59:02 -0400 Subject: [PATCH 16/61] hardcode answer if more distributedSubs than max --- .../Tabs/TabTheorycraft/optimizeTc.tsx | 122 +++++++++--------- 1 file changed, 63 insertions(+), 59 deletions(-) diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.tsx index 1be4003afe..3dbb048422 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.tsx @@ -35,7 +35,7 @@ export function optimizeTc( ): () => void { return () => { const startTime = performance.now() - if (!characterKey || !optimizationTarget) return + if (!optimizationTarget) return const teamData = getTeamData( database, characterKey, @@ -88,69 +88,73 @@ export function optimizeTc( }, 2 ) - const realSubs = [...subs].filter((x) => allSubstatKeys.includes(x as any)) - if (realSubs.reduce((p, x) => p + maxSubstats[x], 0) < distributedSubstats) - realSubs.push('__unused__') + const comp = (statKey: string) => (statKey.endsWith('_') ? 100 : 1) + const substatValue = (x: string, m: number) => + (Artifact.substatValue( + x as SubstatKey, + 5, + charTC.artifact.substats.type + ) / + comp(x)) * + m - let max = -Infinity - const buffer = Object.fromEntries([...subs].map((x) => [x, 0])) - let maxBuffer: typeof buffer | undefined - const bufferSubs = objectMap( - charTC.artifact.substats.stats, - (v, k) => v / comp(k) - ) - const permute = (distributedSubstats: number, [x, ...xs]: string[]) => { - if (xs.length === 0) { - if (distributedSubstats > maxSubstats[x]) return - if (x !== '__unused__') - buffer[x] = - (Artifact.substatValue( - x as SubstatKey, - 5, - charTC.artifact.substats.type - ) / - comp(x)) * - distributedSubstats - const [result] = compute([ - { values: bufferSubs }, - { values: buffer }, - ] as const) - if (result > max) { - max = result - maxBuffer = structuredClone(buffer) + let maxBuffer: Record | undefined + const realSubs = [...subs].filter((x) => allSubstatKeys.includes(x as any)) + if ( + realSubs.reduce((a, x) => a + maxSubstats[x], 0) <= distributedSubstats + ) { + maxBuffer = Object.fromEntries( + realSubs.map((x) => [x, substatValue(x, maxSubstats[x])]) + ) + if (process.env.NODE_ENV === 'development') console.log(maxBuffer) + } else { + let max = -Infinity + const buffer = Object.fromEntries([...subs].map((x) => [x, 0])) + const bufferSubs = objectMap( + charTC.artifact.substats.stats, + (v, k) => v / comp(k) + ) + const permute = (distributedSubstats: number, [x, ...xs]: string[]) => { + if (xs.length === 0) { + if (distributedSubstats > maxSubstats[x]) return + const [result] = compute([ + { values: bufferSubs }, + { values: buffer }, + ] as const) + if (result > max) { + max = result + maxBuffer = structuredClone(buffer) + } + return + } + for ( + let i = 0; + i <= Math.min(maxSubstats[x], distributedSubstats); + i++ + ) { + buffer[x] = substatValue(x, i) + permute(distributedSubstats - i, xs) } - return - } - for (let i = 0; i <= Math.min(maxSubstats[x], distributedSubstats); i++) { - buffer[x] = - (Artifact.substatValue( - x as SubstatKey, - 5, - charTC.artifact.substats.type - ) / - comp(x)) * - i - permute(distributedSubstats - i, xs) } - } - permute(distributedSubstats, realSubs) - if (process.env.NODE_ENV === 'development') { - console.log(`Took ${performance.now() - startTime} ms`) - console.log(maxBuffer) - console.log( - objectMap(maxBuffer!, (v, x) => - allSubstatKeys.includes(x as any) - ? v / - (Artifact.substatValue( - x as SubstatKey, - 5, - charTC.artifact.substats.type - ) / - comp(x)) - : v + permute(distributedSubstats, realSubs) + if (process.env.NODE_ENV === 'development') { + console.log(`Took ${performance.now() - startTime} ms`) + console.log(maxBuffer) + console.log( + objectMap(maxBuffer!, (v, x) => + allSubstatKeys.includes(x as any) + ? v / + (Artifact.substatValue( + x as SubstatKey, + 5, + charTC.artifact.substats.type + ) / + comp(x)) + : v + ) ) - ) + } } if (apply) { From e0d2fb644a40faf31076398055f29d0287af8fa0 Mon Sep 17 00:00:00 2001 From: eeeqeee <103794572+eeeqeee@users.noreply.github.com> Date: Wed, 21 Jun 2023 21:03:38 -0400 Subject: [PATCH 17/61] add comment and fold ignored subs --- .../Tabs/TabTheorycraft/optimizeTc.tsx | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.tsx index 3dbb048422..7031070d0c 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.tsx @@ -1,5 +1,4 @@ import type { ArtifactSetKey, CharacterKey } from '@genshin-optimizer/consts' -import { allArtifactSetKeys } from '@genshin-optimizer/consts' import Artifact from '../../../../Data/Artifacts/Artifact' import type { ArtCharDatabase } from '../../../../Database/Database' import { mergeData, uiDataForTeam } from '../../../../Formula/api' @@ -48,6 +47,8 @@ export function optimizeTc( characterKey ]?.target.data![0] if (!workerData) return + // TODO: It may be better to use different dynamic data and add extra nodes to workerData during optimize so that you don't need to re-constant fold artifact set nodes later. + // https://github.com/frzyc/genshin-optimizer/pull/781#discussion_r1138023281 Object.assign(workerData, mergeData([workerData, dynamicData])) // Mark art fields as dynamic const unoptimizedOptimizationTargetNode = objPathValue( workerData.display ?? {}, @@ -60,17 +61,14 @@ export function optimizeTc( workerData, ({ path: [p] }) => p !== 'dyn' ) - // Const fold the artifact set + // Const fold read nodes nodes = mapFormulas( nodes, (f) => { if (f.operation === 'read' && f.path[0] === 'dyn') { const a = charTC.artifact.sets[f.path[1]] - if (a) { - return constant(a) - } else if (allArtifactSetKeys.includes(f.path[1] as any)) { - return constant(0) - } + if (a) return constant(a) + if (!allSubstatKeys.includes(f.path[1] as any)) return constant(0) } return f }, @@ -99,13 +97,15 @@ export function optimizeTc( comp(x)) * m - let maxBuffer: Record | undefined - const realSubs = [...subs].filter((x) => allSubstatKeys.includes(x as any)) + let maxBuffer: Record = Object.fromEntries( + [...subs].map((x) => [x, 0]) + ) + const subsArr = [...subs] if ( realSubs.reduce((a, x) => a + maxSubstats[x], 0) <= distributedSubstats ) { maxBuffer = Object.fromEntries( - realSubs.map((x) => [x, substatValue(x, maxSubstats[x])]) + subsArr.map((x) => [x, substatValue(x, maxSubstats[x])]) ) if (process.env.NODE_ENV === 'development') console.log(maxBuffer) } else { @@ -137,7 +137,7 @@ export function optimizeTc( permute(distributedSubstats - i, xs) } } - permute(distributedSubstats, realSubs) + permute(distributedSubstats, subsArr) if (process.env.NODE_ENV === 'development') { console.log(`Took ${performance.now() - startTime} ms`) console.log(maxBuffer) From 2e1a339e498726b90b8edfc51e66698b615c621b Mon Sep 17 00:00:00 2001 From: eeeqeee <103794572+eeeqeee@users.noreply.github.com> Date: Wed, 21 Jun 2023 21:48:33 -0400 Subject: [PATCH 18/61] missed rename --- .../CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.tsx index 7031070d0c..554bcaf895 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.tsx @@ -102,7 +102,7 @@ export function optimizeTc( ) const subsArr = [...subs] if ( - realSubs.reduce((a, x) => a + maxSubstats[x], 0) <= distributedSubstats + subsArr.reduce((a, x) => a + maxSubstats[x], 0) <= distributedSubstats ) { maxBuffer = Object.fromEntries( subsArr.map((x) => [x, substatValue(x, maxSubstats[x])]) From 5b02868c047a2a46bad331b4482439ec2eebfc66 Mon Sep 17 00:00:00 2001 From: eeeqeee <103794572+eeeqeee@users.noreply.github.com> Date: Sat, 1 Jul 2023 17:25:15 -0400 Subject: [PATCH 19/61] add comments --- .../CharacterDisplay/Tabs/TabTheorycraft/index.tsx | 4 ---- .../CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.tsx | 7 +++++++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx index 0182d8afec..9893a2ee03 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx @@ -326,10 +326,6 @@ export default function TabTheorycraft() { const { gender } = useDBMeta() - // This solves - // $\argmax_{x\in N^k, \sum x <= n, x <= x_max} f(x)$ without assumptions on the properties of $f$ - // We brute force iterate over all substats in the graph and compute the maximum - // n.b. some substat combinations may not be materializable into real artifacts const optimizeSubstats = useCallback( (apply: boolean) => optimizeTc( diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.tsx index 554bcaf895..77b7e96bab 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.tsx @@ -16,6 +16,11 @@ import { deepClone, objectMap, objPathValue } from '../../../../Util/Util' import { dynamicData } from '../TabOptimize/foreground' import type { SetCharTCAction } from '.' +// This solves +// $\argmax_{x\in N^k, \sum x <= `distributedSubstats`, x <= `maxSubstats`} `optimizationTarget`(x)$ without assumptions on the properties of `optimizationTarget` +// where $N$ are the natural numbers and $k$ is the number of `SubstatKey`s +// We brute force iterate over all substats in the graph and compute the maximum +// n.b. some substat combinations may not be materializable into real artifacts export function optimizeTc( characterKey: CharacterKey, optimizationTarget: string[] | undefined, @@ -133,6 +138,8 @@ export function optimizeTc( i <= Math.min(maxSubstats[x], distributedSubstats); i++ ) { + // TODO: Making sure that i + \sum { maxSubstats[xs] } >= distributedSubstats in each recursion will reduce unnecessary recursion considerably for large problems. It will also tighten the possibilities for the leaf recursion, so you don't need so many checkings. + // https://github.com/frzyc/genshin-optimizer/pull/781#discussion_r1138083742 buffer[x] = substatValue(x, i) permute(distributedSubstats - i, xs) } From 607be09454c020bf15764359502cd6b3a36c63fc Mon Sep 17 00:00:00 2001 From: frzyc Date: Wed, 9 Aug 2023 12:58:24 -0400 Subject: [PATCH 20/61] minor fixes --- .../Tabs/TabTheorycraft/index.tsx | 2 +- .../Tabs/TabTheorycraft/optimizeTc.tsx | 29 +++++++++---------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx index 66f8e5e14b..6065411b16 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx @@ -968,7 +968,7 @@ function ArtifactSubstatEditor({ maxSubstat: number setMaxSubstat: (v: number) => void }) { - const { t } = useTranslation('page_character') + // const { t } = useTranslation('page_character') const substatValue = getSubstatValue(statKey, 5, substatsType) const [rolls, setRolls] = useState(() => value / substatValue) useEffect(() => setRolls(value / substatValue), [value, substatValue]) diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.tsx index 77b7e96bab..f99b270dfb 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.tsx @@ -1,5 +1,12 @@ -import type { ArtifactSetKey, CharacterKey } from '@genshin-optimizer/consts' -import Artifact from '../../../../Data/Artifacts/Artifact' +import type { GenderKey, SubstatKey } from '@genshin-optimizer/consts' +import { + allSubstatKeys, + type ArtifactSetKey, + type CharacterKey, +} from '@genshin-optimizer/consts' +import { getSubstatValue } from '@genshin-optimizer/gi-util' +import { objMap } from '@genshin-optimizer/util' +import type { SetCharTCAction } from '.' import type { ArtCharDatabase } from '../../../../Database/Database' import { mergeData, uiDataForTeam } from '../../../../Formula/api' import { mapFormulas } from '../../../../Formula/internal' @@ -7,14 +14,10 @@ import { optimize, precompute } from '../../../../Formula/optimization' import type { NumNode } from '../../../../Formula/type' import { constant } from '../../../../Formula/utils' import { getTeamData } from '../../../../ReactHooks/useTeamData' -import type { SubstatKey } from '../../../../Types/artifact' -import { allSubstatKeys } from '../../../../Types/artifact' import type { ICharTC } from '../../../../Types/character' -import type { Gender } from '../../../../Types/consts' import type { ICachedWeapon } from '../../../../Types/weapon' -import { deepClone, objectMap, objPathValue } from '../../../../Util/Util' +import { deepClone, objPathValue, objectMap } from '../../../../Util/Util' import { dynamicData } from '../TabOptimize/foreground' -import type { SetCharTCAction } from '.' // This solves // $\argmax_{x\in N^k, \sum x <= `distributedSubstats`, x <= `maxSubstats`} `optimizationTarget`(x)$ without assumptions on the properties of `optimizationTarget` @@ -30,7 +33,7 @@ export function optimizeTc( artSet: Partial> }, overrideWeapon: ICachedWeapon, - gender: Gender, + gender: GenderKey, charTC: ICharTC, maxSubstats: Record, distributedSubstats: number, @@ -94,11 +97,7 @@ export function optimizeTc( const comp = (statKey: string) => (statKey.endsWith('_') ? 100 : 1) const substatValue = (x: string, m: number) => - (Artifact.substatValue( - x as SubstatKey, - 5, - charTC.artifact.substats.type - ) / + (getSubstatValue(x as SubstatKey, 5, charTC.artifact.substats.type) / comp(x)) * m @@ -149,10 +148,10 @@ export function optimizeTc( console.log(`Took ${performance.now() - startTime} ms`) console.log(maxBuffer) console.log( - objectMap(maxBuffer!, (v, x) => + objMap(maxBuffer!, (v, x) => allSubstatKeys.includes(x as any) ? v / - (Artifact.substatValue( + (getSubstatValue( x as SubstatKey, 5, charTC.artifact.substats.type From 734cce66adaf3698715b8d2928759db10d205f8b Mon Sep 17 00:00:00 2001 From: frzyc Date: Sat, 23 Dec 2023 03:33:37 -0500 Subject: [PATCH 21/61] minor updates & fixes --- .../Tabs/TabTheorycraft/index.tsx | 308 +++++++++--------- .../Tabs/TabTheorycraft/optimizeTc.tsx | 2 +- .../Tabs/TabUpgradeOpt/upOpt.ts | 4 +- 3 files changed, 162 insertions(+), 152 deletions(-) diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx index fa756edbbc..44c58dff65 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx @@ -99,6 +99,7 @@ import OptimizationTargetSelector from '../TabOptimize/Components/OptimizationTa import useCharTC from './useCharTC' import useDBMeta from '../../../../ReactHooks/useDBMeta' import { optimizeTc } from './optimizeTc' +import CalculateIcon from '@mui/icons-material/Calculate' const WeaponSelectionModal = React.lazy( () => import('../../../../Components/Weapon/WeaponSelectionModal') ) @@ -394,57 +395,86 @@ export default function TabTheorycraft() { {dataContextValue ? ( - - - - + + + + + + + + s.statKey + )} + distributedSubstats={distributedSubstats} + setDistributedSubstats={setDistributedSubstats} + maxSubstats={maxSubstats} + setMaxSubstats={(k: SubstatKey) => (v: number) => { + if (charTC.optimization.maxSubstats[k] === v) return + const data_ = structuredClone(charTC) + data_.optimization.maxSubstats[k] = v + setCharTC(data_) + }} + /> + - - s.statKey - )} - distributedSubstats={distributedSubstats} - setDistributedSubstats={setDistributedSubstats} - maxSubstats={maxSubstats} - setMaxSubstats={(k: SubstatKey) => (v: number) => { - if (charTC.optimization.maxSubstats[k] === v) return - const data_ = structuredClone(charTC) - data_.optimization.maxSubstats[k] = v - setCharTC(data_) + + + + setOptimizationTarget(target)} + /> + v !== undefined && setDistributedSubstats(v)} + endAdornment={'Substats'} + sx={{ + borderRadius: 1, + px: 1, + textWrap: 'nowrap', + flexShrink: 1, + }} + inputProps={{ + sx: { + textAlign: 'right', + px: 1, + width: '3em', + minWidth: '3em', + }, + min: 0, }} /> - - - setOptimizationTarget(target)} - /> - {process.env.NODE_ENV === 'development' && ( - - )} - + + + + {process.env.NODE_ENV === 'development' && ( + + )} + ) : ( @@ -882,58 +912,48 @@ function ArtifactSubCard({ ) return ( - - - {substatTypeKeys.map((st) => ( - setSubstatsType(st)} - > - {t(`tabTheorycraft.substatType.${st}`)} - - ))} - - {t`tabTheorycraft.maxTotalRolls`}} - placement="top" - > - - 45 ? 'warning' : undefined}> - Rolls: {rolls.toFixed(0)} - - 45 ? 'warning' : undefined}> - RV: {rv.toFixed(1)}% - - - - v !== undefined && setDistributedSubstats(v)} - endAdornment={'Distributed Substats'} - sx={{ borderRadius: 1, px: 1, width: '50%' }} - inputProps={{ - sx: { textAlign: 'right', px: 1, width: '20%' }, - min: 0, - }} - /> - + + + {substatTypeKeys.map((st) => ( + setSubstatsType(st)} + > + {t(`tabTheorycraft.substatType.${st}`)} + + ))} + + {t`tabTheorycraft.maxTotalRolls`}} + placement="top" + > + + 45 ? 'warning' : undefined}> + Rolls: {rolls.toFixed(0)} + + 45 ? 'warning' : undefined}> + RV: {rv.toFixed()}% + + + + {Object.entries(substats).map(([k, v]) => ( + + } + value={parseFloat(displayValue.toFixed(2))} + onChange={(v) => v !== undefined && setValue(v)} + sx={{ borderRadius: 1, px: 1, height: '100%', width: '5em' }} + inputProps={{ sx: { textAlign: 'right' }, min: 0 }} + /> + + + {artDisplayValue(substatValue, unit)} + {unit} + + x + + } + value={parseFloat(rolls.toFixed(2))} + onChange={(v) => v !== undefined && setValue(v * substatValue)} + sx={{ borderRadius: 1, px: 1, my: 0, height: '100%', width: '5.5em' }} + inputProps={{ sx: { textAlign: 'right', pr: 0.5 }, min: 0, step: 1 }} + /> {/* {t(numMains ? `tabTheorycraft.maxRollsMain` : `tabTheorycraft.maxRolls`, { value: maxRolls })}} placement="top"> */} - + - RV: {rv.toFixed(1)}% + RV: {rv.toFixed()}% {/* */} @@ -1026,17 +1081,6 @@ function ArtifactSubstatEditor({ justifyContent="space-between" alignItems="center" > - - } - value={parseFloat(displayValue.toFixed(2))} - onChange={(v) => v !== undefined && setValue(v)} - sx={{ borderRadius: 1, px: 1, height: '100%', width: '6em' }} - inputProps={{ sx: { textAlign: 'right' }, min: 0 }} - /> setRValue(v as number)} /> - - - {artDisplayValue(substatValue, unit)} - {unit} - - x - - } - value={parseFloat(rolls.toFixed(2))} - onChange={(v) => v !== undefined && setValue(v * substatValue)} - sx={{ borderRadius: 1, px: 1, my: 0, height: '100%', width: '7em' }} - inputProps={{ sx: { textAlign: 'right', pr: 0.5 }, min: 0, step: 1 }} - /> + v !== undefined && setMaxSubstat(v)} color={maxSubstat > maxRolls ? 'warning' : 'success'} - sx={{ borderRadius: 1, px: 1, my: 0, height: '100%', width: '7em' }} + sx={{ borderRadius: 1, px: 1, my: 0, height: '100%', width: '6em' }} inputProps={{ sx: { textAlign: 'right', pr: 0.5 }, min: 0, step: 1 }} /> @@ -1139,17 +1160,6 @@ function ArtifactAllSubstatEditor() { justifyContent="space-between" alignItems="center" > - - Change all Substats - All Max} onChange={(v) => v !== undefined && setMaxSubstat(v)} color={(maxSubstat ?? 0) > maxRolls ? 'warning' : 'success'} - sx={{ borderRadius: 1, px: 1, my: 0, height: '100%', width: '7em' }} + sx={{ borderRadius: 1, px: 1, my: 0, height: '100%', width: '6.5em' }} inputProps={{ sx: { textAlign: 'right', pr: 0.5 }, min: 0, step: 1 }} /> diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.tsx index f99b270dfb..5cf49dea76 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.tsx @@ -164,7 +164,7 @@ export function optimizeTc( } if (apply) { - const data_ = deepClone(charTC) + const data_ = structuredClone(charTC) data_.artifact.substats.stats = objectMap( charTC.artifact.substats.stats, (v, k) => v + (maxBuffer![k] ?? 0) * comp(k) diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabUpgradeOpt/upOpt.ts b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabUpgradeOpt/upOpt.ts index f4fcb1979c..0f590908f8 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabUpgradeOpt/upOpt.ts +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabUpgradeOpt/upOpt.ts @@ -186,14 +186,14 @@ export class UpOptCalculator { const evalOpt = optimize(toEval, {}, ({ path: [p] }) => p !== 'dyn') const evalFn = precompute(evalOpt, {}, (f) => f.path[1], 5) - thresholds[0] = evalFn(Object.values(build))[0] // dmg threshold is current objective value + thresholds[0] = evalFn(Object.values(build) as any)[0] // dmg threshold is current objective value this.skippableDerivatives = allSubstatKeys.map((sub) => nodes.every((n) => zero_deriv(n, (f) => f.path[1], sub)) ) this.eval = (stats: DynStat, slot: ArtifactSlotKey) => { const b2 = { ...build, [slot]: { id: '', values: stats } } - const out = evalFn(Object.values(b2)) + const out = evalFn(Object.values(b2) as any) return nodes.map((_, i) => { const ix = i * (1 + allSubstatKeys.length) return { From d7d2d2a65a3be5826d044221e922a690e8f1b385 Mon Sep 17 00:00:00 2001 From: frzyc Date: Sat, 23 Dec 2023 03:45:41 -0500 Subject: [PATCH 22/61] minor fixes --- .../CharacterDisplay/Tabs/TabTheorycraft/index.tsx | 6 ------ .../CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.tsx | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx index 44c58dff65..a19237855e 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx @@ -417,8 +417,6 @@ export default function TabTheorycraft() { mainStatKeys={Object.values(charTC.artifact.slots).map( (s) => s.statKey )} - distributedSubstats={distributedSubstats} - setDistributedSubstats={setDistributedSubstats} maxSubstats={maxSubstats} setMaxSubstats={(k: SubstatKey) => (v: number) => { if (charTC.optimization.maxSubstats[k] === v) return @@ -881,8 +879,6 @@ function ArtifactSubCard({ substatsType, setSubstatsType, mainStatKeys, - distributedSubstats, - setDistributedSubstats, maxSubstats, setMaxSubstats, }: { @@ -891,8 +887,6 @@ function ArtifactSubCard({ substatsType: SubstatTypeKey setSubstatsType: (t: SubstatTypeKey) => void mainStatKeys: MainStatKey[] - distributedSubstats: number - setDistributedSubstats: (f: number) => void maxSubstats: Record setMaxSubstats: (k: SubstatKey) => (v: number) => void }) { diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.tsx index 5cf49dea76..a075d7c0c0 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.tsx @@ -16,7 +16,7 @@ import { constant } from '../../../../Formula/utils' import { getTeamData } from '../../../../ReactHooks/useTeamData' import type { ICharTC } from '../../../../Types/character' import type { ICachedWeapon } from '../../../../Types/weapon' -import { deepClone, objPathValue, objectMap } from '../../../../Util/Util' +import { objPathValue, objectMap } from '../../../../Util/Util' import { dynamicData } from '../TabOptimize/foreground' // This solves From 2e7c97cb71448846e016c29f24ca5c974dd85314 Mon Sep 17 00:00:00 2001 From: eeeqeee <103794572+eeeqeee@users.noreply.github.com> Date: Sat, 23 Dec 2023 23:44:33 -0500 Subject: [PATCH 23/61] forgot to assign the last substat --- .../CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.tsx index a075d7c0c0..bd6957ec37 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.tsx @@ -122,6 +122,7 @@ export function optimizeTc( const permute = (distributedSubstats: number, [x, ...xs]: string[]) => { if (xs.length === 0) { if (distributedSubstats > maxSubstats[x]) return + buffer[x] = substatValue(x, distributedSubstats) const [result] = compute([ { values: bufferSubs }, { values: buffer }, From 76899033651912b852ea6bb11cc8d98695c5bcbf Mon Sep 17 00:00:00 2001 From: frzyc Date: Sun, 24 Dec 2023 04:28:12 -0500 Subject: [PATCH 24/61] minor refactoring --- .../ArtifactMainLevelSlot.tsx | 134 +++ .../ArtifactSetEditor.tsx | 102 ++ .../ArtifactSetsEditor.tsx | 56 ++ .../ArtifactMainStatAndSetEditor/index.tsx | 25 + .../ArtifactAllSubstatEditor.tsx | 91 ++ .../ArtifactSubCard/ArtifactSubstatEditor.tsx | 170 ++++ .../TabTheorycraft/ArtifactSubCard/index.tsx | 94 ++ .../Tabs/TabTheorycraft/CharTCContext.tsx | 10 + .../Tabs/TabTheorycraft/WeaponEditorCard.tsx | 139 +++ .../Tabs/TabTheorycraft/index.tsx | 936 +----------------- .../Tabs/TabTheorycraft/optimizeTc.ts | 141 +++ .../Tabs/TabTheorycraft/optimizeTc.tsx | 177 ---- libs/gi-util/src/artifact/artifact.ts | 25 +- 13 files changed, 1015 insertions(+), 1085 deletions(-) create mode 100644 apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactMainStatAndSetEditor/ArtifactMainLevelSlot.tsx create mode 100644 apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactMainStatAndSetEditor/ArtifactSetEditor.tsx create mode 100644 apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactMainStatAndSetEditor/ArtifactSetsEditor.tsx create mode 100644 apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactMainStatAndSetEditor/index.tsx create mode 100644 apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactAllSubstatEditor.tsx create mode 100644 apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactSubstatEditor.tsx create mode 100644 apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/index.tsx create mode 100644 apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/CharTCContext.tsx create mode 100644 apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/WeaponEditorCard.tsx create mode 100644 apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.ts delete mode 100644 apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.tsx diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactMainStatAndSetEditor/ArtifactMainLevelSlot.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactMainStatAndSetEditor/ArtifactMainLevelSlot.tsx new file mode 100644 index 0000000000..cd57a60189 --- /dev/null +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactMainStatAndSetEditor/ArtifactMainLevelSlot.tsx @@ -0,0 +1,134 @@ +import type { ArtifactRarity, ArtifactSlotKey } from '@genshin-optimizer/consts' +import { artMaxLevel, artSlotMainKeys } from '@genshin-optimizer/consts' +import { + artDisplayValue, + getMainStatDisplayValue, +} from '@genshin-optimizer/gi-util' +import { iconInlineProps } from '@genshin-optimizer/svgicons' +import StarRoundedIcon from '@mui/icons-material/StarRounded' +import { Box, MenuItem } from '@mui/material' +import { useCallback, useContext } from 'react' +import SlotIcon from '../../../../../Components/Artifact/SlotIcon' +import CardDark from '../../../../../Components/Card/CardDark' +import CustomNumberInput from '../../../../../Components/CustomNumberInput' +import DropdownButton from '../../../../../Components/DropdownMenu/DropdownButton' +import { + StatColoredWithUnit, + StatWithUnit, +} from '../../../../../Components/StatDisplay' +import Artifact from '../../../../../Data/Artifacts/Artifact' +import KeyMap from '../../../../../KeyMap' +import StatIcon from '../../../../../KeyMap/StatIcon' +import type { ICharTCArtifactSlot } from '../../../../../Types/character' +import { CharTCContext } from '../CharTCContext' + +export function ArtifactMainLevelSlot({ + slotKey, +}: { + slotKey: ArtifactSlotKey +}) { + const { + charTC: { + artifact: { slots }, + }, + setCharTC, + } = useContext(CharTCContext) + const { level, statKey, rarity } = slots[slotKey] + const keys = artSlotMainKeys[slotKey] + const setSlot = useCallback( + (action: Partial) => { + setCharTC((charTC) => { + const slot = charTC.artifact.slots[slotKey] + charTC.artifact.slots[slotKey] = { ...slot, ...action } + }) + }, + [setCharTC, slotKey] + ) + const setRarity = useCallback( + (r: ArtifactRarity) => { + const mLvl = artMaxLevel[r] ?? 0 + if (level > mLvl) setSlot({ rarity: r, level: mLvl }) + else setSlot({ rarity: r }) + }, + [level, setSlot] + ) + + return ( + + + + {keys.length === 1 ? ( + + {' '} + {KeyMap.getStr(keys[0])} + + ) : ( + } + color={KeyMap.getVariant(statKey) ?? 'success'} + > + {keys.map((msk) => ( + setSlot({ statKey: msk })} + > + + + ))} + + )} + + + {rarity} + + } + > + {[5, 4, 3].map((r) => ( + setRarity(r as ArtifactRarity)} + > + + {r} + + + ))} + + l !== undefined && setSlot({ level: l })} + sx={{ borderRadius: 1, pl: 1, my: 0, height: '100%' }} + inputProps={{ sx: { pl: 0.5, width: '2em' }, max: 20, min: 0 }} + /> + + {`${artDisplayValue( + getMainStatDisplayValue(statKey, rarity, level), + KeyMap.unit(statKey) + )}${KeyMap.unit(statKey)}`} + + + ) +} diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactMainStatAndSetEditor/ArtifactSetEditor.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactMainStatAndSetEditor/ArtifactSetEditor.tsx new file mode 100644 index 0000000000..aeb226aed3 --- /dev/null +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactMainStatAndSetEditor/ArtifactSetEditor.tsx @@ -0,0 +1,102 @@ +import type { ArtifactSetKey } from '@genshin-optimizer/consts' +import { DeleteForever, Info } from '@mui/icons-material' +import { Box, Button, ButtonGroup, MenuItem, Stack } from '@mui/material' +import { useCallback, useContext, useMemo } from 'react' +import ArtifactSetTooltip from '../../../../../Components/Artifact/ArtifactSetTooltip' +import SetEffectDisplay from '../../../../../Components/Artifact/SetEffectDisplay' +import CardLight from '../../../../../Components/Card/CardLight' +import DropdownButton from '../../../../../Components/DropdownMenu/DropdownButton' +import ImgIcon from '../../../../../Components/Image/ImgIcon' +import { getArtSheet } from '../../../../../Data/Artifacts' +import { artifactDefIcon } from '../../../../../Data/Artifacts/ArtifactSheet' +import type { SetNum } from '../../../../../Types/consts' +import { CharTCContext } from '../CharTCContext' + +export function ArtifactSetEditor({ + setKey, + remaining, +}: { + setKey: ArtifactSetKey + remaining: number +}) { + const { + charTC: { + artifact: { sets }, + }, + setCharTC, + } = useContext(CharTCContext) + const value = sets[setKey] + const setValue = useCallback( + (value: 1 | 2 | 4) => { + setCharTC((charTC) => { + charTC.artifact.sets[setKey] = value + }) + }, + [setCharTC, setKey] + ) + const deleteValue = useCallback(() => { + setCharTC((charTC) => { + const { [setKey]: _, ...rest } = charTC.artifact.sets + charTC.artifact.sets = rest + }) + }, [setCharTC, setKey]) + const artifactSheet = getArtSheet(setKey) + + /* Assumes that all conditionals are from 4-Set. needs to change if there are 2-Set conditionals */ + const set4CondNums = useMemo(() => { + if (value < 4) return [] + return Object.keys(artifactSheet.setEffects).filter((setNumKey) => + artifactSheet.setEffects[setNumKey]?.document.some( + (doc) => 'states' in doc + ) + ) + }, [artifactSheet, value]) + + return ( + + + + + + {artifactSheet.setName} + + + + + {value}-set} + > + {Object.keys(artifactSheet.setEffects) + .map((setKey) => parseInt(setKey)) + .map((setKey) => ( + remaining + value} + onClick={() => setValue(setKey as 1 | 2 | 4)} + > + {setKey}-set + + ))} + + + + + {!!set4CondNums.length && ( + + {set4CondNums.map((setNumKey) => ( + + ))} + + )} + + ) +} diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactMainStatAndSetEditor/ArtifactSetsEditor.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactMainStatAndSetEditor/ArtifactSetsEditor.tsx new file mode 100644 index 0000000000..eddc28050e --- /dev/null +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactMainStatAndSetEditor/ArtifactSetsEditor.tsx @@ -0,0 +1,56 @@ +import type { ArtifactSetKey } from '@genshin-optimizer/consts' +import { Stack } from '@mui/material' +import { useCallback, useContext } from 'react' +import ArtifactSetAutocomplete from '../../../../../Components/Artifact/ArtifactSetAutocomplete' +import CardLight from '../../../../../Components/Card/CardLight' +import { getArtSheet } from '../../../../../Data/Artifacts' +import { ArtifactSetEditor } from './ArtifactSetEditor' +import { CharTCContext } from '../CharTCContext' + +export function ArtifactSetsEditor() { + const { + charTC: { + artifact: { sets: artSet }, + }, + setCharTC, + } = useContext(CharTCContext) + const setSet = useCallback( + (setKey: ArtifactSetKey | '') => { + if (!setKey) return + setCharTC((charTC) => { + charTC.artifact.sets[setKey] = parseInt( + Object.keys(getArtSheet(setKey).setEffects)[0] + ) as 1 | 2 | 4 + }) + }, + [setCharTC] + ) + + const remaining = 5 - Object.values(artSet).reduce((a, b) => a + b, 0) + + return ( + + {Object.keys(artSet).map((setKey) => ( + + ))} + + + Object.keys(artSet).includes(key as ArtifactSetKey) || + !key || + Object.keys(getArtSheet(key).setEffects).every( + (n) => parseInt(n) > remaining + ) + } + /> + + + ) +} diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactMainStatAndSetEditor/index.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactMainStatAndSetEditor/index.tsx new file mode 100644 index 0000000000..d4c028ed81 --- /dev/null +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactMainStatAndSetEditor/index.tsx @@ -0,0 +1,25 @@ +import { allArtifactSlotKeys } from '@genshin-optimizer/consts' +import { Skeleton, Stack } from '@mui/material' +import { Suspense } from 'react' +import CardLight from '../../../../../Components/Card/CardLight' +import { ArtifactMainLevelSlot } from './ArtifactMainLevelSlot' +import { ArtifactSetsEditor } from './ArtifactSetsEditor' + +export function ArtifactMainStatAndSetEditor() { + return ( + + + + {allArtifactSlotKeys.map((s) => ( + + ))} + + + } + > + + + + ) +} diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactAllSubstatEditor.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactAllSubstatEditor.tsx new file mode 100644 index 0000000000..d7395d8d4f --- /dev/null +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactAllSubstatEditor.tsx @@ -0,0 +1,91 @@ +import { getSubstatValue } from '@genshin-optimizer/gi-util' +import { objMap } from '@genshin-optimizer/util' +import { Box, Slider } from '@mui/material' +import { useContext, useDeferredValue, useEffect, useState } from 'react' +import CardDark from '../../../../../Components/Card/CardDark' +import CustomNumberInput from '../../../../../Components/CustomNumberInput' +import { CharTCContext } from '../CharTCContext' + +const DEFAULT_MAX_ROLLS = 30 +export function ArtifactAllSubstatEditor() { + const [rolls, setRolls] = useState(undefined as undefined | number) + const [maxSubstat, setMaxSubstat] = useState(undefined as undefined | number) + const { setCharTC } = useContext(CharTCContext) + + const rollsDeferred = useDeferredValue(rolls) + useEffect(() => { + if (rollsDeferred === undefined) return + setCharTC((charTC) => { + const stats = charTC.artifact.substats.stats + const substatsType = charTC.artifact.substats.type + charTC.artifact.substats.stats = objMap(stats, (val, statKey) => { + const substatValue = getSubstatValue(statKey, 5, substatsType) + return substatValue * rollsDeferred + }) + }) + }, [setCharTC, rollsDeferred]) + + const maxSubstatDeferred = useDeferredValue(maxSubstat) + useEffect(() => { + if (maxSubstatDeferred === undefined) return + setCharTC((charTC) => { + charTC.optimization.maxSubstats = objMap( + charTC.optimization.maxSubstats, + (_val, _statKey) => maxSubstatDeferred + ) + }) + }, [setCharTC, maxSubstatDeferred]) + + // 0.0001 to nudge float comparasion + const invalid = (rolls ?? 0 - 0.0001) > DEFAULT_MAX_ROLLS + + return ( + + + setRolls(v as number)} + onChangeCommitted={(e, v) => setRolls(v as number)} + /> + + All Rolls} + value={parseFloat((rolls ?? 0).toFixed(2))} + onChange={(v) => v !== undefined && setRolls(v)} + sx={{ borderRadius: 1, px: 1, my: 0, height: '100%', width: '7em' }} + inputProps={{ sx: { textAlign: 'right', pr: 0.5 }, min: 0, step: 1 }} + /> + All Max} + onChange={(v) => v !== undefined && setMaxSubstat(v)} + color={(maxSubstat ?? 0) > DEFAULT_MAX_ROLLS ? 'warning' : 'success'} + sx={{ borderRadius: 1, px: 1, my: 0, height: '100%', width: '6.5em' }} + inputProps={{ sx: { textAlign: 'right', pr: 0.5 }, min: 0, step: 1 }} + /> + + ) +} diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactSubstatEditor.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactSubstatEditor.tsx new file mode 100644 index 0000000000..c705d81259 --- /dev/null +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactSubstatEditor.tsx @@ -0,0 +1,170 @@ +import type { SubstatKey } from '@genshin-optimizer/consts' +import { artDisplayValue, getSubstatValue } from '@genshin-optimizer/gi-util' +import { Box, Slider, Stack } from '@mui/material' +import { useCallback, useContext, useEffect, useState } from 'react' +import CardDark from '../../../../../Components/Card/CardDark' +import ColorText from '../../../../../Components/ColoredText' +import CustomNumberInput from '../../../../../Components/CustomNumberInput' +import KeyMap from '../../../../../KeyMap' +import StatIcon from '../../../../../KeyMap/StatIcon' +import { CharTCContext } from '../CharTCContext' + +export function ArtifactSubstatEditor({ statKey }: { statKey: SubstatKey }) { + const { + charTC: { + artifact: { + slots, + substats: { type: substatsType, stats: substats }, + }, + optimization: { maxSubstats }, + }, + setCharTC, + } = useContext(CharTCContext) + const mainStatKeys = Object.values(slots).map((s) => s.statKey) + const value = substats[statKey] + const setValue = useCallback( + (v: number) => { + setCharTC((charTC) => { + charTC.artifact.substats.stats[statKey] = v + }) + }, + [setCharTC, statKey] + ) + const maxSubstat = maxSubstats[statKey] + const setMaxSubstat = useCallback( + (v: number) => { + setCharTC((charTC) => { + charTC.optimization.maxSubstats[statKey] = v + }) + }, + [setCharTC, statKey] + ) + // const { t } = useTranslation('page_character') + const substatValue = getSubstatValue(statKey, 5, substatsType) + const [rolls, setRolls] = useState(() => value / substatValue) + useEffect(() => setRolls(value / substatValue), [value, substatValue]) + + const unit = KeyMap.unit(statKey) + const displayValue = rolls * substatValue + + const rv = ((rolls * substatValue) / getSubstatValue(statKey)) * 100 + const numMains = mainStatKeys.reduce( + (t, ms) => t + (ms === statKey ? 1 : 0), + 0 + ) + const maxRolls = (5 - numMains) * 6 + // 0.0001 to nudge float comparasion + const invalid = rolls - 0.0001 > maxRolls + const setRValue = useCallback( + (r: number) => setValue(r * substatValue), + [setValue, substatValue] + ) + + return ( + + + + } + value={parseFloat(displayValue.toFixed(2))} + onChange={(v) => v !== undefined && setValue(v)} + sx={{ borderRadius: 1, px: 1, height: '100%', width: '5em' }} + inputProps={{ sx: { textAlign: 'right' }, min: 0 }} + /> + + + {KeyMap.getStr(statKey)} + {KeyMap.unit(statKey)} + + + + {artDisplayValue(substatValue, unit)} + {unit} + + x + + } + value={parseFloat(rolls.toFixed(2))} + onChange={(v) => v !== undefined && setValue(v * substatValue)} + sx={{ borderRadius: 1, px: 1, my: 0, height: '100%', width: '6.5em' }} + inputProps={{ sx: { textAlign: 'right', pr: 0.5 }, min: 0, step: 1 }} + /> + {/* {t(numMains ? `tabTheorycraft.maxRollsMain` : `tabTheorycraft.maxRolls`, { value: maxRolls })}} placement="top"> */} + + + RV: {rv.toFixed()}% + + + {/* */} + + + + setRolls(v as number)} + onChangeCommitted={(e, v) => setRValue(v as number)} + /> + + + v !== undefined && setMaxSubstat(v)} + color={maxSubstat > maxRolls ? 'warning' : 'success'} + sx={{ borderRadius: 1, px: 1, my: 0, height: '100%', width: '6em' }} + inputProps={{ sx: { textAlign: 'right', pr: 0.5 }, min: 0, step: 1 }} + /> + + + ) +} diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/index.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/index.tsx new file mode 100644 index 0000000000..41437dba24 --- /dev/null +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/index.tsx @@ -0,0 +1,94 @@ +import type { SubstatTypeKey } from '@genshin-optimizer/consts' +import { substatTypeKeys } from '@genshin-optimizer/consts' +import { getSubstatValue } from '@genshin-optimizer/gi-util' +import { Box, MenuItem, Stack, Typography } from '@mui/material' +import { useCallback, useContext } from 'react' +import { useTranslation } from 'react-i18next' +import BootstrapTooltip from '../../../../../Components/BootstrapTooltip' +import CardDark from '../../../../../Components/Card/CardDark' +import CardLight from '../../../../../Components/Card/CardLight' +import ColorText from '../../../../../Components/ColoredText' +import DropdownButton from '../../../../../Components/DropdownMenu/DropdownButton' +import { CharTCContext } from '../CharTCContext' +import { ArtifactAllSubstatEditor } from './ArtifactAllSubstatEditor' +import { ArtifactSubstatEditor } from './ArtifactSubstatEditor' +export function ArtifactSubCard() { + const { t } = useTranslation('page_character') + const { + charTC: { + artifact: { + substats: { type: substatsType, stats: substats }, + }, + }, + setCharTC, + } = useContext(CharTCContext) + const setSubstatsType = useCallback( + (t: SubstatTypeKey) => { + setCharTC((charTC) => { + charTC.artifact.substats.type = t + }) + }, + [setCharTC] + ) + + const rv = + Object.entries(substats).reduce( + (t, [k, v]) => t + v / getSubstatValue(k), + 0 + ) * 100 + const rolls = Object.entries(substats).reduce( + (t, [k, v]) => t + v / getSubstatValue(k, undefined, substatsType), + 0 + ) + return ( + + + + + {substatTypeKeys.map((st) => ( + setSubstatsType(st)} + > + {t(`tabTheorycraft.substatType.${st}`)} + + ))} + + {t`tabTheorycraft.maxTotalRolls`}} + placement="top" + > + + 45 ? 'warning' : undefined}> + Rolls: {rolls.toFixed(0)} + + 45 ? 'warning' : undefined}> + RV: {rv.toFixed()}% + + + + + + {Object.entries(substats).map(([k]) => ( + + ))} + + + ) +} diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/CharTCContext.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/CharTCContext.tsx new file mode 100644 index 0000000000..c1aaa13aef --- /dev/null +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/CharTCContext.tsx @@ -0,0 +1,10 @@ +import { createContext } from 'react' +import type { ICharTC } from '../../../../Types/character' +export type SetCharTCAction = + | Partial + | ((v: ICharTC) => Partial | void) +type CharTCContexObj = { + charTC: ICharTC + setCharTC: (action: SetCharTCAction) => void +} +export const CharTCContext = createContext({} as CharTCContexObj) diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/WeaponEditorCard.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/WeaponEditorCard.tsx new file mode 100644 index 0000000000..01422684bd --- /dev/null +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/WeaponEditorCard.tsx @@ -0,0 +1,139 @@ +import type { WeaponTypeKey } from '@genshin-optimizer/consts' +import { weaponAsset } from '@genshin-optimizer/gi-assets' +import { useBoolState } from '@genshin-optimizer/react-util' +import { + Box, + Button, + CardHeader, + Divider, + ListItem, + Stack, +} from '@mui/material' +import React, { useCallback, useContext, useMemo } from 'react' +import CardDark from '../../../../Components/Card/CardDark' +import CardLight from '../../../../Components/Card/CardLight' +import DocumentDisplay from '../../../../Components/DocumentDisplay' +import { + FieldDisplayList, + NodeFieldDisplay, +} from '../../../../Components/FieldDisplay' +import LevelSelect from '../../../../Components/LevelSelect' +import RefinementDropdown from '../../../../Components/RefinementDropdown' +import { DataContext } from '../../../../Context/DataContext' +import { getWeaponSheet } from '../../../../Data/Weapons' +import { uiInput as input } from '../../../../Formula' +import { computeUIData, dataObjForWeapon } from '../../../../Formula/api' +import type { ICharTC } from '../../../../Types/character' +import type { ICachedWeapon } from '../../../../Types/weapon' +import { CharTCContext } from './CharTCContext' +const WeaponSelectionModal = React.lazy( + () => import('../../../../Components/Weapon/WeaponSelectionModal') +) + +export function WeaponEditorCard({ + weaponTypeKey, +}: { + weaponTypeKey: WeaponTypeKey +}) { + const { charTC, setCharTC } = useContext(CharTCContext) + const setWeapon = useCallback( + (weapon: Partial) => { + setCharTC((charTC) => { + charTC.weapon = { ...charTC.weapon, ...weapon } + }) + }, + [setCharTC] + ) + const weapon: ICachedWeapon = useMemo( + () => ({ + ...charTC.weapon, + location: '', + lock: false, + id: '', + }), + [charTC] + ) + const { key, level = 0, refinement = 1, ascension = 0 } = weapon + const weaponSheet = getWeaponSheet(key) + const [show, onShow, onHide] = useBoolState() + const { data } = useContext(DataContext) + const weaponUIData = useMemo( + () => weapon && computeUIData([weaponSheet.data, dataObjForWeapon(weapon)]), + [weaponSheet, weapon] + ) + return ( + + setWeapon({ key: k })} + weaponTypeFilter={weaponTypeKey} + /> + + + = 2)} + sx={{ + flexshrink: 1, + flexBasis: 0, + maxWidth: '30%', + borderRadius: 1, + }} + /> + + + {weaponSheet.hasRefinement && ( + setWeapon({ refinement: r })} + /> + )} + + + + + + + {weaponUIData && ( + + {[input.weapon.main, input.weapon.sub, input.weapon.sub2].map( + (node) => { + const n = weaponUIData.get(node) + if (n.isEmpty || !n.value) return null + return ( + + ) + } + )} + + )} + + {data && weaponSheet?.document && ( + + )} + + + ) +} diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx index a19237855e..1987cbb99a 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx @@ -1,118 +1,39 @@ -import type { - ArtifactRarity, - ArtifactSetKey, - ArtifactSlotKey, - MainStatKey, - SubstatKey, - SubstatTypeKey, - WeaponTypeKey, -} from '@genshin-optimizer/consts' -import { - allArtifactSlotKeys, - artMaxLevel, - artSlotsData, - substatTypeKeys, -} from '@genshin-optimizer/consts' -import { weaponAsset } from '@genshin-optimizer/gi-assets' -import { - artDisplayValue, - getMainStatDisplayValue, - getSubstatValue, -} from '@genshin-optimizer/gi-util' -import { useBoolState } from '@genshin-optimizer/react-util' -import { iconInlineProps } from '@genshin-optimizer/svgicons' -import { deepClone, objMap } from '@genshin-optimizer/util' -import { CopyAll, DeleteForever, Info, Refresh } from '@mui/icons-material' -import StarRoundedIcon from '@mui/icons-material/StarRounded' +import { getMainStatDisplayValue } from '@genshin-optimizer/gi-util' +import { objMap } from '@genshin-optimizer/util' +import { CopyAll, Refresh } from '@mui/icons-material' +import CalculateIcon from '@mui/icons-material/Calculate' +import { Box, Button, Grid, Skeleton, Stack, ToggleButton } from '@mui/material' import { - Box, - Button, - ButtonGroup, - CardHeader, - Divider, - Grid, - ListItem, - MenuItem, - Skeleton, - Slider, - Stack, - ToggleButton, - Typography, -} from '@mui/material' -import React, { - createContext, - Suspense, useCallback, useContext, useDeferredValue, useEffect, useMemo, - useState, } from 'react' -import { useTranslation } from 'react-i18next' import { useLocation } from 'react-router-dom' -import ArtifactSetAutocomplete from '../../../../Components/Artifact/ArtifactSetAutocomplete' -import ArtifactSetTooltip from '../../../../Components/Artifact/ArtifactSetTooltip' -import SetEffectDisplay from '../../../../Components/Artifact/SetEffectDisplay' -import SlotIcon from '../../../../Components/Artifact/SlotIcon' -import BootstrapTooltip from '../../../../Components/BootstrapTooltip' -import CardDark from '../../../../Components/Card/CardDark' import CardLight from '../../../../Components/Card/CardLight' import StatDisplayComponent from '../../../../Components/Character/StatDisplayComponent' -import ColorText from '../../../../Components/ColoredText' import CustomNumberInput from '../../../../Components/CustomNumberInput' -import DocumentDisplay from '../../../../Components/DocumentDisplay' -import DropdownButton from '../../../../Components/DropdownMenu/DropdownButton' -import { - FieldDisplayList, - NodeFieldDisplay, -} from '../../../../Components/FieldDisplay' -import ImgIcon from '../../../../Components/Image/ImgIcon' -import LevelSelect from '../../../../Components/LevelSelect' -import RefinementDropdown from '../../../../Components/RefinementDropdown' import SolidToggleButtonGroup from '../../../../Components/SolidToggleButtonGroup' -import { - StatColoredWithUnit, - StatWithUnit, -} from '../../../../Components/StatDisplay' import { CharacterContext } from '../../../../Context/CharacterContext' import type { dataContextObj } from '../../../../Context/DataContext' import { DataContext } from '../../../../Context/DataContext' -import { getArtSheet } from '../../../../Data/Artifacts' -import Artifact from '../../../../Data/Artifacts/Artifact' -import { artifactDefIcon } from '../../../../Data/Artifacts/ArtifactSheet' -import { getWeaponSheet } from '../../../../Data/Weapons' import { initCharTC } from '../../../../Database/DataManagers/CharacterTCData' import { DatabaseContext } from '../../../../Database/Database' -import { uiInput as input } from '../../../../Formula' -import { computeUIData, dataObjForWeapon } from '../../../../Formula/api' import { constant, percent } from '../../../../Formula/utils' -import KeyMap from '../../../../KeyMap' -import StatIcon from '../../../../KeyMap/StatIcon' import useTeamData from '../../../../ReactHooks/useTeamData' import type { ICachedArtifact } from '../../../../Types/artifact' -import type { ICharTC, ICharTCArtifactSlot } from '../../../../Types/character' -import type { SetNum } from '../../../../Types/consts' +import type { ICharTC } from '../../../../Types/character' import type { ICachedWeapon } from '../../../../Types/weapon' import { defaultInitialWeaponKey } from '../../../../Util/WeaponUtil' import OptimizationTargetSelector from '../TabOptimize/Components/OptimizationTargetSelector' -import useCharTC from './useCharTC' -import useDBMeta from '../../../../ReactHooks/useDBMeta' +import { ArtifactMainStatAndSetEditor } from './ArtifactMainStatAndSetEditor' +import { ArtifactSubCard } from './ArtifactSubCard' +import type { SetCharTCAction } from './CharTCContext' +import { CharTCContext } from './CharTCContext' +import { WeaponEditorCard } from './WeaponEditorCard' import { optimizeTc } from './optimizeTc' -import CalculateIcon from '@mui/icons-material/Calculate' -const WeaponSelectionModal = React.lazy( - () => import('../../../../Components/Weapon/WeaponSelectionModal') -) - -type ISet = Partial> -export type SetCharTCAction = - | Partial - | ((v: ICharTC) => Partial) -type CharTCContexObj = { - charTC: ICharTC - setCharTC: (action: SetCharTCAction) => void -} -const CharTCContext = createContext({} as CharTCContexObj) +import useCharTC from './useCharTC' export default function TabTheorycraft() { const { database } = useContext(DatabaseContext) @@ -140,12 +61,6 @@ export default function TabTheorycraft() { const resetData = useCallback(() => { setCharTC(initCharTC(defaultInitialWeaponKey(characterSheet.weaponTypeKey))) }, [setCharTC, characterSheet]) - const setWeapon = useCallback( - (action: Partial) => { - setCharTC({ ...charTC, weapon: { ...charTC.weapon, ...action } }) - }, - [setCharTC, charTC] - ) const copyFrom = useCallback( (eWeapon: ICachedWeapon, build: ICachedArtifact[]) => { @@ -219,43 +134,6 @@ export default function TabTheorycraft() { copyFrom, ]) - const weapon: ICachedWeapon = useMemo( - () => ({ - ...charTC.weapon, - location: '', - lock: false, - id: '', - }), - [charTC] - ) - - const setArtifact = useCallback( - (artifact: ICharTC['artifact']) => { - const data_ = structuredClone(charTC) - data_.artifact = artifact - setCharTC(data_) - }, - [charTC, setCharTC] - ) - - const setSubstatsType = useCallback( - (t: SubstatTypeKey) => { - const data_ = structuredClone(charTC) - data_.artifact.substats.type = t - setCharTC(data_) - }, - [charTC, setCharTC] - ) - - const setSubstats = useCallback( - (setSubstats: Record) => { - const data_ = structuredClone(charTC) - data_.artifact.substats.stats = setSubstats - setCharTC(data_) - }, - [charTC, setCharTC] - ) - const deferredData = useDeferredValue(charTC) const overriderArtData = useMemo(() => { const stats = { ...deferredData.artifact.substats.stats } @@ -322,44 +200,31 @@ export default function TabTheorycraft() { const distributedSubstats = charTC.optimization.distributedSubstats const setDistributedSubstats = useCallback( (distributedSubstats: ICharTC['optimization']['distributedSubstats']) => { - const data_ = structuredClone(charTC) - data_.optimization.distributedSubstats = distributedSubstats - setCharTC(data_) + setCharTC((data_) => { + data_.optimization.distributedSubstats = distributedSubstats + }) }, - [charTC, setCharTC] + [setCharTC] ) - const maxSubstats = charTC.optimization.maxSubstats - const { gender } = useDBMeta() - - const optimizeSubstats = useCallback( - (apply: boolean) => - optimizeTc( - characterKey, - optimizationTarget, - database, - overriderArtData, - overrideWeapon, - gender, - charTC, - maxSubstats, - distributedSubstats, - apply, - setCharTC - ), - [ - charTC, + const optimizeSubstats = (apply: boolean) => { + const { maxBuffer, distributed = 0 } = optimizeTc( + teamData, characterKey, - database, - distributedSubstats, - gender, - maxSubstats, - optimizationTarget, - overrideWeapon, - overriderArtData, - setCharTC, - ] - ) + charTC + ) + if (!apply || !maxBuffer || !distributed) return + const comp = (statKey: string) => (statKey.endsWith('_') ? 100 : 1) + setCharTC((charTC) => { + charTC.artifact.substats.stats = objMap( + charTC.artifact.substats.stats, + (v, k) => v + (maxBuffer![k] ?? 0) * comp(k) + ) + charTC.optimization.distributedSubstats = + distributedSubstats - distributed + return charTC + }) + } return ( @@ -399,32 +264,12 @@ export default function TabTheorycraft() { - + - s.statKey - )} - maxSubstats={maxSubstats} - setMaxSubstats={(k: SubstatKey) => (v: number) => { - if (charTC.optimization.maxSubstats[k] === v) return - const data_ = structuredClone(charTC) - data_.optimization.maxSubstats[k] = v - setCharTC(data_) - }} - /> + @@ -455,7 +300,7 @@ export default function TabTheorycraft() { }} /> - {weaponSheet.hasRefinement && ( - setWeapon({ refinement: r })} - /> - )} - - - - - - - {weaponUIData && ( - - {[input.weapon.main, input.weapon.sub, input.weapon.sub2].map( - (node) => { - const n = weaponUIData.get(node) - if (n.isEmpty || !n.value) return null - return ( - - ) - } - )} - - )} - - {data && weaponSheet?.document && ( - - )} - - - ) -} - -function ArtifactMainLevelCard({ - artifactData, - setArtifactData, -}: { - artifactData: ICharTC['artifact'] - setArtifactData: (a: ICharTC['artifact']) => void -}) { - const setSlot = useCallback( - (slotKey: ArtifactSlotKey) => (slot: ICharTCArtifactSlot) => { - const artifactData_ = deepClone(artifactData) - artifactData_.slots[slotKey] = slot - setArtifactData(artifactData_) - }, - [artifactData, setArtifactData] - ) - - const setArtSet = useCallback( - (artSet: ISet) => { - const artifactData_ = deepClone(artifactData) - artifactData_.sets = artSet - setArtifactData(artifactData_) - }, - [artifactData, setArtifactData] - ) - - return ( - - - - {allArtifactSlotKeys.map((s) => ( - - ))} - - - } - > - - - - ) -} -function ArtifactMainLevelSlot({ - slotKey, - slot, - setSlot: setSlotProp, -}: { - slotKey: ArtifactSlotKey - slot: ICharTCArtifactSlot - setSlot: (s: ICharTCArtifactSlot) => void -}) { - const { level, statKey, rarity } = slot - const keys = artSlotsData[slotKey].stats - const setSlot = useCallback( - (action: Partial) => { - setSlotProp({ ...slot, ...action }) - }, - [slot, setSlotProp] - ) - const setRarity = useCallback( - (r: ArtifactRarity) => { - const mLvl = artMaxLevel[r] ?? 0 - if (level > mLvl) setSlot({ rarity: r, level: mLvl }) - else setSlot({ rarity: r }) - }, - [level, setSlot] - ) - - return ( - - - - {keys.length === 1 ? ( - - {' '} - {KeyMap.getStr(keys[0])} - - ) : ( - } - color={KeyMap.getVariant(statKey) ?? 'success'} - > - {keys.map((msk) => ( - setSlot({ statKey: msk })} - > - - - ))} - - )} - - - {rarity} - - } - > - {[5, 4, 3].map((r) => ( - setRarity(r as ArtifactRarity)} - > - - {r} - - - ))} - - l !== undefined && setSlot({ level: l })} - sx={{ borderRadius: 1, pl: 1, my: 0, height: '100%' }} - inputProps={{ sx: { pl: 0.5, width: '2em' }, max: 20, min: 0 }} - /> - - {`${artDisplayValue( - getMainStatDisplayValue(statKey, rarity, level), - KeyMap.unit(statKey) - )}${KeyMap.unit(statKey)}`} - - - ) -} - -function ArtifactSetsEditor({ - artSet, - setArtSet, -}: { - artSet: ISet - setArtSet(artSet: ISet) -}) { - const setSet = useCallback( - (setKey: ArtifactSetKey | '') => { - if (!setKey) return - setArtSet({ - ...artSet, - [setKey]: parseInt(Object.keys(getArtSheet(setKey).setEffects)[0]), - }) - }, - [artSet, setArtSet] - ) - - const setValue = useCallback( - (setKey: ArtifactSetKey) => (value: 1 | 2 | 4) => - setArtSet({ ...artSet, [setKey]: value }), - [artSet, setArtSet] - ) - const deleteValue = useCallback( - (setKey: ArtifactSetKey) => () => { - const { [setKey]: _, ...rest } = artSet - setArtSet({ ...rest }) - }, - [artSet, setArtSet] - ) - - const remaining = 5 - Object.values(artSet).reduce((a, b) => a + b, 0) - - return ( - - {Object.entries(artSet).map(([setKey, value]) => ( - - ))} - - - Object.keys(artSet).includes(key as ArtifactSetKey) || - !key || - Object.keys(getArtSheet(key).setEffects).every( - (n) => parseInt(n) > remaining - ) - } - /> - - - ) -} -function ArtifactSetEditor({ - setKey, - value, - setValue, - deleteValue, - remaining, -}: { - setKey: ArtifactSetKey - value: 1 | 2 | 4 - setValue: (v: 1 | 2 | 4) => void - deleteValue: () => void - remaining: number -}) { - const artifactSheet = getArtSheet(setKey) - - /* Assumes that all conditionals are from 4-Set. needs to change if there are 2-Set conditionals */ - const set4CondNums = useMemo(() => { - if (value < 4) return [] - return Object.keys(artifactSheet.setEffects).filter((setNumKey) => - artifactSheet.setEffects[setNumKey]?.document.some( - (doc) => 'states' in doc - ) - ) - }, [artifactSheet, value]) - - return ( - - - - - - {artifactSheet.setName} - - - - - {value}-set} - > - {Object.keys(artifactSheet.setEffects) - .map((setKey) => parseInt(setKey)) - .map((setKey) => ( - remaining + value} - onClick={() => setValue(setKey as 1 | 2 | 4)} - > - {setKey}-set - - ))} - - - - - {!!set4CondNums.length && ( - - {set4CondNums.map((setNumKey) => ( - - ))} - - )} - - ) -} -function ArtifactSubCard({ - substats, - setSubstats, - substatsType, - setSubstatsType, - mainStatKeys, - maxSubstats, - setMaxSubstats, -}: { - substats: Record - setSubstats: (substats: Record) => void - substatsType: SubstatTypeKey - setSubstatsType: (t: SubstatTypeKey) => void - mainStatKeys: MainStatKey[] - maxSubstats: Record - setMaxSubstats: (k: SubstatKey) => (v: number) => void -}) { - const setValue = useCallback( - (key: SubstatKey) => (v: number) => setSubstats({ ...substats, [key]: v }), - [substats, setSubstats] - ) - const { t } = useTranslation('page_character') - const rv = - Object.entries(substats).reduce( - (t, [k, v]) => t + v / getSubstatValue(k), - 0 - ) * 100 - const rolls = Object.entries(substats).reduce( - (t, [k, v]) => t + v / getSubstatValue(k, undefined, substatsType), - 0 - ) - return ( - - - - - {substatTypeKeys.map((st) => ( - setSubstatsType(st)} - > - {t(`tabTheorycraft.substatType.${st}`)} - - ))} - - {t`tabTheorycraft.maxTotalRolls`}} - placement="top" - > - - 45 ? 'warning' : undefined}> - Rolls: {rolls.toFixed(0)} - - 45 ? 'warning' : undefined}> - RV: {rv.toFixed()}% - - - - - - {Object.entries(substats).map(([k, v]) => ( - - ))} - - - ) -} -function ArtifactSubstatEditor({ - statKey, - value, - setValue, - substatsType, - mainStatKeys, - maxSubstat, - setMaxSubstat, -}: { - statKey: SubstatKey - value: number - setValue: (v: number) => void - substatsType: SubstatTypeKey - mainStatKeys: MainStatKey[] - maxSubstat: number - setMaxSubstat: (v: number) => void -}) { - // const { t } = useTranslation('page_character') - const substatValue = getSubstatValue(statKey, 5, substatsType) - const [rolls, setRolls] = useState(() => value / substatValue) - useEffect(() => setRolls(value / substatValue), [value, substatValue]) - - const unit = KeyMap.unit(statKey) - const displayValue = rolls * substatValue - - const rv = ((rolls * substatValue) / getSubstatValue(statKey)) * 100 - const numMains = mainStatKeys.reduce( - (t, ms) => t + (ms === statKey ? 1 : 0), - 0 - ) - const maxRolls = (5 - numMains) * 6 - // 0.0001 to nudge float comparasion - const invalid = rolls - 0.0001 > maxRolls - const setRValue = useCallback( - (r: number) => setValue(r * substatValue), - [setValue, substatValue] - ) - - return ( - - - - } - value={parseFloat(displayValue.toFixed(2))} - onChange={(v) => v !== undefined && setValue(v)} - sx={{ borderRadius: 1, px: 1, height: '100%', width: '5em' }} - inputProps={{ sx: { textAlign: 'right' }, min: 0 }} - /> - - - {KeyMap.getStr(statKey)} - {KeyMap.unit(statKey)} - - - - {artDisplayValue(substatValue, unit)} - {unit} - - x - - } - value={parseFloat(rolls.toFixed(2))} - onChange={(v) => v !== undefined && setValue(v * substatValue)} - sx={{ borderRadius: 1, px: 1, my: 0, height: '100%', width: '5.5em' }} - inputProps={{ sx: { textAlign: 'right', pr: 0.5 }, min: 0, step: 1 }} - /> - {/* {t(numMains ? `tabTheorycraft.maxRollsMain` : `tabTheorycraft.maxRolls`, { value: maxRolls })}} placement="top"> */} - - - RV: {rv.toFixed()}% - - - {/* */} - - - - setRolls(v as number)} - onChangeCommitted={(e, v) => setRValue(v as number)} - /> - - - v !== undefined && setMaxSubstat(v)} - color={maxSubstat > maxRolls ? 'warning' : 'success'} - sx={{ borderRadius: 1, px: 1, my: 0, height: '100%', width: '6em' }} - inputProps={{ sx: { textAlign: 'right', pr: 0.5 }, min: 0, step: 1 }} - /> - - - ) -} - -function ArtifactAllSubstatEditor() { - const [rolls, setRolls] = useState(undefined as undefined | number) - const [maxSubstat, setMaxSubstat] = useState(undefined as undefined | number) - const { setCharTC } = useContext(CharTCContext) - - const rollsDeferred = useDeferredValue(rolls) - useEffect(() => { - if (rollsDeferred === undefined) return - setCharTC((charTC) => { - const stats = charTC.artifact.substats.stats - const substatsType = charTC.artifact.substats.type - charTC.artifact.substats.stats = objMap(stats, (val, statKey) => { - const substatValue = getSubstatValue(statKey, 5, substatsType) - return substatValue * rollsDeferred - }) - return charTC - }) - }, [setCharTC, rollsDeferred]) - - const maxSubstatDeferred = useDeferredValue(maxSubstat) - useEffect(() => { - if (maxSubstatDeferred === undefined) return - setCharTC((charTC) => { - charTC.optimization.maxSubstats = objMap( - charTC.optimization.maxSubstats, - (_val, _statKey) => maxSubstatDeferred - ) - return charTC - }) - }, [setCharTC, maxSubstatDeferred]) - - const maxRolls = 30 - // 0.0001 to nudge float comparasion - const invalid = (rolls ?? 0 - 0.0001) > maxRolls - - return ( - - - setRolls(v as number)} - onChangeCommitted={(e, v) => setRolls(v as number)} - /> - - All Rolls} - value={parseFloat((rolls ?? 0).toFixed(2))} - onChange={(v) => v !== undefined && setRolls(v)} - sx={{ borderRadius: 1, px: 1, my: 0, height: '100%', width: '7em' }} - inputProps={{ sx: { textAlign: 'right', pr: 0.5 }, min: 0, step: 1 }} - /> - All Max} - onChange={(v) => v !== undefined && setMaxSubstat(v)} - color={(maxSubstat ?? 0) > maxRolls ? 'warning' : 'success'} - sx={{ borderRadius: 1, px: 1, my: 0, height: '100%', width: '6.5em' }} - inputProps={{ sx: { textAlign: 'right', pr: 0.5 }, min: 0, step: 1 }} - /> - - ) -} diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.ts b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.ts new file mode 100644 index 0000000000..3e04ae0b08 --- /dev/null +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.ts @@ -0,0 +1,141 @@ +import type { SubstatKey } from '@genshin-optimizer/consts' +import { allSubstatKeys, type CharacterKey } from '@genshin-optimizer/consts' +import { getSubstatValue } from '@genshin-optimizer/gi-util' +import { objMap } from '@genshin-optimizer/util' +import type { TeamData } from '../../../../Context/DataContext' +import { mergeData } from '../../../../Formula/api' +import { mapFormulas } from '../../../../Formula/internal' +import { optimize, precompute } from '../../../../Formula/optimization' +import type { NumNode } from '../../../../Formula/type' +import { constant } from '../../../../Formula/utils' +import type { ICharTC } from '../../../../Types/character' +import { objPathValue, shouldShowDevComponents } from '../../../../Util/Util' +import { dynamicData } from '../TabOptimize/foreground' + +// This solves +// $\argmax_{x\in N^k, \sum x <= `distributedSubstats`, x <= `maxSubstats`} `optimizationTarget`(x)$ without assumptions on the properties of `optimizationTarget` +// where $N$ are the natural numbers and $k$ is the number of `SubstatKey`s +// We brute force iterate over all substats in the graph and compute the maximum +// n.b. some substat combinations may not be materializable into real artifacts +export function optimizeTc( + teamDataProp: TeamData, + characterKey: CharacterKey, + charTC: ICharTC +) { + const startTime = performance.now() + const { + target: optimizationTarget, + distributedSubstats, + maxSubstats, + } = charTC.optimization + if (!optimizationTarget) return {} + const workerData = teamDataProp[characterKey]?.target.data![0] + if (!workerData) return {} + // TODO: It may be better to use different dynamic data and add extra nodes to workerData during optimize so that you don't need to re-constant fold artifact set nodes later. + // https://github.com/frzyc/genshin-optimizer/pull/781#discussion_r1138023281 + Object.assign(workerData, mergeData([workerData, dynamicData])) // Mark art fields as dynamic + const unoptimizedOptimizationTargetNode = objPathValue( + workerData.display ?? {}, + optimizationTarget + ) as NumNode | undefined + if (!unoptimizedOptimizationTargetNode) return {} + const unoptimizedNodes = [unoptimizedOptimizationTargetNode] + let nodes = optimize( + unoptimizedNodes, + workerData, + ({ path: [p] }) => p !== 'dyn' + ) + // Const fold read nodes + nodes = mapFormulas( + nodes, + (f) => { + if (f.operation === 'read' && f.path[0] === 'dyn') { + const a = charTC.artifact.sets[f.path[1]] + if (a) return constant(a) + if (!allSubstatKeys.includes(f.path[1] as any)) return constant(0) + } + return f + }, + (f) => f + ) + nodes = optimize(nodes, {}, (_) => false) + + const subs = new Set() + const compute = precompute( + nodes, + {}, + (f) => { + subs.add(f.path[1]) + return f.path[1] + }, + 2 + ) + + const comp = (statKey: string) => (statKey.endsWith('_') ? 100 : 1) + const substatValue = (x: string, m: number) => + m * + getSubstatValue(x as SubstatKey, 5, charTC.artifact.substats.type, false) + + let maxBuffer: Record = Object.fromEntries( + [...subs].map((x) => [x, 0]) + ) + const subsArr = [...subs] + let distributed = distributedSubstats + const assignableMaxTot = subsArr.reduce((a, x) => a + maxSubstats[x], 0) + if (assignableMaxTot <= distributedSubstats) { + distributed = assignableMaxTot + maxBuffer = Object.fromEntries( + subsArr.map((x) => [x, substatValue(x, maxSubstats[x])]) + ) + if (shouldShowDevComponents) + console.log({ maxBuffer, subsArr, maxSubstats, distributed }) + } else { + let max = -Infinity + const buffer = Object.fromEntries([...subs].map((x) => [x, 0])) + const existingSubs = objMap( + charTC.artifact.substats.stats, + (v, k) => v / comp(k) + ) + const permute = (toAssign: number, [x, ...xs]: string[]) => { + if (xs.length === 0) { + if (toAssign > maxSubstats[x]) return + buffer[x] = substatValue(x, toAssign) + const [result] = compute([ + { values: existingSubs }, + { values: buffer }, + ] as const) + if (result > max) { + max = result + maxBuffer = structuredClone(buffer) + } + return + } + for (let i = 0; i <= Math.min(maxSubstats[x], toAssign); i++) { + // TODO: Making sure that i + \sum { maxSubstats[xs] } >= distributedSubstats in each recursion will reduce unnecessary recursion considerably for large problems. It will also tighten the possibilities for the leaf recursion, so you don't need so many checkings. + // https://github.com/frzyc/genshin-optimizer/pull/781#discussion_r1138083742 + buffer[x] = substatValue(x, i) + permute(toAssign - i, xs) + } + } + permute(distributedSubstats, subsArr) + if (shouldShowDevComponents) { + console.log(`Took ${performance.now() - startTime} ms`) + console.log({ + maxBuffer, + maxBufferInt: objMap( + maxBuffer, + (v, k) => + v / + getSubstatValue( + k as SubstatKey, + 5, + charTC.artifact.substats.type, + false + ) + ), + subsArr, + }) + } + } + return { maxBuffer, distributed } +} diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.tsx deleted file mode 100644 index bd6957ec37..0000000000 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.tsx +++ /dev/null @@ -1,177 +0,0 @@ -import type { GenderKey, SubstatKey } from '@genshin-optimizer/consts' -import { - allSubstatKeys, - type ArtifactSetKey, - type CharacterKey, -} from '@genshin-optimizer/consts' -import { getSubstatValue } from '@genshin-optimizer/gi-util' -import { objMap } from '@genshin-optimizer/util' -import type { SetCharTCAction } from '.' -import type { ArtCharDatabase } from '../../../../Database/Database' -import { mergeData, uiDataForTeam } from '../../../../Formula/api' -import { mapFormulas } from '../../../../Formula/internal' -import { optimize, precompute } from '../../../../Formula/optimization' -import type { NumNode } from '../../../../Formula/type' -import { constant } from '../../../../Formula/utils' -import { getTeamData } from '../../../../ReactHooks/useTeamData' -import type { ICharTC } from '../../../../Types/character' -import type { ICachedWeapon } from '../../../../Types/weapon' -import { objPathValue, objectMap } from '../../../../Util/Util' -import { dynamicData } from '../TabOptimize/foreground' - -// This solves -// $\argmax_{x\in N^k, \sum x <= `distributedSubstats`, x <= `maxSubstats`} `optimizationTarget`(x)$ without assumptions on the properties of `optimizationTarget` -// where $N$ are the natural numbers and $k$ is the number of `SubstatKey`s -// We brute force iterate over all substats in the graph and compute the maximum -// n.b. some substat combinations may not be materializable into real artifacts -export function optimizeTc( - characterKey: CharacterKey, - optimizationTarget: string[] | undefined, - database: ArtCharDatabase, - overriderArtData: { - art: Record - artSet: Partial> - }, - overrideWeapon: ICachedWeapon, - gender: GenderKey, - charTC: ICharTC, - maxSubstats: Record, - distributedSubstats: number, - apply: boolean, - setCharTC: (data: SetCharTCAction) => void -): () => void { - return () => { - const startTime = performance.now() - if (!optimizationTarget) return - const teamData = getTeamData( - database, - characterKey, - 0, - overriderArtData, - overrideWeapon - ) - if (!teamData) return - const workerData = uiDataForTeam(teamData.teamData, gender, characterKey)[ - characterKey - ]?.target.data![0] - if (!workerData) return - // TODO: It may be better to use different dynamic data and add extra nodes to workerData during optimize so that you don't need to re-constant fold artifact set nodes later. - // https://github.com/frzyc/genshin-optimizer/pull/781#discussion_r1138023281 - Object.assign(workerData, mergeData([workerData, dynamicData])) // Mark art fields as dynamic - const unoptimizedOptimizationTargetNode = objPathValue( - workerData.display ?? {}, - optimizationTarget - ) as NumNode | undefined - if (!unoptimizedOptimizationTargetNode) return - const unoptimizedNodes = [unoptimizedOptimizationTargetNode] - let nodes = optimize( - unoptimizedNodes, - workerData, - ({ path: [p] }) => p !== 'dyn' - ) - // Const fold read nodes - nodes = mapFormulas( - nodes, - (f) => { - if (f.operation === 'read' && f.path[0] === 'dyn') { - const a = charTC.artifact.sets[f.path[1]] - if (a) return constant(a) - if (!allSubstatKeys.includes(f.path[1] as any)) return constant(0) - } - return f - }, - (f) => f - ) - nodes = optimize(nodes, {}, (_) => false) - - const subs = new Set() - const compute = precompute( - nodes, - {}, - (f) => { - subs.add(f.path[1]) - return f.path[1] - }, - 2 - ) - - const comp = (statKey: string) => (statKey.endsWith('_') ? 100 : 1) - const substatValue = (x: string, m: number) => - (getSubstatValue(x as SubstatKey, 5, charTC.artifact.substats.type) / - comp(x)) * - m - - let maxBuffer: Record = Object.fromEntries( - [...subs].map((x) => [x, 0]) - ) - const subsArr = [...subs] - if ( - subsArr.reduce((a, x) => a + maxSubstats[x], 0) <= distributedSubstats - ) { - maxBuffer = Object.fromEntries( - subsArr.map((x) => [x, substatValue(x, maxSubstats[x])]) - ) - if (process.env.NODE_ENV === 'development') console.log(maxBuffer) - } else { - let max = -Infinity - const buffer = Object.fromEntries([...subs].map((x) => [x, 0])) - const bufferSubs = objectMap( - charTC.artifact.substats.stats, - (v, k) => v / comp(k) - ) - const permute = (distributedSubstats: number, [x, ...xs]: string[]) => { - if (xs.length === 0) { - if (distributedSubstats > maxSubstats[x]) return - buffer[x] = substatValue(x, distributedSubstats) - const [result] = compute([ - { values: bufferSubs }, - { values: buffer }, - ] as const) - if (result > max) { - max = result - maxBuffer = structuredClone(buffer) - } - return - } - for ( - let i = 0; - i <= Math.min(maxSubstats[x], distributedSubstats); - i++ - ) { - // TODO: Making sure that i + \sum { maxSubstats[xs] } >= distributedSubstats in each recursion will reduce unnecessary recursion considerably for large problems. It will also tighten the possibilities for the leaf recursion, so you don't need so many checkings. - // https://github.com/frzyc/genshin-optimizer/pull/781#discussion_r1138083742 - buffer[x] = substatValue(x, i) - permute(distributedSubstats - i, xs) - } - } - permute(distributedSubstats, subsArr) - if (process.env.NODE_ENV === 'development') { - console.log(`Took ${performance.now() - startTime} ms`) - console.log(maxBuffer) - console.log( - objMap(maxBuffer!, (v, x) => - allSubstatKeys.includes(x as any) - ? v / - (getSubstatValue( - x as SubstatKey, - 5, - charTC.artifact.substats.type - ) / - comp(x)) - : v - ) - ) - } - } - - if (apply) { - const data_ = structuredClone(charTC) - data_.artifact.substats.stats = objectMap( - charTC.artifact.substats.stats, - (v, k) => v + (maxBuffer![k] ?? 0) * comp(k) - ) - data_.optimization.distributedSubstats = 0 - setCharTC(data_) - } - } -} diff --git a/libs/gi-util/src/artifact/artifact.ts b/libs/gi-util/src/artifact/artifact.ts index 7e261fbb80..d309a20dfb 100644 --- a/libs/gi-util/src/artifact/artifact.ts +++ b/libs/gi-util/src/artifact/artifact.ts @@ -78,19 +78,26 @@ export function getSubstatEfficiency( return max ? clampPercent((sum / max) * 100) : 0 } +const substatCache = new Map() export function getSubstatValue( substatKey: SubstatKey, rarity: RarityKey = 5, - type: 'max' | 'min' | 'mid' = 'max' + type: 'max' | 'min' | 'mid' = 'max', + percent = true ): number { - const substats = allStats.art.sub[rarity][substatKey] - const value = - type === 'max' - ? Math.max(...substats) - : type === 'min' - ? Math.min(...substats) - : substats.reduce((a, b) => a + b, 0) / substats.length - return toPercent(value, substatKey) + const cacheKey = `${substatKey},${rarity},${type}` + let value = substatCache.get(cacheKey) + if (!value) { + const substats = allStats.art.sub[rarity][substatKey] + value = + type === 'max' + ? Math.max(...substats) + : type === 'min' + ? Math.min(...substats) + : substats.reduce((a, b) => a + b, 0) / substats.length + substatCache.set(cacheKey, value) + } + return percent ? toPercent(value, substatKey) : value } /** From 282ae64651e579f6ee15b7e3cdc9bd6ba68495d8 Mon Sep 17 00:00:00 2001 From: frzyc Date: Sun, 24 Dec 2023 05:36:37 -0500 Subject: [PATCH 25/61] add rarity + better max --- .../Database/DataManagers/CharacterTCData.ts | 7 ++-- .../ArtifactAllSubstatEditor.tsx | 34 ++++++++++++++---- .../ArtifactSubCard/ArtifactSubstatEditor.tsx | 18 ++++++---- .../TabTheorycraft/ArtifactSubCard/index.tsx | 20 +++++++++-- .../Tabs/TabTheorycraft/index.tsx | 1 + .../Tabs/TabTheorycraft/optimizeTc.ts | 35 +++++++++++-------- apps/frontend/src/app/Types/character.d.ts | 1 + .../assets/locales/en/page_character.json | 6 ++-- libs/util/src/lib/number.ts | 8 +++++ 9 files changed, 96 insertions(+), 34 deletions(-) diff --git a/apps/frontend/src/app/Database/DataManagers/CharacterTCData.ts b/apps/frontend/src/app/Database/DataManagers/CharacterTCData.ts index a9497eb77b..b4baf24908 100644 --- a/apps/frontend/src/app/Database/DataManagers/CharacterTCData.ts +++ b/apps/frontend/src/app/Database/DataManagers/CharacterTCData.ts @@ -6,6 +6,7 @@ import type { WeaponKey, } from '@genshin-optimizer/consts' import { + allArtifactRarityKeys, allArtifactSlotKeys, allSubstatKeys, allWeaponKeys, @@ -73,6 +74,7 @@ export function initCharTC(weaponKey: WeaponKey): ICharTC { substats: { type: 'max', stats: objKeyMap(allSubstatKeys, () => 0), + rarity: 5, }, sets: {}, }, @@ -115,13 +117,14 @@ function validateCharTCArtifact( if (typeof artifact !== 'object') return undefined let { slots, - substats: { type, stats }, + substats: { type, stats, rarity }, sets, } = artifact as ICharTC['artifact'] const _slots = validateCharTCArtifactSlots(slots) if (!_slots) return undefined slots = _slots if (!substatTypeKeys.includes(type)) type = 'max' + if (!allArtifactRarityKeys.includes(rarity)) rarity = 5 if (typeof stats !== 'object') stats = objKeyMap(allSubstatKeys, () => 0) stats = objKeyMap(allSubstatKeys, (k) => typeof stats[k] === 'number' ? stats[k] : 0 @@ -130,7 +133,7 @@ function validateCharTCArtifact( if (typeof sets !== 'object') sets = {} // TODO: validate sets - return { slots, substats: { type, stats }, sets } + return { slots, substats: { type, stats, rarity }, sets } } function validateCharTCArtifactSlots( slots: unknown diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactAllSubstatEditor.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactAllSubstatEditor.tsx index d7395d8d4f..18ea5b365a 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactAllSubstatEditor.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactAllSubstatEditor.tsx @@ -4,22 +4,44 @@ import { Box, Slider } from '@mui/material' import { useContext, useDeferredValue, useEffect, useState } from 'react' import CardDark from '../../../../../Components/Card/CardDark' import CustomNumberInput from '../../../../../Components/CustomNumberInput' +import type { ICharTC } from '../../../../../Types/character' import { CharTCContext } from '../CharTCContext' const DEFAULT_MAX_ROLLS = 30 + +function getMinRoll(charTC: ICharTC) { + const { + artifact: { + substats: { stats, rarity, type }, + }, + } = charTC + return Math.floor( + Math.min( + ...Object.entries(stats).map( + ([k, v]) => v / getSubstatValue(k, rarity, type) + ) + ) + ) +} +function getMinMax(charTC: ICharTC) { + return Math.floor(Math.min(...Object.values(charTC.optimization.maxSubstats))) +} export function ArtifactAllSubstatEditor() { - const [rolls, setRolls] = useState(undefined as undefined | number) - const [maxSubstat, setMaxSubstat] = useState(undefined as undefined | number) - const { setCharTC } = useContext(CharTCContext) + const { charTC, setCharTC } = useContext(CharTCContext) + const [rolls, setRolls] = useState(() => getMinRoll(charTC)) + const [maxSubstat, setMaxSubstat] = useState(() => getMinMax(charTC)) const rollsDeferred = useDeferredValue(rolls) useEffect(() => { if (rollsDeferred === undefined) return setCharTC((charTC) => { - const stats = charTC.artifact.substats.stats - const substatsType = charTC.artifact.substats.type + const { + artifact: { + substats: { stats, type, rarity }, + }, + } = charTC charTC.artifact.substats.stats = objMap(stats, (val, statKey) => { - const substatValue = getSubstatValue(statKey, 5, substatsType) + const substatValue = getSubstatValue(statKey, rarity, type) return substatValue * rollsDeferred }) }) diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactSubstatEditor.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactSubstatEditor.tsx index c705d81259..b79e8cc169 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactSubstatEditor.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactSubstatEditor.tsx @@ -1,4 +1,4 @@ -import type { SubstatKey } from '@genshin-optimizer/consts' +import { artMaxLevel, type SubstatKey } from '@genshin-optimizer/consts' import { artDisplayValue, getSubstatValue } from '@genshin-optimizer/gi-util' import { Box, Slider, Stack } from '@mui/material' import { useCallback, useContext, useEffect, useState } from 'react' @@ -14,7 +14,7 @@ export function ArtifactSubstatEditor({ statKey }: { statKey: SubstatKey }) { charTC: { artifact: { slots, - substats: { type: substatsType, stats: substats }, + substats: { type: substatsType, stats: substats, rarity }, }, optimization: { maxSubstats }, }, @@ -40,7 +40,7 @@ export function ArtifactSubstatEditor({ statKey }: { statKey: SubstatKey }) { [setCharTC, statKey] ) // const { t } = useTranslation('page_character') - const substatValue = getSubstatValue(statKey, 5, substatsType) + const substatValue = getSubstatValue(statKey, rarity, substatsType) const [rolls, setRolls] = useState(() => value / substatValue) useEffect(() => setRolls(value / substatValue), [value, substatValue]) @@ -52,7 +52,7 @@ export function ArtifactSubstatEditor({ statKey }: { statKey: SubstatKey }) { (t, ms) => t + (ms === statKey ? 1 : 0), 0 ) - const maxRolls = (5 - numMains) * 6 + const maxRolls = (5 - numMains) * (artMaxLevel[rarity] / 4 + 1) // 0.0001 to nudge float comparasion const invalid = rolls - 0.0001 > maxRolls const setRValue = useCallback( @@ -146,7 +146,7 @@ export function ArtifactSubstatEditor({ statKey }: { statKey: SubstatKey }) { v !== undefined && setMaxSubstat(v)} - color={maxSubstat > maxRolls ? 'warning' : 'success'} + color={ + rolls > maxSubstat + ? 'error' + : maxSubstat > maxRolls + ? 'warning' + : 'success' + } sx={{ borderRadius: 1, px: 1, my: 0, height: '100%', width: '6em' }} inputProps={{ sx: { textAlign: 'right', pr: 0.5 }, min: 0, step: 1 }} /> diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/index.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/index.tsx index 41437dba24..f8c1fd4465 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/index.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/index.tsx @@ -1,4 +1,4 @@ -import type { SubstatTypeKey } from '@genshin-optimizer/consts' +import type { ArtifactRarity, SubstatTypeKey } from '@genshin-optimizer/consts' import { substatTypeKeys } from '@genshin-optimizer/consts' import { getSubstatValue } from '@genshin-optimizer/gi-util' import { Box, MenuItem, Stack, Typography } from '@mui/material' @@ -12,12 +12,13 @@ import DropdownButton from '../../../../../Components/DropdownMenu/DropdownButto import { CharTCContext } from '../CharTCContext' import { ArtifactAllSubstatEditor } from './ArtifactAllSubstatEditor' import { ArtifactSubstatEditor } from './ArtifactSubstatEditor' +import ArtifactRarityDropdown from '../../../../../Components/Artifact/ArtifactRarityDropdown' export function ArtifactSubCard() { const { t } = useTranslation('page_character') const { charTC: { artifact: { - substats: { type: substatsType, stats: substats }, + substats: { type: substatsType, stats: substats, rarity }, }, }, setCharTC, @@ -30,6 +31,14 @@ export function ArtifactSubCard() { }, [setCharTC] ) + const setRarity = useCallback( + (r: ArtifactRarity) => { + setCharTC((charTC) => { + charTC.artifact.substats.rarity = r + }) + }, + [setCharTC] + ) const rv = Object.entries(substats).reduce( @@ -37,7 +46,7 @@ export function ArtifactSubCard() { 0 ) * 100 const rolls = Object.entries(substats).reduce( - (t, [k, v]) => t + v / getSubstatValue(k, undefined, substatsType), + (t, [k, v]) => t + v / getSubstatValue(k, rarity, substatsType), 0 ) return ( @@ -58,6 +67,11 @@ export function ArtifactSubCard() { ))} + setRarity(r)} + filter={(r) => r !== rarity} + /> {t`tabTheorycraft.maxTotalRolls`}} placement="top" diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx index 1987cbb99a..eeed033c0d 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx @@ -281,6 +281,7 @@ export default function TabTheorycraft() { /> v !== undefined && setDistributedSubstats(v)} endAdornment={'Substats'} sx={{ diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.ts b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.ts index 3e04ae0b08..71c528b112 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.ts +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.ts @@ -1,7 +1,7 @@ import type { SubstatKey } from '@genshin-optimizer/consts' import { allSubstatKeys, type CharacterKey } from '@genshin-optimizer/consts' import { getSubstatValue } from '@genshin-optimizer/gi-util' -import { objMap } from '@genshin-optimizer/util' +import { clampLow, objMap } from '@genshin-optimizer/util' import type { TeamData } from '../../../../Context/DataContext' import { mergeData } from '../../../../Formula/api' import { mapFormulas } from '../../../../Formula/internal' @@ -24,10 +24,15 @@ export function optimizeTc( ) { const startTime = performance.now() const { - target: optimizationTarget, - distributedSubstats, - maxSubstats, - } = charTC.optimization + artifact: { + substats: { stats: substats, type: substatsType, rarity }, + }, + optimization: { + target: optimizationTarget, + distributedSubstats, + maxSubstats: rawMaxSubstats, + }, + } = charTC if (!optimizationTarget) return {} const workerData = teamDataProp[characterKey]?.target.data![0] if (!workerData) return {} @@ -73,14 +78,22 @@ export function optimizeTc( const comp = (statKey: string) => (statKey.endsWith('_') ? 100 : 1) const substatValue = (x: string, m: number) => - m * - getSubstatValue(x as SubstatKey, 5, charTC.artifact.substats.type, false) + m * getSubstatValue(x as SubstatKey, rarity, substatsType, false) let maxBuffer: Record = Object.fromEntries( [...subs].map((x) => [x, 0]) ) const subsArr = [...subs] let distributed = distributedSubstats + const maxSubstats = objMap(rawMaxSubstats, (v, k) => { + return ( + v - + clampLow( + Math.ceil(substats[k] / getSubstatValue(k, rarity, substatsType)), + 0 + ) + ) + }) const assignableMaxTot = subsArr.reduce((a, x) => a + maxSubstats[x], 0) if (assignableMaxTot <= distributedSubstats) { distributed = assignableMaxTot @@ -125,13 +138,7 @@ export function optimizeTc( maxBufferInt: objMap( maxBuffer, (v, k) => - v / - getSubstatValue( - k as SubstatKey, - 5, - charTC.artifact.substats.type, - false - ) + v / getSubstatValue(k as SubstatKey, rarity, substatsType, false) ), subsArr, }) diff --git a/apps/frontend/src/app/Types/character.d.ts b/apps/frontend/src/app/Types/character.d.ts index 750f7abab9..dc0a54bd54 100644 --- a/apps/frontend/src/app/Types/character.d.ts +++ b/apps/frontend/src/app/Types/character.d.ts @@ -75,6 +75,7 @@ export type ICharTC = { substats: { type: SubstatTypeKey stats: Record + rarity:ArtifactRarity } sets: Partial> } diff --git a/libs/gi-localization/assets/locales/en/page_character.json b/libs/gi-localization/assets/locales/en/page_character.json index dd04f2e8ca..981f986580 100644 --- a/libs/gi-localization/assets/locales/en/page_character.json +++ b/libs/gi-localization/assets/locales/en/page_character.json @@ -46,9 +46,9 @@ }, "tabTheorycraft": { "substatType": { - "min": "Use minimum substat rolls", - "max": "Use maximum substat rolls", - "mid": "Use median substat rolls" + "min": "Min substat roll", + "max": "Max substat roll", + "mid": "Median substat roll" }, "maxTotalRolls": "A \"valid\" build can only have a maximum of 45 rolls.", "maxRolls": "This substat can have a maximum of 30 rolls.", diff --git a/libs/util/src/lib/number.ts b/libs/util/src/lib/number.ts index 4e69a6df86..03440b5d8e 100644 --- a/libs/util/src/lib/number.ts +++ b/libs/util/src/lib/number.ts @@ -3,6 +3,14 @@ export const clamp = (val: number, low: number, high: number) => { if (val > high) return high return val } +export const clampLow = (val: number, low: number) => { + if (val < low) return low + return val +} +export const clampHigh = (val: number, high: number) => { + if (val > high) return high + return val +} export const clamp01 = (val: number) => clamp(val, 0, 1) export const clampPercent = (val: number) => clamp(val, 0, 100) From 8ddc26b4fbc269c736cb7c008eb8ea2460ca59c1 Mon Sep 17 00:00:00 2001 From: frzyc Date: Sun, 24 Dec 2023 05:42:53 -0500 Subject: [PATCH 26/61] update roll calc --- .../ArtifactSubCard/ArtifactSubstatEditor.tsx | 3 ++- .../Tabs/TabTheorycraft/ArtifactSubCard/index.tsx | 10 ++++++---- apps/frontend/src/app/Types/character.d.ts | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactSubstatEditor.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactSubstatEditor.tsx index b79e8cc169..e5043b781c 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactSubstatEditor.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactSubstatEditor.tsx @@ -161,7 +161,8 @@ export function ArtifactSubstatEditor({ statKey }: { statKey: SubstatKey }) { startAdornment={'Max'} onChange={(v) => v !== undefined && setMaxSubstat(v)} color={ - rolls > maxSubstat + // 0.0001 to nudge float comparasion + rolls - 0.0001 > maxSubstat ? 'error' : maxSubstat > maxRolls ? 'warning' diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/index.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/index.tsx index f8c1fd4465..20384f2d78 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/index.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/index.tsx @@ -1,9 +1,10 @@ import type { ArtifactRarity, SubstatTypeKey } from '@genshin-optimizer/consts' -import { substatTypeKeys } from '@genshin-optimizer/consts' +import { artSubstatRollData, substatTypeKeys } from '@genshin-optimizer/consts' import { getSubstatValue } from '@genshin-optimizer/gi-util' import { Box, MenuItem, Stack, Typography } from '@mui/material' import { useCallback, useContext } from 'react' import { useTranslation } from 'react-i18next' +import ArtifactRarityDropdown from '../../../../../Components/Artifact/ArtifactRarityDropdown' import BootstrapTooltip from '../../../../../Components/BootstrapTooltip' import CardDark from '../../../../../Components/Card/CardDark' import CardLight from '../../../../../Components/Card/CardLight' @@ -12,7 +13,6 @@ import DropdownButton from '../../../../../Components/DropdownMenu/DropdownButto import { CharTCContext } from '../CharTCContext' import { ArtifactAllSubstatEditor } from './ArtifactAllSubstatEditor' import { ArtifactSubstatEditor } from './ArtifactSubstatEditor' -import ArtifactRarityDropdown from '../../../../../Components/Artifact/ArtifactRarityDropdown' export function ArtifactSubCard() { const { t } = useTranslation('page_character') const { @@ -49,6 +49,8 @@ export function ArtifactSubCard() { (t, [k, v]) => t + v / getSubstatValue(k, rarity, substatsType), 0 ) + const { high, numUpgrades } = artSubstatRollData[rarity] + const maxRolls = (high + numUpgrades) * 5 return ( @@ -89,10 +91,10 @@ export function ArtifactSubCard() { flexShrink: 1, }} > - 45 ? 'warning' : undefined}> + maxRolls ? 'error' : undefined}> Rolls: {rolls.toFixed(0)} - 45 ? 'warning' : undefined}> + maxRolls ? 'error' : undefined}> RV: {rv.toFixed()}% diff --git a/apps/frontend/src/app/Types/character.d.ts b/apps/frontend/src/app/Types/character.d.ts index dc0a54bd54..d0c111422b 100644 --- a/apps/frontend/src/app/Types/character.d.ts +++ b/apps/frontend/src/app/Types/character.d.ts @@ -75,7 +75,7 @@ export type ICharTC = { substats: { type: SubstatTypeKey stats: Record - rarity:ArtifactRarity + rarity: ArtifactRarity } sets: Partial> } From ad7f27ab65a3073b21bda4f201c016ce3de76828 Mon Sep 17 00:00:00 2001 From: frzyc Date: Sun, 24 Dec 2023 13:40:38 -0500 Subject: [PATCH 27/61] update input --- .../src/app/Components/CustomNumberInput.tsx | 52 +++++++++++-------- .../ArtifactAllSubstatEditor.tsx | 39 +++++++------- 2 files changed, 50 insertions(+), 41 deletions(-) diff --git a/apps/frontend/src/app/Components/CustomNumberInput.tsx b/apps/frontend/src/app/Components/CustomNumberInput.tsx index b26c2f0082..04a49f33b4 100644 --- a/apps/frontend/src/app/Components/CustomNumberInput.tsx +++ b/apps/frontend/src/app/Components/CustomNumberInput.tsx @@ -1,5 +1,6 @@ import type { ButtonProps, InputProps } from '@mui/material' import { Button, InputBase, styled } from '@mui/material' +import type { ChangeEvent, KeyboardEvent } from 'react' import { useCallback, useEffect, useState } from 'react' export type CustomNumberInputProps = Omit & { value?: number | undefined @@ -59,41 +60,48 @@ export default function CustomNumberInput({ ...props }: CustomNumberInputProps) { const { inputProps = {}, ...restProps } = props + const { + inputProps: { min, max }, + } = props + const [display, setDisplay] = useState(value.toString()) + + const onInputChange = useCallback( + (e: ChangeEvent) => + setDisplay(e.target.value), + [] + ) - const [number, setNumber] = useState(value) - const [focused, setFocus] = useState(false) const parseFunc = useCallback( (val: string) => (float ? parseFloat(val) : parseInt(val)), [float] ) - const onBlur = useCallback(() => { - onChange(number) - setFocus(false) - }, [onChange, number, setFocus]) - const onFocus = useCallback(() => { - setFocus(true) - }, [setFocus]) - useEffect(() => setNumber(value), [value, setNumber]) // update value on value change - const onInputChange = useCallback( - (e) => { - const newNum = parseFunc(e.target.value) || 0 - if (inputProps.min !== undefined && newNum < inputProps.min) return - if (inputProps.max !== undefined && newNum > inputProps.max) return - setNumber(newNum) - }, - [setNumber, parseFunc, inputProps.min, inputProps.max] + const onValidate = useCallback(() => { + const change = (v: number) => { + setDisplay(v.toString()) + onChange(v) + } + const newNum = parseFunc(display) || 0 + if (min !== undefined && newNum < min) return change(min) + if (max !== undefined && newNum > max) return change(max) + return change(newNum) + }, [min, max, parseFunc, onChange, display]) + + useEffect(() => setDisplay(value.toString()), [value, setDisplay]) // update value on value change + + const onKeyDown = useCallback( + (e: KeyboardEvent) => + e.key === 'Enter' && onValidate(), + [onValidate] ) - const onKeyDown = useCallback((e) => e.key === 'Enter' && onBlur(), [onBlur]) return ( getMinRoll(charTC)) - const [maxSubstat, setMaxSubstat] = useState(() => getMinMax(charTC)) - - const rollsDeferred = useDeferredValue(rolls) + // Encapsulate the values in an array so that changes to the same number still trigger useEffects + const [rollsData, setRolls] = useState(() => [getMinRoll(charTC)]) + const [maxSubstatData, setMaxSubstat] = useState(() => [getMinMax(charTC)]) + const [rolls] = rollsData + const [maxSubstat] = maxSubstatData + const rollsDeferred = useDeferredValue(rollsData) useEffect(() => { - if (rollsDeferred === undefined) return setCharTC((charTC) => { const { artifact: { @@ -42,24 +42,25 @@ export function ArtifactAllSubstatEditor() { } = charTC charTC.artifact.substats.stats = objMap(stats, (val, statKey) => { const substatValue = getSubstatValue(statKey, rarity, type) - return substatValue * rollsDeferred + return substatValue * rollsDeferred[0] }) }) }, [setCharTC, rollsDeferred]) - const maxSubstatDeferred = useDeferredValue(maxSubstat) + const maxSubstatDeferred = useDeferredValue(maxSubstatData) useEffect(() => { - if (maxSubstatDeferred === undefined) return setCharTC((charTC) => { charTC.optimization.maxSubstats = objMap( charTC.optimization.maxSubstats, - (_val, _statKey) => maxSubstatDeferred + (_val, _statKey) => maxSubstatDeferred[0] ) }) }, [setCharTC, maxSubstatDeferred]) + const maxRolls = + (artSubstatRollData[charTC.artifact.substats.rarity].numUpgrades + 1) * 5 // 0.0001 to nudge float comparasion - const invalid = (rolls ?? 0 - 0.0001) > DEFAULT_MAX_ROLLS + const invalid = (rolls ?? 0 - 0.0001) > maxRolls return ( setRolls(v as number)} - onChangeCommitted={(e, v) => setRolls(v as number)} + onChange={(e, v) => setRolls([v as number])} + onChangeCommitted={(e, v) => setRolls([v as number])} /> All Rolls} value={parseFloat((rolls ?? 0).toFixed(2))} - onChange={(v) => v !== undefined && setRolls(v)} + onChange={(v) => v !== undefined && setRolls([v])} sx={{ borderRadius: 1, px: 1, my: 0, height: '100%', width: '7em' }} inputProps={{ sx: { textAlign: 'right', pr: 0.5 }, min: 0, step: 1 }} /> All Max} - onChange={(v) => v !== undefined && setMaxSubstat(v)} - color={(maxSubstat ?? 0) > DEFAULT_MAX_ROLLS ? 'warning' : 'success'} + onChange={(v) => v !== undefined && setMaxSubstat([v])} + color={(maxSubstat ?? 0) > maxRolls ? 'warning' : 'primary'} sx={{ borderRadius: 1, px: 1, my: 0, height: '100%', width: '6.5em' }} inputProps={{ sx: { textAlign: 'right', pr: 0.5 }, min: 0, step: 1 }} /> From 2404a016afb0ca0e3169cf7998a091b6ed1d3526 Mon Sep 17 00:00:00 2001 From: frzyc Date: Sun, 24 Dec 2023 14:08:51 -0500 Subject: [PATCH 28/61] add kqms button --- .../Tabs/TabTheorycraft/index.tsx | 42 +++++++++++++++++- .../Tabs/TabTheorycraft/kqm.png | Bin 0 -> 219180 bytes 2 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/kqm.png diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx index eeed033c0d..74ce24d054 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx @@ -1,4 +1,7 @@ -import { getMainStatDisplayValue } from '@genshin-optimizer/gi-util' +import { + getMainStatDisplayValue, + getSubstatValue, +} from '@genshin-optimizer/gi-util' import { objMap } from '@genshin-optimizer/util' import { CopyAll, Refresh } from '@mui/icons-material' import CalculateIcon from '@mui/icons-material/Calculate' @@ -34,7 +37,8 @@ import { CharTCContext } from './CharTCContext' import { WeaponEditorCard } from './WeaponEditorCard' import { optimizeTc } from './optimizeTc' import useCharTC from './useCharTC' - +import ImgIcon from '../../../../Components/Image/ImgIcon' +import kqmIcon from './kqm.png' export default function TabTheorycraft() { const { database } = useContext(DatabaseContext) const { data: oldData } = useContext(DataContext) @@ -225,6 +229,37 @@ export default function TabTheorycraft() { return charTC }) } + const kqms = useCallback(() => { + setCharTC((charTC) => { + charTC.artifact.substats.type = 'mid' + + const { + artifact: { + slots, + substats: { stats, type, rarity }, + }, + } = charTC + charTC.optimization.distributedSubstats = + 20 - (rarity === 5 ? 0 : rarity === 4 ? 10 : 15) + charTC.artifact.substats.stats = objMap(stats, (val, statKey) => { + const substatValue = getSubstatValue(statKey, rarity, type) + return substatValue * 2 + }) + charTC.optimization.maxSubstats = objMap( + charTC.optimization.maxSubstats, + (_val, statKey) => { + const rollsPerSlot = 2 + const diffSlot = 5 + if (statKey === 'hp' || statKey === 'atk') + return (diffSlot - 1) * rollsPerSlot + const sameSlot = Object.entries(slots).filter( + ([_slotKey, { statKey: mainStatKey }]) => mainStatKey === statKey + ).length + return (diffSlot - sameSlot) * 2 + } + ) + }) + }, [setCharTC]) return ( @@ -242,6 +277,9 @@ export default function TabTheorycraft() { + &tl#|2P}R>g)Rb=EQ{#g`pc@aA<+VT{ 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

hJp)u0eg)uX0UU(QWm43BB;UAOj|@S120@_F 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

o=O1LYsRd-1FG9iBez1o{_KIt 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 Date: Sun, 24 Dec 2023 17:09:18 -0500 Subject: [PATCH 29/61] prevent all editors to trigger on 1st render --- .../ArtifactSubCard/ArtifactAllSubstatEditor.tsx | 8 ++++++++ libs/ui-common/src/hooks/index.ts | 1 + libs/ui-common/src/hooks/useIsMount.tsx | 9 +++++++++ libs/ui-common/src/index.ts | 1 + 4 files changed, 19 insertions(+) create mode 100644 libs/ui-common/src/hooks/index.ts create mode 100644 libs/ui-common/src/hooks/useIsMount.tsx diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactAllSubstatEditor.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactAllSubstatEditor.tsx index f2a861c9b2..e9266c3c88 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactAllSubstatEditor.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactAllSubstatEditor.tsx @@ -7,6 +7,7 @@ import CustomNumberInput from '../../../../../Components/CustomNumberInput' import type { ICharTC } from '../../../../../Types/character' import { CharTCContext } from '../CharTCContext' import { artSubstatRollData } from '@genshin-optimizer/consts' +import { useIsMount } from '@genshin-optimizer/ui-common' function getMinRoll(charTC: ICharTC) { const { @@ -33,7 +34,9 @@ export function ArtifactAllSubstatEditor() { const [rolls] = rollsData const [maxSubstat] = maxSubstatData const rollsDeferred = useDeferredValue(rollsData) + const isMount = useIsMount() useEffect(() => { + if (isMount) return setCharTC((charTC) => { const { artifact: { @@ -45,16 +48,21 @@ export function ArtifactAllSubstatEditor() { return substatValue * rollsDeferred[0] }) }) + // disable triggering for isMount + // eslint-disable-next-line react-hooks/exhaustive-deps }, [setCharTC, rollsDeferred]) const maxSubstatDeferred = useDeferredValue(maxSubstatData) useEffect(() => { + if (isMount) return setCharTC((charTC) => { charTC.optimization.maxSubstats = objMap( charTC.optimization.maxSubstats, (_val, _statKey) => maxSubstatDeferred[0] ) }) + // disable triggering for isMount + // eslint-disable-next-line react-hooks/exhaustive-deps }, [setCharTC, maxSubstatDeferred]) const maxRolls = diff --git a/libs/ui-common/src/hooks/index.ts b/libs/ui-common/src/hooks/index.ts new file mode 100644 index 0000000000..d6768f04b9 --- /dev/null +++ b/libs/ui-common/src/hooks/index.ts @@ -0,0 +1 @@ +export * from './useIsMount' diff --git a/libs/ui-common/src/hooks/useIsMount.tsx b/libs/ui-common/src/hooks/useIsMount.tsx new file mode 100644 index 0000000000..e6697a752b --- /dev/null +++ b/libs/ui-common/src/hooks/useIsMount.tsx @@ -0,0 +1,9 @@ +import { useEffect, useRef } from 'react' + +export function useIsMount() { + const isMountRef = useRef(true) + useEffect(() => { + isMountRef.current = false + }, []) + return isMountRef.current +} diff --git a/libs/ui-common/src/index.ts b/libs/ui-common/src/index.ts index 0d25d6805b..b1f45df435 100644 --- a/libs/ui-common/src/index.ts +++ b/libs/ui-common/src/index.ts @@ -1,4 +1,5 @@ export * from './components' +export * from './hooks' export * from './theme' declare module '@mui/material/styles' { From 172c7def3ecb4b69af32b8d3efa193816dd085a3 Mon Sep 17 00:00:00 2001 From: eeeqeee <103794572+eeeqeee@users.noreply.github.com> Date: Sun, 24 Dec 2023 22:47:03 -0500 Subject: [PATCH 30/61] avoid using `any` in upOpt.ts --- .../CharacterDisplay/Tabs/TabUpgradeOpt/upOpt.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabUpgradeOpt/upOpt.ts b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabUpgradeOpt/upOpt.ts index 0f590908f8..ca36a45663 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabUpgradeOpt/upOpt.ts +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabUpgradeOpt/upOpt.ts @@ -186,14 +186,14 @@ export class UpOptCalculator { const evalOpt = optimize(toEval, {}, ({ path: [p] }) => p !== 'dyn') const evalFn = precompute(evalOpt, {}, (f) => f.path[1], 5) - thresholds[0] = evalFn(Object.values(build) as any)[0] // dmg threshold is current objective value + thresholds[0] = evalFn(Object.values(build) as ArtifactBuildData[] & { length: 5 })[0] // dmg threshold is current objective value this.skippableDerivatives = allSubstatKeys.map((sub) => nodes.every((n) => zero_deriv(n, (f) => f.path[1], sub)) ) this.eval = (stats: DynStat, slot: ArtifactSlotKey) => { const b2 = { ...build, [slot]: { id: '', values: stats } } - const out = evalFn(Object.values(b2) as any) + const out = evalFn(Object.values(b2) as ArtifactBuildData[] & { length: 5 }) return nodes.map((_, i) => { const ix = i * (1 + allSubstatKeys.length) return { From c76e89e30f154d4119e862fc27c1afadcb52baed Mon Sep 17 00:00:00 2001 From: eeeqeee <103794572+eeeqeee@users.noreply.github.com> Date: Sun, 24 Dec 2023 22:57:29 -0500 Subject: [PATCH 31/61] format --- .../CharacterDisplay/Tabs/TabUpgradeOpt/upOpt.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabUpgradeOpt/upOpt.ts b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabUpgradeOpt/upOpt.ts index ca36a45663..7cb71aec56 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabUpgradeOpt/upOpt.ts +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabUpgradeOpt/upOpt.ts @@ -186,14 +186,18 @@ export class UpOptCalculator { const evalOpt = optimize(toEval, {}, ({ path: [p] }) => p !== 'dyn') const evalFn = precompute(evalOpt, {}, (f) => f.path[1], 5) - thresholds[0] = evalFn(Object.values(build) as ArtifactBuildData[] & { length: 5 })[0] // dmg threshold is current objective value + thresholds[0] = evalFn( + Object.values(build) as ArtifactBuildData[] & { length: 5 } + )[0] // dmg threshold is current objective value this.skippableDerivatives = allSubstatKeys.map((sub) => nodes.every((n) => zero_deriv(n, (f) => f.path[1], sub)) ) this.eval = (stats: DynStat, slot: ArtifactSlotKey) => { const b2 = { ...build, [slot]: { id: '', values: stats } } - const out = evalFn(Object.values(b2) as ArtifactBuildData[] & { length: 5 }) + const out = evalFn( + Object.values(b2) as ArtifactBuildData[] & { length: 5 } + ) return nodes.map((_, i) => { const ix = i * (1 + allSubstatKeys.length) return { From 764afdc7685e5e40795b6a02fb4b13c23a44538a Mon Sep 17 00:00:00 2001 From: eeeqeee <103794572+eeeqeee@users.noreply.github.com> Date: Sun, 24 Dec 2023 23:23:12 -0500 Subject: [PATCH 32/61] switch to `shouldShowDevComponents` --- .../CharacterDisplay/Tabs/TabTheorycraft/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx index 74ce24d054..bcd8d96e62 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx @@ -39,6 +39,7 @@ import { optimizeTc } from './optimizeTc' import useCharTC from './useCharTC' import ImgIcon from '../../../../Components/Image/ImgIcon' import kqmIcon from './kqm.png' +import { shouldShowDevComponents } from '../../../../Util/Util' export default function TabTheorycraft() { const { database } = useContext(DatabaseContext) const { data: oldData } = useContext(DataContext) @@ -348,7 +349,7 @@ export default function TabTheorycraft() { - {process.env.NODE_ENV === 'development' && ( + {shouldShowDevComponents && ( - - + + + - Show TC stats + {t`tabTheorycraft.compareToggle.tc`} - Compare vs. equipped + {t`tabTheorycraft.compareToggle.equipped`} @@ -344,7 +350,7 @@ export default function TabTheorycraft() { color="success" startIcon={} > - Distribute + {t`tabTheorycraft.distribute`} @@ -374,3 +380,120 @@ export default function TabTheorycraft() { ) } +function CopyFromEquippedButton({ action }: { action: () => void }) { + const { t } = useTranslation(['page_character', 'ui']) + const [open, onOpen, onClose] = useBoolState() + return ( + <> + +

+ {t('tabTheorycraft.copyDialog.title')} + + + {t('tabTheorycraft.copyDialog.content')} + + + + + + + + + ) +} + +function ResetButton({ action }: { action: () => void }) { + const { t } = useTranslation(['page_character', 'ui']) + const [open, onOpen, onClose] = useBoolState() + return ( + <> + + + {t('tabTheorycraft.resetDialog.title')} + + + {t('tabTheorycraft.resetDialog.content')} + + + + + + + + + ) +} + +function KQMSButton({ action }: { action: () => void }) { + const { t } = useTranslation(['page_character', 'ui']) + const [open, onOpen, onClose] = useBoolState() + return ( + <> + + + {t('tabTheorycraft.kqmsDialog.title')} + + + + This will replace your current substat setup with + one that adheres to the{' '} + + KQM Standards + + + + + + + + + + + ) +} diff --git a/libs/gi-localization/assets/locales/en/page_character.json b/libs/gi-localization/assets/locales/en/page_character.json index 981f986580..6e2b2fe75c 100644 --- a/libs/gi-localization/assets/locales/en/page_character.json +++ b/libs/gi-localization/assets/locales/en/page_character.json @@ -45,6 +45,32 @@ "info": "You can use these fields to add buffs/debuffs not directly supported in GO, such as food buffs, Abyss cards, or Superconduct. Please refer to the <1>Genshin Impact Wiki for specific values." }, "tabTheorycraft": { + "distribute": "Distribute", + "compareToggle": { + "equipped": "Compare vs. equipped", + "tc": "Show TC stats" + }, + "resetDialog": { + "title": "Reset TC build configuration?", + "content": "This will clear the currently configured TC build you have." + }, + "copyDialog": { + "copyBtn": "Copy from equipped", + "title": "Copy data from equipped artifacts and weapon?", + "content": "This will clear the currently configured TC build you have, with data from the currently equipped build." + }, + "kqmsDialog": { + "kqmsBtn": "Use KQMS", + "title": "Use KQMS config?", + "content": "This will replace your current substat setup with one that adheres to the <0>KQM Standards<0/>" + }, + "all": { + "rolls": "All Rolls", + "max": "All Max" + }, + "substat": { + "max": "Max" + }, "substatType": { "min": "Min substat roll", "max": "Max substat roll", From 46a571cb8bcd5d8b4a80a6461ce021fbf09fba97 Mon Sep 17 00:00:00 2001 From: frzyc Date: Sun, 31 Dec 2023 13:43:52 -0500 Subject: [PATCH 37/61] use KQMS --- libs/gi-localization/assets/locales/en/page_character.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gi-localization/assets/locales/en/page_character.json b/libs/gi-localization/assets/locales/en/page_character.json index 6e2b2fe75c..3e8e477067 100644 --- a/libs/gi-localization/assets/locales/en/page_character.json +++ b/libs/gi-localization/assets/locales/en/page_character.json @@ -61,7 +61,7 @@ }, "kqmsDialog": { "kqmsBtn": "Use KQMS", - "title": "Use KQMS config?", + "title": "Use KQMS?", "content": "This will replace your current substat setup with one that adheres to the <0>KQM Standards<0/>" }, "all": { From e943329196657a86802000d4687cb4a4587e4d19 Mon Sep 17 00:00:00 2001 From: frzyc Date: Sun, 31 Dec 2023 15:02:12 -0500 Subject: [PATCH 38/61] add gcsim snippet --- .../Tabs/TabTheorycraft/GcsimButton.tsx | 165 ++++++++++++++++++ .../Tabs/TabTheorycraft/gcsim.png | Bin 0 -> 47955 bytes .../Tabs/TabTheorycraft/index.tsx | 5 +- .../Tabs/TabTheorycraft/kqm.png | Bin 219180 -> 43405 bytes .../src/app/PageDocumentation/index.tsx | 66 +------ .../assets/locales/en/page_character.json | 7 +- libs/ui-common/src/components/CodeBlock.tsx | 66 +++++++ libs/ui-common/src/components/index.ts | 1 + 8 files changed, 243 insertions(+), 67 deletions(-) create mode 100644 apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/GcsimButton.tsx create mode 100644 apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/gcsim.png create mode 100644 libs/ui-common/src/components/CodeBlock.tsx diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/GcsimButton.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/GcsimButton.tsx new file mode 100644 index 0000000000..be7b1aec8c --- /dev/null +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/GcsimButton.tsx @@ -0,0 +1,165 @@ +import type { MainStatKey, SubstatKey } from '@genshin-optimizer/consts' +import { + ascensionMaxLevel, + getMainStatDisplayValue, +} from '@genshin-optimizer/gi-util' +import { useBoolState } from '@genshin-optimizer/react-util' +import { CardThemed, CodeBlock } from '@genshin-optimizer/ui-common' +import { toDecimal } from '@genshin-optimizer/util' +import ContentPasteIcon from '@mui/icons-material/ContentPaste' +import { + Button, + CardContent, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, + Link, +} from '@mui/material' +import { useContext } from 'react' +import { Trans, useTranslation } from 'react-i18next' +import ImgIcon from '../../../../Components/Image/ImgIcon' +import { CharacterContext } from '../../../../Context/CharacterContext' +import { CharTCContext } from './CharTCContext' +import gcsimIcon from './gcsim.png' + +// From https://github.com/genshinsim/gcsim/blob/68874fb693c4e959b336d106f201c29d100fc2c8/pkg/core/attributes/stats.go#L72 +const GOODtoSRL: Record = { + def_: 'def%', + def: 'def', + hp: 'hp', + hp_: 'hp%', + atk: 'atk', + atk_: 'atk%', + enerRech_: 'er', + eleMas: 'em', + critRate_: 'cr', + critDMG_: 'cd', + heal_: 'heal', + pyro_dmg_: 'pyro%', + hydro_dmg_: 'hydro%', + cryo_dmg_: 'cryo%', + electro_dmg_: 'electro%', + anemo_dmg_: 'anemo%', + geo_dmg_: 'geo%', + dendro_dmg_: 'dendro%', + physical_dmg_: 'phys%', + // "atkspd%", + // "dmg%", +} +export default function GcsimButton() { + const { t } = useTranslation(['page_character', 'settings']) + const [open, onOpen, onClose] = useBoolState() + + const { + character: { + key: characterKey, + level, + ascension, + constellation, + talent: { auto, burst, skill }, + }, + } = useContext(CharacterContext) + const { + charTC: { + weapon: { key: weaponKey, level: wLevel, ascension: wAscension }, + artifact: { + slots, + substats: { stats: substats }, + sets, + }, + }, + } = useContext(CharTCContext) + + const charKeyLow = characterKey.toLowerCase() + + const setText = Object.entries(sets) + .map( + ([key, num]) => + `${charKeyLow} add set="${key.toLowerCase()}" count=${num};` + ) + .join('\n') + + const mainStatsText = Object.entries(slots) + .map( + ([_, { level, statKey, rarity }]) => + `${GOODtoSRL[statKey]}=${getMainStatDisplayValue( + statKey, + rarity, + level + )}` + ) + .join(', ') + const substatsText = Object.entries(substats) + .map( + ([key, value]) => + `${GOODtoSRL[key]}=${toDecimal(value, key).toFixed( + key.endsWith('_') ? 4 : 2 + )}` + ) + .join(', ') + + const text = `# Generated by Genshin Optimizer + +# Character +${charKeyLow} char lvl=${level}/${ + ascensionMaxLevel[ascension] + } cons=${constellation} talent=${auto},${burst},${skill}; + +# Weapon +${charKeyLow} add weapon="${weaponKey.toLowerCase()}" refine=3 lvl=${wLevel}/${ + ascensionMaxLevel[wAscension] + }; + +# Artifact Set +${setText ? setText : '# No Artifact Sets'} + +# Main stats +${charKeyLow} add stats ${mainStatsText}; + +# sub stats +${charKeyLow} add stats ${substatsText};` + const copyToClipboard = () => + navigator.clipboard + .writeText(text) + .then(() => alert(t('tabTheorycraft.gcsimDialog.copied'))) + .catch(console.error) + + return ( + <> + + + {t('tabTheorycraft.gcsimDialog.title')} + + + + Import this build into{' '} + + gcsim + {' '} + by copying the code below. + + + + + + + + + + + + + + ) +} diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/gcsim.png b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/gcsim.png new file mode 100644 index 0000000000000000000000000000000000000000..e493592114f0714e756952709650ca575ee07d61 GIT binary patch literal 47955 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�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 literal 0 HcmV?d00001 diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx index ee9e886a84..938b0edcdf 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx @@ -55,6 +55,7 @@ import { WeaponEditorCard } from './WeaponEditorCard' import kqmIcon from './kqm.png' import { optimizeTc } from './optimizeTc' import useCharTC from './useCharTC' +import GcsimButton from './GcsimButton' export default function TabTheorycraft() { const { t } = useTranslation('page_character') const { database } = useContext(DatabaseContext) @@ -286,6 +287,7 @@ export default function TabTheorycraft() { + void }) { {t('tabTheorycraft.kqmsDialog.title')} - + This will replace your current substat setup with one that adheres to the{' '} void }) { > KQM Standards + . diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/kqm.png b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/kqm.png index 6970c01f2d3aecdccc797ab659928e3965e67bde..2db606f2248e051c913f246dfba25dbb63054fd8 100644 GIT binary patch literal 43405 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/GgnY^>S&xCFgCWEP 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 Q|LmYBt143~^)2lG0VO5#J^%m! literal 219180 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

o=O1LYsRd-1FG9iBez1o{_KIt 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 ) } - -type LineNumberProps = { - digits?: number -} -const LineNumber = styled('textarea')( - ({ theme, digits = 2 }) => ({ - width: `${digits}em`, - overflow: 'hidden', - userSelect: 'none', - color: theme.palette.text.secondary, - resize: 'none', - border: 'none', - whiteSpace: 'pre', - fontFamily: 'monospace', - lineHeight: 1, - '&:disabled': { - backgroundColor: 'transparent', - }, - }) -) - -const CodeArea = styled('textarea')(({ theme }) => ({ - '&:disabled': { - backgroundColor: 'transparent', - }, - lineHeight: 1, - width: '100%', - overflowY: 'auto', - overflowX: 'auto', - fontFamily: 'monospace', - border: 'none', - // padding: 1em; - whiteSpace: 'pre', - backgroundColor: 'transparent', - resize: 'none', - color: theme.palette.info.light, -})) -function CodeBlock({ text }) { - const lines = text.split(/\r\n|\r|\n/).length + 1 - const lineNums = Array.from(Array(lines).keys()) - .map((i) => i + 1) - .join('\n') - - return ( - - - - - ) -} diff --git a/libs/gi-localization/assets/locales/en/page_character.json b/libs/gi-localization/assets/locales/en/page_character.json index 3e8e477067..0162994551 100644 --- a/libs/gi-localization/assets/locales/en/page_character.json +++ b/libs/gi-localization/assets/locales/en/page_character.json @@ -62,7 +62,12 @@ "kqmsDialog": { "kqmsBtn": "Use KQMS", "title": "Use KQMS?", - "content": "This will replace your current substat setup with one that adheres to the <0>KQM Standards<0/>" + "content": "This will replace your current substat setup with one that adheres to the <4>KQM Standards." + }, + "gcsimDialog": { + "title": "gcsim Input", + "content": "Import this build into <2>gcsim by copying the code below.", + "copied": "Copied gcsim config to clipboard." }, "all": { "rolls": "All Rolls", diff --git a/libs/ui-common/src/components/CodeBlock.tsx b/libs/ui-common/src/components/CodeBlock.tsx new file mode 100644 index 0000000000..904633ac73 --- /dev/null +++ b/libs/ui-common/src/components/CodeBlock.tsx @@ -0,0 +1,66 @@ +import { Box, styled } from '@mui/material' + +type LineNumberProps = { + digits?: number +} +const LineNumber = styled('textarea')( + ({ theme, digits = 2 }) => ({ + width: `${digits}em`, + overflow: 'hidden', + userSelect: 'none', + color: theme.palette.text.secondary, + resize: 'none', + border: 'none', + whiteSpace: 'pre', + fontFamily: 'monospace', + lineHeight: 1, + '&:disabled': { + backgroundColor: 'transparent', + }, + }) +) + +const CodeArea = styled('textarea')(({ theme }) => ({ + '&:disabled': { + backgroundColor: 'transparent', + }, + lineHeight: 1, + width: '100%', + overflowY: 'auto', + overflowX: 'auto', + fontFamily: 'monospace', + border: 'none', + // padding: 1em; + whiteSpace: 'pre', + backgroundColor: 'transparent', + resize: 'none', + color: theme.palette.info.light, +})) + +export function CodeBlock({ text }: { text: string }) { + const lines = text.split(/\r\n|\r|\n/).length + 1 + const lineNums = Array.from(Array(lines).keys()) + .map((i) => i + 1) + .join('\n') + + return ( + + + + + ) +} diff --git a/libs/ui-common/src/components/index.ts b/libs/ui-common/src/components/index.ts index ddc29eae5a..95caef77d5 100644 --- a/libs/ui-common/src/components/index.ts +++ b/libs/ui-common/src/components/index.ts @@ -2,6 +2,7 @@ export * from './Card' export * from './BootstrapTooltip' export * from './ColorText' +export * from './CodeBlock' export * from './SqBadge' export * from './ConditionalWrapper' export * from './InfoTooltip' From 22bc917d70d6c413239bfa606ed15cb67934ef20 Mon Sep 17 00:00:00 2001 From: frzyc Date: Sun, 31 Dec 2023 15:26:38 -0500 Subject: [PATCH 39/61] gcsim has no comma --- .../CharacterDisplay/Tabs/TabTheorycraft/GcsimButton.tsx | 4 ++-- libs/gi-localization/assets/locales/en/page_character.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/GcsimButton.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/GcsimButton.tsx index be7b1aec8c..b9e99ddd7d 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/GcsimButton.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/GcsimButton.tsx @@ -90,7 +90,7 @@ export default function GcsimButton() { level )}` ) - .join(', ') + .join(' ') const substatsText = Object.entries(substats) .map( ([key, value]) => @@ -98,7 +98,7 @@ export default function GcsimButton() { key.endsWith('_') ? 4 : 2 )}` ) - .join(', ') + .join(' ') const text = `# Generated by Genshin Optimizer diff --git a/libs/gi-localization/assets/locales/en/page_character.json b/libs/gi-localization/assets/locales/en/page_character.json index 0162994551..1216117032 100644 --- a/libs/gi-localization/assets/locales/en/page_character.json +++ b/libs/gi-localization/assets/locales/en/page_character.json @@ -65,7 +65,7 @@ "content": "This will replace your current substat setup with one that adheres to the <4>KQM Standards." }, "gcsimDialog": { - "title": "gcsim Input", + "title": "gcsim Export", "content": "Import this build into <2>gcsim by copying the code below.", "copied": "Copied gcsim config to clipboard." }, From dc464977e0bf3f151fe82866204af30d73e5d864 Mon Sep 17 00:00:00 2001 From: frzyc Date: Sun, 31 Dec 2023 15:37:26 -0500 Subject: [PATCH 40/61] use raw value --- .../Tabs/TabTheorycraft/GcsimButton.tsx | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/GcsimButton.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/GcsimButton.tsx index b9e99ddd7d..4ad901fc72 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/GcsimButton.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/GcsimButton.tsx @@ -1,8 +1,5 @@ import type { MainStatKey, SubstatKey } from '@genshin-optimizer/consts' -import { - ascensionMaxLevel, - getMainStatDisplayValue, -} from '@genshin-optimizer/gi-util' +import { ascensionMaxLevel, getMainStatValue } from '@genshin-optimizer/gi-util' import { useBoolState } from '@genshin-optimizer/react-util' import { CardThemed, CodeBlock } from '@genshin-optimizer/ui-common' import { toDecimal } from '@genshin-optimizer/util' @@ -84,11 +81,7 @@ export default function GcsimButton() { const mainStatsText = Object.entries(slots) .map( ([_, { level, statKey, rarity }]) => - `${GOODtoSRL[statKey]}=${getMainStatDisplayValue( - statKey, - rarity, - level - )}` + `${GOODtoSRL[statKey]}=${getMainStatValue(statKey, rarity, level)}` ) .join(' ') const substatsText = Object.entries(substats) From d88db5f55c9812e0cfda3ad769608ba7a37ede7d Mon Sep 17 00:00:00 2001 From: frzyc Date: Sun, 31 Dec 2023 16:12:27 -0500 Subject: [PATCH 41/61] refinement --- .../CharacterDisplay/Tabs/TabTheorycraft/GcsimButton.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/GcsimButton.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/GcsimButton.tsx index 4ad901fc72..8477b785cd 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/GcsimButton.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/GcsimButton.tsx @@ -60,7 +60,12 @@ export default function GcsimButton() { } = useContext(CharacterContext) const { charTC: { - weapon: { key: weaponKey, level: wLevel, ascension: wAscension }, + weapon: { + key: weaponKey, + level: wLevel, + ascension: wAscension, + refinement: wRefinement, + }, artifact: { slots, substats: { stats: substats }, @@ -101,7 +106,7 @@ ${charKeyLow} char lvl=${level}/${ } cons=${constellation} talent=${auto},${burst},${skill}; # Weapon -${charKeyLow} add weapon="${weaponKey.toLowerCase()}" refine=3 lvl=${wLevel}/${ +${charKeyLow} add weapon="${weaponKey.toLowerCase()}" refine=${wRefinement} lvl=${wLevel}/${ ascensionMaxLevel[wAscension] }; From b48b5139e89d2bd7a1d14ee62fff21f6c4cd161c Mon Sep 17 00:00:00 2001 From: eeeqeee <103794572+eeeqeee@users.noreply.github.com> Date: Tue, 2 Jan 2024 22:08:41 -0500 Subject: [PATCH 42/61] add `minSubstats` and `scalesWith` --- .../Database/DataManagers/CharacterTCData.ts | 13 +++++++++-- .../Tabs/TabTheorycraft/optimizeTc.ts | 23 ++++++++++++++++--- apps/frontend/src/app/Types/character.d.ts | 4 ++++ 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/apps/frontend/src/app/Database/DataManagers/CharacterTCData.ts b/apps/frontend/src/app/Database/DataManagers/CharacterTCData.ts index 5b29d4bdc5..876c50e591 100644 --- a/apps/frontend/src/app/Database/DataManagers/CharacterTCData.ts +++ b/apps/frontend/src/app/Database/DataManagers/CharacterTCData.ts @@ -85,6 +85,7 @@ export function initCharTC(weaponKey: WeaponKey): ICharTC { target: undefined, distributedSubstats: 45, maxSubstats: initCharTcOptimizationMaxSubstats(), + minSubstats: initCharTcOptimizationMinSubstats(), }, } } @@ -156,7 +157,7 @@ function validateCharTcOptimization( optimization: unknown ): ICharTC['optimization'] | undefined { if (typeof optimization !== 'object') return undefined - let { target, distributedSubstats, maxSubstats } = + let { target, distributedSubstats, maxSubstats, minSubstats } = optimization as ICharTC['optimization'] if (!Array.isArray(target)) target = undefined if (typeof distributedSubstats !== 'number') distributedSubstats = 20 @@ -165,7 +166,12 @@ function validateCharTcOptimization( maxSubstats = objKeyMap([...allSubstatKeys], (k) => typeof maxSubstats[k] === 'number' ? maxSubstats[k] : 0 ) - return { target, distributedSubstats, maxSubstats } + if (typeof minSubstats !== 'object') + minSubstats = initCharTcOptimizationMaxSubstats() + minSubstats = objKeyMap([...allSubstatKeys], (k) => + typeof minSubstats[k] === 'number' ? minSubstats[k] : 0 + ) + return { target, distributedSubstats, maxSubstats, minSubstats } } function initCharTcOptimizationMaxSubstats(): ICharTC['optimization']['maxSubstats'] { return objKeyMap( @@ -173,3 +179,6 @@ function initCharTcOptimizationMaxSubstats(): ICharTC['optimization']['maxSubsta (k) => 6 * (k === 'hp' || k === 'atk' ? 4 : k === 'atk_' ? 2 : 5) ) } +function initCharTcOptimizationMinSubstats(): ICharTC['optimization']['minSubstats'] { + return objKeyMap(allSubstatKeys, () => 0) +} diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.ts b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.ts index 71c528b112..600a28a4a0 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.ts +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.ts @@ -31,6 +31,7 @@ export function optimizeTc( target: optimizationTarget, distributedSubstats, maxSubstats: rawMaxSubstats, + minSubstats, }, } = charTC if (!optimizationTarget) return {} @@ -57,7 +58,8 @@ export function optimizeTc( if (f.operation === 'read' && f.path[0] === 'dyn') { const a = charTC.artifact.sets[f.path[1]] if (a) return constant(a) - if (!allSubstatKeys.includes(f.path[1] as any)) return constant(0) + if (!(allSubstatKeys as readonly string[]).includes(f.path[1])) + return constant(0) } return f }, @@ -85,6 +87,11 @@ export function optimizeTc( ) const subsArr = [...subs] let distributed = distributedSubstats + for (const [k, v] of Object.entries(minSubstats)) { + distributed -= Math.ceil( + v / getSubstatValue(k, rarity, substatsType, false) + ) + } const maxSubstats = objMap(rawMaxSubstats, (v, k) => { return ( v - @@ -107,7 +114,11 @@ export function optimizeTc( const buffer = Object.fromEntries([...subs].map((x) => [x, 0])) const existingSubs = objMap( charTC.artifact.substats.stats, - (v, k) => v / comp(k) + (v, k) => + v / comp(k) + + Math.ceil( + minSubstats[k] / getSubstatValue(k, rarity, substatsType, false) + ) ) const permute = (toAssign: number, [x, ...xs]: string[]) => { if (xs.length === 0) { @@ -144,5 +155,11 @@ export function optimizeTc( }) } } - return { maxBuffer, distributed } + return { + maxBuffer, + distributed, + scalesWith: subsArr.filter((x) => + (allSubstatKeys as readonly string[]).includes(x) + ) as SubstatKey[], + } } diff --git a/apps/frontend/src/app/Types/character.d.ts b/apps/frontend/src/app/Types/character.d.ts index d0c111422b..eff35dac94 100644 --- a/apps/frontend/src/app/Types/character.d.ts +++ b/apps/frontend/src/app/Types/character.d.ts @@ -83,5 +83,9 @@ export type ICharTC = { target?: string[] distributedSubstats: number maxSubstats: Record + /** NB: this is in raw value, not substat count + * e.g. `{enerRech_: 0.3}` + */ + minSubstats: Record } } From 269cd95eb71600aa1314e14016317f38bf7f7bb3 Mon Sep 17 00:00:00 2001 From: eeeqeee <103794572+eeeqeee@users.noreply.github.com> Date: Fri, 5 Jan 2024 02:35:05 -0500 Subject: [PATCH 43/61] change from `minSubstats` to `minTotal` --- .../app/Database/DataManagers/CharacterTCData.ts | 16 ++++++++-------- .../Tabs/TabTheorycraft/optimizeTc.ts | 13 ++++++++----- apps/frontend/src/app/Types/character.d.ts | 5 +++-- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/apps/frontend/src/app/Database/DataManagers/CharacterTCData.ts b/apps/frontend/src/app/Database/DataManagers/CharacterTCData.ts index 876c50e591..2baf452918 100644 --- a/apps/frontend/src/app/Database/DataManagers/CharacterTCData.ts +++ b/apps/frontend/src/app/Database/DataManagers/CharacterTCData.ts @@ -85,7 +85,7 @@ export function initCharTC(weaponKey: WeaponKey): ICharTC { target: undefined, distributedSubstats: 45, maxSubstats: initCharTcOptimizationMaxSubstats(), - minSubstats: initCharTcOptimizationMinSubstats(), + minTotal: initCharTcOptimizationMinTotal(), }, } } @@ -157,7 +157,7 @@ function validateCharTcOptimization( optimization: unknown ): ICharTC['optimization'] | undefined { if (typeof optimization !== 'object') return undefined - let { target, distributedSubstats, maxSubstats, minSubstats } = + let { target, distributedSubstats, maxSubstats, minTotal } = optimization as ICharTC['optimization'] if (!Array.isArray(target)) target = undefined if (typeof distributedSubstats !== 'number') distributedSubstats = 20 @@ -166,12 +166,12 @@ function validateCharTcOptimization( maxSubstats = objKeyMap([...allSubstatKeys], (k) => typeof maxSubstats[k] === 'number' ? maxSubstats[k] : 0 ) - if (typeof minSubstats !== 'object') - minSubstats = initCharTcOptimizationMaxSubstats() - minSubstats = objKeyMap([...allSubstatKeys], (k) => - typeof minSubstats[k] === 'number' ? minSubstats[k] : 0 + if (typeof minTotal !== 'object') + minTotal = initCharTcOptimizationMaxSubstats() + minTotal = objKeyMap([...allSubstatKeys], (k) => + typeof minTotal[k] === 'number' ? minTotal[k] : 0 ) - return { target, distributedSubstats, maxSubstats, minSubstats } + return { target, distributedSubstats, maxSubstats, minTotal: minTotal } } function initCharTcOptimizationMaxSubstats(): ICharTC['optimization']['maxSubstats'] { return objKeyMap( @@ -179,6 +179,6 @@ function initCharTcOptimizationMaxSubstats(): ICharTC['optimization']['maxSubsta (k) => 6 * (k === 'hp' || k === 'atk' ? 4 : k === 'atk_' ? 2 : 5) ) } -function initCharTcOptimizationMinSubstats(): ICharTC['optimization']['minSubstats'] { +function initCharTcOptimizationMinTotal(): ICharTC['optimization']['minTotal'] { return objKeyMap(allSubstatKeys, () => 0) } diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.ts b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.ts index 600a28a4a0..335bbf6ae9 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.ts +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.ts @@ -31,7 +31,7 @@ export function optimizeTc( target: optimizationTarget, distributedSubstats, maxSubstats: rawMaxSubstats, - minSubstats, + minTotal: rawMinTotal, }, } = charTC if (!optimizationTarget) return {} @@ -87,7 +87,12 @@ export function optimizeTc( ) const subsArr = [...subs] let distributed = distributedSubstats - for (const [k, v] of Object.entries(minSubstats)) { + + const minTotal = objMap(rawMinTotal, (v, k) => { + const [node] = optimize([workerData.total[k]], workerData, () => true) + return v - (node.operation === 'const' ? node.value : 0) + }) + for (const [k, v] of Object.entries(minTotal)) { distributed -= Math.ceil( v / getSubstatValue(k, rarity, substatsType, false) ) @@ -116,9 +121,7 @@ export function optimizeTc( charTC.artifact.substats.stats, (v, k) => v / comp(k) + - Math.ceil( - minSubstats[k] / getSubstatValue(k, rarity, substatsType, false) - ) + Math.ceil(minTotal[k] / getSubstatValue(k, rarity, substatsType, false)) ) const permute = (toAssign: number, [x, ...xs]: string[]) => { if (xs.length === 0) { diff --git a/apps/frontend/src/app/Types/character.d.ts b/apps/frontend/src/app/Types/character.d.ts index eff35dac94..bd298cb905 100644 --- a/apps/frontend/src/app/Types/character.d.ts +++ b/apps/frontend/src/app/Types/character.d.ts @@ -83,9 +83,10 @@ export type ICharTC = { target?: string[] distributedSubstats: number maxSubstats: Record - /** NB: this is in raw value, not substat count + /** NB: this is in total raw value, not substat count + * This includes stats from other sources * e.g. `{enerRech_: 0.3}` */ - minSubstats: Record + minTotal: Record, number> } } From 9287d3bf67cd2787577db5eacd83d9f5419a48c4 Mon Sep 17 00:00:00 2001 From: frzyc Date: Sat, 6 Jan 2024 02:35:32 -0500 Subject: [PATCH 44/61] cap --- .../CharacterDisplay/Tabs/TabTheorycraft/GcsimButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/GcsimButton.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/GcsimButton.tsx index 8477b785cd..7e2ce64b8b 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/GcsimButton.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/GcsimButton.tsx @@ -116,7 +116,7 @@ ${setText ? setText : '# No Artifact Sets'} # Main stats ${charKeyLow} add stats ${mainStatsText}; -# sub stats +# Sub stats ${charKeyLow} add stats ${substatsText};` const copyToClipboard = () => navigator.clipboard From 478550d8cf2e1d630fa445db22f7d857cf66f04d Mon Sep 17 00:00:00 2001 From: frzyc Date: Sun, 7 Jan 2024 20:45:10 -0500 Subject: [PATCH 45/61] Revert "change from `minSubstats` to `minTotal`" This reverts commit 269cd95eb71600aa1314e14016317f38bf7f7bb3. --- .../app/Database/DataManagers/CharacterTCData.ts | 16 ++++++++-------- .../Tabs/TabTheorycraft/optimizeTc.ts | 13 +++++-------- apps/frontend/src/app/Types/character.d.ts | 5 ++--- 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/apps/frontend/src/app/Database/DataManagers/CharacterTCData.ts b/apps/frontend/src/app/Database/DataManagers/CharacterTCData.ts index 2baf452918..876c50e591 100644 --- a/apps/frontend/src/app/Database/DataManagers/CharacterTCData.ts +++ b/apps/frontend/src/app/Database/DataManagers/CharacterTCData.ts @@ -85,7 +85,7 @@ export function initCharTC(weaponKey: WeaponKey): ICharTC { target: undefined, distributedSubstats: 45, maxSubstats: initCharTcOptimizationMaxSubstats(), - minTotal: initCharTcOptimizationMinTotal(), + minSubstats: initCharTcOptimizationMinSubstats(), }, } } @@ -157,7 +157,7 @@ function validateCharTcOptimization( optimization: unknown ): ICharTC['optimization'] | undefined { if (typeof optimization !== 'object') return undefined - let { target, distributedSubstats, maxSubstats, minTotal } = + let { target, distributedSubstats, maxSubstats, minSubstats } = optimization as ICharTC['optimization'] if (!Array.isArray(target)) target = undefined if (typeof distributedSubstats !== 'number') distributedSubstats = 20 @@ -166,12 +166,12 @@ function validateCharTcOptimization( maxSubstats = objKeyMap([...allSubstatKeys], (k) => typeof maxSubstats[k] === 'number' ? maxSubstats[k] : 0 ) - if (typeof minTotal !== 'object') - minTotal = initCharTcOptimizationMaxSubstats() - minTotal = objKeyMap([...allSubstatKeys], (k) => - typeof minTotal[k] === 'number' ? minTotal[k] : 0 + if (typeof minSubstats !== 'object') + minSubstats = initCharTcOptimizationMaxSubstats() + minSubstats = objKeyMap([...allSubstatKeys], (k) => + typeof minSubstats[k] === 'number' ? minSubstats[k] : 0 ) - return { target, distributedSubstats, maxSubstats, minTotal: minTotal } + return { target, distributedSubstats, maxSubstats, minSubstats } } function initCharTcOptimizationMaxSubstats(): ICharTC['optimization']['maxSubstats'] { return objKeyMap( @@ -179,6 +179,6 @@ function initCharTcOptimizationMaxSubstats(): ICharTC['optimization']['maxSubsta (k) => 6 * (k === 'hp' || k === 'atk' ? 4 : k === 'atk_' ? 2 : 5) ) } -function initCharTcOptimizationMinTotal(): ICharTC['optimization']['minTotal'] { +function initCharTcOptimizationMinSubstats(): ICharTC['optimization']['minSubstats'] { return objKeyMap(allSubstatKeys, () => 0) } diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.ts b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.ts index 335bbf6ae9..600a28a4a0 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.ts +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.ts @@ -31,7 +31,7 @@ export function optimizeTc( target: optimizationTarget, distributedSubstats, maxSubstats: rawMaxSubstats, - minTotal: rawMinTotal, + minSubstats, }, } = charTC if (!optimizationTarget) return {} @@ -87,12 +87,7 @@ export function optimizeTc( ) const subsArr = [...subs] let distributed = distributedSubstats - - const minTotal = objMap(rawMinTotal, (v, k) => { - const [node] = optimize([workerData.total[k]], workerData, () => true) - return v - (node.operation === 'const' ? node.value : 0) - }) - for (const [k, v] of Object.entries(minTotal)) { + for (const [k, v] of Object.entries(minSubstats)) { distributed -= Math.ceil( v / getSubstatValue(k, rarity, substatsType, false) ) @@ -121,7 +116,9 @@ export function optimizeTc( charTC.artifact.substats.stats, (v, k) => v / comp(k) + - Math.ceil(minTotal[k] / getSubstatValue(k, rarity, substatsType, false)) + Math.ceil( + minSubstats[k] / getSubstatValue(k, rarity, substatsType, false) + ) ) const permute = (toAssign: number, [x, ...xs]: string[]) => { if (xs.length === 0) { diff --git a/apps/frontend/src/app/Types/character.d.ts b/apps/frontend/src/app/Types/character.d.ts index bd298cb905..eff35dac94 100644 --- a/apps/frontend/src/app/Types/character.d.ts +++ b/apps/frontend/src/app/Types/character.d.ts @@ -83,10 +83,9 @@ export type ICharTC = { target?: string[] distributedSubstats: number maxSubstats: Record - /** NB: this is in total raw value, not substat count - * This includes stats from other sources + /** NB: this is in raw value, not substat count * e.g. `{enerRech_: 0.3}` */ - minTotal: Record, number> + minSubstats: Record } } From 6e22acbbc5af717a123cfac2717e087a8b01a48b Mon Sep 17 00:00:00 2001 From: frzyc Date: Sun, 7 Jan 2024 20:45:38 -0500 Subject: [PATCH 46/61] Revert "add `minSubstats` and `scalesWith`" This reverts commit b48b5139e89d2bd7a1d14ee62fff21f6c4cd161c. --- .../Database/DataManagers/CharacterTCData.ts | 13 ++--------- .../Tabs/TabTheorycraft/optimizeTc.ts | 23 +++---------------- apps/frontend/src/app/Types/character.d.ts | 4 ---- 3 files changed, 5 insertions(+), 35 deletions(-) diff --git a/apps/frontend/src/app/Database/DataManagers/CharacterTCData.ts b/apps/frontend/src/app/Database/DataManagers/CharacterTCData.ts index 876c50e591..5b29d4bdc5 100644 --- a/apps/frontend/src/app/Database/DataManagers/CharacterTCData.ts +++ b/apps/frontend/src/app/Database/DataManagers/CharacterTCData.ts @@ -85,7 +85,6 @@ export function initCharTC(weaponKey: WeaponKey): ICharTC { target: undefined, distributedSubstats: 45, maxSubstats: initCharTcOptimizationMaxSubstats(), - minSubstats: initCharTcOptimizationMinSubstats(), }, } } @@ -157,7 +156,7 @@ function validateCharTcOptimization( optimization: unknown ): ICharTC['optimization'] | undefined { if (typeof optimization !== 'object') return undefined - let { target, distributedSubstats, maxSubstats, minSubstats } = + let { target, distributedSubstats, maxSubstats } = optimization as ICharTC['optimization'] if (!Array.isArray(target)) target = undefined if (typeof distributedSubstats !== 'number') distributedSubstats = 20 @@ -166,12 +165,7 @@ function validateCharTcOptimization( maxSubstats = objKeyMap([...allSubstatKeys], (k) => typeof maxSubstats[k] === 'number' ? maxSubstats[k] : 0 ) - if (typeof minSubstats !== 'object') - minSubstats = initCharTcOptimizationMaxSubstats() - minSubstats = objKeyMap([...allSubstatKeys], (k) => - typeof minSubstats[k] === 'number' ? minSubstats[k] : 0 - ) - return { target, distributedSubstats, maxSubstats, minSubstats } + return { target, distributedSubstats, maxSubstats } } function initCharTcOptimizationMaxSubstats(): ICharTC['optimization']['maxSubstats'] { return objKeyMap( @@ -179,6 +173,3 @@ function initCharTcOptimizationMaxSubstats(): ICharTC['optimization']['maxSubsta (k) => 6 * (k === 'hp' || k === 'atk' ? 4 : k === 'atk_' ? 2 : 5) ) } -function initCharTcOptimizationMinSubstats(): ICharTC['optimization']['minSubstats'] { - return objKeyMap(allSubstatKeys, () => 0) -} diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.ts b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.ts index 600a28a4a0..71c528b112 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.ts +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.ts @@ -31,7 +31,6 @@ export function optimizeTc( target: optimizationTarget, distributedSubstats, maxSubstats: rawMaxSubstats, - minSubstats, }, } = charTC if (!optimizationTarget) return {} @@ -58,8 +57,7 @@ export function optimizeTc( if (f.operation === 'read' && f.path[0] === 'dyn') { const a = charTC.artifact.sets[f.path[1]] if (a) return constant(a) - if (!(allSubstatKeys as readonly string[]).includes(f.path[1])) - return constant(0) + if (!allSubstatKeys.includes(f.path[1] as any)) return constant(0) } return f }, @@ -87,11 +85,6 @@ export function optimizeTc( ) const subsArr = [...subs] let distributed = distributedSubstats - for (const [k, v] of Object.entries(minSubstats)) { - distributed -= Math.ceil( - v / getSubstatValue(k, rarity, substatsType, false) - ) - } const maxSubstats = objMap(rawMaxSubstats, (v, k) => { return ( v - @@ -114,11 +107,7 @@ export function optimizeTc( const buffer = Object.fromEntries([...subs].map((x) => [x, 0])) const existingSubs = objMap( charTC.artifact.substats.stats, - (v, k) => - v / comp(k) + - Math.ceil( - minSubstats[k] / getSubstatValue(k, rarity, substatsType, false) - ) + (v, k) => v / comp(k) ) const permute = (toAssign: number, [x, ...xs]: string[]) => { if (xs.length === 0) { @@ -155,11 +144,5 @@ export function optimizeTc( }) } } - return { - maxBuffer, - distributed, - scalesWith: subsArr.filter((x) => - (allSubstatKeys as readonly string[]).includes(x) - ) as SubstatKey[], - } + return { maxBuffer, distributed } } diff --git a/apps/frontend/src/app/Types/character.d.ts b/apps/frontend/src/app/Types/character.d.ts index eff35dac94..d0c111422b 100644 --- a/apps/frontend/src/app/Types/character.d.ts +++ b/apps/frontend/src/app/Types/character.d.ts @@ -83,9 +83,5 @@ export type ICharTC = { target?: string[] distributedSubstats: number maxSubstats: Record - /** NB: this is in raw value, not substat count - * e.g. `{enerRech_: 0.3}` - */ - minSubstats: Record } } From 0fc097b5d912a77139f3dc6aa4658da9fed29b51 Mon Sep 17 00:00:00 2001 From: frzyc Date: Sun, 7 Jan 2024 21:06:35 -0500 Subject: [PATCH 47/61] minor update --- .../CharacterDisplay/Tabs/TabTheorycraft/index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx index 938b0edcdf..ac670e98d0 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx @@ -44,18 +44,18 @@ import useTeamData from '../../../../ReactHooks/useTeamData' import type { ICachedArtifact } from '../../../../Types/artifact' import type { ICharTC } from '../../../../Types/character' import type { ICachedWeapon } from '../../../../Types/weapon' -import { shouldShowDevComponents } from '../../../../Util/Util' +import { isDev } from '../../../../Util/Util' import { defaultInitialWeaponKey } from '../../../../Util/WeaponUtil' import OptimizationTargetSelector from '../TabOptimize/Components/OptimizationTargetSelector' import { ArtifactMainStatAndSetEditor } from './ArtifactMainStatAndSetEditor' import { ArtifactSubCard } from './ArtifactSubCard' import type { SetCharTCAction } from './CharTCContext' import { CharTCContext } from './CharTCContext' +import GcsimButton from './GcsimButton' import { WeaponEditorCard } from './WeaponEditorCard' import kqmIcon from './kqm.png' import { optimizeTc } from './optimizeTc' import useCharTC from './useCharTC' -import GcsimButton from './GcsimButton' export default function TabTheorycraft() { const { t } = useTranslation('page_character') const { database } = useContext(DatabaseContext) @@ -356,7 +356,7 @@ export default function TabTheorycraft() { - {shouldShowDevComponents && ( + {isDev && ( - + {[...(useLow ? milestoneLevelsLow : milestoneLevels)].map( ([lv, as]) => { const selected = lv === level && as === ascension diff --git a/apps/frontend/src/app/Components/RefinementDropdown.tsx b/apps/frontend/src/app/Components/RefinementDropdown.tsx index cd71e41955..f89700d0a5 100644 --- a/apps/frontend/src/app/Components/RefinementDropdown.tsx +++ b/apps/frontend/src/app/Components/RefinementDropdown.tsx @@ -7,13 +7,18 @@ import DropdownButton from './DropdownMenu/DropdownButton' export default function RefinementDropdown({ refinement, setRefinement, + disabled = false, }: { refinement: RefinementKey setRefinement: (r: RefinementKey) => void + disabled?: boolean }) { const { t } = useTranslation('ui') return ( - + {allRefinementKeys.map((r) => ( +export const minTotalStatKeys: MinTotalStatKey[] = [ + 'atk', + 'hp', + 'def', + 'eleMas', + 'enerRech_', + 'critRate_', + 'critDMG_', +] export class CharacterTCDataManager extends DataManager< CharacterKey, @@ -85,6 +97,7 @@ export function initCharTC(weaponKey: WeaponKey): ICharTC { target: undefined, distributedSubstats: 45, maxSubstats: initCharTcOptimizationMaxSubstats(), + minTotal: {}, }, } } @@ -156,7 +169,7 @@ function validateCharTcOptimization( optimization: unknown ): ICharTC['optimization'] | undefined { if (typeof optimization !== 'object') return undefined - let { target, distributedSubstats, maxSubstats } = + let { target, distributedSubstats, maxSubstats, minTotal } = optimization as ICharTC['optimization'] if (!Array.isArray(target)) target = undefined if (typeof distributedSubstats !== 'number') distributedSubstats = 20 @@ -165,7 +178,14 @@ function validateCharTcOptimization( maxSubstats = objKeyMap([...allSubstatKeys], (k) => typeof maxSubstats[k] === 'number' ? maxSubstats[k] : 0 ) - return { target, distributedSubstats, maxSubstats } + if (typeof minTotal !== 'object') minTotal = {} + minTotal = Object.fromEntries( + Object.entries(minTotal).filter( + ([k, v]) => minTotalStatKeys.includes(k) && typeof v === 'number' + ) + ) + + return { target, distributedSubstats, maxSubstats, minTotal } } function initCharTcOptimizationMaxSubstats(): ICharTC['optimization']['maxSubstats'] { return objKeyMap( diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactMainStatAndSetEditor/ArtifactMainLevelSlot.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactMainStatAndSetEditor/ArtifactMainLevelSlot.tsx index cd57a60189..7321a66bba 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactMainStatAndSetEditor/ArtifactMainLevelSlot.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactMainStatAndSetEditor/ArtifactMainLevelSlot.tsx @@ -24,8 +24,10 @@ import { CharTCContext } from '../CharTCContext' export function ArtifactMainLevelSlot({ slotKey, + disabled = false, }: { slotKey: ArtifactSlotKey + disabled?: boolean }) { const { charTC: { @@ -82,6 +84,7 @@ export function ArtifactMainLevelSlot({ fullWidth title={} color={KeyMap.getVariant(statKey) ?? 'success'} + disabled={disabled} > {keys.map((msk) => ( } + disabled={disabled} > {[5, 4, 3].map((r) => ( l !== undefined && setSlot({ level: l })} sx={{ borderRadius: 1, pl: 1, my: 0, height: '100%' }} inputProps={{ sx: { pl: 0.5, width: '2em' }, max: 20, min: 0 }} + disabled={disabled} /> {`${artDisplayValue( diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactMainStatAndSetEditor/ArtifactSetEditor.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactMainStatAndSetEditor/ArtifactSetEditor.tsx index aeb226aed3..6203109334 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactMainStatAndSetEditor/ArtifactSetEditor.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactMainStatAndSetEditor/ArtifactSetEditor.tsx @@ -15,9 +15,11 @@ import { CharTCContext } from '../CharTCContext' export function ArtifactSetEditor({ setKey, remaining, + disabled = false, }: { setKey: ArtifactSetKey remaining: number + disabled?: boolean }) { const { charTC: { @@ -66,6 +68,7 @@ export function ArtifactSetEditor({ {value}-set} + disabled={disabled} > {Object.keys(artifactSheet.setEffects) .map((setKey) => parseInt(setKey)) @@ -79,7 +82,12 @@ export function ArtifactSetEditor({ ))} - @@ -93,6 +101,7 @@ export function ArtifactSetEditor({ setNumKey={parseInt(setNumKey) as SetNum} hideHeader conditionalsOnly + disabled={disabled} /> ))} diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactMainStatAndSetEditor/ArtifactSetsEditor.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactMainStatAndSetEditor/ArtifactSetsEditor.tsx index eddc28050e..d3e80003a3 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactMainStatAndSetEditor/ArtifactSetsEditor.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactMainStatAndSetEditor/ArtifactSetsEditor.tsx @@ -7,7 +7,11 @@ import { getArtSheet } from '../../../../../Data/Artifacts' import { ArtifactSetEditor } from './ArtifactSetEditor' import { CharTCContext } from '../CharTCContext' -export function ArtifactSetsEditor() { +export function ArtifactSetsEditor({ + disabled = false, +}: { + disabled?: boolean +}) { const { charTC: { artifact: { sets: artSet }, @@ -35,6 +39,7 @@ export function ArtifactSetsEditor() { key={setKey} setKey={setKey as ArtifactSetKey} remaining={remaining} + disabled={disabled} /> ))} @@ -49,6 +54,7 @@ export function ArtifactSetsEditor() { (n) => parseInt(n) > remaining ) } + disabled={disabled} /> diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactMainStatAndSetEditor/index.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactMainStatAndSetEditor/index.tsx index d4c028ed81..f200a8bb39 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactMainStatAndSetEditor/index.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactMainStatAndSetEditor/index.tsx @@ -5,20 +5,24 @@ import CardLight from '../../../../../Components/Card/CardLight' import { ArtifactMainLevelSlot } from './ArtifactMainLevelSlot' import { ArtifactSetsEditor } from './ArtifactSetsEditor' -export function ArtifactMainStatAndSetEditor() { +export function ArtifactMainStatAndSetEditor({ + disabled = false, +}: { + disabled?: boolean +}) { return ( {allArtifactSlotKeys.map((s) => ( - + ))} } > - + ) diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactAllSubstatEditor.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactAllSubstatEditor.tsx index ad2a1f2d30..2ef8f8f488 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactAllSubstatEditor.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactAllSubstatEditor.tsx @@ -27,7 +27,11 @@ function getMinRoll(charTC: ICharTC) { function getMinMax(charTC: ICharTC) { return Math.floor(Math.min(...Object.values(charTC.optimization.maxSubstats))) } -export function ArtifactAllSubstatEditor() { +export function ArtifactAllSubstatEditor({ + disabled = false, +}: { + disabled?: boolean +}) { const { t } = useTranslation('page_character') const { charTC, setCharTC } = useContext(CharTCContext) // Encapsulate the values in an array so that changes to the same number still trigger useEffects @@ -100,6 +104,7 @@ export function ArtifactAllSubstatEditor() { valueLabelDisplay="auto" onChange={(e, v) => setRolls([v as number])} onChangeCommitted={(e, v) => setRolls([v as number])} + disabled={disabled} /> v !== undefined && setRolls([v])} sx={{ borderRadius: 1, px: 1, my: 0, height: '100%', width: '7em' }} inputProps={{ sx: { textAlign: 'right', pr: 0.5 }, min: 0, step: 1 }} + disabled={disabled} /> maxRolls ? 'warning' : 'primary'} sx={{ borderRadius: 1, px: 1, my: 0, height: '100%', width: '6.5em' }} inputProps={{ sx: { textAlign: 'right', pr: 0.5 }, min: 0, step: 1 }} + disabled={disabled} /> ) diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactSubstatEditor.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactSubstatEditor.tsx index 160807be38..6624bb3126 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactSubstatEditor.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactSubstatEditor.tsx @@ -10,7 +10,13 @@ import KeyMap from '../../../../../KeyMap' import StatIcon from '../../../../../KeyMap/StatIcon' import { CharTCContext } from '../CharTCContext' -export function ArtifactSubstatEditor({ statKey }: { statKey: SubstatKey }) { +export function ArtifactSubstatEditor({ + statKey, + disabled = false, +}: { + statKey: SubstatKey + disabled?: boolean +}) { const { t } = useTranslation('page_character') const { charTC: { @@ -80,6 +86,7 @@ export function ArtifactSubstatEditor({ statKey }: { statKey: SubstatKey }) { onChange={(v) => v !== undefined && setValue(v)} sx={{ borderRadius: 1, px: 1, height: '100%', width: '5em' }} inputProps={{ sx: { textAlign: 'right' }, min: 0 }} + disabled={disabled} /> v !== undefined && setValue(v * substatValue)} sx={{ borderRadius: 1, px: 1, my: 0, height: '100%', width: '6.5em' }} inputProps={{ sx: { textAlign: 'right', pr: 0.5 }, min: 0, step: 1 }} + disabled={disabled} /> {/* {t(numMains ? `tabTheorycraft.maxRollsMain` : `tabTheorycraft.maxRolls`, { value: maxRolls })}} placement="top"> */} @@ -155,6 +163,7 @@ export function ArtifactSubstatEditor({ statKey }: { statKey: SubstatKey }) { valueLabelDisplay="auto" onChange={(e, v) => setRolls(v as number)} onChangeCommitted={(e, v) => setRValue(v as number)} + disabled={disabled} /> @@ -172,6 +181,7 @@ export function ArtifactSubstatEditor({ statKey }: { statKey: SubstatKey }) { } sx={{ borderRadius: 1, px: 1, my: 0, height: '100%', width: '6em' }} inputProps={{ sx: { textAlign: 'right', pr: 0.5 }, min: 0, step: 1 }} + disabled={disabled} /> diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/index.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/index.tsx index 20384f2d78..7df7e32617 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/index.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/index.tsx @@ -13,7 +13,7 @@ import DropdownButton from '../../../../../Components/DropdownMenu/DropdownButto import { CharTCContext } from '../CharTCContext' import { ArtifactAllSubstatEditor } from './ArtifactAllSubstatEditor' import { ArtifactSubstatEditor } from './ArtifactSubstatEditor' -export function ArtifactSubCard() { +export function ArtifactSubCard({ disabled = false }: { disabled?: boolean }) { const { t } = useTranslation('page_character') const { charTC: { @@ -58,6 +58,7 @@ export function ArtifactSubCard() { {substatTypeKeys.map((st) => ( setRarity(r)} filter={(r) => r !== rarity} + disabled={disabled} /> {t`tabTheorycraft.maxTotalRolls`}} @@ -100,9 +102,9 @@ export function ArtifactSubCard() { - + {Object.entries(substats).map(([k]) => ( - + ))} diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/BuildConstaintCard.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/BuildConstaintCard.tsx new file mode 100644 index 0000000000..3dc6ee65ad --- /dev/null +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/BuildConstaintCard.tsx @@ -0,0 +1,152 @@ +import { StatIcon } from '@genshin-optimizer/gi-svgicons' +import { + CustomNumberInput, + CustomNumberInputButtonGroupWrapper, + DropdownButton, +} from '@genshin-optimizer/ui-common' +import { unit } from '@genshin-optimizer/util' +import ClearIcon from '@mui/icons-material/Clear' +import { + Box, + Button, + ButtonGroup, + CardHeader, + Divider, + ListItemIcon, + ListItemText, + MenuItem, + Stack, +} from '@mui/material' +import { useContext } from 'react' +import { ArtifactStatWithUnit } from '../../../../Components/Artifact/ArtifactStatKeyDisplay' +import CardLight from '../../../../Components/Card/CardLight' +import type { MinTotalStatKey } from '../../../../Database/DataManagers/CharacterTCData' +import { minTotalStatKeys } from '../../../../Database/DataManagers/CharacterTCData' +import { CharTCContext } from './CharTCContext' + +export function BuildConstaintCard({ disabled }: { disabled: boolean }) { + const { + charTC: { + optimization: { minTotal }, + }, + setCharTC, + } = useContext(CharTCContext) + + return ( + + + + + + {Object.entries(minTotal).map(([k, v]) => ( + + ))} + + {minTotalStatKeys.map((k) => ( + + setCharTC((charTC) => { + charTC.optimization.minTotal[k] = 0 + }) + } + > + + + + + + + + ))} + + + + + ) +} + +function Selector({ + statKey, + value, + disabled, +}: { + statKey?: MinTotalStatKey + value?: number + disabled: boolean +}) { + const { setCharTC } = useContext(CharTCContext) + const unitStr = unit(statKey) + return ( + + : undefined} + title={ + statKey ? ( + + ) : ( + 'Select a Stat Constraint' + ) + } + > + {minTotalStatKeys.map((k) => ( + + setCharTC((charTC) => { + charTC.optimization.minTotal[statKey] = 0 + }) + } + > + + + + + + + + ))} + + + + statKey && + setCharTC((charTC) => { + charTC.optimization.minTotal[statKey] = value + }) + } + endAdornment={unitStr || } + disabled={!statKey || disabled} + sx={{ + px: 1, + }} + inputProps={{ + sx: { textAlign: 'right' }, + }} + /> + + {!!statKey && ( + + )} + + ) +} diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/GcsimButton.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/GcsimButton.tsx index 7e2ce64b8b..6951060c8b 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/GcsimButton.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/GcsimButton.tsx @@ -45,7 +45,7 @@ const GOODtoSRL: Record = { // "atkspd%", // "dmg%", } -export default function GcsimButton() { +export default function GcsimButton({ disabled }: { disabled: boolean }) { const { t } = useTranslation(['page_character', 'settings']) const [open, onOpen, onClose] = useBoolState() @@ -126,7 +126,11 @@ ${charKeyLow} add stats ${substatsText};` return ( <> -

diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/WeaponEditorCard.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/WeaponEditorCard.tsx index 01422684bd..fcd81a43a4 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/WeaponEditorCard.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/WeaponEditorCard.tsx @@ -32,8 +32,10 @@ const WeaponSelectionModal = React.lazy( export function WeaponEditorCard({ weaponTypeKey, + disabled, }: { weaponTypeKey: WeaponTypeKey + disabled: boolean }) { const { charTC, setCharTC } = useContext(CharTCContext) const setWeapon = useCallback( @@ -89,11 +91,13 @@ export function WeaponEditorCard({ color="info" sx={{ flexGrow: 1 }} onClick={onShow} + disabled={disabled} > {weaponSheet?.name} {weaponSheet.hasRefinement && ( setWeapon({ refinement: r })} /> @@ -105,6 +109,7 @@ export function WeaponEditorCard({ ascension={ascension} setBoth={setWeapon} useLow={!weaponSheet.hasRefinement} + disabled={disabled} /> {data && weaponSheet?.document && ( - + )}

hJp)u0eg)uX0UU(QWm43BB;UAOj|@S120@_F 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

diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx index ac670e98d0..9e8f0ffcfc 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx @@ -1,11 +1,13 @@ +import { BuildAlert, initialBuildStatus } from '@genshin-optimizer/gi-ui' import { getMainStatDisplayValue, getSubstatValue, } from '@genshin-optimizer/gi-util' import { useBoolState } from '@genshin-optimizer/react-util' -import { objMap } from '@genshin-optimizer/util' +import { objMap, toPercent } from '@genshin-optimizer/util' import { CopyAll, Refresh } from '@mui/icons-material' import CalculateIcon from '@mui/icons-material/Calculate' +import CloseIcon from '@mui/icons-material/Close' import { Box, Button, @@ -26,6 +28,8 @@ import { useDeferredValue, useEffect, useMemo, + useRef, + useState, } from 'react' import { Trans, useTranslation } from 'react-i18next' import { useLocation } from 'react-router-dom' @@ -40,6 +44,7 @@ import { DataContext } from '../../../../Context/DataContext' import { initCharTC } from '../../../../Database/DataManagers/CharacterTCData' import { DatabaseContext } from '../../../../Database/Database' import { constant, percent } from '../../../../Formula/utils' +import useDBMeta from '../../../../ReactHooks/useDBMeta' import useTeamData from '../../../../ReactHooks/useTeamData' import type { ICachedArtifact } from '../../../../Types/artifact' import type { ICharTC } from '../../../../Types/character' @@ -49,17 +54,20 @@ import { defaultInitialWeaponKey } from '../../../../Util/WeaponUtil' import OptimizationTargetSelector from '../TabOptimize/Components/OptimizationTargetSelector' import { ArtifactMainStatAndSetEditor } from './ArtifactMainStatAndSetEditor' import { ArtifactSubCard } from './ArtifactSubCard' +import { BuildConstaintCard } from './BuildConstaintCard' import type { SetCharTCAction } from './CharTCContext' import { CharTCContext } from './CharTCContext' import GcsimButton from './GcsimButton' import { WeaponEditorCard } from './WeaponEditorCard' import kqmIcon from './kqm.png' -import { optimizeTc } from './optimizeTc' +import type { TCWorkerResult } from './optimizeTc' +import { optimizeTcGetNodes } from './optimizeTc' import useCharTC from './useCharTC' export default function TabTheorycraft() { const { t } = useTranslation('page_character') const { database } = useContext(DatabaseContext) const { data: oldData } = useContext(DataContext) + const { gender } = useDBMeta() const { character, character: { key: characterKey, compareData }, @@ -228,25 +236,82 @@ export default function TabTheorycraft() { }, [setCharTC] ) + const workerRef = useRef(null) + if (workerRef.current === null) + workerRef.current = new Worker( + new URL('./optimizeTcWorker.ts', import.meta.url) + ) + + const [status, setStatus] = useState(initialBuildStatus()) + const solving = status.type === 'active' + + const terminateWorker = useCallback(() => { + workerRef.current.terminate() + setStatus(initialBuildStatus()) + }, [workerRef]) const optimizeSubstats = (apply: boolean) => { - const { maxBuffer, distributed = 0 } = optimizeTc( - teamData, - characterKey, - charTC - ) - if (!apply || !maxBuffer || !distributed) return - const comp = (statKey: string) => (statKey.endsWith('_') ? 100 : 1) - setCharTC((charTC) => { - charTC.artifact.substats.stats = objMap( - charTC.artifact.substats.stats, - (v, k) => v + (maxBuffer![k] ?? 0) * comp(k) - ) - charTC.optimization.distributedSubstats = - distributedSubstats - distributed - return charTC - }) + const nodes = optimizeTcGetNodes(teamData, characterKey, charTC) + console.log({ nodes }) + workerRef.current.postMessage({ charTC, ...nodes }) + setStatus((s) => ({ + ...s, + type: 'active', + startTime: performance.now(), + finishTime: undefined, + })) + + workerRef.current.onmessage = ({ data }: MessageEvent) => { + const { resultType } = data + switch (resultType) { + case 'total': + setStatus((s) => ({ ...s, total: data.total })) + break + case 'count': + setStatus((s) => ({ + ...s, + tested: data.tested, + failed: data.failed, + })) + break + case 'finalize': { + const { maxBuffer, distributed, tested, failed, skipped } = data + setStatus((s) => ({ + ...s, + type: 'inactive', + tested, + failed, + skipped, + finishTime: performance.now(), + })) + + if (!apply) { + console.log({ + maxBuffer, + distributed, + tested, + failed, + skipped, + }) + break + } + + setCharTC((charTC) => { + charTC.artifact.substats.stats = objMap( + charTC.artifact.substats.stats, + (v, k) => v + toPercent(maxBuffer![k] ?? 0, k) + ) + charTC.optimization.distributedSubstats = + distributedSubstats - distributed + return charTC + }) + + break + } + } + } } + const kqms = useCallback(() => { setCharTC((charTC) => { charTC.artifact.substats.type = 'mid' @@ -284,10 +349,13 @@ export default function TabTheorycraft() { - - - - + + + + - + - + + + + - + setOptimizationTarget(target)} /> v !== undefined && setDistributedSubstats(v)} endAdornment={'Substats'} sx={{ @@ -346,24 +419,39 @@ export default function TabTheorycraft() { min: 0, }} /> - + {!solving ? ( + + ) : ( + + )} {isDev && ( )} + ) : ( @@ -382,12 +470,23 @@ export default function TabTheorycraft() {
) } -function CopyFromEquippedButton({ action }: { action: () => void }) { +function CopyFromEquippedButton({ + action, + disabled, +}: { + action: () => void + disabled?: boolean +}) { const { t } = useTranslation(['page_character', 'ui']) const [open, onOpen, onClose] = useBoolState() return ( <> - @@ -417,12 +516,23 @@ function CopyFromEquippedButton({ action }: { action: () => void }) { ) } -function ResetButton({ action }: { action: () => void }) { +function ResetButton({ + action, + disabled, +}: { + action: () => void + disabled: boolean +}) { const { t } = useTranslation(['page_character', 'ui']) const [open, onOpen, onClose] = useBoolState() return ( <> - @@ -452,7 +562,13 @@ function ResetButton({ action }: { action: () => void }) { ) } -function KQMSButton({ action }: { action: () => void }) { +function KQMSButton({ + action, + disabled, +}: { + action: () => void + disabled: boolean +}) { const { t } = useTranslation(['page_character', 'ui']) const [open, onOpen, onClose] = useBoolState() return ( @@ -461,6 +577,7 @@ function KQMSButton({ action }: { action: () => void }) { color="keqing" onClick={onOpen} startIcon={} + disabled={disabled} > {t('tabTheorycraft.kqmsDialog.kqmsBtn')} diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.ts b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.ts index 71c528b112..c39462c89e 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.ts +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.ts @@ -1,10 +1,15 @@ import type { SubstatKey } from '@genshin-optimizer/consts' -import { allSubstatKeys, type CharacterKey } from '@genshin-optimizer/consts' +import { + allSubstatKeys, + artSubstatRollData, + type CharacterKey, +} from '@genshin-optimizer/consts' import { getSubstatValue } from '@genshin-optimizer/gi-util' -import { clampLow, objMap } from '@genshin-optimizer/util' +import { objMap, toDecimal } from '@genshin-optimizer/util' import type { TeamData } from '../../../../Context/DataContext' import { mergeData } from '../../../../Formula/api' import { mapFormulas } from '../../../../Formula/internal' +import type { OptNode } from '../../../../Formula/optimization' import { optimize, precompute } from '../../../../Formula/optimization' import type { NumNode } from '../../../../Formula/type' import { constant } from '../../../../Formula/utils' @@ -12,26 +17,41 @@ import type { ICharTC } from '../../../../Types/character' import { objPathValue, shouldShowDevComponents } from '../../../../Util/Util' import { dynamicData } from '../TabOptimize/foreground' +export type TCWorkerResult = TotalResult | CountResult | FinalizeResult + +export interface TotalResult { + resultType: 'total' + total: number +} +export interface CountResult { + resultType: 'count' + tested: number // tested, including `failed` + failed: number // tested but fail the filter criteria, e.g., not enough EM + skipped: number // failed feasibility check +} + +export interface FinalizeResult { + resultType: 'finalize' + maxBuffer: Partial> + distributed: number + tested: number + failed: number + skipped: number +} + // This solves // $\argmax_{x\in N^k, \sum x <= `distributedSubstats`, x <= `maxSubstats`} `optimizationTarget`(x)$ without assumptions on the properties of `optimizationTarget` // where $N$ are the natural numbers and $k$ is the number of `SubstatKey`s // We brute force iterate over all substats in the graph and compute the maximum // n.b. some substat combinations may not be materializable into real artifacts -export function optimizeTc( +export function optimizeTcGetNodes( teamDataProp: TeamData, characterKey: CharacterKey, charTC: ICharTC ) { - const startTime = performance.now() const { - artifact: { - substats: { stats: substats, type: substatsType, rarity }, - }, - optimization: { - target: optimizationTarget, - distributedSubstats, - maxSubstats: rawMaxSubstats, - }, + artifact: { sets: artSets }, + optimization: { target: optimizationTarget, minTotal }, } = charTC if (!optimizationTarget) return {} const workerData = teamDataProp[characterKey]?.target.data![0] @@ -44,9 +64,11 @@ export function optimizeTc( optimizationTarget ) as NumNode | undefined if (!unoptimizedOptimizationTargetNode) return {} - const unoptimizedNodes = [unoptimizedOptimizationTargetNode] + + const constraints = Object.keys(minTotal).map((k) => workerData.total[k]) + let nodes = optimize( - unoptimizedNodes, + [unoptimizedOptimizationTargetNode, ...constraints], workerData, ({ path: [p] }) => p !== 'dyn' ) @@ -55,94 +77,209 @@ export function optimizeTc( nodes, (f) => { if (f.operation === 'read' && f.path[0] === 'dyn') { - const a = charTC.artifact.sets[f.path[1]] + const a = artSets[f.path[1]] if (a) return constant(a) - if (!allSubstatKeys.includes(f.path[1] as any)) return constant(0) + if (!(allSubstatKeys as readonly string[]).includes(f.path[1])) + return constant(0) } return f }, (f) => f ) nodes = optimize(nodes, {}, (_) => false) + return { + nodes, + } +} - const subs = new Set() +export function optimizeTcUsingNodes( + nodes: OptNode[], + charTC: ICharTC, + callback: (r: TCWorkerResult) => void +) { + const startTime = performance.now() + const { + artifact: { + slots, + substats: { stats: substats, type: substatsType, rarity }, + }, + optimization: { distributedSubstats, maxSubstats, minTotal }, + } = charTC + + const scalesWith = new Set() const compute = precompute( nodes, {}, (f) => { - subs.add(f.path[1]) - return f.path[1] + const val = f.path[1] + scalesWith.add(val) + return val }, - 2 + 1 ) - const comp = (statKey: string) => (statKey.endsWith('_') ? 100 : 1) const substatValue = (x: string, m: number) => m * getSubstatValue(x as SubstatKey, rarity, substatsType, false) - let maxBuffer: Record = Object.fromEntries( - [...subs].map((x) => [x, 0]) + const scalesWithSub = [...scalesWith].filter((k) => + allSubstatKeys.includes(k as SubstatKey) + ) + + const existingRolls = objMap(substats, (v, k) => + Math.ceil(substats[k] / getSubstatValue(k, rarity, substatsType)) ) - const subsArr = [...subs] - let distributed = distributedSubstats - const maxSubstats = objMap(rawMaxSubstats, (v, k) => { - return ( - v - - clampLow( - Math.ceil(substats[k] / getSubstatValue(k, rarity, substatsType)), - 0 + const maxSubsAssignable = objMap(maxSubstats, (v, k) => v - existingRolls[k]) + let max = -Infinity + const buffer: Record = {} //Object.fromEntries([...subs].map((x) => [x, 0])) + const bufferRolls: Partial> = { + other: 0, + } // Object.fromEntries([...subs].map((x) => [x, 0])) + let maxBuffer: Record = structuredClone(buffer) + let maxBufferRolls: Partial> = + structuredClone(bufferRolls) + const mainStatsCount = getMainStatsCount(slots) + const minSubLines = getMinSubLines(slots) + + const alreadyFeasible = + getMinOtherRolls( + Object.entries(existingRolls), + mainStatsCount, + minSubLines + ) <= 0 + + callback({ + resultType: 'total', + total: countPerms( + distributedSubstats, + [...scalesWithSub, 'other'].map((k) => + k === 'other' ? distributedSubstats : maxSubsAssignable[k] ) - ) + ), }) - const assignableMaxTot = subsArr.reduce((a, x) => a + maxSubstats[x], 0) - if (assignableMaxTot <= distributedSubstats) { - distributed = assignableMaxTot - maxBuffer = Object.fromEntries( - subsArr.map((x) => [x, substatValue(x, maxSubstats[x])]) - ) - if (shouldShowDevComponents) - console.log({ maxBuffer, subsArr, maxSubstats, distributed }) - } else { - let max = -Infinity - const buffer = Object.fromEntries([...subs].map((x) => [x, 0])) - const existingSubs = objMap( - charTC.artifact.substats.stats, - (v, k) => v / comp(k) - ) - const permute = (toAssign: number, [x, ...xs]: string[]) => { - if (xs.length === 0) { - if (toAssign > maxSubstats[x]) return - buffer[x] = substatValue(x, toAssign) - const [result] = compute([ - { values: existingSubs }, - { values: buffer }, - ] as const) - if (result > max) { - max = result - maxBuffer = structuredClone(buffer) + let tested = 0 + let failed = 0 + let skipped = 0 + const constraints = Object.entries(minTotal).map(([k, v]) => toDecimal(v, k)) + const permute = (toAssign: number, [x, ...xs]: string[]) => { + if (xs.length === 0) { + if (toAssign > maxSubsAssignable[x]) return + tested++ + if (!(tested % 100_000)) + callback({ + resultType: 'count', + tested, + failed, + skipped, + }) + if (x !== 'other') buffer[x] = substatValue(x, toAssign) + bufferRolls[x] = toAssign + if (!alreadyFeasible) { + //check for distributed feasibility + const allRolls = allSubstatKeys.map((k) => [ + k, + (existingRolls[k] ?? 0) + (bufferRolls[k] ?? 0), + ]) as Array<[SubstatKey, number]> + const minOtherRolls = getMinOtherRolls( + allRolls, + mainStatsCount, + minSubLines + ) + // not feasible + if ((bufferRolls.other ?? 0) < minOtherRolls) { + skipped++ + return } + } + const results = compute([{ values: buffer }] as const) + // check constraints + if (constraints.some((c, i) => results[i + 1] < c)) { + failed++ return } - for (let i = 0; i <= Math.min(maxSubstats[x], toAssign); i++) { - // TODO: Making sure that i + \sum { maxSubstats[xs] } >= distributedSubstats in each recursion will reduce unnecessary recursion considerably for large problems. It will also tighten the possibilities for the leaf recursion, so you don't need so many checkings. - // https://github.com/frzyc/genshin-optimizer/pull/781#discussion_r1138083742 - buffer[x] = substatValue(x, i) - permute(toAssign - i, xs) + const result = results[0] + if (result > max) { + max = result + maxBuffer = structuredClone(buffer) + maxBufferRolls = structuredClone(bufferRolls) } + return + } + for (let i = 0; i <= Math.min(maxSubsAssignable[x], toAssign); i++) { + // TODO: Making sure that i + \sum { maxSubstats[xs] } >= distributedSubstats in each recursion will reduce unnecessary recursion considerably for large problems. It will also tighten the possibilities for the leaf recursion, so you don't need so many checkings. + // https://github.com/frzyc/genshin-optimizer/pull/781#discussion_r1138083742 + buffer[x] = substatValue(x, i) + bufferRolls[x] = i + permute(toAssign - i, xs) } - permute(distributedSubstats, subsArr) - if (shouldShowDevComponents) { - console.log(`Took ${performance.now() - startTime} ms`) - console.log({ - maxBuffer, - maxBufferInt: objMap( - maxBuffer, - (v, k) => - v / getSubstatValue(k as SubstatKey, rarity, substatsType, false) - ), - subsArr, - }) + } + permute(distributedSubstats, [...scalesWithSub, 'other']) + if (shouldShowDevComponents) { + console.log(`Took ${performance.now() - startTime} ms`) + console.log({ + maxBuffer, + maxBufferRolls, + scalesWith, + }) + } + const distributed = Object.entries(maxBufferRolls).reduce( + (accu, [k, v]) => accu + (k === 'other' ? 0 : v), + 0 + ) + callback({ + resultType: 'finalize', + distributed, + maxBuffer, + tested, + failed, + skipped, + }) + // return { + // maxBuffer, + // distributed, + // scalesWith, + // } +} + +function getMinOtherRolls( + subsRolls: Array<[SubstatKey, number]>, + mainStatsCount: Partial>, + minSublines: number = 4 * 5 +) { + const maxSubSlots = subsRolls.reduce((accu, [k, v]) => { + const maxStatSlot = 5 - (mainStatsCount[k] ?? 0) + return accu + Math.min(v, maxStatSlot) + }, 0) + return minSublines - maxSubSlots +} + +function getMinSubLines(slots: ICharTC['artifact']['slots']) { + return Object.values(slots).reduce((minSubLines, { rarity, level }) => { + const { high, low } = artSubstatRollData[rarity] + return minSubLines + (level >= 4 ? high : low) + }, 0) +} +function getMainStatsCount(slots: ICharTC['artifact']['slots']) { + const mainStatsCount: Partial> = {} + + Object.values(slots).forEach(({ statKey }) => { + mainStatsCount[statKey as SubstatKey] = + (mainStatsCount[statKey as SubstatKey] ?? 0) + 1 + }, 0) + return mainStatsCount +} +// Count the number of integer solutions of `a_0 + a_1 + ... + a_(N-1) == sum` (where `N == bounds.length`) such that `0 <= a_i <= bounds[i]` +function countPerms(sum: number, bounds: number[]): number { + // counts[s] = the number of ways to sum to `s` + let counts = Array(sum + 1).fill(0) + counts[0] = 1 + for (const bound of bounds) { + const new_counts = Array(sum + 1).fill(0) + for (let a_i = 0; a_i <= bound; a_i++) { + for (let s = a_i; s <= sum; s++) { + new_counts[s] += counts[s - a_i] + } } + counts = new_counts } - return { maxBuffer, distributed } + return counts[sum] } diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTcWorker.ts b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTcWorker.ts new file mode 100644 index 0000000000..2039cb5905 --- /dev/null +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTcWorker.ts @@ -0,0 +1,13 @@ +import type { OptNode } from '../../../../Formula/optimization' +import type { ICharTC } from '../../../../Types/character' +import { optimizeTcUsingNodes } from './optimizeTc' + +type WorkerData = { + nodes: OptNode[] + charTC: ICharTC +} +export {} +onmessage = async (e: MessageEvent) => { + const { nodes, charTC } = e.data + optimizeTcUsingNodes(nodes, charTC, (r) => postMessage(r)) +} diff --git a/apps/frontend/src/app/Types/character.d.ts b/apps/frontend/src/app/Types/character.d.ts index d0c111422b..0acc5de35d 100644 --- a/apps/frontend/src/app/Types/character.d.ts +++ b/apps/frontend/src/app/Types/character.d.ts @@ -83,5 +83,12 @@ export type ICharTC = { target?: string[] distributedSubstats: number maxSubstats: Record + /** NB: this is in total raw value, not substat count + * This includes stats from other sources + * e.g. `{enerRech_: 0.3}` + */ + minTotal: Partial< + Record, number> + > } } diff --git a/libs/gi-ui/src/Character/BuildAlert.tsx b/libs/gi-ui/src/Character/BuildAlert.tsx new file mode 100644 index 0000000000..0f55845eb3 --- /dev/null +++ b/libs/gi-ui/src/Character/BuildAlert.tsx @@ -0,0 +1,132 @@ +import type { CharacterKey, GenderKey } from '@genshin-optimizer/consts' +import { timeStringMs } from '@genshin-optimizer/util' +import { Alert, Grid, LinearProgress, Typography, styled } from '@mui/material' +import type { ReactNode } from 'react' +import { CharacterName } from './Trans' + +export const warningBuildNumber = 10000000 +export type BuildStatus = { + type: 'active' | 'inactive' + tested: number // tested, including `failed` + failed: number // tested but fail the filter criteria, e.g., not enough EM + skipped: number + total: number + startTime?: number + finishTime?: number +} + +export function initialBuildStatus(): BuildStatus { + return { + type: 'inactive', + tested: 0, + failed: 0, + skipped: 0, + total: 0, + } +} + +const Monospace = styled('strong')({ + fontFamily: 'monospace', +}) + +const BorderLinearProgress = styled(LinearProgress)(() => ({ + height: 10, + borderRadius: 5, +})) +export function BuildAlert({ + status: { type, tested, failed: _, skipped, total, startTime, finishTime }, + characterKey, + gender, +}: { + status: BuildStatus + characterKey: CharacterKey + gender: GenderKey +}) { + const hasTotal = isFinite(total) + + const generatingBuilds = type !== 'inactive' + const unskipped = total - skipped + + const testedString = {tested.toLocaleString()} + const unskippedString = {unskipped.toLocaleString()} + const skippedText = !!skipped && ( + + ({{skipped.toLocaleString()}} skipped) + + ) + + const durationString = ( + + {timeStringMs( + Math.round((finishTime ?? performance.now()) - (startTime ?? NaN)) + )} + + ) + + const color = 'success' as 'success' | 'warning' | 'error' + let title = '' as ReactNode + let subtitle = '' as ReactNode + let progress = undefined as undefined | number + + if (generatingBuilds) { + progress = (tested * 100) / unskipped + title = ( + + Generating and testing {testedString} + {hasTotal ? <>/{unskippedString} : undefined} build configurations + against the criteria for{' '} + + + + . {skippedText} + + ) + subtitle = Time elapsed: {durationString} + } else if (tested + skipped) { + progress = 100 + title = ( + + Generated and tested {testedString} Build configurations against the + criteria for{' '} + + + + . {skippedText} + + ) + subtitle = Total duration: {durationString} + } else { + return null + } + + return ( + + {title} + {subtitle} + {progress !== undefined && ( + + {hasTotal && ( + + {`${progress.toFixed(1)}%`} + + )} + + + + + )} + + ) +} diff --git a/libs/gi-ui/src/Character/index.ts b/libs/gi-ui/src/Character/index.ts index 2ddd72de49..827da00b2e 100644 --- a/libs/gi-ui/src/Character/index.ts +++ b/libs/gi-ui/src/Character/index.ts @@ -1 +1,2 @@ export * from './Trans' +export * from './BuildAlert' From ac09bdc4affc963da232cda120bc33a5ad376972 Mon Sep 17 00:00:00 2001 From: frzyc Date: Thu, 18 Jan 2024 22:58:57 -0500 Subject: [PATCH 50/61] add unit test --- .../Tabs/TabTheorycraft/index.tsx | 39 ++--- .../Tabs/TabTheorycraft/optimizeTc.test.ts | 148 ++++++++++++++++++ .../Tabs/TabTheorycraft/optimizeTc.ts | 46 +++++- .../src/app/ReactHooks/useTeamData.tsx | 2 +- 4 files changed, 204 insertions(+), 31 deletions(-) create mode 100644 apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.test.ts diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx index 9e8f0ffcfc..dbf0841e28 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx @@ -61,7 +61,11 @@ import GcsimButton from './GcsimButton' import { WeaponEditorCard } from './WeaponEditorCard' import kqmIcon from './kqm.png' import type { TCWorkerResult } from './optimizeTc' -import { optimizeTcGetNodes } from './optimizeTc' +import { + getArtifactData, + getWeaponData, + optimizeTcGetNodes, +} from './optimizeTc' import useCharTC from './useCharTC' export default function TabTheorycraft() { const { t } = useTranslation('page_character') @@ -164,34 +168,15 @@ export default function TabTheorycraft() { copyFrom, ]) - const deferredData = useDeferredValue(charTC) - const overriderArtData = useMemo(() => { - const stats = { ...deferredData.artifact.substats.stats } - Object.values(deferredData.artifact.slots).forEach( - ({ statKey, rarity, level }) => - (stats[statKey] = - (stats[statKey] ?? 0) + - getMainStatDisplayValue(statKey, rarity, level)) - ) - return { - art: objMap(stats, (v, k) => - k.endsWith('_') ? percent(v / 100) : constant(v) - ), - artSet: objMap(deferredData.artifact.sets, (v) => constant(v)), - } - }, [deferredData]) + const deferredCharTC = useDeferredValue(charTC) + const overriderArtData = useMemo( + () => getArtifactData(deferredCharTC), + [deferredCharTC] + ) const overrideWeapon: ICachedWeapon = useMemo( - () => ({ - id: '', - location: '', - key: charTC.weapon.key, - level: charTC.weapon.level, - ascension: charTC.weapon.ascension, - refinement: charTC.weapon.refinement, - lock: false, - }), - [charTC] + () => getWeaponData(deferredCharTC), + [deferredCharTC] ) const teamData = useTeamData( characterKey, diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.test.ts b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.test.ts new file mode 100644 index 0000000000..cb1f619dc7 --- /dev/null +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.test.ts @@ -0,0 +1,148 @@ +import type { CharacterKey } from '@genshin-optimizer/consts' +import { SandboxStorage } from '@genshin-optimizer/database' +import { ArtCharDatabase } from '../../../../Database/Database' +import { getTeamDataCalc } from '../../../../ReactHooks/useTeamData' +import type { ICharTC } from '../../../../Types/character' +import { + getArtifactData, + getWeaponData, + optimizeTcGetNodes, + optimizeTcUsingNodes, +} from './optimizeTc' + +describe('A general optimizeTC usecase', () => { + it('generate correct distribution', () => { + const charTC: ICharTC = { + artifact: { + slots: { + flower: { level: 20, rarity: 5, statKey: 'hp' }, + plume: { level: 20, rarity: 5, statKey: 'atk' }, + sands: { level: 20, rarity: 5, statKey: 'hp_' }, + goblet: { level: 20, rarity: 5, statKey: 'pyro_dmg_' }, + circlet: { level: 20, rarity: 5, statKey: 'critDMG_' }, + }, + substats: { + type: 'mid', + stats: { + hp: 507.88, + hp_: 9.91, + atk: 33.07, + atk_: 9.91, + def: 39.349999999999994, + def_: 12.39, + eleMas: 39.63, + enerRech_: 49.545, // very close to 50% + critRate_: 6.609999999999999, + critDMG_: 13.209999999999999, + }, + rarity: 5, + }, + sets: { CrimsonWitchOfFlames: 4 }, + }, + weapon: { key: 'StaffOfHoma', level: 90, ascension: 6, refinement: 1 }, + optimization: { + target: ['normal', '0'], + distributedSubstats: 2, + maxSubstats: { + hp: 10, + hp_: 10, + atk: 10, + atk_: 10, + def: 10, + def_: 10, + eleMas: 10, + enerRech_: 10, + critRate_: 10, + critDMG_: 10, + }, + minTotal: { enerRech_: 150 }, + }, + } + const characterKey: CharacterKey = 'HuTao' + const database = new ArtCharDatabase(1, new SandboxStorage({})) + database.weapons.new({ + key: 'StaffOfHoma', + level: 90, + ascension: 6, + refinement: 1, + location: 'HuTao', + lock: true, + }) + database.chars.set(characterKey, { + key: 'HuTao', + level: 89, + ascension: 6, + hitMode: 'avgHit', + reaction: 'vaporize', + conditional: { + StaffOfHoma: { RecklessCinnabar: 'on' }, + HuTao: { GuideToAfterlifeVoyage: 'on', SanguineRouge: 'on' }, + ShimenawasReminiscence: {}, + CrimsonWitchOfFlames: { stack: '1' }, + GildedDreams: { passive: 'on' }, + DesertPavilionChronicle: { set4: 'on' }, + DragonsBane: { BaneOfFlameAndWater: 'on' }, + }, + bonusStats: {}, + enemyOverride: {}, + talent: { auto: 9, skill: 9, burst: 9 }, + infusionAura: '', + constellation: 0, + team: ['Xingqiu', 'Yelan', 'Xiangling'], + teamConditional: { + Xiangling: { + Xiangling: { + afterGuobaHit: 'afterGuobaHit', + afterPyronado: 'duringPyronado', + afterChili: 'afterChili', + }, + }, + Xingqiu: { + NoblesseOblige: { set4: 'on' }, + Xingqiu: { c2: 'on', skill: 'on' }, + }, + Yelan: { Yelan: { a4Stacks: '9' } }, + Zhongli: { Zhongli: { skill: 'on', p1: '5' } }, + KaedeharaKazuha: { + ViridescentVenerer: { swirlpyro: 'pyro' }, + KaedeharaKazuha: { swirlpyro: 'pyro' }, + }, + Diona: { + NoblesseOblige: { set4: 'on' }, + Diona: { Ascension1: 'on', Constellation6: 'higher' }, + }, + }, + compareData: false, + customMultiTarget: [], + }) + + const overrideArt = getArtifactData(charTC) + const overrideWeapon = getWeaponData(charTC) + const teamData = getTeamDataCalc( + database, + characterKey, + 0, + 'F', + overrideArt, + overrideWeapon + ) + + const { nodes } = optimizeTcGetNodes(teamData, characterKey, charTC) + + optimizeTcUsingNodes(nodes, charTC, (data) => { + if (data.resultType !== 'finalize') return + console.log('TEST') + expect(data.maxBufferRolls).toEqual({ + atk: 0, + atk_: 0, + critDMG_: 0, + critRate_: 1, // dmg assignment + eleMas: 0, + enerRech_: 1, // assigned to enerRech for 150 + hp: 0, + hp_: 0, + other: 0, + }) + }) + }) +}) diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.ts b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.ts index c39462c89e..ef32d6f885 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.ts +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTc.ts @@ -4,16 +4,17 @@ import { artSubstatRollData, type CharacterKey, } from '@genshin-optimizer/consts' -import { getSubstatValue } from '@genshin-optimizer/gi-util' +import { getMainStatValue, getSubstatValue } from '@genshin-optimizer/gi-util' import { objMap, toDecimal } from '@genshin-optimizer/util' import type { TeamData } from '../../../../Context/DataContext' import { mergeData } from '../../../../Formula/api' import { mapFormulas } from '../../../../Formula/internal' import type { OptNode } from '../../../../Formula/optimization' import { optimize, precompute } from '../../../../Formula/optimization' -import type { NumNode } from '../../../../Formula/type' -import { constant } from '../../../../Formula/utils' +import type { Data, NumNode } from '../../../../Formula/type' +import { constant, percent } from '../../../../Formula/utils' import type { ICharTC } from '../../../../Types/character' +import type { ICachedWeapon } from '../../../../Types/weapon' import { objPathValue, shouldShowDevComponents } from '../../../../Util/Util' import { dynamicData } from '../TabOptimize/foreground' @@ -33,6 +34,7 @@ export interface CountResult { export interface FinalizeResult { resultType: 'finalize' maxBuffer: Partial> + maxBufferRolls: Partial> distributed: number tested: number failed: number @@ -228,6 +230,7 @@ export function optimizeTcUsingNodes( callback({ resultType: 'finalize', distributed, + maxBufferRolls, maxBuffer, tested, failed, @@ -283,3 +286,40 @@ function countPerms(sum: number, bounds: number[]): number { } return counts[sum] } + +export function getArtifactData(charTC: ICharTC): Data { + const { + artifact: { + slots, + substats: { stats: substats }, + sets, + }, + } = charTC + const allStats = objMap(substats, (v, k) => toDecimal(v, k)) + Object.values(slots).forEach( + ({ statKey, rarity, level }) => + (allStats[statKey] = + (allStats[statKey] ?? 0) + getMainStatValue(statKey, rarity, level)) + ) + return { + art: objMap(allStats, (v, k) => + k.endsWith('_') ? percent(v) : constant(v) + ), + artSet: objMap(sets, (v) => constant(v)), + } +} + +export function getWeaponData(charTC: ICharTC): ICachedWeapon { + const { + weapon: { key, level, ascension, refinement }, + } = charTC + return { + id: '', + location: '', + key, + level, + ascension, + refinement, + lock: false, + } +} diff --git a/apps/frontend/src/app/ReactHooks/useTeamData.tsx b/apps/frontend/src/app/ReactHooks/useTeamData.tsx index 97b1a689dd..728f9d2edf 100644 --- a/apps/frontend/src/app/ReactHooks/useTeamData.tsx +++ b/apps/frontend/src/app/ReactHooks/useTeamData.tsx @@ -74,7 +74,7 @@ export default function useTeamData( return data } -function getTeamDataCalc( +export function getTeamDataCalc( database: ArtCharDatabase, characterKey: CharacterKey | '', mainStatAssumptionLevel = 0, From 3d40c3275e8dfce20fa0a9fba0bebaf643d3ae2b Mon Sep 17 00:00:00 2001 From: frzyc Date: Sun, 21 Jan 2024 11:22:22 -0500 Subject: [PATCH 51/61] add QoL --- .../ArtifactAllSubstatEditor.tsx | 13 +- .../ArtifactSubCard/ArtifactSubstatEditor.tsx | 15 ++ .../Tabs/TabTheorycraft/index.tsx | 140 ++++++++++++------ .../Tabs/TabTheorycraft/optimizeTc.ts | 37 +++++ 4 files changed, 158 insertions(+), 47 deletions(-) diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactAllSubstatEditor.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactAllSubstatEditor.tsx index 2ef8f8f488..554cc29f64 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactAllSubstatEditor.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactAllSubstatEditor.tsx @@ -1,5 +1,5 @@ import { getSubstatValue } from '@genshin-optimizer/gi-util' -import { objMap } from '@genshin-optimizer/util' +import { clamp, objMap } from '@genshin-optimizer/util' import { Box, Slider } from '@mui/material' import { useContext, useDeferredValue, useEffect, useState } from 'react' import CardDark from '../../../../../Components/Card/CardDark' @@ -50,8 +50,17 @@ export function ArtifactAllSubstatEditor({ }, } = charTC charTC.artifact.substats.stats = objMap(stats, (val, statKey) => { + const old = val const substatValue = getSubstatValue(statKey, rarity, type) - return substatValue * rollsDeferred[0] + const newVal = substatValue * rollsDeferred[0] + + const statDiff = Math.round(old / substatValue - rollsDeferred[0]) + charTC.optimization.distributedSubstats = clamp( + charTC.optimization.distributedSubstats + statDiff, + 0, + 45 + ) + return newVal }) }) // disable triggering for isMount diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactSubstatEditor.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactSubstatEditor.tsx index 6624bb3126..3e5a1046ba 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactSubstatEditor.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactSubstatEditor.tsx @@ -1,5 +1,6 @@ import { artMaxLevel, type SubstatKey } from '@genshin-optimizer/consts' import { artDisplayValue, getSubstatValue } from '@genshin-optimizer/gi-util' +import { clamp } from '@genshin-optimizer/util' import { Box, Slider, Stack } from '@mui/material' import { useCallback, useContext, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -33,7 +34,21 @@ export function ArtifactSubstatEditor({ const setValue = useCallback( (v: number) => { setCharTC((charTC) => { + const old = charTC.artifact.substats.stats[statKey] charTC.artifact.substats.stats[statKey] = v + const statDiff = Math.round( + (old - v) / + getSubstatValue( + statKey, + charTC.artifact.substats.rarity, + charTC.artifact.substats.type + ) + ) + charTC.optimization.distributedSubstats = clamp( + charTC.optimization.distributedSubstats + statDiff, + 0, + 45 + ) }) }, [setCharTC, statKey] diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx index dbf0841e28..a6fa5888e8 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx @@ -1,14 +1,14 @@ +import { StatIcon } from '@genshin-optimizer/gi-svgicons' import { BuildAlert, initialBuildStatus } from '@genshin-optimizer/gi-ui' -import { - getMainStatDisplayValue, - getSubstatValue, -} from '@genshin-optimizer/gi-util' +import { getSubstatValue } from '@genshin-optimizer/gi-util' import { useBoolState } from '@genshin-optimizer/react-util' +import { iconInlineProps } from '@genshin-optimizer/svgicons' import { objMap, toPercent } from '@genshin-optimizer/util' import { CopyAll, Refresh } from '@mui/icons-material' import CalculateIcon from '@mui/icons-material/Calculate' import CloseIcon from '@mui/icons-material/Close' import { + Alert, Box, Button, Dialog, @@ -33,6 +33,7 @@ import { } from 'react' import { Trans, useTranslation } from 'react-i18next' import { useLocation } from 'react-router-dom' +import { ArtifactStatWithUnit } from '../../../../Components/Artifact/ArtifactStatKeyDisplay' import CardLight from '../../../../Components/Card/CardLight' import StatDisplayComponent from '../../../../Components/Character/StatDisplayComponent' import CustomNumberInput from '../../../../Components/CustomNumberInput' @@ -43,7 +44,6 @@ import type { dataContextObj } from '../../../../Context/DataContext' import { DataContext } from '../../../../Context/DataContext' import { initCharTC } from '../../../../Database/DataManagers/CharacterTCData' import { DatabaseContext } from '../../../../Database/Database' -import { constant, percent } from '../../../../Formula/utils' import useDBMeta from '../../../../ReactHooks/useDBMeta' import useTeamData from '../../../../ReactHooks/useTeamData' import type { ICachedArtifact } from '../../../../Types/artifact' @@ -63,10 +63,13 @@ import kqmIcon from './kqm.png' import type { TCWorkerResult } from './optimizeTc' import { getArtifactData, + getMinSubAndOtherRolls, + getScalesWith, getWeaponData, optimizeTcGetNodes, } from './optimizeTc' import useCharTC from './useCharTC' +import { SubstatKey } from '@genshin-optimizer/consts' export default function TabTheorycraft() { const { t } = useTranslation('page_character') const { database } = useContext(DatabaseContext) @@ -97,45 +100,42 @@ export default function TabTheorycraft() { }, [setCharTC, characterSheet]) const copyFrom = useCallback( - (eWeapon: ICachedWeapon, build: ICachedArtifact[]) => { - const newData = initCharTC(eWeapon.key) - newData.artifact.substats.type = charTC.artifact.substats.type - - newData.weapon.level = eWeapon.level - newData.weapon.ascension = eWeapon.ascension - newData.weapon.refinement = eWeapon.refinement + (eWeapon: ICachedWeapon, build: ICachedArtifact[]) => + setCharTC((charTC) => { + charTC.weapon.level = eWeapon.level + charTC.weapon.ascension = eWeapon.ascension + charTC.weapon.refinement = eWeapon.refinement - const sets = {} - build.forEach((art) => { - if (!art) return - const { slotKey, setKey, substats, mainStatKey, level, rarity } = art - newData.artifact.slots[slotKey].level = level - newData.artifact.slots[slotKey].statKey = mainStatKey - newData.artifact.slots[slotKey].rarity = rarity - sets[setKey] = (sets[setKey] ?? 0) + 1 - substats.forEach((substat) => { - if (substat.key) - newData.artifact.substats.stats[substat.key] = - (newData.artifact.substats.stats[substat.key] ?? 0) + - substat.accurateValue + const sets = {} + build.forEach((art) => { + if (!art) return + const { slotKey, setKey, substats, mainStatKey, level, rarity } = art + charTC.artifact.slots[slotKey].level = level + charTC.artifact.slots[slotKey].statKey = mainStatKey + charTC.artifact.slots[slotKey].rarity = rarity + sets[setKey] = (sets[setKey] ?? 0) + 1 + substats.forEach((substat) => { + if (substat.key) + charTC.artifact.substats.stats[substat.key] = + (charTC.artifact.substats.stats[substat.key] ?? 0) + + substat.accurateValue + }) }) - }) - newData.artifact.sets = Object.fromEntries( - Object.entries(sets) - .map(([key, value]) => [ - key, - value === 3 - ? 2 - : value === 5 - ? 4 - : value === 1 && !(key as string).startsWith('PrayersFor') - ? 0 - : value, - ]) - .filter(([, value]) => value) - ) - setCharTC(newData) - }, + charTC.artifact.sets = Object.fromEntries( + Object.entries(sets) + .map(([key, value]) => [ + key, + value === 3 + ? 2 + : value === 5 + ? 4 + : value === 1 && !(key as string).startsWith('PrayersFor') + ? 0 + : value, + ]) + .filter(([, value]) => value) + ) + }), [charTC, setCharTC] ) const location = useLocation() @@ -235,9 +235,21 @@ export default function TabTheorycraft() { setStatus(initialBuildStatus()) }, [workerRef]) + const { minSubLines, minOtherRolls } = useMemo( + () => getMinSubAndOtherRolls(charTC), + [charTC] + ) + + const { nodes, scalesWith } = useMemo(() => { + const { nodes } = optimizeTcGetNodes(teamData, characterKey, charTC) + const scalesWith = nodes ? getScalesWith(nodes) : new Set() + return { + nodes, + scalesWith, + } + }, [teamData, characterKey, charTC]) + const optimizeSubstats = (apply: boolean) => { - const nodes = optimizeTcGetNodes(teamData, characterKey, charTC) - console.log({ nodes }) workerRef.current.postMessage({ charTC, ...nodes }) setStatus((s) => ({ ...s, @@ -377,6 +389,14 @@ export default function TabTheorycraft() { + {minOtherRolls > 0 && ( + + The current substat distribution requires at least{' '} + {minSubLines} lines of substats. Need to + assign {minOtherRolls} rolls to other + substats for this solution to be feasible. + + )} optimizeSubstats(true)} - disabled={!optimizationTarget || !distributedSubstats} + disabled={ + !optimizationTarget || + !distributedSubstats || + distributedSubstats > 45 + } color="success" startIcon={} > @@ -432,6 +456,32 @@ export default function TabTheorycraft() { Log Optimized Substats )} + {!!scalesWith.size && ( + + The selected Optimization target and constraints scales with:{' '} + {[...scalesWith] + .map((k) => ( + + + + + )) + .flatMap((value, index, array) => { + if (index === array.length - 2) + return [value, ', and '] + if (index === array.length - 1) return value + return [value, , ] + })} + . The solver will only distribute stats to these substats.{' '} + {minOtherRolls > 0 && ( + + There may be additional leftover substats that should be + distributed to non-scaling stats to ensure the solution is + feasible. + + )} + + )} () + precompute( + nodes, + {}, + (f) => { + const val = f.path[1] + scalesWith.add(val) + return val + }, + 1 + ) + return scalesWith as Set +} + +export function getMinSubAndOtherRolls(charTC: ICharTC) { + const { + artifact: { + slots, + substats: { stats: substats, type: substatsType, rarity }, + }, + } = charTC + const existingRolls = objMap(substats, (v, k) => + Math.ceil(substats[k] / getSubstatValue(k, rarity, substatsType)) + ) + const mainStatsCount = getMainStatsCount(slots) + const minSubLines = getMinSubLines(slots) + return { + minSubLines, + minOtherRolls: getMinOtherRolls( + Object.entries(existingRolls), + mainStatsCount, + minSubLines + ), + } +} + export function optimizeTcUsingNodes( nodes: OptNode[], charTC: ICharTC, From 2399fbe9e25f76a6a85e64b50d1fde942878c7d4 Mon Sep 17 00:00:00 2001 From: frzyc Date: Sun, 21 Jan 2024 11:55:01 -0500 Subject: [PATCH 52/61] fix lint --- .../CharacterDisplay/Tabs/TabTheorycraft/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx index a6fa5888e8..a764e0c42e 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx @@ -1,3 +1,4 @@ +import type { SubstatKey } from '@genshin-optimizer/consts' import { StatIcon } from '@genshin-optimizer/gi-svgicons' import { BuildAlert, initialBuildStatus } from '@genshin-optimizer/gi-ui' import { getSubstatValue } from '@genshin-optimizer/gi-util' @@ -69,7 +70,6 @@ import { optimizeTcGetNodes, } from './optimizeTc' import useCharTC from './useCharTC' -import { SubstatKey } from '@genshin-optimizer/consts' export default function TabTheorycraft() { const { t } = useTranslation('page_character') const { database } = useContext(DatabaseContext) From 781d141c46248af565c5fd4d7256f62b153eee1d Mon Sep 17 00:00:00 2001 From: frzyc Date: Sun, 21 Jan 2024 12:33:50 -0500 Subject: [PATCH 53/61] translation + bugfix --- .../Tabs/TabTheorycraft/index.tsx | 37 ++++++++++++------- .../assets/locales/en/page_character.json | 8 +++- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx index a764e0c42e..74752eb7fc 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx @@ -136,7 +136,7 @@ export default function TabTheorycraft() { .filter(([, value]) => value) ) }), - [charTC, setCharTC] + [setCharTC] ) const location = useLocation() const { build: locBuild } = (location.state as @@ -250,7 +250,7 @@ export default function TabTheorycraft() { }, [teamData, characterKey, charTC]) const optimizeSubstats = (apply: boolean) => { - workerRef.current.postMessage({ charTC, ...nodes }) + workerRef.current.postMessage({ charTC, nodes }) setStatus((s) => ({ ...s, type: 'active', @@ -391,10 +391,16 @@ export default function TabTheorycraft() { {minOtherRolls > 0 && ( - The current substat distribution requires at least{' '} - {minSubLines} lines of substats. Need to - assign {minOtherRolls} rolls to other - substats for this solution to be feasible. + + The current substat distribution requires at least{' '} + {{ minSubLines } as any} lines of substats. + Need to assign {{ minOtherRolls } as any}{' '} + rolls to other substats for this solution to be feasible. + )} @@ -458,27 +464,32 @@ export default function TabTheorycraft() { )} {!!scalesWith.size && ( - The selected Optimization target and constraints scales with:{' '} + + The selected Optimization target and constraints scales + with:{' '} + {[...scalesWith] .map((k) => ( - + )) .flatMap((value, index, array) => { if (index === array.length - 2) - return [value, ', and '] + return [value, ', and '] if (index === array.length - 1) return value - return [value, , ] + return [value, , ] })} - . The solver will only distribute stats to these substats.{' '} + + . The solver will only distribute stats to these substats. + {' '} {minOtherRolls > 0 && ( - + There may be additional leftover substats that should be distributed to non-scaling stats to ensure the solution is feasible. - + )} )} diff --git a/libs/gi-localization/assets/locales/en/page_character.json b/libs/gi-localization/assets/locales/en/page_character.json index 1216117032..9da3806677 100644 --- a/libs/gi-localization/assets/locales/en/page_character.json +++ b/libs/gi-localization/assets/locales/en/page_character.json @@ -83,7 +83,13 @@ }, "maxTotalRolls": "A \"valid\" build can only have a maximum of 45 rolls.", "maxRolls": "This substat can have a maximum of 30 rolls.", - "maxRollsMain": "Because at least one main stat has the same attribute, this substat can have a maximum of {{value}} rolls." + "maxRollsMain": "Because at least one main stat has the same attribute, this substat can have a maximum of {{value}} rolls.", + "feasibilityAlert": "The current substat distribution requires at least {{minSubLines}} lines of substats. Need to assign {{minOtherRolls}} rolls to other substats for this solution to be feasible.", + "optAlert": { + "scalesWith": "The selected Optimization target and constraints scales with: ", + "distribute": ". The solver will only distribute stats to these substats.", + "feasibilty": "There may be additional leftover substats that should be distributed to non-scaling stats to ensure the solution is feasible." + } }, "tabTeambuff": { "team_reso": "Team Resonance", From 8959ae3972c02fbd196f813172d402f7a8ea0df1 Mon Sep 17 00:00:00 2001 From: frzyc Date: Sun, 21 Jan 2024 12:58:39 -0500 Subject: [PATCH 54/61] update constraintCard --- .../TabTheorycraft/BuildConstaintCard.tsx | 63 ++++++++----------- 1 file changed, 25 insertions(+), 38 deletions(-) diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/BuildConstaintCard.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/BuildConstaintCard.tsx index 3dc6ee65ad..c12213699f 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/BuildConstaintCard.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/BuildConstaintCard.tsx @@ -1,5 +1,6 @@ import { StatIcon } from '@genshin-optimizer/gi-svgicons' import { + CardThemed, CustomNumberInput, CustomNumberInputButtonGroupWrapper, DropdownButton, @@ -19,7 +20,6 @@ import { } from '@mui/material' import { useContext } from 'react' import { ArtifactStatWithUnit } from '../../../../Components/Artifact/ArtifactStatKeyDisplay' -import CardLight from '../../../../Components/Card/CardLight' import type { MinTotalStatKey } from '../../../../Database/DataManagers/CharacterTCData' import { minTotalStatKeys } from '../../../../Database/DataManagers/CharacterTCData' import { CharTCContext } from './CharTCContext' @@ -33,7 +33,7 @@ export function BuildConstaintCard({ disabled }: { disabled: boolean }) { } = useContext(CharTCContext) return ( - + @@ -63,7 +63,7 @@ export function BuildConstaintCard({ disabled }: { disabled: boolean }) {
-
+ ) } @@ -77,45 +77,13 @@ function Selector({ disabled: boolean }) { const { setCharTC } = useContext(CharTCContext) - const unitStr = unit(statKey) + const unitStr = unit(statKey) || ' ' return ( - : undefined} - title={ - statKey ? ( - - ) : ( - 'Select a Stat Constraint' - ) - } - > - {minTotalStatKeys.map((k) => ( - - setCharTC((charTC) => { - charTC.optimization.minTotal[statKey] = 0 - }) - } - > - - - - - - - - ))} - statKey && @@ -123,7 +91,26 @@ function Selector({ charTC.optimization.minTotal[statKey] = value }) } - endAdornment={unitStr || } + endAdornment={ + + {unitStr} + + } + startAdornment={ + + + + + } disabled={!statKey || disabled} sx={{ px: 1, From 389bb8e17806b150287baed2d08a21d97b24ad02 Mon Sep 17 00:00:00 2001 From: frzyc Date: Sun, 21 Jan 2024 13:42:22 -0500 Subject: [PATCH 55/61] address comments --- .../Database/DataManagers/CharacterTCData.ts | 11 ++++- .../ArtifactAllSubstatEditor.tsx | 8 ++-- .../ArtifactSubCard/ArtifactSubstatEditor.tsx | 40 +++++++++++++------ .../TabTheorycraft/ArtifactSubCard/index.tsx | 34 +++++++++++----- .../TabTheorycraft/BuildConstaintCard.tsx | 10 +++-- .../Tabs/TabTheorycraft/index.tsx | 18 +++++++-- .../assets/locales/en/page_character.json | 6 ++- libs/gi-ui/src/Character/index.ts | 2 +- libs/ui-common/src/hooks/useIsMount.tsx | 3 ++ 9 files changed, 96 insertions(+), 36 deletions(-) diff --git a/apps/frontend/src/app/Database/DataManagers/CharacterTCData.ts b/apps/frontend/src/app/Database/DataManagers/CharacterTCData.ts index 65cb280324..a5882246db 100644 --- a/apps/frontend/src/app/Database/DataManagers/CharacterTCData.ts +++ b/apps/frontend/src/app/Database/DataManagers/CharacterTCData.ts @@ -11,10 +11,11 @@ import { allArtifactSlotKeys, allSubstatKeys, allWeaponKeys, + artMaxLevel, substatTypeKeys, } from '@genshin-optimizer/consts' import { validateLevelAsc } from '@genshin-optimizer/gi-util' -import { objKeyMap } from '@genshin-optimizer/util' +import { clamp, objKeyMap } from '@genshin-optimizer/util' import type { ICharTC } from '../../Types/character' import { DataManager } from '../DataManager' import type { ArtCharDatabase } from '../Database' @@ -163,6 +164,14 @@ function validateCharTCArtifactSlots( ) ) return initCharTCArtifactSlots() + allArtifactSlotKeys.forEach((slotKey) => { + slots[slotKey].level = clamp( + slots[slotKey].level, + 0, + artMaxLevel[slots[slotKey].rarity] + ) + }) + return slots as ICharTC['artifact']['slots'] } function validateCharTcOptimization( diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactAllSubstatEditor.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactAllSubstatEditor.tsx index 554cc29f64..fbc48afde6 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactAllSubstatEditor.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactAllSubstatEditor.tsx @@ -80,10 +80,10 @@ export function ArtifactAllSubstatEditor({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [setCharTC, maxSubstatDeferred]) - const maxRolls = + const maxRollsPerSub = (artSubstatRollData[charTC.artifact.substats.rarity].numUpgrades + 1) * 5 // 0.0001 to nudge float comparasion - const invalid = (rolls ?? 0 - 0.0001) > maxRolls + const invalid = (rolls ?? 0 - 0.0001) > maxRollsPerSub return ( {t`tabTheorycraft.all.max`} } onChange={(v) => v !== undefined && setMaxSubstat([v])} - color={(maxSubstat ?? 0) > maxRolls ? 'warning' : 'primary'} + color={(maxSubstat ?? 0) > maxRollsPerSub ? 'warning' : 'primary'} sx={{ borderRadius: 1, px: 1, my: 0, height: '100%', width: '6.5em' }} inputProps={{ sx: { textAlign: 'right', pr: 0.5 }, min: 0, step: 1 }} disabled={disabled} diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactSubstatEditor.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactSubstatEditor.tsx index 3e5a1046ba..2d957a6185 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactSubstatEditor.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactSubCard/ArtifactSubstatEditor.tsx @@ -1,16 +1,19 @@ import { artMaxLevel, type SubstatKey } from '@genshin-optimizer/consts' import { artDisplayValue, getSubstatValue } from '@genshin-optimizer/gi-util' +import { + BootstrapTooltip, + CardThemed, + ColorText, +} from '@genshin-optimizer/ui-common' import { clamp } from '@genshin-optimizer/util' -import { Box, Slider, Stack } from '@mui/material' +import InfoIcon from '@mui/icons-material/Info' +import { Box, Slider, Stack, Typography } from '@mui/material' import { useCallback, useContext, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' -import CardDark from '../../../../../Components/Card/CardDark' -import ColorText from '../../../../../Components/ColoredText' import CustomNumberInput from '../../../../../Components/CustomNumberInput' import KeyMap from '../../../../../KeyMap' import StatIcon from '../../../../../KeyMap/StatIcon' import { CharTCContext } from '../CharTCContext' - export function ArtifactSubstatEditor({ statKey, disabled = false, @@ -103,7 +106,7 @@ export function ArtifactSubstatEditor({ inputProps={{ sx: { textAlign: 'right' }, min: 0 }} disabled={disabled} /> - {KeyMap.getStr(statKey)} {KeyMap.unit(statKey)} - + - {/* {t(numMains ? `tabTheorycraft.maxRollsMain` : `tabTheorycraft.maxRolls`, { value: maxRolls })}} placement="top"> */} - + RV: {rv.toFixed()}% + + {t( + numMains + ? `tabTheorycraft.maxRollsMain` + : `tabTheorycraft.maxRolls`, + { value: maxRolls } + )} + + } + placement="top" + > + + - - {/* */} + - setRValue(v as number)} disabled={disabled} /> - + + {t`tabTheorycraft.maxTotalRolls`}} + title={ + + {t('tabTheorycraft.maxTotalRolls', { value: maxTotalRolls })} + + } placement="top" > - maxRolls ? 'error' : undefined}> RV: {rv.toFixed()}% - + @@ -107,6 +119,6 @@ export function ArtifactSubCard({ disabled = false }: { disabled?: boolean }) { ))}
- + ) } diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/BuildConstaintCard.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/BuildConstaintCard.tsx index c12213699f..a1d3c8283c 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/BuildConstaintCard.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/BuildConstaintCard.tsx @@ -23,8 +23,10 @@ import { ArtifactStatWithUnit } from '../../../../Components/Artifact/ArtifactSt import type { MinTotalStatKey } from '../../../../Database/DataManagers/CharacterTCData' import { minTotalStatKeys } from '../../../../Database/DataManagers/CharacterTCData' import { CharTCContext } from './CharTCContext' +import { useTranslation } from 'react-i18next' export function BuildConstaintCard({ disabled }: { disabled: boolean }) { + const { t } = useTranslation('page_character') const { charTC: { optimization: { minTotal }, @@ -34,14 +36,17 @@ export function BuildConstaintCard({ disabled }: { disabled: boolean }) { return ( - + {Object.entries(minTotal).map(([k, v]) => ( ))} - + {minTotalStatKeys.map((k) => ( statKey && diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx index 74752eb7fc..129f0fc588 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx @@ -1,4 +1,4 @@ -import type { SubstatKey } from '@genshin-optimizer/consts' +import { artSubstatRollData, type SubstatKey } from '@genshin-optimizer/consts' import { StatIcon } from '@genshin-optimizer/gi-svgicons' import { BuildAlert, initialBuildStatus } from '@genshin-optimizer/gi-ui' import { getSubstatValue } from '@genshin-optimizer/gi-util' @@ -239,6 +239,15 @@ export default function TabTheorycraft() { () => getMinSubAndOtherRolls(charTC), [charTC] ) + const maxTotalRolls = useMemo( + () => + Object.values(charTC.artifact.slots).reduce( + (accu, { level, rarity }) => + accu + artSubstatRollData[rarity].high + Math.floor(level / 4), + 0 + ), + [charTC] + ) const { nodes, scalesWith } = useMemo(() => { const { nodes } = optimizeTcGetNodes(teamData, characterKey, charTC) @@ -384,7 +393,10 @@ export default function TabTheorycraft() { - + @@ -436,7 +448,7 @@ export default function TabTheorycraft() { disabled={ !optimizationTarget || !distributedSubstats || - distributedSubstats > 45 + distributedSubstats > maxTotalRolls } color="success" startIcon={} diff --git a/libs/gi-localization/assets/locales/en/page_character.json b/libs/gi-localization/assets/locales/en/page_character.json index 9da3806677..05eacfdbd6 100644 --- a/libs/gi-localization/assets/locales/en/page_character.json +++ b/libs/gi-localization/assets/locales/en/page_character.json @@ -81,7 +81,7 @@ "max": "Max substat roll", "mid": "Median substat roll" }, - "maxTotalRolls": "A \"valid\" build can only have a maximum of 45 rolls.", + "maxTotalRolls": "The current build can only have a maximum of {{value}} rolls.", "maxRolls": "This substat can have a maximum of 30 rolls.", "maxRollsMain": "Because at least one main stat has the same attribute, this substat can have a maximum of {{value}} rolls.", "feasibilityAlert": "The current substat distribution requires at least {{minSubLines}} lines of substats. Need to assign {{minOtherRolls}} rolls to other substats for this solution to be feasible.", @@ -89,6 +89,10 @@ "scalesWith": "The selected Optimization target and constraints scales with: ", "distribute": ". The solver will only distribute stats to these substats.", "feasibilty": "There may be additional leftover substats that should be distributed to non-scaling stats to ensure the solution is feasible." + }, + "constraint": { + "title": "Stat Constraints", + "add": "Add a Stat Constraint" } }, "tabTeambuff": { diff --git a/libs/gi-ui/src/Character/index.ts b/libs/gi-ui/src/Character/index.ts index 827da00b2e..65f8e475db 100644 --- a/libs/gi-ui/src/Character/index.ts +++ b/libs/gi-ui/src/Character/index.ts @@ -1,2 +1,2 @@ -export * from './Trans' export * from './BuildAlert' +export * from './Trans' diff --git a/libs/ui-common/src/hooks/useIsMount.tsx b/libs/ui-common/src/hooks/useIsMount.tsx index e6697a752b..2b3c2f6e3e 100644 --- a/libs/ui-common/src/hooks/useIsMount.tsx +++ b/libs/ui-common/src/hooks/useIsMount.tsx @@ -1,5 +1,8 @@ import { useEffect, useRef } from 'react' +/** + * @returns true if it is the currently the initial rendering of the component + */ export function useIsMount() { const isMountRef = useRef(true) useEffect(() => { From 211ffbb73aca9728fad5951f69b2e5cb245101cc Mon Sep 17 00:00:00 2001 From: eeeqeee <103794572+eeeqeee@users.noreply.github.com> Date: Sun, 21 Jan 2024 13:54:39 -0500 Subject: [PATCH 56/61] prevent canceling from breaking future opt runs (#1444) --- .../CharacterDisplay/Tabs/TabTheorycraft/index.tsx | 1 + .../CharacterDisplay/Tabs/TabTheorycraft/optimizeTcWorker.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx index 129f0fc588..e30d135ec9 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx @@ -232,6 +232,7 @@ export default function TabTheorycraft() { const terminateWorker = useCallback(() => { workerRef.current.terminate() + workerRef.current = null setStatus(initialBuildStatus()) }, [workerRef]) diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTcWorker.ts b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTcWorker.ts index 2039cb5905..c4de91c105 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTcWorker.ts +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/optimizeTcWorker.ts @@ -6,7 +6,7 @@ type WorkerData = { nodes: OptNode[] charTC: ICharTC } -export {} + onmessage = async (e: MessageEvent) => { const { nodes, charTC } = e.data optimizeTcUsingNodes(nodes, charTC, (r) => postMessage(r)) From e25dc0f46b32466415553b206dae38dcbb9dbb34 Mon Sep 17 00:00:00 2001 From: frzyc Date: Sun, 21 Jan 2024 17:02:27 -0500 Subject: [PATCH 57/61] fix quotes --- .../CharacterDisplay/Tabs/TabTheorycraft/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx index e30d135ec9..6ca5f9573b 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx @@ -490,7 +490,7 @@ export default function TabTheorycraft() { )) .flatMap((value, index, array) => { if (index === array.length - 2) - return [value, ', and '] + return [value, , and ] if (index === array.length - 1) return value return [value, , ] })} From 6eb998c76a0a29879638d78834ce2922518c87c4 Mon Sep 17 00:00:00 2001 From: frzyc Date: Mon, 22 Jan 2024 23:25:13 -0500 Subject: [PATCH 58/61] addresss comments --- .../src/app/Components/CustomNumberInput.tsx | 4 +- .../Tabs/TabTheorycraft/GcsimButton.tsx | 2 +- .../Tabs/TabTheorycraft/KQMSButton.tsx | 69 +++++++++++++++++++ .../Tabs/TabTheorycraft/index.tsx | 65 ++--------------- .../src/components/CustomNumberInput.tsx | 52 +++++++------- 5 files changed, 100 insertions(+), 92 deletions(-) create mode 100644 apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/KQMSButton.tsx diff --git a/apps/frontend/src/app/Components/CustomNumberInput.tsx b/apps/frontend/src/app/Components/CustomNumberInput.tsx index c55a033888..245de28da9 100644 --- a/apps/frontend/src/app/Components/CustomNumberInput.tsx +++ b/apps/frontend/src/app/Components/CustomNumberInput.tsx @@ -69,9 +69,7 @@ export default function CustomNumberInput({ ...props }: CustomNumberInputProps) { const { inputProps = {}, ...restProps } = props - const { - inputProps: { min, max }, - } = props + const { min, max } = inputProps const [display, setDisplay] = useState(value.toString()) const onInputChange = useCallback( diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/GcsimButton.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/GcsimButton.tsx index 6951060c8b..9b9a9ff3b8 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/GcsimButton.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/GcsimButton.tsx @@ -131,7 +131,7 @@ ${charKeyLow} add stats ${substatsText};` startIcon={} disabled={disabled} > - gcsim + {t('tabTheorycraft.gcsimDialog.title')} {t('tabTheorycraft.gcsimDialog.title')} diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/KQMSButton.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/KQMSButton.tsx new file mode 100644 index 0000000000..2d6851404f --- /dev/null +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/KQMSButton.tsx @@ -0,0 +1,69 @@ +import { useBoolState } from '@genshin-optimizer/react-util' +import { + Button, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, + Link +} from '@mui/material' +import { Trans, useTranslation } from 'react-i18next' +import ImgIcon from '../../../../Components/Image/ImgIcon' +import kqmIcon from './kqm.png' + +export default function KQMSButton({ + action, + disabled, +}: { + action: () => void + disabled: boolean +}) { + const { t } = useTranslation(['page_character', 'ui']) + const [open, onOpen, onClose] = useBoolState() + return ( + <> + + + {t('tabTheorycraft.kqmsDialog.title')} + + + + This will replace your current substat setup with + one that adheres to the{' '} + + KQM Standards + + . + + + + + + + + + + ) +} diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx index 6ca5f9573b..494305b40d 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/index.tsx @@ -18,7 +18,6 @@ import { DialogContentText, DialogTitle, Grid, - Link, Skeleton, Stack, ToggleButton, @@ -38,13 +37,12 @@ import { ArtifactStatWithUnit } from '../../../../Components/Artifact/ArtifactSt import CardLight from '../../../../Components/Card/CardLight' import StatDisplayComponent from '../../../../Components/Character/StatDisplayComponent' import CustomNumberInput from '../../../../Components/CustomNumberInput' -import ImgIcon from '../../../../Components/Image/ImgIcon' import SolidToggleButtonGroup from '../../../../Components/SolidToggleButtonGroup' import { CharacterContext } from '../../../../Context/CharacterContext' import type { dataContextObj } from '../../../../Context/DataContext' import { DataContext } from '../../../../Context/DataContext' -import { initCharTC } from '../../../../Database/DataManagers/CharacterTCData' import { DatabaseContext } from '../../../../Database/Database' +import { initCharTC } from '../../../../Database/DataManagers/CharacterTCData' import useDBMeta from '../../../../ReactHooks/useDBMeta' import useTeamData from '../../../../ReactHooks/useTeamData' import type { ICachedArtifact } from '../../../../Types/artifact' @@ -59,8 +57,7 @@ import { BuildConstaintCard } from './BuildConstaintCard' import type { SetCharTCAction } from './CharTCContext' import { CharTCContext } from './CharTCContext' import GcsimButton from './GcsimButton' -import { WeaponEditorCard } from './WeaponEditorCard' -import kqmIcon from './kqm.png' +import KQMSButton from './KQMSButton' import type { TCWorkerResult } from './optimizeTc' import { getArtifactData, @@ -70,6 +67,7 @@ import { optimizeTcGetNodes, } from './optimizeTc' import useCharTC from './useCharTC' +import { WeaponEditorCard } from './WeaponEditorCard' export default function TabTheorycraft() { const { t } = useTranslation('page_character') const { database } = useContext(DatabaseContext) @@ -102,6 +100,7 @@ export default function TabTheorycraft() { const copyFrom = useCallback( (eWeapon: ICachedWeapon, build: ICachedArtifact[]) => setCharTC((charTC) => { + charTC.weapon.key = eWeapon.key charTC.weapon.level = eWeapon.level charTC.weapon.ascension = eWeapon.ascension charTC.weapon.refinement = eWeapon.refinement @@ -620,59 +619,3 @@ function ResetButton({ ) } - -function KQMSButton({ - action, - disabled, -}: { - action: () => void - disabled: boolean -}) { - const { t } = useTranslation(['page_character', 'ui']) - const [open, onOpen, onClose] = useBoolState() - return ( - <> - - - {t('tabTheorycraft.kqmsDialog.title')} - - - - This will replace your current substat setup with - one that adheres to the{' '} - - KQM Standards - - . - - - - - - - - - - ) -} diff --git a/libs/ui-common/src/components/CustomNumberInput.tsx b/libs/ui-common/src/components/CustomNumberInput.tsx index b5d8ff468d..0c7a4144c0 100644 --- a/libs/ui-common/src/components/CustomNumberInput.tsx +++ b/libs/ui-common/src/components/CustomNumberInput.tsx @@ -60,48 +60,46 @@ export function CustomNumberInput({ ...props }: CustomNumberInputProps) { const { inputProps = {}, ...restProps } = props + const { min, max } = inputProps + const [display, setDisplay] = useState(value.toString()) + + const onInputChange = useCallback( + (e: ChangeEvent) => + setDisplay(e.target.value), + [] + ) - const [number, setNumber] = useState(value) - const [focused, setFocus] = useState(false) const parseFunc = useCallback( (val: string) => (float ? parseFloat(val) : parseInt(val)), [float] ) - const onBlur = useCallback(() => { - onChange(number) - setFocus(false) - }, [onChange, number]) - const onFocus = useCallback(() => { - setFocus(true) - }, []) - useEffect(() => setNumber(value), [value]) // update value on value change + const onValidate = useCallback(() => { + const change = (v: number) => { + setDisplay(v.toString()) + onChange(v) + } + const newNum = parseFunc(display) || 0 + if (min !== undefined && newNum < min) return change(min) + if (max !== undefined && newNum > max) return change(max) + return change(newNum) + }, [min, max, parseFunc, onChange, display]) + + useEffect(() => setDisplay(value.toString()), [value, setDisplay]) // update value on value change - const min = inputProps['min'] - const max = inputProps['max'] - const onInputChange = useCallback( - (e: ChangeEvent) => { - const newNum = parseFunc(e.target?.value) || 0 - if (min !== undefined && newNum < min) return - if (max !== undefined && newNum > max) return - setNumber(newNum) - }, - [parseFunc, min, max] - // [setNumber, parseFunc, inputProps['min'], inputProps['max']] - ) const onKeyDown = useCallback( - (e: KeyboardEvent) => e.key === 'Enter' && onBlur(), - [onBlur] + (e: KeyboardEvent) => + e.key === 'Enter' && onValidate(), + [onValidate] ) return ( Date: Mon, 22 Jan 2024 23:37:43 -0500 Subject: [PATCH 59/61] addresss comments --- .../ArtifactMainStatAndSetEditor/ArtifactSetEditor.tsx | 8 +++++--- .../Tabs/TabTheorycraft/BuildConstaintCard.tsx | 7 +++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactMainStatAndSetEditor/ArtifactSetEditor.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactMainStatAndSetEditor/ArtifactSetEditor.tsx index 6203109334..3a9336d7c4 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactMainStatAndSetEditor/ArtifactSetEditor.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/ArtifactMainStatAndSetEditor/ArtifactSetEditor.tsx @@ -1,5 +1,7 @@ import type { ArtifactSetKey } from '@genshin-optimizer/consts' -import { DeleteForever, Info } from '@mui/icons-material' +import { iconInlineProps } from '@genshin-optimizer/svgicons' +import ClearIcon from '@mui/icons-material/Clear' +import InfoIcon from '@mui/icons-material/Info' import { Box, Button, ButtonGroup, MenuItem, Stack } from '@mui/material' import { useCallback, useContext, useMemo } from 'react' import ArtifactSetTooltip from '../../../../../Components/Artifact/ArtifactSetTooltip' @@ -61,7 +63,7 @@ export function ArtifactSetEditor({ {artifactSheet.setName} - + @@ -88,7 +90,7 @@ export function ArtifactSetEditor({ onClick={deleteValue} disabled={disabled} > - + diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/BuildConstaintCard.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/BuildConstaintCard.tsx index a1d3c8283c..c099d23423 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/BuildConstaintCard.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/BuildConstaintCard.tsx @@ -19,11 +19,11 @@ import { Stack, } from '@mui/material' import { useContext } from 'react' +import { useTranslation } from 'react-i18next' import { ArtifactStatWithUnit } from '../../../../Components/Artifact/ArtifactStatKeyDisplay' import type { MinTotalStatKey } from '../../../../Database/DataManagers/CharacterTCData' import { minTotalStatKeys } from '../../../../Database/DataManagers/CharacterTCData' import { CharTCContext } from './CharTCContext' -import { useTranslation } from 'react-i18next' export function BuildConstaintCard({ disabled }: { disabled: boolean }) { const { t } = useTranslation('page_character') @@ -36,7 +36,10 @@ export function BuildConstaintCard({ disabled }: { disabled: boolean }) { return ( - + From 145b9a2a7e2edc883e373eacee6b4aa8158674e5 Mon Sep 17 00:00:00 2001 From: frzyc Date: Mon, 22 Jan 2024 23:38:40 -0500 Subject: [PATCH 60/61] format --- .../CharacterDisplay/Tabs/TabTheorycraft/KQMSButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/KQMSButton.tsx b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/KQMSButton.tsx index 2d6851404f..e2e8154893 100644 --- a/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/KQMSButton.tsx +++ b/apps/frontend/src/app/PageCharacter/CharacterDisplay/Tabs/TabTheorycraft/KQMSButton.tsx @@ -6,7 +6,7 @@ import { DialogContent, DialogContentText, DialogTitle, - Link + Link, } from '@mui/material' import { Trans, useTranslation } from 'react-i18next' import ImgIcon from '../../../../Components/Image/ImgIcon' From 0f8d40a1bab5b12a90abc17857cfa1d13864dc7b Mon Sep 17 00:00:00 2001 From: frzyc Date: Tue, 23 Jan 2024 19:55:56 -0500 Subject: [PATCH 61/61] fix merge issue --- libs/gi-util/src/artifact/artifact.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/gi-util/src/artifact/artifact.ts b/libs/gi-util/src/artifact/artifact.ts index 0396ba77dc..9369e50986 100644 --- a/libs/gi-util/src/artifact/artifact.ts +++ b/libs/gi-util/src/artifact/artifact.ts @@ -82,7 +82,8 @@ const substatCache = new Map() export function getSubstatValue( substatKey: SubstatKey, rarity: ArtifactRarity = 5, - type: 'max' | 'min' | 'mid' = 'max' + type: 'max' | 'min' | 'mid' = 'max', + percent = true ): number { const cacheKey = `${substatKey},${rarity},${type}` let value = substatCache.get(cacheKey)