Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: potential view with subset of characters #271

Merged
merged 6 commits into from
Apr 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 55 additions & 24 deletions src/components/RelicFilterBar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,13 @@ export default function RelicFilterBar(props) {
})
}

let gradeData = generateGradeTags([2, 3, 4, 5])
let verifiedData = generateVerifiedTags([false, true])
let setsData = generateImageTags(Object.values(Constants.SetsRelics).concat(Object.values(Constants.SetsOrnaments)), (x) => Assets.getSetImage(x, Constants.Parts.PlanarSphere), true)
let partsData = generateImageTags(Object.values(Constants.Parts), (x) => Assets.getPart(x), false)
let mainStatsData = generateImageTags(Constants.MainStats, (x) => Assets.getStatIcon(x, true), true)
let subStatsData = generateImageTags(Constants.SubStats, (x) => Assets.getStatIcon(x, true), true)
let enhanceData = generateTextTags([[0, '+0'], [3, '+3'], [6, '+6'], [9, '+9'], [12, '+12'], [15, '+15']])
const gradeData = generateGradeTags([2, 3, 4, 5])
const verifiedData = generateVerifiedTags([false, true])
const setsData = generateImageTags(Object.values(Constants.SetsRelics).concat(Object.values(Constants.SetsOrnaments)), (x) => Assets.getSetImage(x, Constants.Parts.PlanarSphere), true)
const partsData = generateImageTags(Object.values(Constants.Parts), (x) => Assets.getPart(x), false)
const mainStatsData = generateImageTags(Constants.MainStats, (x) => Assets.getStatIcon(x, true), true)
const subStatsData = generateImageTags(Constants.SubStats, (x) => Assets.getStatIcon(x, true), true)
const enhanceData = generateTextTags([[0, '+0'], [3, '+3'], [6, '+6'], [9, '+9'], [12, '+12'], [15, '+15']])

window.refreshRelicsScore = () => {
// NOTE: the scoring modal (where this event is published) calls .submit() in the same block of code
Expand All @@ -130,36 +130,46 @@ export default function RelicFilterBar(props) {
}, [])

