Merge branch 'main' into ue4

This commit is contained in:
TrèsAbhi committed 2025-08-15 12:40:17 -05:00
commit 8478930f2e
65 files changed
+3226 -1888

No files matched your search

+2
View File
@@ -88,6 +88,7 @@
"gameplay",
"glsl",
"GLTF",
"gtag",
"haha",
"highp",
"iife",
@@ -110,6 +111,7 @@
"Maddoxkkm",
"malum",
"mantlets",
"mediump",
"metallicness",
"metalness",
"minitelrose",
+50
View File
@@ -0,0 +1,50 @@
# BlitzKit License
Version 1.1, August 5th, 2025. Copyright (c) TrèsAbhi.
Permission is hereby granted to anyone to:
- Fork, clone, and download the contents of this repository.
- Modify the code locally in private.
- Use and run the code **only for personal, non-public testing, or educational purposes**.
The following restrictions apply:
- You may **not** deploy, host, or make this project or any derivative publicly accessible.
- You may **not** share screenshots, recordings, or any form of media from this project unless you intend to merge your changes back into BlitzKit.
- You may **not** use this project or its contents in any public or commercial product.
- You may **not** redistribute modified or unmodified versions of this project.
Contributions:
- You are encouraged to propose changes or improvements via pull requests to the original BlitzKit repository.
- Any such contributions will be considered under the same license terms unless explicitly stated otherwise.
Violations of this license may result in revocation of usage rights and takedown requests.
For questions or licensing inquiries, contact support@blitzkit.app.
## Notes on Existing Forks
All forks fall under these categories:
- If your fork was made from commit `8462901` (Apr 17, 2023) or between commits `2958124` (Jul 3, 2023) and `85e483e` (Jul 13, 2024), your fork falls under the **MIT license**.
- If your fork was made from commits between `5646093` (Jul 13, 2024) and `e3f131c` (Aug 5, 2025), your fork falls under the **AGPL-3.0 license**.
- If your fork was made between commit `f7c356d` (Apr 17, 2023) and `2958124` (Jul 3, 2023), your fork falls under **a default exclusive copyright**.
- If your fork was made from commits between `e3f131c` (Aug 5, 2025) and today, your fork falls under **this license**.
MIT and AGPL-3.0 licensed fork owners:
- You may continue to use your fork of BlitzKit under the corresponding license.
- You may upgrade your fork up to commit `5cab394` (Aug 4, 2025) and keep the AGPL-3.0 license.
- You may upgrade to commit `e3f131c` (Aug 5, 2025) or beyond but have to **switch to this license** where all rules apply.
- If you upgrade to commit `e3f131c` (Aug 5, 2025) or beyond, you must retract all public deployments of BlitzKit.
- Existing screenshots, recordings, or any form of media can remain.
Default exclusive copyright fork owners:
- You may continue to use their forks under the AGPL-3.0 license as no license was applied to BlitzKit at the time of forking.
BlitzKit licensed fork owners:
- You may continue to upgrade to the latest commits only under the conditions of this license.
+18
View File
@@ -0,0 +1,18 @@
# BlitzKit 1.27.12
This update only affects the website.
## Features
- Added the ability to sort by HE shell penetration
- The Tankopedia compare bars now account for equipment and selected shells
- Made the hero section of the Tankopedia a bit shorter to show users that the page is scrollable
## Fixes
- The "Clear filters" button no longer shows up after clearing the search bar
- Guns for some tanks no longer go transparent when far away from the camera
- The dynamic armor tab no longer lags and flickers when rotating the turret of a tank
- Reimplemented the missing tool backgrounds in the navigation bar hamburger menu
- The navigation bar now properly goes transparent for pages that request like the Tankopedia pages
- Removed the tank name text outline because it doesn't work on most devices
+19
View File
@@ -0,0 +1,19 @@
# BlitzKit 1.27.13
This update adds a feature for a future version of WoTB 👁️👃👁️
## Features
- Added compatibility for the new mechanic for the upcoming Polish Tank Destroyers
- This code will remain dormant until the update for WoTB is released
- Added a progress bar to the hero section of the Tankopedia to show that the model is loading
- Reordered some Tankopedia statistics to bring important statistics like Damage to the very top
## Fixes
- The second column in the footer is now aligned on mobile devices
- Fixed an issue that stopped the armor highlighting from working on the Kiwi browser and on older devices
## Technical Changes
- Removed permissions for robots to crawl non-blitzkit.app domains
+8
View File
@@ -0,0 +1,8 @@
# BlitzKit 1.27.14
This is the 14th patch for `1.27`, making it the new longest lasting version beating the previous record of `1.15.13`!
## Fixes
- Optimized the rendering of the shells in the Tankopedia search
- The Assault Shells cursor now works on iOS devices
+7
View File
@@ -0,0 +1,7 @@
# BlitzKit 1.27.15
I did not see this coming
## Fixes
- The Assault Shell graph now supports damage factors of above 100% now
+3 -2
View File
@@ -1,7 +1,7 @@
{
"name": "blitzkit",
"private": true,
"version": "1.28.0-beta.1",
"version": "1.27.15",
"description": "🎉 Tools for everything World of Tanks Blitz",
"type": "module",
"types": "./types.d.ts",
@@ -21,7 +21,8 @@
"dev:bot": "bun run build:bot --dev && bun start:bot",
"start:bot": "bun dist/bot/main.js",
"dev:docs": "vitepress dev docs",
"dev:website": "cd packages/website-ue && bun run dev",
"dev:website": "cd packages/website && bun run dev",
"dev": "bun run dev:website",
"dev:api": "bun --filter=@blitzkit/api run dev",
"setup:depotdownloader": "bun --filter=@blitzkit/scripts run setup:depotdownloader",
"setup:patches": "bun --filter=@blitzkit/scripts run setup:patches",
+2
View File
@@ -4,4 +4,6 @@ export const youtubers = [
{ id: 'UCxRpZqMElssTdPYDK3x7wyg', name: 'N90' },
{ id: 'UC28Hc6p2MGoB5s78eHCYkQg', name: 'GuidesBlitz' },
{ id: 'UCLMEfqtlwLDN-UXX21RiEww', name: 'WoT Blitz Best Replays' },
{ id: 'UC3fZbetRUFoJL0IEN7IhG0Q', name: 'EconomicsMate' },
{ id: 'UCtsUVwBlhEnUunAKLn2dtsg', name: 'Megalion69' },
];
+197 -125
View File
@@ -5,10 +5,10 @@
// source: packages/core/src/protos/camouflage_definitions.proto
/* eslint-disable */
import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
import { createBaseI18nString, I18nString } from "./i18n";
import { BinaryReader, BinaryWriter } from '@bufbuild/protobuf/wire';
import { createBaseI18nString, I18nString } from './i18n';
export const protobufPackage = "blitzkit";
export const protobufPackage = 'blitzkit';
export interface CamouflageDefinitions {
camouflages: { [key: number]: Camouflage };
@@ -22,8 +22,8 @@ export interface CamouflageDefinitions_CamouflagesEntry {
export interface Camouflage {
id: number;
name: I18nString;
tank_name?: I18nString | undefined;
tank_name_full?: I18nString | undefined;
tank_name?: I18nString;
tank_name_full?: I18nString;
}
export function createBaseCamouflageDefinitions(): CamouflageDefinitions {
@@ -31,15 +31,25 @@ export function createBaseCamouflageDefinitions(): CamouflageDefinitions {
}
export const CamouflageDefinitions: MessageFns<CamouflageDefinitions> = {
encode(message: CamouflageDefinitions, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
encode(
message: CamouflageDefinitions,
writer: BinaryWriter = new BinaryWriter(),
): BinaryWriter {
Object.entries(message.camouflages).forEach(([key, value]) => {
CamouflageDefinitions_CamouflagesEntry.encode({ key: key as any, value }, writer.uint32(10).fork()).join();
CamouflageDefinitions_CamouflagesEntry.encode(
{ key: key as any, value },
writer.uint32(10).fork(),
).join();
});
return writer;
},
decode(input: BinaryReader | Uint8Array, length?: number): CamouflageDefinitions {
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
decode(
input: BinaryReader | Uint8Array,
length?: number,
): CamouflageDefinitions {
const reader =
input instanceof BinaryReader ? input : new BinaryReader(input);
let end = length === undefined ? reader.len : reader.pos + length;
const message = createBaseCamouflageDefinitions();
while (reader.pos < end) {
@@ -50,7 +60,10 @@ export const CamouflageDefinitions: MessageFns<CamouflageDefinitions> = {
break;
}
const entry1 = CamouflageDefinitions_CamouflagesEntry.decode(reader, reader.uint32());
const entry1 = CamouflageDefinitions_CamouflagesEntry.decode(
reader,
reader.uint32(),
);
if (entry1.value !== undefined) {
message.camouflages[entry1.key] = entry1.value;
}
@@ -68,10 +81,12 @@ export const CamouflageDefinitions: MessageFns<CamouflageDefinitions> = {
fromJSON(object: any): CamouflageDefinitions {
return {
camouflages: isObject(object.camouflages)
? Object.entries(object.camouflages).reduce<{ [key: number]: Camouflage }>((acc, [key, value]) => {
acc[globalThis.Number(key)] = Camouflage.fromJSON(value);
return acc;
}, {})
? Object.entries(object.camouflages).reduce<{
[key: number]: Camouflage;
}>((acc, [key, value]) => {
acc[globalThis.Number(key)] = Camouflage.fromJSON(value);
return acc;
}, {})
: {},
};
},
@@ -90,20 +105,23 @@ export const CamouflageDefinitions: MessageFns<CamouflageDefinitions> = {
return obj;
},
create<I extends Exact<DeepPartial<CamouflageDefinitions>, I>>(base?: I): CamouflageDefinitions {
create<I extends Exact<DeepPartial<CamouflageDefinitions>, I>>(
base?: I,
): CamouflageDefinitions {
return CamouflageDefinitions.fromPartial(base ?? ({} as any));
},
fromPartial<I extends Exact<DeepPartial<CamouflageDefinitions>, I>>(object: I): CamouflageDefinitions {
fromPartial<I extends Exact<DeepPartial<CamouflageDefinitions>, I>>(
object: I,
): CamouflageDefinitions {
const message = createBaseCamouflageDefinitions();
message.camouflages = Object.entries(object.camouflages ?? {}).reduce<{ [key: number]: Camouflage }>(
(acc, [key, value]) => {
if (value !== undefined) {
acc[globalThis.Number(key)] = Camouflage.fromPartial(value);
}
return acc;
},
{},
);
message.camouflages = Object.entries(object.camouflages ?? {}).reduce<{
[key: number]: Camouflage;
}>((acc, [key, value]) => {
if (value !== undefined) {
acc[globalThis.Number(key)] = Camouflage.fromPartial(value);
}
return acc;
}, {});
return message;
},
};
@@ -112,90 +130,116 @@ export function createBaseCamouflageDefinitions_CamouflagesEntry(): CamouflageDe
return { key: 0, value: createBaseCamouflage() };
}
export const CamouflageDefinitions_CamouflagesEntry: MessageFns<CamouflageDefinitions_CamouflagesEntry> = {
encode(message: CamouflageDefinitions_CamouflagesEntry, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
if (message.key !== undefined) {
writer.uint32(8).uint32(message.key);
}
if (message.value !== undefined) {
Camouflage.encode(message.value, writer.uint32(18).fork()).join();
}
return writer;
},
decode(input: BinaryReader | Uint8Array, length?: number): CamouflageDefinitions_CamouflagesEntry {
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
let end = length === undefined ? reader.len : reader.pos + length;
const message = createBaseCamouflageDefinitions_CamouflagesEntry();
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
case 1: {
if (tag !== 8) {
break;
}
message.key = reader.uint32();
continue;
}
case 2: {
if (tag !== 18) {
break;
}
message.value = Camouflage.decode(reader, reader.uint32());
continue;
}
export const CamouflageDefinitions_CamouflagesEntry: MessageFns<CamouflageDefinitions_CamouflagesEntry> =
{
encode(
message: CamouflageDefinitions_CamouflagesEntry,
writer: BinaryWriter = new BinaryWriter(),
): BinaryWriter {
if (message.key !== undefined) {
writer.uint32(8).uint32(message.key);
}
if ((tag & 7) === 4 || tag === 0) {
break;
if (message.value !== undefined) {
Camouflage.encode(message.value, writer.uint32(18).fork()).join();
}
reader.skip(tag & 7);
}
return message;
},
return writer;
},
fromJSON(object: any): CamouflageDefinitions_CamouflagesEntry {
return {
key: globalThis.Number(assertSet("CamouflageDefinitions_CamouflagesEntry.key", object.key)),
value: Camouflage.fromJSON(assertSet("CamouflageDefinitions_CamouflagesEntry.value", object.value)),
};
},
decode(
input: BinaryReader | Uint8Array,
length?: number,
): CamouflageDefinitions_CamouflagesEntry {
const reader =
input instanceof BinaryReader ? input : new BinaryReader(input);
let end = length === undefined ? reader.len : reader.pos + length;
const message = createBaseCamouflageDefinitions_CamouflagesEntry();
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
case 1: {
if (tag !== 8) {
break;
}
toJSON(message: CamouflageDefinitions_CamouflagesEntry): unknown {
const obj: any = {};
if (message.key !== undefined) {
obj.key = Math.round(message.key);
}
if (message.value !== undefined) {
obj.value = Camouflage.toJSON(message.value);
}
return obj;
},
message.key = reader.uint32();
continue;
}
case 2: {
if (tag !== 18) {
break;
}
create<I extends Exact<DeepPartial<CamouflageDefinitions_CamouflagesEntry>, I>>(
base?: I,
): CamouflageDefinitions_CamouflagesEntry {
return CamouflageDefinitions_CamouflagesEntry.fromPartial(base ?? ({} as any));
},
fromPartial<I extends Exact<DeepPartial<CamouflageDefinitions_CamouflagesEntry>, I>>(
object: I,
): CamouflageDefinitions_CamouflagesEntry {
const message = createBaseCamouflageDefinitions_CamouflagesEntry();
message.key = object.key ?? 0;
message.value = (object.value !== undefined && object.value !== null)
? Camouflage.fromPartial(object.value)
: createBaseCamouflage();
return message;
},
};
message.value = Camouflage.decode(reader, reader.uint32());
continue;
}
}
if ((tag & 7) === 4 || tag === 0) {
break;
}
reader.skip(tag & 7);
}
return message;
},
fromJSON(object: any): CamouflageDefinitions_CamouflagesEntry {
return {
key: globalThis.Number(
assertSet('CamouflageDefinitions_CamouflagesEntry.key', object.key),
),
value: Camouflage.fromJSON(
assertSet(
'CamouflageDefinitions_CamouflagesEntry.value',
object.value,
),
),
};
},
toJSON(message: CamouflageDefinitions_CamouflagesEntry): unknown {
const obj: any = {};
if (message.key !== undefined) {
obj.key = Math.round(message.key);
}
if (message.value !== undefined) {
obj.value = Camouflage.toJSON(message.value);
}
return obj;
},
create<
I extends Exact<DeepPartial<CamouflageDefinitions_CamouflagesEntry>, I>,
>(base?: I): CamouflageDefinitions_CamouflagesEntry {
return CamouflageDefinitions_CamouflagesEntry.fromPartial(
base ?? ({} as any),
);
},
fromPartial<
I extends Exact<DeepPartial<CamouflageDefinitions_CamouflagesEntry>, I>,
>(object: I): CamouflageDefinitions_CamouflagesEntry {
const message = createBaseCamouflageDefinitions_CamouflagesEntry();
message.key = object.key ?? 0;
message.value =
object.value !== undefined && object.value !== null
? Camouflage.fromPartial(object.value)
: createBaseCamouflage();
return message;
},
};
export function createBaseCamouflage(): Camouflage {
return { id: 0, name: createBaseI18nString(), tank_name: undefined, tank_name_full: undefined };
return {
id: 0,
name: createBaseI18nString(),
tank_name: undefined,
tank_name_full: undefined,
};
}
export const Camouflage: MessageFns<Camouflage> = {
encode(message: Camouflage, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
encode(
message: Camouflage,
writer: BinaryWriter = new BinaryWriter(),
): BinaryWriter {
if (message.id !== 0) {
writer.uint32(8).uint32(message.id);
}
@@ -206,13 +250,17 @@ export const Camouflage: MessageFns<Camouflage> = {
I18nString.encode(message.tank_name, writer.uint32(26).fork()).join();
}
if (message.tank_name_full !== undefined) {
I18nString.encode(message.tank_name_full, writer.uint32(34).fork()).join();
I18nString.encode(
message.tank_name_full,
writer.uint32(34).fork(),
).join();
}
return writer;
},
decode(input: BinaryReader | Uint8Array, length?: number): Camouflage {
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
const reader =
input instanceof BinaryReader ? input : new BinaryReader(input);
let end = length === undefined ? reader.len : reader.pos + length;
const message = createBaseCamouflage();
while (reader.pos < end) {
@@ -261,10 +309,14 @@ export const Camouflage: MessageFns<Camouflage> = {
fromJSON(object: any): Camouflage {
return {
id: globalThis.Number(assertSet("Camouflage.id", object.id)),
name: I18nString.fromJSON(assertSet("Camouflage.name", object.name)),
tank_name: isSet(object.tank_name) ? I18nString.fromJSON(object.tank_name) : undefined,
tank_name_full: isSet(object.tank_name_full) ? I18nString.fromJSON(object.tank_name_full) : undefined,
id: globalThis.Number(assertSet('Camouflage.id', object.id)),
name: I18nString.fromJSON(assertSet('Camouflage.name', object.name)),
tank_name: isSet(object.tank_name)
? I18nString.fromJSON(object.tank_name)
: undefined,
tank_name_full: isSet(object.tank_name_full)
? I18nString.fromJSON(object.tank_name_full)
: undefined,
};
},
@@ -288,37 +340,57 @@ export const Camouflage: MessageFns<Camouflage> = {
create<I extends Exact<DeepPartial<Camouflage>, I>>(base?: I): Camouflage {
return Camouflage.fromPartial(base ?? ({} as any));
},
fromPartial<I extends Exact<DeepPartial<Camouflage>, I>>(object: I): Camouflage {
fromPartial<I extends Exact<DeepPartial<Camouflage>, I>>(
object: I,
): Camouflage {
const message = createBaseCamouflage();
message.id = object.id ?? 0;
message.name = (object.name !== undefined && object.name !== null)
? I18nString.fromPartial(object.name)
: createBaseI18nString();
message.tank_name = (object.tank_name !== undefined && object.tank_name !== null)
? I18nString.fromPartial(object.tank_name)
: undefined;
message.tank_name_full = (object.tank_name_full !== undefined && object.tank_name_full !== null)
? I18nString.fromPartial(object.tank_name_full)
: undefined;
message.name =
object.name !== undefined && object.name !== null
? I18nString.fromPartial(object.name)
: createBaseI18nString();
message.tank_name =
object.tank_name !== undefined && object.tank_name !== null
? I18nString.fromPartial(object.tank_name)
: undefined;
message.tank_name_full =
object.tank_name_full !== undefined && object.tank_name_full !== null
? I18nString.fromPartial(object.tank_name_full)
: undefined;
return message;
},
};
type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined;
type Builtin =
| Date
| Function
| Uint8Array
| string
| number
| boolean
| undefined;
export type DeepPartial<T> = T extends Builtin ? T
: T extends globalThis.Array<infer U> ? globalThis.Array<DeepPartial<U>>
: T extends ReadonlyArray<infer U> ? ReadonlyArray<DeepPartial<U>>
: T extends { $case: string; value: unknown } ? { $case: T["$case"]; value?: DeepPartial<T["value"]> }
: T extends {} ? { [K in keyof T]?: DeepPartial<T[K]> }
: Partial<T>;
export type DeepPartial<T> = T extends Builtin
? T
: T extends globalThis.Array<infer U>
? globalThis.Array<DeepPartial<U>>
: T extends ReadonlyArray<infer U>
? ReadonlyArray<DeepPartial<U>>
: T extends { $case: string; value: unknown }
? { $case: T['$case']; value?: DeepPartial<T['value']> }
: T extends {}
? { [K in keyof T]?: DeepPartial<T[K]> }
: Partial<T>;
type KeysOfUnion<T> = T extends T ? keyof T : never;
export type Exact<P, I extends P> = P extends Builtin ? P
: P & { [K in keyof P]: Exact<P[K], I[K]> } & { [K in Exclude<keyof I, KeysOfUnion<P>>]: never };
export type Exact<P, I extends P> = P extends Builtin
? P
: P & { [K in keyof P]: Exact<P[K], I[K]> } & {
[K in Exclude<keyof I, KeysOfUnion<P>>]: never;
};
function isObject(value: any): boolean {
return typeof value === "object" && value !== null;
return typeof value === 'object' && value !== null;
}
function isSet(value: any): boolean {
@@ -157,6 +157,17 @@ message GunDefinitionBase {
required float dispersion_damaged = 13;
repeated Unlock unlocks = 14;
required uint32 shell_capacity = 15;
optional AssaultRanges assault_ranges = 16;
}
message AssaultRanges {
repeated AssaultRange ranges = 1;
repeated ShellType types = 2;
}
message AssaultRange {
required float distance = 1;
required float factor = 2;
}
message ShellDefinition {
File diff suppressed because it is too large. Load diff
+6 -1
View File
@@ -799,6 +799,7 @@
"fire.caliber": "caliber",
"fire.standardPenetration": "standard penetration",
"fire.premiumPenetration": "premium penetration",
"fire.tertiaryPenetration": "tertiary penetration",
"fire.damage": "damage",
"fire.moduleDamage": "module damage",
"fire.shellVelocity": "shell velocity",
@@ -902,6 +903,10 @@
"tank_seo_description": "%s1 armor, statistics, equipment, provisions, and more. %s2 DPM, %s3hp alpha, %s4mm penetration, %s5kph top speed, %s6hp health.",
"visualizers": {
"assault": {
"title": "Assault shells"
},
"rotator": {
"hull": "Hull",
"turret": "Turret",
@@ -1094,7 +1099,6 @@
"hull_traverse_speed": "Hull traverse speed",
"camouflage": "Camouflage",
"reload": "Reload",
"crewCount": "Crew count",
"shellNormalization": "Normalization",
"shellRicochet": "Ricochet",
@@ -1108,6 +1112,7 @@
"caliber": "Caliber",
"penetration": "Penetration",
"damage": "Damage",
"damageWithoutAssault": "Damage without assault shells",
"clipDamage": "Clipping potential",
"moduleDamage": "Module damage",
"explosionRadius": "Explosion radius",
+5
View File
@@ -698,6 +698,7 @@
"fire.caliber": "calibre",
"fire.standardPenetration": "penetración estándar",
"fire.premiumPenetration": "penetración prémium",
"fire.tertiaryPenetration": "tertiary penetration",
"fire.damage": "daño",
"fire.moduleDamage": "daño al módulo",
"fire.shellVelocity": "velocidad del proyectil",
@@ -786,6 +787,9 @@
"tank_seo_title": "%s1 - Nivel %s2 %s3 %s4 | %s5 Tankopedia | World of Tanks Blitz (WoTB)",
"tank_seo_description": "%s1 blindaje, estadisticas, equipamiento, proviciones y mas.\n%s2 DPM, %3hp alfa, %s4mm penetracion, %s5kph velocidad maxima, %s6 hp salud",
"visualizers": {
"assault": {
"title": "Assault shells"
},
"rotator": {
"hull": "Hull",
"turret": "Turret",
@@ -975,6 +979,7 @@
"caliber": "Calibre",
"penetration": "Penetración",
"damage": "Daño",
"damageWithoutAssault": "Damage without assault shells",
"clipDamage": "Clipping potential",
"moduleDamage": "Daño a módulos",
"explosionRadius": "Radio de Explosión",
+5
View File
@@ -698,6 +698,7 @@
"fire.caliber": "caliber",
"fire.standardPenetration": "standard penetration",
"fire.premiumPenetration": "premium penetration",
"fire.tertiaryPenetration": "tertiary penetration",
"fire.damage": "damage",
"fire.moduleDamage": "module damage",
"fire.shellVelocity": "shell velocity",
@@ -786,6 +787,9 @@
"tank_seo_title": "%s1 - Tier %s2 %s3 %s4 | %s5 Tankopedia | World of Tanks Blitz (WoTB)",
"tank_seo_description": "%s1 armor, statistics, equipment, provisions, and more. %s2 DPM, %s3hp alpha, %s4mm penetration, %s5kph top speed, %s6hp health.",
"visualizers": {
"assault": {
"title": "Assault shells"
},
"rotator": {
"hull": "Hull",
"turret": "Turret",
@@ -975,6 +979,7 @@
"caliber": "Caliber",
"penetration": "Penetration",
"damage": "Dégâts",
"damageWithoutAssault": "Damage without assault shells",
"clipDamage": "Clipping potential",
"moduleDamage": "Module damage",
"explosionRadius": "Explosion radius",
+5
View File
@@ -698,6 +698,7 @@
"fire.caliber": "砲口径",
"fire.standardPenetration": "通常弾貫通力",
"fire.premiumPenetration": "課金弾貫通力",
"fire.tertiaryPenetration": "tertiary penetration",
"fire.damage": "ダメージ",
"fire.moduleDamage": "モジュールダメージ",
"fire.shellVelocity": "弾速",
@@ -786,6 +787,9 @@
"tank_seo_title": "%s1 - Tier %s2 %s3 %s4 | %s5 Tankopedia | World of Tanks Blitz (WoTB)",
"tank_seo_description": "%s1 armor, statistics, equipment, provisions, and more. %s2 DPM, %s3hp alpha, %s4mm penetration, %s5kph top speed, %s6hp health.",
"visualizers": {
"assault": {
"title": "Assault shells"
},
"rotator": {
"hull": "Hull",
"turret": "Turret",
@@ -975,6 +979,7 @@
"caliber": "口径",
"penetration": "貫通力",
"damage": "ダメージ",
"damageWithoutAssault": "Damage without assault shells",
"clipDamage": "弾倉火力",
"moduleDamage": "モジュールダメージ",
"explosionRadius": "爆風半径",
+6 -1
View File
@@ -380,7 +380,7 @@
},
"embeds": {
"name": "osadzenie",
"description": "Jak działa osadzanie"
"description": "Jak działają osadzone"
},
"introduction": {
"name": "wprowadzenie",
@@ -698,6 +698,7 @@
"fire.caliber": "caliber",
"fire.standardPenetration": "standard penetration",
"fire.premiumPenetration": "premium penetration",
"fire.tertiaryPenetration": "tertiary penetration",
"fire.damage": "damage",
"fire.moduleDamage": "module damage",
"fire.shellVelocity": "shell velocity",
@@ -786,6 +787,9 @@
"tank_seo_title": "%s1 - Tier %s2 %s3 %s4 | %s5 Tankopedia | World of Tanks Blitz (WoTB)",
"tank_seo_description": "%s1 armor, statistics, equipment, provisions, and more. %s2 DPM, %s3hp alpha, %s4mm penetration, %s5kph top speed, %s6hp health.",
"visualizers": {
"assault": {
"title": "Assault shells"
},
"rotator": {
"hull": "Hull",
"turret": "Turret",
@@ -975,6 +979,7 @@
"caliber": "Caliber",
"penetration": "Penetration",
"damage": "Uszkodzenia",
"damageWithoutAssault": "Damage without assault shells",
"clipDamage": "Clipping potential",
"moduleDamage": "Module damage",
"explosionRadius": "Explosion radius",
+5
View File
@@ -698,6 +698,7 @@
"fire.caliber": "calibre",
"fire.standardPenetration": "penetração padrão",
"fire.premiumPenetration": "penetração premium",
"fire.tertiaryPenetration": "tertiary penetration",
"fire.damage": "dano",
"fire.moduleDamage": "dano a módulos",
"fire.shellVelocity": "velocidade do projétil",
@@ -786,6 +787,9 @@
"tank_seo_title": "%s1 - Nível %s2 %s3 %s4 | %s5 Tanquepedia | World of Tanks Blitz (WoTB)",
"tank_seo_description": "%s1 blindagem, estatísticas, equipamentos, provisões e mais. %s2 DPM, %s3hp alfa, %s4mm de penetração, %s5km/h velocidade máxima, %s6hp vida.",
"visualizers": {
"assault": {
"title": "Assault shells"
},
"rotator": {
"hull": "Hull",
"turret": "Turret",
@@ -975,6 +979,7 @@
"caliber": "Calibre",
"penetration": "Penetração",
"damage": "Dano",
"damageWithoutAssault": "Damage without assault shells",
"clipDamage": "Potencial de clipe",
"moduleDamage": "Dano a módulos",
"explosionRadius": "Raio de Explosão",
+6 -1
View File
@@ -697,7 +697,8 @@
"fire.reload": "перезарядка",
"fire.caliber": "калибр",
"fire.standardPenetration": "базовое пробитие",
"fire.premiumPenetration": "улучшенное пробитие ",
"fire.premiumPenetration": "премиумное пробитие ",
"fire.tertiaryPenetration": "пробитие третьего снаряда",
"fire.damage": "урон",
"fire.moduleDamage": "урон по модулям",
"fire.shellVelocity": "скорость снаряда",
@@ -786,6 +787,9 @@
"tank_seo_title": "%s1 - %s4 %s3 %s2 уровня | Танкопедия %s5 | World of Tanks Blitz (WoTB)",
"tank_seo_description": "%s1 броня, статистика, оборудование, снабжение и многое другое. %s2 УВМ, %s3hp альфа, %s4mm пробитие, %s5км/ч лучшая скорость, %s6hp здоровье.",
"visualizers": {
"assault": {
"title": "Assault shells"
},
"rotator": {
"hull": "Корпус",
"turret": "Башня",
@@ -975,6 +979,7 @@
"caliber": "Калибр",
"penetration": "Пробитие",
"damage": "Урон",
"damageWithoutAssault": "Damage without assault shells",
"clipDamage": "Урон с барабана",
"moduleDamage": "Урон по модулям",
"explosionRadius": "Разлет осколков",
+23 -18
View File
@@ -2,7 +2,7 @@
"test": {
"default": "тест",
"long": "This is a long test string. You MUST NEVER see this in the UI. My favorite fruit is %s1 because it has the color of %s2. I like it so much that I will say its name 3 times! %s1! %s1! %s1! Once again, it's %s2, not %s3.",
"fox": "The quick brown fox jumps over the lazy dog."
"fox": "Швидка бура лисиця перестрибує через лінивого собаку."
},
"common": {
"units": {
@@ -12,7 +12,7 @@
"hp": "%s1хп",
"x": "х%s1",
"percentage": "%s1%",
"deg_s": "%s1°/s"
"deg_s": "%s1°/с"
},
"gun_types": {
"auto_loader": "Магазинна система заряджання",
@@ -413,10 +413,10 @@
}
},
"debug": {
"name": "debug",
"description": "Debug information about the bot",
"name": "налагодження",
"description": "Інформація про налагодження бота",
"body": {
"title": "# Debug\n\n",
"title": "# Налагодження\n\n",
"version": "Версія",
"shard": {
"label": "Уламок",
@@ -499,7 +499,7 @@
},
"embed": {
"title": "Невірний обліковий запис?",
"description": "Use the command again and select a username from the search results."
"description": "Використайте команду ще раз і виберіть ім'я користувача з результатів пошуку."
}
},
"search_clans": {
@@ -508,7 +508,7 @@
"options": {
"limit": {
"name": "ліміт",
"description": "The max number of clan results (default: %s1)"
"description": "Максимальна кількість результатів клану (за замовчуванням: %s1)"
}
},
"body": {
@@ -531,16 +531,16 @@
}
},
"rating_leaderboard": {
"name": "rating-leaderboard",
"description": "Rating battles statistics",
"name": "таблиця рейтингу",
"description": "Статистика рейтингових боїв",
"subcommands": {
"neighbors": {
"name": "neighbors",
"description": "Neighboring players on the leaderboard"
},
"league": {
"name": "league",
"description": "Top players of a league"
"name": "ліга",
"description": "Найкращий гравець ліги"
}
},
"options": {
@@ -549,8 +549,8 @@
"description": ""
},
"season": {
"name": "season",
"description": "Season number (default: current)",
"name": "сезон",
"description": "Базова інформація про гравця",
"choices": {
"current": "%s1 (current)"
}
@@ -578,14 +578,14 @@
"description": "Коротка статистика за період"
},
"owned_tanks": {
"name": "owned-tanks",
"description": "Shows all own tanks in a tier",
"name": "отримані танки",
"description": "Показує всі ваші танки даного рівня",
"body": {
"title": "# %s1's owned tier %s2 tanks\n\n"
}
},
"permissions": {
"name": "permissions",
"name": "дозволи",
"description": "Checks wether the bot has the required permissions to function",
"body": {
"title": "# Permissions\n\n%s1",
@@ -615,7 +615,7 @@
},
"player_info": {
"name": "player-info",
"description": "Basic information about a player",
"description": "Базова інформація про гравця",
"body": {
"title": "%s1's information",
"nickname": "Нік",
@@ -635,7 +635,7 @@
"description": "Today's rating statistics"
},
"season": {
"name": "season",
"name": "сезон",
"description": "This season's rating statistics"
}
},
@@ -698,6 +698,7 @@
"fire.caliber": "калібр",
"fire.standardPenetration": "базове пробиття",
"fire.premiumPenetration": "преміум пробиття",
"fire.tertiaryPenetration": "tertiary penetration",
"fire.damage": "шкода",
"fire.moduleDamage": "шкода по модулях",
"fire.shellVelocity": "швидкість польоту снаряда",
@@ -786,6 +787,9 @@
"tank_seo_title": "%s1 - Рівень %s2 %s3 %s4 | %s5 Танкопедія | WoT Blitz",
"tank_seo_description": "%s1 броня, характеристики, обладнання, амуніція та більше. %s2 шкода на хвилину, %s3 разова шкода, %s4 мм пробиття, %s5 км/год максимальна швидкість, %s6 міцність.",
"visualizers": {
"assault": {
"title": "Assault shells"
},
"rotator": {
"hull": "Hull",
"turret": "Turret",
@@ -975,6 +979,7 @@
"caliber": "Калібр",
"penetration": "Бронепробиття",
"damage": "Шкода",
"damageWithoutAssault": "Damage without assault shells",
"clipDamage": "Clipping potential",
"moduleDamage": "Шкода по модулях",
"explosionRadius": "Радіус розльоту уламків",
+22 -17
View File
@@ -41,15 +41,15 @@
"preview": "预览"
},
"locales": {
"en": "英文",
"es": "西班牙文",
"pt": "葡萄牙文",
"ru": "俄文",
"uk": "乌克兰文",
"en": "英语",
"es": "西班牙语",
"pt": "葡萄牙语",
"ru": "俄语",
"uk": "乌克兰语",
"zh": "中文",
"ja": "日文",
"fr": "法文",
"pl": "波兰文"
"ja": "日语",
"fr": "法语",
"pl": "波兰语"
},
"wn8_percentile": {
"0": "非常差",
@@ -283,7 +283,7 @@
"name": "阶级",
"description": "阶级",
"choices": {
"tier": "%s1- %s2 级"
"tier": "%s1阶 - %s2"
}
}
},
@@ -494,7 +494,7 @@
"accounts_linked": "帐号已绑定",
"no_role_permissions": {
"title": "%s1 验证失败",
"description": "我没有管理角色的权限。"
"description": "我没有管理身份的权限。"
}
},
"embed": {
@@ -517,7 +517,7 @@
}
},
"search_players": {
"name": "搜索玩家",
"name": "搜索-玩家",
"description": "搜索玩家",
"options": {
"limit": {
@@ -578,7 +578,7 @@
"description": "一段时间内的快速统计"
},
"owned_tanks": {
"name": "已擁有的戰車",
"name": "已拥有的战车",
"description": "显示该阶级中所有您已拥有的战车",
"body": {
"title": "# %s1 所拥有的 %s2 阶战车\n\n"
@@ -589,8 +589,8 @@
"description": "检查机器人是否拥有运行所需的权限",
"body": {
"title": "# 权限\n\n%s1",
"view_channels": "查看頻道:需要找到重新整理按鈕使用的訊息",
"read_message_history": "讀取歷史訊息: 需要找到訊息以重新整理",
"view_channels": "查看频道:需要找到重新整理按钮使用的讯息",
"read_message_history": "读取消息历史: 需要找到消息以刷新",
"attach_files": "附加文件:需要将图像添加到基于图像的命令",
"external_emojis": "外部表情符号:需要添加国家旗帜和战车类别"
}
@@ -698,6 +698,7 @@
"fire.caliber": "口径",
"fire.standardPenetration": "标准炮弹穿透力",
"fire.premiumPenetration": "加值炮弹穿透力",
"fire.tertiaryPenetration": "tertiary penetration",
"fire.damage": "炮弹伤害值",
"fire.moduleDamage": "模组伤害值",
"fire.shellVelocity": "炮弹飞行速度",
@@ -786,6 +787,9 @@
"tank_seo_title": "%s1 - %s2 阶 %s3 %s4 | %s5 战车百科 | 战车世界闪击战 (WoTB)",
"tank_seo_description": "%s1 装甲,统计设备,装备,供给等。 %s2 每分钟伤害, %s3生命值, %s4毫米穿透, %s5kph 最高速度, %s6hp 生命值。",
"visualizers": {
"assault": {
"title": "Assault shells"
},
"rotator": {
"hull": "车体",
"turret": "炮塔",
@@ -796,9 +800,9 @@
},
"ricochet": {
"angle": "角度",
"normalization": "轉正",
"normalization": "转正",
"effective_angle": "有效角度",
"armor": "裝甲",
"armor": "装甲",
"no_ricochet": "高爆弹不会跳弹"
},
"view_range": {
@@ -854,7 +858,7 @@
"track": "履带"
},
"status": {
"penetration": "%s1生命值 擊穿",
"penetration": "%s1生命值 击穿",
"blocked": "抵挡伤害",
"ricochet": "跳弹",
"splash": "%s1生命值 溅射"
@@ -975,6 +979,7 @@
"caliber": "口径",
"penetration": "穿深",
"damage": "伤害",
"damageWithoutAssault": "Damage without assault shells",
"clipDamage": "弹匣伤害期望值",
"moduleDamage": "模组伤害",
"explosionRadius": "溅射半径",
@@ -7,7 +7,6 @@ import { FileChange } from '../core/github/commitMultipleFiles';
export async function blitzkitTankIcons() {
console.log('Building blitzkit tank icons...');
console.log('Starting website dev server...');
const browser = await launch();
const page = await browser.newPage();
const tanks = Object.values((await fetchTankDefinitions()).tanks);
@@ -19,12 +18,14 @@ export async function blitzkitTankIcons() {
width: BLITZKIT_TANK_ICON_SIZE.width - 1,
height: BLITZKIT_TANK_ICON_SIZE.height,
});
await page.goto(`http://localhost:4321/api/tankopedia/tank-icon/${id}/`, {
waitUntil: 'networkidle0',
});
if (index === 0) {
console.log('Dev server started!');
try {
await page.goto(`http://localhost:4321/api/tankopedia/tank-icon/${id}/`, {
waitUntil: 'networkidle0',
});
} catch (error) {
console.error(`${id} ${name} time out`, error);
continue;
}
const screenshot = await page.screenshot({
File diff suppressed because it is too large. Load diff
+1 -2
View File
@@ -21,7 +21,6 @@ import { gameModeBanners } from './gameModeBanners';
import { glossary } from './glossary';
import { migration } from './migration';
import { moduleIcons } from './moduleIcons';
import { popularTanks } from './popularTanks';
import { shellIcons } from './shellIcons';
import { skillIcons } from './skillIcons';
import { tankArmors } from './tankArmors';
@@ -47,7 +46,7 @@ const methods = [
boosterIcons,
tankIcons,
blitzkitTankIcons,
popularTanks,
// popularTanks,
glossary,
];
@@ -32,9 +32,6 @@ export async function tankModels() {
if (tankKey.includes('tutorial_bot')) continue;
const id = toUniqueId(nation, tank.id);
if (id !== 21777) continue;
const parameters = await readYAMLDVPL<TankParameters>(
`${DATA}/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`,
);
@@ -43,8 +40,6 @@ export async function tankModels() {
parameters.resourcesPath.blitzModelPath.replace(/\.sc2$/, ''),
);
await writeFile('../../temp/model.glb', await nodeIO.writeBinary(model));
changes.push({
path: `3d/tanks/models/${id}.glb`,
content: Buffer.from(await nodeIO.writeBinary(model)),
@@ -73,12 +73,13 @@ export async function commitMultipleFiles(
}
if (diff.status === DiffStatus.Unchanged) {
console.log(`🔵 (${diff.change.toLocaleString()}B) ${change.path}`);
// just don't bother
// console.log(`🔵 (${diff.change.toLocaleString()}B) ${change.path}`);
} else {
console.log(
`${
diff.status === DiffStatus.New ? '🟢' : '🟡'
} (+${diff.change.toLocaleString()}B) ${change.path}`,
} (${diff.change > 0 ? '+' : ''}${diff.change.toLocaleString()}B) ${change.path}`,
);
changes.push(change);
}
+1 -2
View File
@@ -1,6 +1,5 @@
// @ts-check
import partytown from '@astrojs/partytown';
import react from '@astrojs/react';
import sitemap from '@astrojs/sitemap';
import { SUPPORTED_LOCALES } from '@blitzkit/i18n';
@@ -21,7 +20,7 @@ export default defineConfig({
outDir: '../../dist/website',
prefetch: true,
integrations: [react(), sitemap(), partytown()],
integrations: [react(), sitemap()],
vite: {
resolve: {
alias: {
@@ -1,3 +1,5 @@
precision mediump float;
#define USE_FOG
#include <fog_pars_fragment>
@@ -1,3 +1,5 @@
precision mediump float;
#define USE_FOG
#include <fog_pars_vertex>
@@ -1,3 +1,5 @@
precision mediump float;
uniform float thickness;
uniform float penetration;
@@ -1,3 +1,5 @@
precision mediump float;
#include <clipping_planes_pars_vertex>
void main() {
@@ -1,3 +1,5 @@
precision mediump float;
varying vec3 vNormal;
varying vec3 vViewPosition;
@@ -1,3 +1,5 @@
precision mediump float;
varying vec3 vNormal;
varying vec3 vViewPosition;
@@ -79,6 +79,12 @@ export function Body({ stats }: BodyProps) {
<Table.Body>
<CompareRow stats={stats} value="dpm" decimals={0} />
<CompareRow stats={stats} value="damage" decimals={0} />
{hasNonRegularGun && (
<CompareRow stats={stats} indent value="clipDamage" decimals={0} />
)}
<CompareRow stats={stats} value="moduleDamage" decimals={0} />
<CompareRow stats={stats} value="penetration" decimals={0} />
<CompareRow
stats={stats}
name={strings.website.tools.tankopedia.characteristics.values.reload}
@@ -105,12 +111,6 @@ export function Body({ stats }: BodyProps) {
<CompareRow stats={stats} value="shells" decimals={0} />
</>
)}
<CompareRow stats={stats} value="penetration" decimals={0} />
<CompareRow stats={stats} value="damage" decimals={0} />
{hasNonRegularGun && (
<CompareRow stats={stats} indent value="clipDamage" decimals={0} />
)}
<CompareRow stats={stats} value="moduleDamage" decimals={0} />
<CompareRow stats={stats} value="caliber" decimals={0} />
<CompareRow stats={stats} value="shellNormalization" decimals={0} />
<CompareRow
+3 -3
View File
@@ -127,8 +127,8 @@ const strings = getStrings(locale);
</Flex>
</Flex>
<Flex gap="5" display={{ initial: 'flex', sm: 'none' }}>
<Flex direction="column" justify="between" align="end">
<Flex gap="5" display={{ initial: 'flex', sm: 'none' }} width="100%">
<Flex direction="column" gap="1" flexGrow="1" flexBasis="0" align="end">
<Text size="2" color="gray">BlitzKit</Text>
{
PUBLIC_ASSET_BRANCH !== 'preview' && (
@@ -140,7 +140,7 @@ const strings = getStrings(locale);
<Text size="2" color="gray">CDN</Text>
</Flex>
<Flex direction="column" justify="between" align="start">
<Flex direction="column" gap="1" flexGrow="1" flexBasis="0" align="start">
<Code size="2" color="gray">
{packageJson.version}
</Code>
+4 -3
View File
@@ -17,7 +17,7 @@ export interface Props {
integrated?: boolean;
}
const { integrated } = Astro.props;
const { integrated = false } = Astro.props;
const toolsFiltered = TOOLS.filter((tool) => !('href' in tool));
const { locale } = Astro.params;
const branchName = resolveBranchName(locale);
@@ -47,7 +47,7 @@ const strings = getStrings(locale);
height: 100%;
}
.navbar[data-expanded='false'] .content {
.navbar[data-expanded='false'][data-integrated='false'] .content {
background-color: var(--black-a8);
backdrop-filter: blur(4rem);
box-shadow: var(--shadow-3);
@@ -87,6 +87,7 @@ const strings = getStrings(locale);
direction="column"
align="center"
data-expanded="false"
data-integrated={`${integrated}`}
className="navbar"
width="100%"
top="0"
@@ -232,7 +233,7 @@ const strings = getStrings(locale);
<Box
style={{
borderRadius: Var('radius-2'),
backgroundImage: `url(/assets/images/${tool.id}.webp)`,
backgroundImage: `url(/assets/images/tools/${tool.id}.webp)`,
backgroundSize: '200%',
backgroundPosition: 'right center',
}}
+4 -26
View File
@@ -6,42 +6,20 @@ const PUBLIC_GOOGLE_TAG_MANAGER_ID = assertSecret(
);
---
{/* google analytics */}
<script
is:inline
async
type="text/partytown"
src={`https://www.googletagmanager.com/gtag/js?id=${PUBLIC_GOOGLE_TAG_MANAGER_ID}`}
></script>
<script
is:inline
define:vars={{ PUBLIC_GOOGLE_TAG_MANAGER_ID }}
type="text/partytown"
>
<script is:inline define:vars={{ PUBLIC_GOOGLE_TAG_MANAGER_ID }}>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', PUBLIC_GOOGLE_TAG_MANAGER_ID);
</script>
{/* nitro ads */}
{
/*
<script is:inline data-cfasync="false">
window.nitroAds = window.nitroAds || {
createAd: function () {
return new Promise((e) => {
window.nitroAds.queue.push(['createAd', arguments, e]);
});
},
addUserToken: function () {
window.nitroAds.queue.push(['addUserToken', arguments]);
},
queue: [],
};
</script>
*/
}
@@ -80,7 +80,7 @@ export const TankCard = forwardRef<HTMLSpanElement, TankCardProps>(
>
<img
alt={unwrap(tank.name)}
src={asset(`icons/tanks/blitzkit/${tank.id}.webp`)}
src={asset(`icons/tanks/big/${tank.id}.webp`)}
className="image"
draggable={false}
/>
@@ -27,6 +27,7 @@ export function TankSearchCard({ tank, onSelect }: TankSearchCardProps) {
const gun = turret.guns.at(-1)!;
const shell0 = gun.gun_type!.value.base.shells[0];
const shell1 = gun.gun_type!.value.base.shells[1];
const shell2 = gun.gun_type!.value.base.shells[2];
const tracks = tank.tracks.at(-1)!;
const engine = tank.engines.at(-1)!;
const tankModelDefinition = modelDefinitions.models[tank.id];
@@ -57,6 +58,8 @@ export function TankSearchCard({ tank, onSelect }: TankSearchCardProps) {
return shell0.penetration.near.toFixed(0);
case 'fire.premiumPenetration':
return shell1 ? shell1.penetration.near.toFixed(0) : '--';
case 'fire.tertiaryPenetration':
return shell1 ? shell2.penetration.near.toFixed(0) : '--';
case 'fire.shellVelocity':
return shell0.velocity.toFixed(0);
case 'fire.shellCapacity':
@@ -84,6 +84,7 @@ function ShellFilter({ index, premium }: { index: number; premium?: boolean }) {
return (
<IconButton
key={shellType}
value={`${shellType}`}
radius="none"
variant={selected ? 'solid' : 'soft'}
@@ -21,27 +21,17 @@ export function SearchBar({ topResult, skeleton, onSelect }: SearchBarProps) {
const input = useRef<HTMLInputElement>(null);
const performSearch = useCallback(
debounce(() => {
// tankFiltersStore.setState({ searching: false });
$tankFilters.setKey('searching', false);
if (!input.current) return;
const sanitized = input.current.value.trim();
// tankFiltersStore.setState({
// search: sanitized.length === 0 ? undefined : sanitized,
// });
$tankFilters.setKey(
'search',
sanitized.length === 0 ? undefined : sanitized,
);
$tankFilters.setKey('search', sanitized.length === 0 ? null : sanitized);
}, 500),
[],
);
const handleChange = useCallback(() => {
// if (!tankFiltersStore.getState().searching) {
// tankFiltersStore.setState({ searching: true });
// }
if (!tankFilters.searching) {
$tankFilters.setKey('searching', true);
}
@@ -69,7 +59,7 @@ export function SearchBar({ topResult, skeleton, onSelect }: SearchBarProps) {
<TextField.Root
variant="classic"
disabled={skeleton}
defaultValue={tankFilters.search}
defaultValue={tankFilters.search ?? undefined}
style={{ flex: 1 }}
ref={input}
placeholder={strings.website.common.tank_search.search_bar_hint}
@@ -63,6 +63,7 @@ export function Sort() {
<Item by="fire.caliber" />
<Item by="fire.standardPenetration" />
<Item by="fire.premiumPenetration" />
<Item by="fire.tertiaryPenetration" />
<Item by="fire.damage" />
<Item by="fire.moduleDamage" />
<Item by="fire.shellVelocity" />
@@ -60,7 +60,7 @@ export const TankSearch = memo<TankSearchProps>(
const tankFilters = useStore($tankFilters);
const tankopediaSort = useStore($tankopediaSort);
const tanksFiltered = useMemo(() => {
if (tankFilters.search === undefined) {
if (tankFilters.search === null) {
const filtered = awaitedTanksDefinitionsArray.filter((tank) =>
filterTank(tankFilters, tank),
);
@@ -238,6 +238,16 @@ export const TankSearch = memo<TankSearchProps>(
);
break;
case 'fire.tertiaryPenetration':
sorted = filtered.sort(
(a, b) =>
(a.turrets.at(-1)!.guns.at(-1)!.gun_type!.value.base.shells[2]
?.penetration.near ?? 0) -
(b.turrets.at(-1)!.guns.at(-1)!.gun_type!.value.base.shells[2]
?.penetration.near ?? 0),
);
break;
case 'fire.damage':
sorted = filtered.sort(
(a, b) =>
@@ -0,0 +1,7 @@
#assault-cursor {
cursor: grab;
}
#assault-cursor:active {
cursor: grabbing;
}
@@ -0,0 +1,123 @@
import { DragHandleDots2Icon } from '@radix-ui/react-icons';
import { Box, Flex, Text } from '@radix-ui/themes';
import { useEffect, useRef, useState } from 'react';
import { clamp } from 'three/src/math/MathUtils.js';
import {
fakeLogSquishyHigh,
fakeLogSquishyHighInverse,
} from '../../../../../../../../../core/blitzkit/fakeLog';
import { Var } from '../../../../../../../../../core/radix/var';
import { Duel } from '../../../../../../../../../stores/duel';
import './index.css';
interface Props {
maxDistance: number;
maxFakeDistance: number;
}
export function AssaultCursor({ maxDistance, maxFakeDistance }: Props) {
const assaultDistance = Duel.use(
(state) => state.protagonist.assaultDistance,
);
const [x, setX] = useState(fakeLogSquishyHigh(assaultDistance / maxDistance));
const cursor = useRef<HTMLDivElement>(null);
const mutateDuel = Duel.useMutation();
useEffect(() => {
let localX = x;
let lastX = 0;
function handlePointerDown(event: PointerEvent) {
event.preventDefault();
lastX = event.clientX;
window.addEventListener('pointermove', handlePointerMove);
window.addEventListener('pointerup', handlePointerUp);
}
function handlePointerMove(event: PointerEvent) {
event.preventDefault();
const parent = cursor.current?.parentElement;
if (!parent) return;
const width = parent.clientWidth;
const movementX = event.clientX - lastX;
lastX = event.clientX;
setX((state) => {
localX = clamp(state + movementX / width, 0, 1);
return localX;
});
}
function handlePointerUp() {
mutateDuel((draft) => {
draft.protagonist.assaultDistance =
fakeLogSquishyHighInverse(localX * maxFakeDistance) * maxDistance;
});
window.removeEventListener('pointermove', handlePointerMove);
window.removeEventListener('pointerup', handlePointerUp);
}
cursor.current?.addEventListener('pointerdown', handlePointerDown);
return () => {
cursor.current?.removeEventListener('pointerdown', handlePointerDown);
window.removeEventListener('pointermove', handlePointerMove);
window.removeEventListener('pointerup', handlePointerUp);
};
}, []);
return (
<Box
ref={cursor}
position="absolute"
top="0"
left={`${x * 100}%`}
height="100%"
width="2pt"
id="assault-cursor"
style={{
touchAction: 'none',
transform: 'translateX(-50%)',
backgroundColor: Var('accent-11'),
}}
>
<Flex
align="center"
direction="column"
position="absolute"
left="50%"
bottom="100%"
style={{ transform: 'translateX(-50%)' }}
>
<Flex
align="center"
justify="center"
style={{
width: '1.5rem',
height: '1.5rem',
backgroundColor: Var('accent-9'),
borderRadius: `50% 50% ${Var('radius-1')} ${Var('radius-1')}`,
}}
>
<Text style={{ display: 'flex' }} highContrast color="purple">
<DragHandleDots2Icon />
</Text>
</Flex>
<Box
height="0.5rem"
width="2pt"
style={{
backgroundColor: Var('accent-11'),
}}
/>
</Flex>
</Box>
);
}
@@ -0,0 +1,157 @@
import type { AssaultRanges } from '@blitzkit/core';
import { literals } from '@blitzkit/i18n';
import { Box, Card, Flex, Text } from '@radix-ui/themes';
import { clamp } from 'three/src/math/MathUtils.js';
import {
fakeLogSquishyHigh,
fakeLogSquishyLow,
} from '../../../../../../../core/blitzkit/fakeLog';
import type { TankCharacteristics } from '../../../../../../../core/blitzkit/tankCharacteristics';
import { Var } from '../../../../../../../core/radix/var';
import { useLocale } from '../../../../../../../hooks/useLocale';
import { AssaultCursor } from './components/Cursor';
interface Props {
ranges: AssaultRanges;
stats: TankCharacteristics;
}
export function AssaultRangesVisualizer({ ranges: _ranges, stats }: Props) {
const maxDistance = _ranges.ranges.at(-1)!.distance;
const ranges = _ranges.ranges.map((range) => ({
factor: range.factor,
distance: range.distance,
fakeDistance: fakeLogSquishyHigh(range.distance / maxDistance),
}));
const maxFakeDistance = ranges.at(-1)!.fakeDistance;
const maxFactor = ranges[0].factor;
const { strings } = useLocale();
return (
<Card mb="6" variant="classic" style={{ aspectRatio: '1 / 1' }}>
<Flex
position="absolute"
top="0"
left="0"
width="100%"
height="100%"
direction="column"
>
<Text m="3" size="2" color="gray">
{strings.website.tools.tankopedia.visualizers.assault.title}
</Text>
<Flex
direction="column"
p="3"
pt="8"
pr="5"
style={{ backgroundColor: Var('gray-1') }}
flexGrow="1"
>
<Flex flexGrow="1">
<Box position="relative" width={Var('space-8')}>
{ranges.map((range, index) => (
<Text
key={index}
style={{
position: 'absolute',
left: 0,
bottom: `${fakeLogSquishyLow(range.factor / maxFactor) * 100}%`,
transform: 'translateY(50%)',
}}
color="gray"
size="1"
wrap="nowrap"
>
{literals(strings.common.units.hp, [
`${Math.round(stats.damageWithoutAssault * range.factor)}`,
])}
</Text>
))}
</Box>
<Flex flexGrow="1" align="end" position="relative">
{ranges.map((range, index) => {
const mix =
clamp(2 * (range.factor / maxFactor - 1) + 1, 0, 1) * 100;
const color0 = `color-mix(in hsl, ${Var('tomato-9')}, ${Var('jade-9')} ${mix}%)`;
const color1 = `color-mix(in hsl, ${Var('tomato-7')}, ${Var('jade-7')} ${mix}%)`;
const last = ranges[index - 1];
const lastFakeDistance = last?.fakeDistance ?? 0;
const fakeWidth = range.fakeDistance - lastFakeDistance;
const fraction = fakeWidth / ranges.at(-1)!.fakeDistance;
return (
<Box
width={`${fraction * 100}%`}
height={`${fakeLogSquishyLow(range.factor / maxFactor) * 100}%`}
style={{
background: `linear-gradient(45deg, ${color1}, ${color0})`,
}}
key={index}
/>
);
})}
{ranges.map((range, index) => (
<Box
key={index}
position="absolute"
left="0"
bottom={`${fakeLogSquishyLow(range.factor / maxFactor) * 100}%`}
width="100%"
height="1pt"
style={{
transform: 'translateY(50%)',
backgroundColor: Var('gray-11'),
}}
/>
))}
<AssaultCursor
maxDistance={maxDistance}
maxFakeDistance={maxFakeDistance}
/>
</Flex>
</Flex>
<Flex ml="8" height={Var('space-7')}>
{ranges.map((range, index) => {
const last = ranges[index - 1];
const lastFakeDistance = last?.fakeDistance ?? 0;
const fakeWidth = range.fakeDistance - lastFakeDistance;
const fraction = fakeWidth / ranges.at(-1)!.fakeDistance;
return (
<Box
position="relative"
key={index}
width={`${fraction * 100}%`}
height="100%"
>
<Text
size="1"
color="gray"
mt="2"
style={{
position: 'absolute',
right: 0,
transform: 'translateX(50%)',
writingMode: 'vertical-rl',
}}
>
{literals(strings.common.units.m, [
`${Math.round(range.distance)}`,
])}
</Text>
</Box>
);
})}
</Flex>
</Flex>
</Flex>
</Card>
);
}
@@ -21,6 +21,7 @@ import { useEquipment } from '../../../../../../hooks/useEquipment';
import { useLocale } from '../../../../../../hooks/useLocale';
import { useTankModelDefinition } from '../../../../../../hooks/useTankModelDefinition';
import { Duel } from '../../../../../../stores/duel';
import { AssaultRangesVisualizer } from './AssaultRangesVisualizer';
import { Info } from './Info';
import { InfoWithDelta } from './InfoWithDelta';
import { RicochetVisualizer } from './RicochetVisualizer';
@@ -112,6 +113,20 @@ export function Firepower({ stats }: StatsAcceptorProps) {
{strings.common.gun_types[gun.gun_type!.$case]}
</Info>
<InfoWithDelta stats={stats} decimals={0} value="dpm" />
<InfoWithDelta stats={stats} decimals={0} value="damage" />
{gun.gun_type!.$case !== 'regular' && (
<InfoWithDelta stats={stats} indent decimals={0} value="clipDamage" />
)}
<InfoWithDelta stats={stats} decimals={0} value="moduleDamage" />
<InfoWithDelta stats={stats} decimals={0} value="penetration" />
{gun.gun_type!.value.base.assault_ranges?.types.includes(shell.type) && (
<AssaultRangesVisualizer
stats={stats}
ranges={gun.gun_type!.value.base.assault_ranges}
/>
)}
{gun.gun_type!.$case === 'auto_reloader' && (
<InfoWithDelta stats={stats} decimals={0} indent value="dpmEffective" />
)}
@@ -172,7 +187,6 @@ export function Firepower({ stats }: StatsAcceptorProps) {
)}
{/* <ReloadVisualizer stats={stats} /> */}
<InfoWithDelta stats={stats} decimals={0} value="penetration" />
{typeof shell.penetration !== 'number' && (
<>
<InfoWithDelta
@@ -240,11 +254,6 @@ export function Firepower({ stats }: StatsAcceptorProps) {
</Flex>
</>
)}
<InfoWithDelta stats={stats} decimals={0} value="damage" />
{gun.gun_type!.$case !== 'regular' && (
<InfoWithDelta stats={stats} indent decimals={0} value="clipDamage" />
)}
<InfoWithDelta stats={stats} decimals={0} value="moduleDamage" />
{isExplosive(shell.type) && (
<InfoWithDelta
stats={stats}
@@ -65,6 +65,14 @@ export function InfoWithDelta({
: (stats[props.value] as number);
const delta = useDelta(uhWhatDoICallThisVariable);
const protagonistTank = Duel.use((state) => state.protagonist.tank);
const shellIndex = Duel.use((state) =>
state.protagonist.gun.gun_type!.value.base.shells.indexOf(
state.protagonist.shell,
),
);
const equipmentMatrix = Duel.use(
(state) => state.protagonist.equipmentMatrix,
);
const others = useMemo(() => {
const defaultSkills = createDefaultSkills(skillDefinitions);
@@ -86,6 +94,10 @@ export function InfoWithDelta({
provisionDefinitions,
);
if (member.gun.gun_type!.value.base.shells[shellIndex] === undefined) {
return undefined;
}
return tankCharacteristics(
{
applyDynamicArmor: false,
@@ -97,8 +109,8 @@ export function InfoWithDelta({
provisions: member.provisions,
engine: member.engine,
gun: member.gun,
equipmentMatrix: member.equipmentMatrix,
shell: member.shell,
equipmentMatrix: equipmentMatrix,
shell: member.gun.gun_type!.value.base.shells[shellIndex],
stockEngine: tank.engines[0],
stockGun: tank.turrets[0].guns[0],
stockTrack: tank.tracks[0],
@@ -106,6 +118,7 @@ export function InfoWithDelta({
tank,
track: member.track,
turret: member.turret,
assaultDistance: member.assaultDistance,
},
{
equipmentDefinitions,
@@ -115,14 +128,16 @@ export function InfoWithDelta({
);
})
.filter((tank) => {
if (tank === undefined) return false;
const othersValue =
typeof props.value === 'function'
? props.value(tank)!
: (tank[props.value] as number);
return othersValue !== undefined;
});
}, [relativeAgainst]);
}) as TankCharacteristics[];
}, [relativeAgainst, shellIndex, equipmentMatrix]);
const betterTanks = others.filter((tank) => {
const othersValue =
typeof props.value === 'function'
@@ -24,6 +24,9 @@ export function Characteristics() {
const provisions = Duel.use((state) => state.protagonist.provisions);
const consumables = Duel.use((state) => state.protagonist.consumables);
const camouflage = Duel.use((state) => state.protagonist.camouflage);
const assaultDistance = Duel.use(
(state) => state.protagonist.assaultDistance,
);
const equipmentMatrix = Duel.use(
(state) => state.protagonist.equipmentMatrix,
);
@@ -58,6 +61,7 @@ export function Characteristics() {
applyReactiveArmor: false,
applyDynamicArmor: false,
applySpallLiner: false,
assaultDistance,
},
{
tankModelDefinition,
@@ -0,0 +1,25 @@
import { Box, Progress } from '@radix-ui/themes';
import { useProgress } from '@react-three/drei';
interface Props {
fontSize?: string;
}
export function Tracker({ fontSize }: Props) {
const { loaded, total } = useProgress();
const progress = total === 0 ? 0 : (loaded / total) * 100;
return (
<Box
position="absolute"
left="50%"
bottom="0"
width="min(100%, 16rem)"
style={{
transform: `translate(-50%, calc(${fontSize} / 2))`,
}}
>
<Progress color="gray" value={progress} />
</Box>
);
}
@@ -1,13 +1,14 @@
import { TankType } from '@blitzkit/core';
import { Box, Heading } from '@radix-ui/themes';
import { Var } from '../../../../../core/radix/var';
import { useLocale } from '../../../../../hooks/useLocale';
import { Duel } from '../../../../../stores/duel';
import type { RadixColor } from '../../../../../stores/embedState';
import { TankopediaEphemeral } from '../../../../../stores/tankopediaEphemeral';
import { Var } from '../../../../../../core/radix/var';
import { useLocale } from '../../../../../../hooks/useLocale';
import { Duel } from '../../../../../../stores/duel';
import type { RadixColor } from '../../../../../../stores/embedState';
import { TankopediaEphemeral } from '../../../../../../stores/tankopediaEphemeral';
import { Tracker } from './components/Tracker';
interface TitleProps {
outline?: boolean;
foreground?: boolean;
}
export const NATION_COLORS: Record<
@@ -65,7 +66,7 @@ export const NATION_COLORS: Record<
},
};
export function Title({ outline }: TitleProps) {
export function Title({ foreground }: TitleProps) {
const { unwrap } = useLocale();
const protagonist = Duel.use((state) => state.protagonist.tank);
const disturbed = TankopediaEphemeral.use((state) => state.disturbed);
@@ -81,6 +82,11 @@ export function Title({ outline }: TitleProps) {
: `${nationColors.text}-12`,
);
const name = unwrap(protagonist.name);
const fontSize = revealed
? disturbed
? '2rem'
: `min(65vh, ${125 / name.length}vw)`
: `min(48vh, ${75 / name.length}vw)`;
return (
<Box
@@ -97,17 +103,10 @@ export function Title({ outline }: TitleProps) {
fontWeight: 900,
userSelect: 'none',
pointerEvents: 'none',
fontSize: revealed
? disturbed
? '2rem'
: `min(65vh, ${125 / name.length}vw)`
: `min(48vh, ${75 / name.length}vw)`,
fontSize,
whiteSpace: 'nowrap',
color: outline ? Var(`${nationColors.tint}-a2`) : color,
opacity: outline && disturbed ? 0 : 1,
WebkitTextStroke: outline
? `${revealed ? (disturbed ? '1px' : 'min(2px, 0.2vw)') : 0} ${color}`
: undefined,
color: foreground ? Var('gray-a10') : color,
opacity: foreground && disturbed ? 0 : 1,
letterSpacing: disturbed || !revealed ? 0 : '-0.03em',
transition: `
letter-spacing 1.5s ${disturbed ? '' : 'cubic-bezier(.81,-2,.68,1)'},
@@ -120,6 +119,8 @@ export function Title({ outline }: TitleProps) {
>
{name}
</Heading>
{!revealed && <Tracker fontSize={fontSize} />}
</Box>
);
}
@@ -73,6 +73,9 @@ export function SceneProps() {
const crewSkills = TankopediaEphemeral.use((state) => state.skills);
const consumables = Duel.use((state) => state.protagonist.consumables);
const provisions = Duel.use((state) => state.protagonist.provisions);
const assaultDistance = Duel.use(
(state) => state.protagonist.assaultDistance,
);
const equipmentMatrix = Duel.use(
(state) => state.protagonist.equipmentMatrix,
);
@@ -96,6 +99,7 @@ export function SceneProps() {
stockTurret: tank.turrets[0],
stockTrack: tank.tracks[0],
track,
assaultDistance,
},
{ equipmentDefinitions, provisionDefinitions, tankModelDefinition },
);
@@ -1,3 +1,5 @@
precision mediump float;
#define USE_FOG
#include <fog_pars_fragment>
@@ -1,3 +1,5 @@
precision mediump float;
#define USE_FOG
#include <fog_pars_vertex>
@@ -39,11 +39,11 @@ interface TankSandboxProps {
naked?: boolean;
}
export const forNear0 = 20;
export const fogFar0 = 25;
export const forNear0 = 30;
export const fogFar0 = 50;
export const forNear1 = 0;
export const fogFar1 = 0;
export const fogAnimationTime = 1.5;
export const fogAnimationTime = 2.5;
export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
({ thicknessRange, naked }, ref) => {
@@ -74,7 +74,7 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
const interval = setInterval(() => {
const t = (Date.now() - t0) / 1000;
const x = t / fogAnimationTime;
const y = Math.cbrt(-2 * Math.abs(x - 0.5)) + 1;
const y = Math.sin(Math.PI * x);
const near = forNear0 + (forNear1 - forNear0) * y;
const far = fogFar0 + (fogFar1 - fogFar0) * y;
@@ -103,7 +103,7 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
transitionDuration: '1s',
background: isFullScreen ? 'black' : undefined,
}}
height={disturbed && !isFullScreen ? 'calc(100svh - 6rem)' : '100vh'}
height={!isFullScreen ? 'calc(100svh - 6rem)' : '100vh'}
maxHeight={isFullScreen ? undefined : '60rem'}
maxWidth={isFullScreen ? undefined : '120rem'}
flexGrow="1"
@@ -136,7 +136,7 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
height: '100%',
transitionDuration: '2s',
opacity: revealed ? 1 : 0,
filter: disturbed ? undefined : `drop-shadow(0 0 2rem black)`,
filter: disturbed ? undefined : `drop-shadow(0 0 1rem black)`,
}}
>
<Suspense>
@@ -153,7 +153,7 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
</Box>
</Box>
<Title outline />
<Title foreground />
</Flex>
</Flex>
);
@@ -0,0 +1,17 @@
const GAMMA = 1.2;
export function fakeLogSquishyHigh(x: number) {
return Math.sin((Math.PI / 2) * x) ** (1 / GAMMA);
}
export function fakeLogSquishyHighInverse(y: number) {
return (2 / Math.PI) * Math.asin(y ** GAMMA);
}
export function fakeLogSquishyLow(x: number) {
return x ** 2;
}
export function fakeLogSquishyLowInverse(y: number) {
return y ** (1 / 2);
}
@@ -1,12 +1,12 @@
import { fetchEquipmentDefinitions } from '@blitzkit/core';
import type { EquipmentMatrix } from '../../stores/duel';
import { awaitableEquipmentDefinitions } from '../awaitables/equipmentDefinitions';
export async function hasEquipment(
id: number,
equipmentPreset: string,
equipmentMatrix: EquipmentMatrix,
) {
const equipmentDefinitions = await fetchEquipmentDefinitions();
const equipmentDefinitions = await awaitableEquipmentDefinitions;
const preset = equipmentDefinitions.presets[equipmentPreset];
return preset.slots.some((slot, index) => {
const row = Math.floor(index / 3);
@@ -46,6 +46,7 @@ export function tankCharacteristics(
applyReactiveArmor,
applyDynamicArmor,
applySpallLiner,
assaultDistance,
}: {
tank: TankDefinition;
turret: TurretDefinition;
@@ -65,6 +66,7 @@ export function tankCharacteristics(
applyReactiveArmor: boolean;
applyDynamicArmor: boolean;
applySpallLiner: boolean;
assaultDistance: number;
},
{
tankModelDefinition,
@@ -157,7 +159,15 @@ export function tankCharacteristics(
[applyDynamicArmor, -0.1],
[applySpallLiner && shell.type === ShellType.HE, -0.2],
);
const moduleDamageCoefficient = coefficient([hasTungsten, 0.15]);
const assaultDamageCoefficient =
gun.gun_type!.value.base.assault_ranges &&
gun.gun_type!.value.base.assault_ranges.types.includes(shell.type)
? (gun.gun_type!.value.base.assault_ranges.ranges.find(
({ distance }) => distance >= assaultDistance,
)?.factor ?? 0)
: 1;
const moduleDamageCoefficient =
coefficient([hasTungsten, 0.15]) * assaultDamageCoefficient;
const reloadCoefficient =
(coefficient([hasGunRammer, -0.05]) *
coefficient([true, degressiveStat(loaderMastery)])) /
@@ -326,10 +336,13 @@ export function tankCharacteristics(
stockTurret.weight +
stockGun.gun_type!.value.base.weight;
const resolvedEnginePower = engine.power * enginePowerCoefficient;
const damageCoefficientWithoutAssault = armorDamageCoefficient;
const damageCoefficient =
damageCoefficientWithoutAssault * assaultDamageCoefficient;
const dpm = resolveDpm(
gun,
shell,
armorDamageCoefficient,
damageCoefficient,
reloadCoefficient,
intraClipCoefficient,
);
@@ -352,7 +365,9 @@ export function tankCharacteristics(
return current;
}, null)!.index
: undefined;
const damage = shell.armor_damage * armorDamageCoefficient;
const damageWithoutAssault =
shell.armor_damage * damageCoefficientWithoutAssault;
const damage = shell.armor_damage * damageCoefficient;
const dpmEffective =
gun.gun_type!.$case === 'auto_reloader'
? gun.gun_type!.value.extension.shell_reloads[0] >
@@ -533,6 +548,7 @@ export function tankCharacteristics(
caliber,
penetration,
damage,
damageWithoutAssault,
clipDamage,
moduleDamage,
explosionRadius,
@@ -40,5 +40,7 @@ export function tankToDuelMember(
yaw,
pitch,
turret,
assaultDistance:
(gun.gun_type!.value.base.assault_ranges?.ranges[0].distance ?? 0) / 2,
} satisfies DuelMember;
}
@@ -85,6 +85,7 @@ function Content() {
member.key !== thisMember.key &&
member.provisions.includes(101),
),
assaultDistance: thisMember.assaultDistance,
},
{
equipmentDefinitions: equipmentDefinitions,
@@ -60,6 +60,7 @@ const url = import.meta.env.SITE + Astro.url.pathname;
const icon = asset(`icons/tanks/big/${id}.webp`);
const turret = tank.turrets.at(-1)!;
const gun = turret.guns.at(-1)!;
const shell = gun.gun_type!.value.base.shells[0];
const characteristics = tankCharacteristics(
{
tank,
@@ -74,12 +75,13 @@ const characteristics = tankCharacteristics(
turret,
gun,
provisions: createDefaultProvisions(tank, gun, provisionDefinitions),
shell: gun.gun_type!.value.base.shells[0],
shell,
stockEngine: tank.engines[0],
stockGun: tank.turrets[0].guns[0],
stockTurret: tank.turrets[0],
stockTrack: tank.tracks[0],
track: tank.tracks.at(-1)!,
assaultDistance: 0,
},
{ equipmentDefinitions, provisionDefinitions, tankModelDefinition },
);
+4 -1
View File
@@ -1,9 +1,12 @@
import type { APIContext } from 'astro';
const ALLOW_ROBOTS = import.meta.env.ALLOW_ROBOTS;
export async function GET({ request }: APIContext) {
const flags: Record<string, string | null> = {
'User-agent': '*',
Allow: '/',
Allow: ALLOW_ROBOTS ? '/' : null,
Disallow: ALLOW_ROBOTS ? null : '/',
space: null,
Sitemap: `${new URL(request.url).origin}/sitemap-index.xml`,
@@ -39,6 +39,7 @@ export interface DuelMember {
provisions: number[];
camouflage: boolean;
cooldownBooster: number;
assaultDistance: number;
}
export interface DuelStore {
+2 -2
View File
@@ -18,7 +18,7 @@ export interface TankFilters {
classes: TankClass[];
types: TankType[];
testing: 'include' | 'exclude' | 'only';
search?: string;
search: string | null;
searching: boolean;
gunType: CaseType<GunDefinition>[];
shells: [ShellType | null, ShellType | null, ShellType | null];
@@ -30,7 +30,7 @@ export const initialTankFilters: TankFilters = {
classes: [],
types: [],
testing: 'include',
search: undefined,
search: null,
searching: false,
gunType: [],
shells: [null, null, null],
@@ -14,6 +14,7 @@ export const SORT_UNITS: Record<TankopediaSortBy, string | undefined> = {
'fire.gunDepression': '°',
'fire.gunElevation': '°',
'fire.premiumPenetration': 'mm',
'fire.tertiaryPenetration': 'mm',
'fire.reload': 's',
'fire.shellVelocity': 'm/s',
'fire.shellRange': 'm',