Files
blitzkit/src/app/tools/tankopedia/components/TankCard.tsx
T

185 lines
6.3 KiB
TypeScript
Raw Normal View History

2024-06-23 20:45:56 -05:00
import { Flex, Text } from '@radix-ui/themes';
import { assignInlineVars } from '@vanilla-extract/dynamic';
2024-06-24 21:00:51 -05:00
import { use, useMemo } from 'react';
import { classIcons } from '../../../../components/ClassIcon';
2024-06-23 20:45:56 -05:00
import { Link } from '../../../../components/Link';
2024-06-24 21:00:51 -05:00
import { resolveNearPenetration } from '../../../../core/blitz/resolveNearPenetration';
2024-06-21 14:03:20 -05:00
import { asset } from '../../../../core/blitzkit/asset';
2024-06-24 21:00:51 -05:00
import { modelDefinitions } from '../../../../core/blitzkit/modelDefinitions';
import { normalizeBoundingBox } from '../../../../core/blitzkit/normalizeBoundingBox';
import { resolveDpm } from '../../../../core/blitzkit/resolveDpm';
import { resolveReload } from '../../../../core/blitzkit/resolveReload';
2024-06-21 14:03:20 -05:00
import { TankDefinition } from '../../../../core/blitzkit/tankDefinitions';
import { tankIcon } from '../../../../core/blitzkit/tankIcon';
2024-06-24 21:00:51 -05:00
import { unionBoundingBox } from '../../../../core/blitzkit/unionBoundingBox';
import { useTankopediaFilters } from '../../../../stores/tankopediaFilters';
2024-06-23 20:45:56 -05:00
import * as styles from './TankCard.css';
2024-06-21 14:03:20 -05:00
interface TankCardProps {
tank: TankDefinition;
2024-06-25 19:17:53 -05:00
onSelect?: (tank: TankDefinition) => void;
2024-06-21 14:03:20 -05:00
}
2024-06-25 19:17:53 -05:00
export function TankCard({ tank, onSelect }: TankCardProps) {
2024-06-24 21:00:51 -05:00
const awaitedModelDefinitions = use(modelDefinitions);
const Icon = classIcons[tank.class];
2024-06-24 13:54:08 -05:00
// const link = useRef<HTMLAnchorElement>(null);
2024-06-24 21:00:51 -05:00
const sortBy = useTankopediaFilters((state) => state.sort.by);
const discriminator = useMemo(() => {
if (sortBy.startsWith('meta')) return undefined;
2024-06-22 16:39:31 -05:00
2024-06-24 21:00:51 -05:00
const turret = tank.turrets.at(-1)!;
const gun = turret.guns.at(-1)!;
const shell0 = gun.shells[0];
const shell1 = gun.shells[1];
const tracks = tank.tracks.at(-1)!;
const engine = tank.engines.at(-1)!;
const tankModelDefinition = awaitedModelDefinitions[tank.id];
const turretModelDefinition = tankModelDefinition.turrets[turret.id];
const gunModelDefinition = turretModelDefinition.guns[gun.id];
2024-06-22 16:39:31 -05:00
2024-06-24 21:00:51 -05:00
switch (sortBy) {
case 'fire.aimTime':
return gun.aimTime.toFixed(2);
case 'fire.caliber':
return shell0.caliber.toFixed(0);
case 'fire.damage':
return shell0.damage.armor.toFixed(0);
case 'fire.dispersionMoving':
return tracks.dispersion.move.toFixed(3);
case 'fire.dispersionStill':
return gun.dispersion.base.toFixed(3);
case 'fire.dpm':
return resolveDpm(gun, shell0).toFixed(0);
case 'fire.reload':
return resolveReload(gun).toFixed(2);
case 'fire.standardPenetration':
return resolveNearPenetration(shell0.penetration).toFixed(0);
case 'fire.premiumPenetration':
return shell1
? resolveNearPenetration(shell1.penetration).toFixed(0)
: '--';
case 'fire.shellVelocity':
return shell0.speed.toFixed(0);
case 'fire.gunDepression':
return (
gunModelDefinition.pitch.max +
(tankModelDefinition.turretRotation?.pitch ?? 0)
).toFixed(1);
case 'fire.gunElevation':
return (
-gunModelDefinition.pitch.min -
(tankModelDefinition.turretRotation?.pitch ?? 0)
).toFixed(1);
case 'maneuverability.forwardsSpeed':
return tank.speed.forwards.toFixed(0);
case 'maneuverability.backwardsSpeed':
return tank.speed.backwards.toFixed(0);
case 'maneuverability.power':
return engine.power.toFixed(0);
case 'maneuverability.powerToWeight':
return (
(1000 * engine.power) /
(tank.weight +
engine.weight +
tracks.weight +
turret.weight +
gun.weight)
).toFixed(1);
case 'maneuverability.weight':
return (
(tank.weight +
engine.weight +
tracks.weight +
turret.weight +
gun.weight) /
1000
).toFixed(1);
case 'maneuverability.traverseSpeed':
return tracks.traverseSpeed.toFixed(1);
case 'survivability.health':
return (tank.health + turret.health).toFixed(0);
case 'survivability.viewRange':
return turret.viewRange.toFixed(0);
case 'survivability.camouflageStill':
return (tank.camouflage.still * 100).toFixed(0);
case 'survivability.camouflageMoving':
return (tank.camouflage.moving * 100).toFixed(0);
case 'survivability.camouflageShooting':
return (tank.camouflage.still * gun.camouflageLoss * 100).toFixed(0);
case 'survivability.volume': {
const dimensions = normalizeBoundingBox(
unionBoundingBox(
tankModelDefinition.boundingBox,
turretModelDefinition.boundingBox,
),
);
2024-06-21 14:03:20 -05:00
2024-06-24 21:00:51 -05:00
return (dimensions[0] * dimensions[1] * dimensions[2]).toFixed(0);
}
2024-06-21 14:03:20 -05:00
2024-06-24 21:00:51 -05:00
case 'survivability.length':
return Math.max(
...normalizeBoundingBox(
unionBoundingBox(
tankModelDefinition.boundingBox,
turretModelDefinition.boundingBox,
),
),
).toFixed(0);
}
}, [sortBy]);
2024-06-21 14:03:20 -05:00
return (
<Link
2024-06-25 19:17:53 -05:00
tabIndex={onSelect ? 0 : undefined}
2024-06-21 14:03:20 -05:00
size="1"
color={
tank.treeType === 'collector'
? 'blue'
: tank.treeType === 'premium'
? 'amber'
: 'gray'
}
highContrast={tank.treeType === 'researchable'}
underline="hover"
2024-06-25 19:17:53 -05:00
href={onSelect ? undefined : `/tools/tankopedia/${tank.id}`}
onClick={onSelect ? () => onSelect(tank) : undefined}
2024-06-23 20:45:56 -05:00
className={styles.card}
style={assignInlineVars({
[styles.cardBackgroundVar]: `top left / contain no-repeat url(${asset(`flags/scratched/${tank.nation}.webp`)})`,
})}
2024-06-21 14:03:20 -05:00
>
2024-06-23 20:45:56 -05:00
<img alt={tank.name} src={tankIcon(tank.id)} className={styles.image} />
2024-06-21 14:03:20 -05:00
<Flex
justify="center"
gap="1"
align="center"
overflow="hidden"
width="100%"
maxWidth="100%"
>
2024-06-23 20:45:56 -05:00
<Icon className={styles.classIcon} />
<Text align="center" className={styles.name}>
2024-06-21 14:03:20 -05:00
{tank.name}
</Text>
</Flex>
2024-06-22 16:39:31 -05:00
2024-06-24 21:00:51 -05:00
{discriminator && (
2024-06-22 16:39:31 -05:00
<Text
mt="-2"
color="gray"
align="center"
style={{
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{discriminator}
</Text>
2024-06-24 21:00:51 -05:00
)}
2024-06-21 14:03:20 -05:00
</Link>
);
}