function characterSelectorChange(id) {
let relics = Object.values(DB.getRelicsById())
const relics = Object.values(DB.getRelicsById())
console.log('idChange', id)

setScoringAlgorithmFocusCharacter(id)
setCurrentlySelectedCharacterId(id)

let allCharacters = characterOptions.map((val) => val.id)
const allCharacters = characterOptions.map((val) => val.id)
const excludedCharacters = window.store.getState().excludedRelicPotentialCharacters

let relicScorer = new RelicScorer()
const relicScorer = new RelicScorer()

// NOTE: we cannot cache these results between renders by keying on the relic/char id because
// both relic stats and char weights can be edited
for (let relic of relics) {
for (const relic of relics) {
relic.weights = id ? relicScorer.scoreRelic(relic, id) : { current: 0, best: 0, average: 0 }
relic.weights.potentialSelected = id ? relicScorer.scoreRelicPct(relic, id) : { bestPct: 0, averagePct: 0 }
relic.weights.potentialAllAll = 0
relic.weights.potentialAllCustom = 0

for (let cid of allCharacters) {
let pct = relicScorer.scoreRelicPct(relic, cid).bestPct
for (const cid of allCharacters) {
const pct = relicScorer.scoreRelicPct(relic, cid).bestPct
relic.weights.potentialAllAll = Math.max(pct, relic.weights.potentialAllAll)

if (!excludedCharacters.includes(cid)) {
relic.weights.potentialAllCustom = Math.max(pct, relic.weights.potentialAllCustom)
}
}
}

DB.setRelics(relics)

if (id && window.relicsGrid?.current?.api) {
window.relicsGrid.current.api.applyColumnState({
state: [{ colId: 'weights.current', sort: 'desc' }],
defaultState: { sort: null },
})
const isSorted = window.relicsGrid.current.columnApi.getColumnState().filter((s) => s.sort !== null)

if (!isSorted) {
window.relicsGrid.current.api.applyColumnState({
state: [{ colId: 'weights.current', sort: 'desc' }],
defaultState: { sort: null },
})
}
}

DB.refreshRelics()
Expand Down Expand Up @@ -226,8 +236,9 @@ export default function RelicFilterBar(props) {
<HeaderText>Substats</HeaderText>
<FilterRow name="subStats" tags={subStatsData} />
</Flex>

<Flex gap={10}>
<Flex vertical flex={1}>
<Flex vertical flex={0.5}>
<HeaderText>Relic recommendation character</HeaderText>
<Flex gap={10}>
<CharacterSelect
Expand All @@ -252,8 +263,9 @@ export default function RelicFilterBar(props) {
</Button>
</Flex>
</Flex>
<Flex flex={1} gap={10}>
<Flex vertical flex={1}>

<Flex vertical flex={0.25} gap={10}>
<Flex vertical>
<Flex justify="space-between" align="center">
<HeaderText>Relic ratings</HeaderText>
<TooltipImage type={Hint.valueColumns()} />
Expand All @@ -271,6 +283,25 @@ export default function RelicFilterBar(props) {
</Flex>
</Flex>
</Flex>

<Flex vertical flex={0.25}>
<HeaderText>Custom potential characters</HeaderText>
<CharacterSelect
value={window.store.getState().excludedRelicPotentialCharacters}
selectStyle={{ flex: 1 }}
onChange={(x) => {
const excludedCharacterIds = (x || new Map()).entries()
.filter((entry) => entry[1] == true)
.map((entry) => entry[0])
.toArray()
window.store.getState().setExcludedRelicPotentialCharacters(excludedCharacterIds)
SaveState.save()
setTimeout(() => rescoreClicked(), 100)
console.debug(x, excludedCharacterIds)
}}
multipleSelect={true}
/>
</Flex>
</Flex>
</Flex>
)
Expand All @@ -282,17 +313,17 @@ RelicFilterBar.propTypes = {
}

function FilterRow(props) {
let relicTabFilters = window.store((s) => s.relicTabFilters)
let setRelicTabFilters = window.store((s) => s.setRelicTabFilters)
const relicTabFilters = window.store((s) => s.relicTabFilters)
const setRelicTabFilters = window.store((s) => s.setRelicTabFilters)

let selectedTags = relicTabFilters[props.name]
const selectedTags = relicTabFilters[props.name]

const handleChange = (tag, checked) => {
const nextSelectedTags = checked
? [...selectedTags, tag]
: selectedTags.filter((t) => t != tag)

let clonedFilters = Utils.clone(relicTabFilters)
const clonedFilters = Utils.clone(relicTabFilters)
clonedFilters[props.name] = nextSelectedTags
console.log('Relic tab filters', props.name, clonedFilters)

Expand Down
9 changes: 6 additions & 3 deletions src/components/RelicsTab.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -188,11 +188,12 @@ export default function RelicsTab() {
{ column: 'Selected Char\nScore', value: 'weights.current', label: 'Selected character: Score' },
{ column: 'Selected Char\nAvg Potential', value: 'weights.potentialSelected.averagePct', label: 'Selected character: Average potential', percent: true },
{ column: 'Selected Char\nMax Potential', value: 'weights.potentialSelected.bestPct', label: 'Selected character: Max potential', percent: true },
{ column: 'All Characters\nMax Potential', value: 'weights.potentialAllAll', label: 'All characters: Max potential', percent: true },
{ column: 'All Characters\nMax Potential + Sets', disabled: true, value: 'weights.potentialAllSets', label: 'All characters: Max potential + Sets (Coming soon)', percent: true },
{ column: 'Custom Chars\nMax Potential', value: 'weights.potentialAllCustom', label: 'Custom characters: Max potential', percent: true },
{ column: 'All Chars\nMax Potential', value: 'weights.potentialAllAll', label: 'All characters: Max potential', percent: true },
{ column: 'All Chars\nMax Potential + Sets', disabled: true, value: 'weights.potentialAllSets', label: 'All characters: Max potential + Sets (Coming soon)', percent: true },
], [])

const [valueColumns, setValueColumns] = useState(['weights.current', 'weights.potentialSelected.averagePct', 'weights.potentialSelected.bestPct', 'weights.potentialAllAll'])
const [valueColumns, setValueColumns] = useState(['weights.current', 'weights.potentialSelected.averagePct', 'weights.potentialSelected.bestPct', 'weights.potentialAllCustom'])

const columnDefs = useMemo(() => [
{ field: 'equippedBy', headerName: 'Owner', width: 45, cellRenderer: Renderer.characterIcon },
Expand Down Expand Up @@ -326,7 +327,9 @@ export default function RelicsTab() {
let scoreBuckets = null
if (selectedRelic) {
const chars = DB.getMetadata().characters
const excluded = window.store.getState().excludedRelicPotentialCharacters
const allScores = Object.keys(chars)
.filter((id) => !excluded.includes(id))
.map((id) => ({
cid: id,
name: chars[id].displayName,
Expand Down
11 changes: 6 additions & 5 deletions src/components/optimizerTab/OptimizerForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ export default function OptimizerForm() {

// On first load, display the first character from the roster
useEffect(() => {
let characters = DB.getCharacters() || []
const characters = DB.getCharacters() || []
OptimizerTabController.updateCharacter(characters[0]?.id)
}, [])

const onValuesChange = (changedValues, allValues, bypass) => {
if (!changedValues || !allValues || !allValues.characterId) return
let keys = Object.keys(changedValues)
const keys = Object.keys(changedValues)

if (bypass) {
// Only allow certain values to refresh permutations.
Expand Down Expand Up @@ -99,18 +99,19 @@ export default function OptimizerForm() {
return
}

window.store.getState().setOptimizationInProgress(true)

DB.addFromForm(form)
SaveState.save()

let optimizationId = Utils.randomId()
const optimizationId = Utils.randomId()
window.store.getState().setOptimizationId(optimizationId)
form.optimizationId = optimizationId
form.statDisplay = window.store.getState().statDisplay

console.log('Form finished', form)

window.store.getState().setOptimizationInProgress(true)
Optimizer.optimize(form)
setTimeout(() => Optimizer.optimize(form), 50)
}
window.optimizerStartClicked = startClicked

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { Flex, Typography } from 'antd'
import { Utils } from 'lib/utils.js'
import { Assets } from 'lib/assets.js'
import CheckableTag from 'antd/lib/tag/CheckableTag'
import { ElementToDamage, PathToClass } from 'lib/constants.ts'
import { ReactElement } from 'react'

const { Paragraph } = Typography

const parentW = 100
const parentH = 150

export const CardGridItemContent = (props: {
imgSrc: string
text: string
innerW: number
innerH: number
rows: number
}) => {
return (
<div>
<img
width={props.innerW}
src={props.imgSrc}
style={{
transform: `translate(${(props.innerW - parentW) / 2 / props.innerW * -100}%, ${(props.innerH - parentH) / 2 / props.innerH * -100}%)`,
}}
/>
<Paragraph
ellipsis={{ rows: props.rows }}
style={{
position: 'absolute',
bottom: 0,
left: 0,
width: '110%',
textAlign: 'center',
background: '#282B31',
color: '#D0D0D2',
marginLeft: '-5%',
paddingLeft: 10,
paddingRight: 10,
lineHeight: '16px',
height: 18 * props.rows,
alignItems: 'center',
marginBottom: 0,
fontSize: 13,
}}
>
<div
style={{
position: 'relative',
top: '50%',
transform: 'translateY(-50%)',
maxHeight: 18 * props.rows,
textOverflow: 'ellipsis',
overflow: 'hidden',
}}
>
{props.text}
</div>
</Paragraph>
</div>
)
}

export function generatePathTags() {
return Object.keys(PathToClass).map((x) => {
return {
key: x,
display: <img style={{ width: 32 }} src={Assets.getPath(x)} />,
}
})
}

export function generateRarityTags() {
return [5, 4, 3].map((x) => {
const stars: ReactElement[] = []
for (let i = 0; i < x; i++) {
stars.push(<img key={i} style={{ width: 16 }} src={Assets.getStar()} />)
}
return {
key: x,
display: (
<Flex flex={1} justify="center" align="center" style={{ marginTop: 1 }}>
{stars}
</Flex>
),
}
})
}

export function generateElementTags() {
return Object.keys(ElementToDamage).map((x) => {
return {
key: x,
display: <img style={{ width: 30 }} src={Assets.getElement(x)} />,
}
})
}

export function CardGridFilterRow({ currentFilters, name, flexBasis, tags, setCurrentFilters }) {
const selectedTags = currentFilters[name]

const handleChange = (tag, checked) => {
const nextSelectedTags = checked
? [...selectedTags, tag]
: selectedTags.filter((t) => t != tag)

const clonedFilters = Utils.clone(currentFilters)
clonedFilters[name] = nextSelectedTags
console.log('filters', name, clonedFilters)

setCurrentFilters(clonedFilters)
}

return (
<Flex
style={{
flexWrap: 'wrap',
flexGrow: 1,
backgroundColor: '#243356',
boxShadow: '0px 0px 0px 1px #3F5A96 inset',
borderRadius: 6,
overflow: 'hidden',
height: 40,
}}
>
{tags.map((tag) => (
<CheckableTag
key={tag.key}
checked={selectedTags.includes(tag.key)}
onChange={(checked) => handleChange(tag.key, checked)}
style={{
flex: 1,
flexBasis: flexBasis,
boxShadow: '1px 1px 0px 0px #3F5A96',
}}
>
<Flex align="center" justify="space-around" style={{ height: '100%' }}>
{tag.display}
</Flex>
</CheckableTag>
))}
</Flex>
)
}
Loading
Loading