Update handling for over mastery and awakening

- Rings are now arrays, not objects
- Awakenings lookup based on slug, not id
- Use 0-index for rings, not 1-index
- Add appropriate optional operators
- Add temporary lookup table for character awakenings
This commit is contained in:
Justin Edmund 2025-02-09 18:46:29 -08:00
parent a188143422
commit 75a3e26cce
6 changed files with 107 additions and 105 deletions

View file

@ -65,7 +65,7 @@ const CharacterHovercard = (props: Props) => {
}
const overMasterySection = () => {
if (props.gridCharacter && props.gridCharacter.over_mastery) {
if (props.gridCharacter && props.gridCharacter.over_mastery.length > 0) {
return (
<section className={styles.mastery}>
<h5 className={tintElement}>
@ -73,14 +73,13 @@ const CharacterHovercard = (props: Props) => {
</h5>
<ul>
{[...Array(4)].map((e, i) => {
const ringIndex = i + 1
const ringStat: ExtendedMastery =
props.gridCharacter.over_mastery[ringIndex]
props.gridCharacter.over_mastery[i]
if (ringStat && ringStat.modifier && ringStat.modifier > 0) {
if (ringIndex === 1 || ringIndex === 2) {
if (i === 0 || i === 1) {
return masteryElement(overMastery.a, ringStat)
} else if (ringIndex === 3) {
} else if (i === 2) {
return masteryElement(overMastery.b, ringStat)
} else {
return masteryElement(overMastery.c, ringStat)
@ -96,8 +95,9 @@ const CharacterHovercard = (props: Props) => {
const aetherialMasterySection = () => {
if (
props.gridCharacter &&
props.gridCharacter.over_mastery &&
props.gridCharacter.aetherial_mastery &&
props.gridCharacter.aetherial_mastery.modifier > 0
props.gridCharacter.aetherial_mastery?.modifier > 0
) {
return (
<section className={styles.mastery}>
@ -136,9 +136,8 @@ const CharacterHovercard = (props: Props) => {
}
const awakeningSection = () => {
const gridAwakening = props.gridCharacter.awakening
if (gridAwakening) {
if (props.gridCharacter.awakening) {
const gridAwakening = props.gridCharacter.awakening
return (
<section className={styles.awakening}>
<h5 className={tintElement}>

View file

@ -44,6 +44,13 @@ interface Props {
updateCharacter: (object: GridCharacterObject) => Promise<any>
}
const AWAKENING_MAP: { [key: string]: string } = {
'character-balanced': 'b1847c82-ece0-4d7a-8af1-c7868d90f34a',
'character-atk': '6e233877-8cda-4c8f-a091-3db6f68749e2',
'character-def': 'c95441de-f949-4a62-b02b-101aa2e0a638',
'character-multi': 'e36b0573-79c3-4dd2-9524-c95def4bbb1a',
}
const CharacterModal = ({
gridCharacter,
children,
@ -64,12 +71,7 @@ const CharacterModal = ({
// State: Data
const [perpetuity, setPerpetuity] = useState(false)
const [rings, setRings] = useState<CharacterOverMastery>({
1: { ...emptyExtendedMastery, modifier: 1 },
2: { ...emptyExtendedMastery, modifier: 2 },
3: emptyExtendedMastery,
4: emptyExtendedMastery,
})
const [rings, setRings] = useState<CharacterOverMastery>([])
const [earring, setEarring] = useState<ExtendedMastery>(emptyExtendedMastery)
const [awakening, setAwakening] = useState<Awakening>()
const [awakeningLevel, setAwakeningLevel] = useState(1)
@ -94,46 +96,36 @@ const CharacterModal = ({
})
}
setAwakening(gridCharacter.awakening.type)
setAwakeningLevel(gridCharacter.awakening.level)
if (gridCharacter.awakening) {
setAwakening(gridCharacter.awakening.type)
setAwakeningLevel(gridCharacter.awakening.level)
}
setPerpetuity(gridCharacter.perpetuity)
}, [gridCharacter])
// Prepare the GridWeaponObject to send to the server
function prepareObject() {
let object: GridCharacterObject = {
function prepareObject(): GridCharacterObject {
return {
character: {
ring1: {
modifier: rings[1].modifier,
strength: rings[1].strength,
},
ring2: {
modifier: rings[2].modifier,
strength: rings[2].strength,
},
ring3: {
modifier: rings[3].modifier,
strength: rings[3].strength,
},
ring4: {
modifier: rings[4].modifier,
strength: rings[4].strength,
},
rings: rings, // your local rings array
earring: {
modifier: earring.modifier,
strength: earring.strength,
strength:
earring.modifier && earring.modifier > 0 ? earring.strength : 0,
},
// Only include awakening if one is set.
...(awakening
? {
awakening: {
id: awakening.id,
level: awakeningLevel,
},
}
: {}),
transcendence_step: transcendenceStep,
perpetuity: perpetuity,
},
}
if (awakening) {
object.character.awakening_id = awakening.id
object.character.awakening_level = awakeningLevel
}
return object
}
// Methods: Modification checking
@ -152,12 +144,12 @@ const CharacterModal = ({
function ringsChanged() {
// Create an empty ExtendedMastery object
const emptyRingset: CharacterOverMastery = {
1: { ...emptyExtendedMastery, modifier: 1 },
2: { ...emptyExtendedMastery, modifier: 2 },
3: emptyExtendedMastery,
4: emptyExtendedMastery,
}
const emptyRingset: CharacterOverMastery = [
{ ...emptyExtendedMastery, modifier: 1 },
{ ...emptyExtendedMastery, modifier: 2 },
emptyExtendedMastery,
emptyExtendedMastery,
]
// Check if the current ringset is empty on the current GridCharacter and our local state
const isEmptyRingset =
@ -195,8 +187,8 @@ const CharacterModal = ({
function awakeningChanged() {
// Check if the awakening in local state is different from the one on the current GridCharacter
const awakeningChanged =
!isEqual(gridCharacter.awakening.type, awakening) ||
gridCharacter.awakening.level !== awakeningLevel
!isEqual(gridCharacter.awakening?.type, awakening) ||
gridCharacter.awakening?.level !== awakeningLevel
// Return true if the awakening has been modified and is not empty
return awakeningChanged
@ -227,8 +219,26 @@ const CharacterModal = ({
})
}
function receiveAwakeningValues(id: string, level: number) {
setAwakening(gridCharacter.object.awakenings.find((a) => a.id === id))
function receiveAwakeningValues(slug: string, level: number) {
const mappedId = AWAKENING_MAP[slug] || null
const existingAwakening = gridCharacter.object.awakenings.find(
(a) => a.slug === slug
)
if (existingAwakening && mappedId) {
setAwakening({
...existingAwakening,
id: mappedId,
})
} else {
setAwakening({
id: mappedId || '',
slug,
name: { en: '', jp: '' },
order: 0,
})
}
setAwakeningLevel(level)
}
@ -307,13 +317,13 @@ const CharacterModal = ({
object="earring"
dataSet={elementalizeAetherialMastery(gridCharacter)}
selectValue={
gridCharacter.aetherial_mastery
? gridCharacter.aetherial_mastery.modifier
gridCharacter.over_mastery && gridCharacter.aetherial_mastery
? gridCharacter.aetherial_mastery?.modifier
: 0
}
inputValue={
gridCharacter.aetherial_mastery
? gridCharacter.aetherial_mastery.strength
gridCharacter.over_mastery && gridCharacter.aetherial_mastery
? gridCharacter.aetherial_mastery?.strength
: 0
}
sendValidity={receiveValidity}

View file

@ -148,12 +148,12 @@ const CharacterUnit = ({
let character = cloneDeep(gridCharacter)
if (character.over_mastery) {
const overMastery: CharacterOverMastery = {
1: gridCharacter.over_mastery[0],
2: gridCharacter.over_mastery[1],
3: gridCharacter.over_mastery[2],
4: gridCharacter.over_mastery[3],
}
const overMastery: CharacterOverMastery = [
gridCharacter.over_mastery[0],
gridCharacter.over_mastery[1],
gridCharacter.over_mastery[2],
gridCharacter.over_mastery[3],
]
character.over_mastery = overMastery
}

View file

@ -1,4 +1,3 @@
// Core dependencies
import React, { useEffect, useState } from 'react'
import { useRouter } from 'next/router'
import { useTranslation } from 'next-i18next'
@ -73,7 +72,8 @@ const AwakeningSelectWithInput = ({
setCurrentAwakening(awakening)
setCurrentLevel(level ? level : 1)
if (awakening) sendValidity(true)
// If there is an awakening (even if it's the default) we consider the field valid.
if (awakening || defaultAwakening) sendValidity(true)
}, [])
// Methods: UI state management
@ -90,35 +90,32 @@ const AwakeningSelectWithInput = ({
// Methods: Rendering
function generateOptions() {
const sortedDataSet = [...dataSet].sort((a, b) => {
return a.order - b.order
})
let options: React.ReactNode[] = sortedDataSet.map((awakening, i) => {
return generateItem(awakening)
})
const sortedDataSet = [...dataSet].sort((a, b) => a.order - b.order)
let options: React.ReactNode[] = sortedDataSet.map((awakening) =>
generateItem(awakening)
)
if (!dataSet.includes(defaultAwakening))
options.unshift(generateItem(defaultAwakening))
return options
}
function generateItem(awakening: Awakening) {
return (
<SelectItem key={awakening.slug} value={awakening.id}>
<SelectItem key={awakening.slug} value={awakening.slug}>
{awakening.name[locale]}
</SelectItem>
)
}
// Methods: User input detection
function handleSelectChange(id: string) {
function handleSelectChange(value: string) {
// Here, value is the awakening slug.
const input = inputRef.current
if (input && !handleInputError(parseFloat(input.value))) return
setCurrentAwakening(dataSet.find((awakening) => awakening.id === id))
sendValues(id, currentLevel)
const selectedAwakening = dataSet.find((a) => a.slug === value)
setCurrentAwakening(selectedAwakening)
sendValues(value, currentLevel)
}
function handleInputChange(event: React.ChangeEvent<HTMLInputElement>) {
@ -127,7 +124,10 @@ const AwakeningSelectWithInput = ({
const newLevel = parseInt(event.target.value)
setCurrentLevel(newLevel)
sendValues(currentAwakening ? currentAwakening.id : '0', newLevel)
sendValues(
currentAwakening ? currentAwakening.slug : defaultAwakening.slug,
newLevel
)
}
// Methods: Handle error
@ -135,16 +135,12 @@ const AwakeningSelectWithInput = ({
let error = ''
if (currentAwakening) {
if (value && value % 1 != 0) {
if (value && value % 1 !== 0) {
error = t(`awakening.errors.value_not_whole`)
} else if (value < 1) {
error = t(`awakening.errors.value_too_low`, {
minValue: 1,
})
error = t(`awakening.errors.value_too_low`, { minValue: 1 })
} else if (value > maxLevel) {
error = t(`awakening.errors.value_too_high`, {
maxValue: maxLevel,
})
error = t(`awakening.errors.value_too_high`, { maxValue: maxLevel })
} else if (!value || value <= 0) {
error = t(`awakening.errors.value_empty`)
} else {
@ -165,13 +161,9 @@ const AwakeningSelectWithInput = ({
const rangeString = () => {
let placeholder = ''
if (awakening) {
const minValue = 1
const maxValue = maxLevel
placeholder = `${minValue}~${maxValue}`
if (currentAwakening) {
placeholder = `1~${maxLevel}`
}
return placeholder
}
@ -180,7 +172,8 @@ const AwakeningSelectWithInput = ({
<div className={styles.set}>
<Select
key="awakening-type"
value={`${awakening ? awakening.id : defaultAwakening.id}`}
// Use the slug as the value
value={`${awakening ? awakening.slug : defaultAwakening.slug}`}
open={open}
disabled={selectDisabled}
onValueChange={handleSelectChange}
@ -200,7 +193,8 @@ const AwakeningSelectWithInput = ({
className={inputClasses}
fieldsetClassName={classNames({
hidden:
currentAwakening === undefined || currentAwakening.id === '0',
currentAwakening === undefined ||
currentAwakening.slug === defaultAwakening.slug,
})}
wrapperClassName="fullHeight"
bound={true}

View file

@ -324,13 +324,13 @@ const Party = (props: Props) => {
list.forEach((object: GridCharacter) => {
let character = clonedeep(object)
if (character.over_mastery) {
const overMastery: CharacterOverMastery = {
1: object.over_mastery[0],
2: object.over_mastery[1],
3: object.over_mastery[2],
4: object.over_mastery[3],
}
if (character.over_mastery && character.over_mastery) {
const overMastery: CharacterOverMastery = [
object.over_mastery[0],
object.over_mastery[1],
object.over_mastery[2],
object.over_mastery[3],
]
character.over_mastery = overMastery
}

View file

@ -136,13 +136,12 @@ const WeaponModal = ({
}
// Receive values from AwakeningSelectWithInput
function receiveAwakeningValues(id: string, level: number) {
setAwakening(gridWeapon.object.awakenings.find((a) => a.id === id))
console.log(level)
function receiveAwakeningValues(slug: string, level: number) {
// Look up the awakening by its slug, since the select sends a slug.
setAwakening(gridWeapon.object.awakenings.find((a) => a.slug === slug))
setAwakeningLevel(level)
setFormValid(true)
}
// Receive values from WeaponKeySelect
function receiveWeaponKey(value: WeaponKey, slot: number) {
if (slot === 0) setWeaponKey1(value)