mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-23 19:49:50 +02:00
feat: new ricochet visualizer
This commit is contained in:
1 parent
1b94748668
commit
c3d2b063bc
8 files changed
+244
-649
No files matched your search
@@ -11,7 +11,8 @@
|
|||||||
"mm": "%s1mm",
|
"mm": "%s1mm",
|
||||||
"deg": "%s1°",
|
"deg": "%s1°",
|
||||||
"hp": "%s1hp",
|
"hp": "%s1hp",
|
||||||
"x": "x%s1"
|
"x": "x%s1",
|
||||||
|
"percentage": "%s1%"
|
||||||
},
|
},
|
||||||
|
|
||||||
"gun_types": {
|
"gun_types": {
|
||||||
@@ -871,8 +872,10 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
"ricochet": {
|
"ricochet": {
|
||||||
"safe": "Safe <%s1°",
|
"angle": "Angle",
|
||||||
"unsafe": "Unsafe ≥%s1°"
|
"normalization": "Normalization",
|
||||||
|
"effective_angle": "Effective angle",
|
||||||
|
"armor": "Armor"
|
||||||
},
|
},
|
||||||
|
|
||||||
"view_range": {
|
"view_range": {
|
||||||
|
|||||||
+2
-2
@@ -31,7 +31,7 @@ export function HullTraverseVisualizer({ stats }: VisualizerProps) {
|
|||||||
left: '50%',
|
left: '50%',
|
||||||
transform: 'translate(-50%, -50%)',
|
transform: 'translate(-50%, -50%)',
|
||||||
animationDuration: `${
|
animationDuration: `${
|
||||||
rotateHull ? 1 / (stats[`hullTraverse${terrain}Terrain`] / 360) : 0
|
rotateHull ? 360 / stats[`hullTraverse${terrain}Terrain`] : 0
|
||||||
}s`,
|
}s`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -59,7 +59,7 @@ export function HullTraverseVisualizer({ stats }: VisualizerProps) {
|
|||||||
filter: 'drop-shadow(0px 0px 4px black)',
|
filter: 'drop-shadow(0px 0px 4px black)',
|
||||||
transform: 'translate(-50%, -50%)',
|
transform: 'translate(-50%, -50%)',
|
||||||
animationDuration: `${
|
animationDuration: `${
|
||||||
rotateTurret ? 1 / (stats.turretTraverseSpeed / 360) : 0
|
rotateTurret ? 360 / stats.turretTraverseSpeed : 0
|
||||||
}s`,
|
}s`,
|
||||||
}}
|
}}
|
||||||
src="/assets/images/tankopedia/visualizers/hull-traverse/turret.png"
|
src="/assets/images/tankopedia/visualizers/hull-traverse/turret.png"
|
||||||
|
|||||||
+236
@@ -0,0 +1,236 @@
|
|||||||
|
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 { VisualizerProps } from './HullTraverseVisualizer';
|
||||||
|
|
||||||
|
export function RicochetVisualizer({ stats }: VisualizerProps) {
|
||||||
|
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(45));
|
||||||
|
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',
|
||||||
|
}}
|
||||||
|
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, ${Var('gray-a1')}, ${Var('gray-a5')})`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<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-a5'),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Box
|
||||||
|
position="absolute"
|
||||||
|
width="2px"
|
||||||
|
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 4px 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="2px"
|
||||||
|
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(2),
|
||||||
|
])}
|
||||||
|
</Code>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
-134
@@ -1,134 +0,0 @@
|
|||||||
@keyframes penetrating-shell-slide {
|
|
||||||
0% {
|
|
||||||
margin-top: -9rem;
|
|
||||||
}
|
|
||||||
28%,
|
|
||||||
100% {
|
|
||||||
margin-top: 8rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ricochet-visualizer-penetrating-shell {
|
|
||||||
animation: penetrating-shell-slide infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes ricocheting-shell-slide {
|
|
||||||
0% {
|
|
||||||
margin-top: -18rem;
|
|
||||||
}
|
|
||||||
38% {
|
|
||||||
margin-top: -18rem;
|
|
||||||
}
|
|
||||||
63%,
|
|
||||||
69% {
|
|
||||||
margin-top: 0rem;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
margin-top: 8rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes ricocheting-shell-slide-container {
|
|
||||||
0% {
|
|
||||||
transform: translateX(-50%) rotate(80deg);
|
|
||||||
}
|
|
||||||
63% {
|
|
||||||
transform: translateX(-50%) rotate(80deg);
|
|
||||||
}
|
|
||||||
69% {
|
|
||||||
transform: translateX(-50%) rotate(100deg);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: translateX(-50%) rotate(100deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ricochet-visualizer-ricocheting-shell {
|
|
||||||
animation: ricocheting-shell-slide infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ricochet-visualizer-ricocheting-shell-container {
|
|
||||||
animation: ricocheting-shell-slide-container infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes surface-impact {
|
|
||||||
0%,
|
|
||||||
60% {
|
|
||||||
opacity: 1;
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
}
|
|
||||||
66% {
|
|
||||||
opacity: 1;
|
|
||||||
width: 1.5rem;
|
|
||||||
height: 1.5rem;
|
|
||||||
}
|
|
||||||
72%,
|
|
||||||
100% {
|
|
||||||
opacity: 0;
|
|
||||||
width: 3rem;
|
|
||||||
height: 3rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ricochet-visualizer-surface-impact {
|
|
||||||
animation: surface-impact linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes puncture {
|
|
||||||
0%,
|
|
||||||
10% {
|
|
||||||
opacity: 1;
|
|
||||||
margin-top: -6rem;
|
|
||||||
}
|
|
||||||
15%,
|
|
||||||
30% {
|
|
||||||
opacity: 1;
|
|
||||||
margin-top: -1rem;
|
|
||||||
}
|
|
||||||
45%,
|
|
||||||
100% {
|
|
||||||
opacity: 0;
|
|
||||||
margin-top: -1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ricochet-visualizer-puncture {
|
|
||||||
animation: puncture infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes safe {
|
|
||||||
0% {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
5%,
|
|
||||||
28% {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
33%,
|
|
||||||
100% {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ricochet-visualizer-safe {
|
|
||||||
animation: safe infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes unsafe {
|
|
||||||
0%,
|
|
||||||
33% {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
38%,
|
|
||||||
95% {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ricochet-visualizer-unsafe {
|
|
||||||
animation: unsafe infinite;
|
|
||||||
}
|
|
||||||
-229
@@ -1,229 +0,0 @@
|
|||||||
import { asset } from '@blitzkit/core';
|
|
||||||
import { literals } from '@blitzkit/i18n';
|
|
||||||
import { Box, Card, Text } from '@radix-ui/themes';
|
|
||||||
import { Var } from '../../../../../../../core/radix/var';
|
|
||||||
import { useLocale } from '../../../../../../../hooks/useLocale';
|
|
||||||
import { Duel } from '../../../../../../../stores/duel';
|
|
||||||
import type { VisualizerProps } from '../HullTraverseVisualizer';
|
|
||||||
import './index.css';
|
|
||||||
|
|
||||||
export function RicochetVisualizer({ stats }: VisualizerProps) {
|
|
||||||
const ricochet = stats.shellRicochet ?? 90;
|
|
||||||
const shell = Duel.use((state) => state.protagonist.shell);
|
|
||||||
const { strings } = useLocale();
|
|
||||||
const animationDuration = '6s';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card
|
|
||||||
variant="classic"
|
|
||||||
style={{
|
|
||||||
aspectRatio: '2 / 1',
|
|
||||||
}}
|
|
||||||
mb="6"
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
width="100%"
|
|
||||||
height="calc(100% / 3)"
|
|
||||||
position="absolute"
|
|
||||||
bottom="0"
|
|
||||||
left="0"
|
|
||||||
style={{
|
|
||||||
background: `linear-gradient(${Var('gray-a4')}, ${Var('gray-a1')})`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
width="100%"
|
|
||||||
height="100%"
|
|
||||||
style={{
|
|
||||||
opacity: 2 ** -3,
|
|
||||||
backgroundImage: `url(/assets/images/tankopedia/visualizers/ricochet/armor-hash.png)`,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Box
|
|
||||||
position="absolute"
|
|
||||||
left="25%"
|
|
||||||
top="0"
|
|
||||||
style={{
|
|
||||||
transformOrigin: 'top center',
|
|
||||||
transform: `translateX(-50%) rotate(${ricochet / 2}deg)`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
className="ricochet-visualizer-puncture"
|
|
||||||
width="1rem"
|
|
||||||
height="6rem"
|
|
||||||
style={{
|
|
||||||
background: `linear-gradient(${Var('gray-1')}, ${Var('gray-a1')})`,
|
|
||||||
animationDuration,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box
|
|
||||||
width="100%"
|
|
||||||
height="calc(100% * 2 / 3)"
|
|
||||||
position="absolute"
|
|
||||||
top="0"
|
|
||||||
left="0"
|
|
||||||
style={{
|
|
||||||
backgroundColor: Var('gray-3'),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
width="100%"
|
|
||||||
height="100%"
|
|
||||||
style={{
|
|
||||||
background: `
|
|
||||||
conic-gradient(
|
|
||||||
from -90deg at 25% 100%,
|
|
||||||
transparent ${90 - ricochet}deg,
|
|
||||||
${Var('jade-a7')} ${90 - ricochet}deg,
|
|
||||||
${Var('jade-9')} ${90 + ricochet}deg,
|
|
||||||
${Var('tomato-8')} ${90 + ricochet}deg,
|
|
||||||
${Var('tomato-a3')} 180deg,
|
|
||||||
transparent 180deg
|
|
||||||
)
|
|
||||||
`,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* <Box
|
|
||||||
width="1px"
|
|
||||||
height="100%"
|
|
||||||
position="absolute"
|
|
||||||
left="25%"
|
|
||||||
top="0"
|
|
||||||
style={{
|
|
||||||
transform: 'translateX(-50%)',
|
|
||||||
backgroundColor: Var('gray-11'),
|
|
||||||
}}
|
|
||||||
/> */}
|
|
||||||
|
|
||||||
<Box
|
|
||||||
width="3px"
|
|
||||||
height="20rem"
|
|
||||||
position="absolute"
|
|
||||||
left="25%"
|
|
||||||
bottom="0"
|
|
||||||
style={{
|
|
||||||
transformOrigin: 'bottom center',
|
|
||||||
transform: `translateX(-50%) rotate(${ricochet}deg)`,
|
|
||||||
background: `repeating-linear-gradient(${Var('gray-a4')}, ${Var('gray-12')} 15px, transparent 15px, transparent 30px)`,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Box
|
|
||||||
width="3px"
|
|
||||||
height="20rem"
|
|
||||||
position="absolute"
|
|
||||||
left="25%"
|
|
||||||
bottom="0"
|
|
||||||
style={{
|
|
||||||
transformOrigin: 'bottom center',
|
|
||||||
transform: `translateX(-50%) rotate(-${ricochet}deg)`,
|
|
||||||
background: `repeating-linear-gradient(${Var('gray-11')}, ${Var('gray-a3')} 15px, transparent 15px, transparent 30px)`,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Box
|
|
||||||
className="ricochet-visualizer-surface-impact"
|
|
||||||
position="absolute"
|
|
||||||
bottom="0"
|
|
||||||
left="25%"
|
|
||||||
style={{
|
|
||||||
transform: `translate(-50%, 50%) rotate(${ricochet}deg)`,
|
|
||||||
background: `radial-gradient(${Var('gray-a2')}, ${Var('gray-a9')})`,
|
|
||||||
borderRadius: '50%',
|
|
||||||
animationDuration,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Box
|
|
||||||
width="2rem"
|
|
||||||
position="absolute"
|
|
||||||
height="2rem"
|
|
||||||
left="25%"
|
|
||||||
bottom="0"
|
|
||||||
style={{
|
|
||||||
transformOrigin: 'bottom center',
|
|
||||||
transform: `translateX(-50%) rotate(${ricochet / 2}deg)`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
className="ricochet-visualizer-penetrating-shell"
|
|
||||||
src={asset(`icons/shells/${shell.icon}.webp`)}
|
|
||||||
style={{
|
|
||||||
objectFit: 'contain',
|
|
||||||
transform: 'rotate(135deg)',
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
position: 'absolute',
|
|
||||||
animationDuration,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box
|
|
||||||
className="ricochet-visualizer-ricocheting-shell-container"
|
|
||||||
width="2rem"
|
|
||||||
position="absolute"
|
|
||||||
height="2rem"
|
|
||||||
left="25%"
|
|
||||||
bottom="0"
|
|
||||||
style={{
|
|
||||||
transformOrigin: 'bottom center',
|
|
||||||
animationDuration,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
className="ricochet-visualizer-ricocheting-shell"
|
|
||||||
src={asset(`icons/shells/${shell.icon}.webp`)}
|
|
||||||
style={{
|
|
||||||
objectFit: 'contain',
|
|
||||||
transform: 'translateY(50%) rotate(135deg)',
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
position: 'absolute',
|
|
||||||
animationDuration,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Text
|
|
||||||
color="gray"
|
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
left: '30%',
|
|
||||||
bottom: '40%',
|
|
||||||
transform: 'translateX(-50%)',
|
|
||||||
animationDuration,
|
|
||||||
}}
|
|
||||||
className="ricochet-visualizer-safe"
|
|
||||||
>
|
|
||||||
{literals(
|
|
||||||
strings.website.tools.tankopedia.visualizers.ricochet.safe,
|
|
||||||
[ricochet.toFixed(0)],
|
|
||||||
)}
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Text
|
|
||||||
color="gray"
|
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
right: '5%',
|
|
||||||
bottom: '12%',
|
|
||||||
animationDuration,
|
|
||||||
}}
|
|
||||||
className="ricochet-visualizer-unsafe"
|
|
||||||
>
|
|
||||||
{literals(
|
|
||||||
strings.website.tools.tankopedia.visualizers.ricochet.unsafe,
|
|
||||||
[ricochet.toFixed(0)],
|
|
||||||
)}
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
-134
@@ -1,134 +0,0 @@
|
|||||||
@keyframes penetrating-shell-slide {
|
|
||||||
0% {
|
|
||||||
margin-top: -9rem;
|
|
||||||
}
|
|
||||||
28%,
|
|
||||||
100% {
|
|
||||||
margin-top: 8rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ricochet-visualizer-penetrating-shell {
|
|
||||||
animation: penetrating-shell-slide infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes ricocheting-shell-slide {
|
|
||||||
0% {
|
|
||||||
margin-top: -18rem;
|
|
||||||
}
|
|
||||||
38% {
|
|
||||||
margin-top: -18rem;
|
|
||||||
}
|
|
||||||
63%,
|
|
||||||
69% {
|
|
||||||
margin-top: 0rem;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
margin-top: 8rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes ricocheting-shell-slide-container {
|
|
||||||
0% {
|
|
||||||
transform: translateX(-50%) rotate(80deg);
|
|
||||||
}
|
|
||||||
63% {
|
|
||||||
transform: translateX(-50%) rotate(80deg);
|
|
||||||
}
|
|
||||||
69% {
|
|
||||||
transform: translateX(-50%) rotate(100deg);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: translateX(-50%) rotate(100deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ricochet-visualizer-ricocheting-shell {
|
|
||||||
animation: ricocheting-shell-slide infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ricochet-visualizer-ricocheting-shell-container {
|
|
||||||
animation: ricocheting-shell-slide-container infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes surface-impact {
|
|
||||||
0%,
|
|
||||||
60% {
|
|
||||||
opacity: 1;
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
}
|
|
||||||
66% {
|
|
||||||
opacity: 1;
|
|
||||||
width: 1.5rem;
|
|
||||||
height: 1.5rem;
|
|
||||||
}
|
|
||||||
72%,
|
|
||||||
100% {
|
|
||||||
opacity: 0;
|
|
||||||
width: 3rem;
|
|
||||||
height: 3rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ricochet-visualizer-surface-impact {
|
|
||||||
animation: surface-impact linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes puncture {
|
|
||||||
0%,
|
|
||||||
10% {
|
|
||||||
opacity: 1;
|
|
||||||
margin-top: -6rem;
|
|
||||||
}
|
|
||||||
15%,
|
|
||||||
30% {
|
|
||||||
opacity: 1;
|
|
||||||
margin-top: -1rem;
|
|
||||||
}
|
|
||||||
45%,
|
|
||||||
100% {
|
|
||||||
opacity: 0;
|
|
||||||
margin-top: -1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ricochet-visualizer-puncture {
|
|
||||||
animation: puncture infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes safe {
|
|
||||||
0% {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
5%,
|
|
||||||
28% {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
33%,
|
|
||||||
100% {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ricochet-visualizer-safe {
|
|
||||||
animation: safe infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes unsafe {
|
|
||||||
0%,
|
|
||||||
33% {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
38%,
|
|
||||||
95% {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ricochet-visualizer-unsafe {
|
|
||||||
animation: unsafe infinite;
|
|
||||||
}
|
|
||||||
-145
@@ -1,145 +0,0 @@
|
|||||||
import { asset } from '@blitzkit/core';
|
|
||||||
import { Box, Card, Text } from '@radix-ui/themes';
|
|
||||||
import { clamp } from 'lodash-es';
|
|
||||||
import { useRef } from 'react';
|
|
||||||
import { degToRad } from 'three/src/math/MathUtils.js';
|
|
||||||
import { Var } from '../../../../../../../core/radix/var';
|
|
||||||
import { Duel } from '../../../../../../../stores/duel';
|
|
||||||
import type { VisualizerProps } from '../HullTraverseVisualizer';
|
|
||||||
import './index.css';
|
|
||||||
|
|
||||||
const MAX_THICKNESS = 300;
|
|
||||||
const EXAGGERATION = 4;
|
|
||||||
|
|
||||||
export function RicochetVisualizer2({ stats }: VisualizerProps) {
|
|
||||||
const thickness = 150;
|
|
||||||
const shell = Duel.use((state) => state.protagonist.shell);
|
|
||||||
const container = useRef<HTMLDivElement>(null);
|
|
||||||
const incomingTracer = useRef<HTMLDivElement>(null);
|
|
||||||
const normalizedTracer = useRef<HTMLDivElement>(null);
|
|
||||||
const normalization = EXAGGERATION * degToRad(stats.shellNormalization);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card
|
|
||||||
variant="classic"
|
|
||||||
style={{
|
|
||||||
aspectRatio: '1 / 1',
|
|
||||||
}}
|
|
||||||
mb="6"
|
|
||||||
ref={container}
|
|
||||||
onPointerMove={(event) => {
|
|
||||||
if (
|
|
||||||
!container.current ||
|
|
||||||
!incomingTracer.current ||
|
|
||||||
!normalizedTracer.current
|
|
||||||
)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const rect = container.current.getBoundingClientRect();
|
|
||||||
const x = event.clientX - rect.left - rect.width / 2;
|
|
||||||
const y = event.clientY - rect.top - rect.height / 2;
|
|
||||||
const angle = clamp(Math.atan2(y, x), -Math.PI / 2, Math.PI / 2);
|
|
||||||
const normalizedAngle =
|
|
||||||
Math.sign(angle) * Math.max(0, Math.abs(angle) - normalization);
|
|
||||||
|
|
||||||
incomingTracer.current.style.transform = `rotate(${-Math.PI / 2 + angle}rad)`;
|
|
||||||
normalizedTracer.current.style.transform = `rotate(${-Math.PI / 2 + normalizedAngle}rad)`;
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
width={`${(thickness / MAX_THICKNESS) * 50}%`}
|
|
||||||
height="100%"
|
|
||||||
position="absolute"
|
|
||||||
top="0"
|
|
||||||
right="50%"
|
|
||||||
style={{
|
|
||||||
background: `linear-gradient(to right, ${Var('gray-a2')}, ${Var('gray-a4')})`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
width="100%"
|
|
||||||
height="100%"
|
|
||||||
style={{
|
|
||||||
opacity: 2 ** -3,
|
|
||||||
backgroundImage: `url(/assets/images/tankopedia/visualizers/ricochet/armor-hash.png)`,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box
|
|
||||||
ref={incomingTracer}
|
|
||||||
position="absolute"
|
|
||||||
left="50%"
|
|
||||||
top="50%"
|
|
||||||
height="13rem"
|
|
||||||
style={{
|
|
||||||
transformOrigin: 'top center',
|
|
||||||
transform: 'translateX(-50%) rotate(-90deg)',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
position="absolute"
|
|
||||||
width="1px"
|
|
||||||
height="100%"
|
|
||||||
bottom="100%"
|
|
||||||
style={{
|
|
||||||
transform: 'translateX(-50%)',
|
|
||||||
backgroundColor: Var('gray-a5'),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Box
|
|
||||||
position="absolute"
|
|
||||||
width="2px"
|
|
||||||
height="100%"
|
|
||||||
style={{
|
|
||||||
transform: 'translateX(-50%)',
|
|
||||||
background: `linear-gradient(${Var('gray-a11')}, ${Var('gray-a4')})`,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<img
|
|
||||||
className="ricochet-visualizer-penetrating-shell"
|
|
||||||
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,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box
|
|
||||||
ref={normalizedTracer}
|
|
||||||
position="absolute"
|
|
||||||
left="50%"
|
|
||||||
top="50%"
|
|
||||||
height="16rem"
|
|
||||||
style={{
|
|
||||||
transformOrigin: 'top center',
|
|
||||||
transform: 'translateX(-50%) rotate(-90deg)',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
position="absolute"
|
|
||||||
width="2px"
|
|
||||||
height="100%"
|
|
||||||
top="-100%"
|
|
||||||
style={{
|
|
||||||
transform: 'translateX(-50%)',
|
|
||||||
background: `linear-gradient(${Var('gray-a4')}, ${Var('gray-a11')})`,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box position="absolute" right="3" bottom="2">
|
|
||||||
<Text style={{ opacity: 0.5 }} size="1">
|
|
||||||
<i>*Not to scale</i>
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
-2
@@ -35,7 +35,6 @@ import { HullTraverseVisualizer } from './components/HullTraverseVisualizer';
|
|||||||
import { Info } from './components/Info';
|
import { Info } from './components/Info';
|
||||||
import { InfoWithDelta } from './components/InfoWithDelta';
|
import { InfoWithDelta } from './components/InfoWithDelta';
|
||||||
import { RicochetVisualizer } from './components/RicochetVisualizer';
|
import { RicochetVisualizer } from './components/RicochetVisualizer';
|
||||||
import { RicochetVisualizer2 } from './components/RicochetVisualizer2';
|
|
||||||
import { ViewRangeVisualizer } from './components/ViewRangeVisualizer';
|
import { ViewRangeVisualizer } from './components/ViewRangeVisualizer';
|
||||||
|
|
||||||
const [equipmentDefinitions, provisionDefinitions] = await Promise.all([
|
const [equipmentDefinitions, provisionDefinitions] = await Promise.all([
|
||||||
@@ -390,7 +389,6 @@ export function Characteristics() {
|
|||||||
value="shellRicochet"
|
value="shellRicochet"
|
||||||
/>
|
/>
|
||||||
<RicochetVisualizer stats={stats} />
|
<RicochetVisualizer stats={stats} />
|
||||||
<RicochetVisualizer2 stats={stats} />
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<InfoWithDelta
|
<InfoWithDelta
|
||||||
|
|||||||
Reference in new issue
Block a user