-
- {toy} toy
+ {Toy ? (
+
+ ) : (
+ {toy}
+ )}
);
diff --git a/packages/website/src/components/TankopediaToys/index.tsx b/packages/website/src/components/TankopediaToys/index.tsx
new file mode 100644
index 000000000..52e548cff
--- /dev/null
+++ b/packages/website/src/components/TankopediaToys/index.tsx
@@ -0,0 +1,229 @@
+import { isExplosive } from "@blitzkit/core";
+import { useRef, useState } from "react";
+import { degToRad } from "three/src/math/MathUtils.js";
+import type { ToyProps } from "../../config/toys";
+import { DuelModule, useDuel } from "../../hooks/useDuel";
+import { useStrings } from "../../hooks/useStrings";
+
+export function RicochetToy({ characteristics }: ToyProps) {
+ const strings = useStrings();
+
+ const shell = useDuel("protagonist", DuelModule.Shell);
+
+ const container = useRef(null);
+
+ const normalization = degToRad(
+ (characteristics.get("normalization") as number) ?? 0,
+ );
+ const ricochet = degToRad((characteristics.get("ricochet") as number) ?? 90);
+ const explodes = isExplosive(shell.type);
+
+ const [rawAngle, setAngle] = useState(degToRad(-25));
+
+ 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 armorPercentage = Math.min(999, 100 / Math.cos(effectiveAngle));
+
+ return (
+ {
+ event.preventDefault();
+
+ 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);
+ }}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {explodes && (
+
+
+ {strings.website.tools.tankopedia.visualizers.ricochet.no_ricochet}
+
+
+ )}
+
+ );
+}
diff --git a/packages/website/src/config/toys.ts b/packages/website/src/config/toys.ts
new file mode 100644
index 000000000..2fdb6e8c4
--- /dev/null
+++ b/packages/website/src/config/toys.ts
@@ -0,0 +1,11 @@
+import type { ReactNode } from "react";
+import { RicochetToy } from "../components/TankopediaToys";
+import type { ComputedCharacteristics } from "../types/characteristics";
+
+export interface ToyProps {
+ characteristics: ComputedCharacteristics;
+}
+
+export const toys: Record ReactNode> = {
+ ricochet: RicochetToy,
+};
diff --git a/packages/website/src/types/characteristics.ts b/packages/website/src/types/characteristics.ts
index 730ccf9a4..a74da634e 100644
--- a/packages/website/src/types/characteristics.ts
+++ b/packages/website/src/types/characteristics.ts
@@ -11,6 +11,7 @@ import type {
} from "@blitzkit/protos";
import type { ReactNode } from "react";
import type { characteristics } from "../config/characteristics";
+import type { toys } from "../config/toys";
import type { DuelSide } from "../hooks/useEquipment";
import type { TankEnvironment } from "../stores/tankopedia";
import type { TankState } from "../tankopedia/tankState";
@@ -99,9 +100,11 @@ export interface ToyRenderConfig {
}
export interface LabelRenderConfig {
- label: string;
+ label: ToyName;
}
+export type ToyName = keyof typeof toys;
+
export type CharacteristicsGroup = {
name: string;
order: (CharacteristicRenderConfig | LabelRenderConfig | ToyRenderConfig)[];