Compare commits

...
15 Commits
Author SHA1 Message Date
TrèsAbhi 191843f972 Merge pull request #220 from blitzkit/main-i18n
New Crowdin updates
2026-03-24 09:39:43 -05:00
TrèsAbhi c1fde44809 Merge pull request #223 from blitzkit/fixes
fixes
2026-03-24 09:39:28 -05:00
TrèsAbhi 1a3517e035 feat: logs 2026-03-23 22:50:34 -05:00
TrèsAbhi afec6d9851 feat: enable all tiers 2026-03-23 22:41:34 -05:00
TrèsAbhi e3f2fd70ad fix: use turret mantlet 2026-03-23 22:36:10 -05:00
TrèsAbhi 5adbec25c4 fix: compact compare headers 2026-03-23 22:20:29 -05:00
TrèsAbhi 4ff8d77460 fix: object review videos 2026-03-23 22:13:29 -05:00
TrèsAbhi 22faa6d294 fix: stale compare indices 2026-03-23 22:07:20 -05:00
TrèsAbhi a2abfa7f85 fix: v-stabs now give 12% 2026-03-23 21:56:33 -05:00
TrèsAbhi 7b93b12fcb Merge pull request #222 from karelpak1/Position-fix 2026-03-23 19:56:17 -05:00
GonnaHetzMe ed252d5dfe Reset localTranslation for model extraction 2026-03-23 13:17:42 +01:00
TrèsAbhi c365993541 feat: remove discord bot
.
2026-03-19 09:21:58 -05:00
TrèsAbhi 30f9475208 feat: disable bot 2026-03-19 09:19:28 -05:00
tresabhi 8394f2ec1e feat: logs 2026-03-11 16:51:32 -05:00
tresabhi f14e73d811 fix: alpha clipping 2026-03-11 16:50:18 -05:00
20 changed files with 219 additions and 135 deletions

No files matched your search

