Compare commits

..
12 Commits
56 changed files with 603 additions and 633 deletions

No files matched your search

+47
View File
@@ -0,0 +1,47 @@
# BlitzKit Credits
BlitzKit is a one man band **TrèsAbhi** but I am far from solo. Helpers, in the order of contribution over time:
### S0AP
BlitzKit Discord bot verification, testing, advertisement, and the initial encouragement to create BlitzKit.
### Prince_NA
BlitzKit Discord bot and website testing and general advise.
### Vovko
BlitzKit Discord bot and website rendering pipeline.
### Maddox
Game file decompression.
### Pyogenics
Game 3D model parsing.
### Aeson
Game file layout, general advise, advertisement, and Tankopedia pre-release testing.
### Minitelrose, ChickenMan7777
3-caliber rule edge cases.
### HisRoyalFatness, Synx
Embed UX.
### [OPTML] clan, \_34, xkdkw, Droodles Blitz
Tankopedia pre-release testing.
### GonnaHetzMe
Redacted by request.
### Mark Greene
GitHub contributor on bug fixes and feature additions.
+3 -3
View File
@@ -1,14 +1,14 @@
# BlitzKit 1.29.10 # BlitzKit 1.29.10
Thank you to [HitJack](https://github.com/Hitjack007) for your contributions this update. Thank you to [Mark Greene](https://github.com/Hitjack007) for your contributions this update.
## Features ## Features
- Combined game mode passive and active abilities into a single dropdown - Combined game mode passive and active abilities into a single dropdown
- Added tier filters to the Guess game [HitJack] - Added tier filters to the Guess game [Mark Greene]
- Changing tiers will break your streak - Changing tiers will break your streak
## Fixes ## Fixes
- module damage is now properly shown when sorting with it in the tank search [HitJack] - module damage is now properly shown when sorting with it in the tank search [Mark Greene]
- Duplicate abilities in the tank filters are now removed - Duplicate abilities in the tank filters are now removed
+2 -2
View File
@@ -1,6 +1,6 @@
# BlitzKit 1.29.8 # BlitzKit 1.29.8
Thank you to [HitJack](https://github.com/Hitjack007) for your contributions this update. Thank you to [Mark Greene](https://github.com/Hitjack007) for your contributions this update.
## Features ## Features
@@ -15,5 +15,5 @@ Thank you to [HitJack](https://github.com/Hitjack007) for your contributions thi
## Fixes ## Fixes
- The Tankopedia now smoothly resizes when enlarging on interaction - The Tankopedia now smoothly resizes when enlarging on interaction
- Stock turrets now correctly mount the corresponding stock gun [HitJack] - Stock turrets now correctly mount the corresponding stock gun [Mark Greene]
- The consumable and provision buttons in Compare are no longer tiny - The consumable and provision buttons in Compare are no longer tiny
+3 -3
View File
@@ -1,14 +1,14 @@
# BlitzKit 1.29.9 # BlitzKit 1.29.9
Thank you to [HitJack](https://github.com/Hitjack007) for your contributions this update. Thank you to [Mark Greene](https://github.com/Hitjack007) for your contributions this update.
## Features ## Features
- Reworked tank search filters to be far more compact to make way for new filters - Reworked tank search filters to be far more compact to make way for new filters
- Added the ability to filter tanks by consumables [HitJack] - Added the ability to filter tanks by consumables [Mark Greene]
- Added the ability to filter tanks by provisions and game-mode abilities and powers - Added the ability to filter tanks by provisions and game-mode abilities and powers
## Fixes ## Fixes
- Consumable and provision icons in Compare are now properly sized - Consumable and provision icons in Compare are now properly sized
- Provisions now properly affect terrain resistances [HitJack] - Provisions now properly affect terrain resistances [Mark Greene]
+5 -5
View File
@@ -1,6 +1,6 @@
# BlitzKit 1.30.7 # BlitzKit 1.30.7
Thank you to [HitJack](https://github.com/Hitjack007) for your contributions this update. Thank you to [Mark Greene](https://github.com/Hitjack007) for your contributions this update.
## Features ## Features
@@ -8,13 +8,13 @@ Thank you to [HitJack](https://github.com/Hitjack007) for your contributions thi
## Fixes ## Fixes
- Clicking on the armor multiple times with HE no longer crashes BlitzKit [HitJack] - Clicking on the armor multiple times with HE no longer crashes BlitzKit [Mark Greene]
- Ricochets that later intersect with external modules no longer falsely claim a successful damaging penetration [HitJack] - Ricochets that later intersect with external modules no longer falsely claim a successful damaging penetration [Mark Greene]
- The lighting animation no longer stops prematurely when BlitzKit is unfocused - The lighting animation no longer stops prematurely when BlitzKit is unfocused
- The spot light angle has been increased to more evenly light up larger tanks - The spot light angle has been increased to more evenly light up larger tanks
- BlitzKit once again uses soft shadows, reducing the pixelated shadows - BlitzKit once again uses soft shadows, reducing the pixelated shadows
- Fixed the weird patterns that formed on the tracks of tanks under self-shadows - Fixed the weird patterns that formed on the tracks of tanks under self-shadows
- Increased the floor size and light decay to create the illusion that the floor is infinite - Increased the floor size and light decay to create the illusion that the floor is infinite
- Safari no longer crashes when unloading textures for unloaded tanks [HitJack] - Safari no longer crashes when unloading textures for unloaded tanks [Mark Greene]
- The gun depression transition between two constraints (like often behind tanks) now uses a gentler zoned and can never be 0 - The gun depression transition between two constraints (like often behind tanks) now uses a gentler zoned and can never be 0
- Armor plate names in Static Armor are now properly capitalized in English and translated to other languages [HitJack] - Armor plate names in Static Armor are now properly capitalized in English and translated to other languages [Mark Greene]
+34 -34
View File
@@ -1,75 +1,75 @@
# BlitzKit 1.32.0 # BlitzKit 1.32.0
Thank you to [HitJack](https://github.com/Hitjack007) for your contributions this update. Thank you to [Mark Greene](https://github.com/Hitjack007) for your contributions this update.
## Features ## Features
### Tankopedia & Armor ### Tankopedia & Armor
- Added a spall liner button to the Tankopedia beside the other equipment [HitJack] - Added a spall liner button to the Tankopedia beside the other equipment [Mark Greene]
- Listed track repair times in the Tankopedia [HitJack] - Listed track repair times in the Tankopedia [Mark Greene]
- The penetration chance is now displayed when penetration testing armor [HitJack] - The penetration chance is now displayed when penetration testing armor [Mark Greene]
- Added a new setting to disable automatically loading tanks in Tankopedia [HitJack] - Added a new setting to disable automatically loading tanks in Tankopedia [Mark Greene]
### Tank Performance & Mechanics ### Tank Performance & Mechanics
- Added Peekaboo DPM [HitJack] - Added Peekaboo DPM [Mark Greene]
- Your Peekaboo DPM is lower than your normal DPM if your reload is faster than 10s as you'll have to wait for the remaining 10s for you to get unspotted before you can poke and shoot - Your Peekaboo DPM is lower than your normal DPM if your reload is faster than 10s as you'll have to wait for the remaining 10s for you to get unspotted before you can poke and shoot
### Tank Filters ### Tank Filters
- The test and owned tank filters are now more intuitive as clicking on them now turns the clicked option on instead of off [HitJack] - The test and owned tank filters are now more intuitive as clicking on them now turns the clicked option on instead of off [Mark Greene]
### Game Compatibility ### Game Compatibility
- Added compatibility for tier XI tanks [HitJack] - Added compatibility for tier XI tanks [Mark Greene]
## Fixes ## Fixes
### Tankopedia & Armor ### Tankopedia & Armor
- The armor no longer disappears when you turn on Dynamic Armor System [HitJack] - The armor no longer disappears when you turn on Dynamic Armor System [Mark Greene]
- Camouflage values in Tankopedia are now more descriptive when shooting [HitJack] - Camouflage values in Tankopedia are now more descriptive when shooting [Mark Greene]
- The penetration chance is now displayed when penetration testing armor [HitJack] - The penetration chance is now displayed when penetration testing armor [Mark Greene]
- Listed track repair times in the Tankopedia [HitJack] - Listed track repair times in the Tankopedia [Mark Greene]
- Added a spall liner button to the Tankopedia beside the other equipment [HitJack] - Added a spall liner button to the Tankopedia beside the other equipment [Mark Greene]
- The flags in the Tankopedia tech tree are no longer offset to the top left [HitJack] - The flags in the Tankopedia tech tree are no longer offset to the top left [Mark Greene]
- The loading bar below the name of the tanks in Tankopedia is now sufficiently spaced below [HitJack] - The loading bar below the name of the tanks in Tankopedia is now sufficiently spaced below [Mark Greene]
- Removed the vertical sliver of pixels found to the right of the Equalizer toggle in Tankopedia - Removed the vertical sliver of pixels found to the right of the Equalizer toggle in Tankopedia
### Tank Search & Filters ### Tank Search & Filters
- The search bar no longer redirects to a seemingly random tank when clicking enter when searching [HitJack] - The search bar no longer redirects to a seemingly random tank when clicking enter when searching [Mark Greene]
- Tank search now properly handles the full names of tanks like the Super Pershing [HitJack] - Tank search now properly handles the full names of tanks like the Super Pershing [Mark Greene]
- The tank search now remains consistent in order even when an added filter does not add/remove tanks from the list [HitJack] - The tank search now remains consistent in order even when an added filter does not add/remove tanks from the list [Mark Greene]
- The test and owned tank filters are now more intuitive as clicking on them now turns the clicked option on instead of off [HitJack] - The test and owned tank filters are now more intuitive as clicking on them now turns the clicked option on instead of off [Mark Greene]
### Visuals & UI ### Visuals & UI
- Removed the Beta badge from the Equalizer button [HitJack] - Removed the Beta badge from the Equalizer button [Mark Greene]
- Updated the background image for the Equalizer button to the actual banner of the new game mode - Updated the background image for the Equalizer button to the actual banner of the new game mode
- The contact shadows on the ground no longer grow & darken when switching between tabs [HitJack] - The contact shadows on the ground no longer grow & darken when switching between tabs [Mark Greene]
- Additionally, the contact shadows stays visible at low angles [HitJack] - Additionally, the contact shadows stays visible at low angles [Mark Greene]
- The "Help Translate BlitzKit" text on the home page is now properly centered vertically - The "Help Translate BlitzKit" text on the home page is now properly centered vertically
- The footer now says "Branch" instead of "CDN" [HitJack] - The footer now says "Branch" instead of "CDN" [Mark Greene]
- The game version in the footer now displays a shorthand version instead of the entire version number [HitJack] - The game version in the footer now displays a shorthand version instead of the entire version number [Mark Greene]
- The reload visualizer now displays just 1 decimal place [HitJack] - The reload visualizer now displays just 1 decimal place [Mark Greene]
- Plot controls for the Static armor tab no longer pokes in from the right on non-Static armor tabs in some languages [HitJack] - Plot controls for the Static armor tab no longer pokes in from the right on non-Static armor tabs in some languages [Mark Greene]
- Mixer's controllers now scale better on smaller screens [HitJack] - Mixer's controllers now scale better on smaller screens [Mark Greene]
- The list of available tanks in the Playlist is no longer excessively wide [HitJack] - The list of available tanks in the Playlist is no longer excessively wide [Mark Greene]
### Tank Mechanics & Models ### Tank Mechanics & Models
- The gun depression visualizer now properly handles the inherent tilt of tanks like the Minotauro [HitJack] - The gun depression visualizer now properly handles the inherent tilt of tanks like the Minotauro [Mark Greene]
- When moving the tracks of tanks, the textures no longer move at different rates [HitJack] - When moving the tracks of tanks, the textures no longer move at different rates [Mark Greene]
### Localization ### Localization
- Users with a preference on a language are once again redirected to the correct localized pages [HitJack] - Users with a preference on a language are once again redirected to the correct localized pages [Mark Greene]
### Session & Game Compatibility ### Session & Game Compatibility
- BlitzKit Session now properly handles tanks removed from the game [HitJack] - BlitzKit Session now properly handles tanks removed from the game [Mark Greene]
## Technical Fixes ## Technical Fixes
@@ -78,7 +78,7 @@ Thank you to [HitJack](https://github.com/Hitjack007) for your contributions thi
- This was the last bug standing in the way of BlitzKit Previews - This was the last bug standing in the way of BlitzKit Previews
- Welcome back, BlitzKit Previews! - Welcome back, BlitzKit Previews!
- Removed some dead code from the BlitzKit model mangler - Removed some dead code from the BlitzKit model mangler
- The player discovery loop now persists for longer than 1 iteration [HitJack] - The player discovery loop now persists for longer than 1 iteration [Mark Greene]
- Bun is now forced to run scripts with the `--bun` flag - Bun is now forced to run scripts with the `--bun` flag
## Technical Features ## Technical Features
@@ -94,5 +94,5 @@ Thank you to [HitJack](https://github.com/Hitjack007) for your contributions thi
- This was the last bug standing in the way of BlitzKit Previews - This was the last bug standing in the way of BlitzKit Previews
- Welcome back, BlitzKit Previews! - Welcome back, BlitzKit Previews!
- Removed some dead code from the BlitzKit model mangler - Removed some dead code from the BlitzKit model mangler
- The player discovery loop now persists for longer than 1 iteration [HitJack] - The player discovery loop now persists for longer than 1 iteration [Mark Greene]
- Bun is now forced to run scripts with the `--bun` flag - Bun is now forced to run scripts with the `--bun` flag
+2 -1
View File
@@ -10,7 +10,8 @@
"build-protos": "cd packages/scripts && bun run build-protos", "build-protos": "cd packages/scripts && bun run build-protos",
"record-performance": "cd packages/scripts && bun run record-performance", "record-performance": "cd packages/scripts && bun run record-performance",
"download-client": "cd packages/scripts && bun run download-client", "download-client": "cd packages/scripts && bun run download-client",
"clean-astro": "rm -rf packages/website/.astro packages/website/node_modules/.astro packages/website/node_modules/.vite" "clean-astro": "rm -rf packages/website/.astro packages/website/node_modules/.astro packages/website/node_modules/.vite",
"vfs": "cd packages/scripts && bun run vfs"
}, },
"type": "module", "type": "module",
"workspaces": [ "workspaces": [
@@ -246,6 +246,27 @@ export abstract class ServerBlitzKitAPI0 extends BlitzKitAPI {
return this; return this;
} }
protected getTankFilterIds(
name: string,
definitionKey: string,
filter: "include" | "exclude",
) {
const keys = name.trim().split(/\s+/).filter(Boolean);
return keys.flatMap((key) => {
const id = this.tankStringIdMap[key];
if (id === undefined) {
console.warn(
`Skipping unavailable tank "${key}" in ${filter} vehicle filter for "${definitionKey}"`,
);
return [];
}
return [id];
});
}
protected getString(name: string) { protected getString(name: string) {
const collection: Record<string, string> = { const collection: Record<string, string> = {
[locales.default]: this.stringsI18n[locales.default][name], [locales.default]: this.stringsI18n[locales.default][name],
@@ -595,9 +595,25 @@ export abstract class ServerBlitzKitAPI1 extends ServerBlitzKitAPI0 {
for (const shellKey in gunListEntry.shots) { for (const shellKey in gunListEntry.shots) {
const gunShellEntry = gunListEntry.shots[shellKey]; const gunShellEntry = gunListEntry.shots[shellKey];
const shell = shellList.root[shellKey]; const shell = shellList.root[shellKey];
// Newer client data stores shot properties on the shell rather
// than repeating them under every gun's <shots> entry.
const speed =
typeof gunShellEntry === "string"
? shell.speed
: gunShellEntry.speed;
const maxDistance =
typeof gunShellEntry === "string"
? shell.maxDistance
: gunShellEntry.maxDistance;
const piercingPower =
typeof gunShellEntry === "string"
? shell.piercingPower
: gunShellEntry.piercingPower;
const shellId = toUniqueId(nation, shell.id); const shellId = toUniqueId(nation, shell.id);
const shellName = this.getString(shell.userString); const shellName = this.getString(shell.userString);
const penetrationRaw = gunShellEntry.piercingPower const penetrationRaw = piercingPower
.split(" ") .split(" ")
.filter((penetrationString) => penetrationString !== "") .filter((penetrationString) => penetrationString !== "")
.map(Number); .map(Number);
@@ -607,7 +623,7 @@ export abstract class ServerBlitzKitAPI1 extends ServerBlitzKitAPI0 {
].shells.push({ ].shells.push({
id: shellId, id: shellId,
name: shellName, name: shellName,
velocity: gunShellEntry.speed, velocity: speed,
armor_damage: shell.damage.armor, armor_damage: shell.damage.armor,
module_damage: shell.damage.devices, module_damage: shell.damage.devices,
caliber: shell.caliber, caliber: shell.caliber,
@@ -623,7 +639,7 @@ export abstract class ServerBlitzKitAPI1 extends ServerBlitzKitAPI0 {
near: penetrationRaw[0], near: penetrationRaw[0],
far: penetrationRaw[1], far: penetrationRaw[1],
}, },
range: gunShellEntry.maxDistance, range: maxDistance,
}); });
} }
@@ -10,6 +10,8 @@ export abstract class ServerBlitzKitAPI2 extends ServerBlitzKitAPI1 {
for (const camoKey in this.camouflagesXml!.root.camouflages) { for (const camoKey in this.camouflagesXml!.root.camouflages) {
const camo = this.camouflagesXml!.root.camouflages[camoKey]; const camo = this.camouflagesXml!.root.camouflages[camoKey];
if (!(camoKey in this.camouflagesYaml!)) continue;
const yamlEntry = this.camouflagesYaml![camoKey]; const yamlEntry = this.camouflagesYaml![camoKey];
const fullName = yamlEntry.userString const fullName = yamlEntry.userString
? this.getString(yamlEntry.userString) ? this.getString(yamlEntry.userString)
@@ -4,7 +4,13 @@ import {
ChassisDefinitionsList, ChassisDefinitionsList,
Crew, Crew,
GunDefinitionsList, GunDefinitionsList,
I_HAT,
J_HAT,
K_HAT,
ModelDefinitions, ModelDefinitions,
parseHierarchies,
Sc2ReadStream,
ScgReadStream,
TankParameters, TankParameters,
toUniqueId, toUniqueId,
TurretDefinitionsList, TurretDefinitionsList,
@@ -12,9 +18,10 @@ import {
VehicleDefinitionArmor, VehicleDefinitionArmor,
VehicleDefinitionList, VehicleDefinitionList,
VehicleDefinitions, VehicleDefinitions,
VertexAttribute,
} from "@blitzkit/core"; } from "@blitzkit/core";
import { parse as parsePath } from "path"; import { parse as parsePath } from "path";
import { Vector3Tuple } from "three"; import { MathUtils, Vector3 as THREEVector3, Vector3Tuple } from "three";
import { Cache } from "./0_base"; import { Cache } from "./0_base";
import { ServerBlitzKitAPI2 } from "./2_camouflageDefinitions"; import { ServerBlitzKitAPI2 } from "./2_camouflageDefinitions";
@@ -23,6 +30,87 @@ export abstract class ServerBlitzKitAPI3 extends ServerBlitzKitAPI2 {
return { x: tuple[0], y: tuple[1], z: tuple[2] } satisfies Vector3; return { x: tuple[0], y: tuple[1], z: tuple[2] } satisfies Vector3;
} }
private async collisionBoundingBoxes(
nation: string,
tankKey: string,
turretRotation?: { yaw: number; pitch: number; roll: number },
) {
const boundingBoxes: Record<
string,
{ min: Vector3Tuple; max: Vector3Tuple }
> = {};
const path = `Data/3d/Tanks/CollisionMeshes/${nation}-${tankKey}`;
if (!(await this.vfs.resolve(`${path}.sc2`))) {
throw new Error(`Missing collision mesh for ${nation}-${tankKey}`);
}
const sc2 = new Sc2ReadStream(
(await this.vfs.file(`${path}.sc2`)).buffer as ArrayBuffer,
).sc2();
const scg = new ScgReadStream(
(await this.vfs.file(`${path}.scg`)).buffer as ArrayBuffer,
).scg();
parseHierarchies(sc2["#hierarchy"], null, (hierarchy, components) => {
for (const component of components) {
if (component["comp.typename"] !== "RenderComponent") continue;
const polygonGroup = scg.get(
component["rc.renderObj"]["ro.batches"]["0000"]["rb.datasource"],
);
if (!polygonGroup) continue;
const min: Vector3Tuple = [Infinity, Infinity, Infinity];
const max: Vector3Tuple = [-Infinity, -Infinity, -Infinity];
const rotate =
turretRotation && hierarchy.name.toLowerCase().startsWith("turret");
polygonGroup.vertices.forEach((vertex) => {
vertex.forEach(({ attribute, value }) => {
if (attribute !== VertexAttribute.VERTEX) return;
const position = new THREEVector3(...value);
if (rotate) {
position
.applyAxisAngle(
I_HAT,
-MathUtils.degToRad(turretRotation.pitch),
)
.applyAxisAngle(
J_HAT,
-MathUtils.degToRad(turretRotation.roll),
)
.applyAxisAngle(
K_HAT,
-MathUtils.degToRad(turretRotation.yaw),
);
}
const coordinates = position.toArray();
for (let axis = 0; axis < 3; axis++) {
min[axis] = Math.min(min[axis], coordinates[axis]);
max[axis] = Math.max(max[axis], coordinates[axis]);
}
});
});
boundingBoxes[hierarchy.name.toLowerCase()] = { min, max };
}
return null;
});
if (!boundingBoxes.hull) {
throw new Error(`Missing hull collision mesh for ${nation}-${tankKey}`);
}
return boundingBoxes;
}
private assignArmor( private assignArmor(
raw: VehicleDefinitionArmor[string], raw: VehicleDefinitionArmor[string],
id: number, id: number,
@@ -67,6 +155,12 @@ export abstract class ServerBlitzKitAPI3 extends ServerBlitzKitAPI2 {
const tankParameters = await this.vfs.yaml<TankParameters>( const tankParameters = await this.vfs.yaml<TankParameters>(
`Data/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`, `Data/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`,
); );
const collisionBoundingBoxes = await this.collisionBoundingBoxes(
nation,
tankKey,
tankDefinition.root.hull.turretInitialRotation,
);
const hullBoundingBox = collisionBoundingBoxes.hull;
const turretOrigin = tankDefinition.root.hull.turretPositions.turret const turretOrigin = tankDefinition.root.hull.turretPositions.turret
.split(" ") .split(" ")
.map(Number) as Vector3Tuple; .map(Number) as Vector3Tuple;
@@ -141,12 +235,8 @@ export abstract class ServerBlitzKitAPI3 extends ServerBlitzKitAPI2 {
} }
: undefined, : undefined,
bounding_box: { bounding_box: {
min: this.vector3TupleToBlitzkit( min: this.vector3TupleToBlitzkit(hullBoundingBox.min),
tankParameters.collision.hull.bbox.min, max: this.vector3TupleToBlitzkit(hullBoundingBox.max),
),
max: this.vector3TupleToBlitzkit(
tankParameters.collision.hull.bbox.max,
),
}, },
turrets: {}, turrets: {},
tracks: {}, tracks: {},
@@ -208,26 +298,35 @@ export abstract class ServerBlitzKitAPI3 extends ServerBlitzKitAPI2 {
turret.userString; turret.userString;
const turretCollisionName = parsePath(
turret.hitTester.collisionModel,
).name.toLowerCase();
const isFullYaw = -turretYaw[0] + turretYaw[1] === 360;
if (!collisionBoundingBoxes[turretCollisionName] && isFullYaw) {
throw new Error(
`Missing turret collision mesh for ${nation}-${tankKey} ${turretKey}`,
);
}
const turretBoundingBox = collisionBoundingBoxes[
turretCollisionName
] ?? {
min: [0, 0, 0] as Vector3Tuple,
max: [0, 0, 0] as Vector3Tuple,
};
modelDefinitions.models[tankId].turrets[turretId] = { modelDefinitions.models[tankId].turrets[turretId] = {
armor: turretArmor, armor: turretArmor,
gun_origin: this.vector3TupleToBlitzkit(gunOrigin), gun_origin: this.vector3TupleToBlitzkit(gunOrigin),
model_id: turretModel, model_id: turretModel,
yaw: yaw: isFullYaw
-turretYaw[0] + turretYaw[1] === 360 ? undefined
? undefined : { min: turretYaw[0], max: turretYaw[1] },
: { min: turretYaw[0], max: turretYaw[1] },
guns: {}, guns: {},
bounding_box: { bounding_box: {
min: this.vector3TupleToBlitzkit( min: this.vector3TupleToBlitzkit(turretBoundingBox.min),
tankParameters.collision[ max: this.vector3TupleToBlitzkit(turretBoundingBox.max),
parsePath(turret.hitTester.collisionModel).name.toLowerCase()
].bbox.min,
),
max: this.vector3TupleToBlitzkit(
tankParameters.collision[
parsePath(turret.hitTester.collisionModel).name.toLowerCase()
].bbox.max,
),
}, },
}; };
@@ -44,9 +44,7 @@ export abstract class ServerBlitzKitAPI6 extends ServerBlitzKitAPI5 {
filter_type: { filter_type: {
$case: "ids", $case: "ids",
value: { value: {
ids: includeRaw.name.split(/ +/).map((key) => { ids: this.getTankFilterIds(includeRaw.name, key, "include"),
return this.tankStringIdMap[key];
}),
}, },
}, },
}); });
@@ -72,9 +70,7 @@ export abstract class ServerBlitzKitAPI6 extends ServerBlitzKitAPI5 {
filter_type: { filter_type: {
$case: "ids", $case: "ids",
value: { value: {
ids: excludeRaw.name.split(/ +/).map((key) => { ids: this.getTankFilterIds(excludeRaw.name, key, "exclude"),
return this.tankStringIdMap[key];
}),
}, },
}, },
}); });
@@ -11,7 +11,7 @@ export abstract class ServerBlitzKitAPI7 extends ServerBlitzKitAPI6 {
async provisionDefinitions() { async provisionDefinitions() {
const provisionDefinitions = ProvisionDefinitions.create(); const provisionDefinitions = ProvisionDefinitions.create();
Object.entries(this.provisionsCommon).forEach(([, provision]) => { Object.entries(this.provisionsCommon).forEach(([key, provision]) => {
const entry: Provision = { const entry: Provision = {
id: provision.id, id: provision.id,
exclude: [], exclude: [],
@@ -42,9 +42,7 @@ export abstract class ServerBlitzKitAPI7 extends ServerBlitzKitAPI6 {
filter_type: { filter_type: {
$case: "ids", $case: "ids",
value: { value: {
ids: includeRaw.name.split(/ +/).map((key) => { ids: this.getTankFilterIds(includeRaw.name, key, "include"),
return this.tankStringIdMap[key];
}),
}, },
}, },
}); });
@@ -70,9 +68,7 @@ export abstract class ServerBlitzKitAPI7 extends ServerBlitzKitAPI6 {
filter_type: { filter_type: {
$case: "ids", $case: "ids",
value: { value: {
ids: excludeRaw.name ids: this.getTankFilterIds(excludeRaw.name, key, "exclude"),
.split(/ +/)
.map((key) => this.tankStringIdMap[key]),
}, },
}, },
}); });
+1
View File
@@ -25,6 +25,7 @@ export * from "./sluggify";
export * from "./staticAsset"; export * from "./staticAsset";
export * from "./sum"; export * from "./sum";
export * from "./tankDefinitions"; export * from "./tankDefinitions";
export * from "./tankBoundingBox";
export * from "./tankIcon"; export * from "./tankIcon";
export * from "./tankNames"; export * from "./tankNames";
export * from "./unionBoundingBox"; export * from "./unionBoundingBox";
@@ -0,0 +1,32 @@
import { ModelDefinition, TurretModelDefinition } from "../protos";
import { unionBoundingBox } from "./unionBoundingBox";
export function tankBoundingBox(
tankModelDefinition: ModelDefinition,
turretModelDefinition: TurretModelDefinition,
) {
const hull = tankModelDefinition.bounding_box!;
const turret = turretModelDefinition.bounding_box!;
const origin = tankModelDefinition.turret_origin!;
if (
turret.min!.x === turret.max!.x &&
turret.min!.y === turret.max!.y &&
turret.min!.z === turret.max!.z
) {
return unionBoundingBox(hull, hull);
}
return unionBoundingBox(hull, {
min: {
x: turret.min!.x + origin.x,
y: turret.min!.y + origin.z,
z: turret.min!.z + origin.y,
},
max: {
x: turret.max!.x + origin.x,
y: turret.max!.y + origin.z,
z: turret.max!.z + origin.y,
},
});
}
@@ -1,5 +1,5 @@
import { times } from "lodash-es"; import { times } from "lodash-es";
import { CrewType, ShellType, TankClass, TankType } from "../../protos"; import { CrewType, ShellType } from "../../protos";
export const SHELL_NAMES: Record<ShellType, string> = { export const SHELL_NAMES: Record<ShellType, string> = {
[ShellType.SHELL_TYPE_AP]: "AP", [ShellType.SHELL_TYPE_AP]: "AP",
@@ -15,33 +15,6 @@ export const CREW_MEMBER_NAMES = {
[CrewType.CREW_TYPE_RADIOMAN]: "radioman", [CrewType.CREW_TYPE_RADIOMAN]: "radioman",
} as const; } as const;
/**
* @deprecated use svg
*/
export const TANK_ICONS: Record<TankClass, string> = {
[TankClass.TANK_CLASS_TANK_DESTROYER]: "https://i.imgur.com/BIHSEH0.png",
[TankClass.TANK_CLASS_LIGHT]: "https://i.imgur.com/CSNha5V.png",
[TankClass.TANK_CLASS_MEDIUM]: "https://i.imgur.com/wvf3ltm.png",
[TankClass.TANK_CLASS_HEAVY]: "https://i.imgur.com/ECeqlZa.png",
};
/**
* @deprecated use svg
*/
export const TANK_ICONS_PREMIUM: Record<TankClass, string> = {
[TankClass.TANK_CLASS_TANK_DESTROYER]: "https://i.imgur.com/TCu3EdR.png",
[TankClass.TANK_CLASS_LIGHT]: "https://i.imgur.com/zdkpTRb.png",
[TankClass.TANK_CLASS_MEDIUM]: "https://i.imgur.com/3z7eHX6.png",
[TankClass.TANK_CLASS_HEAVY]: "https://i.imgur.com/P3vbmyA.png",
};
/**
* @deprecated use svg
*/
export const TANK_ICONS_COLLECTOR: Record<TankClass, string> = {
[TankClass.TANK_CLASS_TANK_DESTROYER]: "https://i.imgur.com/WTjeirB.png",
[TankClass.TANK_CLASS_LIGHT]: "https://i.imgur.com/EwhtKkU.png",
[TankClass.TANK_CLASS_MEDIUM]: "https://i.imgur.com/u8YDMBh.png",
[TankClass.TANK_CLASS_HEAVY]: "https://i.imgur.com/8xRf3nc.png",
};
export const TIERS = times(10, (index) => index + 1); export const TIERS = times(10, (index) => index + 1);
export const TIER_ROMAN_NUMERALS: Record<number, string> = { export const TIER_ROMAN_NUMERALS: Record<number, string> = {
1: "I", 1: "I",
@@ -68,8 +41,3 @@ export const flags: Record<string, string> = {
european: "🇪🇺", european: "🇪🇺",
other: "<:other:1218421572243558482>", other: "<:other:1218421572243558482>",
}; };
export const TREE_TYPE_ICONS: Record<TankType, Record<TankClass, string>> = {
[TankType.TANK_TYPE_RESEARCHABLE]: TANK_ICONS,
[TankType.TANK_TYPE_PREMIUM]: TANK_ICONS_PREMIUM,
[TankType.TANK_TYPE_COLLECTOR]: TANK_ICONS_COLLECTOR,
};
-50
View File
@@ -1,50 +0,0 @@
export enum ImgurSize {
/**
* The default size
*/
Default = '',
/**
* Unknown size but really really small
*/
UltraSmall = 'ss',
/**
* 90x90
*/
SmallSquare = 's',
/**
* 160x160
*/
BigSquare = 'b',
/**
* 160x160
*/
SmallThumbnail = 't',
/**
* 320x320
*/
MediumThumbnail = 'm',
/**
* 640x640
*/
LargeThumbnail = 'l',
/**
* 1024x1024
*/
HugeThumbnail = 'h',
}
interface ImgurOptions {
format: 'jpeg' | 'png';
size: ImgurSize;
}
const defaultImgurOptions: ImgurOptions = {
format: 'png',
size: ImgurSize.Default,
};
export function imgur(id: string, imgurOptions?: Partial<ImgurOptions>) {
const options = { ...defaultImgurOptions, ...imgurOptions };
return `https://i.imgur.com/${id}${options.size}.${options.format}`;
}
-1
View File
@@ -1 +0,0 @@
export * from './imgur';
-1
View File
@@ -1,6 +1,5 @@
export * from "./blitz"; export * from "./blitz";
export * from "./blitzkit"; export * from "./blitzkit";
export * from "./imgur";
export * from "./math"; export * from "./math";
export * from "./patreon"; export * from "./patreon";
export * from "./protobuf"; export * from "./protobuf";
+22
View File
@@ -90,6 +90,28 @@ export interface Hierarchy {
name: string; name: string;
} }
export function parseHierarchies<Parent>(
hierarchies: Hierarchy[],
parent: Parent,
parseHierarchy: (
hierarchy: Hierarchy,
components: Component[],
parent: Parent,
) => Parent | undefined,
) {
for (const hierarchy of hierarchies) {
const components = Array.from(
{ length: hierarchy.components.count },
(_, index) => hierarchy.components[index.toString().padStart(4, "0")],
);
const child = parseHierarchy(hierarchy, components, parent);
if (child !== undefined && hierarchy["#hierarchy"]) {
parseHierarchies(hierarchy["#hierarchy"], child, parseHierarchy);
}
}
}
// 0001, 0002, 0003...: Component // 0001, 0002, 0003...: Component
type Components = { type Components = {
count: number; count: number;
@@ -20,11 +20,13 @@ export interface GunDefinitionsList {
burst?: { count: number; rate: number }; burst?: { count: number; rate: number };
clip?: { count: number; rate: number }; clip?: { count: number; rate: number };
shots: { shots: {
[key: string]: { [key: string]:
speed: number; | ""
piercingPower: string; | {
maxDistance: number; speed: number;
}; piercingPower: string;
maxDistance: number;
};
}; };
}; };
}; };
@@ -10,6 +10,9 @@ export type ShellDefinitionsList = Record<
normalizationAngle?: number; normalizationAngle?: number;
ricochetAngle?: number; ricochetAngle?: number;
explosionRadius?: number; explosionRadius?: number;
speed: number;
maxDistance?: number;
piercingPower: string;
} }
> & { > & {
icons: Record<string, string>; icons: Record<string, string>;
+5 -2
View File
@@ -1,6 +1,8 @@
{ {
"credits": { "test": {
"title": "BlitzKit is the work of many" "default": "test",
"long": "This is a long test string. You MUST NEVER see this in the UI. My favorite fruit is {fruit} because it has the color of {color}. I like it so much that I will say its name 3 times! {fruit}! {fruit}! {fruit}! Once again, it's {color}, not {not_color}.",
"fox": "The quick brown fox jumps over the lazy dog."
}, },
"common": { "common": {
@@ -994,6 +996,7 @@
"about": { "about": {
"title": "About", "title": "About",
"credits": "Credits",
"discord": "Discord", "discord": "Discord",
"github": "GitHub", "github": "GitHub",
"patches": "Patches" "patches": "Patches"
+101 -101
View File
@@ -220,190 +220,190 @@
"clear": "Clear", "clear": "Clear",
"tier": "Tier {tier}", "tier": "Tier {tier}",
"passive": "{name} (Passive)", "passive": "{name} (Passive)",
"active": "{name} (Active)", "active": "{name} (Aktywne)",
"no_tanks_found": { "no_tanks_found": {
"body": "No tanks found.", "body": "Nie znaleziono czołgu.",
"search_again": "", "search_again": "",
"clear_filters": "Try clearing filters." "clear_filters": "Try clearing filters."
}, },
"sort": { "sort": {
"meta.none": "none", "meta.none": "brak",
"survivability.health": "health", "survivability.health": "punkty.życia",
"survivability.viewRange": "view range", "survivability.viewRange": "zasięg widzenia",
"survivability.camouflageStill": "camouflage still", "survivability.camouflageStill": "kamuflaż w bezruchu",
"survivability.camouflageMoving": "camouflage moving", "survivability.camouflageMoving": "kamuflaż w trakcie ruchu",
"survivability.camouflageShooting": "camouflage shooting", "survivability.camouflageShooting": "kamuflaż podczas strzału",
"survivability.volume": "volume", "survivability.volume": "wartość",
"survivability.length": "length", "survivability.length": "długość",
"fire.dpm": "standard DPM", "fire.dpm": "DPM pocisków podstawowych",
"fire.dpmPremium": "premium DPM", "fire.dpmPremium": "DPM pocisków premium",
"fire.reload": "reload", "fire.reload": "przeładowanie",
"fire.caliber": "caliber", "fire.caliber": "kaliber",
"fire.standardPenetration": "standard penetration", "fire.standardPenetration": "Penetracja pocisków standardowych",
"fire.premiumPenetration": "premium penetration", "fire.premiumPenetration": "Penetracja pocisków premium",
"fire.tertiaryPenetration": "tertiary penetration", "fire.tertiaryPenetration": "tertiary penetration",
"fire.damage": "damage", "fire.damage": "uszkodzenia",
"fire.moduleDamage": "module damage", "fire.moduleDamage": "Uszkodzenia modułów",
"fire.shellVelocity": "shell velocity", "fire.shellVelocity": "Prędkość pocisku",
"fire.shellRange": "shell range", "fire.shellRange": "Zasięg pocisku",
"fire.shellCapacity": "shell capacity", "fire.shellCapacity": "Maksymalna ilość pocisków",
"fire.aimTime": "aim time", "fire.aimTime": "Czas celowania",
"fire.dispersionStill": "dispersion still", "fire.dispersionStill": "dispersion still",
"fire.dispersionMoving": "dispersion moving", "fire.dispersionMoving": "dispersion moving",
"fire.gunDepression": "gun depression", "fire.gunDepression": "Depresja działa",
"fire.gunElevation": "gun elevation", "fire.gunElevation": "Elewacja działa",
"maneuverability.forwardsSpeed": "forwards speed", "maneuverability.forwardsSpeed": "Maksymalna prędkość do przodu",
"maneuverability.backwardsSpeed": "backwards speed", "maneuverability.backwardsSpeed": "Maksymalna prędkość do tyłu",
"maneuverability.power": "power", "maneuverability.power": "Moc silnika",
"maneuverability.powerToWeight": "power to weight", "maneuverability.powerToWeight": "Proporcja mocy do masy",
"maneuverability.weight": "weight", "maneuverability.weight": "Masa czołgu",
"maneuverability.traverseSpeed": "traverse speed" "maneuverability.traverseSpeed": "traverse speed"
}, },
"sort_dropdown": { "sort_dropdown": {
"label": "Sort", "label": "Filtruj",
"groups": { "groups": {
"sort": { "sort": {
"no_sort": "No sort", "no_sort": "Brak filtrów",
"label": "Sort by", "label": "Filtruj według",
"fire": "Fire", "fire": "Siła ognia",
"maneuverability": "Maneuverability", "maneuverability": "Mobilność ",
"survivability": "Survivability" "survivability": "Przetrwanie"
}, },
"direction": { "direction": {
"label": "Direction", "label": "Direction",
"ascending": "Ascending", "ascending": "Rosnąco",
"descending": "Descending" "descending": "Malejąco"
} }
} }
} }
}, },
"warnings": { "warnings": {
"test": "Tanks in testing are subject to change and may not represent the final product." "test": "Wartości statystyk czołgów w fazie testowej mogą ulec zmianie."
} }
}, },
"home": { "home": {
"discord": { "discord": {
"join_short": "Join cool people!", "join_short": "Dołącz do elity!",
"join_long": "Join super duper cool people!", "join_long": "Dołącz do topowej elity!",
"action": "Discord" "action": "Discord"
}, },
"hero": { "hero": {
"credit": "By {name} ❤️" "credit": "Według {name} ♥️"
}, },
"hot_tanks": { "hot_tanks": {
"title": "Popular tanks", "title": "Popularne czołgi",
"subtitle": "Last {days} days" "subtitle": "Ostatnie {days} dni"
}, },
"other_projects": { "other_projects": {
"title": "Other cool projects" "title": "Inne epickie projekty"
}, },
"help_translate": "Please help translate {name} to {language}!", "help_translate": "Proszę pomóż przetłumaczyć {name} na {language}!",
"seo_description": "Tools for everything World of Tanks Blitz" "seo_description": "Narzędzia do wszystkiego World of Tanks Blitz"
}, },
"docs": { "docs": {
"patches": { "patches": {
"name": "BlitzKit Changelogs", "name": "Zapisy zmian BlitzKit",
"seo_description": "Changelogs for BlitzKit Discord bot and website" "seo_description": "Zapisy zmian BlitzKit: Discord bot i strona internetowa"
} }
}, },
"tools": { "tools": {
"playlist": { "playlist": {
"reset": "Reset", "reset": "Resetuj",
"shuffle": "Shuffle", "shuffle": "Wymieszaj",
"remove": "Remove played", "remove": "Usuń czołgi, którymi grałem",
"tracking": "BlitzKit is tracking your battles", "tracking": "BlitzKit zapisuje twoje bitwy",
"sort": { "sort": {
"button": "Sort by", "button": "Filtruj według",
"last_played": "Last played", "last_played": "Ostatnio używane czołgi",
"least_played": "Least played" "least_played": "Najmniej używane czołgi"
}, },
"table": { "table": {
"index": "#", "index": "#",
"never": "Never", "never": "Nigdy",
"tank": "Tank", "tank": "Czołg",
"last_played": "Last played", "last_played": "Ostatnio używane czołgi",
"battles": "Career battles", "battles": "Wszystkie bitwy na koncie",
"unrecorded": "--", "unrecorded": "--",
"wins": "Zwycięstwa" "wins": "Zwycięstwa"
}, },
"large_warning": { "large_warning": {
"title": "That's a lot of tanks!", "title": "To jest mnóstwo czołgów!",
"description": "Are you sure you want to create a playlist of {count} tanks? That's {time} hours of gameplay at 50% winrate!", "description": "Czy na pewno chcesz stworzyć playlistę złożoną z {count} czołgów? To {time} godzin gry przy 50% zwycięstw!",
"cancel": "Cancel", "cancel": "Anuluj",
"proceed": "Proceed" "proceed": "Zatwierdź"
}, },
"generate": "Generate playlist", "generate": "Wygeneruj playlistę",
"name": "Playlist", "name": "Playlista",
"description": "Tank playlist generator", "description": "Generator playlisty czołgów",
"seo_description": "Don't know what to play in World of Tanks Blitz? Use BlitzKit Playlist to get suggestions!", "seo_description": "Nie wiesz, którym czołgiem grać w World of Tanks Blitz? Użyj funkcji playlisty BlitzKit, i ułóż własną listę czołgów do gry!",
"button": "Generate", "button": "Wygeneruj",
"pool_size": "{count} possible tanks" "pool_size": "{count} możliwe czołgi"
}, },
"mixer": { "mixer": {
"name": "Mixer", "name": "Mikser",
"button": "Mix and match", "button": "Mix and match",
"description": "Mix tank modules", "description": "Mix tank modules",
"seo_description": "Mix and match hulls, turrets, and guns of tanks in World of Tanks Blitz", "seo_description": "Mix and match hulls, turrets, and guns of tanks in World of Tanks Blitz",
"screenshot": "Screenshot", "screenshot": "Zrzut ekranu",
"randomize": "Randomize" "randomize": "Wymieszaj"
}, },
"guess": { "guess": {
"name": "Guess", "name": "Zgadnij",
"button": "What could it be?", "button": "What could it be?",
"description": "Silhouette mini-game", "description": "Silhouette mini-game",
"seo_description": "Play a tank guessing game with a silhouette in World of Tanks Blitz", "seo_description": "Play a tank guessing game with a silhouette in World of Tanks Blitz",
"search": { "search": {
"placeholder": "Type to guess...", "placeholder": "Wpisz odpowiedź",
"skip": "Skip", "skip": "Pomiń",
"guess": "Guess", "guess": "Zgaduję",
"next": "Next", "next": "Następny",
"no_results": "Brak wyników" "no_results": "Brak wyników"
}, },
"stats": "{correct} / {total} correct, {streak} streak", "stats": "{correct} / {total} correct, {streak} streak",
"help": { "help": {
"title": "Partially reveal tank?", "title": "Odsłonić część czołgu?",
"description": "Reveals the tank without showing the name. This round will be considered incorrect and break your streak!", "description": "Reveals the tank without showing the name. This round will be considered incorrect and break your streak!",
"cancel": "Cancel", "cancel": "Anuluj",
"reveal": "Reveal" "reveal": "Pokaż"
}, },
"clear": "Clear", "clear": "Wyczyść",
"select_all": "Select all", "select_all": "Zaznacz wszystko ",
"tier": "Tier {tier}" "tier": "Poziom czołgu {tier}"
}, },
"tankopedia": { "tankopedia": {
"search": "Search a stat...", "search": "Wyszukaj statystykę...",
"name": "Tankopedia", "name": "Tankopedia",
"button": "Find tanks", "button": "Znajdź czołg",
"description": "Blitz tank encyclopedia", "description": "Encyklopedia czołgów w WoTb",
"seo_description": "Statistics, armor, and more for all tanks in World of Tanks Blitz", "seo_description": "Statistics, armor, and more for all tanks in World of Tanks Blitz",
"tank_seo_title": "{name} - Tier {tier} {nationality} {class} | {website} Tankopedia | World of Tanks Blitz (WoTB)", "tank_seo_title": "{name} - Tier {tier} {nationality} {class} | {website} Tankopedia | World of Tanks Blitz (WoTB)",
"tank_seo_description": "{name} armor, statistics, equipment, provisions, and more. {dpm} DPM, {alpha}hp alpha, {penetration}mm penetration, {speed}kph top speed, {health}hp health.", "tank_seo_description": "{name} armor, statistics, equipment, provisions, and more. {dpm} DPM, {alpha}hp alpha, {penetration}mm penetration, {speed}kph top speed, {health}hp health.",
"visualizers": { "visualizers": {
"reload": { "reload": {
"skip": "Skip" "skip": "Pomiń"
}, },
"assault": { "assault": {
"title": "Assault shells" "title": "Assault shells"
}, },
"rotator": { "rotator": {
"hull": "Hull", "hull": "Kadłub",
"turret": "Turret", "turret": "Wieża",
"hard": "Hard", "hard": "Utwardzony",
"medium": "Średni", "medium": "Średni",
"soft": "Soft", "soft": "Miękki",
"effective": "Effective" "effective": "Rzeczywisty"
}, },
"ricochet": { "ricochet": {
"angle": "Angle", "angle": "Kąt",
"normalization": "Normalization", "normalization": "Normalization",
"effective_angle": "Effective angle", "effective_angle": "Rzeczywisty kąt nachylenia pancerza",
"armor": "Armor", "armor": "Pancerz",
"no_ricochet": "Explosive shells don't ricochet" "no_ricochet": "Pociski odłamkowe nie rykoszetują."
}, },
"view_range": { "view_range": {
"camo": "Camo", "camo": "Kamuflaż",
"ghost_factory": "Ghost Factory", "ghost_factory": "Fabryka duchów",
"dead_rail": "Dead Rail" "dead_rail": "Martwy tor"
}, },
"flexibility": { "flexibility": {
"yaw": "Yaw", "yaw": "Yaw",
@@ -656,10 +656,10 @@
"loader": "Loader" "loader": "Loader"
}, },
"maneuverability": { "maneuverability": {
"title": "Maneuverability" "title": "Mobilność "
}, },
"survivability": { "survivability": {
"title": "Survivability" "title": "Przetrwanie"
} }
}, },
"compare": { "compare": {
+5 -5
View File
@@ -41,7 +41,7 @@
"development": "Development", "development": "Development",
"opentest": "TesteAberto", "opentest": "TesteAberto",
"preview": "Preview", "preview": "Preview",
"production": "Production" "production": "Produção"
}, },
"locales": { "locales": {
"en": "Inglês", "en": "Inglês",
@@ -187,11 +187,11 @@
}, },
"website": { "website": {
"redirect": { "redirect": {
"title": "301: moved permanently", "title": "301: movido permanentemente",
"body": "This page has moved. Redirecting..." "body": "This page has moved. Redirecting..."
}, },
"settings": { "settings": {
"title": "Settings", "title": "Configurações",
"description": "Change BlitzKit settings", "description": "Change BlitzKit settings",
"dev_mode": "Modo de desenvolvimento", "dev_mode": "Modo de desenvolvimento",
"auto_load_models": "Auto-load models", "auto_load_models": "Auto-load models",
@@ -207,9 +207,9 @@
"copied": "Copiado!" "copied": "Copiado!"
}, },
"tank_search": { "tank_search": {
"select_dialog_title": "Search for tanks", "select_dialog_title": "Procura por tanque",
"swap_dialog_title": "Swap tank", "swap_dialog_title": "Swap tank",
"clear_filters": "Clear filters", "clear_filters": "Limpar filtros",
"login": "Login to use this", "login": "Login to use this",
"search_bar_hint": "Procurar tanques...", "search_bar_hint": "Procurar tanques...",
"recent": "Visto recentemente", "recent": "Visto recentemente",
+2 -1
View File
@@ -5,7 +5,8 @@
"discover-ids": "bun src/discoverIds/index.ts", "discover-ids": "bun src/discoverIds/index.ts",
"build-protos": "bun src/protos.ts", "build-protos": "bun src/protos.ts",
"record-performance": "bun src/recordPerformance/index.ts", "record-performance": "bun src/recordPerformance/index.ts",
"download-client": "bun src/downloadClient/index.ts" "download-client": "bun src/downloadClient/index.ts",
"vfs": "bun src/vfs/index.ts"
}, },
"dependencies": { "dependencies": {
"@blitzkit/closed": "workspace:*", "@blitzkit/closed": "workspace:*",
+23
View File
@@ -0,0 +1,23 @@
import { assertSecret } from "@blitzkit/core";
import { LocalVFS } from "@blitzkit/core/src/blitzkit/vfs/local";
export const vfs = new LocalVFS(assertSecret(import.meta.env.CLIENT_DIR));
const command = Bun.argv[2];
const path = Bun.argv[3];
if (!path) {
console.error("Gimme a path to assess dumb ass");
process.exit(1);
}
if (command === "cat") {
const content = await vfs.file(path);
await Bun.write(Bun.stdout, content);
} else if (command === "ls") {
const files = await vfs.dir(path);
for (const file of files) {
console.log(file);
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

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