Compare commits

..
13 Commits
Author SHA1 Message Date
tresabhi d8925a47b5 i am satisfied 2026-10-10 23:12:41 -05:00
tresabhi 60f482d820 hahaha 2026-10-10 20:48:37 -05:00
tresabhi c2fd81c98e remove old credits 2026-10-10 18:47:46 -05:00
tresabhi 0fbc868738 more colors 2026-10-10 18:43:44 -05:00
tresabhi ff0cfbaf0a target 2026-10-10 18:38:05 -05:00
tresabhi 4642b74bca colors! 2026-10-10 18:37:47 -05:00
tresabhi 88d49bd5a3 avenir 2026-10-10 18:24:14 -05:00
tresabhi 6fd2423445 more progress 2026-10-10 18:12:01 -05:00
tresabhi a0dba52649 complete list? 2026-10-10 17:49:06 -05:00
tresabhi 80d76e3729 more more more! 2026-10-10 17:34:07 -05:00
tresabhi 7915b39d9d more! 2026-10-10 17:17:57 -05:00
tresabhi c94a84222d add more 2026-10-10 17:05:36 -05:00
tresabhi 1bca762781 start of credits 2026-10-10 16:59:34 -05:00
44 changed files with 783 additions and 590 deletions

No files matched your search

-47
View File
@@ -1,47 +0,0 @@
# BlitzKit Credits
BlitzKit is a one man band **TrèsAbhi** but I am far from solo. Helpers, in the order of contribution over time:
### S0AP
BlitzKit Discord bot verification, testing, advertisement, and the initial encouragement to create BlitzKit.
### Prince_NA
BlitzKit Discord bot and website testing and general advise.
### Vovko
BlitzKit Discord bot and website rendering pipeline.
### Maddox
Game file decompression.
### Pyogenics
Game 3D model parsing.
### Aeson
Game file layout, general advise, advertisement, and Tankopedia pre-release testing.
### Minitelrose, ChickenMan7777
3-caliber rule edge cases.
### HisRoyalFatness, Synx
Embed UX.
### [OPTML] clan, \_34, xkdkw, Droodles Blitz
Tankopedia pre-release testing.
### GonnaHetzMe
Redacted by request.
### Mark Greene
GitHub contributor on bug fixes and feature additions.
+3 -3
View File
@@ -1,14 +1,14 @@
# BlitzKit 1.29.10
Thank you to [Mark Greene](https://github.com/Hitjack007) for your contributions this update.
Thank you to [HitJack](https://github.com/Hitjack007) for your contributions this update.
## Features
- Combined game mode passive and active abilities into a single dropdown
- Added tier filters to the Guess game [Mark Greene]
- Added tier filters to the Guess game [HitJack]
- Changing tiers will break your streak
## Fixes
- module damage is now properly shown when sorting with it in the tank search [Mark Greene]
- module damage is now properly shown when sorting with it in the tank search [HitJack]
- Duplicate abilities in the tank filters are now removed
+2 -2
View File
@@ -1,6 +1,6 @@
# BlitzKit 1.29.8
Thank you to [Mark Greene](https://github.com/Hitjack007) for your contributions this update.
Thank you to [HitJack](https://github.com/Hitjack007) for your contributions this update.
## Features
@@ -15,5 +15,5 @@ Thank you to [Mark Greene](https://github.com/Hitjack007) for your contributions
## Fixes
- The Tankopedia now smoothly resizes when enlarging on interaction
- Stock turrets now correctly mount the corresponding stock gun [Mark Greene]
- Stock turrets now correctly mount the corresponding stock gun [HitJack]
- The consumable and provision buttons in Compare are no longer tiny
+3 -3
View File
@@ -1,14 +1,14 @@
# BlitzKit 1.29.9
Thank you to [Mark Greene](https://github.com/Hitjack007) for your contributions this update.
Thank you to [HitJack](https://github.com/Hitjack007) for your contributions this update.
## Features
- Reworked tank search filters to be far more compact to make way for new filters
- Added the ability to filter tanks by consumables [Mark Greene]
- Added the ability to filter tanks by consumables [HitJack]
- Added the ability to filter tanks by provisions and game-mode abilities and powers
## Fixes
- Consumable and provision icons in Compare are now properly sized
- Provisions now properly affect terrain resistances [Mark Greene]
- Provisions now properly affect terrain resistances [HitJack]
+5 -5
View File
@@ -1,6 +1,6 @@
# BlitzKit 1.30.7
Thank you to [Mark Greene](https://github.com/Hitjack007) for your contributions this update.
Thank you to [HitJack](https://github.com/Hitjack007) for your contributions this update.
## Features
@@ -8,13 +8,13 @@ Thank you to [Mark Greene](https://github.com/Hitjack007) for your contributions
## Fixes
- Clicking on the armor multiple times with HE no longer crashes BlitzKit [Mark Greene]
- Ricochets that later intersect with external modules no longer falsely claim a successful damaging penetration [Mark Greene]
- Clicking on the armor multiple times with HE no longer crashes BlitzKit [HitJack]
- Ricochets that later intersect with external modules no longer falsely claim a successful damaging penetration [HitJack]
- The lighting animation no longer stops prematurely when BlitzKit is unfocused
- The spot light angle has been increased to more evenly light up larger tanks
- BlitzKit once again uses soft shadows, reducing the pixelated shadows
- Fixed the weird patterns that formed on the tracks of tanks under self-shadows
- Increased the floor size and light decay to create the illusion that the floor is infinite
- Safari no longer crashes when unloading textures for unloaded tanks [Mark Greene]
- Safari no longer crashes when unloading textures for unloaded tanks [HitJack]
- The gun depression transition between two constraints (like often behind tanks) now uses a gentler zoned and can never be 0
- Armor plate names in Static Armor are now properly capitalized in English and translated to other languages [Mark Greene]
- Armor plate names in Static Armor are now properly capitalized in English and translated to other languages [HitJack]
+34 -34
View File
@@ -1,75 +1,75 @@
# BlitzKit 1.32.0
Thank you to [Mark Greene](https://github.com/Hitjack007) for your contributions this update.
Thank you to [HitJack](https://github.com/Hitjack007) for your contributions this update.
## Features
### Tankopedia & Armor
- Added a spall liner button to the Tankopedia beside the other equipment [Mark Greene]
- Listed track repair times in the Tankopedia [Mark Greene]
- The penetration chance is now displayed when penetration testing armor [Mark Greene]
- Added a new setting to disable automatically loading tanks in Tankopedia [Mark Greene]
- Added a spall liner button to the Tankopedia beside the other equipment [HitJack]
- Listed track repair times in the Tankopedia [HitJack]
- The penetration chance is now displayed when penetration testing armor [HitJack]
- Added a new setting to disable automatically loading tanks in Tankopedia [HitJack]
### Tank Performance & Mechanics
- Added Peekaboo DPM [Mark Greene]
- Added Peekaboo DPM [HitJack]
- Your Peekaboo DPM is lower than your normal DPM if your reload is faster than 10s as you'll have to wait for the remaining 10s for you to get unspotted before you can poke and shoot
### Tank Filters
- The test and owned tank filters are now more intuitive as clicking on them now turns the clicked option on instead of off [Mark Greene]
- The test and owned tank filters are now more intuitive as clicking on them now turns the clicked option on instead of off [HitJack]
### Game Compatibility
- Added compatibility for tier XI tanks [Mark Greene]
- Added compatibility for tier XI tanks [HitJack]
## Fixes
### Tankopedia & Armor
- The armor no longer disappears when you turn on Dynamic Armor System [Mark Greene]
- Camouflage values in Tankopedia are now more descriptive when shooting [Mark Greene]
- The penetration chance is now displayed when penetration testing armor [Mark Greene]
- Listed track repair times in the Tankopedia [Mark Greene]
- Added a spall liner button to the Tankopedia beside the other equipment [Mark Greene]
- The flags in the Tankopedia tech tree are no longer offset to the top left [Mark Greene]
- The loading bar below the name of the tanks in Tankopedia is now sufficiently spaced below [Mark Greene]
- The armor no longer disappears when you turn on Dynamic Armor System [HitJack]
- Camouflage values in Tankopedia are now more descriptive when shooting [HitJack]
- The penetration chance is now displayed when penetration testing armor [HitJack]
- Listed track repair times in the Tankopedia [HitJack]
- Added a spall liner button to the Tankopedia beside the other equipment [HitJack]
- The flags in the Tankopedia tech tree are no longer offset to the top left [HitJack]
- The loading bar below the name of the tanks in Tankopedia is now sufficiently spaced below [HitJack]
- Removed the vertical sliver of pixels found to the right of the Equalizer toggle in Tankopedia
### Tank Search & Filters
- The search bar no longer redirects to a seemingly random tank when clicking enter when searching [Mark Greene]
- Tank search now properly handles the full names of tanks like the Super Pershing [Mark Greene]
- The tank search now remains consistent in order even when an added filter does not add/remove tanks from the list [Mark Greene]
- The test and owned tank filters are now more intuitive as clicking on them now turns the clicked option on instead of off [Mark Greene]
- The search bar no longer redirects to a seemingly random tank when clicking enter when searching [HitJack]
- Tank search now properly handles the full names of tanks like the Super Pershing [HitJack]
- The tank search now remains consistent in order even when an added filter does not add/remove tanks from the list [HitJack]
- The test and owned tank filters are now more intuitive as clicking on them now turns the clicked option on instead of off [HitJack]
### Visuals & UI
- Removed the Beta badge from the Equalizer button [Mark Greene]
- Removed the Beta badge from the Equalizer button [HitJack]
- Updated the background image for the Equalizer button to the actual banner of the new game mode
- The contact shadows on the ground no longer grow & darken when switching between tabs [Mark Greene]
- Additionally, the contact shadows stays visible at low angles [Mark Greene]
- The contact shadows on the ground no longer grow & darken when switching between tabs [HitJack]
- Additionally, the contact shadows stays visible at low angles [HitJack]
- The "Help Translate BlitzKit" text on the home page is now properly centered vertically
- The footer now says "Branch" instead of "CDN" [Mark Greene]
- The game version in the footer now displays a shorthand version instead of the entire version number [Mark Greene]
- The reload visualizer now displays just 1 decimal place [Mark Greene]
- Plot controls for the Static armor tab no longer pokes in from the right on non-Static armor tabs in some languages [Mark Greene]
- Mixer's controllers now scale better on smaller screens [Mark Greene]
- The list of available tanks in the Playlist is no longer excessively wide [Mark Greene]
- The footer now says "Branch" instead of "CDN" [HitJack]
- The game version in the footer now displays a shorthand version instead of the entire version number [HitJack]
- The reload visualizer now displays just 1 decimal place [HitJack]
- Plot controls for the Static armor tab no longer pokes in from the right on non-Static armor tabs in some languages [HitJack]
- Mixer's controllers now scale better on smaller screens [HitJack]
- The list of available tanks in the Playlist is no longer excessively wide [HitJack]
### Tank Mechanics & Models
- The gun depression visualizer now properly handles the inherent tilt of tanks like the Minotauro [Mark Greene]
- When moving the tracks of tanks, the textures no longer move at different rates [Mark Greene]
- The gun depression visualizer now properly handles the inherent tilt of tanks like the Minotauro [HitJack]
- When moving the tracks of tanks, the textures no longer move at different rates [HitJack]
### Localization
- Users with a preference on a language are once again redirected to the correct localized pages [Mark Greene]
- Users with a preference on a language are once again redirected to the correct localized pages [HitJack]
### Session & Game Compatibility
- BlitzKit Session now properly handles tanks removed from the game [Mark Greene]
- BlitzKit Session now properly handles tanks removed from the game [HitJack]
## Technical Fixes
@@ -78,7 +78,7 @@ Thank you to [Mark Greene](https://github.com/Hitjack007) for your contributions
- This was the last bug standing in the way of BlitzKit Previews
- Welcome back, BlitzKit Previews!
- Removed some dead code from the BlitzKit model mangler
- The player discovery loop now persists for longer than 1 iteration [Mark Greene]
- The player discovery loop now persists for longer than 1 iteration [HitJack]
- Bun is now forced to run scripts with the `--bun` flag
## Technical Features
@@ -94,5 +94,5 @@ Thank you to [Mark Greene](https://github.com/Hitjack007) for your contributions
- This was the last bug standing in the way of BlitzKit Previews
- Welcome back, BlitzKit Previews!
- Removed some dead code from the BlitzKit model mangler
- The player discovery loop now persists for longer than 1 iteration [Mark Greene]
- The player discovery loop now persists for longer than 1 iteration [HitJack]
- Bun is now forced to run scripts with the `--bun` flag
@@ -4,13 +4,7 @@ import {
ChassisDefinitionsList,
Crew,
GunDefinitionsList,
I_HAT,
J_HAT,
K_HAT,
ModelDefinitions,
parseHierarchies,
Sc2ReadStream,
ScgReadStream,
TankParameters,
toUniqueId,
TurretDefinitionsList,
@@ -18,10 +12,9 @@ import {
VehicleDefinitionArmor,
VehicleDefinitionList,
VehicleDefinitions,
VertexAttribute,
} from "@blitzkit/core";
import { parse as parsePath } from "path";
import { MathUtils, Vector3 as THREEVector3, Vector3Tuple } from "three";
import { Vector3Tuple } from "three";
import { Cache } from "./0_base";
import { ServerBlitzKitAPI2 } from "./2_camouflageDefinitions";
@@ -30,87 +23,6 @@ export abstract class ServerBlitzKitAPI3 extends ServerBlitzKitAPI2 {
return { x: tuple[0], y: tuple[1], z: tuple[2] } satisfies Vector3;
}
private async collisionBoundingBoxes(
nation: string,
tankKey: string,
turretRotation?: { yaw: number; pitch: number; roll: number },
) {
const boundingBoxes: Record<
string,
{ min: Vector3Tuple; max: Vector3Tuple }
> = {};
const path = `Data/3d/Tanks/CollisionMeshes/${nation}-${tankKey}`;
if (!(await this.vfs.resolve(`${path}.sc2`))) {
throw new Error(`Missing collision mesh for ${nation}-${tankKey}`);
}
const sc2 = new Sc2ReadStream(
(await this.vfs.file(`${path}.sc2`)).buffer as ArrayBuffer,
).sc2();
const scg = new ScgReadStream(
(await this.vfs.file(`${path}.scg`)).buffer as ArrayBuffer,
).scg();
parseHierarchies(sc2["#hierarchy"], null, (hierarchy, components) => {
for (const component of components) {
if (component["comp.typename"] !== "RenderComponent") continue;
const polygonGroup = scg.get(
component["rc.renderObj"]["ro.batches"]["0000"]["rb.datasource"],
);
if (!polygonGroup) continue;
const min: Vector3Tuple = [Infinity, Infinity, Infinity];
const max: Vector3Tuple = [-Infinity, -Infinity, -Infinity];
const rotate =
turretRotation && hierarchy.name.toLowerCase().startsWith("turret");
polygonGroup.vertices.forEach((vertex) => {
vertex.forEach(({ attribute, value }) => {
if (attribute !== VertexAttribute.VERTEX) return;
const position = new THREEVector3(...value);
if (rotate) {
position
.applyAxisAngle(
I_HAT,
-MathUtils.degToRad(turretRotation.pitch),
)
.applyAxisAngle(
J_HAT,
-MathUtils.degToRad(turretRotation.roll),
)
.applyAxisAngle(
K_HAT,
-MathUtils.degToRad(turretRotation.yaw),
);
}
const coordinates = position.toArray();
for (let axis = 0; axis < 3; axis++) {
min[axis] = Math.min(min[axis], coordinates[axis]);
max[axis] = Math.max(max[axis], coordinates[axis]);
}
});
});
boundingBoxes[hierarchy.name.toLowerCase()] = { min, max };
}
return null;
});
if (!boundingBoxes.hull) {
throw new Error(`Missing hull collision mesh for ${nation}-${tankKey}`);
}
return boundingBoxes;
}
private assignArmor(
raw: VehicleDefinitionArmor[string],
id: number,
@@ -155,12 +67,6 @@ export abstract class ServerBlitzKitAPI3 extends ServerBlitzKitAPI2 {
const tankParameters = await this.vfs.yaml<TankParameters>(
`Data/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`,
);
const collisionBoundingBoxes = await this.collisionBoundingBoxes(
nation,
tankKey,
tankDefinition.root.hull.turretInitialRotation,
);
const hullBoundingBox = collisionBoundingBoxes.hull;
const turretOrigin = tankDefinition.root.hull.turretPositions.turret
.split(" ")
.map(Number) as Vector3Tuple;
@@ -235,8 +141,12 @@ export abstract class ServerBlitzKitAPI3 extends ServerBlitzKitAPI2 {
}
: undefined,
bounding_box: {
min: this.vector3TupleToBlitzkit(hullBoundingBox.min),
max: this.vector3TupleToBlitzkit(hullBoundingBox.max),
min: this.vector3TupleToBlitzkit(
tankParameters.collision.hull.bbox.min,
),
max: this.vector3TupleToBlitzkit(
tankParameters.collision.hull.bbox.max,
),
},
turrets: {},
tracks: {},
@@ -298,35 +208,26 @@ export abstract class ServerBlitzKitAPI3 extends ServerBlitzKitAPI2 {
turret.userString;
const turretCollisionName = parsePath(
turret.hitTester.collisionModel,
).name.toLowerCase();
const isFullYaw = -turretYaw[0] + turretYaw[1] === 360;
if (!collisionBoundingBoxes[turretCollisionName] && isFullYaw) {
throw new Error(
`Missing turret collision mesh for ${nation}-${tankKey} ${turretKey}`,
);
}
const turretBoundingBox = collisionBoundingBoxes[
turretCollisionName
] ?? {
min: [0, 0, 0] as Vector3Tuple,
max: [0, 0, 0] as Vector3Tuple,
};
modelDefinitions.models[tankId].turrets[turretId] = {
armor: turretArmor,
gun_origin: this.vector3TupleToBlitzkit(gunOrigin),
model_id: turretModel,
yaw: isFullYaw
? undefined
: { min: turretYaw[0], max: turretYaw[1] },
yaw:
-turretYaw[0] + turretYaw[1] === 360
? undefined
: { min: turretYaw[0], max: turretYaw[1] },
guns: {},
bounding_box: {
min: this.vector3TupleToBlitzkit(turretBoundingBox.min),
max: this.vector3TupleToBlitzkit(turretBoundingBox.max),
min: this.vector3TupleToBlitzkit(
tankParameters.collision[
parsePath(turret.hitTester.collisionModel).name.toLowerCase()
].bbox.min,
),
max: this.vector3TupleToBlitzkit(
tankParameters.collision[
parsePath(turret.hitTester.collisionModel).name.toLowerCase()
].bbox.max,
),
},
};
-1
View File
@@ -25,7 +25,6 @@ export * from "./sluggify";
export * from "./staticAsset";
export * from "./sum";
export * from "./tankDefinitions";
export * from "./tankBoundingBox";
export * from "./tankIcon";
export * from "./tankNames";
export * from "./unionBoundingBox";
@@ -1,32 +0,0 @@
import { ModelDefinition, TurretModelDefinition } from "../protos";
import { unionBoundingBox } from "./unionBoundingBox";
export function tankBoundingBox(
tankModelDefinition: ModelDefinition,
turretModelDefinition: TurretModelDefinition,
) {
const hull = tankModelDefinition.bounding_box!;
const turret = turretModelDefinition.bounding_box!;
const origin = tankModelDefinition.turret_origin!;
if (
turret.min!.x === turret.max!.x &&
turret.min!.y === turret.max!.y &&
turret.min!.z === turret.max!.z
) {
return unionBoundingBox(hull, hull);
}
return unionBoundingBox(hull, {
min: {
x: turret.min!.x + origin.x,
y: turret.min!.y + origin.z,
z: turret.min!.z + origin.y,
},
max: {
x: turret.max!.x + origin.x,
y: turret.max!.y + origin.z,
z: turret.max!.z + origin.y,
},
});
}
@@ -1,5 +1,5 @@
import { times } from "lodash-es";
import { CrewType, ShellType } from "../../protos";
import { CrewType, ShellType, TankClass, TankType } from "../../protos";
export const SHELL_NAMES: Record<ShellType, string> = {
[ShellType.SHELL_TYPE_AP]: "AP",
@@ -15,6 +15,33 @@ export const CREW_MEMBER_NAMES = {
[CrewType.CREW_TYPE_RADIOMAN]: "radioman",
} as const;
/**
* @deprecated use svg
*/
export const TANK_ICONS: Record<TankClass, string> = {
[TankClass.TANK_CLASS_TANK_DESTROYER]: "https://i.imgur.com/BIHSEH0.png",
[TankClass.TANK_CLASS_LIGHT]: "https://i.imgur.com/CSNha5V.png",
[TankClass.TANK_CLASS_MEDIUM]: "https://i.imgur.com/wvf3ltm.png",
[TankClass.TANK_CLASS_HEAVY]: "https://i.imgur.com/ECeqlZa.png",
};
/**
* @deprecated use svg
*/
export const TANK_ICONS_PREMIUM: Record<TankClass, string> = {
[TankClass.TANK_CLASS_TANK_DESTROYER]: "https://i.imgur.com/TCu3EdR.png",
[TankClass.TANK_CLASS_LIGHT]: "https://i.imgur.com/zdkpTRb.png",
[TankClass.TANK_CLASS_MEDIUM]: "https://i.imgur.com/3z7eHX6.png",
[TankClass.TANK_CLASS_HEAVY]: "https://i.imgur.com/P3vbmyA.png",
};
/**
* @deprecated use svg
*/
export const TANK_ICONS_COLLECTOR: Record<TankClass, string> = {
[TankClass.TANK_CLASS_TANK_DESTROYER]: "https://i.imgur.com/WTjeirB.png",
[TankClass.TANK_CLASS_LIGHT]: "https://i.imgur.com/EwhtKkU.png",
[TankClass.TANK_CLASS_MEDIUM]: "https://i.imgur.com/u8YDMBh.png",
[TankClass.TANK_CLASS_HEAVY]: "https://i.imgur.com/8xRf3nc.png",
};
export const TIERS = times(10, (index) => index + 1);
export const TIER_ROMAN_NUMERALS: Record<number, string> = {
1: "I",
@@ -41,3 +68,8 @@ export const flags: Record<string, string> = {
european: "🇪🇺",
other: "<:other:1218421572243558482>",
};
export const TREE_TYPE_ICONS: Record<TankType, Record<TankClass, string>> = {
[TankType.TANK_TYPE_RESEARCHABLE]: TANK_ICONS,
[TankType.TANK_TYPE_PREMIUM]: TANK_ICONS_PREMIUM,
[TankType.TANK_TYPE_COLLECTOR]: TANK_ICONS_COLLECTOR,
};
+50
View File
@@ -0,0 +1,50 @@
export enum ImgurSize {
/**
* The default size
*/
Default = '',
/**
* Unknown size but really really small
*/
UltraSmall = 'ss',
/**
* 90x90
*/
SmallSquare = 's',
/**
* 160x160
*/
BigSquare = 'b',
/**
* 160x160
*/
SmallThumbnail = 't',
/**
* 320x320
*/
MediumThumbnail = 'm',
/**
* 640x640
*/
LargeThumbnail = 'l',
/**
* 1024x1024
*/
HugeThumbnail = 'h',
}
interface ImgurOptions {
format: 'jpeg' | 'png';
size: ImgurSize;
}
const defaultImgurOptions: ImgurOptions = {
format: 'png',
size: ImgurSize.Default,
};
export function imgur(id: string, imgurOptions?: Partial<ImgurOptions>) {
const options = { ...defaultImgurOptions, ...imgurOptions };
return `https://i.imgur.com/${id}${options.size}.${options.format}`;
}
+1
View File
@@ -0,0 +1 @@
export * from './imgur';
+1
View File
@@ -1,5 +1,6 @@
export * from "./blitz";
export * from "./blitzkit";
export * from "./imgur";
export * from "./math";
export * from "./patreon";
export * from "./protobuf";
-22
View File
@@ -90,28 +90,6 @@ export interface Hierarchy {
name: string;
}
export function parseHierarchies<Parent>(
hierarchies: Hierarchy[],
parent: Parent,
parseHierarchy: (
hierarchy: Hierarchy,
components: Component[],
parent: Parent,
) => Parent | undefined,
) {
for (const hierarchy of hierarchies) {
const components = Array.from(
{ length: hierarchy.components.count },
(_, index) => hierarchy.components[index.toString().padStart(4, "0")],
);
const child = parseHierarchy(hierarchy, components, parent);
if (child !== undefined && hierarchy["#hierarchy"]) {
parseHierarchies(hierarchy["#hierarchy"], child, parseHierarchy);
}
}
}
// 0001, 0002, 0003...: Component
type Components = {
count: number;
+2 -5
View File
@@ -1,8 +1,6 @@
{
"test": {
"default": "test",
"long": "This is a long test string. You MUST NEVER see this in the UI. My favorite fruit is {fruit} because it has the color of {color}. I like it so much that I will say its name 3 times! {fruit}! {fruit}! {fruit}! Once again, it's {color}, not {not_color}.",
"fox": "The quick brown fox jumps over the lazy dog."
"credits": {
"title": "BlitzKit is the work of many"
},
"common": {
@@ -996,7 +994,6 @@
"about": {
"title": "About",
"credits": "Credits",
"discord": "Discord",
"github": "GitHub",
"patches": "Patches"
+200 -200
View File
@@ -1,7 +1,7 @@
{
"test": {
"default": "test",
"long": "To jest długi ciąg testowy. NIGDY NIE ZOBACZYSZ tego w interfejsie użytkownika. Mój ulubiony owoc to {fruit}, ponieważ ma kolor {color}. Tak bardzo go lubię, że powiem jego nazwę 3 razy! {fruit}! {fruit}! {fruit}! Po raz kolejny to {color}, a nie {not_color}.",
"long": "This is a long test string. You MUST NEVER see this in the UI. My favorite fruit is {fruit} because it has the color of {color}. I like it so much that I will say its name 3 times! {fruit}! {fruit}! {fruit}! Once again, it's {color}, not {not_color}.",
"fox": "Szybki brązowy lis przeskoczył nad leniwym psem."
},
"common": {
@@ -19,7 +19,7 @@
"gun_types": {
"auto_loader": "Samo ładowacz",
"auto_reloader": "Automatyczne przeładowanie",
"regular": "Regularny"
"regular": "Regular"
},
"shells": {
"ap": "AP",
@@ -38,10 +38,10 @@
},
"branches": {
"beta": "Beta",
"development": "Rozwój",
"opentest": "OtwartyTest",
"preview": "Przegląd",
"production": "Produkcja"
"development": "Development",
"opentest": "OpenTest",
"preview": "Preview",
"production": "Production"
},
"locales": {
"en": "Angielski",
@@ -102,28 +102,28 @@
"other": "Hybryda"
},
"tank_class_short": {
"-1": "nierozpoznany",
"-1": "unrecognized",
"0": "Lekki",
"1": "Średni",
"2": "Ciężki",
"3": "NC"
"3": "TD"
},
"tank_class_short_plural_lowercase": {
"0": "lekkie",
"1": "średnie",
"2": "ciężkie",
"3": "NC-ów"
"3": "TDs"
},
"tank_class_medium": {
"-1": "nierozpoznany",
"-1": "unrecognized",
"0": "Lekki",
"1": "Średni",
"2": "Ciężki",
"3": "Niszczyciel czołgów"
},
"tree_type": {
"-1": "nierozpoznany",
"0": "Drzewko technologiczne",
"-1": "unrecognized",
"0": "Tech tree",
"1": "Premium",
"2": "Kolekcjonerski"
},
@@ -187,223 +187,223 @@
},
"website": {
"redirect": {
"title": "301: przeniesiony na stałe",
"body": "Ta strona została przeniesiona. Przekierowywanie..."
"title": "301: moved permanently",
"body": "This page has moved. Redirecting..."
},
"settings": {
"title": "Ustawienia",
"description": "Zmień ustawienia BlitzKit",
"dev_mode": "Tryb deweloperski",
"auto_load_models": "Automatycznie wczytaj modele",
"language": "Język"
"title": "Settings",
"description": "Change BlitzKit settings",
"dev_mode": "Development mode",
"auto_load_models": "Auto-load models",
"language": "Language"
},
"common": {
"player_search": {
"hint": "Szukaj graczy...",
"no_results": "Brak wyników",
"searching": "Wyszukiwanie..."
"hint": "Search players...",
"no_results": "No results",
"searching": "Searching..."
},
"copy_button": {
"copied": "Skopiowano!"
"copied": "Copied!"
},
"tank_search": {
"select_dialog_title": "Szukaj czołgów",
"swap_dialog_title": "Zamień czołg",
"clear_filters": "Wyczyść filtry",
"login": "Zaloguj się, aby tego użyć",
"search_bar_hint": "Szukaj czołgów...",
"recent": "Ostatnio oglądane",
"select_all": "Zaznacz wszystko",
"count_singular": "1 czołg",
"count_plural": "{count} czołgi",
"sorting_by": "Sortowanie według {name}",
"clear": "Wyczyść",
"tier": "Poziom czołgu {tier}",
"passive": "{name} (Pasywny)",
"active": "{name} (Aktywne)",
"select_dialog_title": "Search for tanks",
"swap_dialog_title": "Swap tank",
"clear_filters": "Clear filters",
"login": "Login to use this",
"search_bar_hint": "Search tanks...",
"recent": "Recently viewed",
"select_all": "Select all",
"count_singular": "1 tank",
"count_plural": "{count} tanks",
"sorting_by": "Sorting by {name}",
"clear": "Clear",
"tier": "Tier {tier}",
"passive": "{name} (Passive)",
"active": "{name} (Active)",
"no_tanks_found": {
"body": "Nie znaleziono czołgu.",
"body": "No tanks found.",
"search_again": "",
"clear_filters": "Spróbuj wyczyścić filtry."
"clear_filters": "Try clearing filters."
},
"sort": {
"meta.none": "brak",
"survivability.health": "punkty.życia",
"survivability.viewRange": "zasięg widzenia",
"survivability.camouflageStill": "kamuflaż w bezruchu",
"survivability.camouflageMoving": "kamuflaż w trakcie ruchu",
"survivability.camouflageShooting": "kamuflaż podczas strzału",
"survivability.volume": "wartość",
"survivability.length": "długość",
"fire.dpm": "DPM pocisków podstawowych",
"fire.dpmPremium": "DPM pocisków premium",
"fire.reload": "przeładowanie",
"fire.caliber": "kaliber",
"fire.standardPenetration": "Penetracja pocisków standardowych",
"fire.premiumPenetration": "Penetracja pocisków premium",
"meta.none": "none",
"survivability.health": "health",
"survivability.viewRange": "view range",
"survivability.camouflageStill": "camouflage still",
"survivability.camouflageMoving": "camouflage moving",
"survivability.camouflageShooting": "camouflage shooting",
"survivability.volume": "volume",
"survivability.length": "length",
"fire.dpm": "standard DPM",
"fire.dpmPremium": "premium DPM",
"fire.reload": "reload",
"fire.caliber": "caliber",
"fire.standardPenetration": "standard penetration",
"fire.premiumPenetration": "premium penetration",
"fire.tertiaryPenetration": "tertiary penetration",
"fire.damage": "uszkodzenia",
"fire.moduleDamage": "Uszkodzenia modułów",
"fire.shellVelocity": "Prędkość pocisku",
"fire.shellRange": "Zasięg pocisku",
"fire.shellCapacity": "Maksymalna ilość pocisków",
"fire.aimTime": "Czas celowania",
"fire.damage": "damage",
"fire.moduleDamage": "module damage",
"fire.shellVelocity": "shell velocity",
"fire.shellRange": "shell range",
"fire.shellCapacity": "shell capacity",
"fire.aimTime": "aim time",
"fire.dispersionStill": "dispersion still",
"fire.dispersionMoving": "dispersion moving",
"fire.gunDepression": "Depresja działa",
"fire.gunElevation": "Elewacja działa",
"maneuverability.forwardsSpeed": "Maksymalna prędkość do przodu",
"maneuverability.backwardsSpeed": "Maksymalna prędkość do tyłu",
"maneuverability.power": "Moc silnika",
"maneuverability.powerToWeight": "Proporcja mocy do masy",
"maneuverability.weight": "Masa czołgu",
"fire.gunDepression": "gun depression",
"fire.gunElevation": "gun elevation",
"maneuverability.forwardsSpeed": "forwards speed",
"maneuverability.backwardsSpeed": "backwards speed",
"maneuverability.power": "power",
"maneuverability.powerToWeight": "power to weight",
"maneuverability.weight": "weight",
"maneuverability.traverseSpeed": "traverse speed"
},
"sort_dropdown": {
"label": "Filtruj",
"label": "Sort",
"groups": {
"sort": {
"no_sort": "Brak filtrów",
"label": "Filtruj według",
"fire": "Siła ognia",
"maneuverability": "Mobilność ",
"survivability": "Przetrwanie"
"no_sort": "No sort",
"label": "Sort by",
"fire": "Fire",
"maneuverability": "Maneuverability",
"survivability": "Survivability"
},
"direction": {
"label": "Direction",
"ascending": "Rosnąco",
"descending": "Malejąco"
"ascending": "Ascending",
"descending": "Descending"
}
}
}
},
"warnings": {
"test": "Wartości statystyk czołgów w fazie testowej mogą ulec zmianie."
"test": "Tanks in testing are subject to change and may not represent the final product."
}
},
"home": {
"discord": {
"join_short": "Dołącz do elity!",
"join_long": "Dołącz do topowej elity!",
"join_short": "Join cool people!",
"join_long": "Join super duper cool people!",
"action": "Discord"
},
"hero": {
"credit": "Według {name} ♥️"
"credit": "By {name} ❤️"
},
"hot_tanks": {
"title": "Popularne czołgi",
"subtitle": "Ostatnie {days} dni"
"title": "Popular tanks",
"subtitle": "Last {days} days"
},
"other_projects": {
"title": "Inne epickie projekty"
"title": "Other cool projects"
},
"help_translate": "Proszę pomóż przetłumaczyć {name} na {language}!",
"seo_description": "Narzędzia do wszystkiego World of Tanks Blitz"
"help_translate": "Please help translate {name} to {language}!",
"seo_description": "Tools for everything World of Tanks Blitz"
},
"docs": {
"patches": {
"name": "Zapisy zmian BlitzKit",
"seo_description": "Zapisy zmian BlitzKit: Discord bot i strona internetowa"
"name": "BlitzKit Changelogs",
"seo_description": "Changelogs for BlitzKit Discord bot and website"
}
},
"tools": {
"playlist": {
"reset": "Resetuj",
"shuffle": "Wymieszaj",
"remove": "Usuń czołgi, którymi grałem",
"tracking": "BlitzKit zapisuje twoje bitwy",
"reset": "Reset",
"shuffle": "Shuffle",
"remove": "Remove played",
"tracking": "BlitzKit is tracking your battles",
"sort": {
"button": "Filtruj według",
"last_played": "Ostatnio używane czołgi",
"least_played": "Najmniej używane czołgi"
"button": "Sort by",
"last_played": "Last played",
"least_played": "Least played"
},
"table": {
"index": "#",
"never": "Nigdy",
"tank": "Czołg",
"last_played": "Ostatnio używane czołgi",
"battles": "Wszystkie bitwy na koncie",
"never": "Never",
"tank": "Tank",
"last_played": "Last played",
"battles": "Career battles",
"unrecorded": "--",
"wins": "Zwycięstwa"
},
"large_warning": {
"title": "To jest mnóstwo czołgów!",
"description": "Czy na pewno chcesz stworzyć playlistę złożoną z {count} czołgów? To {time} godzin gry przy 50% zwycięstw!",
"cancel": "Anuluj",
"proceed": "Zatwierdź"
"title": "That's a lot of tanks!",
"description": "Are you sure you want to create a playlist of {count} tanks? That's {time} hours of gameplay at 50% winrate!",
"cancel": "Cancel",
"proceed": "Proceed"
},
"generate": "Wygeneruj playlistę",
"name": "Playlista",
"description": "Generator playlisty czołgów",
"seo_description": "Nie wiesz, którym czołgiem grać w World of Tanks Blitz? Użyj funkcji playlisty BlitzKit, i ułóż własną listę czołgów do gry!",
"button": "Wygeneruj",
"pool_size": "{count} możliwe czołgi"
"generate": "Generate playlist",
"name": "Playlist",
"description": "Tank playlist generator",
"seo_description": "Don't know what to play in World of Tanks Blitz? Use BlitzKit Playlist to get suggestions!",
"button": "Generate",
"pool_size": "{count} possible tanks"
},
"mixer": {
"name": "Mikser",
"name": "Mixer",
"button": "Mix and match",
"description": "Mix tank modules",
"seo_description": "Mix and match hulls, turrets, and guns of tanks in World of Tanks Blitz",
"screenshot": "Zrzut ekranu",
"randomize": "Wymieszaj"
"screenshot": "Screenshot",
"randomize": "Randomize"
},
"guess": {
"name": "Zgadnij",
"name": "Guess",
"button": "What could it be?",
"description": "Silhouette mini-game",
"seo_description": "Play a tank guessing game with a silhouette in World of Tanks Blitz",
"search": {
"placeholder": "Wpisz odpowiedź",
"skip": "Pomiń",
"guess": "Zgaduję",
"next": "Następny",
"placeholder": "Type to guess...",
"skip": "Skip",
"guess": "Guess",
"next": "Next",
"no_results": "Brak wyników"
},
"stats": "{correct} / {total} correct, {streak} streak",
"help": {
"title": "Odsłonić część czołgu?",
"title": "Partially reveal tank?",
"description": "Reveals the tank without showing the name. This round will be considered incorrect and break your streak!",
"cancel": "Anuluj",
"reveal": "Pokaż"
"cancel": "Cancel",
"reveal": "Reveal"
},
"clear": "Wyczyść",
"select_all": "Zaznacz wszystko ",
"tier": "Poziom czołgu {tier}"
"clear": "Clear",
"select_all": "Select all",
"tier": "Tier {tier}"
},
"tankopedia": {
"search": "Wyszukaj statystykę...",
"search": "Search a stat...",
"name": "Tankopedia",
"button": "Znajdź czołg",
"description": "Encyklopedia czołgów w WoTb",
"button": "Find tanks",
"description": "Blitz tank encyclopedia",
"seo_description": "Statistics, armor, and more for all tanks in World of Tanks Blitz",
"tank_seo_title": "{name} - Tier {tier} {nationality} {class} | {website} Tankopedia | World of Tanks Blitz (WoTB)",
"tank_seo_description": "{name} armor, statistics, equipment, provisions, and more. {dpm} DPM, {alpha}hp alpha, {penetration}mm penetration, {speed}kph top speed, {health}hp health.",
"visualizers": {
"reload": {
"skip": "Pomiń"
"skip": "Skip"
},
"assault": {
"title": "Assault shells"
},
"rotator": {
"hull": "Kadłub",
"turret": "Wieża",
"hard": "Utwardzony",
"hull": "Hull",
"turret": "Turret",
"hard": "Hard",
"medium": "Średni",
"soft": "Miękki",
"effective": "Rzeczywisty"
"soft": "Soft",
"effective": "Effective"
},
"ricochet": {
"angle": "Kąt",
"angle": "Angle",
"normalization": "Normalization",
"effective_angle": "Rzeczywisty kąt nachylenia pancerza",
"armor": "Pancerz",
"no_ricochet": "Pociski odłamkowe nie rykoszetują."
"effective_angle": "Effective angle",
"armor": "Armor",
"no_ricochet": "Explosive shells don't ricochet"
},
"view_range": {
"camo": "Kamuflaż",
"ghost_factory": "Fabryka duchów",
"dead_rail": "Martwy tor"
"camo": "Camo",
"ghost_factory": "Ghost Factory",
"dead_rail": "Dead Rail"
},
"flexibility": {
"yaw": "Yaw",
@@ -443,71 +443,71 @@
"ricochet": "Ricochet",
"explosion_radius": "Explosion radius",
"exit": "Exit",
"reset": "Zresetuj"
"reset": "Reset"
},
"advanced": "Zaawansowane podświetlanie",
"advanced": "Advanced highlighting",
"shot_card": {
"element": {
"primary": "Główny",
"primary": "Primary",
"spaced": "Spaced",
"external": "Zewnętrzny",
"gap": "Odstęp",
"gun": "Działo",
"track": "Gąsienice"
"external": "External",
"gap": "Gap",
"gun": "Gun",
"track": "Tracks"
},
"status": {
"penetration": "{damage} penetracja punktów wytrzymałości",
"blocked": "Zablokowany",
"ricochet": "Rykoszet",
"splash": "{damage} rozprysk punktów wytrzymałości"
"penetration": "{damage}hp penetration",
"blocked": "Blocked",
"ricochet": "Ricochet",
"splash": "{damage}hp splash"
},
"stats": {
"thickness_and_angle": "{thickness}mm {angle}",
"nominal": "{thickness}mm nominalny",
"ricochet_loss": "{loss}% penetracji"
"thickness_and_angle": "{thickness}mm {angle}°",
"nominal": "{thickness}mm nominal",
"ricochet_loss": "{loss}% penetration"
}
}
},
"static": {
"name": "Pancerz statyczny",
"primary": "Główny Pancerz",
"spaced": "Ekran przeciwkumulacyjny",
"external": "Zewnętrzny",
"name": "Static armor",
"primary": "Primary",
"spaced": "Spaced",
"external": "External",
"edit": {
"label": "Edytuj",
"reset": "Zresetuj"
"label": "Edit",
"reset": "Reset"
}
}
},
"meta": {
"back": "Powrót",
"compare": "Porównaj",
"back": "Back",
"compare": "Compare",
"swap": {
"button": "Zamień",
"title": "Zamień czołgi"
"button": "Swap",
"title": "Swap tanks"
},
"vanilla": "Vanilla",
"opentest": "OpenTest",
"full_name": "Pełna nazwa",
"full_name": "Full name",
"nation": "Naród",
"class": "Klasa",
"tier": "Poziom",
"type": "Rodzaj",
"dev_id": "DEV: ID",
"dev_name": "DEV: NAZWA",
"purchase": "Cena zakupu",
"restoration": "Cena odzyskania",
"dev_name": "DEV: NAME",
"purchase": "Purchase price",
"restoration": "Restoration price",
"sale": "Sale price",
"research": "Research XP"
},
"tech_tree": {
"title": "Drzewko technologiczne",
"battle": "1 bitwa",
"battles": "{count} bitew",
"title": "Tech tree",
"battle": "1 battle",
"battles": "{count} battles",
"route": "Route {index} of {count}"
},
"game_modes": {
"title": "Umiejętności trybu gry"
"title": "Game mode abilities"
},
"review": {
"title": "Review videos",
@@ -520,26 +520,26 @@
"equalize": "Equalize"
},
"modules": {
"title": "Moduły",
"stock": "Standardowy",
"upgrade": "Ulepsz"
"title": "Modules",
"stock": "Stock",
"upgrade": "Upgrade"
},
"equipment": {
"title": "Wyposażenie",
"clear": "Wyczyść"
"title": "Equipment",
"clear": "Clear"
},
"provisions": {
"title": "Materiały eksploatacyjne",
"clear": "Wyczyść"
"title": "Provisions",
"clear": "Clear"
},
"consumables": {
"title": "Consumables",
"clear": "Wyczyść",
"clear": "Clear",
"info": "Yellow consumables do not affect the statistics."
},
"skills": {
"title": "Crew skills",
"clear": "Wyczyść",
"clear": "Clear",
"maximize": "Maximize",
"info": "Yellow skills only apply under special circumstances. Hold Space to quickly toggle between level 0 and 7."
},
@@ -586,19 +586,19 @@
"damageWithoutAssault": "Damage without assault shells",
"clipDamage": "Clipping potential",
"moduleDamage": "Module damage",
"explosionRadius": "Promień eksplozji",
"shellVelocity": "Prędkość pocisku",
"aimTime": "Czas celowania",
"explosionRadius": "Explosion radius",
"shellVelocity": "Shell velocity",
"aimTime": "Aim time",
"dispersion": "Still",
"dispersionMoving": "Moving",
"dispersionHullTraversing": "Hull traversing",
"dispersionTurretTraversing": "Turret traversing",
"dispersionShooting": "Strzelanie",
"dispersionGunDamaged": "Uszkodzone Działo",
"gunDepression": "Limit obrotu działa w dół",
"gunElevation": "Limit obrotu działa w górę",
"gunFrontalDepression": "Przedni limit obrotu działa w dół",
"gunFrontalElevation": "Przedni limit obrotu działa w górę",
"dispersionShooting": "Shooting",
"dispersionGunDamaged": "Gun damaged",
"gunDepression": "Depression",
"gunElevation": "Elevation",
"gunFrontalDepression": "Frontal depression",
"gunFrontalElevation": "Frontal elevation",
"gunRearDepression": "Rear depression",
"gunRearElevation": "Rear elevation",
"azimuthLeft": "Azimuth left",
@@ -656,14 +656,14 @@
"loader": "Loader"
},
"maneuverability": {
"title": "Mobilność "
"title": "Maneuverability"
},
"survivability": {
"title": "Przetrwanie"
"title": "Survivability"
}
},
"compare": {
"name": "Porównaj",
"name": "Compare",
"button": "Compare tanks",
"description": "Compare tank statistics",
"seo_description": "Compare tanks statistics and loadouts in World of Tanks Blitz",
@@ -673,7 +673,7 @@
"title": "Add tanks",
"description": "Select tanks to add to comparison"
},
"clear": "Wyczyść",
"clear": "Clear",
"deltas": {
"none": "No deltas",
"percentage": "Percentage",
@@ -686,17 +686,17 @@
},
"table": {
"equipment": {
"clear": "Wyczyść",
"clear": "Clear",
"apply_to_all": "Apply to all"
},
"skills": {
"clear": "Wyczyść",
"clear": "Clear",
"maximize": "Maximize",
"equalize": "Equalize"
},
"modules": {
"stock": "Standardowy",
"upgrade": "Ulepsz"
"stock": "Stock",
"upgrade": "Upgrade"
},
"provisions": {
"clear": "Clear this tank",
@@ -761,14 +761,14 @@
"seo_description": "Track your stats in real time as you play World of Tanks Blitz",
"search": {
"lookup": "Look up a player to get started",
"hint": "Szukaj graczy...",
"searching": "Wyszukiwanie...",
"hint": "Search players...",
"searching": "Searching...",
"no_results": "No players found"
},
"controls": {
"title": "Tracking {name}",
"subtitle": "Since {time}",
"reset": "Zresetuj",
"reset": "Reset",
"change": "Change"
},
"no_cc": {
@@ -792,7 +792,7 @@
"description": "Rank Blitz tanks",
"seo_description": "Explore tier lists ranking tanks from best to worst, based on player ratings and statistical performance in World of Tanks Blitz",
"buttons": {
"reset": "Zresetuj",
"reset": "Reset",
"share": "Share"
}
},
@@ -828,7 +828,7 @@
"success": {
"title": "Copy URL",
"info": "Generated the URL for {name}. The embed will start tracking as soon as the URL is pasted. Reset at any time by right-clicking on PC or long-pressing on mobile and then selecting \"Reset\".",
"back": "Powrót",
"back": "Back",
"copy": "Copy URL"
}
},
@@ -839,7 +839,7 @@
},
"customize": {
"title": "Customize",
"reset": "Zresetuj",
"reset": "Reset",
"description": "Bells and whistles of your embed"
}
},
+5 -5
View File
@@ -41,7 +41,7 @@
"development": "Development",
"opentest": "TesteAberto",
"preview": "Preview",
"production": "Produção"
"production": "Production"
},
"locales": {
"en": "Inglês",
@@ -187,11 +187,11 @@
},
"website": {
"redirect": {
"title": "301: movido permanentemente",
"title": "301: moved permanently",
"body": "This page has moved. Redirecting..."
},
"settings": {
"title": "Configurações",
"title": "Settings",
"description": "Change BlitzKit settings",
"dev_mode": "Modo de desenvolvimento",
"auto_load_models": "Auto-load models",
@@ -207,9 +207,9 @@
"copied": "Copiado!"
},
"tank_search": {
"select_dialog_title": "Procura por tanque",
"select_dialog_title": "Search for tanks",
"swap_dialog_title": "Swap tank",
"clear_filters": "Limpar filtros",
"clear_filters": "Clear filters",
"login": "Login to use this",
"search_bar_hint": "Procurar tanques...",
"recent": "Visto recentemente",
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

@@ -0,0 +1,38 @@
---
import { tankIcon } from "@blitzkit/core";
import { Flex, Heading, Link, Strong, Text } from "@radix-ui/themes";
---
<Flex justify="center" px="4">
<Link
color="gray"
highContrast
style={{ display: "content" }}
href="/docs/guides/christmas-2025"
underline="none"
>
<Flex
mt="3"
px="5"
py="4"
style={{
borderRadius: "var(--radius-3)",
backgroundImage: "url(https://i.imgur.com/LRYmSkg.png)",
backgroundSize: "cover",
backgroundPosition: "center",
boxSizing: "border-box",
}}
align="center"
gap="5"
>
<img src={tankIcon(25169)} style={{ height: "min(15vw, 5rem)" }} />
<Flex direction="column">
<Heading size={{ initial: "4", sm: "6" }}>Christmas 2025</Heading>
<Text size={{ initial: "2", sm: "3" }}>
Win the <Strong>GSOR the TANK</Strong> and more!
</Text>
</Flex>
</Flex>
</Link>
</Flex>
@@ -0,0 +1,113 @@
---
import { Grid, Heading, Text } from "@radix-ui/themes";
import { credits } from "../config/credits";
import { getStrings } from "../core/i18n/getStrings";
interface Props {
locale: string;
}
const { locale } = Astro.props;
const strings = getStrings(locale);
---
<style>
.credits {
display: flex;
justify-content: center;
padding: var(--space-4);
margin-top: var(--space-4);
.inner {
display: flex;
flex-direction: column;
gap: var(--space-2);
width: 100%;
max-width: 80rem;
.list {
gap: var(--space-5);
.link {
display: contents;
.person {
--profile-size: 5rem;
display: flex;
flex-direction: column;
background-color: var(--gray-3);
padding: var(--space-4);
padding-top: 0;
margin-top: calc(var(--profile-size) / 2);
gap: var(--space-1);
box-shadow: var(--shadow-2);
border-radius: var(--radius-3);
.link:first-child & {
background-color: var(--gray-5);
box-shadow: 0 0 2rem var(--gray-a4);
outline: 1px solid var(--gray-7);
/* background-color: var(--blue-3);
outline: 1px solid var(--blue-5);
box-shadow: 0 0 3rem var(--blue-a3); */
}
img {
border-radius: 50%;
position: relative;
margin-top: calc(var(--profile-size) / -2);
object-fit: cover;
width: var(--profile-size);
height: var(--profile-size);
margin-bottom: var(--space-2);
}
}
}
}
}
}
</style>
<div class="credits">
<div class="inner">
<Heading align="center">{strings.credits.title}</Heading>
<Grid
className="list"
py="4"
columns="repeat(auto-fill, minmax(16rem, 1fr))"
flow="dense"
gap="4"
gapY="8"
>
{
credits.map((person) => (
<a target="_blank" href={person.url} class="link">
<div class="person">
<img src={person.image} />
<Text weight="medium" className="text" color="gray" highContrast>
{person.name}
</Text>
<Text className="text" color="gray" wrap="pretty">
{person.description}
</Text>
</div>
</a>
))
}</Grid
>
</div>
</div>
@@ -84,9 +84,6 @@ const strings = getStrings(locale);
sm: "start",
}}
>
<LinkI18n {locale} href="/docs/about/credits" color="gray">
{strings.website.footer.about.credits}
</LinkI18n>
<Link
href="https://discord.gg/nDt7AjGJQH"
target="_blank"
@@ -0,0 +1,89 @@
import { Code, Flex, Heading, Link, Text } from "@radix-ui/themes";
import { useEffect, useState, type ReactNode } from "react";
import type { MaybeSkeletonComponentProps } from "../types/maybeSkeletonComponentProps";
import { InlineSkeleton } from "./InlineSkeleton";
const TIME = 1764898200 * 1000;
export function Giveaway({ skeleton }: MaybeSkeletonComponentProps) {
const [timeLeft, setTimeLeft] = useState<number>(TIME - Date.now());
useEffect(() => {
const interval = setInterval(() => {
setTimeLeft(TIME - Date.now());
}, 1000);
return () => {
clearInterval(interval);
};
}, []);
let string: ReactNode;
if (timeLeft > 0) {
const hours = Math.floor(timeLeft / (1000 * 60 * 60));
const minutes = Math.floor((timeLeft / (1000 * 60)) % 60)
.toString()
.padStart(2, "0");
const seconds = Math.floor((timeLeft / 1000) % 60)
.toString()
.padStart(2, "0");
string = (
<>
in{" "}
<Code highContrast>
{hours}h {minutes}m {seconds}s
</Code>
</>
);
} else {
string = "has ended. Come again later!";
}
return (
<Flex justify="center" px="4">
<Link
color="gray"
highContrast
style={{ display: "content" }}
href="https://discord.gg/njQdBhNS?event=1446208951136813117"
underline="none"
>
<Flex
mt="3"
px="5"
py="4"
style={{
borderRadius: "var(--radius-3)",
backgroundImage: "url(https://i.imgur.com/LRYmSkg.png)",
backgroundSize: "cover",
backgroundPosition: "center",
boxSizing: "border-box",
boxShadow: "0 0 var(--space-7) var(--blue-a5)",
}}
align="center"
gap="5"
>
<img
src="https://i.imgur.com/mvmJQgB.png"
style={{
height: "min(15vw, 5rem)",
filter: "drop-shadow(0.5rem 0.5rem 1rem #00000080)",
}}
/>
<Flex direction="column">
<Heading size={{ initial: "4", sm: "6" }}>
Music Box giveaway {!skeleton && string}{" "}
{skeleton && <InlineSkeleton width="10rem" />}
</Heading>
<Text size={{ initial: "2", sm: "3" }}>
Join the BlitzKit Discord server event
</Text>
</Flex>
</Flex>
</Link>
</Flex>
);
}
+2 -9
View File
@@ -1,20 +1,13 @@
import { Link, Table } from '@radix-ui/themes';
import MarkdownToJSX from 'markdown-to-jsx';
import type { ComponentProps } from 'react';
import { docImageUrl, isExternalImage } from '../core/blitzkit/docImages';
interface MarkdownProps {
children: string;
}
function Image({ src, ...props }: ComponentProps<'img'>) {
return (
<img
style={{ maxWidth: '100%' }}
src={src && isExternalImage(src) ? docImageUrl(src) : src}
{...props}
/>
);
function Image(props: ComponentProps<'img'>) {
return <img style={{ maxWidth: '100%' }} {...props} />;
}
export function Markdown({ children }: MarkdownProps) {
@@ -0,0 +1,3 @@
export function StupidCat() {
return <img src="https://i.imgur.com/6FBBzb9.png" />;
}
@@ -1,7 +1,7 @@
import {
normalizeBoundingBox,
resolveDpm,
tankBoundingBox,
unionBoundingBox,
type TankDefinition,
} from "@blitzkit/core";
import { useMemo } from "react";
@@ -112,7 +112,10 @@ export function TankSearchCard({ tank, onSelect }: TankSearchCardProps) {
return (tank.camouflage_still * gun.camouflage_loss * 100).toFixed(0);
case "survivability.volume": {
const dimensions = normalizeBoundingBox(
tankBoundingBox(tankModelDefinition, turretModelDefinition),
unionBoundingBox(
tankModelDefinition.bounding_box!,
turretModelDefinition.bounding_box!,
),
);
return (dimensions.x * dimensions.y * dimensions.z).toFixed(0);
@@ -120,7 +123,10 @@ export function TankSearchCard({ tank, onSelect }: TankSearchCardProps) {
case "survivability.length": {
const bounds = normalizeBoundingBox(
tankBoundingBox(tankModelDefinition, turretModelDefinition),
unionBoundingBox(
tankModelDefinition.bounding_box!,
turretModelDefinition.bounding_box!,
),
);
return Math.max(bounds.x, bounds.y, bounds.z).toFixed(0);
@@ -3,7 +3,7 @@ import {
normalizeBoundingBox,
resolveDpm,
SEARCH_KEYS,
tankBoundingBox,
unionBoundingBox,
type TankDefinition,
} from "@blitzkit/core";
import { literals } from "@blitzkit/i18n/src/literals";
@@ -137,10 +137,16 @@ export const TankSearch = memo<TankSearchProps>(
const bTurretModelDefinition =
bTankModelDefinition.turrets[b.turrets.at(-1)!.id];
const aSize = normalizeBoundingBox(
tankBoundingBox(aTankModelDefinition, aTurretModelDefinition),
unionBoundingBox(
aTankModelDefinition.bounding_box!,
aTurretModelDefinition.bounding_box!,
),
);
const bSize = normalizeBoundingBox(
tankBoundingBox(bTankModelDefinition, bTurretModelDefinition),
unionBoundingBox(
bTankModelDefinition.bounding_box!,
bTurretModelDefinition.bounding_box!,
),
);
const aVolume = aSize.x * aSize.y * aSize.z;
const bVolume = bSize.x * bSize.y * bSize.z;
@@ -158,10 +164,16 @@ export const TankSearch = memo<TankSearchProps>(
const bTurretModelDefinition =
bTankModelDefinition.turrets[b.turrets.at(-1)!.id];
const aSize = normalizeBoundingBox(
tankBoundingBox(aTankModelDefinition, aTurretModelDefinition),
unionBoundingBox(
aTankModelDefinition.bounding_box!,
aTurretModelDefinition.bounding_box!,
),
);
const bSize = normalizeBoundingBox(
tankBoundingBox(bTankModelDefinition, bTurretModelDefinition),
unionBoundingBox(
bTankModelDefinition.bounding_box!,
bTurretModelDefinition.bounding_box!,
),
);
const aLength = Math.max(aSize.x, aSize.y, aSize.z);
const bLength = Math.max(bSize.x, bSize.y, bSize.z);
+113
View File
@@ -0,0 +1,113 @@
interface CreditPeople {
name: string;
description: string;
url?: string;
image: string;
}
export const credits: CreditPeople[] = [
{
name: "TrèsAbhi",
description: "Founder and lead developer",
url: "https://abhi-deep.com/",
image: "https://github.com/tresabhi.png",
},
{
name: "HitJack",
description: "Developer on website",
url: "https://github.com/Hitjack007",
image: "https://github.com/Hitjack007.png",
},
{
name: "aeson000",
description: "Advisor on game and website",
url: "https://discord.gg/WHdER7ZPAD",
image: "/media/users/aeson000.png",
},
{
name: "GonnaHetzMe",
description: "Developer and advisor on back end",
url: "https://github.com/karelpak1",
image: "https://github.com/karelpak1.png",
},
{
name: "ChickenMan7777",
description: "Moderator and former advisor on game mechanics",
url: "https://discord.gg/rEsUbXCqD9",
image: "/media/users/chickenman7777.webp",
},
{
name: "Avenir",
description: "Moderator and elite Advanced Recon Commando",
image: "/media/users/avenir.webp",
},
{
name: "Pyogenics",
description: "Former advisor on back end",
url: "https://github.com/Pyogenics",
image: "https://github.com/Pyogenics.png",
},
{
name: "Prince_NA",
description: "Former advisor on bot and website",
image: "/media/users/prince-na.webp",
},
{
name: "Vovko",
description: "Former advisor on bot",
url: "https://amth.one/",
image: "https://github.com/cufee.png",
},
{
name: "S0AP",
description: "Former advisor and advertiser on bot",
url: "https://discord.gg/3KfH93um3Y",
image: "/media/users/s0ap.webp",
},
{
name: "xkdkw",
description: "Former advisor on bot and website",
image: "/media/users/xkdkw.webp",
},
{
name: "Synx",
description: "Former advisor on bot and website",
url: "https://www.youtube.com/@Synx_Blitz",
image: "/media/users/synx.webp",
},
{
name: "34wotb",
description: "Former advisor on bot",
url: "https://www.twitch.tv/34wotb/",
image: "/media/users/34wotb.jpg",
},
{
name: "HisRoyalFatness",
description: "Former advisor on website",
url: "https://www.youtube.com/@hisroyalfatness",
image: "/media/users/hisroyalfatness.jpg",
},
{
name: "Droodles Blitz",
description: "Former advisor on website",
url: "https://www.youtube.com/@DroodlesBlitz",
image: "/media/users/droodles-blitz.jpg",
},
{
name: "Minitelrose",
description: "Former advisor on game mechanics",
url: "https://wotinspector.com/",
image: "/media/users/minitelrose.webp",
},
{
name: "Maddox",
description: "Game asset decompression",
url: "https://github.com/Maddoxkkm",
image: "https://github.com/Maddoxkkm.png",
},
{
name: "TJ Duff",
description: "Chief executive cheer leader",
image: "/media/users/tj-duff.jpg",
},
];
@@ -1,32 +0,0 @@
import { alias } from "@blitzkit/core";
const EXTERNAL_IMAGE = /^https?:\/\//;
const MARKDOWN_IMAGES = [
/!\[[^\]]*\]\((\S+?)(?:\s+"[^"]*")?\)/g,
/<img[^>]*\ssrc=["']([^"']+)["']/g,
];
export function isExternalImage(src: string) {
return EXTERNAL_IMAGE.test(src);
}
export function docImageId(url: string) {
let hash = 0x811c9dc5;
for (let index = 0; index < url.length; index++) {
hash ^= url.charCodeAt(index);
hash = Math.imul(hash, 0x01000193) >>> 0;
}
return hash.toString(16).padStart(8, "0");
}
export function docImageUrl(url: string) {
return alias("api", `/docs/images/${docImageId(url)}.webp`);
}
export function externalDocImages(markdown: string) {
return MARKDOWN_IMAGES.flatMap((pattern) =>
Array.from(markdown.matchAll(pattern), (match) => match[1]),
).filter(isExternalImage);
}
@@ -8,8 +8,8 @@ import {
resolvePenetrationCoefficient,
ShellType,
sum,
tankBoundingBox,
TankClass,
unionBoundingBox,
type EngineDefinition,
type EquipmentDefinitions,
type GunDefinition,
@@ -336,7 +336,10 @@ export function tankCharacteristics(
);
const size = normalizeBoundingBox(
tankBoundingBox(tankModelDefinition, turretModelDefinition),
unionBoundingBox(
tankModelDefinition.bounding_box!,
turretModelDefinition.bounding_box!,
),
);
const weightKg =
tank.weight + engine.weight + track.weight + turret.weight + gun.weight;
@@ -1,39 +0,0 @@
import type { APIContext, GetStaticPathsItem } from "astro";
import sharp from "sharp";
import {
docImageId,
externalDocImages,
} from "../../../../core/blitzkit/docImages";
import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths";
import { getStaticPaths as _getStaticPaths } from "../../_index";
export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => {
const documents = import.meta.glob<string>(
"../../../../../../../docs/**/*.md",
{ eager: true, query: "?raw", import: "default" },
);
const urls = new Set(Object.values(documents).flatMap(externalDocImages));
return Array.from(
urls,
(url) =>
({
params: { id: docImageId(url) },
props: { url },
}) satisfies GetStaticPathsItem,
);
});
export async function GET({ props }: APIContext<{ url: string }>) {
const response = await fetch(props.url);
if (!response.ok) {
throw new Error(`Failed to fetch ${props.url}: ${response.status}`);
}
const image = await sharp(Buffer.from(await response.arrayBuffer()))
.webp()
.toBuffer();
return new Response(new Uint8Array(image));
}
@@ -1,15 +1,16 @@
import {
parseHierarchies,
Sc2ReadStream,
ScgReadStream,
toUniqueId,
VertexAttribute,
vertexAttributeVectorSizes,
type Hierarchy,
type VehicleDefinitionList,
} from "@blitzkit/core";
import type { AbstractVFS } from "@blitzkit/core/src/blitzkit/vfs/abstract";
import { Accessor, Document, Node, NodeIO, Scene } from "@gltf-transform/core";
import type { APIContext } from "astro";
import { times } from "lodash-es";
import { vfs } from "../../../../core/blitzkit/vfs";
export { getStaticPaths } from "./_index";
@@ -55,10 +56,13 @@ async function extractArmor(vfs: AbstractVFS, fileName: string) {
const scene = document.createScene();
const buffer = document.createBuffer();
parseHierarchies<Scene | Node>(
sc2["#hierarchy"],
scene,
(hierarchy, components, parent) => {
function parseHierarchies(hierarchies: Hierarchy[], parent: Scene | Node) {
hierarchies.forEach((hierarchy) => {
const components = times(
hierarchy.components.count,
(index) => hierarchy.components[index.toString().padStart(4, "0")],
);
components.forEach((component) => {
switch (component["comp.typename"]) {
case "TransformComponent":
@@ -159,9 +163,13 @@ async function extractArmor(vfs: AbstractVFS, fileName: string) {
}
});
return parent;
},
);
if (hierarchy["#hierarchy"]) {
parseHierarchies(hierarchy["#hierarchy"], parent);
}
});
}
parseHierarchies(sc2["#hierarchy"], scene);
return document;
}
@@ -1,13 +1,13 @@
import {
bufferToBigInt,
DdsReadStream,
parseHierarchies,
PvrReadStream,
Sc2ReadStream,
ScgReadStream,
toUniqueId,
VertexAttribute,
type ConfigArchive,
type Hierarchy,
type TankParameters,
type Textures,
type VehicleDefinitionList,
@@ -240,10 +240,8 @@ async function extractModel(vfs: AbstractVFS, path: string) {
materials.set(id, resolvedMaterial);
});
parseHierarchies<Scene | Node>(
sc2["#hierarchy"],
scene,
(hierarchy, components, parent) => {
function parseHierarchies(hierarchies: Hierarchy[], parent: Scene | Node) {
hierarchies.forEach((hierarchy) => {
if (
omitMeshNames.start.some((omit) => hierarchy.name.startsWith(omit)) ||
omitMeshNames.end.some((omit) => hierarchy.name.endsWith(omit))
@@ -251,6 +249,10 @@ async function extractModel(vfs: AbstractVFS, path: string) {
return;
const node = document.createNode(hierarchy.name);
const components = times(
hierarchy.components.count,
(index) => hierarchy.components[index.toString().padStart(4, "0")],
);
components.forEach((component) => {
switch (component["comp.typename"]) {
@@ -359,11 +361,15 @@ async function extractModel(vfs: AbstractVFS, path: string) {
}
});
parent.addChild(node);
if (hierarchy["#hierarchy"]) {
parseHierarchies(hierarchy["#hierarchy"], node);
}
return node;
},
);
parent.addChild(node);
});
}
parseHierarchies(sc2["#hierarchy"], scene);
scene.addChild(document.createNode("test"));
@@ -5,6 +5,7 @@ import locales from "@blitzkit/i18n/locales.json";
import { literals } from "@blitzkit/i18n/src/literals";
import { Box, Flex, Link } from "@radix-ui/themes";
import { AesonPlug } from "../../../components/AesonPlug";
import CreditsPromo from "../../../components/CreditsPromo.astro";
import HomeHero from "../../../components/HomeHero.astro";
import { Plugs } from "../../../components/Plugs";
import ToolCard from "../../../components/ToolCard.astro";
@@ -113,5 +114,7 @@ export const getStaticPaths = mixStaticPaths(_getStaticPaths, () => {
</Flex>
</Flex>
<CreditsPromo {locale} />
<div style={{ flex: 1 }}></div>
</BaseLayout>