From fe545c415e63d8f868a1bbce8d1ecfad9d6e5ca6 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Mon, 9 Nov 2026 23:23:04 -0600 Subject: [PATCH] antagonist settings --- .../CustomShellButton/index.module.css | 36 +++ .../components/CustomShellButton/index.tsx | 306 ++++++++++++++++++ .../components/Discriminator/index.module.css | 14 + .../src/components/Discriminator/index.tsx | 13 + .../ModuleButtons/TankComponentButton.tsx | 31 +- .../src/components/Popover/index.module.css | 16 + .../website/src/components/Popover/index.tsx | 5 + .../src/components/Select/index.module.css | 62 ++++ .../website/src/components/Select/index.tsx | 5 + .../src/components/Slider/index.module.css | 42 +++ .../website/src/components/Slider/index.tsx | 5 + .../index.module.css | 11 + .../TankopediaAntagonistControl/index.tsx | 284 ++++++++-------- .../components/CustomShellButton.tsx | 295 ----------------- packages/website/src/stores/tankopedia.ts | 4 +- 15 files changed, 654 insertions(+), 475 deletions(-) create mode 100644 packages/website/src/components/CustomShellButton/index.module.css create mode 100644 packages/website/src/components/CustomShellButton/index.tsx create mode 100644 packages/website/src/components/Discriminator/index.module.css create mode 100644 packages/website/src/components/Discriminator/index.tsx create mode 100644 packages/website/src/components/Popover/index.module.css create mode 100644 packages/website/src/components/Popover/index.tsx create mode 100644 packages/website/src/components/Select/index.module.css create mode 100644 packages/website/src/components/Select/index.tsx create mode 100644 packages/website/src/components/Slider/index.module.css create mode 100644 packages/website/src/components/Slider/index.tsx delete mode 100644 packages/website/src/components/TankopediaOptions/components/CustomShellButton.tsx diff --git a/packages/website/src/components/CustomShellButton/index.module.css b/packages/website/src/components/CustomShellButton/index.module.css new file mode 100644 index 000000000..fba29f96e --- /dev/null +++ b/packages/website/src/components/CustomShellButton/index.module.css @@ -0,0 +1,36 @@ +.form { + display: flex; + flex-direction: column; + gap: var(--space-3); +} + +.row { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-4); +} + +.field { + max-width: 7rem; +} + +.slider { + display: flex; + align-items: center; + gap: var(--space-3); + + flex-grow: 1; + min-width: 8rem; + max-width: 15rem; + + > :last-child { + flex-grow: 1; + } +} + +.actions { + display: flex; + justify-content: flex-end; + gap: var(--space-3); +} diff --git a/packages/website/src/components/CustomShellButton/index.tsx b/packages/website/src/components/CustomShellButton/index.tsx new file mode 100644 index 000000000..0025f778f --- /dev/null +++ b/packages/website/src/components/CustomShellButton/index.tsx @@ -0,0 +1,306 @@ +import { literals } from "@blitzkit/i18n/src/literals"; +import { ShellType } from "@blitzkit/protos"; +import { throttle } from "lodash-es"; +import { useEffect, useMemo, useState, type KeyboardEvent } from "react"; +import { DuelModule, useDuel } from "../../hooks/useDuel"; +import { useStrings } from "../../hooks/useStrings"; +import { Tankopedia } from "../../stores/tankopedia"; +import { Button } from "../Button"; +import { IconButton } from "../IconButton"; +import { Popover } from "../Popover"; +import { Select } from "../Select"; +import { Slider } from "../Slider"; +import { Text } from "../Text"; +import { TextField } from "../TextField"; +import styles from "./index.module.css"; + +interface NumberFieldProps { + value: number; + suffix: string; + onValueChange(value: number): void; +} + +/** Text field that only commits valid numbers and reverts on Escape */ +function NumberField({ value, suffix, onValueChange }: NumberFieldProps) { + const [text, setText] = useState(`${value}`); + + useEffect(() => setText(`${value}`), [value]); + + function handleKeyDown(event: KeyboardEvent) { + // keep tankopedia hotkeys from firing while typing + event.stopPropagation(); + + const input = event.target as HTMLInputElement; + + if (event.key === "Enter") { + input.blur(); + } else if (event.key === "Escape") { + setText(`${value}`); + onValueChange(value); + input.blur(); + } + } + + return ( + (event.target as HTMLInputElement).select?.()} + onKeyDown={handleKeyDown} + onChange={(event) => { + setText(event.target.value); + + const parsed = Number(event.target.value); + if (event.target.value.trim() !== "" && Number.isFinite(parsed)) { + onValueChange(parsed); + } + }} + > + {suffix} + + ); +} + +export function CustomShellButton() { + const customShell = Tankopedia.use((state) => state.customShell); + const gun = useDuel("protagonist", DuelModule.Gun); + const antagonistShell = useDuel("antagonist", DuelModule.Shell); + const strings = useStrings(); + + const mm = literals(strings.common.units.mm, { value: "" }); + const setRicochet = useMemo( + () => + throttle((value: number) => { + Tankopedia.mutate((draft) => { + draft.customShell!.ricochet = value; + }); + }, 1000), + [], + ); + + return ( + { + if (!open || customShell) return; + + Tankopedia.mutate((draft) => { + draft.customShell = antagonistShell; + }); + }} + > + + + custom shell + + + + + + {customShell && ( + <> +
+
+ {strings.tanks.custom_shell.type} + + { + const type = Number(value) as ShellType; + const modified = { ...customShell, type }; + + switch (type) { + case ShellType.SHELL_TYPE_AP: { + modified.normalization = 5; + modified.ricochet = 70; + modified.explosion_radius = undefined; + break; + } + case ShellType.SHELL_TYPE_APCR: { + modified.normalization = 2; + modified.ricochet = 70; + modified.explosion_radius = undefined; + break; + } + case ShellType.SHELL_TYPE_HEAT: { + modified.normalization = undefined; + modified.ricochet = undefined; + modified.explosion_radius = undefined; + break; + } + case ShellType.SHELL_TYPE_HE: { + modified.normalization = undefined; + modified.ricochet = undefined; + modified.explosion_radius = + gun.shells.find( + (shell) => shell.type === ShellType.SHELL_TYPE_HE, + )?.explosion_radius ?? 1; + break; + } + } + + Tankopedia.mutate((draft) => { + draft.customShell = modified; + }); + }} + > + + + + + + + + {[ + [ShellType.SHELL_TYPE_AP, strings.common.shells.ap], + [ + ShellType.SHELL_TYPE_APCR, + strings.common.shells.ap_cr, + ], + [ + ShellType.SHELL_TYPE_HEAT, + strings.common.shells.hc, + ], + [ShellType.SHELL_TYPE_HE, strings.common.shells.he], + ].map(([type, label]) => ( + + {label} + + ))} + + + + +
+ +
+ {strings.tanks.custom_shell.caliber} + { + Tankopedia.mutate((draft) => { + draft.customShell!.caliber = value; + }); + }} + /> +
+ +
+ {strings.tanks.custom_shell.penetration} + { + Tankopedia.mutate((draft) => { + draft.customShell!.penetration!.near = value; + }); + }} + /> +
+ + {(customShell.type === ShellType.SHELL_TYPE_AP || + customShell.type === ShellType.SHELL_TYPE_APCR) && ( + <> +
+ {strings.tanks.custom_shell.normalization} + +
+ + {literals(strings.common.units.deg, { + value: (customShell.normalization ?? 0).toFixed(0), + })} + + { + Tankopedia.mutate((draft) => { + draft.customShell!.normalization = value; + }); + }} + > + + + + + +
+
+ +
+ {strings.tanks.custom_shell.ricochet} + +
+ + {literals(strings.common.units.deg, { + value: (customShell.ricochet ?? 90).toFixed(0), + })} + + setRicochet(value)} + > + + + + + +
+
+ + )} + + {customShell.type === ShellType.SHELL_TYPE_HE && ( +
+ Explosion radius + { + Tankopedia.mutate((draft) => { + draft.customShell!.explosion_radius = value; + }); + }} + /> +
+ )} +
+ +
+ + + + + +
+ + )} +
+
+
+ ); +} diff --git a/packages/website/src/components/Discriminator/index.module.css b/packages/website/src/components/Discriminator/index.module.css new file mode 100644 index 000000000..b6dca8df9 --- /dev/null +++ b/packages/website/src/components/Discriminator/index.module.css @@ -0,0 +1,14 @@ +.discriminator { + position: absolute; + right: var(--space-3); + + font-size: var(--font-size-minor); + line-height: 1.2; + text-align: right; + text-shadow: 0 0 4px black; + color: var(--white-a12); + + pointer-events: none; + + top: 50%; +} diff --git a/packages/website/src/components/Discriminator/index.tsx b/packages/website/src/components/Discriminator/index.tsx new file mode 100644 index 000000000..53485bd67 --- /dev/null +++ b/packages/website/src/components/Discriminator/index.tsx @@ -0,0 +1,13 @@ +import type { ComponentProps } from "react"; +import { classNames } from "../../ui/classNames"; +import styles from "./index.module.css"; + +/** Small label overlaid on the right edge of a `position: relative` parent */ +export function Discriminator({ + className, + ...props +}: ComponentProps<"span">) { + return ( + + ); +} diff --git a/packages/website/src/components/ModuleButtons/TankComponentButton.tsx b/packages/website/src/components/ModuleButtons/TankComponentButton.tsx index 85d61101e..5aed9ccb1 100644 --- a/packages/website/src/components/ModuleButtons/TankComponentButton.tsx +++ b/packages/website/src/components/ModuleButtons/TankComponentButton.tsx @@ -1,5 +1,6 @@ -import { Button, Text, Tooltip } from '@radix-ui/themes'; +import { Button, Tooltip } from '@radix-ui/themes'; import type { ComponentProps, ReactNode } from 'react'; +import { Discriminator } from '../Discriminator'; export interface TankComponentButtonProps extends ComponentProps { @@ -61,35 +62,13 @@ export function TankComponentButton({ {children} {discriminator !== undefined && ( - - {discriminator} - + {discriminator} )} {secondaryDiscriminator !== undefined && ( - + {secondaryDiscriminator} - + )} ); diff --git a/packages/website/src/components/Popover/index.module.css b/packages/website/src/components/Popover/index.module.css new file mode 100644 index 000000000..8f2aec11a --- /dev/null +++ b/packages/website/src/components/Popover/index.module.css @@ -0,0 +1,16 @@ +.Content { + z-index: 1; + + display: flex; + flex-direction: column; + gap: var(--space-4); + + margin: var(--space-4); + padding: var(--space-4); + max-width: calc(100dvw - 2 * var(--space-4)); + + background-color: var(--gray-2); + box-shadow: var(--shadow-subtle); + border-radius: var(--space-4); + outline: var(--outline-interactive) !important; +} diff --git a/packages/website/src/components/Popover/index.tsx b/packages/website/src/components/Popover/index.tsx new file mode 100644 index 000000000..96b5ab72b --- /dev/null +++ b/packages/website/src/components/Popover/index.tsx @@ -0,0 +1,5 @@ +import { Popover as _ } from "radix-ui"; +import { composeRadixComponents } from "../../ui/compose"; +import styles from "./index.module.css"; + +export const Popover = composeRadixComponents(_, styles); diff --git a/packages/website/src/components/Select/index.module.css b/packages/website/src/components/Select/index.module.css new file mode 100644 index 000000000..4d19d9a29 --- /dev/null +++ b/packages/website/src/components/Select/index.module.css @@ -0,0 +1,62 @@ +.Trigger { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-3); + + padding: var(--space-3) var(--space-4); + border: none; + border-radius: var(--max-size); + + background-color: var(--gray-1); + outline: var(--outline-interactive); + color: var(--gray-12); + font: inherit; + cursor: pointer; + + &:focus-visible { + outline: var(--outline-focus); + box-shadow: var(--shadow-focus); + } +} + +.Content { + /* above popovers */ + z-index: 2; + + overflow: hidden; + background-color: var(--gray-2); + box-shadow: var(--shadow-subtle); + border-radius: var(--space-4); + outline: var(--outline-interactive) !important; +} + +.Item { + position: relative; + + display: flex; + align-items: center; + + padding: var(--space-3) var(--space-4) var(--space-3) var(--space-5); + background-color: var(--gray-3); + + font-size: var(--font-size-minor); + cursor: pointer; + user-select: none; + + &[data-highlighted] { + outline: none; + background-color: var(--gray-5); + } + + &:not(:last-child) { + border-bottom: var(--space-1) solid var(--gray-2); + } +} + +.ItemIndicator { + position: absolute; + left: var(--space-3); + + display: flex; +} diff --git a/packages/website/src/components/Select/index.tsx b/packages/website/src/components/Select/index.tsx new file mode 100644 index 000000000..5e264acb5 --- /dev/null +++ b/packages/website/src/components/Select/index.tsx @@ -0,0 +1,5 @@ +import { Select as _ } from "radix-ui"; +import { composeRadixComponents } from "../../ui/compose"; +import styles from "./index.module.css"; + +export const Select = composeRadixComponents(_, styles); diff --git a/packages/website/src/components/Slider/index.module.css b/packages/website/src/components/Slider/index.module.css new file mode 100644 index 000000000..ff6f98d12 --- /dev/null +++ b/packages/website/src/components/Slider/index.module.css @@ -0,0 +1,42 @@ +.Root { + position: relative; + + display: flex; + align-items: center; + + height: var(--space-5); + touch-action: none; + user-select: none; +} + +.Track { + position: relative; + flex: 1; + height: var(--space-2); + + border-radius: var(--max-size); + background-color: var(--gray-5); +} + +.Range { + position: absolute; + height: 100%; + + border-radius: var(--max-size); + background-color: var(--accent-8); +} + +.Thumb { + display: block; + width: var(--space-5); + height: var(--space-5); + + border-radius: var(--max-size); + background-color: var(--gray-12); + box-shadow: var(--shadow-subtle); + + &:focus-visible { + outline: var(--outline-focus); + box-shadow: var(--shadow-focus); + } +} diff --git a/packages/website/src/components/Slider/index.tsx b/packages/website/src/components/Slider/index.tsx new file mode 100644 index 000000000..cd08098eb --- /dev/null +++ b/packages/website/src/components/Slider/index.tsx @@ -0,0 +1,5 @@ +import { Slider as _ } from "radix-ui"; +import { composeRadixComponents } from "../../ui/compose"; +import styles from "./index.module.css"; + +export const Slider = composeRadixComponents(_, styles); diff --git a/packages/website/src/components/TankopediaAntagonistControl/index.module.css b/packages/website/src/components/TankopediaAntagonistControl/index.module.css index cc7169855..b6951acf8 100644 --- a/packages/website/src/components/TankopediaAntagonistControl/index.module.css +++ b/packages/website/src/components/TankopediaAntagonistControl/index.module.css @@ -1,4 +1,6 @@ .antagonist-control { + pointer-events: auto; + align-items: flex-end; display: flex; flex-direction: column; justify-content: end; @@ -17,3 +19,12 @@ transform: translate(0, -50%); } } + +.group { + display: flex; + flex-direction: column; +} + +.gun-button { + position: relative; +} diff --git a/packages/website/src/components/TankopediaAntagonistControl/index.tsx b/packages/website/src/components/TankopediaAntagonistControl/index.tsx index bb94112a2..ea07d5d38 100644 --- a/packages/website/src/components/TankopediaAntagonistControl/index.tsx +++ b/packages/website/src/components/TankopediaAntagonistControl/index.tsx @@ -1,6 +1,5 @@ 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"; @@ -9,10 +8,13 @@ import { useUnwrapper } from "../../hooks/useUnwrapper"; import { Tankopedia } from "../../stores/tankopedia"; import { TankopediaDisplay } from "../../stores/tankopediaPersistent/constants"; import type { ComputedCharacteristics } from "../../types/characteristics"; +import { CustomShellButton } from "../CustomShellButton"; import { EqualIcon } from "../EqualIcon"; import { IconButton } from "../IconButton"; -import { CustomShellButton } from "../TankopediaOptions/components/CustomShellButton"; + +import { Discriminator } from "../Discriminator"; import { Text } from "../Text"; +import { Tooltip } from "../Tooltip"; import styles from "./index.module.css"; interface TankopediaAntagonistControlProps { @@ -25,14 +27,15 @@ export function TankopediaAntagonistControl({ const strings = useStrings(); const unwrap = useUnwrapper(); - const requestedDisplay = Tankopedia.use((state) => state.requestedDisplay); + const showControls = Tankopedia.use((state) => state.showControls); + const display = Tankopedia.use((state) => state.loaders.display.loaded); 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 hasCalibratedShells = useEquipment("antagonist", 103); + const hasEnhancedArmor = useEquipment("antagonist", 110); const tank = useDuel("antagonist", DuelModule.Tank); const gun = useDuel("antagonist", DuelModule.Gun); @@ -43,7 +46,7 @@ export function TankopediaAntagonistControl({ [tank.turrets], ); - const isVisible = requestedDisplay === TankopediaDisplay.DynamicArmor; + const isVisible = showControls && display === TankopediaDisplay.DynamicArmor; return (
@@ -56,166 +59,143 @@ export function TankopediaAntagonistControl({ {antagonistUniqueGuns.length > 1 && ( - - - - - {romanize(gun.tier)} - +
+ {[...antagonistUniqueGuns].reverse().map((entry) => { + const selected = entry.gun.id === gun.id; + const name = unwrap(entry.gun.name!); - Antagonist Gun - - + return ( + + { + Tankopedia.mutate((draft) => { + draft.antagonist.turret = entry.turret.id; + draft.antagonist.gun = entry.gun.id; + draft.antagonist.shell = entry.gun.shells[0].id; + draft.shot = undefined; + draft.customShell = undefined; + }); + }} + > + {name} - -
- {[...antagonistUniqueGuns].reverse().map(({ gun, turret }) => ( - - + {romanize(entry.gun.tier)} - // { - // 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; - }); - }} - /> - ))} +
+ {gun.shells.map((thisShell) => { + const selected = thisShell.id === shell.id && !hasCustomShell; + const name = unwrap(thisShell.name!); + + return ( + + { + Tankopedia.mutate((draft) => { + draft.antagonist.shell = thisShell.id; + draft.shot = undefined; + draft.customShell = undefined; + }); + }} + > + {name} + + + ); + })} +
-
- { - Tankopedia.mutate((draft) => { - draft.shot = 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; - } + > + Calibrated Shells + + - draft.shot = undefined; - }); - }} - > - Enhanced Armor + { + Tankopedia.mutate((draft) => { + draft.shot = undefined; + + if (hasEnhancedArmor) { + delete draft.antagonist.equipment[4]; + } else { + draft.antagonist.equipment[4] = 0; + } + }); }} - /> - + > + Enhanced Armor + +
- { - Tankopedia.mutate((draft) => { - draft.environment.equalize = !draft.environment.equalize; - 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 deleted file mode 100644 index 6df072174..000000000 --- a/packages/website/src/components/TankopediaOptions/components/CustomShellButton.tsx +++ /dev/null @@ -1,295 +0,0 @@ -import { literals } from "@blitzkit/i18n/src/literals"; -import { ShellType } from "@blitzkit/protos"; -import { - Button, - Flex, - IconButton, - Popover, - Select, - Slider, - Text, - TextField, -} from "@radix-ui/themes"; -import { throttle } from "lodash-es"; -import { type ChangeEvent } from "react"; -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 = useDuel("protagonist", DuelModule.Gun); - const antagonistShell = useDuel("antagonist", DuelModule.Shell); - const strings = useStrings(); - - return ( - { - if (!open || customShell) return; - - Tankopedia.mutate((draft) => { - draft.customShell = antagonistShell; - }); - }} - > - - - custom shell - - - - - {customShell && ( - <> - - - {strings.tanks.custom_shell.type} - { - const type = Number(value) as ShellType; - const modified = { ...customShell, type }; - - switch (type) { - case ShellType.SHELL_TYPE_AP: { - modified.normalization = 5; - modified.ricochet = 70; - modified.explosion_radius = undefined; - break; - } - case ShellType.SHELL_TYPE_APCR: { - modified.normalization = 2; - modified.ricochet = 70; - modified.explosion_radius = undefined; - break; - } - case ShellType.SHELL_TYPE_HEAT: { - modified.normalization = undefined; - modified.ricochet = undefined; - modified.explosion_radius = undefined; - } - case ShellType.SHELL_TYPE_HE: { - modified.normalization = undefined; - modified.ricochet = undefined; - modified.explosion_radius = - gun.shells.find( - (shell) => shell.type === ShellType.SHELL_TYPE_HE, - )?.explosion_radius ?? 1; - break; - } - } - - Tankopedia.mutate((draft) => { - draft.customShell = modified; - }); - }} - > - - - - - {strings.common.shells.ap} - - - {strings.common.shells.ap_cr} - - - {strings.common.shells.hc} - - - {strings.common.shells.he} - - - - - - - {strings.tanks.custom_shell.caliber} - { - Tankopedia.mutate((draft) => { - draft.customShell!.caliber = event.target.valueAsNumber; - }); - }} - onFocus={(event) => event.target.select()} - onKeyDown={(event) => { - event.stopPropagation(); - - if (event.key === "Enter") { - (event.target as HTMLInputElement).blur(); - } else if (event.key === "Escape") { - (event.target as HTMLInputElement).valueAsNumber = - customShell.caliber; - (event.target as HTMLInputElement).blur(); - } - }} - > - - {literals(strings.common.units.mm, { value: "" })} - - - - - - {strings.tanks.custom_shell.penetration} - ) => { - Tankopedia.mutate((draft) => { - draft.customShell!.penetration!.near = - event.target.valueAsNumber; - }); - }} - onFocus={(event) => event.target.select()} - onKeyDown={(event) => { - event.stopPropagation(); - - if (event.key === "Enter") { - (event.target as HTMLInputElement).blur(); - } else if (event.key === "Escape") { - (event.target as HTMLInputElement).valueAsNumber = - customShell.penetration!.near; - (event.target as HTMLInputElement).blur(); - } - }} - > - - {literals(strings.common.units.mm, { value: "" })} - - - - - {(customShell.type === ShellType.SHELL_TYPE_AP || - customShell.type === ShellType.SHELL_TYPE_APCR) && ( - <> - - {strings.tanks.custom_shell.normalization} - - - - {literals(strings.common.units.deg, { - value: (customShell.normalization ?? 0).toFixed(0), - })} - - { - Tankopedia.mutate((draft) => { - draft.customShell!.normalization = value; - }); - }} - /> - - - - - {strings.tanks.custom_shell.ricochet} - - - - {literals(strings.common.units.deg, { - value: (customShell.ricochet ?? 90).toFixed(0), - })} - - { - Tankopedia.mutate((draft) => { - draft.customShell!.ricochet = value; - }); - }, 1000)} - /> - - - - )} - - {customShell.type === ShellType.SHELL_TYPE_HE && ( - - Explosion radius - ) => { - Tankopedia.mutate((draft) => { - draft.customShell!.explosion_radius = - event.target.valueAsNumber; - }); - }} - onFocus={(event) => event.target.select()} - onKeyDown={(event) => { - event.stopPropagation(); - - if (event.key === "Enter") { - (event.target as HTMLInputElement).blur(); - } else if (event.key === "Escape") { - (event.target as HTMLInputElement).valueAsNumber = - customShell.explosion_radius ?? 1; - (event.target as HTMLInputElement).blur(); - } - }} - > - m - - - )} - - - - - - - - - - )} - - - ); -} diff --git a/packages/website/src/stores/tankopedia.ts b/packages/website/src/stores/tankopedia.ts index 0454d954a..454d14f73 100644 --- a/packages/website/src/stores/tankopedia.ts +++ b/packages/website/src/stores/tankopedia.ts @@ -1,5 +1,5 @@ import { createDefaultEnvironment, type TankEnvironment } from "@blitzkit/core"; -import type { TankDefinition } from "@blitzkit/protos"; +import type { ShellDefinition, TankDefinition } from "@blitzkit/protos"; import { Soapstone } from "soapstone"; import type { Vector3 } from "three"; import { api } from "../api/dynamic"; @@ -121,7 +121,7 @@ interface Tankopedia { // editStatic: boolean; // xpMultiplier: (typeof XP_MULTIPLIERS)[number]; - // customShell?: ShellDefinition; + customShell?: ShellDefinition; // statSearch?: string; }