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)} + + + Antagonist Gun + + + + +
+ {[...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; + }); + }} + > + Enhanced Armor + +
+ + { + 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)} - - - Antagonist Gun - - - - - - {[...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; - } - }); - }} - > - Calibrated Shells - - { - Tankopedia.mutate((draft) => { - if (hasEnhancedArmor) { - delete draft.protagonist.equipment[4]; - } else { - draft.antagonist.equipment[4] = 0; - } - - draft.shot = undefined; - }); - }} - > - Enhanced Armor - - - - - - {!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(