diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/InfoWithDelta.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/InfoWithDelta.tsx index bf0321cf5..b767fa3d4 100644 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/InfoWithDelta.tsx +++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/InfoWithDelta.tsx @@ -1,7 +1,7 @@ import { createDefaultSkills } from '@blitzkit/core'; import { Flex, Progress, Text } from '@radix-ui/themes'; import { clamp } from 'lodash-es'; -import { useMemo, type ComponentProps, type ReactNode } from 'react'; +import { memo, useMemo, type ComponentProps, type ReactNode } from 'react'; import { awaitableEquipmentDefinitions } from '../../../../../../core/awaitables/equipmentDefinitions'; import { awaitableModelDefinitions } from '../../../../../../core/awaitables/modelDefinitions'; import { awaitableProvisionDefinitions } from '../../../../../../core/awaitables/provisionDefinitions'; @@ -48,148 +48,151 @@ const [ awaitableSkillDefinitions, ]); -export function InfoWithDelta({ - stats, - indent, - noRanking, - deltaType, - ...props -}: InfoWithDeltaProps) { - const { strings } = useLocale(); - const relativeAgainst = TankopediaEphemeral.use( - (state) => state.relativeAgainst, - ); - const uhWhatDoICallThisVariable = - typeof props.value === 'function' - ? props.value(stats)! - : (stats[props.value] as number); - const delta = useDelta(uhWhatDoICallThisVariable); - const protagonistTank = Duel.use((state) => state.protagonist.tank); - const shellIndex = Duel.use((state) => - state.protagonist.gun.gun_type!.value.base.shells.indexOf( - state.protagonist.shell, - ), - ); - const equipmentMatrix = Duel.use( - (state) => state.protagonist.equipmentMatrix, - ); - const others = useMemo(() => { - const defaultSkills = createDefaultSkills(skillDefinitions); - - return Object.values(tankDefinitions.tanks) - .filter( - (tank) => - (relativeAgainst === TankopediaRelativeAgainst.Class && - tank.tier === protagonistTank.tier && - tank.class === protagonistTank.class) || - (relativeAgainst === TankopediaRelativeAgainst.Tier && - tank.tier === protagonistTank.tier) || - relativeAgainst === TankopediaRelativeAgainst.All, - ) - .map((tank) => { - const thisTankModel = modelDefinitions.models[tank.id]; - const member = tankToDuelMember( - tank, - thisTankModel, - provisionDefinitions, - ); - - if (member.gun.gun_type!.value.base.shells[shellIndex] === undefined) { - return undefined; - } - - return tankCharacteristics( - { - applyDynamicArmor: false, - applyReactiveArmor: false, - applySpallLiner: false, - camouflage: member.camouflage, - consumables: member.consumables, - crewSkills: defaultSkills, - provisions: member.provisions, - engine: member.engine, - gun: member.gun, - equipmentMatrix: equipmentMatrix, - shell: member.gun.gun_type!.value.base.shells[shellIndex], - stockEngine: tank.engines[0], - stockGun: tank.turrets[0].guns[0], - stockTrack: tank.tracks[0], - stockTurret: tank.turrets[0], - tank, - track: member.track, - turret: member.turret, - assaultDistance: member.assaultDistance, - }, - { - equipmentDefinitions, - provisionDefinitions, - tankModelDefinition: modelDefinitions.models[tank.id], - }, - ); - }) - .filter((tank) => { - if (tank === undefined) return false; - - const othersValue = - typeof props.value === 'function' - ? props.value(tank)! - : (tank[props.value] as number); - - return othersValue !== undefined; - }) as TankCharacteristics[]; - }, [relativeAgainst, shellIndex, equipmentMatrix]); - const betterTanks = others.filter((tank) => { - const othersValue = +export const InfoWithDelta = memo( + ({ stats, indent, noRanking, deltaType, ...props }) => { + const { strings } = useLocale(); + const relativeAgainst = TankopediaEphemeral.use( + (state) => state.relativeAgainst, + ); + const uhWhatDoICallThisVariable = typeof props.value === 'function' - ? props.value(tank)! - : (tank[props.value] as number); + ? props.value(stats)! + : (stats[props.value] as number); + const delta = useDelta(uhWhatDoICallThisVariable); + const protagonistTank = Duel.use((state) => state.protagonist.tank); + const shellIndex = Duel.use((state) => + state.protagonist.gun.gun_type!.value.base.shells.indexOf( + state.protagonist.shell, + ), + ); + const equipmentMatrix = Duel.use( + (state) => state.protagonist.equipmentMatrix, + ); + const others = useMemo(() => { + const defaultSkills = createDefaultSkills(skillDefinitions); - if (othersValue === undefined) return false; - return deltaType === 'lowerIsBetter' - ? othersValue < uhWhatDoICallThisVariable - : othersValue > uhWhatDoICallThisVariable; - }); - const goodness = (others.length - betterTanks.length) / others.length; - let color: ComponentProps['color']; + return Object.values(tankDefinitions.tanks) + .filter( + (tank) => + (relativeAgainst === TankopediaRelativeAgainst.Class && + tank.tier === protagonistTank.tier && + tank.class === protagonistTank.class) || + (relativeAgainst === TankopediaRelativeAgainst.Tier && + tank.tier === protagonistTank.tier) || + relativeAgainst === TankopediaRelativeAgainst.All, + ) + .map((tank) => { + const thisTankModel = modelDefinitions.models[tank.id]; + const member = tankToDuelMember( + tank, + thisTankModel, + provisionDefinitions, + ); - if (goodness <= 0.25) color = 'red'; - else if (goodness <= 0.5) color = 'orange'; - else if (goodness <= 0.75) color = 'yellow'; - else color = 'green'; + if ( + member.gun.gun_type!.value.base.shells[shellIndex] === undefined + ) { + return undefined; + } - return ( - - - {uhWhatDoICallThisVariable} - + return tankCharacteristics( + { + applyDynamicArmor: false, + applyReactiveArmor: false, + applySpallLiner: false, + camouflage: member.camouflage, + consumables: member.consumables, + crewSkills: defaultSkills, + provisions: member.provisions, + engine: member.engine, + gun: member.gun, + equipmentMatrix: equipmentMatrix, + shell: member.gun.gun_type!.value.base.shells[shellIndex], + stockEngine: tank.engines[0], + stockGun: tank.turrets[0].guns[0], + stockTrack: tank.tracks[0], + stockTurret: tank.turrets[0], + tank, + track: member.track, + turret: member.turret, + assaultDistance: member.assaultDistance, + }, + { + equipmentDefinitions, + provisionDefinitions, + tankModelDefinition: modelDefinitions.models[tank.id], + }, + ); + }) + .filter((tank) => { + if (tank === undefined) return false; - {!noRanking && ( - - + const othersValue = + typeof props.value === 'function' + ? props.value(tank)! + : (tank[props.value] as number); - - {clamp(betterTanks.length + 1, 1, others.length)} / {others.length} - - - )} - - ); -} + return othersValue !== undefined; + }) as TankCharacteristics[]; + }, [relativeAgainst, shellIndex, equipmentMatrix]); + const betterTanks = others.filter((tank) => { + const othersValue = + typeof props.value === 'function' + ? props.value(tank)! + : (tank[props.value] as number); + + if (othersValue === undefined) return false; + return deltaType === 'lowerIsBetter' + ? othersValue < uhWhatDoICallThisVariable + : othersValue > uhWhatDoICallThisVariable; + }); + const goodness = (others.length - betterTanks.length) / others.length; + let color: ComponentProps['color']; + + if (goodness <= 0.25) color = 'red'; + else if (goodness <= 0.5) color = 'orange'; + else if (goodness <= 0.75) color = 'yellow'; + else color = 'green'; + + return ( + + + {uhWhatDoICallThisVariable} + + + {!noRanking && ( + + + + + {clamp(betterTanks.length + 1, 1, others.length)} /{' '} + {others.length} + + + )} + + ); + }, + + (a, b) => + (typeof a.value === 'function' ? a.value(a.stats) : a.stats[a.value]) === + (typeof b.value === 'function' ? b.value(b.stats) : b.stats[b.value]), +);