From 9e388154756d1ad3206a1b42b970137762950df6 Mon Sep 17 00:00:00 2001 From: Mark Greene Date: Tue, 14 Jul 2026 10:31:14 +1200 Subject: [PATCH 01/21] Add Spall Liner toggle to Tankopedia model viewer --- packages/i18n/strings/en.json | 4 + .../SpacedArmorSceneComponent/index.tsx | 11 ++ .../Options/components/SpallLinerSwitcher.tsx | 103 ++++++++++++++++++ .../HeroSection/components/Options/index.tsx | 3 + .../website/src/core/blitzkit/spallLiner.ts | 8 ++ .../src/core/blitzkit/tankCharacteristics.ts | 6 +- 6 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 packages/website/src/components/Tankopedia/HeroSection/components/Options/components/SpallLinerSwitcher.tsx create mode 100644 packages/website/src/core/blitzkit/spallLiner.ts diff --git a/packages/i18n/strings/en.json b/packages/i18n/strings/en.json index 534e2fa8d..9ac4145ee 100644 --- a/packages/i18n/strings/en.json +++ b/packages/i18n/strings/en.json @@ -527,6 +527,10 @@ "label": "Edit", "reset": "Reset" } + }, + + "spall_liner": { + "swap_prompt": "Spall Liner will replace one of your active provisions. Choose which one to remove:" } }, diff --git a/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/index.tsx b/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/index.tsx index f2c770593..784836464 100644 --- a/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/index.tsx +++ b/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/index.tsx @@ -18,6 +18,10 @@ import { import { degToRad } from "three/src/math/MathUtils.js"; import { hasEquipment } from "../../../../core/blitzkit/hasEquipment"; import { jsxTree } from "../../../../core/blitzkit/jsxTree"; +import { + SPALL_LINER_HE_DAMAGE_DELTA, + SPALL_LINER_PROVISION_ID, +} from "../../../../core/blitzkit/spallLiner"; import { defaultEqualizer } from "../../../../core/blitzkit/tankToDuelMember"; import { discardClippingPlane } from "../../../../core/three/discardClippingPlane"; import { Duel } from "../../../../stores/duel"; @@ -384,6 +388,13 @@ export function SpacedArmorSceneComponent({ shot.damage *= antagonistEqualizer.damage; + const hasSpallLiner = Duel.state.protagonist.provisions.includes( + SPALL_LINER_PROVISION_ID, + ); + if (hasSpallLiner && shell.type === ShellType.SHELL_TYPE_HE) { + shot.damage *= 1 + SPALL_LINER_HE_DAMAGE_DELTA; + } + return shot; }, [camera], diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/SpallLinerSwitcher.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/SpallLinerSwitcher.tsx new file mode 100644 index 000000000..3b66d75dc --- /dev/null +++ b/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/SpallLinerSwitcher.tsx @@ -0,0 +1,103 @@ +import { alias, availableProvisions } from "@blitzkit/core"; +import { Flex, IconButton, Popover, Text } from "@radix-ui/themes"; +import { api } from "../../../../../../core/blitzkit/api"; +import { SPALL_LINER_PROVISION_ID } from "../../../../../../core/blitzkit/spallLiner"; +import { useLocale } from "../../../../../../hooks/useLocale"; +import { Duel } from "../../../../../../stores/duel"; +import { Tankopedia } from "../../../../../../stores/tankopedia"; +import { ProvisionButton } from "../../../../../ModuleButtons/ProvisionButton"; + +const provisionDefinitions = await api.provisionDefinitions(); + +export function SpallLinerSwitcher() { + const { tank, gun, provisions } = Duel.use((state) => state.protagonist); + const { strings, unwrap } = useLocale(); + const isSpallLinerActive = provisions.includes(SPALL_LINER_PROVISION_ID); + const hasSlotAvailable = provisions.length < tank.max_provisions; + + const canEquipSpallLiner = availableProvisions( + tank, + gun, + provisionDefinitions, + ).some((provision) => provision.id === SPALL_LINER_PROVISION_ID); + + if (!canEquipSpallLiner) return null; + + function equip(replacing?: number) { + Duel.mutate((draft) => { + if (replacing !== undefined) { + const provisions = draft.protagonist.provisions; + provisions[provisions.indexOf(replacing)] = SPALL_LINER_PROVISION_ID; + } else { + draft.protagonist.provisions.push(SPALL_LINER_PROVISION_ID); + } + }); + Tankopedia.mutate((draft) => { + draft.shot = undefined; + }); + } + + function unequip() { + Duel.mutate((draft) => { + draft.protagonist.provisions = draft.protagonist.provisions.filter( + (id) => id !== SPALL_LINER_PROVISION_ID, + ); + }); + Tankopedia.mutate((draft) => { + draft.shot = undefined; + }); + } + + const button = ( + { + if (isSpallLinerActive) { + unequip(); + } else if (hasSlotAvailable) { + equip(); + } + }} + > + {unwrap(provisionDefinitions.provisions[SPALL_LINER_PROVISION_ID].name!)} + + ); + + if (isSpallLinerActive || hasSlotAvailable) return button; + + return ( + + {button} + + + + + {strings.website.tools.tankopedia.sandbox.spall_liner.swap_prompt} + + + + {provisions.map((id) => ( + + equip(id)} + /> + + ))} + + + + + ); +} diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/Options/index.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/Options/index.tsx index 05fc67d92..5856c66d0 100644 --- a/packages/website/src/components/Tankopedia/HeroSection/components/Options/index.tsx +++ b/packages/website/src/components/Tankopedia/HeroSection/components/Options/index.tsx @@ -46,6 +46,7 @@ import { TankSearch } from "../../../../TankSearch"; import { CustomShellButton } from "./components/CustomShellButton"; import { DynamicArmorSwitcher } from "./components/DynamicArmorSwitcher"; import { QuickInputs } from "./components/QuickInputs"; +import { SpallLinerSwitcher } from "./components/SpallLinerSwitcher"; import { Thicknesses } from "./components/Thicknesses"; type OptionsProps = MaybeSkeletonComponentProps & { @@ -278,6 +279,8 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) { }} /> + + {!skeleton && ( diff --git a/packages/website/src/core/blitzkit/spallLiner.ts b/packages/website/src/core/blitzkit/spallLiner.ts new file mode 100644 index 000000000..80598d5c1 --- /dev/null +++ b/packages/website/src/core/blitzkit/spallLiner.ts @@ -0,0 +1,8 @@ +export const SPALL_LINER_PROVISION_ID = 101; + +/** + * Matches the in-game reduction Spall Liner grants against HE splash + * damage. Shared between the characteristics panel and the Dynamic + * Armor 3D viewer's damage readout so the figure isn't duplicated. + */ +export const SPALL_LINER_HE_DAMAGE_DELTA = -0.2; diff --git a/packages/website/src/core/blitzkit/tankCharacteristics.ts b/packages/website/src/core/blitzkit/tankCharacteristics.ts index d9d041098..f03bbf3e7 100644 --- a/packages/website/src/core/blitzkit/tankCharacteristics.ts +++ b/packages/website/src/core/blitzkit/tankCharacteristics.ts @@ -22,6 +22,7 @@ import { } from "@blitzkit/core"; import { coefficient } from "@blitzkit/core/src/blitzkit/coefficient"; import type { EquipmentMatrix } from "../../stores/duel"; +import { SPALL_LINER_HE_DAMAGE_DELTA } from "./spallLiner"; import { defaultEqualizer } from "./tankToDuelMember"; export type TankCharacteristics = ReturnType; @@ -170,7 +171,10 @@ export function tankCharacteristics( coefficient( [applyReactiveArmor && shell.type !== ShellType.SHELL_TYPE_HE, -0.27], [applyDynamicArmor, -0.1], - [applySpallLiner && shell.type === ShellType.SHELL_TYPE_HE, -0.2], + [ + applySpallLiner && shell.type === ShellType.SHELL_TYPE_HE, + SPALL_LINER_HE_DAMAGE_DELTA, + ], ); const assaultDamageCoefficient = gun.assault_ranges && gun.assault_ranges.types.includes(shell.type) From 85b8c7057d040de7fa72d786c8fb83af3e76c1ef Mon Sep 17 00:00:00 2001 From: Mark Greene Date: Sat, 26 Sep 2026 19:45:17 +1200 Subject: [PATCH 02/21] Redesign Spall Liner swap popover with icons Replace wordy swap prompt with compact icon row (spall liner icon, swap icon, current provisions) and drop unused ProvisionButton tooltip that flashed on popover open. --- packages/i18n/strings/en.json | 4 -- .../Options/components/SpallLinerSwitcher.tsx | 49 ++++++++++++++----- 2 files changed, 37 insertions(+), 16 deletions(-) diff --git a/packages/i18n/strings/en.json b/packages/i18n/strings/en.json index 9ac4145ee..534e2fa8d 100644 --- a/packages/i18n/strings/en.json +++ b/packages/i18n/strings/en.json @@ -527,10 +527,6 @@ "label": "Edit", "reset": "Reset" } - }, - - "spall_liner": { - "swap_prompt": "Spall Liner will replace one of your active provisions. Choose which one to remove:" } }, diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/SpallLinerSwitcher.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/SpallLinerSwitcher.tsx index 3b66d75dc..34ecffe18 100644 --- a/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/SpallLinerSwitcher.tsx +++ b/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/SpallLinerSwitcher.tsx @@ -1,17 +1,17 @@ import { alias, availableProvisions } from "@blitzkit/core"; -import { Flex, IconButton, Popover, Text } from "@radix-ui/themes"; +import { ShuffleIcon } from "@radix-ui/react-icons"; +import { Flex, IconButton, Popover } from "@radix-ui/themes"; import { api } from "../../../../../../core/blitzkit/api"; import { SPALL_LINER_PROVISION_ID } from "../../../../../../core/blitzkit/spallLiner"; import { useLocale } from "../../../../../../hooks/useLocale"; import { Duel } from "../../../../../../stores/duel"; import { Tankopedia } from "../../../../../../stores/tankopedia"; -import { ProvisionButton } from "../../../../../ModuleButtons/ProvisionButton"; const provisionDefinitions = await api.provisionDefinitions(); export function SpallLinerSwitcher() { const { tank, gun, provisions } = Duel.use((state) => state.protagonist); - const { strings, unwrap } = useLocale(); + const { unwrap } = useLocale(); const isSpallLinerActive = provisions.includes(SPALL_LINER_PROVISION_ID); const hasSlotAvailable = provisions.length < tank.max_provisions; @@ -80,19 +80,44 @@ export function SpallLinerSwitcher() { {button} - - - {strings.website.tools.tankopedia.sandbox.spall_liner.swap_prompt} - + + + {unwrap( + - + + + {provisions.map((id) => ( - equip(id)} - /> + > + {unwrap(provisionDefinitions.provisions[id].name!)} + ))} From 0bdb66742b261941465f9e6ff81b52ab6e8e6295 Mon Sep 17 00:00:00 2001 From: Mark Greene Date: Sun, 4 Oct 2026 13:27:34 +1300 Subject: [PATCH 03/21] Feature: Add Spall Liner toggle to Tankopedia model viewer ## Summary - Add a Spall Liner toggle button to the Tankopedia model viewer, placed alongside the existing Calibrated Shells / Enhanced Armor buttons. It's only shown for tanks that can actually equip Spall Liner. - The toggle reads/writes the same `Duel.protagonist.provisions` array as the Provisions tab, so both stay in sync automatically. - If a provision slot is free, clicking equips Spall Liner immediately. If all slots are full, a popover shows icons with a shuffle arrow so you can pick which active provision to swap out in one step. - Buttons use the same medium rounding as the consumable buttons on the tank page. - When Spall Liner is equipped, the Dynamic Armor 3D viewer's click-damage tooltip now applies the same 20% HE damage reduction Spall Liner grants in-game. - Refactored the hardcoded `-0.2` HE damage coefficient in `tankCharacteristics.ts` into a shared constant so the Compare tool and the 3D viewer use a single source of truth. ## Scope This only affects the single-tank Tankopedia page. The Firepower stats table (the viewed tank's own outgoing gun damage) is intentionally untouched, since a defensive damage reduction would be semantically backwards there. The Dynamic Armor viewer's click tooltip is the only place a "damage taken by this tank" number exists, so that's where the reduction is wired in. --- .../SpacedArmorSceneComponent/index.tsx | 11 ++ .../Options/components/SpallLinerSwitcher.tsx | 128 ++++++++++++++++++ .../HeroSection/components/Options/index.tsx | 3 + .../website/src/core/blitzkit/spallLiner.ts | 8 ++ .../src/core/blitzkit/tankCharacteristics.ts | 6 +- 5 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 packages/website/src/components/Tankopedia/HeroSection/components/Options/components/SpallLinerSwitcher.tsx create mode 100644 packages/website/src/core/blitzkit/spallLiner.ts diff --git a/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/index.tsx b/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/index.tsx index f2c770593..784836464 100644 --- a/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/index.tsx +++ b/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/index.tsx @@ -18,6 +18,10 @@ import { import { degToRad } from "three/src/math/MathUtils.js"; import { hasEquipment } from "../../../../core/blitzkit/hasEquipment"; import { jsxTree } from "../../../../core/blitzkit/jsxTree"; +import { + SPALL_LINER_HE_DAMAGE_DELTA, + SPALL_LINER_PROVISION_ID, +} from "../../../../core/blitzkit/spallLiner"; import { defaultEqualizer } from "../../../../core/blitzkit/tankToDuelMember"; import { discardClippingPlane } from "../../../../core/three/discardClippingPlane"; import { Duel } from "../../../../stores/duel"; @@ -384,6 +388,13 @@ export function SpacedArmorSceneComponent({ shot.damage *= antagonistEqualizer.damage; + const hasSpallLiner = Duel.state.protagonist.provisions.includes( + SPALL_LINER_PROVISION_ID, + ); + if (hasSpallLiner && shell.type === ShellType.SHELL_TYPE_HE) { + shot.damage *= 1 + SPALL_LINER_HE_DAMAGE_DELTA; + } + return shot; }, [camera], diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/SpallLinerSwitcher.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/SpallLinerSwitcher.tsx new file mode 100644 index 000000000..021f5cacf --- /dev/null +++ b/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/SpallLinerSwitcher.tsx @@ -0,0 +1,128 @@ +import { alias, availableProvisions } from "@blitzkit/core"; +import { ShuffleIcon } from "@radix-ui/react-icons"; +import { Flex, IconButton, Popover } from "@radix-ui/themes"; +import { api } from "../../../../../../core/blitzkit/api"; +import { SPALL_LINER_PROVISION_ID } from "../../../../../../core/blitzkit/spallLiner"; +import { useLocale } from "../../../../../../hooks/useLocale"; +import { Duel } from "../../../../../../stores/duel"; +import { Tankopedia } from "../../../../../../stores/tankopedia"; + +const provisionDefinitions = await api.provisionDefinitions(); + +export function SpallLinerSwitcher() { + const { tank, gun, provisions } = Duel.use((state) => state.protagonist); + const { unwrap } = useLocale(); + const isSpallLinerActive = provisions.includes(SPALL_LINER_PROVISION_ID); + const hasSlotAvailable = provisions.length < tank.max_provisions; + + const canEquipSpallLiner = availableProvisions( + tank, + gun, + provisionDefinitions, + ).some((provision) => provision.id === SPALL_LINER_PROVISION_ID); + + if (!canEquipSpallLiner) return null; + + function equip(replacing?: number) { + Duel.mutate((draft) => { + if (replacing !== undefined) { + const provisions = draft.protagonist.provisions; + provisions[provisions.indexOf(replacing)] = SPALL_LINER_PROVISION_ID; + } else { + draft.protagonist.provisions.push(SPALL_LINER_PROVISION_ID); + } + }); + Tankopedia.mutate((draft) => { + draft.shot = undefined; + }); + } + + function unequip() { + Duel.mutate((draft) => { + draft.protagonist.provisions = draft.protagonist.provisions.filter( + (id) => id !== SPALL_LINER_PROVISION_ID, + ); + }); + Tankopedia.mutate((draft) => { + draft.shot = undefined; + }); + } + + const button = ( + { + if (isSpallLinerActive) { + unequip(); + } else if (hasSlotAvailable) { + equip(); + } + }} + > + {unwrap(provisionDefinitions.provisions[SPALL_LINER_PROVISION_ID].name!)} + + ); + + if (isSpallLinerActive || hasSlotAvailable) return button; + + return ( + + {button} + + + + + {unwrap( + + + + + + {provisions.map((id) => ( + + equip(id)} + > + {unwrap(provisionDefinitions.provisions[id].name!)} + + + ))} + + + + + ); +} diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/Options/index.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/Options/index.tsx index 05fc67d92..5856c66d0 100644 --- a/packages/website/src/components/Tankopedia/HeroSection/components/Options/index.tsx +++ b/packages/website/src/components/Tankopedia/HeroSection/components/Options/index.tsx @@ -46,6 +46,7 @@ import { TankSearch } from "../../../../TankSearch"; import { CustomShellButton } from "./components/CustomShellButton"; import { DynamicArmorSwitcher } from "./components/DynamicArmorSwitcher"; import { QuickInputs } from "./components/QuickInputs"; +import { SpallLinerSwitcher } from "./components/SpallLinerSwitcher"; import { Thicknesses } from "./components/Thicknesses"; type OptionsProps = MaybeSkeletonComponentProps & { @@ -278,6 +279,8 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) { }} /> + + {!skeleton && ( diff --git a/packages/website/src/core/blitzkit/spallLiner.ts b/packages/website/src/core/blitzkit/spallLiner.ts new file mode 100644 index 000000000..80598d5c1 --- /dev/null +++ b/packages/website/src/core/blitzkit/spallLiner.ts @@ -0,0 +1,8 @@ +export const SPALL_LINER_PROVISION_ID = 101; + +/** + * Matches the in-game reduction Spall Liner grants against HE splash + * damage. Shared between the characteristics panel and the Dynamic + * Armor 3D viewer's damage readout so the figure isn't duplicated. + */ +export const SPALL_LINER_HE_DAMAGE_DELTA = -0.2; diff --git a/packages/website/src/core/blitzkit/tankCharacteristics.ts b/packages/website/src/core/blitzkit/tankCharacteristics.ts index 0cc72302e..51301190a 100644 --- a/packages/website/src/core/blitzkit/tankCharacteristics.ts +++ b/packages/website/src/core/blitzkit/tankCharacteristics.ts @@ -22,6 +22,7 @@ import { } from "@blitzkit/core"; import { coefficient } from "@blitzkit/core/src/blitzkit/coefficient"; import type { EquipmentMatrix } from "../../stores/duel"; +import { SPALL_LINER_HE_DAMAGE_DELTA } from "./spallLiner"; import { defaultEqualizer } from "./tankToDuelMember"; export type TankCharacteristics = ReturnType; @@ -170,7 +171,10 @@ export function tankCharacteristics( coefficient( [applyReactiveArmor && shell.type !== ShellType.SHELL_TYPE_HE, -0.27], [applyDynamicArmor, -0.1], - [applySpallLiner && shell.type === ShellType.SHELL_TYPE_HE, -0.2], + [ + applySpallLiner && shell.type === ShellType.SHELL_TYPE_HE, + SPALL_LINER_HE_DAMAGE_DELTA, + ], ); const assaultDamageCoefficient = gun.assault_ranges && gun.assault_ranges.types.includes(shell.type) From 98b555fa7ee1b74219d33ebec53046a0bb21becb Mon Sep 17 00:00:00 2001 From: Mark Greene Date: Sun, 4 Oct 2026 13:38:20 +1300 Subject: [PATCH 04/21] Fix: gun depression visualizer ignoring turret mount tilt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The gun flexibility visualizer at the bottom of the Tankopedia Firepower section computed depression/elevation purely from the raw gun `pitch.min`/`pitch.max` values, ignoring `initial_turret_rotation.pitch` (the tank's baked-in turret mount tilt). Every other place that reports gun depression/elevation already accounts for this offset; the visualizer was the outlier. On tanks with a level turret mount the offset is zero, so it was invisible almost everywhere. Minotauro's turret sits pitched 3° forward, which made the visualizer's min/max/pitch stats and wedge shape disagree with the Firepower stats directly above it (e.g. 7°/-18° instead of 10°/15°). **Changes:** - `GunFlexibilityVisualizer/index.tsx`: fold the turret's initial pitch offset into `minPitch`, `maxPitch` and the displayed `pitch` stat, and into the drawn limit shape, the pitch dot and the pointer mapping, so the dashed neutrality circle sits at the displayed 0°. - `GunFlexibilityVisualizer/components/CanvasContent.tsx`: nest the gun mesh inside a turret group carrying the same static tilt as its base rotation, so the mini 3D preview's live drag-pitch composes on top of it, mirroring `useTankTransform.ts`. --- .../components/CanvasContent.tsx | 27 +++++++++++-- .../GunFlexibilityVisualizer/index.tsx | 40 +++++++++++++------ 2 files changed, 51 insertions(+), 16 deletions(-) diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/GunFlexibilityVisualizer/components/CanvasContent.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/GunFlexibilityVisualizer/components/CanvasContent.tsx index 2f1d7342c..1fdc38bb1 100644 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/GunFlexibilityVisualizer/components/CanvasContent.tsx +++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/GunFlexibilityVisualizer/components/CanvasContent.tsx @@ -1,6 +1,7 @@ import type { QuicklimeEvent } from "quicklime"; import { useEffect, useRef } from "react"; import type { Group } from "three"; +import { degToRad } from "three/src/math/MathUtils.js"; import { api } from "../../../../../../../../core/blitzkit/api"; import { modelTransformEvent, @@ -21,6 +22,13 @@ export function CanvasContent() { const tankModel = modelDefinitions.models[tank.id]; const turretModel = tankModel.turrets[turret.id]; + // baked-in turret mount tilt (e.g. Minotauro sits pitched 3deg forward); + // applied as a static base rotation so the live gun pitch composes on top + // of it, mirroring useTankTransform.ts's turretContainer/gunContainer nesting + const initialTurretPitch = -degToRad( + tankModel.initial_turret_rotation?.pitch ?? 0, + ); + useEffect(() => { function handleModelTransformEvent( event: QuicklimeEvent, @@ -45,10 +53,23 @@ export function CanvasContent() { - + + + - - + + + + ); diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/GunFlexibilityVisualizer/index.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/GunFlexibilityVisualizer/index.tsx index 171c1a73d..93d7a5e1a 100644 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/GunFlexibilityVisualizer/index.tsx +++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/GunFlexibilityVisualizer/index.tsx @@ -48,13 +48,27 @@ export function GunFlexibilityVisualizer({ const turretModel = tankModel.turrets[turret.id]; const gunModel = turretModel.guns[gun.id]; + // turret's own baked-in mount tilt (e.g. Minotauro's turret sits pitched + // 3deg forward on the hull); must be folded into displayed pitch values + // the same way tankCharacteristics.ts and QuickInputs do + const initialTurretPitch = degToRad( + tankModel.initial_turret_rotation?.pitch ?? 0, + ); + + // radial map is in the tilt-compensated frame so the neutrality circle sits + // at the displayed 0deg + const tiltDeg = radToDeg(initialTurretPitch); + const magTilted = (deg: number) => mag(deg + tiltDeg); + const [yaw, setYaw] = useState(0); const [pitch, setPitch] = useState(0); const [minPitch, setMinPitch] = useState( - -degToRad(gunModel.pitch!.front?.max ?? gunModel.pitch!.max), + -degToRad(gunModel.pitch!.front?.max ?? gunModel.pitch!.max) - + initialTurretPitch, ); const [maxPitch, setMaxPitch] = useState( - -degToRad(gunModel.pitch!.front?.min ?? gunModel.pitch!.min), + -degToRad(gunModel.pitch!.front?.min ?? gunModel.pitch!.min) - + initialTurretPitch, ); const container = useRef(null); @@ -70,7 +84,7 @@ export function GunFlexibilityVisualizer({ let d = ""; // right - const mo = mag(p.max); + const mo = magTilted(p.max); if (y) { d += `M 0 0`; d += `L ${c(90 - y.max, mo)}`; @@ -87,7 +101,7 @@ export function GunFlexibilityVisualizer({ // front if (f) { - const m = mag(f.max); + const m = magTilted(f.max); d += `L ${c(90 - f.range / 2, m)}`; d += `A ${m} ${m} 0 0 0 ${c(90 + f.range / 2, m)}`; } @@ -108,7 +122,7 @@ export function GunFlexibilityVisualizer({ // back if (!y && b) { - const m = mag(b.max); + const m = magTilted(b.max); d += `L ${c(-90 - b.range / 2, m)}`; d += `A ${m} ${m} 0 0 0 ${c(-90 + b.range / 2, m)}`; } @@ -116,7 +130,7 @@ export function GunFlexibilityVisualizer({ d += `Z`; // right - const mi = mag(p.min); + const mi = magTilted(p.min); if (y) { d += `M 0 0`; d += `L ${c(90 - y.max, mi)}`; @@ -133,7 +147,7 @@ export function GunFlexibilityVisualizer({ // front if (f) { - const m = mag(f.min); + const m = magTilted(f.min); d += `L ${c(90 - f.range / 2, m)}`; d += `A ${m} ${m} 0 0 0 ${c(90 + f.range / 2, m)}`; } @@ -154,14 +168,14 @@ export function GunFlexibilityVisualizer({ // back if (!y && b) { - const m = mag(b.min); + const m = magTilted(b.min); d += `L ${c(-90 - b.range / 2, m)}`; d += `A ${m} ${m} 0 0 0 ${c(-90 + b.range / 2, m)}`; } d += `Z`; - const r = mag(radToDeg(-pitch)); + const r = mag(radToDeg(-(pitch - initialTurretPitch))); const dotX = r * Math.sin(yaw); const dotY = r * Math.cos(yaw); @@ -251,13 +265,13 @@ export function GunFlexibilityVisualizer({ yaw = min[1]; let t = 2 * Math.sqrt((u / rect.width) ** 2 + (v / rect.height) ** 2); t = clamp(t, 0, 1); - let pitch = degToRad(magInverse(-t)); + let pitch = degToRad(magInverse(-t)) + initialTurretPitch; pitch = clamp(pitch, min[0], max[0]); setPitch(pitch); setYaw(yaw); - setMinPitch(min[0]); - setMaxPitch(max[0]); + setMinPitch(min[0] - initialTurretPitch); + setMaxPitch(max[0] - initialTurretPitch); modelTransformEvent.dispatch({ pitch, yaw }); }} @@ -376,7 +390,7 @@ export function GunFlexibilityVisualizer({ From 90006dfd2a182ad7e8368178b2932352624fea83 Mon Sep 17 00:00:00 2001 From: Mark Greene Date: Sun, 4 Oct 2026 14:14:57 +1300 Subject: [PATCH 05/21] Fix: loading bar too close to tank title The hero loading bar was offset below the title by half the font size plus a fixed 16px. The title is centered as a line box, so its descenders and heavy caps reach lower than half the font size, and the fixed gap didn't grow with the title. Offset the bar by 0.75 of the font size instead, so the gap scales with the title. --- .../components/TankSandbox/Title/components/Tracker.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/Title/components/Tracker.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/Title/components/Tracker.tsx index 2b6d7071d..1f1e340fa 100644 --- a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/Title/components/Tracker.tsx +++ b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/Title/components/Tracker.tsx @@ -15,7 +15,7 @@ export function Tracker({ fontSize }: Props) { top="50%" width="min(100%, 16rem)" style={{ - transform: `translate(-50%, calc(${fontSize} / 2 + var(--space-4)))`, + transform: `translate(-50%, calc(${fontSize} * 0.75 + var(--space-4)))`, }} > From 6153beced6fa8d0754b9d48c3e65cfc418f7319a Mon Sep 17 00:00:00 2001 From: Mark Greene Date: Sun, 4 Oct 2026 14:24:02 +1300 Subject: [PATCH 06/21] Fix: search redirecting early and missing full tank names **Enter key** - Enter redirected to the previous results' first tank while a search was still buffering, because the key handler captured a stale `searching` value. - Enter on an empty field redirected to the first tank of the unfiltered list (Sheridan). - The handler now reads live filter state and only acts when there is a settled search, matching the quick-link button. **Full names in search** - Search only knew short tank names, so queries like "super pershing" found nothing when the game stores a short form. - Add an optional `name_full` to `TankDefinition`, filled from the game's full name when a short name exists, and index it (plus an accent-stripped copy) in `fetchTankNames` / `SEARCH_KEYS`. - Displayed names are unchanged. Full names become searchable once the definitions are regenerated on the next build. --- .../blitzkit/api/server/1_tankDefinitions.ts | 3 +++ packages/core/src/blitzkit/tankNames.ts | 13 ++++++++++++ .../core/src/protos/tank_definitions.proto | 1 + packages/core/src/protos/tank_definitions.ts | 20 +++++++++++++++++++ .../TankSearch/components/SearchBar.tsx | 3 ++- 5 files changed, 39 insertions(+), 1 deletion(-) diff --git a/packages/core/src/blitzkit/api/server/1_tankDefinitions.ts b/packages/core/src/blitzkit/api/server/1_tankDefinitions.ts index ee3f69a58..b7ff54c8d 100644 --- a/packages/core/src/blitzkit/api/server/1_tankDefinitions.ts +++ b/packages/core/src/blitzkit/api/server/1_tankDefinitions.ts @@ -371,6 +371,9 @@ export abstract class ServerBlitzKitAPI1 extends ServerBlitzKitAPI0 { (tank.shortUserString ? this.getString(tank.shortUserString) : undefined) ?? this.getString(tank.userString), + name_full: tank.shortUserString + ? this.getString(tank.userString) + : undefined, slug, nation, type: tankTags.includes("collectible") diff --git a/packages/core/src/blitzkit/tankNames.ts b/packages/core/src/blitzkit/tankNames.ts index bb19c9597..15462add5 100644 --- a/packages/core/src/blitzkit/tankNames.ts +++ b/packages/core/src/blitzkit/tankNames.ts @@ -18,11 +18,22 @@ export async function fetchTankNames(api: BlitzKitAPI) { searchableNameDeburr.locales[key] = deburr(value); }); + const searchableNameFull: I18nString = { + locales: { ...tank.name_full?.locales }, + }; + const searchableNameFullDeburr: I18nString = { locales: {} }; + + Object.entries(searchableNameFull.locales).forEach(([key, value]) => { + searchableNameFullDeburr.locales[key] = deburr(value); + }); + return { id: tank.id, name: tank.name, searchableName: tank.name, searchableNameDeburr, + searchableNameFull, + searchableNameFullDeburr, camouflages: tank.camouflages ?.map((id) => locales.supported.map( @@ -45,6 +56,8 @@ export const SEARCH_KEYS = [ .map(({ locale }) => [ `searchableName.locales.${locale}`, `searchableNameDeburr.locales.${locale}`, + `searchableNameFull.locales.${locale}`, + `searchableNameFullDeburr.locales.${locale}`, ]) .flat(), "camouflages", diff --git a/packages/core/src/protos/tank_definitions.proto b/packages/core/src/protos/tank_definitions.proto index 0c15d59f1..6c9cb1078 100644 --- a/packages/core/src/protos/tank_definitions.proto +++ b/packages/core/src/protos/tank_definitions.proto @@ -41,6 +41,7 @@ message TankDefinition { required uint32 weight = 31; required string dev_name = 32; optional Equalizer equalizer = 33; + optional I18nString name_full = 34; } message Equalizer { diff --git a/packages/core/src/protos/tank_definitions.ts b/packages/core/src/protos/tank_definitions.ts index 302424353..be6e2a1f0 100644 --- a/packages/core/src/protos/tank_definitions.ts +++ b/packages/core/src/protos/tank_definitions.ts @@ -292,6 +292,7 @@ export interface TankDefinition { weight: number; dev_name: string; equalizer?: Equalizer | undefined; + name_full?: I18nString | undefined; } export interface TankDefinition_RolesEntry { @@ -651,6 +652,7 @@ function createBaseTankDefinition(): TankDefinition { weight: 0, dev_name: "", equalizer: undefined, + name_full: undefined, }; } @@ -752,6 +754,9 @@ export const TankDefinition: MessageFns = { if (message.equalizer !== undefined) { Equalizer.encode(message.equalizer, writer.uint32(266).fork()).join(); } + if (message.name_full !== undefined) { + I18nString.encode(message.name_full, writer.uint32(274).fork()).join(); + } return writer; }, @@ -1051,6 +1056,14 @@ export const TankDefinition: MessageFns = { message.equalizer = Equalizer.decode(reader, reader.uint32()); continue; } + case 34: { + if (tag !== 274) { + break; + } + + message.name_full = I18nString.decode(reader, reader.uint32()); + continue; + } } if ((tag & 7) === 4 || tag === 0) { break; @@ -1111,6 +1124,7 @@ export const TankDefinition: MessageFns = { weight: isSet(object.weight) ? globalThis.Number(object.weight) : 0, dev_name: isSet(object.dev_name) ? globalThis.String(object.dev_name) : "", equalizer: isSet(object.equalizer) ? Equalizer.fromJSON(object.equalizer) : undefined, + name_full: isSet(object.name_full) ? I18nString.fromJSON(object.name_full) : undefined, }; }, @@ -1218,6 +1232,9 @@ export const TankDefinition: MessageFns = { if (message.equalizer !== undefined) { obj.equalizer = Equalizer.toJSON(message.equalizer); } + if (message.name_full !== undefined) { + obj.name_full = I18nString.toJSON(message.name_full); + } return obj; }, @@ -1271,6 +1288,9 @@ export const TankDefinition: MessageFns = { message.equalizer = (object.equalizer !== undefined && object.equalizer !== null) ? Equalizer.fromPartial(object.equalizer) : undefined; + message.name_full = (object.name_full !== undefined && object.name_full !== null) + ? I18nString.fromPartial(object.name_full) + : undefined; return message; }, }; diff --git a/packages/website/src/components/TankSearch/components/SearchBar.tsx b/packages/website/src/components/TankSearch/components/SearchBar.tsx index 32eba599f..1a97aa3d2 100644 --- a/packages/website/src/components/TankSearch/components/SearchBar.tsx +++ b/packages/website/src/components/TankSearch/components/SearchBar.tsx @@ -46,7 +46,8 @@ export function SearchBar({ topResult, skeleton, onSelect }: SearchBarProps) { }, [searching]); const handleKeyDown = useCallback( (event) => { - if (event.key !== "Enter" || !topResult || searching) return; + if (event.key !== "Enter" || !topResult) return; + if (!TankFilters.state.search || TankFilters.state.searching) return; event.preventDefault(); From f8aa9e75d5c9a70e4748f4a4df7fe8987af5c9ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Sat, 3 Oct 2026 21:42:03 -0500 Subject: [PATCH 07/21] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 38 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 20 ++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..dd84ea782 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 000000000..bbcbbe7d6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. From 46bd8f631638ac82cea503f83750fc3066b8ea2d Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sat, 3 Oct 2026 21:51:21 -0500 Subject: [PATCH 08/21] add template --- .github/ISSUE_TEMPLATE/bug_report.md | 38 ------------ .github/ISSUE_TEMPLATE/bug_report.yaml | 70 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yaml | 10 ++++ .github/ISSUE_TEMPLATE/feature_request.md | 20 ------- 4 files changed, 80 insertions(+), 58 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yaml create mode 100644 .github/ISSUE_TEMPLATE/config.yaml delete mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index dd84ea782..000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: '' -assignees: '' - ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Desktop (please complete the following information):** - - OS: [e.g. iOS] - - Browser [e.g. chrome, safari] - - Version [e.g. 22] - -**Smartphone (please complete the following information):** - - Device: [e.g. iPhone6] - - OS: [e.g. iOS8.1] - - Browser [e.g. stock browser, safari] - - Version [e.g. 22] - -**Additional context** -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml new file mode 100644 index 000000000..9ea35e4ab --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -0,0 +1,70 @@ +name: 🐞 Bug Report +description: BlitzKit is not behaving as expected +title: "[Bug]: " +labels: ["bug", "triage"] +type: bug + +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report! + - type: input + id: contact + attributes: + label: Contact Details + description: How can we get in touch with you if we need more info? + placeholder: ex. email@example.com + validations: + required: false + - type: textarea + id: what-happened + attributes: + label: What happened? + description: Also tell us, what did you expect to happen? + placeholder: Tell us what you see! + value: "A bug happened!" + validations: + required: true + - type: dropdown + id: version + attributes: + label: Version + description: What version of our software are you running? + options: + - 1.0.2 (Default) + - 1.0.3 (Edge) + default: 0 + validations: + required: true + - type: dropdown + id: browsers + attributes: + label: What browsers are you seeing the problem on? + multiple: true + options: + - Firefox + - Chrome + - Safari + - Microsoft Edge + - type: textarea + id: logs + attributes: + label: Relevant log output + description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. + render: shell + - type: checkboxes + id: terms + attributes: + label: Code of Conduct + description: By submitting this issue, you agree to follow our [Code of Conduct](https://example.com). + options: + - label: I agree to follow this project's Code of Conduct + required: true + - type: upload + id: screenshots + attributes: + label: Upload screenshots + description: If applicable, add screenshots to help explain your problem. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yaml b/.github/ISSUE_TEMPLATE/config.yaml new file mode 100644 index 000000000..6f47ee63c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yaml @@ -0,0 +1,10 @@ +blank_issues_enabled: false + +contact_links: + - name: Discord Server + about: Got questions? We answer better on Discord! + url: https://discord.gg/nDt7AjGJQH + + - name: Formal Email + about: Reach out to BlitzKit formally through email. + url: mailto:support@blitzkit.app diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index bbcbbe7d6..000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: '' -labels: '' -assignees: '' - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. From 551183a65bc883f1af27b5f75f190d3f639c2148 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sat, 3 Oct 2026 21:51:50 -0500 Subject: [PATCH 09/21] yml --- .github/ISSUE_TEMPLATE/{config.yaml => config.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/ISSUE_TEMPLATE/{config.yaml => config.yml} (100%) diff --git a/.github/ISSUE_TEMPLATE/config.yaml b/.github/ISSUE_TEMPLATE/config.yml similarity index 100% rename from .github/ISSUE_TEMPLATE/config.yaml rename to .github/ISSUE_TEMPLATE/config.yml From d7e06a2b46a8d55471a07a4a701c0547ebca1457 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sat, 3 Oct 2026 21:52:39 -0500 Subject: [PATCH 10/21] email link --- .github/ISSUE_TEMPLATE/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 6f47ee63c..ba8f3ae9f 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -7,4 +7,4 @@ contact_links: - name: Formal Email about: Reach out to BlitzKit formally through email. - url: mailto:support@blitzkit.app + url: https://blitzkit.com/contact From a5637c9f0771cb1b13f41bf12afb38590b54b154 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sat, 3 Oct 2026 22:01:26 -0500 Subject: [PATCH 11/21] format --- .github/ISSUE_TEMPLATE/bug.yaml | 42 ++++++++++++++++ .github/ISSUE_TEMPLATE/bug_report.yaml | 70 -------------------------- .github/ISSUE_TEMPLATE/config.yml | 6 +-- 3 files changed, 45 insertions(+), 73 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug.yaml delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.yaml diff --git a/.github/ISSUE_TEMPLATE/bug.yaml b/.github/ISSUE_TEMPLATE/bug.yaml new file mode 100644 index 000000000..25e6f4e54 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.yaml @@ -0,0 +1,42 @@ +name: 🐞 Bug Report +description: BlitzKit is not behaving as expected. +labels: ["bug"] +type: bug + +body: + - type: markdown + attributes: + value: | + Thank you so much for reporting an issue! Please fill out the form + below in its entirety and we'll address your issue as soon as possible. + + - type: textarea + validations: + required: true + attributes: + label: What happened? + description: What about BlitzKit's behavior did you find unexpected? + + - type: textarea + validations: + required: true + attributes: + label: What should've happened? + description: What did you expect BlitzKit to do instead of what you observed? + + - type: textarea + attributes: + label: How do you reproduce the issue? + description: What steps one must follow to recreate your issue? + + - type: dropdown + attributes: + label: Branch + description: What branch of BlitzKit were you using? Please see the "Branch" section of the footer at the very bottom of any page. + options: + - Production + - Preview + - OpenTest + default: 0 + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml deleted file mode 100644 index 9ea35e4ab..000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ /dev/null @@ -1,70 +0,0 @@ -name: 🐞 Bug Report -description: BlitzKit is not behaving as expected -title: "[Bug]: " -labels: ["bug", "triage"] -type: bug - -body: - - type: markdown - attributes: - value: | - Thanks for taking the time to fill out this bug report! - - type: input - id: contact - attributes: - label: Contact Details - description: How can we get in touch with you if we need more info? - placeholder: ex. email@example.com - validations: - required: false - - type: textarea - id: what-happened - attributes: - label: What happened? - description: Also tell us, what did you expect to happen? - placeholder: Tell us what you see! - value: "A bug happened!" - validations: - required: true - - type: dropdown - id: version - attributes: - label: Version - description: What version of our software are you running? - options: - - 1.0.2 (Default) - - 1.0.3 (Edge) - default: 0 - validations: - required: true - - type: dropdown - id: browsers - attributes: - label: What browsers are you seeing the problem on? - multiple: true - options: - - Firefox - - Chrome - - Safari - - Microsoft Edge - - type: textarea - id: logs - attributes: - label: Relevant log output - description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. - render: shell - - type: checkboxes - id: terms - attributes: - label: Code of Conduct - description: By submitting this issue, you agree to follow our [Code of Conduct](https://example.com). - options: - - label: I agree to follow this project's Code of Conduct - required: true - - type: upload - id: screenshots - attributes: - label: Upload screenshots - description: If applicable, add screenshots to help explain your problem. - validations: - required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index ba8f3ae9f..eb67d647a 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,10 +1,10 @@ blank_issues_enabled: false contact_links: - - name: Discord Server + - name: 🎮 Discord Server about: Got questions? We answer better on Discord! url: https://discord.gg/nDt7AjGJQH - - name: Formal Email + - name: ✉️ Email about: Reach out to BlitzKit formally through email. - url: https://blitzkit.com/contact + url: https://blitzkit.app/contact From 8d5ef297719ea60fbf564927255576c3408a1289 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sat, 3 Oct 2026 22:05:07 -0500 Subject: [PATCH 12/21] feature --- .github/ISSUE_TEMPLATE/bug.yaml | 9 ++++++--- .github/ISSUE_TEMPLATE/feature.yaml | 16 ++++++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/feature.yaml diff --git a/.github/ISSUE_TEMPLATE/bug.yaml b/.github/ISSUE_TEMPLATE/bug.yaml index 25e6f4e54..f02658323 100644 --- a/.github/ISSUE_TEMPLATE/bug.yaml +++ b/.github/ISSUE_TEMPLATE/bug.yaml @@ -6,9 +6,7 @@ type: bug body: - type: markdown attributes: - value: | - Thank you so much for reporting an issue! Please fill out the form - below in its entirety and we'll address your issue as soon as possible. + value: Thank you so much for reporting an issue! Please fill out the form below in its entirety and we'll address your issue as soon as possible. - type: textarea validations: @@ -40,3 +38,8 @@ body: default: 0 validations: required: true + + - type: input + attributes: + label: Discord username + description: Do you have a Discord account that we can reach out to for further information? diff --git a/.github/ISSUE_TEMPLATE/feature.yaml b/.github/ISSUE_TEMPLATE/feature.yaml new file mode 100644 index 000000000..3c456044e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature.yaml @@ -0,0 +1,16 @@ +name: 💡 Feature Request +description: BlitzKit could use a new idea. +labels: ["enhancement"] +type: feature + +body: + - type: markdown + attributes: + value: Thank you so much for pitching in an idea! + + - type: textarea + validations: + required: true + attributes: + label: What's your idea? + description: Please be descriptive and feel free to add drawings/screenshots if needed. From 067c9cb2670ccc014e61c41d87c3c4150c305659 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sat, 3 Oct 2026 22:10:10 -0500 Subject: [PATCH 13/21] simpler bug forms --- .github/ISSUE_TEMPLATE/bug.yaml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug.yaml b/.github/ISSUE_TEMPLATE/bug.yaml index f02658323..58ff6f92c 100644 --- a/.github/ISSUE_TEMPLATE/bug.yaml +++ b/.github/ISSUE_TEMPLATE/bug.yaml @@ -15,13 +15,6 @@ body: label: What happened? description: What about BlitzKit's behavior did you find unexpected? - - type: textarea - validations: - required: true - attributes: - label: What should've happened? - description: What did you expect BlitzKit to do instead of what you observed? - - type: textarea attributes: label: How do you reproduce the issue? From 86e7ace3f0be232646598b18ff5344f975c58946 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Sun, 4 Oct 2026 12:22:36 -0500 Subject: [PATCH 14/21] Remove misleading text Removed contribution note regarding sensitive submodules. --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index cc7f8f283..620b2af73 100644 --- a/README.md +++ b/README.md @@ -3,5 +3,3 @@ Tools for everything and anything, World of Tanks Blitz. This GitHub repository contains the code for BlitzKit and all its services. If you are here, looking to use BlitzKit, go to https://blitzkit.app/. - -If you intend to contribute to the code, note that you may need to check out sensitive, closed-source submodules to locally develop certain aspects of BlitzKit. Communicate with `@tresabhi` on Discord to get started or discuss sensitive content, such as environment variables or submodules. From d9f29cd0c9c90e03c740c733eff8002dd34ad145 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sun, 4 Oct 2026 13:11:36 -0500 Subject: [PATCH 15/21] start of docs --- README.md | 28 ++++++++++++++++++++++++++-- packages/scripts/src/protos.ts | 2 ++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 620b2af73..658c47e5e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,29 @@ # BlitzKit -Tools for everything and anything, World of Tanks Blitz. +Your Blitz experience, elevated. https://blitzkit.app/. -This GitHub repository contains the code for BlitzKit and all its services. If you are here, looking to use BlitzKit, go to https://blitzkit.app/. +## Developing + +### Prerequisites + +#### Bun + +[Bun](https://bun.sh/) is the package manager and runtime that BlitzKit uses. + +Ideally, you should also uninstall Node.js if you have it installed to avoid any cases where Node.js is used to run code instead of Bun which happens in some cases. But, this is not required and is unlikely to cause issues. + +#### Linux (Optional) + +All work on BlitzKit is done on Linux and macOS. It is theoretically possible to work on Windows; please feel free to try and share your discoveries with us! + +### Setting Up + +Start by cloning the repository and changing directories. + +```bash +git clone https://github.com/blitzkit/blitzkit.git +cd blitzkit +``` + +> [!INFO] +> There is a closed source submodule under `packages/closed` that you can safely ignore. This sole purpose of this submodule is to provide `dvp.ts`, a proprietary Wargaming game asset tree compression tool. You will not need this at any point. diff --git a/packages/scripts/src/protos.ts b/packages/scripts/src/protos.ts index 28789d95a..6972d3bd2 100644 --- a/packages/scripts/src/protos.ts +++ b/packages/scripts/src/protos.ts @@ -6,7 +6,9 @@ import { promisify } from "util"; const MAX_COMMAND_LENGTH = 2 ** 11; const TS_PROTO_EXECUTABLE_LOCATIONS = [ "./node_modules/.bin/protoc-gen-ts_proto", + "./node_modules/.bin/protoc-gen-ts_proto.exe", "../../node_modules/.bin/protoc-gen-ts_proto", + "../../node_modules/.bin/protoc-gen-ts_proto.exe", ]; const roots = [ From 8b03784be5d2b8a497ebb120b3d4287d623122ed Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sun, 4 Oct 2026 13:21:21 -0500 Subject: [PATCH 16/21] clis --- README.md | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 658c47e5e..4dc57a641 100644 --- a/README.md +++ b/README.md @@ -2,21 +2,33 @@ Your Blitz experience, elevated. https://blitzkit.app/. -## Developing +## Developing: Prerequisites -### Prerequisites +There's a few things you'll need to get started. If this is your first time working with BlitzKit, it's completely safe to ignore all optional prerequisites below to get things running as quickly as possible. -#### Bun +### Bun [Bun](https://bun.sh/) is the package manager and runtime that BlitzKit uses. Ideally, you should also uninstall Node.js if you have it installed to avoid any cases where Node.js is used to run code instead of Bun which happens in some cases. But, this is not required and is unlikely to cause issues. -#### Linux (Optional) +### Protocol Buffer + +BlitzKit uses [Protocol Buffer's protoc CLI](https://protobuf.dev/installation/) to generate efficiently packed data structures to serve data to clients in place of JSON. + +### aria2 (Optional) + +If you plan to use BlitzKit's client-management CLI for BlitzKit Previews, you will need to [get aria2](https://aria2.github.io/) to speed up your downloads. + +### libarchive (Optional) + +If you plan to use BlitzKit's client-management CLI for BlitzKit OpenTests, you will need to [get libarchive](https://www.libarchive.org/) to keep your downloads completely in-memory. + +### Linux (Optional) All work on BlitzKit is done on Linux and macOS. It is theoretically possible to work on Windows; please feel free to try and share your discoveries with us! -### Setting Up +## Developing: Setting Up Start by cloning the repository and changing directories. @@ -27,3 +39,5 @@ cd blitzkit > [!INFO] > There is a closed source submodule under `packages/closed` that you can safely ignore. This sole purpose of this submodule is to provide `dvp.ts`, a proprietary Wargaming game asset tree compression tool. You will not need this at any point. + +## Developing: Running From 784c3e58c162ef9f4f9797df9028a17f4b0bbb82 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sun, 4 Oct 2026 13:27:46 -0500 Subject: [PATCH 17/21] protos --- README.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4dc57a641..0f9e9b155 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,10 @@ Ideally, you should also uninstall Node.js if you have it installed to avoid any BlitzKit uses [Protocol Buffer's protoc CLI](https://protobuf.dev/installation/) to generate efficiently packed data structures to serve data to clients in place of JSON. +### World of Tanks Blitz + +You will need a local installation of [World of Tanks Blitz](https://wotblitz.com/) for BlitzKit to build off. + ### aria2 (Optional) If you plan to use BlitzKit's client-management CLI for BlitzKit Previews, you will need to [get aria2](https://aria2.github.io/) to speed up your downloads. @@ -37,7 +41,23 @@ git clone https://github.com/blitzkit/blitzkit.git cd blitzkit ``` -> [!INFO] +> [!NOTE] > There is a closed source submodule under `packages/closed` that you can safely ignore. This sole purpose of this submodule is to provide `dvp.ts`, a proprietary Wargaming game asset tree compression tool. You will not need this at any point. +Then, download the dependencies with Bun. + +```bash +bun install +``` + +Finally, compile the proto files into TypeScript. + +```bash +build build-protos +``` + ## Developing: Running + +```bash +bun dev +``` From 6be4ff97f3b0707c22356b04126f84acd2f30dd2 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sun, 4 Oct 2026 13:36:33 -0500 Subject: [PATCH 18/21] env --- README.md | 4 ++++ packages/website/template.env | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 packages/website/template.env diff --git a/README.md b/README.md index 0f9e9b155..e2a50f4ef 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,10 @@ Finally, compile the proto files into TypeScript. build build-protos ``` +## Developing: Environment + +Go through the different packages in the `packages` directory and clone their respective `template.env`'s to `.env` files. Follow the instructions in the `.env` files to fill them out as much as you can. + ## Developing: Running ```bash diff --git a/packages/website/template.env b/packages/website/template.env new file mode 100644 index 000000000..e2f4d103f --- /dev/null +++ b/packages/website/template.env @@ -0,0 +1,35 @@ +# Point this to your local installation of the game (make sure you see the Data +# folder within the directory you're putting here); don't forget to use +# double quotation marks if your path has spaces +CLIENT_DIR= + +# Should be set to * for local development to build all chunks at once +CHUNK=* + +# Should always be development when using CHUNKS=* +PUBLIC_ENVIRONMENT=development + +# GuidesBlitz is the official textual partner of BlitzKit, but, really any +# website that supports BlitzKit tank slugs like example.com/tank-slug works +# too +PUBLIC_GUIDE_WEBSITE=https://guidesblitz.com + +# The tag manager doesn't do anything in localhost, but here's what BlitzKit +# uses for completeness sake +PUBLIC_GOOGLE_TAG_MANAGER_ID=G-GL2JVHCGPQ + +# A very rare, but required variable, enabling the advertising of BlitzKit +# OpenTest; TODO: make this a string field to allow for cross-advertising for +# any branch of BlitzKit +PUBLIC_PROMOTE_OPENTEST=false + +# Legacy variable; this will be removed soon +PUBLIC_STATIC_ASSET_BASE=https://assets.blitzkit.app + +# Proprietary Wargaming fields; you shouldn't need these for 99% of the tasks +WOTB_GLOSSARY= +WOTB_GLOSSARY_DOMAIN= +WOTB_GLOSSARY_PATH= + +# Generate your own application id: https://developers.wargaming.net +PUBLIC_WARGAMING_APPLICATION_ID= \ No newline at end of file From dd11efd48165e79ffe7ac3cc8a6ed7793aa8f864 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sun, 4 Oct 2026 13:37:21 -0500 Subject: [PATCH 19/21] running --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index e2a50f4ef..1568ac800 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,14 @@ Go through the different packages in the `packages` directory and clone their re ## Developing: Running +Launching the website is as easy as running the `dev` script. + ```bash bun dev ``` + +If you ever make a change to any proto file, make sure to rebuild them. + +```bash +build build-protos +``` From 9a659845f54030c0ae90a8c53200c17e0bccf1c6 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sun, 4 Oct 2026 13:51:53 -0500 Subject: [PATCH 20/21] make spall icon not a button --- .../Options/components/SpallLinerSwitcher.tsx | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/SpallLinerSwitcher.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/SpallLinerSwitcher.tsx index 021f5cacf..77653a3c9 100644 --- a/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/SpallLinerSwitcher.tsx +++ b/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/SpallLinerSwitcher.tsx @@ -63,7 +63,9 @@ export function SpallLinerSwitcher() { }} > {unwrap(provisionDefinitions.provisions[SPALL_LINER_PROVISION_ID].name!)} - - {unwrap( - + {unwrap( From f3a8f2faab21c0423d8c229b3e3ed1b2f165678f Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sun, 4 Oct 2026 14:03:32 -0500 Subject: [PATCH 21/21] only supply full name if it's different --- .../blitzkit/api/server/1_tankDefinitions.ts | 18 +++++++++++------- .../TankSearch/components/SearchBar.tsx | 14 ++++++++++---- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/packages/core/src/blitzkit/api/server/1_tankDefinitions.ts b/packages/core/src/blitzkit/api/server/1_tankDefinitions.ts index b7ff54c8d..e7192e140 100644 --- a/packages/core/src/blitzkit/api/server/1_tankDefinitions.ts +++ b/packages/core/src/blitzkit/api/server/1_tankDefinitions.ts @@ -350,6 +350,15 @@ export abstract class ServerBlitzKitAPI1 extends ServerBlitzKitAPI0 { const tankClass = this.blitzTankClassToBlitzkit[blitzTankClass as BlitzTankClass]; + const canonicalFullName = this.getString(tank.userString); + const name = + (tank.shortUserString + ? this.getString(tank.shortUserString) + : undefined) ?? canonicalFullName; + // only supply full name if it's different from the displayed short name to save some bytes + const name_full = + name === canonicalFullName ? undefined : canonicalFullName; + tankDefinitions.tanks[tankId] = { ancestors: [], successors: [], @@ -367,13 +376,8 @@ export abstract class ServerBlitzKitAPI1 extends ServerBlitzKitAPI0 { typeof equipment === "string" ? equipment : equipment.at(-1)!, max_consumables: tankDefinition.root.consumableSlots, max_provisions: tankDefinition.root.provisionSlots, - name: - (tank.shortUserString - ? this.getString(tank.shortUserString) - : undefined) ?? this.getString(tank.userString), - name_full: tank.shortUserString - ? this.getString(tank.userString) - : undefined, + name, + name_full, slug, nation, type: tankTags.includes("collectible") diff --git a/packages/website/src/components/TankSearch/components/SearchBar.tsx b/packages/website/src/components/TankSearch/components/SearchBar.tsx index 1a97aa3d2..034a2e458 100644 --- a/packages/website/src/components/TankSearch/components/SearchBar.tsx +++ b/packages/website/src/components/TankSearch/components/SearchBar.tsx @@ -33,7 +33,7 @@ export function SearchBar({ topResult, skeleton, onSelect }: SearchBarProps) { draft.search = sanitized.length === 0 ? null : sanitized; }); }, 500), - [] + [], ); const handleChange = useCallback(() => { if (!searching) { @@ -46,8 +46,14 @@ export function SearchBar({ topResult, skeleton, onSelect }: SearchBarProps) { }, [searching]); const handleKeyDown = useCallback( (event) => { - if (event.key !== "Enter" || !topResult) return; - if (!TankFilters.state.search || TankFilters.state.searching) return; + if ( + event.key !== "Enter" || + !topResult || + !TankFilters.state.search || + TankFilters.state.searching + ) { + return; + } event.preventDefault(); @@ -57,7 +63,7 @@ export function SearchBar({ topResult, skeleton, onSelect }: SearchBarProps) { window.location.href = `/tanks/${topResult.slug}`; } }, - [topResult] + [topResult], ); return (