From 0136dda0dd5f8ccbaa0d8724b100a40081e5d9c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Fri, 28 Jun 2024 20:41:24 -0500 Subject: [PATCH] feat: responsive shot card --- .../components/Model/components/Options.tsx | 8 +- .../[id]/components/ShotDisplaySection.tsx | 16 ++ src/app/tools/tankopedia/[id]/page.tsx | 2 + .../Armor/components/ShotDisplay.tsx | 242 ++---------------- .../Armor/components/ShotDisplayCard.tsx | 242 ++++++++++++++++++ 5 files changed, 276 insertions(+), 234 deletions(-) create mode 100644 src/app/tools/tankopedia/[id]/components/ShotDisplaySection.tsx create mode 100644 src/components/Armor/components/ShotDisplayCard.tsx diff --git a/src/app/tools/tankopedia/[id]/components/Model/components/Options.tsx b/src/app/tools/tankopedia/[id]/components/Model/components/Options.tsx index 210ad08e8..ed9d79b1f 100644 --- a/src/app/tools/tankopedia/[id]/components/Model/components/Options.tsx +++ b/src/app/tools/tankopedia/[id]/components/Model/components/Options.tsx @@ -116,13 +116,7 @@ export function Options() { }} align="end" > - + {( resolvePenetrationCoefficient( hasCalibratedShells, diff --git a/src/app/tools/tankopedia/[id]/components/ShotDisplaySection.tsx b/src/app/tools/tankopedia/[id]/components/ShotDisplaySection.tsx new file mode 100644 index 000000000..a7f1a0b85 --- /dev/null +++ b/src/app/tools/tankopedia/[id]/components/ShotDisplaySection.tsx @@ -0,0 +1,16 @@ +import { Flex } from '@radix-ui/themes'; +import { ShotDisplayCard } from '../../../../../components/Armor/components/ShotDisplayCard'; + +export function ShotDisplaySection() { + return ( + + + + ); +} diff --git a/src/app/tools/tankopedia/[id]/page.tsx b/src/app/tools/tankopedia/[id]/page.tsx index d1b2a9191..7b39f787f 100644 --- a/src/app/tools/tankopedia/[id]/page.tsx +++ b/src/app/tools/tankopedia/[id]/page.tsx @@ -11,6 +11,7 @@ import { HistorySection } from './components/HistorySection'; import { CharacteristicsSection } from './components/Model/CharacteristicsSection'; import { HeroSection } from './components/Model/HeroSection'; import { TechTreeSection } from './components/Model/TechTreeSection'; +import { ShotDisplaySection } from './components/ShotDisplaySection'; import { VideoSection } from './components/VideoSection'; export default function Page({ params }: { params: { id: string } }) { @@ -77,6 +78,7 @@ export default function Page({ params }: { params: { id: string } }) { {assigned && ( <> + {!exempt && } {!exempt && } diff --git a/src/components/Armor/components/ShotDisplay.tsx b/src/components/Armor/components/ShotDisplay.tsx index 2569378b9..877eec688 100644 --- a/src/components/Armor/components/ShotDisplay.tsx +++ b/src/components/Armor/components/ShotDisplay.tsx @@ -1,39 +1,17 @@ -import { Card, Flex, Inset, Separator, Text } from '@radix-ui/themes'; +import { Box, Flex, Text } from '@radix-ui/themes'; import { Html } from '@react-three/drei'; import { useFrame } from '@react-three/fiber'; import { ComponentProps, useRef } from 'react'; import { Euler, Mesh, Quaternion } from 'three'; -import { radToDeg } from 'three/src/math/MathUtils'; import { J_HAT } from '../../../constants/axis'; import { - ShotLayer, ShotLayerNonExternal, ShotStatus, useTankopediaTemporary, } from '../../../stores/tankopedia'; -import { ArmorType } from './SpacedArmorScene'; -import { ExternalModuleVariant } from './SpacedArmorSceneComponent'; +import { ShotDisplayCard } from './ShotDisplayCard'; -const layerTypeNames: Record = { - [ArmorType.Core]: 'Core', - [ArmorType.Spaced]: 'Spaced', - [ArmorType.External]: 'External', - null: 'Gap', -}; - -const externalLayerNames: Record = { - gun: 'Gun', - track: 'Tracks', -}; - -const shotStatusNames: Record = { - blocked: 'Blocked', - ricochet: 'Ricochet', - penetration: 'penetration', - splash: 'splash', -}; - -const shotStatusColors: Record< +export const shotStatusColors: Record< ShotStatus, ComponentProps['color'] > = { @@ -44,93 +22,10 @@ const shotStatusColors: Record< }; const SURFACE_POINT_SIZE = 12; -const LENGTH_INFINITY = 4; +export const SHOT_DISPLAY_LENGTH_INFINITY = 4; const TRACER_THICK = 1 / 32; const TRACER_THIN = TRACER_THICK / 2; -function LayerEntry({ - layerIndex, - shotStatusColor, - layerName, - layer, - angle, -}: { - layerIndex?: string; - shotStatusColor: ComponentProps['color']; - layerName: string; - layer: ShotLayer; - angle?: number; -}) { - return ( - - - {layerIndex} - - - {layerName} - - - {layer.type === null && ( - <> - - {Math.round(layer.distance * 1000).toLocaleString()} - mm - - - - {Math.max(-100, Math.round(-50 * layer.distance))}% penetration - - - )} - - {layer.type === ArmorType.External && ( - - {Math.round(layer.thickness).toLocaleString()} - mm - - )} - - {(layer.type === ArmorType.Core || layer.type === ArmorType.Spaced) && ( - <> - - {Math.round(layer.thicknessAngled).toLocaleString()} - {`mm${angle === undefined ? '' : ` ${Math.round(radToDeg(angle))}°`}`} - - - - {Math.round(layer.thickness).toLocaleString()} - mm nominal - - - )} - - ); -} - export function ShotDisplay() { const shot = useTankopediaTemporary((state) => state.shot); const inTracer = useRef(null); @@ -157,13 +52,13 @@ export function ShotDisplay() { shot.in.layers.reduce((accumulator, layer) => { if (layer.type === null) return accumulator + layer.distance; return accumulator; - }, 0) + LENGTH_INFINITY; + }, 0) + SHOT_DISPLAY_LENGTH_INFINITY; const outLength = shot.out && shot.out.layers.length > 0 ? (shot.in.layers.at(-1) as ShotLayerNonExternal).point.distanceTo( (shot.out.layers.at(-1) as ShotLayerNonExternal).point, ) - : LENGTH_INFINITY; + : SHOT_DISPLAY_LENGTH_INFINITY; const inLast = shot.in.layers.findLast( (layer) => layer.type !== null, ) as ShotLayerNonExternal; @@ -181,122 +76,15 @@ export function ShotDisplay() { transform: 'translateY(-50%)', }} > - - - - - {shot.in.status !== 'blocked' && - shot.in.status !== 'ricochet' && ( - <> - {Math.round(shot.damage).toLocaleString()} - hp - - )} - {` ${shotStatusNames[shot.in.status]} `} - - - - {shot.in.layers.map((layer, index) => { - const layerName = - layer.type === ArmorType.External - ? externalLayerNames[layer.variant] - : layerTypeNames[`${layer.type}`]; - const layerIndex = - layer.type === null - ? undefined - : `${(shot.containsGaps ? index / 2 : index) + 1}.`; - const shotStatusColor = - shot.in.status === 'splash' - ? shotStatusColors.splash - : shotStatusColors[layer.status]; - - return ( - - ); - })} - - - - {shot.out && shot.out.layers.length > 0 && ( - <> - - - - - ricochet (-25% penetration) - - - - - - - {shot.out.status !== 'blocked' && - shot.out.status !== 'ricochet' && ( - <> - {Math.round(shot.damage).toLocaleString()} - hp - - )} - {` ${shotStatusNames[shot.out.status]} `} - - - - {shot.out.layers.map((layer, index) => { - const layerName = - layer.type === ArmorType.External - ? externalLayerNames[layer.variant] - : layerTypeNames[`${layer.type}`]; - const shiftedIndex = index + shot.out!.layers.length; - const layerIndex = - layer.type === null - ? undefined - : `${(shot.containsGaps ? shiftedIndex / 2 : shiftedIndex) + 1}.`; - const shotStatusColor = - shot.out!.status === 'splash' - ? shotStatusColors.splash - : shotStatusColors[layer.status]; - - return ( - - ); - })} - - - )} - - + + + {[...shot.in.layers, ...(shot.out?.layers ?? [])].map((layer, index) => { diff --git a/src/components/Armor/components/ShotDisplayCard.tsx b/src/components/Armor/components/ShotDisplayCard.tsx new file mode 100644 index 000000000..3b67d740a --- /dev/null +++ b/src/components/Armor/components/ShotDisplayCard.tsx @@ -0,0 +1,242 @@ +import { Card, Flex, Inset, Separator, Text } from '@radix-ui/themes'; +import { ComponentProps } from 'react'; +import { radToDeg } from 'three/src/math/MathUtils'; +import { + ShotLayer, + ShotStatus, + useTankopediaTemporary, +} from '../../../stores/tankopedia'; +import { shotStatusColors } from './ShotDisplay'; +import { ArmorType } from './SpacedArmorScene'; +import { ExternalModuleVariant } from './SpacedArmorSceneComponent'; + +const layerTypeNames: Record = { + [ArmorType.Core]: 'Core', + [ArmorType.Spaced]: 'Spaced', + [ArmorType.External]: 'External', + null: 'Gap', +}; + +const externalLayerNames: Record = { + gun: 'Gun', + track: 'Tracks', +}; + +const shotStatusNames: Record = { + blocked: 'Blocked', + ricochet: 'Ricochet', + penetration: 'penetration', + splash: 'splash', +}; + +function LayerEntry({ + layerIndex, + shotStatusColor, + layerName, + layer, + angle, +}: { + layerIndex?: string; + shotStatusColor: ComponentProps['color']; + layerName: string; + layer: ShotLayer; + angle?: number; +}) { + return ( + + + {layerIndex} + + + {layerName} + + + {layer.type === null && ( + <> + + {Math.round(layer.distance * 1000).toLocaleString()} + mm + + + + {Math.max(-100, Math.round(-50 * layer.distance))}% penetration + + + )} + + {layer.type === ArmorType.External && ( + + {Math.round(layer.thickness).toLocaleString()} + mm + + )} + + {(layer.type === ArmorType.Core || layer.type === ArmorType.Spaced) && ( + <> + + {Math.round(layer.thicknessAngled).toLocaleString()} + {`mm${angle === undefined ? '' : ` ${Math.round(radToDeg(angle))}°`}`} + + + + {Math.round(layer.thickness).toLocaleString()} + mm nominal + + + )} + + ); +} + +export function ShotDisplayCard() { + const shot = useTankopediaTemporary((state) => state.shot); + + if (!shot) return null; + + const outTitleColor = shot.out + ? shotStatusColors[shot.out.status] + : undefined; + const inTitleColor = shotStatusColors[shot.in.status]; + + return ( + + + + + {shot.in.status !== 'blocked' && shot.in.status !== 'ricochet' && ( + <> + {Math.round(shot.damage).toLocaleString()} + hp + + )} + {` ${shotStatusNames[shot.in.status]} `} + + + + {shot.in.layers.map((layer, index) => { + const layerName = + layer.type === ArmorType.External + ? externalLayerNames[layer.variant] + : layerTypeNames[`${layer.type}`]; + const layerIndex = + layer.type === null + ? undefined + : `${(shot.containsGaps ? index / 2 : index) + 1}.`; + const shotStatusColor = + shot.in.status === 'splash' + ? shotStatusColors.splash + : shotStatusColors[layer.status]; + + return ( + + ); + })} + + + + {shot.out && shot.out.layers.length > 0 && ( + <> + + + + + ricochet (-25% penetration) + + + + + + + {shot.out.status !== 'blocked' && + shot.out.status !== 'ricochet' && ( + <> + {Math.round(shot.damage).toLocaleString()} + hp + + )} + {` ${shotStatusNames[shot.out.status]} `} + + + + {shot.out.layers.map((layer, index) => { + const layerName = + layer.type === ArmorType.External + ? externalLayerNames[layer.variant] + : layerTypeNames[`${layer.type}`]; + const shiftedIndex = index + shot.out!.layers.length; + const layerIndex = + layer.type === null + ? undefined + : `${(shot.containsGaps ? shiftedIndex / 2 : shiftedIndex) + 1}.`; + const shotStatusColor = + shot.out!.status === 'splash' + ? shotStatusColors.splash + : shotStatusColors[layer.status]; + + return ( + + ); + })} + + + )} + + + ); +}