From df1bc56a4c713ac1a5f07e060d2da7b4c422df64 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Tue, 22 Apr 2025 21:22:26 -0500 Subject: [PATCH] feat: isolate stat sections --- .../Characteristics/components/Crew.tsx | 112 +++ .../Characteristics/components/Firepower.tsx | 414 +++++++++ .../HullTraverseVisualizer/index.tsx | 4 +- .../components/Maneuverability.tsx | 150 ++++ .../components/RicochetVisualizer.tsx | 4 +- .../components/Survivability.tsx | 115 +++ .../components/ViewRangeVisualizer.tsx | 4 +- .../components/Characteristics/index.tsx | 795 +----------------- 8 files changed, 807 insertions(+), 791 deletions(-) create mode 100644 packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Crew.tsx create mode 100644 packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Firepower.tsx create mode 100644 packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Maneuverability.tsx create mode 100644 packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Survivability.tsx diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Crew.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Crew.tsx new file mode 100644 index 000000000..56545abfb --- /dev/null +++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Crew.tsx @@ -0,0 +1,112 @@ +import { CREW_MEMBER_NAMES, CrewType } from '@blitzkit/core'; +import { literals } from '@blitzkit/i18n'; +import { AccessibilityIcon, InfoCircledIcon } from '@radix-ui/react-icons'; +import { Flex, Heading, IconButton, Popover, Text } from '@radix-ui/themes'; +import { Fragment } from 'react/jsx-runtime'; +import { awaitableProvisionDefinitions } from '../../../../../../core/awaitables/provisionDefinitions'; +import { useEquipment } from '../../../../../../hooks/useEquipment'; +import { useLocale } from '../../../../../../hooks/useLocale'; +import { Duel } from '../../../../../../stores/duel'; +import type { StatsAcceptorProps } from './HullTraverseVisualizer'; +import { InfoWithDelta } from './InfoWithDelta'; + +const provisionDefinitions = await awaitableProvisionDefinitions; + +export function Crew({ stats }: StatsAcceptorProps) { + const tank = Duel.use((state) => state.protagonist.tank); + const { strings } = useLocale(); + const provisions = Duel.use((state) => state.protagonist.provisions); + const hasImprovedVentilation = useEquipment(102); + const provisionCrewBonus = + provisions.reduce( + (total, provision) => + provisionDefinitions.provisions[provision].crew + ? total + provisionDefinitions.provisions[provision].crew! / 100 + : total, + 0, + ) + (hasImprovedVentilation ? 0.08 : 0); + const commanderMastery = 1 + provisionCrewBonus; + + return ( + + + + {strings.website.tools.tankopedia.crew.title} + + + + + + + + + + + + + {strings.website.tools.tankopedia.crew.info} + + + + + + + + {tank.crew.map((member) => { + return ( + + 1 + ? ` ${literals(strings.common.units.x, [`${member.count}`])}` + : '' + }`} + unit="%" + decimals={0} + noRanking + value={() => + (member.type === CrewType.COMMANDER + ? commanderMastery + : commanderMastery * 1.1) * 100 + } + /> + + {member.substitute.length > 0 && ( + + + + {member.substitute + .map((sub, index) => + index === 0 + ? strings.website.tools.tankopedia.crew[ + CREW_MEMBER_NAMES[sub] + ] + : sub, + ) + .join(', ')} + + + } + noRanking + value={() => commanderMastery * 1.05 * 100} + /> + )} + + ); + })} + + ); +} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Firepower.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Firepower.tsx new file mode 100644 index 000000000..37a971ba4 --- /dev/null +++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Firepower.tsx @@ -0,0 +1,414 @@ +import { + asset, + coefficient, + isExplosive, + resolvePenetrationCoefficient, +} from '@blitzkit/core'; +import { literals } from '@blitzkit/i18n'; +import { + Flex, + Heading, + IconButton, + Slider, + Text, + TextField, + Tooltip, +} from '@radix-ui/themes'; +import { debounce } from 'lodash-es'; +import { useEffect, useRef, useState } from 'react'; +import { lerp } from 'three/src/math/MathUtils.js'; +import { useEquipment } from '../../../../../../hooks/useEquipment'; +import { useLocale } from '../../../../../../hooks/useLocale'; +import { useTankModelDefinition } from '../../../../../../hooks/useTankModelDefinition'; +import { Duel } from '../../../../../../stores/duel'; +import type { StatsAcceptorProps } from './HullTraverseVisualizer'; +import { Info } from './Info'; +import { InfoWithDelta } from './InfoWithDelta'; +import { RicochetVisualizer } from './RicochetVisualizer'; + +export function Firepower({ stats }: StatsAcceptorProps) { + const mutateDuel = Duel.useMutation(); + const { strings, unwrap } = useLocale(); + const [penetrationDistance, setPenetrationDistance] = useState(250); + const setPenetrationDistanceDebounced = debounce((value: number) => { + setPenetrationDistance(value); + }, 500); + const { gun, shell, turret } = Duel.use((state) => state.protagonist); + const penetrationDistanceInput = useRef(null); + const hasSupercharger = useEquipment(107); + const hasCalibratedShells = useEquipment(103); + const penetrationCoefficient = coefficient([ + hasCalibratedShells, + resolvePenetrationCoefficient(true, shell.type) - 1, + ]); + const penetrationLossOverDistanceCoefficient = coefficient([ + hasSupercharger, + -0.6, + ]); + const tankModelDefinition = useTankModelDefinition(); + const turretModelDefinition = tankModelDefinition.turrets[turret.id]; + const gunModelDefinition = + turretModelDefinition.guns[gun.gun_type!.value.base.id]; + + useEffect(() => { + if (penetrationDistanceInput.current) { + penetrationDistanceInput.current.value = `${penetrationDistance}`; + } + }, [penetrationDistance]); + + return ( + + + + {strings.website.tools.tankopedia.firepower.title} + + + + {gun.gun_type!.value.base.shells.map((thisShell, shellIndex) => ( + + { + mutateDuel((draft) => { + draft.protagonist.shell = thisShell; + }); + }} + > + {unwrap(thisShell.name)} + + + ))} + + + + + {strings.common.gun_types[gun.gun_type!.$case]} + + + {gun.gun_type!.$case === 'auto_reloader' && ( + + )} + {gun.gun_type!.$case !== 'regular' && ( + + )} + {gun.gun_type!.$case === 'auto_reloader' ? ( + <> + + {stats.mostOptimalShellIndex! + 1} + + + {stats.shellReloads!.map((reload, index) => ( + reload} + /> + ))} + + ) : ( + + )} + {(gun.gun_type!.$case === 'auto_loader' || + gun.gun_type!.$case === 'auto_reloader') && ( + + )} + + {typeof shell.penetration !== 'number' && ( + <> + + lerp( + shell.penetration.near, + shell.penetration.far, + (penetrationDistance * penetrationLossOverDistanceCoefficient) / + 500, + ) * penetrationCoefficient + } + /> + + + { + strings.website.tools.tankopedia.characteristics.values + .at_distance_slider_label + } + + { + setPenetrationDistanceDebounced(value); + + if (!penetrationDistanceInput.current) return; + + penetrationDistanceInput.current.value = value.toString(); + }} + /> + { + setPenetrationDistance( + Math.min( + parseInt(penetrationDistanceInput.current!.value), + 500, + ), + ); + }} + onKeyDown={(event) => { + if (event.key === 'Enter') { + event.currentTarget.blur(); + } + }} + > + m + + + + )} + + {gun.gun_type!.$case !== 'regular' && ( + + )} + + {isExplosive(shell.type) && ( + + )} + + + {!isExplosive(shell.type) && ( + <> + + + + + )} + + + + + + + + + + + + + + + {gunModelDefinition.pitch.front && ( + <> + + + + )} + {gunModelDefinition.pitch.back && ( + <> + + + + )} + {turretModelDefinition.yaw && ( + <> + + + + )} + + ); +} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/HullTraverseVisualizer/index.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/HullTraverseVisualizer/index.tsx index 08774a8eb..c9788a814 100644 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/HullTraverseVisualizer/index.tsx +++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/HullTraverseVisualizer/index.tsx @@ -4,13 +4,13 @@ import type { TankCharacteristics } from '../../../../../../../core/blitzkit/tan import { useLocale } from '../../../../../../../hooks/useLocale'; import './index.css'; -export interface VisualizerProps { +export interface StatsAcceptorProps { stats: TankCharacteristics; } type Terrain = 'Hard' | 'Medium' | 'Soft'; -export function HullTraverseVisualizer({ stats }: VisualizerProps) { +export function HullTraverseVisualizer({ stats }: StatsAcceptorProps) { const hull = useRef(null); const turret = useRef(null); const [terrain, setTerrain] = useState('Hard'); diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Maneuverability.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Maneuverability.tsx new file mode 100644 index 000000000..f61ac9b04 --- /dev/null +++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Maneuverability.tsx @@ -0,0 +1,150 @@ +import { Flex, Heading } from '@radix-ui/themes'; +import { useLocale } from '../../../../../../hooks/useLocale'; +import { + HullTraverseVisualizer, + type StatsAcceptorProps, +} from './HullTraverseVisualizer'; +import { Info } from './Info'; +import { InfoWithDelta } from './InfoWithDelta'; + +export function Maneuverability({ stats }: StatsAcceptorProps) { + const { strings } = useLocale(); + + return ( + + + {strings.website.tools.tankopedia.maneuverability.title} + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/RicochetVisualizer.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/RicochetVisualizer.tsx index 8f9c67f77..328203712 100644 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/RicochetVisualizer.tsx +++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/RicochetVisualizer.tsx @@ -6,9 +6,9 @@ import { clamp, degToRad, radToDeg } from 'three/src/math/MathUtils.js'; import { Var } from '../../../../../../core/radix/var'; import { useLocale } from '../../../../../../hooks/useLocale'; import { Duel } from '../../../../../../stores/duel'; -import type { VisualizerProps } from './HullTraverseVisualizer'; +import type { StatsAcceptorProps } from './HullTraverseVisualizer'; -export function RicochetVisualizer({ stats }: VisualizerProps) { +export function RicochetVisualizer({ stats }: StatsAcceptorProps) { const shell = Duel.use((state) => state.protagonist.shell); const container = useRef(null); const normalization = degToRad(stats.shellNormalization); diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Survivability.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Survivability.tsx new file mode 100644 index 000000000..6ca40635a --- /dev/null +++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Survivability.tsx @@ -0,0 +1,115 @@ +import { Flex, Heading } from '@radix-ui/themes'; +import { useLocale } from '../../../../../../hooks/useLocale'; +import type { StatsAcceptorProps } from './HullTraverseVisualizer'; +import { Info } from './Info'; +import { InfoWithDelta } from './InfoWithDelta'; +import { ViewRangeVisualizer } from './ViewRangeVisualizer'; + +export function Survivability({ stats }: StatsAcceptorProps) { + const { strings } = useLocale(); + + return ( + + + {strings.website.tools.tankopedia.survivability.title} + + + stats.fireChance * 100} + /> + + + + stats.camouflageStill * 100} + stats={stats} + indent + name={ + strings.website.tools.tankopedia.characteristics.values + .camouflageStill + } + decimals={2} + /> + stats.camouflageMoving * 100} + stats={stats} + indent + name={ + strings.website.tools.tankopedia.characteristics.values + .camouflageMoving + } + decimals={2} + /> + stats.camouflageShootingStill * 100} + /> + stats.camouflageShootingMoving * 100} + /> + stats.camouflageCaughtOnFire * 100} + /> + + + + + + ); +} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/ViewRangeVisualizer.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/ViewRangeVisualizer.tsx index d0288f2ea..2864ba246 100644 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/ViewRangeVisualizer.tsx +++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/ViewRangeVisualizer.tsx @@ -4,7 +4,7 @@ import { Box, Card, Code, Flex, Slider, Tabs, Text } from '@radix-ui/themes'; import { useRef, useState } from 'react'; import { Var } from '../../../../../../core/radix/var'; import { useLocale } from '../../../../../../hooks/useLocale'; -import type { VisualizerProps } from './HullTraverseVisualizer'; +import type { StatsAcceptorProps } from './HullTraverseVisualizer'; const maps = [ { @@ -19,7 +19,7 @@ const maps = [ }, ] as const; -export function ViewRangeVisualizer({ stats }: VisualizerProps) { +export function ViewRangeVisualizer({ stats }: StatsAcceptorProps) { const [mapIndex, setMapIndex] = useState(0); const [camouflage, setCamouflage] = useState(0); const map = maps[mapIndex]; diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/index.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/index.tsx index 72010177e..acd15b239 100644 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/index.tsx +++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/index.tsx @@ -1,41 +1,17 @@ -import { - asset, - coefficient, - CREW_MEMBER_NAMES, - CrewType, - isExplosive, - resolvePenetrationCoefficient, -} from '@blitzkit/core'; -import { literals } from '@blitzkit/i18n/src/literals'; -import { AccessibilityIcon, InfoCircledIcon } from '@radix-ui/react-icons'; -import { - Flex, - Heading, - IconButton, - Popover, - Slider, - Text, - TextField, - Tooltip, -} from '@radix-ui/themes'; -import { debounce } from 'lodash-es'; -import { useEffect, useRef, useState } from 'react'; -import { Fragment } from 'react/jsx-runtime'; -import { lerp } from 'three/src/math/MathUtils.js'; +import { Flex } from '@radix-ui/themes'; +import { useEffect } from 'react'; import { awaitableEquipmentDefinitions } from '../../../../../core/awaitables/equipmentDefinitions'; import { awaitableProvisionDefinitions } from '../../../../../core/awaitables/provisionDefinitions'; import { applyPitchYawLimits } from '../../../../../core/blitz/applyPitchYawLimits'; import { tankCharacteristics } from '../../../../../core/blitzkit/tankCharacteristics'; import { useEquipment } from '../../../../../hooks/useEquipment'; -import { useLocale } from '../../../../../hooks/useLocale'; import { useTankModelDefinition } from '../../../../../hooks/useTankModelDefinition'; import { Duel } from '../../../../../stores/duel'; import { TankopediaEphemeral } from '../../../../../stores/tankopediaEphemeral'; -import { HullTraverseVisualizer } from './components/HullTraverseVisualizer'; -import { Info } from './components/Info'; -import { InfoWithDelta } from './components/InfoWithDelta'; -import { RicochetVisualizer } from './components/RicochetVisualizer'; -import { ViewRangeVisualizer } from './components/ViewRangeVisualizer'; +import { Crew } from './components/Crew'; +import { Firepower } from './components/Firepower'; +import { Maneuverability } from './components/Maneuverability'; +import { Survivability } from './components/Survivability'; const [equipmentDefinitions, provisionDefinitions] = await Promise.all([ awaitableEquipmentDefinitions, @@ -43,26 +19,9 @@ const [equipmentDefinitions, provisionDefinitions] = await Promise.all([ ]); export function Characteristics() { - const { unwrap, strings } = useLocale(); const mutateDuel = Duel.useMutation(); const crewSkills = TankopediaEphemeral.use((state) => state.skills); - const penetrationDistanceInput = useRef(null); - const hasImprovedVentilation = useEquipment(102); - const [penetrationDistance, setPenetrationDistance] = useState(250); - const setPenetrationDistanceDebounced = debounce((value: number) => { - setPenetrationDistance(value); - }, 500); - const provisions = Duel.use((state) => state.protagonist.provisions); - const provisionCrewBonus = - provisions.reduce( - (total, provision) => - provisionDefinitions.provisions[provision].crew - ? total + provisionDefinitions.provisions[provision].crew! / 100 - : total, - 0, - ) + (hasImprovedVentilation ? 0.08 : 0); - const commanderMastery = 1 + provisionCrewBonus; const consumables = Duel.use((state) => state.protagonist.consumables); const camouflage = Duel.use((state) => state.protagonist.camouflage); const equipmentMatrix = Duel.use( @@ -107,24 +66,9 @@ export function Characteristics() { }, ); - const hasSupercharger = useEquipment(107); - const hasCalibratedShells = useEquipment(103); const hasImprovedVerticalStabilizer = useEquipment(122); const hasDownImprovedVerticalStabilizer = useEquipment(124); - const penetrationCoefficient = coefficient([ - hasCalibratedShells, - resolvePenetrationCoefficient(true, shell.type) - 1, - ]); - const penetrationLossOverDistanceCoefficient = coefficient([ - hasSupercharger, - -0.6, - ]); - useEffect(() => { - if (penetrationDistanceInput.current) { - penetrationDistanceInput.current.value = `${penetrationDistance}`; - } - }, [penetrationDistance]); useEffect(() => { mutateDuel((draft) => { [draft.protagonist.pitch, draft.protagonist.yaw] = applyPitchYawLimits( @@ -147,732 +91,13 @@ export function Characteristics() { }} > - - - - {strings.website.tools.tankopedia.firepower.title} - - - - {gun.gun_type!.value.base.shells.map((thisShell, shellIndex) => ( - - { - mutateDuel((draft) => { - draft.protagonist.shell = thisShell; - }); - }} - > - {unwrap(thisShell.name)} - - - ))} - - - - {strings.common.gun_types[gun.gun_type!.$case]} - - - {gun.gun_type!.$case === 'auto_reloader' && ( - - )} - {gun.gun_type!.$case !== 'regular' && ( - - )} - {gun.gun_type!.$case === 'auto_reloader' ? ( - <> - - {stats.mostOptimalShellIndex! + 1} - - - {stats.shellReloads!.map((reload, index) => ( - reload} - /> - ))} - - ) : ( - - )} - {(gun.gun_type!.$case === 'auto_loader' || - gun.gun_type!.$case === 'auto_reloader') && ( - - )} - - {typeof shell.penetration !== 'number' && ( - <> - - lerp( - shell.penetration.near, - shell.penetration.far, - (penetrationDistance * - penetrationLossOverDistanceCoefficient) / - 500, - ) * penetrationCoefficient - } - /> - - - { - strings.website.tools.tankopedia.characteristics.values - .at_distance_slider_label - } - - { - setPenetrationDistanceDebounced(value); - - if (!penetrationDistanceInput.current) return; - - penetrationDistanceInput.current.value = value.toString(); - }} - /> - { - setPenetrationDistance( - Math.min( - parseInt(penetrationDistanceInput.current!.value), - 500, - ), - ); - }} - onKeyDown={(event) => { - if (event.key === 'Enter') { - event.currentTarget.blur(); - } - }} - > - m - - - - )} - - {gun.gun_type!.$case !== 'regular' && ( - - )} - - {isExplosive(shell.type) && ( - - )} - - - {!isExplosive(shell.type) && ( - <> - - - - - )} - - - - - - - - - - - - - - - {gunModelDefinition.pitch.front && ( - <> - - - - )} - {gunModelDefinition.pitch.back && ( - <> - - - - )} - {turretModelDefinition.yaw && ( - <> - - - - )} - - - - - - {strings.website.tools.tankopedia.crew.title} - - - - - - - - - - - - - {strings.website.tools.tankopedia.crew.info} - - - - - - - - {tank.crew.map((member) => { - return ( - - 1 - ? ` ${literals(strings.common.units.x, [ - `${member.count}`, - ])}` - : '' - }`} - unit="%" - decimals={0} - noRanking - value={() => - (member.type === CrewType.COMMANDER - ? commanderMastery - : commanderMastery * 1.1) * 100 - } - /> - - {member.substitute.length > 0 && ( - - - - {member.substitute - .map((sub, index) => - index === 0 - ? strings.website.tools.tankopedia.crew[ - CREW_MEMBER_NAMES[sub] - ] - : sub, - ) - .join(', ')} - - - } - noRanking - value={() => commanderMastery * 1.05 * 100} - /> - )} - - ); - })} - + + - - - {strings.website.tools.tankopedia.maneuverability.title} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {strings.website.tools.tankopedia.survivability.title} - - - stats.fireChance * 100} - /> - - - - stats.camouflageStill * 100} - stats={stats} - indent - name={ - strings.website.tools.tankopedia.characteristics.values - .camouflageStill - } - decimals={2} - /> - stats.camouflageMoving * 100} - stats={stats} - indent - name={ - strings.website.tools.tankopedia.characteristics.values - .camouflageMoving - } - decimals={2} - /> - stats.camouflageShootingStill * 100} - /> - stats.camouflageShootingMoving * 100} - /> - stats.camouflageCaughtOnFire * 100} - /> - - - - - + + );