mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 20:19:49 +02:00
feat: responsive shot card
This commit is contained in:
1 parent
8d2ec5f5b4
commit
0136dda0dd
5 files changed
+276
-234
No files matched your search
@@ -116,13 +116,7 @@ export function Options() {
|
|||||||
}}
|
}}
|
||||||
align="end"
|
align="end"
|
||||||
>
|
>
|
||||||
<Text
|
<Text color="gray" size={{ initial: '1', sm: '2' }}>
|
||||||
color="gray"
|
|
||||||
size={{
|
|
||||||
initial: '1',
|
|
||||||
sm: '2',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{(
|
{(
|
||||||
resolvePenetrationCoefficient(
|
resolvePenetrationCoefficient(
|
||||||
hasCalibratedShells,
|
hasCalibratedShells,
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { Flex } from '@radix-ui/themes';
|
||||||
|
import { ShotDisplayCard } from '../../../../../components/Armor/components/ShotDisplayCard';
|
||||||
|
|
||||||
|
export function ShotDisplaySection() {
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
justify="center"
|
||||||
|
display={{
|
||||||
|
initial: 'flex',
|
||||||
|
sm: 'none',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ShotDisplayCard />
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import { HistorySection } from './components/HistorySection';
|
|||||||
import { CharacteristicsSection } from './components/Model/CharacteristicsSection';
|
import { CharacteristicsSection } from './components/Model/CharacteristicsSection';
|
||||||
import { HeroSection } from './components/Model/HeroSection';
|
import { HeroSection } from './components/Model/HeroSection';
|
||||||
import { TechTreeSection } from './components/Model/TechTreeSection';
|
import { TechTreeSection } from './components/Model/TechTreeSection';
|
||||||
|
import { ShotDisplaySection } from './components/ShotDisplaySection';
|
||||||
import { VideoSection } from './components/VideoSection';
|
import { VideoSection } from './components/VideoSection';
|
||||||
|
|
||||||
export default function Page({ params }: { params: { id: string } }) {
|
export default function Page({ params }: { params: { id: string } }) {
|
||||||
@@ -77,6 +78,7 @@ export default function Page({ params }: { params: { id: string } }) {
|
|||||||
{assigned && (
|
{assigned && (
|
||||||
<>
|
<>
|
||||||
<HeroSection />
|
<HeroSection />
|
||||||
|
<ShotDisplaySection />
|
||||||
{!exempt && <AdMidSectionResponsive />}
|
{!exempt && <AdMidSectionResponsive />}
|
||||||
<CharacteristicsSection />
|
<CharacteristicsSection />
|
||||||
{!exempt && <AdMidSectionResponsive />}
|
{!exempt && <AdMidSectionResponsive />}
|
||||||
|
|||||||
@@ -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 { Html } from '@react-three/drei';
|
||||||
import { useFrame } from '@react-three/fiber';
|
import { useFrame } from '@react-three/fiber';
|
||||||
import { ComponentProps, useRef } from 'react';
|
import { ComponentProps, useRef } from 'react';
|
||||||
import { Euler, Mesh, Quaternion } from 'three';
|
import { Euler, Mesh, Quaternion } from 'three';
|
||||||
import { radToDeg } from 'three/src/math/MathUtils';
|
|
||||||
import { J_HAT } from '../../../constants/axis';
|
import { J_HAT } from '../../../constants/axis';
|
||||||
import {
|
import {
|
||||||
ShotLayer,
|
|
||||||
ShotLayerNonExternal,
|
ShotLayerNonExternal,
|
||||||
ShotStatus,
|
ShotStatus,
|
||||||
useTankopediaTemporary,
|
useTankopediaTemporary,
|
||||||
} from '../../../stores/tankopedia';
|
} from '../../../stores/tankopedia';
|
||||||
import { ArmorType } from './SpacedArmorScene';
|
import { ShotDisplayCard } from './ShotDisplayCard';
|
||||||
import { ExternalModuleVariant } from './SpacedArmorSceneComponent';
|
|
||||||
|
|
||||||
const layerTypeNames: Record<ArmorType | 'null', string> = {
|
export const shotStatusColors: Record<
|
||||||
[ArmorType.Core]: 'Core',
|
|
||||||
[ArmorType.Spaced]: 'Spaced',
|
|
||||||
[ArmorType.External]: 'External',
|
|
||||||
null: 'Gap',
|
|
||||||
};
|
|
||||||
|
|
||||||
const externalLayerNames: Record<ExternalModuleVariant, string> = {
|
|
||||||
gun: 'Gun',
|
|
||||||
track: 'Tracks',
|
|
||||||
};
|
|
||||||
|
|
||||||
const shotStatusNames: Record<ShotStatus, string> = {
|
|
||||||
blocked: 'Blocked',
|
|
||||||
ricochet: 'Ricochet',
|
|
||||||
penetration: 'penetration',
|
|
||||||
splash: 'splash',
|
|
||||||
};
|
|
||||||
|
|
||||||
const shotStatusColors: Record<
|
|
||||||
ShotStatus,
|
ShotStatus,
|
||||||
ComponentProps<typeof Text>['color']
|
ComponentProps<typeof Text>['color']
|
||||||
> = {
|
> = {
|
||||||
@@ -44,93 +22,10 @@ const shotStatusColors: Record<
|
|||||||
};
|
};
|
||||||
|
|
||||||
const SURFACE_POINT_SIZE = 12;
|
const SURFACE_POINT_SIZE = 12;
|
||||||
const LENGTH_INFINITY = 4;
|
export const SHOT_DISPLAY_LENGTH_INFINITY = 4;
|
||||||
const TRACER_THICK = 1 / 32;
|
const TRACER_THICK = 1 / 32;
|
||||||
const TRACER_THIN = TRACER_THICK / 2;
|
const TRACER_THIN = TRACER_THICK / 2;
|
||||||
|
|
||||||
function LayerEntry({
|
|
||||||
layerIndex,
|
|
||||||
shotStatusColor,
|
|
||||||
layerName,
|
|
||||||
layer,
|
|
||||||
angle,
|
|
||||||
}: {
|
|
||||||
layerIndex?: string;
|
|
||||||
shotStatusColor: ComponentProps<typeof Text>['color'];
|
|
||||||
layerName: string;
|
|
||||||
layer: ShotLayer;
|
|
||||||
angle?: number;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<Flex align="center" gap="2">
|
|
||||||
<Text
|
|
||||||
size="1"
|
|
||||||
color="gray"
|
|
||||||
style={{
|
|
||||||
width: 12,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{layerIndex}
|
|
||||||
</Text>
|
|
||||||
<Text size="2" color={shotStatusColor} style={{ width: 47 }}>
|
|
||||||
{layerName}
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
{layer.type === null && (
|
|
||||||
<>
|
|
||||||
<Text
|
|
||||||
size="2"
|
|
||||||
color={shotStatusColor}
|
|
||||||
style={{
|
|
||||||
width: 64,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{Math.round(layer.distance * 1000).toLocaleString()}
|
|
||||||
<Text size="1">mm</Text>
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Text size="2" color="gray">
|
|
||||||
{Math.max(-100, Math.round(-50 * layer.distance))}% penetration
|
|
||||||
</Text>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{layer.type === ArmorType.External && (
|
|
||||||
<Text
|
|
||||||
size="2"
|
|
||||||
color={shotStatusColor}
|
|
||||||
style={{
|
|
||||||
width: 64,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{Math.round(layer.thickness).toLocaleString()}
|
|
||||||
<Text size="1">mm</Text>
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{(layer.type === ArmorType.Core || layer.type === ArmorType.Spaced) && (
|
|
||||||
<>
|
|
||||||
<Text
|
|
||||||
size="2"
|
|
||||||
color={shotStatusColor}
|
|
||||||
style={{
|
|
||||||
width: 80,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{Math.round(layer.thicknessAngled).toLocaleString()}
|
|
||||||
<Text size="1">{`mm${angle === undefined ? '' : ` ${Math.round(radToDeg(angle))}°`}`}</Text>
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Text size="2" color="gray">
|
|
||||||
{Math.round(layer.thickness).toLocaleString()}
|
|
||||||
<Text size="1">mm</Text> nominal
|
|
||||||
</Text>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ShotDisplay() {
|
export function ShotDisplay() {
|
||||||
const shot = useTankopediaTemporary((state) => state.shot);
|
const shot = useTankopediaTemporary((state) => state.shot);
|
||||||
const inTracer = useRef<Mesh>(null);
|
const inTracer = useRef<Mesh>(null);
|
||||||
@@ -157,13 +52,13 @@ export function ShotDisplay() {
|
|||||||
shot.in.layers.reduce((accumulator, layer) => {
|
shot.in.layers.reduce((accumulator, layer) => {
|
||||||
if (layer.type === null) return accumulator + layer.distance;
|
if (layer.type === null) return accumulator + layer.distance;
|
||||||
return accumulator;
|
return accumulator;
|
||||||
}, 0) + LENGTH_INFINITY;
|
}, 0) + SHOT_DISPLAY_LENGTH_INFINITY;
|
||||||
const outLength =
|
const outLength =
|
||||||
shot.out && shot.out.layers.length > 0
|
shot.out && shot.out.layers.length > 0
|
||||||
? (shot.in.layers.at(-1) as ShotLayerNonExternal).point.distanceTo(
|
? (shot.in.layers.at(-1) as ShotLayerNonExternal).point.distanceTo(
|
||||||
(shot.out.layers.at(-1) as ShotLayerNonExternal).point,
|
(shot.out.layers.at(-1) as ShotLayerNonExternal).point,
|
||||||
)
|
)
|
||||||
: LENGTH_INFINITY;
|
: SHOT_DISPLAY_LENGTH_INFINITY;
|
||||||
const inLast = shot.in.layers.findLast(
|
const inLast = shot.in.layers.findLast(
|
||||||
(layer) => layer.type !== null,
|
(layer) => layer.type !== null,
|
||||||
) as ShotLayerNonExternal;
|
) as ShotLayerNonExternal;
|
||||||
@@ -181,122 +76,15 @@ export function ShotDisplay() {
|
|||||||
transform: 'translateY(-50%)',
|
transform: 'translateY(-50%)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Card ml="9" style={{ width: 300 }}>
|
<Box
|
||||||
<Flex direction="column" gap="2">
|
pl="9"
|
||||||
<Flex direction="column" gap="1">
|
display={{
|
||||||
<Text color={inTitleColor} weight="bold">
|
initial: 'none',
|
||||||
{shot.in.status !== 'blocked' &&
|
sm: 'block',
|
||||||
shot.in.status !== 'ricochet' && (
|
}}
|
||||||
<>
|
>
|
||||||
{Math.round(shot.damage).toLocaleString()}
|
<ShotDisplayCard />
|
||||||
<Text size="1">hp</Text>
|
</Box>
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{` ${shotStatusNames[shot.in.status]} `}
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Flex direction="column">
|
|
||||||
{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 (
|
|
||||||
<LayerEntry
|
|
||||||
key={index}
|
|
||||||
layer={layer}
|
|
||||||
layerIndex={layerIndex}
|
|
||||||
layerName={layerName}
|
|
||||||
shotStatusColor={shotStatusColor}
|
|
||||||
angle={
|
|
||||||
layer.type === ArmorType.Core ||
|
|
||||||
layer.type === ArmorType.Spaced
|
|
||||||
? layer.angle
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
{shot.out && shot.out.layers.length > 0 && (
|
|
||||||
<>
|
|
||||||
<Inset side="x">
|
|
||||||
<Flex align="center" gap="2">
|
|
||||||
<Separator
|
|
||||||
style={{
|
|
||||||
flex: 1,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Text size="1" color="gray">
|
|
||||||
ricochet (-25% penetration)
|
|
||||||
</Text>
|
|
||||||
<Separator
|
|
||||||
style={{
|
|
||||||
flex: 1,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
</Inset>
|
|
||||||
|
|
||||||
<Text color={outTitleColor} weight="bold">
|
|
||||||
{shot.out.status !== 'blocked' &&
|
|
||||||
shot.out.status !== 'ricochet' && (
|
|
||||||
<>
|
|
||||||
{Math.round(shot.damage).toLocaleString()}
|
|
||||||
<Text size="1">hp</Text>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{` ${shotStatusNames[shot.out.status]} `}
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Flex direction="column">
|
|
||||||
{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 (
|
|
||||||
<LayerEntry
|
|
||||||
key={index}
|
|
||||||
layer={layer}
|
|
||||||
layerIndex={layerIndex}
|
|
||||||
layerName={layerName}
|
|
||||||
shotStatusColor={shotStatusColor}
|
|
||||||
angle={
|
|
||||||
layer.type === ArmorType.Core ||
|
|
||||||
layer.type === ArmorType.Spaced
|
|
||||||
? layer.angle
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Flex>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
</Card>
|
|
||||||
</Html>
|
</Html>
|
||||||
|
|
||||||
{[...shot.in.layers, ...(shot.out?.layers ?? [])].map((layer, index) => {
|
{[...shot.in.layers, ...(shot.out?.layers ?? [])].map((layer, index) => {
|
||||||
|
|||||||
@@ -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 | 'null', string> = {
|
||||||
|
[ArmorType.Core]: 'Core',
|
||||||
|
[ArmorType.Spaced]: 'Spaced',
|
||||||
|
[ArmorType.External]: 'External',
|
||||||
|
null: 'Gap',
|
||||||
|
};
|
||||||
|
|
||||||
|
const externalLayerNames: Record<ExternalModuleVariant, string> = {
|
||||||
|
gun: 'Gun',
|
||||||
|
track: 'Tracks',
|
||||||
|
};
|
||||||
|
|
||||||
|
const shotStatusNames: Record<ShotStatus, string> = {
|
||||||
|
blocked: 'Blocked',
|
||||||
|
ricochet: 'Ricochet',
|
||||||
|
penetration: 'penetration',
|
||||||
|
splash: 'splash',
|
||||||
|
};
|
||||||
|
|
||||||
|
function LayerEntry({
|
||||||
|
layerIndex,
|
||||||
|
shotStatusColor,
|
||||||
|
layerName,
|
||||||
|
layer,
|
||||||
|
angle,
|
||||||
|
}: {
|
||||||
|
layerIndex?: string;
|
||||||
|
shotStatusColor: ComponentProps<typeof Text>['color'];
|
||||||
|
layerName: string;
|
||||||
|
layer: ShotLayer;
|
||||||
|
angle?: number;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Flex align="center" gap="2">
|
||||||
|
<Text
|
||||||
|
size="1"
|
||||||
|
color="gray"
|
||||||
|
style={{
|
||||||
|
width: 12,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{layerIndex}
|
||||||
|
</Text>
|
||||||
|
<Text size="2" color={shotStatusColor} style={{ width: 47 }}>
|
||||||
|
{layerName}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
{layer.type === null && (
|
||||||
|
<>
|
||||||
|
<Text
|
||||||
|
size="2"
|
||||||
|
color={shotStatusColor}
|
||||||
|
style={{
|
||||||
|
width: 64,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Math.round(layer.distance * 1000).toLocaleString()}
|
||||||
|
<Text size="1">mm</Text>
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Text size="2" color="gray">
|
||||||
|
{Math.max(-100, Math.round(-50 * layer.distance))}% penetration
|
||||||
|
</Text>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{layer.type === ArmorType.External && (
|
||||||
|
<Text
|
||||||
|
size="2"
|
||||||
|
color={shotStatusColor}
|
||||||
|
style={{
|
||||||
|
width: 64,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Math.round(layer.thickness).toLocaleString()}
|
||||||
|
<Text size="1">mm</Text>
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{(layer.type === ArmorType.Core || layer.type === ArmorType.Spaced) && (
|
||||||
|
<>
|
||||||
|
<Text
|
||||||
|
size="2"
|
||||||
|
color={shotStatusColor}
|
||||||
|
style={{
|
||||||
|
width: 80,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Math.round(layer.thicknessAngled).toLocaleString()}
|
||||||
|
<Text size="1">{`mm${angle === undefined ? '' : ` ${Math.round(radToDeg(angle))}°`}`}</Text>
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Text size="2" color="gray">
|
||||||
|
{Math.round(layer.thickness).toLocaleString()}
|
||||||
|
<Text size="1">mm</Text> nominal
|
||||||
|
</Text>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<Card style={{ width: 300 }}>
|
||||||
|
<Flex direction="column" gap="2">
|
||||||
|
<Flex direction="column" gap="1">
|
||||||
|
<Text color={inTitleColor} weight="bold">
|
||||||
|
{shot.in.status !== 'blocked' && shot.in.status !== 'ricochet' && (
|
||||||
|
<>
|
||||||
|
{Math.round(shot.damage).toLocaleString()}
|
||||||
|
<Text size="1">hp</Text>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{` ${shotStatusNames[shot.in.status]} `}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Flex direction="column">
|
||||||
|
{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 (
|
||||||
|
<LayerEntry
|
||||||
|
key={index}
|
||||||
|
layer={layer}
|
||||||
|
layerIndex={layerIndex}
|
||||||
|
layerName={layerName}
|
||||||
|
shotStatusColor={shotStatusColor}
|
||||||
|
angle={
|
||||||
|
layer.type === ArmorType.Core ||
|
||||||
|
layer.type === ArmorType.Spaced
|
||||||
|
? layer.angle
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
{shot.out && shot.out.layers.length > 0 && (
|
||||||
|
<>
|
||||||
|
<Inset side="x">
|
||||||
|
<Flex align="center" gap="2">
|
||||||
|
<Separator
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Text size="1" color="gray">
|
||||||
|
ricochet (-25% penetration)
|
||||||
|
</Text>
|
||||||
|
<Separator
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
</Inset>
|
||||||
|
|
||||||
|
<Text color={outTitleColor} weight="bold">
|
||||||
|
{shot.out.status !== 'blocked' &&
|
||||||
|
shot.out.status !== 'ricochet' && (
|
||||||
|
<>
|
||||||
|
{Math.round(shot.damage).toLocaleString()}
|
||||||
|
<Text size="1">hp</Text>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{` ${shotStatusNames[shot.out.status]} `}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Flex direction="column">
|
||||||
|
{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 (
|
||||||
|
<LayerEntry
|
||||||
|
key={index}
|
||||||
|
layer={layer}
|
||||||
|
layerIndex={layerIndex}
|
||||||
|
layerName={layerName}
|
||||||
|
shotStatusColor={shotStatusColor}
|
||||||
|
angle={
|
||||||
|
layer.type === ArmorType.Core ||
|
||||||
|
layer.type === ArmorType.Spaced
|
||||||
|
? layer.angle
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Flex>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user