mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-27 04:29:50 +01:00
feat: ricochet visualizer shell switcher
This commit is contained in:
1 parent
47a60adac7
commit
af08df1526
5 files changed
+365
-272
No files matched your search
@@ -892,7 +892,8 @@
|
|||||||
"angle": "Angle",
|
"angle": "Angle",
|
||||||
"normalization": "Normalization",
|
"normalization": "Normalization",
|
||||||
"effective_angle": "Effective angle",
|
"effective_angle": "Effective angle",
|
||||||
"armor": "Armor"
|
"armor": "Armor",
|
||||||
|
"no_ricochet": "Explosive shells don't ricochet"
|
||||||
},
|
},
|
||||||
|
|
||||||
"view_range": {
|
"view_range": {
|
||||||
|
|||||||
+41
-34
@@ -65,39 +65,44 @@ export function Firepower({ stats }: StatsAcceptorProps) {
|
|||||||
</Heading>
|
</Heading>
|
||||||
|
|
||||||
<Flex>
|
<Flex>
|
||||||
{gun.gun_type!.value.base.shells.map((thisShell, shellIndex) => (
|
{gun.gun_type!.value.base.shells.map((thisShell, shellIndex) => {
|
||||||
<Tooltip content={unwrap(thisShell.name)} key={thisShell.id}>
|
const selected = thisShell.id === shell.id;
|
||||||
<IconButton
|
|
||||||
color={thisShell.id === shell.id ? undefined : 'gray'}
|
return (
|
||||||
variant="soft"
|
<Tooltip content={unwrap(thisShell.name)} key={thisShell.id}>
|
||||||
style={{
|
<IconButton
|
||||||
borderTopLeftRadius: shellIndex === 0 ? undefined : 0,
|
color={selected ? undefined : 'gray'}
|
||||||
borderBottomLeftRadius: shellIndex === 0 ? undefined : 0,
|
variant={selected ? 'solid' : 'soft'}
|
||||||
borderTopRightRadius:
|
highContrast={selected}
|
||||||
shellIndex === gun.gun_type!.value.base.shells.length - 1
|
style={{
|
||||||
? undefined
|
borderTopLeftRadius: shellIndex === 0 ? undefined : 0,
|
||||||
: 0,
|
borderBottomLeftRadius: shellIndex === 0 ? undefined : 0,
|
||||||
borderBottomRightRadius:
|
borderTopRightRadius:
|
||||||
shellIndex === gun.gun_type!.value.base.shells.length - 1
|
shellIndex === gun.gun_type!.value.base.shells.length - 1
|
||||||
? undefined
|
? undefined
|
||||||
: 0,
|
: 0,
|
||||||
marginLeft: shellIndex === 0 ? 0 : -1,
|
borderBottomRightRadius:
|
||||||
}}
|
shellIndex === gun.gun_type!.value.base.shells.length - 1
|
||||||
onClick={() => {
|
? undefined
|
||||||
mutateDuel((draft) => {
|
: 0,
|
||||||
draft.protagonist.shell = thisShell;
|
marginLeft: shellIndex === 0 ? 0 : -1,
|
||||||
});
|
}}
|
||||||
}}
|
onClick={() => {
|
||||||
>
|
mutateDuel((draft) => {
|
||||||
<img
|
draft.protagonist.shell = thisShell;
|
||||||
alt={unwrap(thisShell.name)}
|
});
|
||||||
width={16}
|
}}
|
||||||
height={16}
|
>
|
||||||
src={asset(`icons/shells/${thisShell.icon}.webp`)}
|
<img
|
||||||
/>
|
alt={unwrap(thisShell.name)}
|
||||||
</IconButton>
|
width={16}
|
||||||
</Tooltip>
|
height={16}
|
||||||
))}
|
src={asset(`icons/shells/${thisShell.icon}.webp`)}
|
||||||
|
/>
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
@@ -265,9 +270,11 @@ export function Firepower({ stats }: StatsAcceptorProps) {
|
|||||||
deltaType="lowerIsBetter"
|
deltaType="lowerIsBetter"
|
||||||
value="shellRicochet"
|
value="shellRicochet"
|
||||||
/>
|
/>
|
||||||
<RicochetVisualizer stats={stats} />
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<RicochetVisualizer stats={stats} />
|
||||||
|
|
||||||
<InfoWithDelta stats={stats} decimals={0} value="shellVelocity" />
|
<InfoWithDelta stats={stats} decimals={0} value="shellVelocity" />
|
||||||
<InfoWithDelta stats={stats} decimals={0} value="shellRange" />
|
<InfoWithDelta stats={stats} decimals={0} value="shellRange" />
|
||||||
<InfoWithDelta stats={stats} decimals={0} value="shellCapacity" />
|
<InfoWithDelta stats={stats} decimals={0} value="shellCapacity" />
|
||||||
|
|||||||
-237
@@ -1,237 +0,0 @@
|
|||||||
import { asset } from '@blitzkit/core';
|
|
||||||
import { literals } from '@blitzkit/i18n';
|
|
||||||
import { Box, Card, Code, Flex, Text } from '@radix-ui/themes';
|
|
||||||
import { useRef, useState } from 'react';
|
|
||||||
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 { StatsAcceptorProps } from './HullTraverseVisualizer';
|
|
||||||
|
|
||||||
export function RicochetVisualizer({ stats }: StatsAcceptorProps) {
|
|
||||||
const shell = Duel.use((state) => state.protagonist.shell);
|
|
||||||
const container = useRef<HTMLDivElement>(null);
|
|
||||||
const normalization = degToRad(stats.shellNormalization);
|
|
||||||
const ricochet = degToRad(stats.shellRicochet ?? 90);
|
|
||||||
const [angle, setAngle] = useState(degToRad(-25));
|
|
||||||
const doesRicochet = Math.abs(angle) >= ricochet;
|
|
||||||
const effectiveAngle = doesRicochet
|
|
||||||
? -angle + Math.PI
|
|
||||||
: Math.sign(angle) * Math.max(0, Math.abs(angle) - normalization);
|
|
||||||
const { strings } = useLocale();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card
|
|
||||||
variant="classic"
|
|
||||||
style={{
|
|
||||||
aspectRatio: '1 / 1',
|
|
||||||
touchAction: 'none',
|
|
||||||
}}
|
|
||||||
mb="6"
|
|
||||||
ref={container}
|
|
||||||
onPointerMove={(event) => {
|
|
||||||
if (!container.current) return;
|
|
||||||
|
|
||||||
const rect = container.current.getBoundingClientRect();
|
|
||||||
const x = event.clientX - rect.left - rect.width / 3;
|
|
||||||
const y = event.clientY - rect.top - rect.height / 2;
|
|
||||||
const angle = clamp(Math.atan2(-y, x), -Math.PI / 2, Math.PI / 2);
|
|
||||||
|
|
||||||
setAngle(angle);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
width="33%"
|
|
||||||
height="100%"
|
|
||||||
position="absolute"
|
|
||||||
top="0"
|
|
||||||
left="0"
|
|
||||||
style={{
|
|
||||||
background: `linear-gradient(to right, transparent, ${Var('gray-a4')})`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
width="100%"
|
|
||||||
height="100%"
|
|
||||||
style={{
|
|
||||||
opacity: 2 ** -3,
|
|
||||||
backgroundImage: `url(/assets/images/tankopedia/visualizers/ricochet/armor-hash.png)`,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box
|
|
||||||
left="33%"
|
|
||||||
top="0"
|
|
||||||
width="67%"
|
|
||||||
height="100%"
|
|
||||||
position="absolute"
|
|
||||||
style={{
|
|
||||||
background: `conic-gradient(
|
|
||||||
at 0 50%,
|
|
||||||
|
|
||||||
${Var('ruby-a2')},
|
|
||||||
${Var('ruby-a1')} ${Math.PI / 2 - ricochet}rad,
|
|
||||||
|
|
||||||
${Var('jade-a2')} ${Math.PI / 2 - ricochet}rad,
|
|
||||||
${Var('jade-a4')} ${Math.PI / 2 - normalization}rad,
|
|
||||||
|
|
||||||
${Var('cyan-a5')} ${Math.PI / 2 - normalization}rad,
|
|
||||||
${Var('cyan-a3')} ${Math.PI / 2 + normalization}rad,
|
|
||||||
|
|
||||||
${Var('jade-a2')} ${Math.PI / 2 + normalization}rad,
|
|
||||||
${Var('jade-a4')} ${Math.PI / 2 + ricochet}rad,
|
|
||||||
|
|
||||||
${Var('ruby-a1')} ${Math.PI / 2 + ricochet}rad,
|
|
||||||
${Var('ruby-a2')} 180deg
|
|
||||||
`,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Box
|
|
||||||
position="absolute"
|
|
||||||
left="33%"
|
|
||||||
top="50%"
|
|
||||||
height="18rem"
|
|
||||||
style={{
|
|
||||||
transformOrigin: 'top center',
|
|
||||||
transform: `translateX(-50%) rotate(${-Math.PI / 2 - angle}rad)`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
position="absolute"
|
|
||||||
width="1px"
|
|
||||||
height="100%"
|
|
||||||
bottom="100%"
|
|
||||||
style={{
|
|
||||||
transform: 'translateX(-50%)',
|
|
||||||
backgroundColor: Var('gray-a8'),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Box
|
|
||||||
position="absolute"
|
|
||||||
width="3px"
|
|
||||||
height="100%"
|
|
||||||
style={{
|
|
||||||
transform: 'translateX(-50%)',
|
|
||||||
background: `linear-gradient(${Var('gray-a11')}, ${Var('gray-a4')})`,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<img
|
|
||||||
src={asset(`icons/shells/${shell.icon}.webp`)}
|
|
||||||
style={{
|
|
||||||
objectFit: 'contain',
|
|
||||||
transform: 'translate(-50%, -50%) rotate(-45deg)',
|
|
||||||
position: 'absolute',
|
|
||||||
width: '2rem',
|
|
||||||
height: '2rem',
|
|
||||||
top: '6rem',
|
|
||||||
left: 0,
|
|
||||||
filter: 'drop-shadow(0px 0px 0.25rem black)',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box
|
|
||||||
position="absolute"
|
|
||||||
left="33%"
|
|
||||||
top="50%"
|
|
||||||
height="16rem"
|
|
||||||
style={{
|
|
||||||
transformOrigin: 'top center',
|
|
||||||
transform: `translateX(-50%) rotate(${-Math.PI / 2 - effectiveAngle}rad)`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
position="absolute"
|
|
||||||
width="3px"
|
|
||||||
height="100%"
|
|
||||||
top="-100%"
|
|
||||||
style={{
|
|
||||||
transform: 'translateX(-50%)',
|
|
||||||
background: `linear-gradient(${Var('gray-a4')}, ${Var('gray-a11')})`,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Flex
|
|
||||||
style={{ userSelect: 'none' }}
|
|
||||||
gap="4"
|
|
||||||
position="absolute"
|
|
||||||
top="0"
|
|
||||||
left="0"
|
|
||||||
p="3"
|
|
||||||
width="100%"
|
|
||||||
justify="between"
|
|
||||||
>
|
|
||||||
<Flex gap="2">
|
|
||||||
<Text color="gray" size="1">
|
|
||||||
{strings.website.tools.tankopedia.visualizers.ricochet.angle}
|
|
||||||
</Text>
|
|
||||||
<Code size="1" variant="ghost">
|
|
||||||
{literals(strings.common.units.deg, [
|
|
||||||
radToDeg(Math.abs(angle)).toFixed(0),
|
|
||||||
])}
|
|
||||||
</Code>
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
<Flex gap="2">
|
|
||||||
<Text color="gray" size="1">
|
|
||||||
{
|
|
||||||
strings.website.tools.tankopedia.visualizers.ricochet
|
|
||||||
.normalization
|
|
||||||
}
|
|
||||||
</Text>
|
|
||||||
<Code size="1" variant="ghost">
|
|
||||||
{doesRicochet
|
|
||||||
? '-'
|
|
||||||
: literals(strings.common.units.deg, [
|
|
||||||
radToDeg(Math.min(normalization, Math.abs(angle))).toFixed(0),
|
|
||||||
])}
|
|
||||||
</Code>
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
<Flex
|
|
||||||
style={{ userSelect: 'none' }}
|
|
||||||
gap="4"
|
|
||||||
position="absolute"
|
|
||||||
bottom="0"
|
|
||||||
left="0"
|
|
||||||
p="3"
|
|
||||||
width="100%"
|
|
||||||
justify="between"
|
|
||||||
>
|
|
||||||
<Flex gap="2">
|
|
||||||
<Text color="gray" size="1">
|
|
||||||
{
|
|
||||||
strings.website.tools.tankopedia.visualizers.ricochet
|
|
||||||
.effective_angle
|
|
||||||
}
|
|
||||||
</Text>
|
|
||||||
<Code size="1" variant="ghost">
|
|
||||||
{doesRicochet
|
|
||||||
? '-'
|
|
||||||
: literals(strings.common.units.deg, [
|
|
||||||
radToDeg(Math.abs(effectiveAngle)).toFixed(0),
|
|
||||||
])}
|
|
||||||
</Code>
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
<Flex gap="2">
|
|
||||||
<Text color="gray" size="1">
|
|
||||||
{strings.website.tools.tankopedia.visualizers.ricochet.armor}
|
|
||||||
</Text>
|
|
||||||
<Code size="1" variant="ghost">
|
|
||||||
{doesRicochet
|
|
||||||
? '-'
|
|
||||||
: literals(strings.common.units.percentage, [
|
|
||||||
(100 / Math.cos(effectiveAngle)).toFixed(0),
|
|
||||||
])}
|
|
||||||
</Code>
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
import { asset } from '@blitzkit/core';
|
||||||
|
import { Flex, IconButton, Tooltip } from '@radix-ui/themes';
|
||||||
|
import { useLocale } from '../../../../../../../../hooks/useLocale';
|
||||||
|
import { Duel } from '../../../../../../../../stores/duel';
|
||||||
|
|
||||||
|
export function ShellSwitcher() {
|
||||||
|
const gun = Duel.use((state) => state.protagonist.gun);
|
||||||
|
const shell = Duel.use((state) => state.protagonist.shell);
|
||||||
|
const mutateDuel = Duel.useMutation();
|
||||||
|
const { unwrap } = useLocale();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex>
|
||||||
|
{gun.gun_type!.value.base.shells.map((thisShell, shellIndex) => {
|
||||||
|
const selected = thisShell.id === shell.id;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip content={unwrap(thisShell.name)} key={thisShell.id}>
|
||||||
|
<IconButton
|
||||||
|
size="3"
|
||||||
|
color={selected ? undefined : 'gray'}
|
||||||
|
variant={selected ? 'solid' : 'soft'}
|
||||||
|
highContrast={selected}
|
||||||
|
style={{
|
||||||
|
borderTopLeftRadius: 0,
|
||||||
|
borderBottomLeftRadius: shellIndex === 0 ? '50%' : 0,
|
||||||
|
borderTopRightRadius: 0,
|
||||||
|
borderBottomRightRadius:
|
||||||
|
shellIndex === gun.gun_type!.value.base.shells.length - 1
|
||||||
|
? '50%'
|
||||||
|
: 0,
|
||||||
|
marginLeft: shellIndex === 0 ? 0 : -1,
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
mutateDuel((draft) => {
|
||||||
|
draft.protagonist.shell = thisShell;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
alt={unwrap(thisShell.name)}
|
||||||
|
width={16}
|
||||||
|
height={16}
|
||||||
|
src={asset(`icons/shells/${thisShell.icon}.webp`)}
|
||||||
|
/>
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
||||||
+270
@@ -0,0 +1,270 @@
|
|||||||
|
import { asset, isExplosive } from '@blitzkit/core';
|
||||||
|
import { literals } from '@blitzkit/i18n';
|
||||||
|
import { Box, Card, Code, Flex, Text } from '@radix-ui/themes';
|
||||||
|
import { useRef, useState } from 'react';
|
||||||
|
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 { StatsAcceptorProps } from '../HullTraverseVisualizer';
|
||||||
|
import { ShellSwitcher } from './components/ShellSwitcher';
|
||||||
|
|
||||||
|
export function RicochetVisualizer({ stats }: StatsAcceptorProps) {
|
||||||
|
const shell = Duel.use((state) => state.protagonist.shell);
|
||||||
|
const container = useRef<HTMLDivElement>(null);
|
||||||
|
const normalization = degToRad(stats.shellNormalization);
|
||||||
|
const ricochet = degToRad(stats.shellRicochet ?? 90);
|
||||||
|
const [rawAngle, setAngle] = useState(degToRad(-25));
|
||||||
|
const explodes = isExplosive(shell.type);
|
||||||
|
const angle = explodes ? degToRad(-70) : rawAngle;
|
||||||
|
const doesRicochet = Math.abs(angle) >= ricochet;
|
||||||
|
const effectiveAngle = doesRicochet
|
||||||
|
? -angle + Math.PI
|
||||||
|
: Math.sign(angle) * Math.max(0, Math.abs(angle) - normalization);
|
||||||
|
const { strings } = useLocale();
|
||||||
|
const armorPercentage = Math.min(999, 100 / Math.cos(effectiveAngle));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex direction="column" mb="6" align="center">
|
||||||
|
<Card
|
||||||
|
variant="classic"
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
aspectRatio: '1 / 1',
|
||||||
|
touchAction: 'none',
|
||||||
|
}}
|
||||||
|
ref={container}
|
||||||
|
onPointerMove={(event) => {
|
||||||
|
if (!container.current || explodes) return;
|
||||||
|
|
||||||
|
const rect = container.current.getBoundingClientRect();
|
||||||
|
const x = event.clientX - rect.left - rect.width / 3;
|
||||||
|
const y = event.clientY - rect.top - rect.height / 2;
|
||||||
|
const angle = clamp(Math.atan2(-y, x), -Math.PI / 2, Math.PI / 2);
|
||||||
|
|
||||||
|
setAngle(angle);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
width="33%"
|
||||||
|
height="100%"
|
||||||
|
position="absolute"
|
||||||
|
top="0"
|
||||||
|
left="0"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(to right, transparent, ${Var('gray-a4')})`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
style={{
|
||||||
|
opacity: 2 ** -3,
|
||||||
|
backgroundImage: `url(/assets/images/tankopedia/visualizers/ricochet/armor-hash.png)`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
left="33%"
|
||||||
|
top="0"
|
||||||
|
width="67%"
|
||||||
|
height="100%"
|
||||||
|
position="absolute"
|
||||||
|
style={{
|
||||||
|
background: `conic-gradient(
|
||||||
|
at 0 50%,
|
||||||
|
|
||||||
|
${Var('ruby-a2')},
|
||||||
|
${Var('ruby-a1')} ${Math.PI / 2 - ricochet}rad,
|
||||||
|
|
||||||
|
${Var('jade-a2')} ${Math.PI / 2 - ricochet}rad,
|
||||||
|
${Var('jade-a4')} ${Math.PI / 2 - normalization}rad,
|
||||||
|
|
||||||
|
${Var('cyan-a5')} ${Math.PI / 2 - normalization}rad,
|
||||||
|
${Var('cyan-a3')} ${Math.PI / 2 + normalization}rad,
|
||||||
|
|
||||||
|
${Var('jade-a2')} ${Math.PI / 2 + normalization}rad,
|
||||||
|
${Var('jade-a4')} ${Math.PI / 2 + ricochet}rad,
|
||||||
|
|
||||||
|
${Var('ruby-a1')} ${Math.PI / 2 + ricochet}rad,
|
||||||
|
${Var('ruby-a2')} 180deg
|
||||||
|
`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
position="absolute"
|
||||||
|
left="33%"
|
||||||
|
top="50%"
|
||||||
|
height="18rem"
|
||||||
|
style={{
|
||||||
|
transformOrigin: 'top center',
|
||||||
|
transform: `translateX(-50%) rotate(${-Math.PI / 2 - angle}rad)`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
position="absolute"
|
||||||
|
width="1px"
|
||||||
|
height="100%"
|
||||||
|
bottom="100%"
|
||||||
|
style={{
|
||||||
|
transform: 'translateX(-50%)',
|
||||||
|
backgroundColor: Var('gray-a8'),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Box
|
||||||
|
position="absolute"
|
||||||
|
width="3px"
|
||||||
|
height="100%"
|
||||||
|
style={{
|
||||||
|
transform: 'translateX(-50%)',
|
||||||
|
background: `linear-gradient(${Var('gray-a11')}, ${Var('gray-a4')})`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<img
|
||||||
|
src={asset(`icons/shells/${shell.icon}.webp`)}
|
||||||
|
style={{
|
||||||
|
objectFit: 'contain',
|
||||||
|
transform: 'translate(-50%, -50%) rotate(-45deg)',
|
||||||
|
position: 'absolute',
|
||||||
|
width: '2rem',
|
||||||
|
height: '2rem',
|
||||||
|
top: '6rem',
|
||||||
|
left: 0,
|
||||||
|
filter: 'drop-shadow(0px 0px 0.25rem black)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
position="absolute"
|
||||||
|
left="33%"
|
||||||
|
top="50%"
|
||||||
|
height="16rem"
|
||||||
|
style={{
|
||||||
|
transformOrigin: 'top center',
|
||||||
|
transform: `translateX(-50%) rotate(${-Math.PI / 2 - effectiveAngle}rad)`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
position="absolute"
|
||||||
|
width="3px"
|
||||||
|
height="100%"
|
||||||
|
top="-100%"
|
||||||
|
style={{
|
||||||
|
transform: 'translateX(-50%)',
|
||||||
|
background: `linear-gradient(${Var('gray-a4')}, ${Var('gray-a11')})`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Flex
|
||||||
|
style={{ userSelect: 'none' }}
|
||||||
|
gap="4"
|
||||||
|
position="absolute"
|
||||||
|
top="0"
|
||||||
|
left="0"
|
||||||
|
p="3"
|
||||||
|
width="100%"
|
||||||
|
justify="between"
|
||||||
|
>
|
||||||
|
<Flex gap="2">
|
||||||
|
<Text color="gray" size="1">
|
||||||
|
{strings.website.tools.tankopedia.visualizers.ricochet.angle}
|
||||||
|
</Text>
|
||||||
|
<Code size="1" variant="ghost">
|
||||||
|
{literals(strings.common.units.deg, [
|
||||||
|
radToDeg(Math.abs(angle)).toFixed(0),
|
||||||
|
])}
|
||||||
|
</Code>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<Flex gap="2">
|
||||||
|
<Text color="gray" size="1">
|
||||||
|
{
|
||||||
|
strings.website.tools.tankopedia.visualizers.ricochet
|
||||||
|
.normalization
|
||||||
|
}
|
||||||
|
</Text>
|
||||||
|
<Code size="1" variant="ghost">
|
||||||
|
{doesRicochet
|
||||||
|
? '-'
|
||||||
|
: literals(strings.common.units.deg, [
|
||||||
|
radToDeg(Math.min(normalization, Math.abs(angle))).toFixed(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
])}
|
||||||
|
</Code>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<Flex
|
||||||
|
style={{ userSelect: 'none' }}
|
||||||
|
gap="4"
|
||||||
|
position="absolute"
|
||||||
|
bottom="0"
|
||||||
|
left="0"
|
||||||
|
p="3"
|
||||||
|
width="100%"
|
||||||
|
justify="between"
|
||||||
|
>
|
||||||
|
<Flex gap="2">
|
||||||
|
<Text color="gray" size="1">
|
||||||
|
{
|
||||||
|
strings.website.tools.tankopedia.visualizers.ricochet
|
||||||
|
.effective_angle
|
||||||
|
}
|
||||||
|
</Text>
|
||||||
|
<Code size="1" variant="ghost">
|
||||||
|
{doesRicochet
|
||||||
|
? '-'
|
||||||
|
: literals(strings.common.units.deg, [
|
||||||
|
radToDeg(Math.abs(effectiveAngle)).toFixed(0),
|
||||||
|
])}
|
||||||
|
</Code>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<Flex gap="2">
|
||||||
|
<Text color="gray" size="1">
|
||||||
|
{strings.website.tools.tankopedia.visualizers.ricochet.armor}
|
||||||
|
</Text>
|
||||||
|
<Code size="1" variant="ghost">
|
||||||
|
{doesRicochet
|
||||||
|
? '-'
|
||||||
|
: literals(strings.common.units.percentage, [
|
||||||
|
armorPercentage.toFixed(0),
|
||||||
|
])}
|
||||||
|
</Code>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
{explodes && (
|
||||||
|
<Flex
|
||||||
|
p="3"
|
||||||
|
justify="center"
|
||||||
|
align="center"
|
||||||
|
height="100%"
|
||||||
|
width="100%"
|
||||||
|
position="absolute"
|
||||||
|
top="0"
|
||||||
|
left="0"
|
||||||
|
style={{
|
||||||
|
background: `radial-gradient(${Var('black-a7')}, ${Var('black-a11')})`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text color="gray" align="center">
|
||||||
|
{
|
||||||
|
strings.website.tools.tankopedia.visualizers.ricochet
|
||||||
|
.no_ricochet
|
||||||
|
}
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<ShellSwitcher />
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user