From e6158dec8d8d2f3e3e627887716f95c046321620 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Mon, 9 Nov 2026 22:46:59 -0600 Subject: [PATCH] shot card --- packages/i18n/strings/en.json | 25 + .../src/components/ArmorPlateDisplay.tsx | 31 +- .../src/components/ShotCard/index.module.css | 85 ++++ .../{ShotDisplay.tsx => ShotCard/index.tsx} | 256 ++++++++-- .../src/components/ShotDisplayCard.tsx | 274 ----------- .../components/SpacedArmorSceneComponent.tsx | 14 +- .../components/ConfigurationChildWrapper.tsx | 24 - .../components/Consumables.tsx | 95 ---- .../components/Equipment.tsx | 53 --- .../components/Miscellaneous.tsx | 65 --- .../components/Modules.tsx | 446 ------------------ .../components/Provisions.tsx | 48 -- .../components/Skills.tsx | 82 ---- .../components/TankopediaSandbox/index.tsx | 14 +- .../TankopediaSkinSwitcher/index.tsx | 2 - .../website/src/components/Text/index.tsx | 16 +- .../[...locale]/tanks/[slug]/index.astro | 1 + 17 files changed, 373 insertions(+), 1158 deletions(-) create mode 100644 packages/website/src/components/ShotCard/index.module.css rename packages/website/src/components/{ShotDisplay.tsx => ShotCard/index.tsx} (53%) delete mode 100644 packages/website/src/components/ShotDisplayCard.tsx delete mode 100644 packages/website/src/components/Tankopedia/CharacteristicsSection/components/ConfigurationChildWrapper.tsx delete mode 100644 packages/website/src/components/Tankopedia/CharacteristicsSection/components/Consumables.tsx delete mode 100644 packages/website/src/components/Tankopedia/CharacteristicsSection/components/Equipment.tsx delete mode 100644 packages/website/src/components/Tankopedia/CharacteristicsSection/components/Miscellaneous.tsx delete mode 100644 packages/website/src/components/Tankopedia/CharacteristicsSection/components/Modules.tsx delete mode 100644 packages/website/src/components/Tankopedia/CharacteristicsSection/components/Provisions.tsx delete mode 100644 packages/website/src/components/Tankopedia/CharacteristicsSection/components/Skills.tsx diff --git a/packages/i18n/strings/en.json b/packages/i18n/strings/en.json index 9e18a6ad1..b64555c41 100644 --- a/packages/i18n/strings/en.json +++ b/packages/i18n/strings/en.json @@ -50,6 +50,31 @@ "reset": "Reset" }, + "shot_card": { + "element": { + "primary": "Primary", + "spaced": "Spaced", + "external": "External", + "gap": "Gap", + "gun": "Gun", + "track": "Tracks" + }, + + "status": { + "penetration": "{damage}hp penetration", + "blocked": "Blocked", + "ricochet": "Ricochet", + "splash": "{damage}hp splash" + }, + + "stats": { + "thickness_and_angle": "{thickness}mm {angle}°", + "nominal": "{thickness}mm nominal", + "ricochet_loss": "{loss}% penetration", + "ricochet_divider": "Ricochet (-25% penetration)" + } + }, + "tabs": { "model": "Model", "dynamic_armor": "Dynamic armor", diff --git a/packages/website/src/components/ArmorPlateDisplay.tsx b/packages/website/src/components/ArmorPlateDisplay.tsx index c3306f75e..d445ba217 100644 --- a/packages/website/src/components/ArmorPlateDisplay.tsx +++ b/packages/website/src/components/ArmorPlateDisplay.tsx @@ -15,7 +15,7 @@ import { LocaleProvider, useLocale } from "../hooks/useLocale"; import { useStrings } from "../hooks/useStrings"; import { App } from "../stores/app"; import { Tankopedia } from "../stores/tankopedia"; -import { layerTypeNames } from "./ShotDisplayCard"; +import { layerTypeNames } from "./ShotCard"; import { ArmorType } from "./SpacedArmorScene"; export function ArmorPlateDisplay() { @@ -102,11 +102,11 @@ export function ArmorPlateDisplay() { Tankopedia.mutate((draft) => { draft.highlightArmor = undefined; - const { protagonist } = Duel.state; - const tank = draft.model; - const turret = tank.turrets[protagonist.turret.id]; - const gun = turret.guns[protagonist.gun.id]; - const track = tank.tracks[protagonist.track.id]; + const { protagonist } = draft; + const tank = protagonist.model; + const turret = tank.turrets[protagonist.turret]; + const gun = turret.guns[protagonist.gun]; + const track = tank.tracks[protagonist.track]; if (highlightArmor.name.startsWith("hull_")) { tank.armor!.thickness[index] = thickness; @@ -155,18 +155,17 @@ export function ArmorPlateDisplay() { draft.highlightArmor = undefined; const initialState = Tankopedia.initial; - const { protagonist } = Duel.state; - const tank = draft.model; - const initialTank = initialState.model; - const turret = tank.turrets[protagonist.turret.id]; + const { protagonist } = draft; + const tank = protagonist.model; + const initialTank = initialState.protagonist.model; + const turret = tank.turrets[protagonist.turret]; const initialTurret = - initialTank.turrets[protagonist.turret.id]; - const gun = turret.guns[protagonist.gun.id]; - const initialGun = - initialTurret.guns[protagonist.gun.id]; - const track = tank.tracks[protagonist.track.id]; + initialTank.turrets[protagonist.turret]; + const gun = turret.guns[protagonist.gun]; + const initialGun = initialTurret.guns[protagonist.gun]; + const track = tank.tracks[protagonist.track]; const initialTrack = - initialTank.tracks[protagonist.track.id]; + initialTank.tracks[protagonist.track]; if (highlightArmor.name.startsWith("hull_")) { tank.armor!.thickness[index] = diff --git a/packages/website/src/components/ShotCard/index.module.css b/packages/website/src/components/ShotCard/index.module.css new file mode 100644 index 000000000..32a901d56 --- /dev/null +++ b/packages/website/src/components/ShotCard/index.module.css @@ -0,0 +1,85 @@ +.cardWrapper { + pointer-events: none; + + /* keeps the card clear of the tracer, on the side it isn't heading toward */ + &[data-going-up="true"] { + transform: translateY(50%); + margin-top: 4rem; + padding-top: 1rem; + } + + &[data-going-up="false"] { + transform: translateY(-50%); + margin-bottom: 4rem; + padding-bottom: 1rem; + } +} + +.point { + opacity: 0.75; + border-radius: 50%; +} + +.card { + display: flex; + flex-direction: column; + gap: var(--space-4); + + padding: var(--space-4); + border-radius: var(--space-3); + border: 1px solid var(--gray-a4); + background-color: var(--gray-2); + + white-space: nowrap; +} + +.section { + display: flex; + flex-direction: column; + gap: var(--space-3); +} + +.header { + display: flex; + justify-content: space-between; + align-items: center; + gap: var(--space-4); +} + +.layers { + display: table; + + /* column-gap doesn't apply to tables; border-spacing also pads the outer + edges, so pull them back in */ + border-spacing: var(--space-3) var(--space-2); + margin: 0 calc(-1 * var(--space-3)); +} + +.row { + display: table-row; +} + +.cell { + display: table-cell; + + &[data-align="right"] { + text-align: right; + } +} + +.divider { + display: flex; + align-items: center; + gap: var(--space-3); + + /* bleed to the card edges */ + margin: 0 calc(-1 * var(--space-4)); + + &::before, + &::after { + content: ""; + flex: 1; + height: 1px; + background-color: var(--gray-a4); + } +} diff --git a/packages/website/src/components/ShotDisplay.tsx b/packages/website/src/components/ShotCard/index.tsx similarity index 53% rename from packages/website/src/components/ShotDisplay.tsx rename to packages/website/src/components/ShotCard/index.tsx index 01c9eca70..0a107c634 100644 --- a/packages/website/src/components/ShotDisplay.tsx +++ b/packages/website/src/components/ShotCard/index.tsx @@ -1,5 +1,5 @@ import { J_HAT, K_HAT } from "@blitzkit/core"; -import { Box, Flex, Text } from "@radix-ui/themes"; +import { literals } from "@blitzkit/i18n/src/literals"; import { Html } from "@react-three/drei"; import { useFrame } from "@react-three/fiber"; import { clamp } from "lodash-es"; @@ -16,37 +16,236 @@ import { Path, Quaternion, } from "three"; -import { isHalloween } from "../core/blitzkit/isHalloween"; -import { LocaleProvider, useLocale } from "../hooks/useLocale"; +import { radToDeg } from "three/src/math/MathUtils.js"; +import { isHalloween } from "../../core/blitzkit/isHalloween"; +import { LocaleProvider, useLocale } from "../../hooks/useLocale"; +import { useStrings } from "../../hooks/useStrings"; import { Tankopedia, + type Shot, + type ShotLayer, type ShotLayerNonExternal, type ShotStatus, -} from "../stores/tankopedia"; -import { ShotDisplayCard } from "./ShotDisplayCard"; +} from "../../stores/tankopedia"; +import { classNames } from "../../ui/classNames"; +import { ArmorType } from "../SpacedArmorScene"; +import { Text, type TextProps } from "../Text"; +import styles from "./index.module.css"; -export const shotStatusColors: Record< - ShotStatus, - ComponentProps["color"] -> = { +export const shotStatusColors: Record = { blocked: "red", penetration: "green", ricochet: "yellow", splash: "orange", }; +export const layerTypeNames = { + [ArmorType.Primary]: "primary", + [ArmorType.Spaced]: "spaced", + [ArmorType.External]: "external", + null: "gap", +} as const; + +interface LayerEntryProps { + layerIndex?: string; + color: TextProps["color"]; + layerName: string; + layer: ShotLayer; + angle?: number; +} + +function LayerEntry({ + layerIndex, + color, + layerName, + layer, + angle, +}: LayerEntryProps) { + const locale = useLocale(); + const strings = useStrings(); + const { stats } = strings.tanks.shot_card; + const format = (value: number) => Math.round(value).toLocaleString(locale); + const mm = (value: number) => + literals(strings.common.units.mm, { value: format(value) }); + + return ( +
+
+ + {layerIndex} + +
+ +
+ {layerName} +
+ +
+ + {layer.type === null && mm(layer.distance * 1000)} + {layer.type === ArmorType.External && mm(layer.thickness)} + {(layer.type === ArmorType.Primary || + layer.type === ArmorType.Spaced) && + (angle === undefined + ? mm(layer.thicknessAngled) + : literals(stats.thickness_and_angle, { + thickness: format(layer.thicknessAngled), + angle: radToDeg(angle).toFixed(0), + }))} + +
+ +
+ {layer.type === null && ( + + {literals(stats.ricochet_loss, { + loss: Math.max(-100, -50 * layer.distance).toFixed(0), + })} + + )} + + {(layer.type === ArmorType.Primary || + layer.type === ArmorType.Spaced) && ( + + {literals(stats.nominal, { thickness: format(layer.thickness) })} + + )} +
+
+ ); +} + +interface LayersProps { + layers: ShotLayer[]; + status: Shot["in"]["status"]; + containsGaps: boolean; + /** Offset applied to displayed layer numbers (for post-ricochet layers). */ + offset?: number; +} + +function Layers({ layers, status, containsGaps, offset = 0 }: LayersProps) { + const strings = useStrings(); + + return ( +
+ {layers.map((layer, index) => { + const shiftedIndex = index + offset; + + return ( + + ); + })} +
+ ); +} + +interface ShotCardHtmlProps extends ComponentProps<"div"> { + shot: Shot; +} + +function ShotCardHtml({ shot, className, ...props }: ShotCardHtmlProps) { + const locale = useLocale(); + const strings = useStrings(); + + if (!shot) return null; + + const { status, stats } = strings.tanks.shot_card; + const damage = Math.round(shot.damage).toLocaleString(locale); + const hasOutLayers = shot.out !== undefined && shot.out.layers.length > 0; + const penetrationChance = shot.penetrationChance !== undefined && ( + + {literals(strings.common.units.percentage, { + value: (shot.penetrationChance * 100).toFixed(0), + })} + + ); + + return ( +
+
+
+ + {literals(status[shot.in.status], { damage })} + + + {!hasOutLayers && penetrationChance} +
+ + +
+ + {shot.out && hasOutLayers && ( + <> +
+ + {stats.ricochet_divider} + +
+ +
+
+ + {literals(status[shot.out.status], { damage })} + + + {penetrationChance} +
+ + +
+ + )} +
+ ); +} + const SURFACE_POINT_SIZE = 12; -export const SHOT_DISPLAY_LENGTH_INFINITY = 4; +const SHOT_DISPLAY_LENGTH_INFINITY = 4; const TRACER_THICK = 1 / 32; const TRACER_THIN = TRACER_THICK / 2; -export function ShotDisplay() { +export function ShotCard() { const shot = Tankopedia.use((state) => state.shot); const inTracer = useRef(null); const outTracer = useRef(null); const splashRadiusWrapper = useRef(null); const splashRadiusMaterial = useRef(null); - const { locale } = useLocale(); + const locale = useLocale(); useEffect(() => { if (shot?.splashRadius !== undefined) { @@ -140,20 +339,11 @@ export function ShotDisplay() { )} - +
- + - +
{[...shot.in.layers, ...(shot.out?.layers ?? [])].map((layer, index) => { @@ -173,21 +363,15 @@ export function ShotDisplay() { transform: "translateY(-50%)", }} > - -
- + /> ); })} diff --git a/packages/website/src/components/ShotDisplayCard.tsx b/packages/website/src/components/ShotDisplayCard.tsx deleted file mode 100644 index b0ee3b0d8..000000000 --- a/packages/website/src/components/ShotDisplayCard.tsx +++ /dev/null @@ -1,274 +0,0 @@ -import { literals } from "@blitzkit/i18n/src/literals"; -import { - Card, - Flex, - Inset, - Separator, - Text, - type CardProps, -} from "@radix-ui/themes"; -import type { ComponentProps } from "react"; -import { radToDeg } from "three/src/math/MathUtils.js"; -import { useLocale } from "../hooks/useLocale"; -import type { Shot, ShotLayer } from "../stores/tankopedia"; -import { shotStatusColors } from "./ShotDisplay"; -import { ArmorType } from "./SpacedArmorScene"; - -export const layerTypeNames = { - [ArmorType.Primary]: "primary", - [ArmorType.Spaced]: "spaced", - [ArmorType.External]: "external", - null: "gap", -} as const; - -function LayerEntry({ - layerIndex, - shotStatusColor, - layerName, - layer, - angle, -}: { - layerIndex?: string; - shotStatusColor: ComponentProps["color"]; - layerName: string; - layer: ShotLayer; - angle?: number; -}) { - const { locale, strings } = useLocale(); - - return ( - - - - {layerIndex} - - - - - - {layerName} - - - - - {layer.type === null && ( - - {literals(strings.common.units.mm, { - value: Math.round(layer.distance * 1000).toLocaleString(locale), - })} - - )} - - {layer.type === ArmorType.External && ( - - {literals(strings.common.units.mm, { - value: Math.round(layer.thickness).toLocaleString(locale), - })} - - )} - {(layer.type === ArmorType.Primary || - layer.type === ArmorType.Spaced) && ( - - {angle === undefined - ? literals(strings.common.units.mm, { - value: Math.round(layer.thicknessAngled).toLocaleString( - locale - ), - }) - : literals( - strings.website.tools.tankopedia.sandbox.dynamic.shot_card - .stats.thickness_and_angle, - { - thickness: Math.round(layer.thicknessAngled).toLocaleString( - locale - ), - angle: radToDeg(angle ?? 0).toFixed(0), - } - )} - - )} - - - - {layer.type === null && ( - - {literals( - strings.website.tools.tankopedia.sandbox.dynamic.shot_card.stats - .ricochet_loss, - { loss: Math.max(-100, -50 * layer.distance).toFixed(0) } - )} - - )} - - {(layer.type === ArmorType.Primary || - layer.type === ArmorType.Spaced) && ( - - {literals( - strings.website.tools.tankopedia.sandbox.dynamic.shot_card.stats - .nominal, - { thickness: Math.round(layer.thickness).toLocaleString(locale) } - )} - - )} - - - ); -} - -interface ShotDisplayCardProps extends CardProps { - shot: Shot; -} - -export function ShotDisplayCard({ shot, ...props }: ShotDisplayCardProps) { - const { locale, strings } = useLocale(); - - if (!shot) return null; - - const outTitleColor = shot.out - ? shotStatusColors[shot.out.status] - : undefined; - const inTitleColor = shotStatusColors[shot.in.status]; - const hasOutLayers = shot.out !== undefined && shot.out.layers.length > 0; - const penetrationChance = shot.penetrationChance !== undefined && ( - - {literals(strings.common.units.percentage, { - value: (shot.penetrationChance * 100).toFixed(0), - })} - - ); - - return ( - - - - - - {literals( - strings.website.tools.tankopedia.sandbox.dynamic.shot_card - .status[shot.in.status], - { damage: Math.round(shot.damage).toLocaleString(locale) } - )} - - - {!hasOutLayers && penetrationChance} - - - - {shot.in.layers.map((layer, index) => { - const layerName = - strings.website.tools.tankopedia.sandbox.dynamic.shot_card - .element[ - layer.type === ArmorType.External - ? layer.variant - : layerTypeNames[`${layer.type}`] - ]; - const layerIndex = - layer.type === null - ? undefined - : `${(shot.containsGaps ? index / 2 : index) + 1}.`; - const shotStatusColor = - shot.in.status === "splash" - ? shotStatusColors.splash - : shotStatusColors[layer.status]; - - return ( - - ); - })} -
-
- - {shot.out && shot.out.layers.length > 0 && ( - <> - - - - - ricochet (-25% penetration) - - - - - - - - {literals( - strings.website.tools.tankopedia.sandbox.dynamic.shot_card - .status[shot.out.status], - { damage: Math.round(shot.damage).toLocaleString(locale) } - )} - - - {penetrationChance} - - - - {shot.out.layers.map((layer, index) => { - const layerName = - strings.website.tools.tankopedia.sandbox.dynamic.shot_card - .element[ - layer.type === ArmorType.External - ? layer.variant - : layerTypeNames[`${layer.type}`] - ]; - const shiftedIndex = index + shot.out!.layers.length; - const layerIndex = - layer.type === null - ? undefined - : `${ - (shot.containsGaps ? shiftedIndex / 2 : shiftedIndex) + - 1 - }.`; - const shotStatusColor = - shot.out!.status === "splash" - ? shotStatusColors.splash - : shotStatusColors[layer.status]; - - return ( - - ); - })} - - - )} -
-
- ); -} diff --git a/packages/website/src/components/SpacedArmorSceneComponent.tsx b/packages/website/src/components/SpacedArmorSceneComponent.tsx index 62b552c0d..cdf7ff901 100644 --- a/packages/website/src/components/SpacedArmorSceneComponent.tsx +++ b/packages/website/src/components/SpacedArmorSceneComponent.tsx @@ -15,6 +15,10 @@ import { import { degToRad } from "three/src/math/MathUtils.js"; import { defaultEqualizer } from "../config/equalizer"; import { jsxTree } from "../core/blitzkit/jsxTree"; +import { + SPALL_LINER_HE_DAMAGE_DELTA, + SPALL_LINER_PROVISION_ID, +} from "../core/blitzkit/spallLiner"; import { discardClippingPlane } from "../core/three/discardClippingPlane"; import { DuelModule, getDuel } from "../hooks/useDuel"; import { hasEquipment } from "../hooks/useEquipment"; @@ -240,8 +244,8 @@ export function SpacedArmorSceneComponent({ shell.type === ShellType.SHELL_TYPE_HE ? "blocked" : blocked - ? "blocked" - : "penetration", + ? "blocked" + : "penetration", variant: layer.variant, }); } else { @@ -292,8 +296,8 @@ export function SpacedArmorSceneComponent({ layer.type !== ArmorType.Primary ? "blocked" : blocked - ? "blocked" - : "penetration", + ? "blocked" + : "penetration", angle, }); } @@ -415,7 +419,7 @@ export function SpacedArmorSceneComponent({ shot.damage *= antagonistEqualizer.damage; - const hasSpallLiner = Duel.state.protagonist.provisions.includes( + const hasSpallLiner = Tankopedia.state.protagonist.provisions.includes( SPALL_LINER_PROVISION_ID, ); if (hasSpallLiner && shell.type === ShellType.SHELL_TYPE_HE) { diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/ConfigurationChildWrapper.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/ConfigurationChildWrapper.tsx deleted file mode 100644 index 5b161ebb7..000000000 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/ConfigurationChildWrapper.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { type FlexProps, Flex } from "@radix-ui/themes"; -import type { ReactNode } from "react"; - -type ConfigurationChildWrapperProps = FlexProps & { - children: ReactNode; -}; - -export function ConfigurationChildWrapper({ - children, - style, - ...props -}: ConfigurationChildWrapperProps) { - return ( - - {children} - - ); -} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Consumables.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Consumables.tsx deleted file mode 100644 index 02949cc26..000000000 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Consumables.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { checkConsumableProvisionInclusivity } from "@blitzkit/core/src/blitzkit/checkConsumableProvisionInclusivity"; -import { InfoCircledIcon } from "@radix-ui/react-icons"; -import { - Button, - Flex, - Heading, - IconButton, - Popover, - Text, -} from "@radix-ui/themes"; -import { api } from "../../../../api/dynamic"; -import { useEquipment } from "../../../../hooks/useEquipment"; -import { useLocale } from "../../../../hooks/useLocale"; -import { Duel } from "../../../../stores/duel"; -import { ConsumablesManager } from "../../../ConsumablesManager"; -import { ConfigurationChildWrapper } from "./ConfigurationChildWrapper"; - -const consumableDefinitions = await api.consumables(); - -export function Consumables() { - const protagonist = Duel.use((state) => state.protagonist); - const consumables = Duel.use((state) => state.protagonist.consumables); - const consumablesList = Object.values( - consumableDefinitions.consumables, - ).filter((consumable) => - checkConsumableProvisionInclusivity( - consumable, - protagonist.tank, - protagonist.gun, - ), - ); - const cooldownBooster = Duel.use( - (state) => state.protagonist.cooldownBooster, - ); - const hasConsumableDeliverySystem = useEquipment("protagonist", 118); - const hasHighEndConsumables = useEquipment("protagonist", 101); - const { strings } = useLocale(); - - return ( - - - - - {strings.website.tools.tankopedia.configuration.consumables.title} - - - - - - - - - - - - { - strings.website.tools.tankopedia.configuration.consumables - .info - } - - - - - - - - - { - Duel.mutate((draft) => { - draft.protagonist.consumables = consumables; - }); - }} - justify={{ initial: "center", sm: "start" }} - /> - - ); -} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Equipment.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Equipment.tsx deleted file mode 100644 index ff37622e0..000000000 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Equipment.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { Button, Flex, Heading } from "@radix-ui/themes"; -import { api } from "../../../../api/dynamic"; -import { useLocale } from "../../../../hooks/useLocale"; -import { Duel } from "../../../../stores/duel"; -import { EquipmentManager } from "../../../EquipmentManager"; -import { ConfigurationChildWrapper } from "./ConfigurationChildWrapper"; - -const equipmentDefinitions = await api.equipment(); - -export function Equipment() { - const protagonist = Duel.use((state) => state.protagonist); - const equipmentPreset = - equipmentDefinitions.presets[protagonist.tank.equipment_preset]; - const equipmentMatrix = Duel.use( - (state) => state.protagonist.equipmentMatrix, - ); - const { strings } = useLocale(); - - return ( - - - - {strings.website.tools.tankopedia.configuration.equipment.title} - - - - - { - Duel.mutate((draft) => { - draft.protagonist.equipmentMatrix = matrix; - }); - }} - /> - - ); -} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Miscellaneous.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Miscellaneous.tsx deleted file mode 100644 index 298966160..000000000 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Miscellaneous.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { alias } from "@blitzkit/core"; -import { Flex, Heading } from "@radix-ui/themes"; -import { useLocale } from "../../../../hooks/useLocale"; -import { Duel } from "../../../../stores/duel"; -import { GenericTankComponentButton } from "../../../ModuleButtons/GenericTankComponentButton"; -import { ConfigurationChildWrapper } from "./ConfigurationChildWrapper"; - -export function Miscellaneous() { - const camouflage = Duel.use((state) => state.protagonist.camouflage); - const cooldownBooster = Duel.use( - (state) => state.protagonist.cooldownBooster, - ); - const { tank } = Duel.use((state) => state.protagonist); - const { strings } = useLocale(); - - return ( - - - {strings.website.tools.tankopedia.configuration.miscellaneous.title} - - - - {!tank.fixed_camouflage && ( - { - Duel.mutate((draft) => { - draft.protagonist.camouflage = !camouflage; - }); - }} - /> - )} - 0} - banner={ - cooldownBooster === 0 - ? undefined - : cooldownBooster === 1 - ? "#afb0abc0" - : cooldownBooster === 2 - ? "#5f72cbc0" - : "#9b3cc0c0" - } - iconStyles={{ - transform: "translate(-50%, -50%) scale(0.8)", - }} - onClick={() => { - Duel.mutate((draft) => { - draft.protagonist.cooldownBooster++; - if (draft.protagonist.cooldownBooster === 4) - draft.protagonist.cooldownBooster = 0; - }); - }} - /> - - - ); -} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Modules.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Modules.tsx deleted file mode 100644 index b123944e2..000000000 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Modules.tsx +++ /dev/null @@ -1,446 +0,0 @@ -import { - alias, - formatCompact, - tankIcon, - romanize, -} from "@blitzkit/core"; -import { - EngineDefinition, - GunDefinition, - ModuleType, - TrackDefinition, - TurretDefinition, - type TankDefinition, - type Unlock, -} from "@blitzkit/protos"; -import { - Button, - ChevronDownIcon, - Flex, - Heading, - IconButton, - Text, - Tooltip, -} from "@radix-ui/themes"; -import { api } from "../../../../api/dynamic"; -import { useLocale } from "../../../../hooks/useLocale"; -import { Duel } from "../../../../stores/duel"; -import { LinkI18n } from "../../../LinkI18n"; -import { ConfigurationChildWrapper } from "./ConfigurationChildWrapper"; - -type ModuleDefinition = - | TurretDefinition - | GunDefinition - | TrackDefinition - | EngineDefinition; - -function extractModule(raw: ModuleDefinition | TankDefinition) { - if ("gun_type" in raw) { - return raw; - } else - return raw as Exclude; -} - -interface ModuleWithUnlocks { - unlocks: Unlock[]; -} - -const moduleTypeTypeIconName: Record = { - [ModuleType.MODULE_TYPE_VEHICLE]: "", - [ModuleType.MODULE_TYPE_ENGINE]: "engine", - [ModuleType.MODULE_TYPE_TRACKS]: "chassis", - [ModuleType.MODULE_TYPE_TURRET]: "turret", - [ModuleType.MODULE_TYPE_GUN]: "gun", -}; - -function ModuleButton({ - tier, - selected, - unlock, - top, - tooltip, - onClick, -}: { - unlock: Unlock; - tier: number; - selected: boolean; - top: boolean; - tooltip?: string; - onClick: () => void; -}) { - const isTank = unlock.type === ModuleType.MODULE_TYPE_VEHICLE; - const { locale } = useLocale(); - - const button = ( - - - {romanize(tier)} - - - {unlock.cost!.value > 0 && ( - - - {formatCompact(locale, unlock.cost!.value)} - - - {unlock.cost!.type} - - )} - - {ModuleType[unlock.type]} - - ); - - const node = isTank ? ( - - {button} - - ) : ( - button - ); - - if (!tooltip) return node; - - return {node}; -} - -const tankDefinitions = await api.tanks(); - -export function Modules() { - const tank = Duel.use((state) => state.protagonist.tank); - const hasUpgrades = - tank.turrets.length > 1 || - tank.turrets[0].guns.length > 1 || - tank.engines.length > 1 || - tank.tracks.length > 1; - const turret = Duel.use((state) => state.protagonist.turret); - const gun = Duel.use((state) => state.protagonist.gun); - const engine = Duel.use((state) => state.protagonist.engine); - const track = Duel.use((state) => state.protagonist.track); - const turret0 = tank.turrets[0]; - const gun0 = turret0.guns[0]; - const engine0 = tank.engines[0]; - const track0 = tank.tracks[0]; - const topTurret = tank.turrets.at(-1)!; - const topGun = topTurret.guns.at(-1)!; - const topEngine = tank.engines.at(-1)!; - const topTrack = tank.tracks.at(-1)!; - const { strings, unwrap } = useLocale(); - - function setByUnlock(unlock: Unlock) { - Duel.mutate((draft) => { - if (unlock.type === ModuleType.MODULE_TYPE_TURRET) { - draft.protagonist.turret = draft.protagonist.tank.turrets.find( - (turret) => turret.id === unlock.id, - )!; - - // Check if current gun exists in new turret - const gunInNewTurret = draft.protagonist.turret.guns.find( - (gun) => gun.id === draft.protagonist.gun.id, - ); - - if (gunInNewTurret) { - // Gun exists - get it FROM this turret - draft.protagonist.gun = gunInNewTurret; - draft.protagonist.shell = gunInNewTurret.shells[0]; - } else { - // Gun doesn't exist - select top gun from new turret - draft.protagonist.gun = draft.protagonist.turret.guns.at(-1)!; - draft.protagonist.shell = draft.protagonist.gun.shells[0]; - } - } else if (unlock.type === ModuleType.MODULE_TYPE_GUN) { - const gunInTurret = draft.protagonist.turret.guns.find( - (gun) => gun.id === unlock.id, - ); - if (gunInTurret) { - draft.protagonist.gun = gunInTurret; - draft.protagonist.shell = gunInTurret.shells[0]; - } else { - // TODO: warn somehow? - const suitableTurret = draft.protagonist.tank.turrets.find((turret) => - turret.guns.some((gun) => gun.id === unlock.id), - )!; - const gunInSuitableTurret = suitableTurret.guns.find( - (gun) => gun.id === unlock.id, - )!; - - draft.protagonist.turret = suitableTurret; - draft.protagonist.gun = gunInSuitableTurret; - draft.protagonist.shell = gunInSuitableTurret.shells[0]; - } - } else if (unlock.type === ModuleType.MODULE_TYPE_ENGINE) { - draft.protagonist.engine = draft.protagonist.tank.engines.find( - (engine) => engine.id === unlock.id, - )!; - } else if (unlock.type === ModuleType.MODULE_TYPE_TRACKS) { - draft.protagonist.track = draft.protagonist.tank.tracks.find( - (track) => track.id === unlock.id, - )!; - } - }); - } - - function tree(type: ModuleType, unlocks: Unlock[]) { - return ( - - {unlocks.map((unlock, index) => { - const first = index === 0; - const last = index === unlocks.length - 1; - const central = !first && !last; - let module: ModuleDefinition | TankDefinition | undefined = undefined; - - if (unlock.type === ModuleType.MODULE_TYPE_TRACKS) { - module = tank.tracks.find((track) => track.id === unlock.id)!; - } else if (unlock.type === ModuleType.MODULE_TYPE_ENGINE) { - module = tank.engines.find((engine) => engine.id === unlock.id)!; - } else if (unlock.type === ModuleType.MODULE_TYPE_TURRET) { - module = tank.turrets.find((turret) => turret.id === unlock.id)!; - } else if (unlock.type === ModuleType.MODULE_TYPE_GUN) { - module = tank.turrets - .find((turret) => - turret.guns.some((gun) => gun.id === unlock.id), - )! - .guns.find((gun) => gun.id === unlock.id)!; - } else if (unlock.type === ModuleType.MODULE_TYPE_VEHICLE) { - module = tankDefinitions.tanks[unlock.id]; - } - - if (module === undefined) return null; - - const tooltip = unwrap(module.name!); - - return ( - - {unlock.cost!.value > 0 && ( - <> - {unlocks.length > 1 && ( -
- )} - - -
- - - - )} - - setByUnlock(unlock)} - /> - - {(extractModule(module) as ModuleWithUnlocks).unlocks && - (extractModule(module) as ModuleWithUnlocks).unlocks!.length > - 1 && ( -
- )} - - {(extractModule(module) as ModuleWithUnlocks).unlocks && - tree( - type, - (extractModule(module) as ModuleWithUnlocks).unlocks!, - )} - - ); - })} - - ); - } - - return ( - - - - {strings.website.tools.tankopedia.configuration.modules.title} - - - {hasUpgrades && ( - <> - - - - )} - - - - {tree(ModuleType.MODULE_TYPE_ENGINE, [ - { - cost: { type: "xp", value: 0 }, - id: engine0.id, - type: ModuleType.MODULE_TYPE_ENGINE, - }, - ])} - {tree(ModuleType.MODULE_TYPE_TRACKS, [ - { - cost: { type: "xp", value: 0 }, - id: track0.id, - type: ModuleType.MODULE_TYPE_TRACKS, - }, - ])} - {tree(ModuleType.MODULE_TYPE_TURRET, [ - { - cost: { type: "xp", value: 0 }, - id: turret0.id, - type: ModuleType.MODULE_TYPE_TURRET, - }, - ])} - {tree(ModuleType.MODULE_TYPE_GUN, [ - { - cost: { type: "xp", value: 0 }, - id: gun0.id, - type: ModuleType.MODULE_TYPE_GUN, - }, - ])} - - - ); -} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Provisions.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Provisions.tsx deleted file mode 100644 index 8052b9d57..000000000 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Provisions.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { availableProvisions } from "@blitzkit/core"; -import { Button, Flex, Heading } from "@radix-ui/themes"; -import { api } from "../../../../api/dynamic"; -import { useLocale } from "../../../../hooks/useLocale"; -import { Duel } from "../../../../stores/duel"; -import { ProvisionsManager } from "../../../ProvisionsManager"; -import { ConfigurationChildWrapper } from "./ConfigurationChildWrapper"; - -const provisionDefinitions = await api.provisions(); - -export function Provisions() { - const { tank, gun } = Duel.use((state) => state.protagonist); - const provisions = Duel.use((state) => state.protagonist.provisions); - const provisionsList = availableProvisions(tank, gun, provisionDefinitions); - const { strings } = useLocale(); - - return ( - - - - {strings.website.tools.tankopedia.configuration.provisions.title} - - - - - provision.id)} - selected={provisions} - disabled={tank.max_provisions === provisions.length} - onChange={(provisions) => { - Duel.mutate((draft) => { - draft.protagonist.provisions = provisions; - }); - }} - /> - - ); -} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Skills.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Skills.tsx deleted file mode 100644 index 8a82df99e..000000000 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Skills.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { MAX_SKILL_LEVEL } from "@blitzkit/core"; -import { InfoCircledIcon } from "@radix-ui/react-icons"; -import { - Button, - Flex, - Heading, - IconButton, - Popover, - Text, -} from "@radix-ui/themes"; -import { useLocale } from "../../../../hooks/useLocale"; -import { Tankopedia } from "../../../../stores/tankopedia"; -import { CrewSkillManager } from "../../../CrewSkillManager"; -import { ConfigurationChildWrapper } from "./ConfigurationChildWrapper"; - -export function Skills() { - const skillLevels = Tankopedia.use((state) => state.skills); - const { strings } = useLocale(); - - return ( - - - - - {strings.website.tools.tankopedia.configuration.skills.title} - - - - - - - - - - - - - {strings.website.tools.tankopedia.configuration.skills.info} - - - - - - - - - - - { - Tankopedia.mutate((draft) => { - draft.skills = skills; - }); - }} - /> - - ); -} diff --git a/packages/website/src/components/TankopediaSandbox/index.tsx b/packages/website/src/components/TankopediaSandbox/index.tsx index 148ff23ab..d050106c1 100644 --- a/packages/website/src/components/TankopediaSandbox/index.tsx +++ b/packages/website/src/components/TankopediaSandbox/index.tsx @@ -17,7 +17,7 @@ import { CanvasControl } from "../CanvasControl"; import { InitialCameraAligner } from "../InitialCameraAligner"; import { Lighting, transitionEvent } from "../Lighting"; import { SceneProps } from "../SceneProps"; -import { ShotDisplay } from "../ShotDisplay"; +import { ShotCard } from "../ShotCard"; import { SmartCanvas } from "../SmartCanvas"; import { TankDynamicArmor } from "../TankDynamicArmor"; import { TankModel } from "../TankModel"; @@ -75,7 +75,7 @@ export function TankopediaSandbox({ gunModelDefinition.pitch!, turretModelDefinition.yaw, hasImprovedVerticalStabilizer, - hasDownImprovedVerticalStabilizer, + hasDownImprovedVerticalStabilizer ); modelTransformEvent.dispatch({ pitch, yaw }); @@ -89,7 +89,7 @@ export function TankopediaSandbox({ 0, gunModelDefinition.pitch!, turretModelDefinition.yaw, - hasImprovedVerticalStabilizer, + hasImprovedVerticalStabilizer ); modelTransformEvent.dispatch({ pitch, yaw }); @@ -103,7 +103,7 @@ export function TankopediaSandbox({ 0, gunModelDefinition.pitch!, turretModelDefinition.yaw, - hasImprovedVerticalStabilizer, + hasImprovedVerticalStabilizer ); modelTransformEvent.dispatch({ pitch, yaw }); @@ -131,7 +131,7 @@ export function TankopediaSandbox({ modelTransformEvent.last!.yaw, gunModelDefinition.pitch!, turretModelDefinition.yaw, - hasImprovedVerticalStabilizer, + hasImprovedVerticalStabilizer ); modelTransformEvent.dispatch({ pitch, yaw }); @@ -191,7 +191,7 @@ function Content({ thicknessRange }: { thicknessRange: ThicknessRange }) { const { hasPbr } = useModel(tank.id); const hideTankModelUnderArmor = TankopediaPersistent.use( - (state) => state.hideTankModelUnderArmor, + (state) => state.hideTankModelUnderArmor ); return ( @@ -208,7 +208,7 @@ function Content({ thicknessRange }: { thicknessRange: ThicknessRange }) { )} - + diff --git a/packages/website/src/components/TankopediaSkinSwitcher/index.tsx b/packages/website/src/components/TankopediaSkinSwitcher/index.tsx index 6cf61a20c..a66c4c33b 100644 --- a/packages/website/src/components/TankopediaSkinSwitcher/index.tsx +++ b/packages/website/src/components/TankopediaSkinSwitcher/index.tsx @@ -47,8 +47,6 @@ export function TankopediaSkinSwitcher() { hasSkinAnimations(tank.id, loadedSkin) || hasSkinAnimations(tank.id, loadedAttachment); - console.log("hasAnimations", hasAnimations); - return (
{groups.map((group, index) => ( diff --git a/packages/website/src/components/Text/index.tsx b/packages/website/src/components/Text/index.tsx index 6adeefc03..0877200d4 100644 --- a/packages/website/src/components/Text/index.tsx +++ b/packages/website/src/components/Text/index.tsx @@ -23,10 +23,18 @@ export function Text({ }: TextProps) { const scale = color === "gray" && lowContrast ? "white" : color; const isBW = color === "black" || color === "white"; - let power: string | number = isBW ? "a12" : 12; + let power = color === "gray" ? "12" : "11"; if (lowContrast) { - power = color === "gray" ? "a9" : isBW ? "a11" : 11; + power = "5"; + + if (color === "gray") { + power = "a9"; + } + + if (isBW) { + power = "a11"; + } } return ( @@ -36,9 +44,7 @@ export function Text({ data-size={size} data-weight={weight} data-align={align === "left" ? undefined : align} - style={{ - color: `var(--${scale}-${power})`, - }} + style={{ color: `var(--${scale}-${power})` }} {...props} /> ); diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/tanks/[slug]/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/tanks/[slug]/index.astro index 89c26f73a..8247dcc52 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/tanks/[slug]/index.astro +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/tanks/[slug]/index.astro @@ -28,6 +28,7 @@ const { id, locale } = Astro.props; +