diff --git a/.github/ISSUE_TEMPLATE/bug.yaml b/.github/ISSUE_TEMPLATE/bug.yaml
new file mode 100644
index 000000000..58ff6f92c
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug.yaml
@@ -0,0 +1,38 @@
+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
+ 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
+
+ - 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/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 000000000..eb67d647a
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -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: ✉️ Email
+ about: Reach out to BlitzKit formally through email.
+ url: https://blitzkit.app/contact
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.
diff --git a/README.md b/README.md
index cc7f8f283..1568ac800 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,75 @@
# 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
-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.
+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](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.
+
+### 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.
+
+### 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.
+
+### 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!
+
+## Developing: Setting Up
+
+Start by cloning the repository and changing directories.
+
+```bash
+git clone https://github.com/blitzkit/blitzkit.git
+cd blitzkit
+```
+
+> [!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: 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
+
+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
+```
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 = [
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..77653a3c9
--- /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();
+ }
+ }}
+ >
+
+
+ );
+
+ if (isSpallLinerActive || hasSlotAvailable) return button;
+
+ return (
+
+ {button}
+
+
+
+
+
+
+
+
+ {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 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)
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