Compare commits

...
10 Commits
Author SHA1 Message Date
TrèsAbhi 43b7b4159f layout 2026-06-12 23:50:24 -05:00
TrèsAbhi 1aeae3b93b submodules 2026-06-12 23:41:28 -05:00
TrèsAbhi d657cd2360 clone 2026-06-12 23:38:56 -05:00
TrèsAbhi 222746be26 game 2026-06-12 23:34:38 -05:00
TrèsAbhi 95dd73de52 protoc 2026-06-12 23:29:36 -05:00
TrèsAbhi c893fa0a03 .net 2026-06-12 23:19:12 -05:00
TrèsAbhi c0e7f2f00a bun 2026-06-12 23:16:17 -05:00
TrèsAbhi 4dce8046cb Linux 2026-06-12 23:13:47 -05:00
TrèsAbhi 7df2425f76 readme 2026-06-12 23:06:35 -05:00
tresabhi e3cf2d51cc lighting 2026-06-12 20:59:43 -05:00
6 changed files with 197 additions and 87 deletions

No files matched your search

+77 -37
View File
@@ -1,47 +1,87 @@
# BlitzKit
Tools for everything and anything, World of Tanks Blitz.
Apps for 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/.
## Development
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.
Setting up a local developer environment for BlitzKit is more complex than most projects, but this section will guide you through all the steps. If you're ever lost or confused, feel free to ask for help in [the official Discord server](https://discord.gg/nDt7AjGJQH).
## Packages
### Prerequisites
| Package | Title | Source | Memo |
| ------------------------------------ | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------ |
| [packages/app](packages/app) | BlitzKit | [Play Store](https://play.google.com/store/apps/details?id=com.tresabhi.blitzkit), [App Store](https://apps.apple.com/app/4321), [Microsoft Store](https://apps.microsoft.com/detail/zyxw) | The native app of BlitzKit for all platforms. Unavailable from stores at this time. |
| [packages/bot](packages/bot) | BlitzKit Discord Bot | [Discord Application Directory](https://discord.com/discovery/applications/1097673957865443370) | The one-in-all Discord bot for statistics. |
| [packages/core](packages/core) | | | Mutual internal code shared between all other packages. |
| [packages/i18n](packages/i18n) | | [Crowdin](https://crowdin.com/project/blitzkrieg) | Translations served as JSON shared across all other packages, including some utility code. |
| [packages/scripts](packages/scripts) | | | CLI utilities for the development and deployment of BlitzKit services. |
| [packages/varuna](packages/varuna) | Varuna | [NPM](https://www.npmjs.com/package/varuna) | React and Astro-oriented state management library, awaiting separation from BlitzKit. Unavailable from NPM at this time. |
| [packages/website](packages/website) | BlitzKit | https://blitzkit.app | The BlitzKit website provides a wide range of tools, APIs, and interconnects all other services. |
Before you touch any code, you'll need the following.
## Submodules
#### 1. Linux
| Submodule | Title | Source | Memo |
| ------------------------------------------------------ | --------- | ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [submodules/blitzkit-closed](packages/blitzkit-closed) | ClosedKit | [blitzkit/closed](https://github.com/blitzkit/closed) | Closed source, sensitive code hidden from the public to prevent bad actors from abusing the game. Discuss with `@tresabhi` on Discord for **special permission** to access. |
Because Node API for .NET doesn't resolve runtime-specific object files for libraries, the entirety of the BlitzKit project is "vendor-locked" to a single OS. GitHub Actions, responsible for BlitzKit CD/CI works best with Ubuntu, thus we must also use a Linux OS.
## Environment Variables
#### 2. Bun
| Name | Source | BlitzKit Default | Memo |
| ------------------------------------------------------------- | -------------------------------------------------------------------------------- | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `PUBLIC_WARGAMING_APPLICATION_ID` | [Wargaming Developer's Room](https://developers.wargaming.net/) | | Application ID needed to communicate with Wargaming APIs. |
| `PUBLIC_PROMOTE_OPENTEST` | | `false` | Optional boolean dictating whether the website advertises [the opentest subdomain of BlitzKit](https://opentest.blitzkit.app/). |
| `ALLOW_ROBOTS` | | `true` | Toggles between `Allow: /` and `Disallow: /` in the website's `/robots.txt`. This is usually `false` for all BlitzKit's subdomains. |
| `WOTB_GLOSSARY`, `WOTB_GLOSSARY_DOMAIN`, `WOTB_GLOSSARY_PATH` | | | |
| `PUBLIC_GOOGLE_TAG_MANAGER_ID` | [Google Tag Manager](https://tagmanager.google.com/) | `G-GL2JVHCGPQ` | Google Tag Manager's unique identifier for analytical purposes. |
| `PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID` | [Google Analytics](https://analytics.google.com/) | `430711767` | Google Analytics' unique property ID for [BlitzKit's apex domain](https://blitzkit.app/) and subdomains. |
| `GOOGLE_APPLICATION_CREDENTIALS` | [Google Cloud](https://cloud.google.com/) | | Google Cloud's API key, primarily used for ranking popular tanks and finding relevant tank review videos in Tankopedia. |
| `PUBLIC_DISCORD_SERVER_ID` | [BlitzKit Discord Server](https://discord.gg/nDt7AjGJQH) | `734786591205359697` | BlitzKit Discord Server's id. |
| `PUBLIC_ASSET_BASE` | [blitzkit/assets](https://github.com/blitzkit/assets) | `https://blitzkit.github.io/assets` | The root domain and path that serves the external assets like tank models and icons. |
| `PUBLIC_BRANCH` | | `main` | The branch that the website is operating on. This is completely nominal. |
| `PUBLIC_GUIDE_WEBSITE` | [GuidesBlitz](https://guidesblitz.com/) | `https://guidesblitz.com` | The website that serves the encyclopedia-like content in BlitzKit. |
| `SERVICING` | | `false` | Wether the website is down for servicing. |
| `DISCORD_CLIENT_ID` | [Discord Developer Portal](https://discord.com/developers/applications) | `1097673957865443370` | The BlitzKit Discord Bot's user ID. |
| `GH_TOKEN` | [GitHub Fine-Grained Tokens](https://github.com/settings/personal-access-tokens) | | The GitHub API key used for checking out private submodules and pushing content to the asset repository. |
| `DISCORD_TOKEN` | [Discord Developer Portal](https://discord.com/developers/applications) | | The Discord API Key for the BlitzKit Discord Bot. |
| `DATABASE_URL` | [CockroachDB](https://www.cockroachlabs.com/) | | The CockroachDB-SQL database URL used to link Wargaming and Discord users. |
| `WOTB_DLC_CDN` | | | |
https://bun.com/
BlitzKit is written mostly in TypeScript, with packages fetched from NPM, managed by Bun.
#### 3. .NET
https://dotnet.microsoft.com/download/dotnet/9.0/
You'll need the .NET 9.0 SDK to build and run the interface layer between BlitzKit and your local installation of World of Tanks Blitz.
#### 4. Protocol Buffers
https://protobuf.dev/installation/
Both BlitzKit and World of Tanks Blitz encode their APIs with Protocol Buffers.
#### 5. World of Tanks Blitz
https://store.steampowered.com/app/444200/
You'll need an up-to-date installation of World of Tanks Blitz on your computer. You may use the same installation that you play, even on a Windows partition if you dual boot.
#### 6. UE4SS
...
#### 7. FModel
...
### Set Up
#### 1. Cloning
BlitzKit is a massive monorepo with a lot of submodules. You'll likely not need all the branches and commit history. So, clone with depth 1.
```bash
git clone --depth 1 https://github.com/blitzkit/blitzkit.git
cd blitzkit
git submodule update --init --recursive --depth 1
```
#### 2. Proto Files
...
#### 3. Mappings
...
#### 4. Environment Variables
...
#### 5. The Big One
...
### Development Server
...
### Building
...
### Branches & Pull Requests
...
+4 -1
View File
@@ -138,7 +138,10 @@ public partial class GameInterface
{
var path = texturePaths[name];
var texture = provider.LoadPackageObject<UTexture2D>(path);
return provider.Image(texture);
var isRMAO = name.EndsWith("_RMAO");
var postProcess = isRMAO ? ImagePostProcess.RMAO : ImagePostProcess.None;
return provider.Image(texture, postProcess);
}
public HashSet<string> Files => files;
+31 -1
View File
@@ -17,6 +17,12 @@ using SkiaSharp;
namespace BlitzKit.Game.Models;
public enum ImagePostProcess
{
None,
RMAO,
}
public partial class BlitzFileProvider : DefaultFileProvider
{
[GeneratedRegex(@"\(GameplayTags=\(\(TagName=""(.+)""\)\)\)")]
@@ -87,13 +93,37 @@ public partial class BlitzFileProvider : DefaultFileProvider
private readonly SKEncodedImageFormat imageFormat = SKEncodedImageFormat.Webp;
private readonly int imageQuality = 80;
public byte[] Image(UTexture2D uTexture)
public byte[] Image(UTexture2D uTexture, ImagePostProcess postProcess = ImagePostProcess.None)
{
var cTexture = uTexture.Decode(ETexturePlatform.DesktopMobile);
ArgumentNullException.ThrowIfNull(cTexture);
var bitmap = cTexture.ToSkBitmap();
return Image(bitmap, postProcess);
}
private byte[] Image(SKBitmap bitmap, ImagePostProcess postProcess)
{
if (postProcess != ImagePostProcess.None)
{
for (int y = 0; y < bitmap.Height; y++)
{
for (int x = 0; x < bitmap.Width; x++)
{
var color = bitmap.GetPixel(x, y);
switch (postProcess)
{
case ImagePostProcess.RMAO:
bitmap.SetPixel(x, y, new SKColor(color.Blue, color.Red, color.Green, color.Alpha));
break;
}
}
}
}
var data = bitmap.Encode(imageFormat, imageQuality);
return data.ToArray();
@@ -0,0 +1,53 @@
import { times } from "lodash-es";
import { Quicklime } from "quicklime";
import { degToRad, lerp } from "three/src/math/MathUtils.js";
const ANGLE = Math.PI * 2 ** -2;
const LIGHTS_COUNT = 5;
const THETA_OFFSET = degToRad(-152);
const LIGHT_DISTANCE = 13;
const LIGHT_HEIGHT_0 = 4;
const LIGHT_HEIGHT_1 = 2;
const INTENSITY_0 = 3.5;
const INTENSITY_1 = 1.5;
const HEMISPHERE_INTENSITY = 2;
export const transitionEvent = new Quicklime<number>(0);
export function TankopediaLighting() {
return (
<group>
<hemisphereLight
intensity={HEMISPHERE_INTENSITY}
color="#ffffff"
groundColor="#afafaf"
/>
{times(LIGHTS_COUNT, (index) => {
const x = index / (LIGHTS_COUNT - 1);
const theta = 2 * Math.PI * (index / LIGHTS_COUNT) + THETA_OFFSET;
const position = [
LIGHT_DISTANCE * Math.sin(theta),
lerp(LIGHT_HEIGHT_0, LIGHT_HEIGHT_1, x),
LIGHT_DISTANCE * Math.cos(theta),
] as const;
const intensity = lerp(INTENSITY_0, INTENSITY_1, x);
return (
<spotLight
castShadow
userData={{ index }}
key={index}
position={position}
intensity={intensity}
penumbra={1}
decay={0}
color="#ffffff"
angle={ANGLE}
/>
);
})}
</group>
);
}
@@ -1,14 +1,11 @@
import {
VisualChanges_TankPart,
type StageParameters,
} from "@protos/blitz_static_tank_upgrade_single_stage";
import { type StageParameters } from "@protos/blitz_static_tank_upgrade_single_stage";
import { OrbitControls } from "@react-three/drei";
import { Canvas } from "@react-three/fiber";
import { useProtagonist } from "../../hooks/useProtagonist";
import { useTankChassisModel } from "../../hooks/useTankChassisModel";
import { useTankGunModel } from "../../hooks/useTankGunModel";
import { useTankHullModel } from "../../hooks/useTankHullModel";
import { useTankTurretModel } from "../../hooks/useTankTurretModel";
import { TankopediaLighting } from "../TankopediaLighting";
import styles from "./index.module.css";
interface TankopediaSandboxProps {
@@ -33,17 +30,6 @@ export function TankopediaSandbox({ parameters }: TankopediaSandboxProps) {
}
function Content({ parameters }: TankopediaSandboxProps) {
const tank = useProtagonist();
const selectedTurret = parameters.visual_changes.find(
({ tank_part }) => tank_part === VisualChanges_TankPart.TANK_PART_TURRET,
)!.name;
const selectedGun = parameters.visual_changes.find(
({ tank_part }) => tank_part === VisualChanges_TankPart.TANK_PART_GUN,
)!.name;
const collision = false;
const hull = useTankHullModel();
const chassis = useTankChassisModel();
const turret = useTankTurretModel(parameters);
@@ -64,23 +50,11 @@ function Content({ parameters }: TankopediaSandboxProps) {
<primitive object={gun} />
</group>
<spotLight
castShadow
position={[3, 10, 5]}
intensity={2 ** 10}
penumbra={1}
/>
<spotLight
castShadow
position={[-3, -10, -5]}
intensity={2 ** 10}
penumbra={1}
/>
<ambientLight />
<TankopediaLighting />
<mesh receiveShadow rotation={[-Math.PI / 2, 0, 0]}>
<planeGeometry args={[2 ** 8, 2 ** 8]} />
<meshStandardMaterial color="gray" />
<planeGeometry args={[2 ** 9, 2 ** 9]} />
<meshStandardMaterial color="black" />
</mesh>
<OrbitControls />
+27 -17
View File
@@ -1,33 +1,43 @@
import { useLoader } from "@react-three/fiber";
import { useEffect } from "react";
import { Mesh, MeshStandardMaterial } from "three";
import { GLTFLoader } from "three-stdlib";
import { OnObjectUpdate } from "three/src/nodes/utils/EventNode.js";
export function useTankModel(url: string, map: Record<string, boolean>) {
const gltf = useLoader(GLTFLoader, url);
const [root] = gltf.scene.children;
root.traverse((child) => {
if (
child instanceof Mesh &&
child.material instanceof MeshStandardMaterial
) {
// TODO: remove this hack and fix the gltf
child.material.vertexColors = false;
useEffect(() => {
for (const material of Object.values(gltf.materials)) {
if (material instanceof MeshStandardMaterial) {
// TODO: remove this hack and fix the gltf
material.vertexColors = false;
material.needsUpdate = true;
}
}
child.castShadow = true;
child.receiveShadow = true;
});
console.log(gltf.nodes);
for (const child of root.children) {
if (child.name in map) {
child.visible = map[child.name];
continue;
for (const node of Object.values(gltf.nodes)) {
if (node instanceof Mesh) {
node.castShadow = true;
node.receiveShadow = true;
}
}
}, [url]);
child.visible = false;
console.warn(`Root child ${child.name} isn't recognized`);
}
useEffect(() => {
for (const child of root.children) {
if (child.name in map) {
child.visible = map[child.name];
continue;
}
child.visible = false;
console.warn(`Root child ${child.name} isn't recognized`);
}
}, [url, map]);
return root;
}