-6
View File
@@ -1,13 +1,7 @@
name: Host
on:
push:
branches: main
schedule:
- cron: "0 */5 * * *"
workflow_dispatch:
release:
types: [created, published, released, edited]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
+5
View File
@@ -0,0 +1,5 @@
# BlitzKit 1.29.15
## Fixes
- Tanks now use alpha clipping to render translucency, fixing a lot of "ghostly" looking tanks
+18
View File
@@ -0,0 +1,18 @@
# BlitzKit 1.29.16
Thank you to [GonnaHetzMe](https://github.com/karelpak1) for your contributions this update.
## Fixes
- [GonnaHetzMe] The turret for Centurion I is no longer offset from the body
- Please allow up to 24 hours for the model to update
- Vertical Stabilizers now give the correct 12% buff instead of 15%
- Re-ordering tanks in Compare now works once again
- Fixed review videos from being about the Object 452K across all tanks
- Please allow up to 2 weeks for all tanks to refresh their videos
- Tanks in Mixer with oscillating turrets are not handled properly
## Features
- Headings in the Compare tool are now even more compact for mobile devices
- Enabled all tiers for the randomizer in Mixer
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "blitzkit",
"version": "1.29.14",
"version": "1.29.16",
"license": "BlitzKit License",
"private": true,
"scripts": {
@@ -0,0 +1,3 @@
export function bufferToBigInt(buffer: ArrayBuffer) {
return new DataView(buffer).getBigUint64(0, true);
}
+2
View File
@@ -1,4 +1,5 @@
export * from './buffer';
export * from './bufferToBigInt';
export * from './dds';
export * from './dids';
export * from './pvr';
@@ -7,3 +8,4 @@ export * from './sc2';
export * from './scg';
export * from './scpg';
export * from './windows';
@@ -12,12 +12,14 @@ export async function tankModels() {
using uploader = new AssetUploader("tank models");
const nodeIO = new NodeIO().registerExtensions(ALL_EXTENSIONS);
const nations = vfs.dir(`Data/XML/item_defs/vehicles`).filter((nation) => nation !== "common")
const nations = vfs
.dir(`Data/XML/item_defs/vehicles`)
.filter((nation) => nation !== "common");
for (const nationIndex in nations) {
const nation = nations[nationIndex];
const tanks = await vfs.xml<{ root: VehicleDefinitionList }>(
`Data/XML/item_defs/vehicles/${nation}/list.xml`
`Data/XML/item_defs/vehicles/${nation}/list.xml`,
);
const entries = Object.entries(tanks.root);
@@ -26,11 +28,11 @@ export async function tankModels() {
const id = toUniqueId(nation, tank.id);
const parameters = await vfs.yaml<TankParameters>(
`Data/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`
`Data/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`,
);
const model = await extractModel(
vfs,
parameters.resourcesPath.blitzModelPath.replace(/\.sc2$/, "")
parameters.resourcesPath.blitzModelPath.replace(/\.sc2$/, ""),
);
const content = Buffer.from(await nodeIO.writeBinary(model));
@@ -39,4 +41,4 @@ export async function tankModels() {
}
await uploader.flush();
}
}
@@ -6,13 +6,13 @@ import {
} from "@blitzkit/core";
import { Accessor, Document, Node, Scene } from "@gltf-transform/core";
import { times } from "lodash-es";
import { SteamVFS } from "../vfs/steam";
import { AbstractVFS } from "../vfs/abstract";
import {
vertexAttributeGLTFName,
vertexAttributeGltfVectorSizes,
} from "./extractModel/constants";
export async function extractArmor(vfs: SteamVFS, fileName: string) {
export async function extractArmor(vfs: AbstractVFS, fileName: string) {
const sc2Path = `Data/3d/Tanks/CollisionMeshes/${fileName}.sc2`;
const scgPath = `Data/3d/Tanks/CollisionMeshes/${fileName}.scg`;
const sc2 = new Sc2ReadStream(
@@ -141,4 +141,4 @@ export async function extractArmor(vfs: SteamVFS, fileName: string) {
parseHierarchies(sc2["#hierarchy"], scene);
return document;
}
}
@@ -1,4 +1,5 @@
import {
bufferToBigInt,
Hierarchy,
Sc2ReadStream,
ScgReadStream,
@@ -9,7 +10,7 @@ import { Document, Material, Node, Scene } from "@gltf-transform/core";
import { dedup, prune } from "@gltf-transform/functions";
import { times } from "lodash-es";
import { dirname } from "path";
import { SteamVFS } from "../../vfs/steam";
import { AbstractVFS } from "../../vfs/abstract";
import { readBaseColor } from "../readBaseColor";
import { readNormal } from "../readNormal";
import { readRoughnessMetallic } from "../readRoughnessMetallic";
@@ -25,7 +26,7 @@ const omitMeshNames = {
end: ["_POINT"],
};
export async function extractModel(vfs: SteamVFS, path: string) {
export async function extractModel(vfs: AbstractVFS, path: string) {
const sc2Path = `Data/3d/${path}.sc2`;
const scgPath = `Data/3d/${path}.scg`;
const sc2 = new Sc2ReadStream(
@@ -40,7 +41,7 @@ export async function extractModel(vfs: SteamVFS, path: string) {
const materials = new Map<bigint, Material | bigint | undefined>();
for (const node of sc2["#dataNodes"]) {
const id = new DataView(node["#id"]).getBigUint64(0, true);
const id = bufferToBigInt(node["#id"]);
if (node.parentMaterialKey !== undefined) {
/**
@@ -80,8 +81,20 @@ export async function extractModel(vfs: SteamVFS, path: string) {
configIndex < node.configCount;
configIndex++
) {
if (!hasAlpha) continue;
material.setAlphaMode("BLEND").setDoubleSided(true);
const archive = node[`configArchive_${configIndex}`];
if (
archive.configName !== "Default" ||
!archive.enabledPresets?.AlphaTest
) {
continue;
}
const view = new DataView(archive.properties.alphatestThreshold);
const _ = view.getUint8(0); // only god knows what these flags are
const alphaCutoff = view.getFloat32(1, true);
material.setAlphaMode("MASK").setAlphaCutoff(alphaCutoff);
}
}
@@ -116,7 +129,7 @@ export async function extractModel(vfs: SteamVFS, path: string) {
);
}
materials.set(new DataView(node["#id"]).getBigUint64(0, true), material);
materials.set(id, material);
}
}
@@ -157,6 +170,7 @@ export async function extractModel(vfs: SteamVFS, path: string) {
break;
case "TransformComponent": {
component["tc.localTranslation"] = [0, 0, 0]; // Game when is loading model resets position to [0, 0, 0], so any set value is ignored.
node.setTranslation(component["tc.localTranslation"]);
node.setRotation(component["tc.localRotation"]);
node.setScale(component["tc.localScale"]);
@@ -266,4 +280,4 @@ export async function extractModel(vfs: SteamVFS, path: string) {
await document.transform(prune({ keepAttributes: true }), dedup());
return document;
}
}
+10 -5
View File
@@ -5,6 +5,7 @@ import {
Video,
youtubers,
} from "@blitzkit/core";
import locales from "@blitzkit/i18n/locales.json";
import { google } from "googleapis";
import { cloneDeep, uniqBy } from "lodash-es";
import { AssetUploader } from "./core/github/assetUploader";
@@ -42,9 +43,11 @@ for (const tank of tanksSanitized) {
}
try {
const q = `World of Tanks Blitz ${tank.name.locales[locales.default]}`;
const results = await youtube.search.list({
part: ["snippet"],
q: `World of Tanks Blitz ${tank.name}`,
q,
});
if (!results.data.items || results.data.items.length === 0) continue;
@@ -54,9 +57,9 @@ for (const tank of tanksSanitized) {
(item) =>
item.snippet?.channelId &&
item.id?.videoId &&
youtubers.some(({ id }) => id === item.snippet?.channelId)
youtubers.some(({ id }) => id === item.snippet?.channelId),
),
(item) => item.snippet!.channelId
(item) => item.snippet!.channelId,
);
reviews.reviews[tank.id] = {
@@ -66,11 +69,13 @@ for (const tank of tanksSanitized) {
({
id: item.id!.videoId!,
author: item.snippet!.channelId!,
} satisfies Video)
}) satisfies Video,
),
};
console.log(`${++done} done; found ${items.length} for ${tank.name.locales.en}`);
console.log(
`${++done} done; found ${items.length} for ${tank.name.locales.en}`,
);
await new Promise((resolve) => setTimeout(resolve, 1000 / 5));
} catch (error) {
@@ -14,7 +14,7 @@ import { StickyRowHeaderCell } from "../StickyRowHeaderCell";
type CompareRowProps = {
display?: (
member: Awaited<TankCharacteristics>
member: Awaited<TankCharacteristics>,
) => number | string | undefined;
deltaType?: CompareCellDirection;
decimals?: number;
@@ -46,7 +46,7 @@ export function CompareRow({
const values = stats.map((stat) =>
typeof props.value === "function"
? props.value(stat)!
: (stat[props.value] as number)
: (stat[props.value] as number),
);
const id = useRef(Math.random());
const name =
@@ -56,10 +56,10 @@ export function CompareRow({
return (
<Table.Row>
<StickyRowHeaderCell>
<StickyRowHeaderCell left="-1.25rem">
<Flex
align="center"
style={{ whiteSpace: "nowrap" }}
maxWidth={{ initial: "6rem", sm: "unset" }}
gap={indent ? { initial: "4", sm: "6" } : { initial: "1", sm: "2" }}
>
<IconButton
@@ -111,8 +111,8 @@ export function CompareRow({
const resolvedDisplayValue = display
? display(stats[index])
: decimals === undefined
? value
: value?.toFixed(decimals);
? value
: value?.toFixed(decimals);
return (
<CompareCell
@@ -145,8 +145,8 @@ export function CompareRow({
deltaNominalDisplay
? deltaNominalDisplay(delta)
: decimals === undefined
? delta
: delta.toFixed(decimals)
? delta
: delta.toFixed(decimals)
}`}
)
</Text>
@@ -156,7 +156,7 @@ export function CompareRow({
<Text color="gray">
(
{`${deltaPercentage > 0 ? "+" : ""}${Math.round(
deltaPercentage * 100
deltaPercentage * 100,
)}%`}
)
</Text>
@@ -1,7 +1,7 @@
import { Heading, Table } from '@radix-ui/themes';
import { times } from 'lodash-es';
import { CompareEphemeral } from '../../stores/compareEphemeral';
import { StickyColumnHeaderCell } from '../StickyColumnHeaderCell';
import { Heading, Table } from "@radix-ui/themes";
import { times } from "lodash-es";
import { CompareEphemeral } from "../../stores/compareEphemeral";
import { StickyColumnHeaderCell } from "../StickyColumnHeaderCell";
export function CompareSectionTitle({ children }: { children: string }) {
const memberCount = CompareEphemeral.use((state) => state.members.length);
@@ -105,13 +105,13 @@ export function CompareTable({ stats }: CompareTableProps) {
crewSkills[skill] === 0
? Var("gray-11")
: permanentSkills.includes(skill)
? Var("crimson-11")
: Var("amber-11"),
? Var("crimson-11")
: Var("amber-11"),
}}
/>
))}
</Flex>
)
),
)}
</Flex>
</Button>
@@ -173,19 +173,19 @@ export function CompareTable({ stats }: CompareTableProps) {
consumables,
camouflage,
},
index
index,
) => {
const equipmentPreset =
equipmentDefinitions.presets[tank.equipment_preset];
const provisionsList = availableProvisions(
tank,
gun,
provisionDefinitions
provisionDefinitions,
);
const consumablesList = Object.values(
consumableDefinitions.consumables
consumableDefinitions.consumables,
).filter((consumable) =>
checkConsumableProvisionInclusivity(consumable, tank, gun)
checkConsumableProvisionInclusivity(consumable, tank, gun),
);
return (
@@ -294,11 +294,11 @@ export function CompareTable({ stats }: CompareTableProps) {
<img
alt={unwrap(
provisionDefinitions.provisions[provision]
.name
.name,
)}
key={provision}
src={asset(
`/icons/provisions/${provision}.webp`
`/icons/provisions/${provision}.webp`,
)}
style={{
left: "50%",
@@ -335,7 +335,7 @@ export function CompareTable({ stats }: CompareTableProps) {
<Popover.Content>
<ProvisionsManager
provisions={provisionsList.map(
(provision) => provision.id
(provision) => provision.id,
)}
selected={provisions}
disabled={tank.max_provisions === provisions.length}
@@ -452,7 +452,7 @@ export function CompareTable({ stats }: CompareTableProps) {
CompareEphemeral.mutate((draft) => {
draft.members[index].equipmentMatrix = times(
3,
() => times(3, () => 0)
() => times(3, () => 0),
) as EquipmentMatrix;
draft.sorting = undefined;
});
@@ -501,11 +501,11 @@ export function CompareTable({ stats }: CompareTableProps) {
alt={unwrap(
consumableDefinitions.consumables[
consumable
].name
].name,
)}
key={consumable}
src={asset(
`/icons/consumables/${consumable}.webp`
`/icons/consumables/${consumable}.webp`,
)}
style={{
left: "50%",
@@ -610,7 +610,7 @@ export function CompareTable({ stats }: CompareTableProps) {
</Flex>
</Table.Cell>
);
}
},
)}
</Table.Row>
</Table.Body>
@@ -55,7 +55,7 @@ export function TankCard({ index, tank }: TankCardProps) {
};
});
const closest = distances.reduce((a, b) =>
a.distance < b.distance ? a : b
a.distance < b.distance ? a : b,
);
dropIndex = closest.index;
@@ -85,8 +85,8 @@ export function TankCard({ index, tank }: TankCardProps) {
CompareEphemeral.mutate((draft) => {
const fromIndex = index;
const toIndex = dropIndex > index ? dropIndex - 1 : dropIndex;
const member = draft.members[fromIndex];
draft.members.splice(fromIndex, 1);
draft.members.splice(toIndex, 0, member);
});
@@ -99,7 +99,7 @@ export function TankCard({ index, tank }: TankCardProps) {
return () => {
draggable.current?.removeEventListener("pointerdown", handlePointerDown);
};
}, []);
}, [index]);
return (
<StickyColumnHeaderCell width="0">
@@ -141,7 +141,6 @@ export function TankCard({ index, tank }: TankCardProps) {
</Text>
</Flex>
</Flex>
<InsertionMarker index={index + 1} />
</StickyColumnHeaderCell>
);
@@ -84,7 +84,7 @@ export function Model() {
useEffect(() => {
function handleModelTransform(
event: QuicklimeEvent<ModelTransformEventData>
event: QuicklimeEvent<ModelTransformEventData>,
) {
turretGroup.current.rotation.z = event.data.yaw;
gunGroup.current.rotation.x = event.data.pitch;
@@ -171,6 +171,49 @@ export function Model() {
turretModel.gun_origin.y,
]}
>
<group
position={[
-turretTankModel.turret_origin.x -
turretTrackModel.origin.x -
turretModel.gun_origin.x,
-turretTankModel.turret_origin.z -
turretTrackModel.origin.z -
turretModel.gun_origin.z,
-turretTankModel.turret_origin.y -
turretTrackModel.origin.y -
turretModel.gun_origin.y,
]}
>
{turretNodes.map((node) => {
const isCurrentMantlet =
node.name ===
`gun_${turretModel.guns[turret.turret.guns.at(-1)!.id].model_id.toString().padStart(2, "0")}_mask`;
// const isCurrentGun =
// node.name ===
// `gun_${gunModel.model_id.toString().padStart(2, "0")}`;
const isVisible = isCurrentMantlet;
if (!isVisible) return null;
return jsxTree(node, {
mesh(_, props, key) {
return (
<mesh
{...props}
key={key}
castShadow
receiveShadow
onPointerDown={(event) => {
enablePitchRotation.current = true;
handlePointerDown(event);
}}
/>
);
},
});
})}
</group>
<group
position={[
-gunTankModel.turret_origin.x -
@@ -185,13 +228,13 @@ export function Model() {
]}
>
{gunNodes.map((node) => {
const isCurrentMantlet =
node.name ===
`gun_${gunModel.model_id.toString().padStart(2, "0")}_mask`;
// const isCurrentMantlet =
// node.name ===
// `gun_${gunModel.model_id.toString().padStart(2, "0")}_mask`;
const isCurrentGun =
node.name ===
`gun_${gunModel.model_id.toString().padStart(2, "0")}`;
const isVisible = isCurrentGun || isCurrentMantlet;
const isVisible = isCurrentGun;
if (!isVisible) return null;
@@ -1,12 +1,16 @@
import { Table } from "@radix-ui/themes";
import type { RowHeaderCellProps } from "@radix-ui/themes/src/components/table.js";
export function StickyRowHeaderCell({ style, ...props }: RowHeaderCellProps) {
interface Props extends RowHeaderCellProps {
left?: string | number;
}
export function StickyRowHeaderCell({ style, left = 0, ...props }: Props) {
return (
<Table.RowHeaderCell
style={{
position: "sticky",
left: 0,
left,
backgroundColor: "var(--color-panel)",
...style,
}}
@@ -1,12 +1,12 @@
import { youtubers } from '@blitzkit/core';
import { Box, Flex, Heading, Link, Skeleton, Text } from '@radix-ui/themes';
import { awaitableReviews } from '../../core/awaitables/reviews';
import { Var } from '../../core/radix/var';
import { useLocale } from '../../hooks/useLocale';
import { Duel } from '../../stores/duel';
import type { MaybeSkeletonComponentProps } from '../../types/maybeSkeletonComponentProps';
import { PageWrapper } from '../PageWrapper';
import { VerifiedIcon } from '../VerifiedIcon';
import { youtubers } from "@blitzkit/core";
import { Box, Flex, Heading, Link, Skeleton, Text } from "@radix-ui/themes";
import { awaitableReviews } from "../../core/awaitables/reviews";
import { Var } from "../../core/radix/var";
import { useLocale } from "../../hooks/useLocale";
import { Duel } from "../../stores/duel";
import type { MaybeSkeletonComponentProps } from "../../types/maybeSkeletonComponentProps";
import { PageWrapper } from "../PageWrapper";
import { VerifiedIcon } from "../VerifiedIcon";
const reviews = await awaitableReviews;
@@ -47,22 +47,22 @@ export function VideoSection({ skeleton }: MaybeSkeletonComponentProps) {
<Flex
direction="column"
style={{
borderRadius: 'var(--radius-3)',
overflow: 'hidden',
borderRadius: "var(--radius-3)",
overflow: "hidden",
background: skeleton
? Var('color-panel-solid')
? Var("color-panel-solid")
: `url(${image})`,
backgroundPosition: '0 18px',
backgroundSize: 'cover',
backgroundPosition: "0 18px",
backgroundSize: "cover",
}}
>
<Box
style={{
background: skeleton ? 'transparent' : `url(${image})`,
backgroundSize: 'cover',
backgroundPosition: 'center',
background: skeleton ? "transparent" : `url(${image})`,
backgroundSize: "cover",
backgroundPosition: "center",
height: 128,
aspectRatio: '16 / 9',
aspectRatio: "16 / 9",
}}
/>
<Flex
@@ -70,8 +70,8 @@ export function VideoSection({ skeleton }: MaybeSkeletonComponentProps) {
width="100%"
justify="center"
style={{
backdropFilter: 'blur(4rem)',
WebkitBackdropFilter: 'blur(4rem)',
backdropFilter: "blur(4rem)",
WebkitBackdropFilter: "blur(4rem)",
}}
align="center"
gap="1"
-5
View File
@@ -55,11 +55,6 @@ export const tools: Record<string, Tool> = {
id: "session",
button: "blue",
},
discord: {
id: "discord",
href: "https://discord.com/application-directory/1097673957865443370",
button: "indigo",
},
tier_list: {
id: "tier-list",
strings: "tier_list",
@@ -8,7 +8,7 @@ const tanks = Object.values(tankDefinitions.tanks).filter((tank) => {
const turret = tank.turrets.at(-1)!;
const turretModel = tankModel.turrets[turret.id];
return tank.tier >= 10 && !turretModel.yaw;
return !turretModel.yaw;
});
export function curateMixer() {
@@ -76,7 +76,7 @@ export function tankCharacteristics(
tankModelDefinition: ModelDefinition;
equipmentDefinitions: EquipmentDefinitions;
provisionDefinitions: ProvisionDefinitions;
}
},
) {
const preset = equipmentDefinitions.presets[tank.equipment_preset];
const turretModelDefinition = tankModelDefinition.turrets[turret.id];
@@ -138,7 +138,7 @@ export function tankCharacteristics(
provisionDefinitions.provisions[provision].crew
? total + provisionDefinitions.provisions[provision].crew! / 100
: total,
0
0,
) + (hasImprovedVentilation ? 0.08 : 0);
const commanderMastery = 1 + provisionCrewBonus;
const loaderMastery =
@@ -158,13 +158,13 @@ export function tankCharacteristics(
coefficient(
[applyReactiveArmor && shell.type !== ShellType.HE, -0.27],
[applyDynamicArmor, -0.1],
[applySpallLiner && shell.type === ShellType.HE, -0.2]
[applySpallLiner && shell.type === ShellType.HE, -0.2],
);
const assaultDamageCoefficient =
gun.assault_ranges && gun.assault_ranges.types.includes(shell.type)
? gun.assault_ranges.ranges.find(
({ distance }) => distance >= assaultDistance
)?.factor ?? 0
? (gun.assault_ranges.ranges.find(
({ distance }) => distance >= assaultDistance,
)?.factor ?? 0)
: 1;
const moduleDamageCoefficient = coefficient([hasTungsten, 0.15]);
const reloadCoefficient =
@@ -178,16 +178,16 @@ export function tankCharacteristics(
const healthCoefficient = coefficient(
[hasSandbagArmor, 0.03],
[hasEnhancedSandbagArmor, 0.06],
[hasImprovedAssembly, 0.04]
[hasImprovedAssembly, 0.04],
);
const shellVelocityCoefficient = coefficient(
[hasSupercharger, 0.35],
[hasImprovedGunPowder, 0.3]
[hasImprovedGunPowder, 0.3],
);
const aimTimeCoefficient =
coefficient(
[hasEnhancedGunLayingDrive, -0.15],
[hasReticleCalibration, -0.3]
[hasReticleCalibration, -0.3],
) * coefficient([true, degressiveStat(gunnerMastery)]);
const dispersionStillCoefficient =
coefficient([hasRefinedGun, -0.15], [hasReticleCalibration, -0.3]) *
@@ -197,20 +197,20 @@ export function tankCharacteristics(
-0.12,
]);
const dispersionHullTraverseCoefficient = coefficient(
[hasVerticalStabilizer, -0.15],
[true, -crewSkills.smooth_turn / 100]
[hasVerticalStabilizer, -0.12],
[true, -crewSkills.smooth_turn / 100],
);
const dispersionAfterShotCoefficient = coefficient(
[hasVerticalStabilizer, -0.15],
[true, -crewSkills.soft_recoil / 100]
[hasVerticalStabilizer, -0.12],
[true, -crewSkills.soft_recoil / 100],
);
const dispersionMovingCoefficient = coefficient(
[hasVerticalStabilizer, -0.15],
[true, -crewSkills.smooth_driving / 100]
[hasVerticalStabilizer, -0.12],
[true, -crewSkills.smooth_driving / 100],
);
const dispersionTurretTraverseCoefficient = coefficient(
[hasVerticalStabilizer, -0.15],
[true, -crewSkills.smooth_turret * (2 / 100)]
[hasVerticalStabilizer, -0.12],
[true, -crewSkills.smooth_turret * (2 / 100)],
);
const enginePowerCoefficient = coefficient(
[
@@ -225,7 +225,7 @@ export function tankCharacteristics(
[hasStandardFuel, 0.03],
[hasImprovedFuel, 0.1],
[hasGearOil, 0.03],
[hasImprovedGearOil, 0.06]
[hasImprovedGearOil, 0.06],
);
const turretTraverseCoefficient =
coefficient([hasStandardFuel, 0.03], [hasImprovedFuel, 0.1]) *
@@ -234,7 +234,7 @@ export function tankCharacteristics(
coefficient(
[hasImprovedControl, 0.11],
[hasImprovedEnginePowerBoost, 0.01],
[true, crewSkills.virtuoso / 100]
[true, crewSkills.virtuoso / 100],
) * coefficient([true, progressiveStat(driverMastery)]);
const resistanceHardCoefficient =
@@ -253,22 +253,22 @@ export function tankCharacteristics(
tank.class === TankClass.HEAVY
? 0.06
: tank.class === TankClass.MEDIUM
? 0.08
: tank.class === TankClass.LIGHT
? 0.11
: 0.04,
? 0.08
: tank.class === TankClass.LIGHT
? 0.11
: 0.04,
]) * coefficient([true, progressiveStat(commanderMastery)]);
const fireChanceCoefficient = coefficient([hasProtectiveKit, -0.2]);
const speedForwardsSum = sum(
[hasImprovedEnginePowerBoost, 4],
[hasGearOil, 2],
[hasImprovedGearOil, 4]
[hasImprovedGearOil, 4],
);
const speedBackwardsSum = sum(
[hasImprovedEnginePowerBoost, 8],
[hasGearOil, 2],
[hasImprovedGearOil, 4]
[hasImprovedGearOil, 4],
);
const camouflageCoefficientMoving = sum(
[
@@ -278,8 +278,8 @@ export function tankCharacteristics(
tank.class === TankClass.TANK_DESTROYER
? 0.04
: tank.class === TankClass.HEAVY
? 0.03
: 0.02,
? 0.03
: 0.02,
]),
],
[
@@ -287,11 +287,11 @@ export function tankCharacteristics(
tank.class === TankClass.HEAVY
? 0.02
: tank.class === TankClass.MEDIUM
? 0.04
: tank.class === TankClass.LIGHT
? 0.07
: 0.1,
]
? 0.04
: tank.class === TankClass.LIGHT
? 0.07
: 0.1,
],
);
const camouflageCoefficientStill = sum(
[
@@ -302,10 +302,10 @@ export function tankCharacteristics(
tank.class === TankClass.TANK_DESTROYER
? 0.04
: tank.class === TankClass.HEAVY
? 0.03
: 0.02,
? 0.03
: 0.02,
],
[true, crewSkills.camouflage * (3 / 100)]
[true, crewSkills.camouflage * (3 / 100)],
),
],
[
@@ -313,18 +313,18 @@ export function tankCharacteristics(
tank.class === TankClass.HEAVY
? 0.04
: tank.class === TankClass.MEDIUM
? 0.08
: tank.class === TankClass.LIGHT
? 0.14
: 0.2,
]
? 0.08
: tank.class === TankClass.LIGHT
? 0.14
: 0.2,
],
);
const size = normalizeBoundingBox(
unionBoundingBox(
tankModelDefinition.bounding_box,
turretModelDefinition.bounding_box
)
turretModelDefinition.bounding_box,
),
);
const weightKg =
tank.weight + engine.weight + track.weight + turret.weight + gun.weight;
@@ -344,7 +344,7 @@ export function tankCharacteristics(
shell,
damageCoefficient,
reloadCoefficient,
intraClipCoefficient
intraClipCoefficient,
);
const intraClip =
gun.gun_type?.$case === "regular"
@@ -398,7 +398,7 @@ export function tankCharacteristics(
const shellReloads =
gun.gun_type!.$case === "auto_reloader"
? gun.gun_type!.value.shell_reloads.map(
(reload) => reload * reloadCoefficient
(reload) => reload * reloadCoefficient,
)
: undefined;
const shellReload =