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", "gameplay",
"glsl", "glsl",
"GLTF", "GLTF",
"gtag",
"haha", "haha",
"highp", "highp",
"iife", "iife",
@@ -110,6 +111,7 @@
"Maddoxkkm", "Maddoxkkm",
"malum", "malum",
"mantlets", "mantlets",
"mediump",
"metallicness", "metallicness",
"metalness", "metalness",
"minitelrose", "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", "name": "blitzkit",
"private": true, "private": true,
"version": "1.28.0-beta.1", "version": "1.27.15",
"description": "🎉 Tools for everything World of Tanks Blitz", "description": "🎉 Tools for everything World of Tanks Blitz",
"type": "module", "type": "module",
"types": "./types.d.ts", "types": "./types.d.ts",
@@ -21,7 +21,8 @@
"dev:bot": "bun run build:bot --dev && bun start:bot", "dev:bot": "bun run build:bot --dev && bun start:bot",
"start:bot": "bun dist/bot/main.js", "start:bot": "bun dist/bot/main.js",
"dev:docs": "vitepress dev docs", "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", "dev:api": "bun --filter=@blitzkit/api run dev",
"setup:depotdownloader": "bun --filter=@blitzkit/scripts run setup:depotdownloader", "setup:depotdownloader": "bun --filter=@blitzkit/scripts run setup:depotdownloader",
"setup:patches": "bun --filter=@blitzkit/scripts run setup:patches", "setup:patches": "bun --filter=@blitzkit/scripts run setup:patches",
+2
View File
@@ -4,4 +4,6 @@ export const youtubers = [
{ id: 'UCxRpZqMElssTdPYDK3x7wyg', name: 'N90' }, { id: 'UCxRpZqMElssTdPYDK3x7wyg', name: 'N90' },
{ id: 'UC28Hc6p2MGoB5s78eHCYkQg', name: 'GuidesBlitz' }, { id: 'UC28Hc6p2MGoB5s78eHCYkQg', name: 'GuidesBlitz' },
{ id: 'UCLMEfqtlwLDN-UXX21RiEww', name: 'WoT Blitz Best Replays' }, { 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 // source: packages/core/src/protos/camouflage_definitions.proto
/* eslint-disable */ /* eslint-disable */
import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; import { BinaryReader, BinaryWriter } from '@bufbuild/protobuf/wire';
import { createBaseI18nString, I18nString } from "./i18n"; import { createBaseI18nString, I18nString } from './i18n';
export const protobufPackage = "blitzkit"; export const protobufPackage = 'blitzkit';
export interface CamouflageDefinitions { export interface CamouflageDefinitions {
camouflages: { [key: number]: Camouflage }; camouflages: { [key: number]: Camouflage };
@@ -22,8 +22,8 @@ export interface CamouflageDefinitions_CamouflagesEntry {
export interface Camouflage { export interface Camouflage {
id: number; id: number;
name: I18nString; name: I18nString;
tank_name?: I18nString | undefined; tank_name?: I18nString;
tank_name_full?: I18nString | undefined; tank_name_full?: I18nString;
} }
export function createBaseCamouflageDefinitions(): CamouflageDefinitions { export function createBaseCamouflageDefinitions(): CamouflageDefinitions {
@@ -31,15 +31,25 @@ export function createBaseCamouflageDefinitions(): CamouflageDefinitions {
} }
export const CamouflageDefinitions: MessageFns<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]) => { 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; return writer;
}, },
decode(input: BinaryReader | Uint8Array, length?: number): CamouflageDefinitions { decode(
const reader = input instanceof BinaryReader ? input : new BinaryReader(input); input: BinaryReader | Uint8Array,
length?: number,
): CamouflageDefinitions {
const reader =
input instanceof BinaryReader ? input : new BinaryReader(input);
let end = length === undefined ? reader.len : reader.pos + length; let end = length === undefined ? reader.len : reader.pos + length;
const message = createBaseCamouflageDefinitions(); const message = createBaseCamouflageDefinitions();
while (reader.pos < end) { while (reader.pos < end) {
@@ -50,7 +60,10 @@ export const CamouflageDefinitions: MessageFns<CamouflageDefinitions> = {
break; break;
} }
const entry1 = CamouflageDefinitions_CamouflagesEntry.decode(reader, reader.uint32()); const entry1 = CamouflageDefinitions_CamouflagesEntry.decode(
reader,
reader.uint32(),
);
if (entry1.value !== undefined) { if (entry1.value !== undefined) {
message.camouflages[entry1.key] = entry1.value; message.camouflages[entry1.key] = entry1.value;
} }
@@ -68,10 +81,12 @@ export const CamouflageDefinitions: MessageFns<CamouflageDefinitions> = {
fromJSON(object: any): CamouflageDefinitions { fromJSON(object: any): CamouflageDefinitions {
return { return {
camouflages: isObject(object.camouflages) camouflages: isObject(object.camouflages)
? Object.entries(object.camouflages).reduce<{ [key: number]: Camouflage }>((acc, [key, value]) => { ? Object.entries(object.camouflages).reduce<{
acc[globalThis.Number(key)] = Camouflage.fromJSON(value); [key: number]: Camouflage;
return acc; }>((acc, [key, value]) => {
}, {}) acc[globalThis.Number(key)] = Camouflage.fromJSON(value);
return acc;
}, {})
: {}, : {},
}; };
}, },
@@ -90,20 +105,23 @@ export const CamouflageDefinitions: MessageFns<CamouflageDefinitions> = {
return obj; 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)); 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(); const message = createBaseCamouflageDefinitions();
message.camouflages = Object.entries(object.camouflages ?? {}).reduce<{ [key: number]: Camouflage }>( message.camouflages = Object.entries(object.camouflages ?? {}).reduce<{
(acc, [key, value]) => { [key: number]: Camouflage;
if (value !== undefined) { }>((acc, [key, value]) => {
acc[globalThis.Number(key)] = Camouflage.fromPartial(value); if (value !== undefined) {
} acc[globalThis.Number(key)] = Camouflage.fromPartial(value);
return acc; }
}, return acc;
{}, }, {});
);
return message; return message;
}, },
}; };
@@ -112,90 +130,116 @@ export function createBaseCamouflageDefinitions_CamouflagesEntry(): CamouflageDe
return { key: 0, value: createBaseCamouflage() }; return { key: 0, value: createBaseCamouflage() };
} }
export const CamouflageDefinitions_CamouflagesEntry: MessageFns<CamouflageDefinitions_CamouflagesEntry> = { export const CamouflageDefinitions_CamouflagesEntry: MessageFns<CamouflageDefinitions_CamouflagesEntry> =
encode(message: CamouflageDefinitions_CamouflagesEntry, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { {
if (message.key !== undefined) { encode(
writer.uint32(8).uint32(message.key); message: CamouflageDefinitions_CamouflagesEntry,
} writer: BinaryWriter = new BinaryWriter(),
if (message.value !== undefined) { ): BinaryWriter {
Camouflage.encode(message.value, writer.uint32(18).fork()).join(); if (message.key !== undefined) {
} writer.uint32(8).uint32(message.key);
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;
}
} }
if ((tag & 7) === 4 || tag === 0) { if (message.value !== undefined) {
break; Camouflage.encode(message.value, writer.uint32(18).fork()).join();
} }
reader.skip(tag & 7); return writer;
} },
return message;
},
fromJSON(object: any): CamouflageDefinitions_CamouflagesEntry { decode(
return { input: BinaryReader | Uint8Array,
key: globalThis.Number(assertSet("CamouflageDefinitions_CamouflagesEntry.key", object.key)), length?: number,
value: Camouflage.fromJSON(assertSet("CamouflageDefinitions_CamouflagesEntry.value", object.value)), ): 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 { message.key = reader.uint32();
const obj: any = {}; continue;
if (message.key !== undefined) { }
obj.key = Math.round(message.key); case 2: {
} if (tag !== 18) {
if (message.value !== undefined) { break;
obj.value = Camouflage.toJSON(message.value); }
}
return obj;
},
create<I extends Exact<DeepPartial<CamouflageDefinitions_CamouflagesEntry>, I>>( message.value = Camouflage.decode(reader, reader.uint32());
base?: I, continue;
): CamouflageDefinitions_CamouflagesEntry { }
return CamouflageDefinitions_CamouflagesEntry.fromPartial(base ?? ({} as any)); }
}, if ((tag & 7) === 4 || tag === 0) {
fromPartial<I extends Exact<DeepPartial<CamouflageDefinitions_CamouflagesEntry>, I>>( break;
object: I, }
): CamouflageDefinitions_CamouflagesEntry { reader.skip(tag & 7);
const message = createBaseCamouflageDefinitions_CamouflagesEntry(); }
message.key = object.key ?? 0; return message;
message.value = (object.value !== undefined && object.value !== null) },
? Camouflage.fromPartial(object.value)
: createBaseCamouflage(); fromJSON(object: any): CamouflageDefinitions_CamouflagesEntry {
return message; 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 { 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> = { 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) { if (message.id !== 0) {
writer.uint32(8).uint32(message.id); 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(); I18nString.encode(message.tank_name, writer.uint32(26).fork()).join();
} }
if (message.tank_name_full !== undefined) { 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; return writer;
}, },
decode(input: BinaryReader | Uint8Array, length?: number): Camouflage { 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; let end = length === undefined ? reader.len : reader.pos + length;
const message = createBaseCamouflage(); const message = createBaseCamouflage();
while (reader.pos < end) { while (reader.pos < end) {
@@ -261,10 +309,14 @@ export const Camouflage: MessageFns<Camouflage> = {
fromJSON(object: any): Camouflage { fromJSON(object: any): Camouflage {
return { return {
id: globalThis.Number(assertSet("Camouflage.id", object.id)), id: globalThis.Number(assertSet('Camouflage.id', object.id)),
name: I18nString.fromJSON(assertSet("Camouflage.name", object.name)), name: I18nString.fromJSON(assertSet('Camouflage.name', object.name)),
tank_name: isSet(object.tank_name) ? I18nString.fromJSON(object.tank_name) : undefined, tank_name: isSet(object.tank_name)
tank_name_full: isSet(object.tank_name_full) ? I18nString.fromJSON(object.tank_name_full) : undefined, ? 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 { create<I extends Exact<DeepPartial<Camouflage>, I>>(base?: I): Camouflage {
return Camouflage.fromPartial(base ?? ({} as any)); 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(); const message = createBaseCamouflage();
message.id = object.id ?? 0; message.id = object.id ?? 0;
message.name = (object.name !== undefined && object.name !== null) message.name =
? I18nString.fromPartial(object.name) object.name !== undefined && object.name !== null
: createBaseI18nString(); ? I18nString.fromPartial(object.name)
message.tank_name = (object.tank_name !== undefined && object.tank_name !== null) : createBaseI18nString();
? I18nString.fromPartial(object.tank_name) message.tank_name =
: undefined; object.tank_name !== undefined && object.tank_name !== null
message.tank_name_full = (object.tank_name_full !== undefined && object.tank_name_full !== null) ? I18nString.fromPartial(object.tank_name)
? I18nString.fromPartial(object.tank_name_full) : undefined;
: undefined; message.tank_name_full =
object.tank_name_full !== undefined && object.tank_name_full !== null
? I18nString.fromPartial(object.tank_name_full)
: undefined;
return message; 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 export type DeepPartial<T> = T extends Builtin
: T extends globalThis.Array<infer U> ? globalThis.Array<DeepPartial<U>> ? T
: T extends ReadonlyArray<infer U> ? ReadonlyArray<DeepPartial<U>> : T extends globalThis.Array<infer U>
: T extends { $case: string; value: unknown } ? { $case: T["$case"]; value?: DeepPartial<T["value"]> } ? globalThis.Array<DeepPartial<U>>
: T extends {} ? { [K in keyof T]?: DeepPartial<T[K]> } : T extends ReadonlyArray<infer U>
: Partial<T>; ? 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; type KeysOfUnion<T> = T extends T ? keyof T : never;
export type Exact<P, I extends P> = P extends Builtin ? P export type Exact<P, I extends P> = P extends Builtin
: P & { [K in keyof P]: Exact<P[K], I[K]> } & { [K in Exclude<keyof I, KeysOfUnion<P>>]: never }; ? P
: P & { [K in keyof P]: Exact<P[K], I[K]> } & {
[K in Exclude<keyof I, KeysOfUnion<P>>]: never;
};
function isObject(value: any): boolean { function isObject(value: any): boolean {
return typeof value === "object" && value !== null; return typeof value === 'object' && value !== null;
} }
function isSet(value: any): boolean { function isSet(value: any): boolean {
@@ -157,6 +157,17 @@ message GunDefinitionBase {
required float dispersion_damaged = 13; required float dispersion_damaged = 13;
repeated Unlock unlocks = 14; repeated Unlock unlocks = 14;
required uint32 shell_capacity = 15; 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 { message ShellDefinition {
File diff suppressed because it is too large. Load diff
+6 -1
View File
@@ -799,6 +799,7 @@
"fire.caliber": "caliber", "fire.caliber": "caliber",
"fire.standardPenetration": "standard penetration", "fire.standardPenetration": "standard penetration",
"fire.premiumPenetration": "premium penetration", "fire.premiumPenetration": "premium penetration",
"fire.tertiaryPenetration": "tertiary penetration",
"fire.damage": "damage", "fire.damage": "damage",
"fire.moduleDamage": "module damage", "fire.moduleDamage": "module damage",
"fire.shellVelocity": "shell velocity", "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.", "tank_seo_description": "%s1 armor, statistics, equipment, provisions, and more. %s2 DPM, %s3hp alpha, %s4mm penetration, %s5kph top speed, %s6hp health.",
"visualizers": { "visualizers": {
"assault": {
"title": "Assault shells"
},
"rotator": { "rotator": {
"hull": "Hull", "hull": "Hull",
"turret": "Turret", "turret": "Turret",
@@ -1094,7 +1099,6 @@
"hull_traverse_speed": "Hull traverse speed", "hull_traverse_speed": "Hull traverse speed",
"camouflage": "Camouflage", "camouflage": "Camouflage",
"reload": "Reload", "reload": "Reload",
"crewCount": "Crew count", "crewCount": "Crew count",
"shellNormalization": "Normalization", "shellNormalization": "Normalization",
"shellRicochet": "Ricochet", "shellRicochet": "Ricochet",
@@ -1108,6 +1112,7 @@
"caliber": "Caliber", "caliber": "Caliber",
"penetration": "Penetration", "penetration": "Penetration",
"damage": "Damage", "damage": "Damage",
"damageWithoutAssault": "Damage without assault shells",
"clipDamage": "Clipping potential", "clipDamage": "Clipping potential",
"moduleDamage": "Module damage", "moduleDamage": "Module damage",
"explosionRadius": "Explosion radius", "explosionRadius": "Explosion radius",
+5
View File
@@ -698,6 +698,7 @@
"fire.caliber": "calibre", "fire.caliber": "calibre",
"fire.standardPenetration": "penetración estándar", "fire.standardPenetration": "penetración estándar",
"fire.premiumPenetration": "penetración prémium", "fire.premiumPenetration": "penetración prémium",
"fire.tertiaryPenetration": "tertiary penetration",
"fire.damage": "daño", "fire.damage": "daño",
"fire.moduleDamage": "daño al módulo", "fire.moduleDamage": "daño al módulo",
"fire.shellVelocity": "velocidad del proyectil", "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_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", "tank_seo_description": "%s1 blindaje, estadisticas, equipamiento, proviciones y mas.\n%s2 DPM, %3hp alfa, %s4mm penetracion, %s5kph velocidad maxima, %s6 hp salud",
"visualizers": { "visualizers": {
"assault": {
"title": "Assault shells"
},
"rotator": { "rotator": {
"hull": "Hull", "hull": "Hull",
"turret": "Turret", "turret": "Turret",
@@ -975,6 +979,7 @@
"caliber": "Calibre", "caliber": "Calibre",
"penetration": "Penetración", "penetration": "Penetración",
"damage": "Daño", "damage": "Daño",
"damageWithoutAssault": "Damage without assault shells",
"clipDamage": "Clipping potential", "clipDamage": "Clipping potential",
"moduleDamage": "Daño a módulos", "moduleDamage": "Daño a módulos",
"explosionRadius": "Radio de Explosión", "explosionRadius": "Radio de Explosión",
+5
View File
@@ -698,6 +698,7 @@
"fire.caliber": "caliber", "fire.caliber": "caliber",
"fire.standardPenetration": "standard penetration", "fire.standardPenetration": "standard penetration",
"fire.premiumPenetration": "premium penetration", "fire.premiumPenetration": "premium penetration",
"fire.tertiaryPenetration": "tertiary penetration",
"fire.damage": "damage", "fire.damage": "damage",
"fire.moduleDamage": "module damage", "fire.moduleDamage": "module damage",
"fire.shellVelocity": "shell velocity", "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_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.", "tank_seo_description": "%s1 armor, statistics, equipment, provisions, and more. %s2 DPM, %s3hp alpha, %s4mm penetration, %s5kph top speed, %s6hp health.",
"visualizers": { "visualizers": {
"assault": {
"title": "Assault shells"
},
"rotator": { "rotator": {
"hull": "Hull", "hull": "Hull",
"turret": "Turret", "turret": "Turret",
@@ -975,6 +979,7 @@
"caliber": "Caliber", "caliber": "Caliber",
"penetration": "Penetration", "penetration": "Penetration",
"damage": "Dégâts", "damage": "Dégâts",
"damageWithoutAssault": "Damage without assault shells",
"clipDamage": "Clipping potential", "clipDamage": "Clipping potential",
"moduleDamage": "Module damage", "moduleDamage": "Module damage",
"explosionRadius": "Explosion radius", "explosionRadius": "Explosion radius",
+5
View File
@@ -698,6 +698,7 @@
"fire.caliber": "砲口径", "fire.caliber": "砲口径",
"fire.standardPenetration": "通常弾貫通力", "fire.standardPenetration": "通常弾貫通力",
"fire.premiumPenetration": "課金弾貫通力", "fire.premiumPenetration": "課金弾貫通力",
"fire.tertiaryPenetration": "tertiary penetration",
"fire.damage": "ダメージ", "fire.damage": "ダメージ",
"fire.moduleDamage": "モジュールダメージ", "fire.moduleDamage": "モジュールダメージ",
"fire.shellVelocity": "弾速", "fire.shellVelocity": "弾速",
@@ -786,6 +787,9 @@
"tank_seo_title": "%s1 - Tier %s2 %s3 %s4 | %s5 Tankopedia | World of Tanks Blitz (WoTB)", "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.", "tank_seo_description": "%s1 armor, statistics, equipment, provisions, and more. %s2 DPM, %s3hp alpha, %s4mm penetration, %s5kph top speed, %s6hp health.",
"visualizers": { "visualizers": {
"assault": {
"title": "Assault shells"
},
"rotator": { "rotator": {
"hull": "Hull", "hull": "Hull",
"turret": "Turret", "turret": "Turret",
@@ -975,6 +979,7 @@
"caliber": "口径", "caliber": "口径",
"penetration": "貫通力", "penetration": "貫通力",
"damage": "ダメージ", "damage": "ダメージ",
"damageWithoutAssault": "Damage without assault shells",
"clipDamage": "弾倉火力", "clipDamage": "弾倉火力",
"moduleDamage": "モジュールダメージ", "moduleDamage": "モジュールダメージ",
"explosionRadius": "爆風半径", "explosionRadius": "爆風半径",
+6 -1
View File
@@ -380,7 +380,7 @@
}, },
"embeds": { "embeds": {
"name": "osadzenie", "name": "osadzenie",
"description": "Jak działa osadzanie" "description": "Jak działają osadzone"
}, },
"introduction": { "introduction": {
"name": "wprowadzenie", "name": "wprowadzenie",
@@ -698,6 +698,7 @@
"fire.caliber": "caliber", "fire.caliber": "caliber",
"fire.standardPenetration": "standard penetration", "fire.standardPenetration": "standard penetration",
"fire.premiumPenetration": "premium penetration", "fire.premiumPenetration": "premium penetration",
"fire.tertiaryPenetration": "tertiary penetration",
"fire.damage": "damage", "fire.damage": "damage",
"fire.moduleDamage": "module damage", "fire.moduleDamage": "module damage",
"fire.shellVelocity": "shell velocity", "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_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.", "tank_seo_description": "%s1 armor, statistics, equipment, provisions, and more. %s2 DPM, %s3hp alpha, %s4mm penetration, %s5kph top speed, %s6hp health.",
"visualizers": { "visualizers": {
"assault": {
"title": "Assault shells"
},
"rotator": { "rotator": {
"hull": "Hull", "hull": "Hull",
"turret": "Turret", "turret": "Turret",
@@ -975,6 +979,7 @@
"caliber": "Caliber", "caliber": "Caliber",
"penetration": "Penetration", "penetration": "Penetration",
"damage": "Uszkodzenia", "damage": "Uszkodzenia",
"damageWithoutAssault": "Damage without assault shells",
"clipDamage": "Clipping potential", "clipDamage": "Clipping potential",
"moduleDamage": "Module damage", "moduleDamage": "Module damage",
"explosionRadius": "Explosion radius", "explosionRadius": "Explosion radius",
+5
View File
@@ -698,6 +698,7 @@
"fire.caliber": "calibre", "fire.caliber": "calibre",
"fire.standardPenetration": "penetração padrão", "fire.standardPenetration": "penetração padrão",
"fire.premiumPenetration": "penetração premium", "fire.premiumPenetration": "penetração premium",
"fire.tertiaryPenetration": "tertiary penetration",
"fire.damage": "dano", "fire.damage": "dano",
"fire.moduleDamage": "dano a módulos", "fire.moduleDamage": "dano a módulos",
"fire.shellVelocity": "velocidade do projétil", "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_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.", "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": { "visualizers": {
"assault": {
"title": "Assault shells"
},
"rotator": { "rotator": {
"hull": "Hull", "hull": "Hull",
"turret": "Turret", "turret": "Turret",
@@ -975,6 +979,7 @@
"caliber": "Calibre", "caliber": "Calibre",
"penetration": "Penetração", "penetration": "Penetração",
"damage": "Dano", "damage": "Dano",
"damageWithoutAssault": "Damage without assault shells",
"clipDamage": "Potencial de clipe", "clipDamage": "Potencial de clipe",
"moduleDamage": "Dano a módulos", "moduleDamage": "Dano a módulos",
"explosionRadius": "Raio de Explosão", "explosionRadius": "Raio de Explosão",
+6 -1
View File
@@ -697,7 +697,8 @@
"fire.reload": "перезарядка", "fire.reload": "перезарядка",
"fire.caliber": "калибр", "fire.caliber": "калибр",
"fire.standardPenetration": "базовое пробитие", "fire.standardPenetration": "базовое пробитие",
"fire.premiumPenetration": "улучшенное пробитие ", "fire.premiumPenetration": "премиумное пробитие ",
"fire.tertiaryPenetration": "пробитие третьего снаряда",
"fire.damage": "урон", "fire.damage": "урон",
"fire.moduleDamage": "урон по модулям", "fire.moduleDamage": "урон по модулям",
"fire.shellVelocity": "скорость снаряда", "fire.shellVelocity": "скорость снаряда",
@@ -786,6 +787,9 @@
"tank_seo_title": "%s1 - %s4 %s3 %s2 уровня | Танкопедия %s5 | World of Tanks Blitz (WoTB)", "tank_seo_title": "%s1 - %s4 %s3 %s2 уровня | Танкопедия %s5 | World of Tanks Blitz (WoTB)",
"tank_seo_description": "%s1 броня, статистика, оборудование, снабжение и многое другое. %s2 УВМ, %s3hp альфа, %s4mm пробитие, %s5км/ч лучшая скорость, %s6hp здоровье.", "tank_seo_description": "%s1 броня, статистика, оборудование, снабжение и многое другое. %s2 УВМ, %s3hp альфа, %s4mm пробитие, %s5км/ч лучшая скорость, %s6hp здоровье.",
"visualizers": { "visualizers": {
"assault": {
"title": "Assault shells"
},
"rotator": { "rotator": {
"hull": "Корпус", "hull": "Корпус",
"turret": "Башня", "turret": "Башня",
@@ -975,6 +979,7 @@
"caliber": "Калибр", "caliber": "Калибр",
"penetration": "Пробитие", "penetration": "Пробитие",
"damage": "Урон", "damage": "Урон",
"damageWithoutAssault": "Damage without assault shells",
"clipDamage": "Урон с барабана", "clipDamage": "Урон с барабана",
"moduleDamage": "Урон по модулям", "moduleDamage": "Урон по модулям",
"explosionRadius": "Разлет осколков", "explosionRadius": "Разлет осколков",
+23 -18
View File
@@ -2,7 +2,7 @@
"test": { "test": {
"default": "тест", "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.", "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": { "common": {
"units": { "units": {
@@ -12,7 +12,7 @@
"hp": "%s1хп", "hp": "%s1хп",
"x": "х%s1", "x": "х%s1",
"percentage": "%s1%", "percentage": "%s1%",
"deg_s": "%s1°/s" "deg_s": "%s1°/с"
}, },
"gun_types": { "gun_types": {
"auto_loader": "Магазинна система заряджання", "auto_loader": "Магазинна система заряджання",
@@ -413,10 +413,10 @@
} }
}, },
"debug": { "debug": {
"name": "debug", "name": "налагодження",
"description": "Debug information about the bot", "description": "Інформація про налагодження бота",
"body": { "body": {
"title": "# Debug\n\n", "title": "# Налагодження\n\n",
"version": "Версія", "version": "Версія",
"shard": { "shard": {
"label": "Уламок", "label": "Уламок",
@@ -499,7 +499,7 @@
}, },
"embed": { "embed": {
"title": "Невірний обліковий запис?", "title": "Невірний обліковий запис?",
"description": "Use the command again and select a username from the search results." "description": "Використайте команду ще раз і виберіть ім'я користувача з результатів пошуку."
} }
}, },
"search_clans": { "search_clans": {
@@ -508,7 +508,7 @@
"options": { "options": {
"limit": { "limit": {
"name": "ліміт", "name": "ліміт",
"description": "The max number of clan results (default: %s1)" "description": "Максимальна кількість результатів клану (за замовчуванням: %s1)"
} }
}, },
"body": { "body": {
@@ -531,16 +531,16 @@
} }
}, },
"rating_leaderboard": { "rating_leaderboard": {
"name": "rating-leaderboard", "name": "таблиця рейтингу",
"description": "Rating battles statistics", "description": "Статистика рейтингових боїв",
"subcommands": { "subcommands": {
"neighbors": { "neighbors": {
"name": "neighbors", "name": "neighbors",
"description": "Neighboring players on the leaderboard" "description": "Neighboring players on the leaderboard"
}, },
"league": { "league": {
"name": "league", "name": "ліга",
"description": "Top players of a league" "description": "Найкращий гравець ліги"
} }
}, },
"options": { "options": {
@@ -549,8 +549,8 @@
"description": "" "description": ""
}, },
"season": { "season": {
"name": "season", "name": "сезон",
"description": "Season number (default: current)", "description": "Базова інформація про гравця",
"choices": { "choices": {
"current": "%s1 (current)" "current": "%s1 (current)"
} }
@@ -578,14 +578,14 @@
"description": "Коротка статистика за період" "description": "Коротка статистика за період"
}, },
"owned_tanks": { "owned_tanks": {
"name": "owned-tanks", "name": "отримані танки",
"description": "Shows all own tanks in a tier", "description": "Показує всі ваші танки даного рівня",
"body": { "body": {
"title": "# %s1's owned tier %s2 tanks\n\n" "title": "# %s1's owned tier %s2 tanks\n\n"
} }
}, },
"permissions": { "permissions": {
"name": "permissions", "name": "дозволи",
"description": "Checks wether the bot has the required permissions to function", "description": "Checks wether the bot has the required permissions to function",
"body": { "body": {
"title": "# Permissions\n\n%s1", "title": "# Permissions\n\n%s1",
@@ -615,7 +615,7 @@
}, },
"player_info": { "player_info": {
"name": "player-info", "name": "player-info",
"description": "Basic information about a player", "description": "Базова інформація про гравця",
"body": { "body": {
"title": "%s1's information", "title": "%s1's information",
"nickname": "Нік", "nickname": "Нік",
@@ -635,7 +635,7 @@
"description": "Today's rating statistics" "description": "Today's rating statistics"
}, },
"season": { "season": {
"name": "season", "name": "сезон",
"description": "This season's rating statistics" "description": "This season's rating statistics"
} }
}, },
@@ -698,6 +698,7 @@
"fire.caliber": "калібр", "fire.caliber": "калібр",
"fire.standardPenetration": "базове пробиття", "fire.standardPenetration": "базове пробиття",
"fire.premiumPenetration": "преміум пробиття", "fire.premiumPenetration": "преміум пробиття",
"fire.tertiaryPenetration": "tertiary penetration",
"fire.damage": "шкода", "fire.damage": "шкода",
"fire.moduleDamage": "шкода по модулях", "fire.moduleDamage": "шкода по модулях",
"fire.shellVelocity": "швидкість польоту снаряда", "fire.shellVelocity": "швидкість польоту снаряда",
@@ -786,6 +787,9 @@
"tank_seo_title": "%s1 - Рівень %s2 %s3 %s4 | %s5 Танкопедія | WoT Blitz", "tank_seo_title": "%s1 - Рівень %s2 %s3 %s4 | %s5 Танкопедія | WoT Blitz",
"tank_seo_description": "%s1 броня, характеристики, обладнання, амуніція та більше. %s2 шкода на хвилину, %s3 разова шкода, %s4 мм пробиття, %s5 км/год максимальна швидкість, %s6 міцність.", "tank_seo_description": "%s1 броня, характеристики, обладнання, амуніція та більше. %s2 шкода на хвилину, %s3 разова шкода, %s4 мм пробиття, %s5 км/год максимальна швидкість, %s6 міцність.",
"visualizers": { "visualizers": {
"assault": {
"title": "Assault shells"
},
"rotator": { "rotator": {
"hull": "Hull", "hull": "Hull",
"turret": "Turret", "turret": "Turret",
@@ -975,6 +979,7 @@
"caliber": "Калібр", "caliber": "Калібр",
"penetration": "Бронепробиття", "penetration": "Бронепробиття",
"damage": "Шкода", "damage": "Шкода",
"damageWithoutAssault": "Damage without assault shells",
"clipDamage": "Clipping potential", "clipDamage": "Clipping potential",
"moduleDamage": "Шкода по модулях", "moduleDamage": "Шкода по модулях",
"explosionRadius": "Радіус розльоту уламків", "explosionRadius": "Радіус розльоту уламків",
+22 -17
View File
@@ -41,15 +41,15 @@
"preview": "预览" "preview": "预览"
}, },
"locales": { "locales": {
"en": "英文", "en": "英语",
"es": "西班牙文", "es": "西班牙语",
"pt": "葡萄牙文", "pt": "葡萄牙语",
"ru": "俄文", "ru": "俄语",
"uk": "乌克兰文", "uk": "乌克兰语",
"zh": "中文", "zh": "中文",
"ja": "日文", "ja": "日语",
"fr": "法文", "fr": "法语",
"pl": "波兰文" "pl": "波兰语"
}, },
"wn8_percentile": { "wn8_percentile": {
"0": "非常差", "0": "非常差",
@@ -283,7 +283,7 @@
"name": "阶级", "name": "阶级",
"description": "阶级", "description": "阶级",
"choices": { "choices": {
"tier": "%s1- %s2 级" "tier": "%s1阶 - %s2"
} }
} }
}, },
@@ -494,7 +494,7 @@
"accounts_linked": "帐号已绑定", "accounts_linked": "帐号已绑定",
"no_role_permissions": { "no_role_permissions": {
"title": "%s1 验证失败", "title": "%s1 验证失败",
"description": "我没有管理角色的权限。" "description": "我没有管理身份的权限。"
} }
}, },
"embed": { "embed": {
@@ -517,7 +517,7 @@
} }
}, },
"search_players": { "search_players": {
"name": "搜索玩家", "name": "搜索-玩家",
"description": "搜索玩家", "description": "搜索玩家",
"options": { "options": {
"limit": { "limit": {
@@ -578,7 +578,7 @@
"description": "一段时间内的快速统计" "description": "一段时间内的快速统计"
}, },
"owned_tanks": { "owned_tanks": {
"name": "已擁有的戰車", "name": "已拥有的战车",
"description": "显示该阶级中所有您已拥有的战车", "description": "显示该阶级中所有您已拥有的战车",
"body": { "body": {
"title": "# %s1 所拥有的 %s2 阶战车\n\n" "title": "# %s1 所拥有的 %s2 阶战车\n\n"
@@ -589,8 +589,8 @@
"description": "检查机器人是否拥有运行所需的权限", "description": "检查机器人是否拥有运行所需的权限",
"body": { "body": {
"title": "# 权限\n\n%s1", "title": "# 权限\n\n%s1",
"view_channels": "查看頻道:需要找到重新整理按鈕使用的訊息", "view_channels": "查看频道:需要找到重新整理按钮使用的讯息",
"read_message_history": "讀取歷史訊息: 需要找到訊息以重新整理", "read_message_history": "读取消息历史: 需要找到消息以刷新",
"attach_files": "附加文件:需要将图像添加到基于图像的命令", "attach_files": "附加文件:需要将图像添加到基于图像的命令",
"external_emojis": "外部表情符号:需要添加国家旗帜和战车类别" "external_emojis": "外部表情符号:需要添加国家旗帜和战车类别"
} }
@@ -698,6 +698,7 @@
"fire.caliber": "口径", "fire.caliber": "口径",
"fire.standardPenetration": "标准炮弹穿透力", "fire.standardPenetration": "标准炮弹穿透力",
"fire.premiumPenetration": "加值炮弹穿透力", "fire.premiumPenetration": "加值炮弹穿透力",
"fire.tertiaryPenetration": "tertiary penetration",
"fire.damage": "炮弹伤害值", "fire.damage": "炮弹伤害值",
"fire.moduleDamage": "模组伤害值", "fire.moduleDamage": "模组伤害值",
"fire.shellVelocity": "炮弹飞行速度", "fire.shellVelocity": "炮弹飞行速度",
@@ -786,6 +787,9 @@
"tank_seo_title": "%s1 - %s2 阶 %s3 %s4 | %s5 战车百科 | 战车世界闪击战 (WoTB)", "tank_seo_title": "%s1 - %s2 阶 %s3 %s4 | %s5 战车百科 | 战车世界闪击战 (WoTB)",
"tank_seo_description": "%s1 装甲,统计设备,装备,供给等。 %s2 每分钟伤害, %s3生命值, %s4毫米穿透, %s5kph 最高速度, %s6hp 生命值。", "tank_seo_description": "%s1 装甲,统计设备,装备,供给等。 %s2 每分钟伤害, %s3生命值, %s4毫米穿透, %s5kph 最高速度, %s6hp 生命值。",
"visualizers": { "visualizers": {
"assault": {
"title": "Assault shells"
},
"rotator": { "rotator": {
"hull": "车体", "hull": "车体",
"turret": "炮塔", "turret": "炮塔",
@@ -796,9 +800,9 @@
}, },
"ricochet": { "ricochet": {
"angle": "角度", "angle": "角度",
"normalization": "轉正", "normalization": "转正",
"effective_angle": "有效角度", "effective_angle": "有效角度",
"armor": "裝甲", "armor": "装甲",
"no_ricochet": "高爆弹不会跳弹" "no_ricochet": "高爆弹不会跳弹"
}, },
"view_range": { "view_range": {
@@ -854,7 +858,7 @@
"track": "履带" "track": "履带"
}, },
"status": { "status": {
"penetration": "%s1生命值 擊穿", "penetration": "%s1生命值 击穿",
"blocked": "抵挡伤害", "blocked": "抵挡伤害",
"ricochet": "跳弹", "ricochet": "跳弹",
"splash": "%s1生命值 溅射" "splash": "%s1生命值 溅射"
@@ -975,6 +979,7 @@
"caliber": "口径", "caliber": "口径",
"penetration": "穿深", "penetration": "穿深",
"damage": "伤害", "damage": "伤害",
"damageWithoutAssault": "Damage without assault shells",
"clipDamage": "弹匣伤害期望值", "clipDamage": "弹匣伤害期望值",
"moduleDamage": "模组伤害", "moduleDamage": "模组伤害",
"explosionRadius": "溅射半径", "explosionRadius": "溅射半径",
@@ -7,7 +7,6 @@ import { FileChange } from '../core/github/commitMultipleFiles';
export async function blitzkitTankIcons() { export async function blitzkitTankIcons() {
console.log('Building blitzkit tank icons...'); console.log('Building blitzkit tank icons...');
console.log('Starting website dev server...');
const browser = await launch(); const browser = await launch();
const page = await browser.newPage(); const page = await browser.newPage();
const tanks = Object.values((await fetchTankDefinitions()).tanks); const tanks = Object.values((await fetchTankDefinitions()).tanks);
@@ -19,12 +18,14 @@ export async function blitzkitTankIcons() {
width: BLITZKIT_TANK_ICON_SIZE.width - 1, width: BLITZKIT_TANK_ICON_SIZE.width - 1,
height: BLITZKIT_TANK_ICON_SIZE.height, height: BLITZKIT_TANK_ICON_SIZE.height,
}); });
await page.goto(`http://localhost:4321/api/tankopedia/tank-icon/${id}/`, {
waitUntil: 'networkidle0',
});
if (index === 0) { try {
console.log('Dev server started!'); 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({ 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 { glossary } from './glossary';
import { migration } from './migration'; import { migration } from './migration';
import { moduleIcons } from './moduleIcons'; import { moduleIcons } from './moduleIcons';
import { popularTanks } from './popularTanks';
import { shellIcons } from './shellIcons'; import { shellIcons } from './shellIcons';
import { skillIcons } from './skillIcons'; import { skillIcons } from './skillIcons';
import { tankArmors } from './tankArmors'; import { tankArmors } from './tankArmors';
@@ -47,7 +46,7 @@ const methods = [
boosterIcons, boosterIcons,
tankIcons, tankIcons,
blitzkitTankIcons, blitzkitTankIcons,
popularTanks, // popularTanks,
glossary, glossary,
]; ];
@@ -32,9 +32,6 @@ export async function tankModels() {
if (tankKey.includes('tutorial_bot')) continue; if (tankKey.includes('tutorial_bot')) continue;
const id = toUniqueId(nation, tank.id); const id = toUniqueId(nation, tank.id);
if (id !== 21777) continue;
const parameters = await readYAMLDVPL<TankParameters>( const parameters = await readYAMLDVPL<TankParameters>(
`${DATA}/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`, `${DATA}/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`,
); );
@@ -43,8 +40,6 @@ export async function tankModels() {
parameters.resourcesPath.blitzModelPath.replace(/\.sc2$/, ''), parameters.resourcesPath.blitzModelPath.replace(/\.sc2$/, ''),
); );
await writeFile('../../temp/model.glb', await nodeIO.writeBinary(model));
changes.push({ changes.push({
path: `3d/tanks/models/${id}.glb`, path: `3d/tanks/models/${id}.glb`,
content: Buffer.from(await nodeIO.writeBinary(model)), content: Buffer.from(await nodeIO.writeBinary(model)),
@@ -73,12 +73,13 @@ export async function commitMultipleFiles(
} }
if (diff.status === DiffStatus.Unchanged) { 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 { } else {
console.log( console.log(
`${ `${
diff.status === DiffStatus.New ? '🟢' : '🟡' diff.status === DiffStatus.New ? '🟢' : '🟡'
} (+${diff.change.toLocaleString()}B) ${change.path}`, } (${diff.change > 0 ? '+' : ''}${diff.change.toLocaleString()}B) ${change.path}`,
); );
changes.push(change); changes.push(change);
} }
+1 -2
View File
@@ -1,6 +1,5 @@
// @ts-check // @ts-check
import partytown from '@astrojs/partytown';
import react from '@astrojs/react'; import react from '@astrojs/react';
import sitemap from '@astrojs/sitemap'; import sitemap from '@astrojs/sitemap';
import { SUPPORTED_LOCALES } from '@blitzkit/i18n'; import { SUPPORTED_LOCALES } from '@blitzkit/i18n';
@@ -21,7 +20,7 @@ export default defineConfig({
outDir: '../../dist/website', outDir: '../../dist/website',
prefetch: true, prefetch: true,
integrations: [react(), sitemap(), partytown()], integrations: [react(), sitemap()],
vite: { vite: {
resolve: { resolve: {
alias: { alias: {
@@ -1,3 +1,5 @@
precision mediump float;
#define USE_FOG #define USE_FOG
#include <fog_pars_fragment> #include <fog_pars_fragment>
@@ -1,3 +1,5 @@
precision mediump float;
#define USE_FOG #define USE_FOG
#include <fog_pars_vertex> #include <fog_pars_vertex>
@@ -1,3 +1,5 @@
precision mediump float;
uniform float thickness; uniform float thickness;
uniform float penetration; uniform float penetration;
@@ -1,3 +1,5 @@
precision mediump float;
#include <clipping_planes_pars_vertex> #include <clipping_planes_pars_vertex>
void main() { void main() {
@@ -1,3 +1,5 @@
precision mediump float;
varying vec3 vNormal; varying vec3 vNormal;
varying vec3 vViewPosition; varying vec3 vViewPosition;
@@ -1,3 +1,5 @@
precision mediump float;
varying vec3 vNormal; varying vec3 vNormal;
varying vec3 vViewPosition; varying vec3 vViewPosition;
@@ -79,6 +79,12 @@ export function Body({ stats }: BodyProps) {
<Table.Body> <Table.Body>
<CompareRow stats={stats} value="dpm" decimals={0} /> <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 <CompareRow
stats={stats} stats={stats}
name={strings.website.tools.tankopedia.characteristics.values.reload} 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="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="caliber" decimals={0} />
<CompareRow stats={stats} value="shellNormalization" decimals={0} /> <CompareRow stats={stats} value="shellNormalization" decimals={0} />
<CompareRow <CompareRow
+3 -3
View File
@@ -127,8 +127,8 @@ const strings = getStrings(locale);
</Flex> </Flex>
</Flex> </Flex>
<Flex gap="5" display={{ initial: 'flex', sm: 'none' }}> <Flex gap="5" display={{ initial: 'flex', sm: 'none' }} width="100%">
<Flex direction="column" justify="between" align="end"> <Flex direction="column" gap="1" flexGrow="1" flexBasis="0" align="end">
<Text size="2" color="gray">BlitzKit</Text> <Text size="2" color="gray">BlitzKit</Text>
{ {
PUBLIC_ASSET_BRANCH !== 'preview' && ( PUBLIC_ASSET_BRANCH !== 'preview' && (
@@ -140,7 +140,7 @@ const strings = getStrings(locale);
<Text size="2" color="gray">CDN</Text> <Text size="2" color="gray">CDN</Text>
</Flex> </Flex>
<Flex direction="column" justify="between" align="start"> <Flex direction="column" gap="1" flexGrow="1" flexBasis="0" align="start">
<Code size="2" color="gray"> <Code size="2" color="gray">
{packageJson.version} {packageJson.version}
</Code> </Code>
+4 -3
View File
@@ -17,7 +17,7 @@ export interface Props {
integrated?: boolean; integrated?: boolean;
} }
const { integrated } = Astro.props; const { integrated = false } = Astro.props;
const toolsFiltered = TOOLS.filter((tool) => !('href' in tool)); const toolsFiltered = TOOLS.filter((tool) => !('href' in tool));
const { locale } = Astro.params; const { locale } = Astro.params;
const branchName = resolveBranchName(locale); const branchName = resolveBranchName(locale);
@@ -47,7 +47,7 @@ const strings = getStrings(locale);
height: 100%; height: 100%;
} }
.navbar[data-expanded='false'] .content { .navbar[data-expanded='false'][data-integrated='false'] .content {
background-color: var(--black-a8); background-color: var(--black-a8);
backdrop-filter: blur(4rem); backdrop-filter: blur(4rem);
box-shadow: var(--shadow-3); box-shadow: var(--shadow-3);
@@ -87,6 +87,7 @@ const strings = getStrings(locale);
direction="column" direction="column"
align="center" align="center"
data-expanded="false" data-expanded="false"
data-integrated={`${integrated}`}
className="navbar" className="navbar"
width="100%" width="100%"
top="0" top="0"
@@ -232,7 +233,7 @@ const strings = getStrings(locale);
<Box <Box
style={{ style={{
borderRadius: Var('radius-2'), borderRadius: Var('radius-2'),
backgroundImage: `url(/assets/images/${tool.id}.webp)`, backgroundImage: `url(/assets/images/tools/${tool.id}.webp)`,
backgroundSize: '200%', backgroundSize: '200%',
backgroundPosition: 'right center', backgroundPosition: 'right center',
}} }}
+4 -26
View File
@@ -6,42 +6,20 @@ const PUBLIC_GOOGLE_TAG_MANAGER_ID = assertSecret(
); );
--- ---
{/* google analytics */}
<script <script
is:inline is:inline
async async
type="text/partytown"
src={`https://www.googletagmanager.com/gtag/js?id=${PUBLIC_GOOGLE_TAG_MANAGER_ID}`} src={`https://www.googletagmanager.com/gtag/js?id=${PUBLIC_GOOGLE_TAG_MANAGER_ID}`}
></script> ></script>
<script
is:inline <script is:inline define:vars={{ PUBLIC_GOOGLE_TAG_MANAGER_ID }}>
define:vars={{ PUBLIC_GOOGLE_TAG_MANAGER_ID }}
type="text/partytown"
>
window.dataLayer = window.dataLayer || []; window.dataLayer = window.dataLayer || [];
function gtag() { function gtag() {
dataLayer.push(arguments); dataLayer.push(arguments);
} }
gtag('js', new Date()); gtag('js', new Date());
gtag('config', PUBLIC_GOOGLE_TAG_MANAGER_ID); gtag('config', PUBLIC_GOOGLE_TAG_MANAGER_ID);
</script> </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 <img
alt={unwrap(tank.name)} alt={unwrap(tank.name)}
src={asset(`icons/tanks/blitzkit/${tank.id}.webp`)} src={asset(`icons/tanks/big/${tank.id}.webp`)}
className="image" className="image"
draggable={false} draggable={false}
/> />
@@ -27,6 +27,7 @@ export function TankSearchCard({ tank, onSelect }: TankSearchCardProps) {
const gun = turret.guns.at(-1)!; const gun = turret.guns.at(-1)!;
const shell0 = gun.gun_type!.value.base.shells[0]; const shell0 = gun.gun_type!.value.base.shells[0];
const shell1 = gun.gun_type!.value.base.shells[1]; 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 tracks = tank.tracks.at(-1)!;
const engine = tank.engines.at(-1)!; const engine = tank.engines.at(-1)!;
const tankModelDefinition = modelDefinitions.models[tank.id]; const tankModelDefinition = modelDefinitions.models[tank.id];
@@ -57,6 +58,8 @@ export function TankSearchCard({ tank, onSelect }: TankSearchCardProps) {
return shell0.penetration.near.toFixed(0); return shell0.penetration.near.toFixed(0);
case 'fire.premiumPenetration': case 'fire.premiumPenetration':
return shell1 ? shell1.penetration.near.toFixed(0) : '--'; return shell1 ? shell1.penetration.near.toFixed(0) : '--';
case 'fire.tertiaryPenetration':
return shell1 ? shell2.penetration.near.toFixed(0) : '--';
case 'fire.shellVelocity': case 'fire.shellVelocity':
return shell0.velocity.toFixed(0); return shell0.velocity.toFixed(0);
case 'fire.shellCapacity': case 'fire.shellCapacity':
@@ -84,6 +84,7 @@ function ShellFilter({ index, premium }: { index: number; premium?: boolean }) {
return ( return (
<IconButton <IconButton
key={shellType}
value={`${shellType}`} value={`${shellType}`}
radius="none" radius="none"
variant={selected ? 'solid' : 'soft'} variant={selected ? 'solid' : 'soft'}
@@ -21,27 +21,17 @@ export function SearchBar({ topResult, skeleton, onSelect }: SearchBarProps) {
const input = useRef<HTMLInputElement>(null); const input = useRef<HTMLInputElement>(null);
const performSearch = useCallback( const performSearch = useCallback(
debounce(() => { debounce(() => {
// tankFiltersStore.setState({ searching: false });
$tankFilters.setKey('searching', false); $tankFilters.setKey('searching', false);
if (!input.current) return; if (!input.current) return;
const sanitized = input.current.value.trim(); const sanitized = input.current.value.trim();
// tankFiltersStore.setState({ $tankFilters.setKey('search', sanitized.length === 0 ? null : sanitized);
// search: sanitized.length === 0 ? undefined : sanitized,
// });
$tankFilters.setKey(
'search',
sanitized.length === 0 ? undefined : sanitized,
);
}, 500), }, 500),
[], [],
); );
const handleChange = useCallback(() => { const handleChange = useCallback(() => {
// if (!tankFiltersStore.getState().searching) {
// tankFiltersStore.setState({ searching: true });
// }
if (!tankFilters.searching) { if (!tankFilters.searching) {
$tankFilters.setKey('searching', true); $tankFilters.setKey('searching', true);
} }
@@ -69,7 +59,7 @@ export function SearchBar({ topResult, skeleton, onSelect }: SearchBarProps) {
<TextField.Root <TextField.Root
variant="classic" variant="classic"
disabled={skeleton} disabled={skeleton}
defaultValue={tankFilters.search} defaultValue={tankFilters.search ?? undefined}
style={{ flex: 1 }} style={{ flex: 1 }}
ref={input} ref={input}
placeholder={strings.website.common.tank_search.search_bar_hint} placeholder={strings.website.common.tank_search.search_bar_hint}
@@ -63,6 +63,7 @@ export function Sort() {
<Item by="fire.caliber" /> <Item by="fire.caliber" />
<Item by="fire.standardPenetration" /> <Item by="fire.standardPenetration" />
<Item by="fire.premiumPenetration" /> <Item by="fire.premiumPenetration" />
<Item by="fire.tertiaryPenetration" />
<Item by="fire.damage" /> <Item by="fire.damage" />
<Item by="fire.moduleDamage" /> <Item by="fire.moduleDamage" />
<Item by="fire.shellVelocity" /> <Item by="fire.shellVelocity" />
@@ -60,7 +60,7 @@ export const TankSearch = memo<TankSearchProps>(
const tankFilters = useStore($tankFilters); const tankFilters = useStore($tankFilters);
const tankopediaSort = useStore($tankopediaSort); const tankopediaSort = useStore($tankopediaSort);
const tanksFiltered = useMemo(() => { const tanksFiltered = useMemo(() => {
if (tankFilters.search === undefined) { if (tankFilters.search === null) {
const filtered = awaitedTanksDefinitionsArray.filter((tank) => const filtered = awaitedTanksDefinitionsArray.filter((tank) =>
filterTank(tankFilters, tank), filterTank(tankFilters, tank),
); );
@@ -238,6 +238,16 @@ export const TankSearch = memo<TankSearchProps>(
); );
break; 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': case 'fire.damage':
sorted = filtered.sort( sorted = filtered.sort(
(a, b) => (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 { useLocale } from '../../../../../../hooks/useLocale';
import { useTankModelDefinition } from '../../../../../../hooks/useTankModelDefinition'; import { useTankModelDefinition } from '../../../../../../hooks/useTankModelDefinition';
import { Duel } from '../../../../../../stores/duel'; import { Duel } from '../../../../../../stores/duel';
import { AssaultRangesVisualizer } from './AssaultRangesVisualizer';
import { Info } from './Info'; import { Info } from './Info';
import { InfoWithDelta } from './InfoWithDelta'; import { InfoWithDelta } from './InfoWithDelta';
import { RicochetVisualizer } from './RicochetVisualizer'; import { RicochetVisualizer } from './RicochetVisualizer';
@@ -112,6 +113,20 @@ export function Firepower({ stats }: StatsAcceptorProps) {
{strings.common.gun_types[gun.gun_type!.$case]} {strings.common.gun_types[gun.gun_type!.$case]}
</Info> </Info>
<InfoWithDelta stats={stats} decimals={0} value="dpm" /> <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' && ( {gun.gun_type!.$case === 'auto_reloader' && (
<InfoWithDelta stats={stats} decimals={0} indent value="dpmEffective" /> <InfoWithDelta stats={stats} decimals={0} indent value="dpmEffective" />
)} )}
@@ -172,7 +187,6 @@ export function Firepower({ stats }: StatsAcceptorProps) {
)} )}
{/* <ReloadVisualizer stats={stats} /> */} {/* <ReloadVisualizer stats={stats} /> */}
<InfoWithDelta stats={stats} decimals={0} value="penetration" />
{typeof shell.penetration !== 'number' && ( {typeof shell.penetration !== 'number' && (
<> <>
<InfoWithDelta <InfoWithDelta
@@ -240,11 +254,6 @@ export function Firepower({ stats }: StatsAcceptorProps) {
</Flex> </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) && ( {isExplosive(shell.type) && (
<InfoWithDelta <InfoWithDelta
stats={stats} stats={stats}
@@ -65,6 +65,14 @@ export function InfoWithDelta({
: (stats[props.value] as number); : (stats[props.value] as number);
const delta = useDelta(uhWhatDoICallThisVariable); const delta = useDelta(uhWhatDoICallThisVariable);
const protagonistTank = Duel.use((state) => state.protagonist.tank); 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 others = useMemo(() => {
const defaultSkills = createDefaultSkills(skillDefinitions); const defaultSkills = createDefaultSkills(skillDefinitions);
@@ -86,6 +94,10 @@ export function InfoWithDelta({
provisionDefinitions, provisionDefinitions,
); );
if (member.gun.gun_type!.value.base.shells[shellIndex] === undefined) {
return undefined;
}
return tankCharacteristics( return tankCharacteristics(
{ {
applyDynamicArmor: false, applyDynamicArmor: false,
@@ -97,8 +109,8 @@ export function InfoWithDelta({
provisions: member.provisions, provisions: member.provisions,
engine: member.engine, engine: member.engine,
gun: member.gun, gun: member.gun,
equipmentMatrix: member.equipmentMatrix, equipmentMatrix: equipmentMatrix,
shell: member.shell, shell: member.gun.gun_type!.value.base.shells[shellIndex],
stockEngine: tank.engines[0], stockEngine: tank.engines[0],
stockGun: tank.turrets[0].guns[0], stockGun: tank.turrets[0].guns[0],
stockTrack: tank.tracks[0], stockTrack: tank.tracks[0],
@@ -106,6 +118,7 @@ export function InfoWithDelta({
tank, tank,
track: member.track, track: member.track,
turret: member.turret, turret: member.turret,
assaultDistance: member.assaultDistance,
}, },
{ {
equipmentDefinitions, equipmentDefinitions,
@@ -115,14 +128,16 @@ export function InfoWithDelta({
); );
}) })
.filter((tank) => { .filter((tank) => {
if (tank === undefined) return false;
const othersValue = const othersValue =
typeof props.value === 'function' typeof props.value === 'function'
? props.value(tank)! ? props.value(tank)!
: (tank[props.value] as number); : (tank[props.value] as number);
return othersValue !== undefined; return othersValue !== undefined;
}); }) as TankCharacteristics[];
}, [relativeAgainst]); }, [relativeAgainst, shellIndex, equipmentMatrix]);
const betterTanks = others.filter((tank) => { const betterTanks = others.filter((tank) => {
const othersValue = const othersValue =
typeof props.value === 'function' typeof props.value === 'function'
@@ -24,6 +24,9 @@ export function Characteristics() {
const provisions = Duel.use((state) => state.protagonist.provisions); const provisions = Duel.use((state) => state.protagonist.provisions);
const consumables = Duel.use((state) => state.protagonist.consumables); const consumables = Duel.use((state) => state.protagonist.consumables);
const camouflage = Duel.use((state) => state.protagonist.camouflage); const camouflage = Duel.use((state) => state.protagonist.camouflage);
const assaultDistance = Duel.use(
(state) => state.protagonist.assaultDistance,
);
const equipmentMatrix = Duel.use( const equipmentMatrix = Duel.use(
(state) => state.protagonist.equipmentMatrix, (state) => state.protagonist.equipmentMatrix,
); );
@@ -58,6 +61,7 @@ export function Characteristics() {
applyReactiveArmor: false, applyReactiveArmor: false,
applyDynamicArmor: false, applyDynamicArmor: false,
applySpallLiner: false, applySpallLiner: false,
assaultDistance,
}, },
{ {
tankModelDefinition, 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 { TankType } from '@blitzkit/core';
import { Box, Heading } from '@radix-ui/themes'; import { Box, Heading } from '@radix-ui/themes';
import { Var } from '../../../../../core/radix/var'; import { Var } from '../../../../../../core/radix/var';
import { useLocale } from '../../../../../hooks/useLocale'; import { useLocale } from '../../../../../../hooks/useLocale';
import { Duel } from '../../../../../stores/duel'; import { Duel } from '../../../../../../stores/duel';
import type { RadixColor } from '../../../../../stores/embedState'; import type { RadixColor } from '../../../../../../stores/embedState';
import { TankopediaEphemeral } from '../../../../../stores/tankopediaEphemeral'; import { TankopediaEphemeral } from '../../../../../../stores/tankopediaEphemeral';
import { Tracker } from './components/Tracker';
interface TitleProps { interface TitleProps {
outline?: boolean; foreground?: boolean;
} }
export const NATION_COLORS: Record< 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 { unwrap } = useLocale();
const protagonist = Duel.use((state) => state.protagonist.tank); const protagonist = Duel.use((state) => state.protagonist.tank);
const disturbed = TankopediaEphemeral.use((state) => state.disturbed); const disturbed = TankopediaEphemeral.use((state) => state.disturbed);
@@ -81,6 +82,11 @@ export function Title({ outline }: TitleProps) {
: `${nationColors.text}-12`, : `${nationColors.text}-12`,
); );
const name = unwrap(protagonist.name); const name = unwrap(protagonist.name);
const fontSize = revealed
? disturbed
? '2rem'
: `min(65vh, ${125 / name.length}vw)`
: `min(48vh, ${75 / name.length}vw)`;
return ( return (
<Box <Box
@@ -97,17 +103,10 @@ export function Title({ outline }: TitleProps) {
fontWeight: 900, fontWeight: 900,
userSelect: 'none', userSelect: 'none',
pointerEvents: 'none', pointerEvents: 'none',
fontSize: revealed fontSize,
? disturbed
? '2rem'
: `min(65vh, ${125 / name.length}vw)`
: `min(48vh, ${75 / name.length}vw)`,
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
color: outline ? Var(`${nationColors.tint}-a2`) : color, color: foreground ? Var('gray-a10') : color,
opacity: outline && disturbed ? 0 : 1, opacity: foreground && disturbed ? 0 : 1,
WebkitTextStroke: outline
? `${revealed ? (disturbed ? '1px' : 'min(2px, 0.2vw)') : 0} ${color}`
: undefined,
letterSpacing: disturbed || !revealed ? 0 : '-0.03em', letterSpacing: disturbed || !revealed ? 0 : '-0.03em',
transition: ` transition: `
letter-spacing 1.5s ${disturbed ? '' : 'cubic-bezier(.81,-2,.68,1)'}, letter-spacing 1.5s ${disturbed ? '' : 'cubic-bezier(.81,-2,.68,1)'},
@@ -120,6 +119,8 @@ export function Title({ outline }: TitleProps) {
> >
{name} {name}
</Heading> </Heading>
{!revealed && <Tracker fontSize={fontSize} />}
</Box> </Box>
); );
} }
@@ -73,6 +73,9 @@ export function SceneProps() {
const crewSkills = TankopediaEphemeral.use((state) => state.skills); const crewSkills = TankopediaEphemeral.use((state) => state.skills);
const consumables = Duel.use((state) => state.protagonist.consumables); const consumables = Duel.use((state) => state.protagonist.consumables);
const provisions = Duel.use((state) => state.protagonist.provisions); const provisions = Duel.use((state) => state.protagonist.provisions);
const assaultDistance = Duel.use(
(state) => state.protagonist.assaultDistance,
);
const equipmentMatrix = Duel.use( const equipmentMatrix = Duel.use(
(state) => state.protagonist.equipmentMatrix, (state) => state.protagonist.equipmentMatrix,
); );
@@ -96,6 +99,7 @@ export function SceneProps() {
stockTurret: tank.turrets[0], stockTurret: tank.turrets[0],
stockTrack: tank.tracks[0], stockTrack: tank.tracks[0],
track, track,
assaultDistance,
}, },
{ equipmentDefinitions, provisionDefinitions, tankModelDefinition }, { equipmentDefinitions, provisionDefinitions, tankModelDefinition },
); );
@@ -1,3 +1,5 @@
precision mediump float;
#define USE_FOG #define USE_FOG
#include <fog_pars_fragment> #include <fog_pars_fragment>
@@ -1,3 +1,5 @@
precision mediump float;
#define USE_FOG #define USE_FOG
#include <fog_pars_vertex> #include <fog_pars_vertex>
@@ -39,11 +39,11 @@ interface TankSandboxProps {
naked?: boolean; naked?: boolean;
} }
export const forNear0 = 20; export const forNear0 = 30;
export const fogFar0 = 25; export const fogFar0 = 50;
export const forNear1 = 0; export const forNear1 = 0;
export const fogFar1 = 0; export const fogFar1 = 0;
export const fogAnimationTime = 1.5; export const fogAnimationTime = 2.5;
export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>( export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
({ thicknessRange, naked }, ref) => { ({ thicknessRange, naked }, ref) => {
@@ -74,7 +74,7 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
const interval = setInterval(() => { const interval = setInterval(() => {
const t = (Date.now() - t0) / 1000; const t = (Date.now() - t0) / 1000;
const x = t / fogAnimationTime; 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 near = forNear0 + (forNear1 - forNear0) * y;
const far = fogFar0 + (fogFar1 - fogFar0) * y; const far = fogFar0 + (fogFar1 - fogFar0) * y;
@@ -103,7 +103,7 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
transitionDuration: '1s', transitionDuration: '1s',
background: isFullScreen ? 'black' : undefined, background: isFullScreen ? 'black' : undefined,
}} }}
height={disturbed && !isFullScreen ? 'calc(100svh - 6rem)' : '100vh'} height={!isFullScreen ? 'calc(100svh - 6rem)' : '100vh'}
maxHeight={isFullScreen ? undefined : '60rem'} maxHeight={isFullScreen ? undefined : '60rem'}
maxWidth={isFullScreen ? undefined : '120rem'} maxWidth={isFullScreen ? undefined : '120rem'}
flexGrow="1" flexGrow="1"
@@ -136,7 +136,7 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
height: '100%', height: '100%',
transitionDuration: '2s', transitionDuration: '2s',
opacity: revealed ? 1 : 0, opacity: revealed ? 1 : 0,
filter: disturbed ? undefined : `drop-shadow(0 0 2rem black)`, filter: disturbed ? undefined : `drop-shadow(0 0 1rem black)`,
}} }}
> >
<Suspense> <Suspense>
@@ -153,7 +153,7 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
</Box> </Box>
</Box> </Box>
<Title outline /> <Title foreground />
</Flex> </Flex>
</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 type { EquipmentMatrix } from '../../stores/duel';
import { awaitableEquipmentDefinitions } from '../awaitables/equipmentDefinitions';
export async function hasEquipment( export async function hasEquipment(
id: number, id: number,
equipmentPreset: string, equipmentPreset: string,
equipmentMatrix: EquipmentMatrix, equipmentMatrix: EquipmentMatrix,
) { ) {
const equipmentDefinitions = await fetchEquipmentDefinitions(); const equipmentDefinitions = await awaitableEquipmentDefinitions;
const preset = equipmentDefinitions.presets[equipmentPreset]; const preset = equipmentDefinitions.presets[equipmentPreset];
return preset.slots.some((slot, index) => { return preset.slots.some((slot, index) => {
const row = Math.floor(index / 3); const row = Math.floor(index / 3);
@@ -46,6 +46,7 @@ export function tankCharacteristics(
applyReactiveArmor, applyReactiveArmor,
applyDynamicArmor, applyDynamicArmor,
applySpallLiner, applySpallLiner,
assaultDistance,
}: { }: {
tank: TankDefinition; tank: TankDefinition;
turret: TurretDefinition; turret: TurretDefinition;
@@ -65,6 +66,7 @@ export function tankCharacteristics(
applyReactiveArmor: boolean; applyReactiveArmor: boolean;
applyDynamicArmor: boolean; applyDynamicArmor: boolean;
applySpallLiner: boolean; applySpallLiner: boolean;
assaultDistance: number;
}, },
{ {
tankModelDefinition, tankModelDefinition,
@@ -157,7 +159,15 @@ export function tankCharacteristics(
[applyDynamicArmor, -0.1], [applyDynamicArmor, -0.1],
[applySpallLiner && shell.type === ShellType.HE, -0.2], [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 = const reloadCoefficient =
(coefficient([hasGunRammer, -0.05]) * (coefficient([hasGunRammer, -0.05]) *
coefficient([true, degressiveStat(loaderMastery)])) / coefficient([true, degressiveStat(loaderMastery)])) /
@@ -326,10 +336,13 @@ export function tankCharacteristics(
stockTurret.weight + stockTurret.weight +
stockGun.gun_type!.value.base.weight; stockGun.gun_type!.value.base.weight;
const resolvedEnginePower = engine.power * enginePowerCoefficient; const resolvedEnginePower = engine.power * enginePowerCoefficient;
const damageCoefficientWithoutAssault = armorDamageCoefficient;
const damageCoefficient =
damageCoefficientWithoutAssault * assaultDamageCoefficient;
const dpm = resolveDpm( const dpm = resolveDpm(
gun, gun,
shell, shell,
armorDamageCoefficient, damageCoefficient,
reloadCoefficient, reloadCoefficient,
intraClipCoefficient, intraClipCoefficient,
); );
@@ -352,7 +365,9 @@ export function tankCharacteristics(
return current; return current;
}, null)!.index }, null)!.index
: undefined; : undefined;
const damage = shell.armor_damage * armorDamageCoefficient; const damageWithoutAssault =
shell.armor_damage * damageCoefficientWithoutAssault;
const damage = shell.armor_damage * damageCoefficient;
const dpmEffective = const dpmEffective =
gun.gun_type!.$case === 'auto_reloader' gun.gun_type!.$case === 'auto_reloader'
? gun.gun_type!.value.extension.shell_reloads[0] > ? gun.gun_type!.value.extension.shell_reloads[0] >
@@ -533,6 +548,7 @@ export function tankCharacteristics(
caliber, caliber,
penetration, penetration,
damage, damage,
damageWithoutAssault,
clipDamage, clipDamage,
moduleDamage, moduleDamage,
explosionRadius, explosionRadius,
@@ -40,5 +40,7 @@ export function tankToDuelMember(
yaw, yaw,
pitch, pitch,
turret, turret,
assaultDistance:
(gun.gun_type!.value.base.assault_ranges?.ranges[0].distance ?? 0) / 2,
} satisfies DuelMember; } satisfies DuelMember;
} }
@@ -85,6 +85,7 @@ function Content() {
member.key !== thisMember.key && member.key !== thisMember.key &&
member.provisions.includes(101), member.provisions.includes(101),
), ),
assaultDistance: thisMember.assaultDistance,
}, },
{ {
equipmentDefinitions: equipmentDefinitions, equipmentDefinitions: equipmentDefinitions,
@@ -60,6 +60,7 @@ const url = import.meta.env.SITE + Astro.url.pathname;
const icon = asset(`icons/tanks/big/${id}.webp`); const icon = asset(`icons/tanks/big/${id}.webp`);
const turret = tank.turrets.at(-1)!; const turret = tank.turrets.at(-1)!;
const gun = turret.guns.at(-1)!; const gun = turret.guns.at(-1)!;
const shell = gun.gun_type!.value.base.shells[0];
const characteristics = tankCharacteristics( const characteristics = tankCharacteristics(
{ {
tank, tank,
@@ -74,12 +75,13 @@ const characteristics = tankCharacteristics(
turret, turret,
gun, gun,
provisions: createDefaultProvisions(tank, gun, provisionDefinitions), provisions: createDefaultProvisions(tank, gun, provisionDefinitions),
shell: gun.gun_type!.value.base.shells[0], shell,
stockEngine: tank.engines[0], stockEngine: tank.engines[0],
stockGun: tank.turrets[0].guns[0], stockGun: tank.turrets[0].guns[0],
stockTurret: tank.turrets[0], stockTurret: tank.turrets[0],
stockTrack: tank.tracks[0], stockTrack: tank.tracks[0],
track: tank.tracks.at(-1)!, track: tank.tracks.at(-1)!,
assaultDistance: 0,
}, },
{ equipmentDefinitions, provisionDefinitions, tankModelDefinition }, { equipmentDefinitions, provisionDefinitions, tankModelDefinition },
); );
+4 -1
View File
@@ -1,9 +1,12 @@
import type { APIContext } from 'astro'; import type { APIContext } from 'astro';
const ALLOW_ROBOTS = import.meta.env.ALLOW_ROBOTS;
export async function GET({ request }: APIContext) { export async function GET({ request }: APIContext) {
const flags: Record<string, string | null> = { const flags: Record<string, string | null> = {
'User-agent': '*', 'User-agent': '*',
Allow: '/', Allow: ALLOW_ROBOTS ? '/' : null,
Disallow: ALLOW_ROBOTS ? null : '/',
space: null, space: null,
Sitemap: `${new URL(request.url).origin}/sitemap-index.xml`, Sitemap: `${new URL(request.url).origin}/sitemap-index.xml`,
@@ -39,6 +39,7 @@ export interface DuelMember {
provisions: number[]; provisions: number[];
camouflage: boolean; camouflage: boolean;
cooldownBooster: number; cooldownBooster: number;
assaultDistance: number;
} }
export interface DuelStore { export interface DuelStore {
+2 -2
View File
@@ -18,7 +18,7 @@ export interface TankFilters {
classes: TankClass[]; classes: TankClass[];
types: TankType[]; types: TankType[];
testing: 'include' | 'exclude' | 'only'; testing: 'include' | 'exclude' | 'only';
search?: string; search: string | null;
searching: boolean; searching: boolean;
gunType: CaseType<GunDefinition>[]; gunType: CaseType<GunDefinition>[];
shells: [ShellType | null, ShellType | null, ShellType | null]; shells: [ShellType | null, ShellType | null, ShellType | null];
@@ -30,7 +30,7 @@ export const initialTankFilters: TankFilters = {
classes: [], classes: [],
types: [], types: [],
testing: 'include', testing: 'include',
search: undefined, search: null,
searching: false, searching: false,
gunType: [], gunType: [],
shells: [null, null, null], shells: [null, null, null],
@@ -14,6 +14,7 @@ export const SORT_UNITS: Record<TankopediaSortBy, string | undefined> = {
'fire.gunDepression': '°', 'fire.gunDepression': '°',
'fire.gunElevation': '°', 'fire.gunElevation': '°',
'fire.premiumPenetration': 'mm', 'fire.premiumPenetration': 'mm',
'fire.tertiaryPenetration': 'mm',
'fire.reload': 's', 'fire.reload': 's',
'fire.shellVelocity': 'm/s', 'fire.shellVelocity': 'm/s',
'fire.shellRange': 'm', 'fire.shellRange': 'm',