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

View file

@ -44,6 +44,13 @@ interface Props {
updateCharacter: (object: GridCharacterObject) => Promise<any> 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 = ({ const CharacterModal = ({
gridCharacter, gridCharacter,
children, children,
@ -64,12 +71,7 @@ const CharacterModal = ({
// State: Data // State: Data
const [perpetuity, setPerpetuity] = useState(false) const [perpetuity, setPerpetuity] = useState(false)
const [rings, setRings] = useState<CharacterOverMastery>({ const [rings, setRings] = useState<CharacterOverMastery>([])
1: { ...emptyExtendedMastery, modifier: 1 },
2: { ...emptyExtendedMastery, modifier: 2 },
3: emptyExtendedMastery,
4: emptyExtendedMastery,
})
const [earring, setEarring] = useState<ExtendedMastery>(emptyExtendedMastery) const [earring, setEarring] = useState<ExtendedMastery>(emptyExtendedMastery)
const [awakening, setAwakening] = useState<Awakening>() const [awakening, setAwakening] = useState<Awakening>()
const [awakeningLevel, setAwakeningLevel] = useState(1) const [awakeningLevel, setAwakeningLevel] = useState(1)
@ -94,46 +96,36 @@ const CharacterModal = ({
}) })
} }
if (gridCharacter.awakening) {
setAwakening(gridCharacter.awakening.type) setAwakening(gridCharacter.awakening.type)
setAwakeningLevel(gridCharacter.awakening.level) setAwakeningLevel(gridCharacter.awakening.level)
}
setPerpetuity(gridCharacter.perpetuity) setPerpetuity(gridCharacter.perpetuity)
}, [gridCharacter]) }, [gridCharacter])
// Prepare the GridWeaponObject to send to the server // Prepare the GridWeaponObject to send to the server
function prepareObject() { function prepareObject(): GridCharacterObject {
let object: GridCharacterObject = { return {
character: { character: {
ring1: { rings: rings, // your local rings array
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,
},
earring: { earring: {
modifier: earring.modifier, 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, transcendence_step: transcendenceStep,
perpetuity: perpetuity, perpetuity: perpetuity,
}, },
} }
if (awakening) {
object.character.awakening_id = awakening.id
object.character.awakening_level = awakeningLevel
}
return object
} }
// Methods: Modification checking // Methods: Modification checking
@ -152,12 +144,12 @@ const CharacterModal = ({
function ringsChanged() { function ringsChanged() {
// Create an empty ExtendedMastery object // Create an empty ExtendedMastery object
const emptyRingset: CharacterOverMastery = { const emptyRingset: CharacterOverMastery = [
1: { ...emptyExtendedMastery, modifier: 1 }, { ...emptyExtendedMastery, modifier: 1 },
2: { ...emptyExtendedMastery, modifier: 2 }, { ...emptyExtendedMastery, modifier: 2 },
3: emptyExtendedMastery, emptyExtendedMastery,
4: emptyExtendedMastery, emptyExtendedMastery,
} ]
// Check if the current ringset is empty on the current GridCharacter and our local state // Check if the current ringset is empty on the current GridCharacter and our local state
const isEmptyRingset = const isEmptyRingset =
@ -195,8 +187,8 @@ const CharacterModal = ({
function awakeningChanged() { function awakeningChanged() {
// Check if the awakening in local state is different from the one on the current GridCharacter // Check if the awakening in local state is different from the one on the current GridCharacter
const awakeningChanged = const awakeningChanged =
!isEqual(gridCharacter.awakening.type, awakening) || !isEqual(gridCharacter.awakening?.type, awakening) ||
gridCharacter.awakening.level !== awakeningLevel gridCharacter.awakening?.level !== awakeningLevel
// Return true if the awakening has been modified and is not empty // Return true if the awakening has been modified and is not empty
return awakeningChanged return awakeningChanged
@ -227,8 +219,26 @@ const CharacterModal = ({
}) })
} }
function receiveAwakeningValues(id: string, level: number) { function receiveAwakeningValues(slug: string, level: number) {
setAwakening(gridCharacter.object.awakenings.find((a) => a.id === id)) 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) setAwakeningLevel(level)
} }
@ -307,13 +317,13 @@ const CharacterModal = ({
object="earring" object="earring"
dataSet={elementalizeAetherialMastery(gridCharacter)} dataSet={elementalizeAetherialMastery(gridCharacter)}
selectValue={ selectValue={
gridCharacter.aetherial_mastery gridCharacter.over_mastery && gridCharacter.aetherial_mastery
? gridCharacter.aetherial_mastery.modifier ? gridCharacter.aetherial_mastery?.modifier
: 0 : 0
} }
inputValue={ inputValue={
gridCharacter.aetherial_mastery gridCharacter.over_mastery && gridCharacter.aetherial_mastery
? gridCharacter.aetherial_mastery.strength ? gridCharacter.aetherial_mastery?.strength
: 0 : 0
} }
sendValidity={receiveValidity} sendValidity={receiveValidity}

View file

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

View file

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

View file

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

View file

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