From af08df1526f8158012db47b067a3194150eca859 Mon Sep 17 00:00:00 2001
From: tresabhi <43380238+tresabhi@users.noreply.github.com>
Date: Thu, 15 May 2025 23:02:15 -0500
Subject: [PATCH] feat: ricochet visualizer shell switcher
---
packages/i18n/strings/en.json | 3 +-
.../Characteristics/components/Firepower.tsx | 75 ++---
.../components/RicochetVisualizer.tsx | 237 ---------------
.../components/ShellSwitcher.tsx | 52 ++++
.../components/RicochetVisualizer/index.tsx | 270 ++++++++++++++++++
5 files changed, 365 insertions(+), 272 deletions(-)
delete mode 100644 packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/RicochetVisualizer.tsx
create mode 100644 packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/RicochetVisualizer/components/ShellSwitcher.tsx
create mode 100644 packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/RicochetVisualizer/index.tsx
diff --git a/packages/i18n/strings/en.json b/packages/i18n/strings/en.json
index 7a9cbdb4b..4090ac1a9 100644
--- a/packages/i18n/strings/en.json
+++ b/packages/i18n/strings/en.json
@@ -892,7 +892,8 @@
"angle": "Angle",
"normalization": "Normalization",
"effective_angle": "Effective angle",
- "armor": "Armor"
+ "armor": "Armor",
+ "no_ricochet": "Explosive shells don't ricochet"
},
"view_range": {
diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Firepower.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Firepower.tsx
index e96e9f438..02bb96e51 100644
--- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Firepower.tsx
+++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Firepower.tsx
@@ -65,39 +65,44 @@ export function Firepower({ stats }: StatsAcceptorProps) {
- {gun.gun_type!.value.base.shells.map((thisShell, shellIndex) => (
-
- {
- mutateDuel((draft) => {
- draft.protagonist.shell = thisShell;
- });
- }}
- >
-
-
-
- ))}
+ {gun.gun_type!.value.base.shells.map((thisShell, shellIndex) => {
+ const selected = thisShell.id === shell.id;
+
+ return (
+
+ {
+ mutateDuel((draft) => {
+ draft.protagonist.shell = thisShell;
+ });
+ }}
+ >
+
+
+
+ );
+ })}
@@ -265,9 +270,11 @@ export function Firepower({ stats }: StatsAcceptorProps) {
deltaType="lowerIsBetter"
value="shellRicochet"
/>
-
>
)}
+
+
+
diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/RicochetVisualizer.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/RicochetVisualizer.tsx
deleted file mode 100644
index 7bd3d32cf..000000000
--- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/RicochetVisualizer.tsx
+++ /dev/null
@@ -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(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 (
- {
- 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);
- }}
- >
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {strings.website.tools.tankopedia.visualizers.ricochet.angle}
-
-
- {literals(strings.common.units.deg, [
- radToDeg(Math.abs(angle)).toFixed(0),
- ])}
-
-
-
-
-
- {
- strings.website.tools.tankopedia.visualizers.ricochet
- .normalization
- }
-
-
- {doesRicochet
- ? '-'
- : literals(strings.common.units.deg, [
- radToDeg(Math.min(normalization, Math.abs(angle))).toFixed(0),
- ])}
-
-
-
-
-
-
-
- {
- strings.website.tools.tankopedia.visualizers.ricochet
- .effective_angle
- }
-
-
- {doesRicochet
- ? '-'
- : literals(strings.common.units.deg, [
- radToDeg(Math.abs(effectiveAngle)).toFixed(0),
- ])}
-
-
-
-
-
- {strings.website.tools.tankopedia.visualizers.ricochet.armor}
-
-
- {doesRicochet
- ? '-'
- : literals(strings.common.units.percentage, [
- (100 / Math.cos(effectiveAngle)).toFixed(0),
- ])}
-
-
-
-
- );
-}
diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/RicochetVisualizer/components/ShellSwitcher.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/RicochetVisualizer/components/ShellSwitcher.tsx
new file mode 100644
index 000000000..63ed31738
--- /dev/null
+++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/RicochetVisualizer/components/ShellSwitcher.tsx
@@ -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 (
+
+ {gun.gun_type!.value.base.shells.map((thisShell, shellIndex) => {
+ const selected = thisShell.id === shell.id;
+
+ return (
+
+ {
+ mutateDuel((draft) => {
+ draft.protagonist.shell = thisShell;
+ });
+ }}
+ >
+
+
+
+ );
+ })}
+
+ );
+}
diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/RicochetVisualizer/index.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/RicochetVisualizer/index.tsx
new file mode 100644
index 000000000..45c30a373
--- /dev/null
+++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/RicochetVisualizer/index.tsx
@@ -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(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 (
+
+ {
+ 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);
+ }}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {strings.website.tools.tankopedia.visualizers.ricochet.angle}
+
+
+ {literals(strings.common.units.deg, [
+ radToDeg(Math.abs(angle)).toFixed(0),
+ ])}
+
+
+
+
+
+ {
+ strings.website.tools.tankopedia.visualizers.ricochet
+ .normalization
+ }
+
+
+ {doesRicochet
+ ? '-'
+ : literals(strings.common.units.deg, [
+ radToDeg(Math.min(normalization, Math.abs(angle))).toFixed(
+ 0,
+ ),
+ ])}
+
+
+
+
+
+
+
+ {
+ strings.website.tools.tankopedia.visualizers.ricochet
+ .effective_angle
+ }
+
+
+ {doesRicochet
+ ? '-'
+ : literals(strings.common.units.deg, [
+ radToDeg(Math.abs(effectiveAngle)).toFixed(0),
+ ])}
+
+
+
+
+
+ {strings.website.tools.tankopedia.visualizers.ricochet.armor}
+
+
+ {doesRicochet
+ ? '-'
+ : literals(strings.common.units.percentage, [
+ armorPercentage.toFixed(0),
+ ])}
+
+
+
+
+ {explodes && (
+
+
+ {
+ strings.website.tools.tankopedia.visualizers.ricochet
+ .no_ricochet
+ }
+
+
+ )}
+
+
+
+
+ );
+}