diff --git a/packages/i18n/strings/en.json b/packages/i18n/strings/en.json
index 28e328d25..9d79b350a 100644
--- a/packages/i18n/strings/en.json
+++ b/packages/i18n/strings/en.json
@@ -20,6 +20,16 @@
},
"tanks": {
+ "custom_shell": {
+ "type": "Type",
+ "caliber": "Caliber",
+ "penetration": "Penetration",
+ "normalization": "Normalization",
+ "ricochet": "Ricochet",
+ "exit": "Exit",
+ "reset": "Reset"
+ },
+
"tabs": {
"model": "Model",
"dynamic_armor": "Dynamic armor",
diff --git a/packages/website/src/components/TankopediaAntagonistControl/index.module.css b/packages/website/src/components/TankopediaAntagonistControl/index.module.css
new file mode 100644
index 000000000..cc7169855
--- /dev/null
+++ b/packages/website/src/components/TankopediaAntagonistControl/index.module.css
@@ -0,0 +1,19 @@
+.antagonist-control {
+ display: flex;
+ flex-direction: column;
+ justify-content: end;
+
+ gap: var(--space-4);
+
+ transition: var(--duration-brief);
+
+ position: absolute;
+ top: 50%;
+ right: var(--space-4);
+
+ transform: translate(calc(100% + var(--space-4)), -50%);
+
+ &[data-visible="true"] {
+ transform: translate(0, -50%);
+ }
+}
diff --git a/packages/website/src/components/TankopediaAntagonistControl/index.tsx b/packages/website/src/components/TankopediaAntagonistControl/index.tsx
new file mode 100644
index 000000000..bb94112a2
--- /dev/null
+++ b/packages/website/src/components/TankopediaAntagonistControl/index.tsx
@@ -0,0 +1,221 @@
+import { alias, romanize, uniqueGuns } from "@blitzkit/core";
+import { literals } from "@blitzkit/i18n";
+import { Popover } from "radix-ui";
+import { useMemo } from "react";
+import { DuelModule, useDuel } from "../../hooks/useDuel";
+import { useEquipment } from "../../hooks/useEquipment";
+import { useStrings } from "../../hooks/useStrings";
+import { useUnwrapper } from "../../hooks/useUnwrapper";
+import { Tankopedia } from "../../stores/tankopedia";
+import { TankopediaDisplay } from "../../stores/tankopediaPersistent/constants";
+import type { ComputedCharacteristics } from "../../types/characteristics";
+import { EqualIcon } from "../EqualIcon";
+import { IconButton } from "../IconButton";
+import { CustomShellButton } from "../TankopediaOptions/components/CustomShellButton";
+import { Text } from "../Text";
+import styles from "./index.module.css";
+
+interface TankopediaAntagonistControlProps {
+ antagonistCharacteristics: ComputedCharacteristics;
+}
+
+export function TankopediaAntagonistControl({
+ antagonistCharacteristics,
+}: TankopediaAntagonistControlProps) {
+ const strings = useStrings();
+ const unwrap = useUnwrapper();
+
+ const requestedDisplay = Tankopedia.use((state) => state.requestedDisplay);
+ const hasCustomShell = Tankopedia.use(
+ (state) => state.customShell !== undefined,
+ );
+ const equalize = Tankopedia.use((state) => state.environment.equalize);
+
+ const hasCalibratedShells = useEquipment("protagonist", 103);
+ const hasEnhancedArmor = useEquipment("protagonist", 110);
+
+ const tank = useDuel("antagonist", DuelModule.Tank);
+ const gun = useDuel("antagonist", DuelModule.Gun);
+ const shell = useDuel("antagonist", DuelModule.Shell);
+
+ const antagonistUniqueGuns = useMemo(
+ () => uniqueGuns(tank.turrets),
+ [tank.turrets],
+ );
+
+ const isVisible = requestedDisplay === TankopediaDisplay.DynamicArmor;
+
+ return (
+
+
+ {literals(strings.common.units.mm, {
+ value: (
+ antagonistCharacteristics.get("penetration") as number
+ ).toFixed(0),
+ })}
+
+
+ {antagonistUniqueGuns.length > 1 && (
+
+
+
+
+ {romanize(gun.tier)}
+
+
+
+
+
+
+
+
+ {[...antagonistUniqueGuns].reverse().map(({ gun, turret }) => (
+
+
+
+ //
{
+ // Tankopedia.mutate((draft) => {
+ // draft.antagonist.turret = turret.id;
+ // draft.antagonist.gun = gun.id;
+ // draft.antagonist.shell = gun.shells[0].id;
+ // });
+ // }}
+ // selected={gun.id === antagonistGun.id}
+ // discriminator={romanize(gun.tier)}
+ // secondaryDiscriminator={
+ //
+ // {literals(strings.common.units.mm, {
+ // value: gun.shells[0].caliber.toFixed(0),
+ // })}
+ //
+ // }
+ // />
+ ))}
+
+
+
+ )}
+
+
+ {gun.shells.map((thisShell) => (
+ {
+ Tankopedia.mutate((draft) => {
+ draft.antagonist.shell = thisShell.id;
+ draft.shot = undefined;
+ draft.customShell = undefined;
+ });
+ }}
+ />
+ ))}
+
+
+
+
+
{
+ Tankopedia.mutate((draft) => {
+ draft.shot = undefined;
+
+ if (hasCalibratedShells) {
+ delete draft.protagonist.equipment[0];
+ } else {
+ draft.antagonist.equipment[0] = 1;
+ }
+ });
+ }}
+ >
+
+
+
{
+ Tankopedia.mutate((draft) => {
+ if (hasEnhancedArmor) {
+ delete draft.protagonist.equipment[4];
+ } else {
+ draft.antagonist.equipment[4] = 0;
+ }
+
+ draft.shot = undefined;
+ });
+ }}
+ >
+
+
+
+
+
{
+ Tankopedia.mutate((draft) => {
+ draft.environment.equalize = !draft.environment.equalize;
+ draft.shot = undefined;
+ });
+ }}
+ >
+
+
+
+ );
+}
diff --git a/packages/website/src/components/TankopediaOptions/components/CustomShellButton.tsx b/packages/website/src/components/TankopediaOptions/components/CustomShellButton.tsx
index 183a0928e..5a1e2d467 100644
--- a/packages/website/src/components/TankopediaOptions/components/CustomShellButton.tsx
+++ b/packages/website/src/components/TankopediaOptions/components/CustomShellButton.tsx
@@ -12,15 +12,15 @@ import {
} from "@radix-ui/themes";
import { throttle } from "lodash-es";
import { type ChangeEvent } from "react";
-import { Duel } from "../../../../../../stores/duel";
-import { useLocale } from "../../../hooks/useLocale";
+import { DuelModule, useDuel } from "../../../hooks/useDuel";
+import { useStrings } from "../../../hooks/useStrings";
import { Tankopedia } from "../../../stores/tankopedia";
export function CustomShellButton() {
const customShell = Tankopedia.use((state) => state.customShell);
- const gun = Duel.use((state) => state.protagonist.gun);
- const antagonistShell = Duel.use((state) => state.antagonist.shell);
- const { strings } = useLocale();
+ const gun = useDuel("protagonist", DuelModule.Gun);
+ const antagonistShell = useDuel("antagonist", DuelModule.Shell);
+ const strings = useStrings();
return (
-
- {
- strings.website.tools.tankopedia.sandbox.dynamic
- .custom_shell.type
- }
-
+ {strings.tanks.custom_shell.type}
{
@@ -121,12 +116,7 @@ export function CustomShellButton() {
-
- {
- strings.website.tools.tankopedia.sandbox.dynamic
- .custom_shell.caliber
- }
-
+ {strings.tanks.custom_shell.caliber}
-
- {
- strings.website.tools.tankopedia.sandbox.dynamic
- .custom_shell.penetration
- }
-
+ {strings.tanks.custom_shell.penetration}
-
- {
- strings.website.tools.tankopedia.sandbox.dynamic
- .custom_shell.normalization
- }
-
+ {strings.tanks.custom_shell.normalization}
-
- {
- strings.website.tools.tankopedia.sandbox.dynamic
- .custom_shell.ricochet
- }
-
+ {strings.tanks.custom_shell.ricochet}
@@ -300,10 +275,7 @@ export function CustomShellButton() {
>
diff --git a/packages/website/src/components/TankopediaOptions/index.tsx b/packages/website/src/components/TankopediaOptions/index.tsx
index 6fc025d9e..666e0cb9a 100644
--- a/packages/website/src/components/TankopediaOptions/index.tsx
+++ b/packages/website/src/components/TankopediaOptions/index.tsx
@@ -1,28 +1,7 @@
import { alias, romanize, uniqueGuns } from "@blitzkit/core";
import { literals } from "@blitzkit/i18n/src/literals";
-import {
- Crosshair1Icon,
- EnterFullScreenIcon,
- ExitFullScreenIcon,
- EyeOpenIcon,
- ShadowInnerIcon,
- ShadowNoneIcon,
- SunIcon,
-} from "@radix-ui/react-icons";
-import {
- Button,
- Dialog,
- DropdownMenu,
- Flex,
- IconButton,
- Popover,
- SegmentedControl,
- Tabs,
- Text,
- Tooltip,
-} from "@radix-ui/themes";
-import { Suspense, useState, type RefObject } from "react";
-import { Pose, poseEvent } from "../../core/blitzkit/pose";
+import { Flex, IconButton, Popover, Text } from "@radix-ui/themes";
+import { useState, type RefObject } from "react";
import { DuelModule, useDuel } from "../../hooks/useDuel";
import { useEquipment } from "../../hooks/useEquipment";
import { useFullScreen } from "../../hooks/useFullScreen";
@@ -32,25 +11,26 @@ import { useUnwrapper } from "../../hooks/useUnwrapper";
import { Tankopedia } from "../../stores/tankopedia";
import { TankopediaPersistent } from "../../stores/tankopediaPersistent";
import { TankopediaDisplay } from "../../stores/tankopediaPersistent/constants";
+import type { ComputedCharacteristics } from "../../types/characteristics";
import type { MaybeSkeletonComponentProps } from "../../types/maybeSkeletonComponentProps";
import { EqualIcon } from "../EqualIcon";
import { ModuleButton } from "../ModuleButtons/ModuleButton";
-import { ScreenshotButton } from "../ScreenshotButton";
-import { SmallTankIcon } from "../SmallTankIcon";
import {
SidebarIconButton,
SidebarIconGroup,
} from "../TankopediaSidebarIconButtons";
-import { TankSearch } from "../TankSearch";
import { CustomShellButton } from "./components/CustomShellButton";
-import { DynamicArmorSwitcher } from "./components/DynamicArmorSwitcher";
-import { SpallLinerSwitcher } from "./components/SpallLinerSwitcher";
type OptionsProps = MaybeSkeletonComponentProps & {
canvas: RefObject;
+ antagonistCharacteristics: ComputedCharacteristics;
};
-export function TankopediaOptions({ canvas, skeleton }: OptionsProps) {
+export function TankopediaOptions({
+ canvas,
+ skeleton,
+ antagonistCharacteristics,
+}: OptionsProps) {
const strings = useStrings();
const unwrap = useUnwrapper();
@@ -80,206 +60,9 @@ export function TankopediaOptions({ canvas, skeleton }: OptionsProps) {
return (
<>
-
- {!hasCustomShell && (
-
- {/* {literals(strings.common.units.mm, {
- value: (
- resolvePenetrationCoefficient(
- hasCalibratedShells,
- equalize,
- antagonistShell.type,
- equalizer,
- ) * antagonistShell.penetration!.near
- ).toFixed(0),
- })} */}
- what
-
- )}
+
- {antagonistUniqueGuns.length > 1 && (
-
-
-
-
- {romanize(antagonistGun.tier)}
-
-
-
-
-
-
-
-
- {[...antagonistUniqueGuns].reverse().map(({ gun, turret }) => (
- {
- Tankopedia.mutate((draft) => {
- draft.antagonist.turret = turret.id;
- draft.antagonist.gun = gun.id;
- draft.antagonist.shell = gun.shells[0].id;
- });
- }}
- selected={gun.id === antagonistGun.id}
- discriminator={romanize(gun.tier)}
- secondaryDiscriminator={
-
- {literals(strings.common.units.mm, {
- value: gun.shells[0].caliber.toFixed(0),
- })}
-
- }
- />
- ))}
-
-
-
- )}
-
-
- {antagonistGun.shells.map((thisShell) => (
- {
- Tankopedia.mutate((draft) => {
- draft.antagonist.shell = thisShell.id;
- draft.shot = undefined;
- draft.customShell = undefined;
- });
- }}
- />
- ))}
-
-
-
-
- {
- Tankopedia.mutate((draft) => {
- draft.shot = undefined;
-
- if (hasCalibratedShells) {
- delete draft.protagonist.equipment[0];
- } else {
- draft.antagonist.equipment[0] = 1;
- }
- });
- }}
- >
-
-
- {
- Tankopedia.mutate((draft) => {
- if (hasEnhancedArmor) {
- delete draft.protagonist.equipment[4];
- } else {
- draft.antagonist.equipment[4] = 0;
- }
-
- draft.shot = undefined;
- });
- }}
- >
-
-
-
-
-
-
- {!skeleton && modelRequested && (
-
-
-
- )}
-
- {
- Tankopedia.mutate((draft) => {
- draft.environment.equalize = !draft.environment.equalize;
- draft.shot = undefined;
- });
- }}
- >
-
-
-
-
-
-
+
-
+
-
+
)}
-
+ */}
>
);
}
diff --git a/packages/website/src/components/TankopediaSandbox/index.tsx b/packages/website/src/components/TankopediaSandbox/index.tsx
index c46e8e62b..710cc4b22 100644
--- a/packages/website/src/components/TankopediaSandbox/index.tsx
+++ b/packages/website/src/components/TankopediaSandbox/index.tsx
@@ -9,6 +9,7 @@ import { useModel } from "../../hooks/useModel";
import { Tankopedia } from "../../stores/tankopedia";
import { TankopediaPersistent } from "../../stores/tankopediaPersistent";
import { TankopediaDisplay } from "../../stores/tankopediaPersistent/constants";
+import type { ComputedCharacteristics } from "../../types/characteristics";
import type { MaybeSkeletonComponentProps } from "../../types/maybeSkeletonComponentProps";
import { ArmorPlateDisplay } from "../ArmorPlateDisplay";
import { CanvasAutoClear } from "../CanvasAutoClear";
@@ -21,17 +22,19 @@ import { ShotDisplay } from "../ShotDisplay";
import { SmartCanvas } from "../SmartCanvas";
import { StaticArmor, type ThicknessRange } from "../StaticArmor";
import { TankModel } from "../TankModel";
-import { TankopediaOptions } from "../TankopediaOptions";
+import { TankopediaAntagonistControl } from "../TankopediaAntagonistControl";
import { TankopediaSkinSwitcher } from "../TankopediaSkinSwitcher";
import styles from "./index.module.css";
type TankSandboxProps = MaybeSkeletonComponentProps & {
thicknessRange: ThicknessRange;
+ antagonistCharacteristics: ComputedCharacteristics;
};
export function TankopediaSandbox({
skeleton,
thicknessRange,
+ antagonistCharacteristics,
}: TankSandboxProps) {
const wrapper = useRef(null);
const canvas = useRef(null);
@@ -172,7 +175,9 @@ export function TankopediaSandbox({
-
+
);
}
diff --git a/packages/website/src/hooks/useCharacteristics.ts b/packages/website/src/hooks/useCharacteristics.ts
index 50d279eee..605da61c4 100644
--- a/packages/website/src/hooks/useCharacteristics.ts
+++ b/packages/website/src/hooks/useCharacteristics.ts
@@ -191,12 +191,14 @@ export function computeCharacteristics(
return computed;
}
-export function useCharacteristics() {
- const protagonist = Tankopedia.use((state) => state.protagonist);
- const antagonist = Tankopedia.use((state) => state.antagonist);
+export function useCharacteristics(side: DuelSide) {
+ const A = Tankopedia.use((state) => state[side]);
+ const B = Tankopedia.use(
+ (state) => state[side === "antagonist" ? "protagonist" : "antagonist"],
+ );
const environment = Tankopedia.use((state) => state.environment);
- return computeCharacteristics({ protagonist, antagonist }, environment);
+ return computeCharacteristics({ protagonist: A, antagonist: B }, environment);
}
export function computeDefaultCharacteristics(tank: TankDefinition) {
diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/tanks/[slug]/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/tanks/[slug]/_index.tsx
index 8a78372d1..9400a1c44 100644
--- a/packages/website/src/pages/[...chunk_default]/[...locale]/tanks/[slug]/_index.tsx
+++ b/packages/website/src/pages/[...chunk_default]/[...locale]/tanks/[slug]/_index.tsx
@@ -30,7 +30,8 @@ export const Page = withErrorWrapper(
const protagonist = Tankopedia.use((state) => state.protagonist);
const equalize = Tankopedia.use((state) => state.environment.equalize);
- const characteristics = useCharacteristics();
+ const characteristics = useCharacteristics("protagonist");
+ const antagonistCharacteristics = useCharacteristics("antagonist");
const thicknessRange = useMemo(() => {
const entries = Object.values(tanks.tanks);
@@ -62,6 +63,7 @@ export const Page = withErrorWrapper(