Skip to content

Commit

Permalink
feat: computed stats refactor - dynamic properties & traceable stat s…
Browse files Browse the repository at this point in the history
…ources tooling (#676)

* refactor: dataparser types

* feat: traceable stat sources

* feat: experimenting

* feat: array experiments

* feat: defining keys

* feat: migrate set conditionals

* feat: migrate calculateStats

* feat: migrate conditionals type

* feat: this is impossible

* feat: testing dynamic properties

* fix: getter key naming

* feat: change conditional stats array initialization syntax

* chore: prevent ci on draft pr 2

* feat: reimplement set conditionals, remove effect

* fix: core naming

* feat: update initialization

* feat: migrate most of dynamic set conditionals

* feat: pass computedstatsarray through actions

* feat: restore worker output

* feat: finish passthrough, migrate all dynamic sets

* feat: migrate dynamicconditionals file

* feat: migrate damage calculations

* feat: remove getter performance issues

* fix: weakness broken variables

* feat: migrate bronya, s1, teammate sets

* feat: mass migrate lightcone t

* feat: mass migrate content return

* feat: mass migrate character precompute

* feat: mass migrate character precompute mutual

* feat: mass migrate character teammate

* feat: mass migrate conditional fn arguments

* feat: mass migrate content definitions

* feat: migrate robin

* feat: migrate huohuo

* feat: migrate jingliu

* feat: migrate kafka

* feat: migrate ruan mei

* feat: migrate black swan

* feat: migrate lingsha

* feat: migrate acheron

* feat: migrate argenti

* feat: migrate arlan

* feat: migrate asta

* feat: migrate aventurine

* feat: migrate bailu

* feat: migrate blade

* feat: migrate boothill

* feat: migrate clara

* feat: migrate dan heng

* feat: migrate ratio

* feat: migrate feixiao

* feat: migrate firefly

* feat: migrate fugue

* feat: migrate fu xuan

* feat: migrate gallagher

* feat: migrate gepard

* feat: migrate guinaifen

* feat: migrate hanya

* feat: migrate herta

* feat: migrate himeko

* feat: migrate hook

* feat: migrate imbibitor lunae

* feat: migrate jade

* feat: migrate jiaoqiu

* feat: migrate jingliu

* feat: migrate jing yuan

* feat: migrate luka

* feat: migrate luocha

* feat: migrate lynx

* feat: migrate march 7th

* feat: migrate march 8th

* feat: migrate misha

* feat: migrate moze

* feat: migrate natasha

* feat: migrate pela

* feat: migrate qingque

* feat: migrate rappa

* feat: migrate seele

* feat: migrate serval

* feat: migrate silverwolf

* feat: migrate sparkle

* feat: migrate sunday

* feat: migrate sushang

* feat: migrate tingyun

* feat: migrate topaz

* feat: upgrade tbd

* feat: tbh

* feat: migrate tbp

* feat: migrate welt

* feat: migrate xueyi

* feat: migate yanqing

* feat: migrate yukong

* feat: migrate amber

* feat: migrate 3* lcs

* feat: migrate 4* lcs part 1

* feat: migrate 4* lcs part 2

* feat: migrate 4* light cones

* feat: migrate 5* lcs

* feat: fix remaining issues

* feat: migrate yunli

* refactor: optimizer worker filter cleanup

* feat: refactor optimizer buffers as float32

* feat: optimize stat addition

* feat: optimize calc damage, bump workers

* refactor: cleanup

* refactor: reorder character formitem

* refactor: reorder light cone formitem

* refactor: cleanup
  • Loading branch information
fribbels authored Nov 7, 2024
1 parent 3247e8d commit 8b6f7e5
Show file tree
Hide file tree
Showing 200 changed files with 6,506 additions and 5,836 deletions.
1 change: 0 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ on:

jobs:
build:
if: github.event.pull_request.draft == false
runs-on: ubuntu-latest
container: pandoc/latex # "ubuntu" is a more generic container, using "pandoc/latex" because of dependencies, used in the specific "build.sh"
steps:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:
branches: [ main, beta ]
pull_request:
branches: [ main, beta ]
types: [ review_requested, ready_for_review ]
jobs:
test:
timeout-minutes: 60
Expand Down
Binary file modified docs/dev/ide-settings/webstorm-settings.zip
Binary file not shown.
2 changes: 1 addition & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ const config = tseslint.config(
// As we're migrating to TS these make the process easier
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-unsafe-return': 'off',
'@typescript-eslint/no-unused-vars': 'warn',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
},
},
Expand Down
2 changes: 1 addition & 1 deletion public/locales/en/conditionals.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,7 @@ Lightcones:
content: After the wearer uses Basic ATK or Skill, deals Additional DMG equal to {{Multiplier}}% of the wearer's ATK to a random enemy that has been attacked.
WoofWalkTime:
Content:
atkBoost:
enemyBurnedBleeding:
text: Enemy burn / bleed DMG boost
content: Increases the wearer's DMG to enemies afflicted with Burn or Bleed by {{DmgBuff}}%. This also applies to DoT.
Adversarial:
Expand Down
23 changes: 14 additions & 9 deletions src/components/CharacterPreview.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,11 @@ import { AppPages, DB } from 'lib/db'
import { Message } from 'lib/message'
import { calculateBuild } from 'lib/optimizer/calculateBuild'
import { OptimizerTabController } from 'lib/optimizerTabController'
import { RelicFilters } from 'lib/relicFilters'
import { RelicModalController } from 'lib/relicModalController'
import { RelicScorer } from 'lib/relicScorerPotential'
import { SaveState } from 'lib/saveState'
import { StatCalculator } from 'lib/statCalculator'
import { Utils } from 'lib/utils'
import PropTypes from 'prop-types'
import React, { useEffect, useRef, useState } from 'react'
Expand Down Expand Up @@ -184,13 +186,14 @@ export function CharacterPreview(props) {
gap={defaultGap}
id={props.id}
>
<div style={{
width: parentW,
overflow: 'hidden',
outline: `2px solid ${token.colorBgContainer}`,
height: '100%',
borderRadius: '8px',
}}
<div
style={{
width: parentW,
overflow: 'hidden',
outline: `2px solid ${token.colorBgContainer}`,
height: '100%',
borderRadius: '8px',
}}
>
{/* This is a placeholder for the character portrait when no character is selected */}
</div>
Expand Down Expand Up @@ -251,7 +254,7 @@ export function CharacterPreview(props) {

const statCalculationRelics = Utils.clone(displayRelics)
RelicFilters.condenseRelicSubstatsForOptimizerSingle(Object.values(statCalculationRelics))
const finalStats = calculateBuild(OptimizerTabController.fixForm(OptimizerTabController.getDisplayFormValues(character.form)), statCalculationRelics)
const { c: finalStats } = calculateBuild(OptimizerTabController.fixForm(OptimizerTabController.getDisplayFormValues(character.form)), statCalculationRelics)
finalStats.CV = StatCalculator.calculateCv(Object.values(statCalculationRelics))
finalStats[elementalDmgValue] = finalStats.ELEMENTAL_DMG

Expand Down Expand Up @@ -313,7 +316,9 @@ export function CharacterPreview(props) {
// Some APIs return empty light cone as '0'
const charCenter = DB.getMetadata().characters[character.id].imageCenter

const lcCenter = (character.form.lightCone && character.form.lightCone != '0') ? DB.getMetadata().lightCones[character.form.lightCone].imageCenter : 0
const lcCenter = (character.form.lightCone && character.form.lightCone != '0')
? DB.getMetadata().lightCones[character.form.lightCone].imageCenter
: 0

const tempLcParentW = simScoringResult ? parentW : lcParentW

Expand Down
58 changes: 42 additions & 16 deletions src/components/RelicScorerTab.jsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
import React, { useEffect, useMemo, useRef, useState } from 'react'
import Icon, {
CameraOutlined,
DownloadOutlined,
EditOutlined,
ExperimentOutlined,
ImportOutlined,
LineChartOutlined,
} from '@ant-design/icons'
import { Button, Dropdown, Flex, Form, Input, Segmented, theme, Typography } from 'antd'
import CharacterModal from 'components/CharacterModal'
import { CharacterPreview } from 'components/CharacterPreview'
import { SaveState } from 'lib/saveState'
import { CharacterConverter } from 'lib/characterConverter'
import { applySpdPreset } from 'components/optimizerTab/optimizerForm/RecommendedPresetsButton'
import { Assets } from 'lib/assets'
import PropTypes from 'prop-types'
import { CharacterConverter } from 'lib/characterConverter'
import { Constants, CURRENT_DATA_VERSION, officialOnly } from 'lib/constants'
import { SavedSessionKeys } from 'lib/constantsSession'
import DB, { AppPages, PageToRoute } from 'lib/db'
import { Utils } from 'lib/utils'
import Icon, { CameraOutlined, DownloadOutlined, EditOutlined, ExperimentOutlined, ImportOutlined, LineChartOutlined } from '@ant-design/icons'
import { Message } from 'lib/message'
import CharacterModal from 'components/CharacterModal'
import { SavedSessionKeys } from 'lib/constantsSession'
import { applySpdPreset } from 'components/optimizerTab/optimizerForm/RecommendedPresetsButton'
import { calculateBuild } from 'lib/optimizer/calculateBuild'
import { OptimizerTabController } from 'lib/optimizerTabController'
import { Constants, CURRENT_DATA_VERSION, officialOnly } from 'lib/constants'
import { SaveState } from 'lib/saveState'
import { Utils } from 'lib/utils'
import PropTypes from 'prop-types'
import React, { useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'

const { useToken } = theme
Expand Down Expand Up @@ -201,7 +208,9 @@ export default function RelicScorerTab() {
*/}
<Flex gap={10} vertical align='center'>
<Text>
{officialOnly ? t('Header.WithoutVersion') : t('Header.WithVersion', { beta_version: CURRENT_DATA_VERSION })}
{officialOnly
? t('Header.WithoutVersion')
: t('Header.WithVersion', { beta_version: CURRENT_DATA_VERSION })}
{
/*
"WithVersion": "Enter your account UID to score your profile character at level 80 & maxed traces. Log out to refresh instantly. (Current version {{beta_version}} )",
Expand Down Expand Up @@ -264,11 +273,17 @@ function CharacterPreviewSelection(props) {

const items = [
{
label: <Flex gap={10}><ImportOutlined/>{t('ImportLabels.AllCharacters')/* Import all characters & all relics into optimizer */}</Flex>,
label: (
<Flex gap={10}><ImportOutlined/>{t('ImportLabels.AllCharacters')/* Import all characters & all relics into optimizer */}
</Flex>
),
key: 'import characters',
},
{
label: <Flex gap={10}><ImportOutlined/>{t('ImportLabels.SingleCharacter')/* Import selected character & all relics into optimizer */}</Flex>,
label: (
<Flex gap={10}><ImportOutlined/>{t('ImportLabels.SingleCharacter')/* Import selected character & all relics into optimizer */}
</Flex>
),
key: 'import single character',
},
]
Expand Down Expand Up @@ -458,7 +473,7 @@ function CharacterPreviewSelection(props) {
RelicFilters.condenseRelicSubstatsForOptimizer(relicsByPart)

// Calculate the build's speed value to use as a preset
const c = calculateBuild(cleanedForm, equippedRelics)
const { c } = calculateBuild(cleanedForm, equippedRelics)
applySpdPreset(Utils.precisionRound(c.SPD, 3), characterId)

// Timeout to allow the form to populate before optimizing
Expand All @@ -474,10 +489,21 @@ function CharacterPreviewSelection(props) {
<Flex vertical style={{ display: (props.availableCharacters.length > 0) ? 'flex' : 'none', width: '100%' }}>
<Sidebar presetClicked={presetClicked} optimizeClicked={optimizeClicked} activeKey={activeKey}/>
<Flex style={{ display: (props.availableCharacters.length > 0) ? 'flex' : 'none' }} justify='space-between'>
<Button onClick={clipboardClicked} style={{ width: 225 }} icon={<CameraOutlined/>} loading={screenshotLoading} type='primary'>
<Button
onClick={clipboardClicked}
style={{ width: 225 }}
icon={<CameraOutlined/>}
loading={screenshotLoading}
type='primary'
>
{t('CopyScreenshot')/* Copy screenshot */}
</Button>
<Button style={{ width: 40 }} icon={<DownloadOutlined/>} onClick={downloadClicked} loading={downloadLoading}/>
<Button
style={{ width: 40 }}
icon={<DownloadOutlined/>}
onClick={downloadClicked}
loading={downloadLoading}
/>
<Dropdown.Button
onClick={importClicked}
style={{ width: 250 }}
Expand Down
9 changes: 4 additions & 5 deletions src/components/characterPreview/CharacterScoringSummary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,12 @@ export const CharacterScoringSummary = (props: { simScoringResult: SimulationSco
)
}

function ScoringText(props: { label: string; text?: string; textWidth?: number }) {
function ScoringText(props: { label: string; text?: string }) {
const value = props.text ?? ''
return (
<Flex align='center' gap={1} justify='space-between'>
<pre style={{ margin: 0 }}>{props.label}</pre>
<pre style={{ margin: 0, width: props.textWidth }}>{value}</pre>
<pre style={{ margin: 0, textAlign: 'right' }}>{value}</pre>
</Flex>
)
}
Expand Down Expand Up @@ -347,7 +347,6 @@ export const CharacterScoringSummary = (props: { simScoringResult: SimulationSco
// @ts-ignore type of key is not specific enough for ts to know that t() will resolve properly
t(`CharacterPreview.BuildAnalysis.CombatResults.Abilities.${result.characterMetadata.scoringMetadata.sortOption.key}`)
}
textWidth={72}
/>
<ScoringNumber label={t('CharacterPreview.BuildAnalysis.CombatResults.Character')} number={result.originalSimScore} precision={1}/>
<ScoringNumber label={t('CharacterPreview.BuildAnalysis.CombatResults.Baseline')} number={result.baselineSimScore} precision={1}/>
Expand Down Expand Up @@ -432,8 +431,8 @@ function addOnHitStats(simulationScore: SimulationScore) {

x.ELEMENTAL_DMG += x[`${ability}_BOOST`]
if (ability != SortOption.DOT.key) {
x[Stats.CR] += x[`${ability}_CR_BOOST`]
x[Stats.CD] += x[`${ability}_CD_BOOST`]
x.CR += x[`${ability}_CR_BOOST`]
x.CD += x[`${ability}_CD_BOOST`]
}
}

Expand Down
11 changes: 11 additions & 0 deletions src/lib/TsUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,17 @@ export const TsUtils = {
console.warn('An unknown error occurred', err)
}
},

splitFloat64ToFloat32Parts(value: number) {
const high = Math.floor(value / 2 ** 32)
const low = value % 2 ** 32

return { high, low }
},

reconstructFloat64FromParts(high: number, low: number) {
return high * 2 ** 32 + low
},
}

const getEmptyT = <
Expand Down
94 changes: 51 additions & 43 deletions src/lib/bufferPacker.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { BasicStatsObject } from 'lib/conditionals/conditionalConstants'
import { Stats } from 'lib/constants'
import { FixedSizePriorityQueue } from 'lib/fixedSizePriorityQueue'
import { ComputedStatsArray, Key } from 'lib/optimizer/computedStatsArray'

const SIZE = 38
const SIZE = 40

export type OptimizerDisplayData = {
'id': number
Expand Down Expand Up @@ -45,6 +45,8 @@ export type OptimizerDisplayData = {
'xELEMENTAL_DMG': number
'relicSetIndex': number
'ornamentSetIndex': number
'low': number
'high': number

'statSim'?: {
key: string
Expand Down Expand Up @@ -93,6 +95,8 @@ export const BufferPacker = {
'xELEMENTAL_DMG': arr[offset + 35],
'relicSetIndex': arr[offset + 36],
'ornamentSetIndex': arr[offset + 37],
'low': arr[offset + 38],
'high': arr[offset + 39],
}
},

Expand All @@ -108,54 +112,58 @@ export const BufferPacker = {
}
},

packCharacter: (arr: number[], offset: number, character: BasicStatsObject) => {
packCharacter: (arr: number[], offset: number, x: ComputedStatsArray) => {
offset = offset * SIZE
const c = x.c
const a = x.a

arr[offset] = character.id // 0
arr[offset + 1] = character[Stats.HP]
arr[offset + 2] = character[Stats.ATK]
arr[offset + 3] = character[Stats.DEF]
arr[offset + 4] = character[Stats.SPD]
arr[offset + 5] = character[Stats.CD]
arr[offset + 6] = character[Stats.CR]
arr[offset + 7] = character[Stats.EHR]
arr[offset + 8] = character[Stats.RES]
arr[offset + 9] = character[Stats.BE]
arr[offset + 10] = character[Stats.ERR] // 10
arr[offset + 11] = character[Stats.OHB]
arr[offset + 12] = character.ELEMENTAL_DMG
arr[offset + 13] = character.WEIGHT
arr[offset + 14] = character.x.EHP
arr[offset + 15] = character.x.HEAL_VALUE
arr[offset + 16] = character.x.SHIELD_VALUE
arr[offset + 17] = character.x.BASIC_DMG
arr[offset + 18] = character.x.SKILL_DMG
arr[offset + 19] = character.x.ULT_DMG
arr[offset + 20] = character.x.FUA_DMG
arr[offset + 21] = character.x.DOT_DMG
arr[offset + 22] = character.x.BREAK_DMG // 22
arr[offset + 23] = character.x.COMBO_DMG
arr[offset + 24] = character.x[Stats.HP]
arr[offset + 25] = character.x[Stats.ATK]
arr[offset + 26] = character.x[Stats.DEF]
arr[offset + 27] = character.x[Stats.SPD]
arr[offset + 28] = character.x[Stats.CR]
arr[offset + 29] = character.x[Stats.CD]
arr[offset + 30] = character.x[Stats.EHR]
arr[offset + 31] = character.x[Stats.RES]
arr[offset + 32] = character.x[Stats.BE]
arr[offset + 33] = character.x[Stats.ERR] // 33
arr[offset + 34] = character.x[Stats.OHB]
arr[offset + 35] = character.x.ELEMENTAL_DMG
arr[offset + 36] = character.relicSetIndex
arr[offset + 37] = character.ornamentSetIndex
arr[offset] = x.c.id // 0
arr[offset + 1] = c[Stats.HP]
arr[offset + 2] = c[Stats.ATK]
arr[offset + 3] = c[Stats.DEF]
arr[offset + 4] = c[Stats.SPD]
arr[offset + 5] = c[Stats.CR]
arr[offset + 6] = c[Stats.CD]
arr[offset + 7] = c[Stats.EHR]
arr[offset + 8] = c[Stats.RES]
arr[offset + 9] = c[Stats.BE]
arr[offset + 10] = c[Stats.ERR] // 10
arr[offset + 11] = c[Stats.OHB]
arr[offset + 12] = c.ELEMENTAL_DMG
arr[offset + 13] = c.WEIGHT
arr[offset + 14] = a[Key.EHP]
arr[offset + 15] = a[Key.HEAL_VALUE]
arr[offset + 16] = a[Key.SHIELD_VALUE]
arr[offset + 17] = a[Key.BASIC_DMG]
arr[offset + 18] = a[Key.SKILL_DMG]
arr[offset + 19] = a[Key.ULT_DMG]
arr[offset + 20] = a[Key.FUA_DMG]
arr[offset + 21] = a[Key.DOT_DMG]
arr[offset + 22] = a[Key.BREAK_DMG] // 22
arr[offset + 23] = a[Key.COMBO_DMG]
arr[offset + 24] = a[Key.HP]
arr[offset + 25] = a[Key.ATK]
arr[offset + 26] = a[Key.DEF]
arr[offset + 27] = a[Key.SPD]
arr[offset + 28] = a[Key.CR]
arr[offset + 29] = a[Key.CD]
arr[offset + 30] = a[Key.EHR]
arr[offset + 31] = a[Key.RES]
arr[offset + 32] = a[Key.BE]
arr[offset + 33] = a[Key.ERR] // 33
arr[offset + 34] = a[Key.OHB]
arr[offset + 35] = a[Key.ELEMENTAL_DMG]
arr[offset + 36] = c.relicSetIndex
arr[offset + 37] = c.ornamentSetIndex
arr[offset + 38] = c.low
arr[offset + 39] = c.high
},

cleanFloatBuffer: (buffer: ArrayBuffer) => {
new Float64Array(buffer).fill(0)
new Float32Array(buffer).fill(0)
},

createFloatBuffer: (length: number) => {
return new Float64Array(length * SIZE).buffer
return new Float32Array(length * SIZE).buffer
},
}
Loading

0 comments on commit 8b6f7e5

Please sign in to comment.