2024-09-22 21:27:46 -05:00
|
|
|
import {
|
|
|
|
|
Card,
|
|
|
|
|
Flex,
|
|
|
|
|
Inset,
|
|
|
|
|
Separator,
|
|
|
|
|
Text,
|
|
|
|
|
type CardProps,
|
|
|
|
|
} from '@radix-ui/themes';
|
2024-09-14 19:43:14 -05:00
|
|
|
import type { ComponentProps } from 'react';
|
2024-09-22 21:27:46 -05:00
|
|
|
import { radToDeg } from 'three/src/math/MathUtils.js';
|
2025-01-13 20:56:30 -06:00
|
|
|
import { literals } from '../../../core/i18n/literals';
|
2025-01-07 12:47:55 -06:00
|
|
|
import { useLocale } from '../../../hooks/useLocale';
|
2025-01-13 20:56:30 -06:00
|
|
|
import type { Shot, ShotLayer } from '../../../stores/tankopediaEphemeral';
|
2024-09-22 21:27:46 -05:00
|
|
|
import { shotStatusColors } from './ShotDisplay';
|
2024-06-28 20:41:24 -05:00
|
|
|
import { ArmorType } from './SpacedArmorScene';
|
|
|
|
|
|
2025-01-13 20:56:30 -06:00
|
|
|
export const layerTypeNames = {
|
|
|
|
|
[ArmorType.Primary]: 'primary',
|
|
|
|
|
[ArmorType.Spaced]: 'spaced',
|
|
|
|
|
[ArmorType.External]: 'external',
|
|
|
|
|
null: 'gap',
|
|
|
|
|
} as const;
|
2024-06-28 20:41:24 -05:00
|
|
|
|
|
|
|
|
function LayerEntry({
|
|
|
|
|
layerIndex,
|
|
|
|
|
shotStatusColor,
|
|
|
|
|
layerName,
|
|
|
|
|
layer,
|
|
|
|
|
angle,
|
|
|
|
|
}: {
|
|
|
|
|
layerIndex?: string;
|
|
|
|
|
shotStatusColor: ComponentProps<typeof Text>['color'];
|
|
|
|
|
layerName: string;
|
2024-09-22 21:12:18 -05:00
|
|
|
layer: ShotLayer;
|
2024-06-28 20:41:24 -05:00
|
|
|
angle?: number;
|
|
|
|
|
}) {
|
2025-01-13 20:56:30 -06:00
|
|
|
const { locale, strings } = useLocale();
|
2025-01-07 12:47:55 -06:00
|
|
|
|
2024-06-28 20:41:24 -05:00
|
|
|
return (
|
|
|
|
|
<Flex align="center" gap="2">
|
2024-07-29 20:18:31 -05:00
|
|
|
<Text size="1" color="gray" style={{ width: 12 }}>
|
2024-06-28 20:41:24 -05:00
|
|
|
{layerIndex}
|
|
|
|
|
</Text>
|
2024-07-29 20:46:57 -05:00
|
|
|
<Text size="2" color={shotStatusColor} style={{ width: 48 }}>
|
2024-06-28 20:41:24 -05:00
|
|
|
{layerName}
|
|
|
|
|
</Text>
|
|
|
|
|
|
|
|
|
|
{layer.type === null && (
|
|
|
|
|
<>
|
2024-07-29 20:18:31 -05:00
|
|
|
<Text size="2" color={shotStatusColor} style={{ width: 64 }}>
|
2025-01-14 14:13:00 -06:00
|
|
|
{literals(strings.common.units.mm, [
|
2025-01-13 20:56:30 -06:00
|
|
|
Math.round(layer.distance * 1000).toLocaleString(locale),
|
|
|
|
|
])}
|
2024-06-28 20:41:24 -05:00
|
|
|
</Text>
|
|
|
|
|
|
|
|
|
|
<Text size="2" color="gray">
|
2025-01-13 20:56:30 -06:00
|
|
|
{literals(
|
|
|
|
|
strings.website.tools.tankopedia.sandbox.dynamic.shot_card.stats
|
|
|
|
|
.ricochet_loss,
|
|
|
|
|
[Math.max(-100, -50 * layer.distance).toFixed(0)],
|
|
|
|
|
)}
|
2024-06-28 20:41:24 -05:00
|
|
|
</Text>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{layer.type === ArmorType.External && (
|
2024-07-29 20:18:31 -05:00
|
|
|
<Text size="2" color={shotStatusColor} style={{ width: 64 }}>
|
2025-01-14 14:13:00 -06:00
|
|
|
{literals(strings.common.units.mm, [
|
2025-01-13 20:56:30 -06:00
|
|
|
Math.round(layer.thickness).toLocaleString(locale),
|
|
|
|
|
])}
|
2024-06-28 20:41:24 -05:00
|
|
|
</Text>
|
|
|
|
|
)}
|
|
|
|
|
|
2024-07-29 20:46:57 -05:00
|
|
|
{(layer.type === ArmorType.Primary ||
|
|
|
|
|
layer.type === ArmorType.Spaced) && (
|
2024-06-28 20:41:24 -05:00
|
|
|
<>
|
2024-07-29 20:18:31 -05:00
|
|
|
<Text size="2" color={shotStatusColor} style={{ width: 80 }}>
|
2025-01-13 20:56:30 -06:00
|
|
|
{literals(
|
|
|
|
|
angle === undefined
|
2025-01-14 14:13:00 -06:00
|
|
|
? strings.common.units.mm
|
2025-01-13 20:56:30 -06:00
|
|
|
: strings.website.tools.tankopedia.sandbox.dynamic.shot_card
|
|
|
|
|
.stats.thickness_and_angle,
|
|
|
|
|
[
|
|
|
|
|
Math.round(layer.thicknessAngled).toLocaleString(locale),
|
|
|
|
|
radToDeg(angle ?? 0).toFixed(0),
|
|
|
|
|
],
|
|
|
|
|
)}
|
2024-06-28 20:41:24 -05:00
|
|
|
</Text>
|
|
|
|
|
|
|
|
|
|
<Text size="2" color="gray">
|
2025-01-13 20:56:30 -06:00
|
|
|
{literals(
|
|
|
|
|
strings.website.tools.tankopedia.sandbox.dynamic.shot_card.stats
|
|
|
|
|
.nominal,
|
|
|
|
|
[Math.round(layer.thickness).toLocaleString(locale)],
|
|
|
|
|
)}
|
2024-06-28 20:41:24 -05:00
|
|
|
</Text>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
</Flex>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-29 20:18:31 -05:00
|
|
|
interface ShotDisplayCardProps extends CardProps {
|
2024-09-22 21:12:18 -05:00
|
|
|
shot: Shot;
|
2024-07-13 19:49:00 -05:00
|
|
|
}
|
2024-06-28 20:41:24 -05:00
|
|
|
|
2024-07-29 20:18:31 -05:00
|
|
|
export function ShotDisplayCard({ shot, ...props }: ShotDisplayCardProps) {
|
2025-01-13 20:56:30 -06:00
|
|
|
const { locale, strings } = useLocale();
|
2025-01-07 12:47:55 -06:00
|
|
|
|
2024-06-28 20:41:24 -05:00
|
|
|
if (!shot) return null;
|
|
|
|
|
|
|
|
|
|
const outTitleColor = shot.out
|
|
|
|
|
? shotStatusColors[shot.out.status]
|
|
|
|
|
: undefined;
|
|
|
|
|
const inTitleColor = shotStatusColors[shot.in.status];
|
|
|
|
|
|
|
|
|
|
return (
|
2024-07-29 20:18:31 -05:00
|
|
|
<Card style={{ width: 300 }} {...props}>
|
2024-06-28 20:41:24 -05:00
|
|
|
<Flex direction="column" gap="2">
|
|
|
|
|
<Flex direction="column" gap="1">
|
|
|
|
|
<Text color={inTitleColor} weight="bold">
|
2025-01-13 20:56:30 -06:00
|
|
|
{literals(
|
|
|
|
|
strings.website.tools.tankopedia.sandbox.dynamic.shot_card.status[
|
|
|
|
|
shot.in.status
|
|
|
|
|
],
|
|
|
|
|
[Math.round(shot.damage).toLocaleString(locale)],
|
2024-06-28 20:41:24 -05:00
|
|
|
)}
|
|
|
|
|
</Text>
|
|
|
|
|
|
|
|
|
|
<Flex direction="column">
|
|
|
|
|
{shot.in.layers.map((layer, index) => {
|
|
|
|
|
const layerName =
|
2025-01-13 20:56:30 -06:00
|
|
|
strings.website.tools.tankopedia.sandbox.dynamic.shot_card
|
|
|
|
|
.element[
|
|
|
|
|
layer.type === ArmorType.External
|
|
|
|
|
? layer.variant
|
|
|
|
|
: layerTypeNames[`${layer.type}`]
|
|
|
|
|
];
|
2024-06-28 20:41:24 -05:00
|
|
|
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={
|
2024-07-29 20:46:57 -05:00
|
|
|
layer.type === ArmorType.Primary ||
|
2024-06-28 20:41:24 -05:00
|
|
|
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">
|
2025-01-13 20:56:30 -06:00
|
|
|
{literals(
|
|
|
|
|
strings.website.tools.tankopedia.sandbox.dynamic.shot_card
|
|
|
|
|
.status[shot.out.status],
|
|
|
|
|
[Math.round(shot.damage).toLocaleString(locale)],
|
|
|
|
|
)}
|
2024-06-28 20:41:24 -05:00
|
|
|
</Text>
|
|
|
|
|
|
|
|
|
|
<Flex direction="column">
|
|
|
|
|
{shot.out.layers.map((layer, index) => {
|
|
|
|
|
const layerName =
|
2025-01-13 20:56:30 -06:00
|
|
|
strings.website.tools.tankopedia.sandbox.dynamic.shot_card
|
|
|
|
|
.element[
|
|
|
|
|
layer.type === ArmorType.External
|
|
|
|
|
? layer.variant
|
|
|
|
|
: layerTypeNames[`${layer.type}`]
|
|
|
|
|
];
|
2024-06-28 20:41:24 -05:00
|
|
|
const shiftedIndex = index + shot.out!.layers.length;
|
|
|
|
|
const layerIndex =
|
|
|
|
|
layer.type === null
|
|
|
|
|
? undefined
|
2024-09-06 23:17:53 -05:00
|
|
|
: `${
|
|
|
|
|
(shot.containsGaps ? shiftedIndex / 2 : shiftedIndex) +
|
|
|
|
|
1
|
|
|
|
|
}.`;
|
2024-06-28 20:41:24 -05:00
|
|
|
const shotStatusColor =
|
|
|
|
|
shot.out!.status === 'splash'
|
|
|
|
|
? shotStatusColors.splash
|
|
|
|
|
: shotStatusColors[layer.status];
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<LayerEntry
|
|
|
|
|
key={index}
|
|
|
|
|
layer={layer}
|
|
|
|
|
layerIndex={layerIndex}
|
|
|
|
|
layerName={layerName}
|
|
|
|
|
shotStatusColor={shotStatusColor}
|
|
|
|
|
angle={
|
2024-07-29 20:46:57 -05:00
|
|
|
layer.type === ArmorType.Primary ||
|
2024-06-28 20:41:24 -05:00
|
|
|
layer.type === ArmorType.Spaced
|
|
|
|
|
? layer.angle
|
|
|
|
|
: undefined
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</Flex>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
</Flex>
|
|
|
|
|
</Card>
|
|
|
|
|
);
|
|
|
|
|
}
|