mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-23 11:39:50 +02:00
fix: update all
This commit is contained in:
1 parent
bdea9fdab3
commit
e2f50871ec
86 files changed
+1939
-1537
No files matched your search
@@ -1,11 +1,5 @@
|
||||
import { ReloadIcon } from "@radix-ui/react-icons";
|
||||
import {
|
||||
Card,
|
||||
Flex,
|
||||
IconButton,
|
||||
Text,
|
||||
TextField,
|
||||
} from "@radix-ui/themes";
|
||||
import { Card, Flex, IconButton, Text, TextField } from "@radix-ui/themes";
|
||||
import { Html } from "@react-three/drei";
|
||||
import { useRef } from "react";
|
||||
import { radToDeg } from "three/src/math/MathUtils.js";
|
||||
@@ -107,12 +101,12 @@ export function ArmorPlateDisplay() {
|
||||
const track = tank.tracks[protagonist.track.id];
|
||||
|
||||
if (highlightArmor.name.startsWith("hull_")) {
|
||||
tank.armor.thickness[index] = thickness;
|
||||
tank.armor!.thickness[index] = thickness;
|
||||
} else if (highlightArmor.name.startsWith("turret_")) {
|
||||
turret.armor.thickness[index] = thickness;
|
||||
turret.armor!.thickness[index] = thickness;
|
||||
} else if (highlightArmor.name.startsWith("gun_")) {
|
||||
if (highlightArmor.name.includes("_armor_")) {
|
||||
gun.armor.thickness[index] = thickness;
|
||||
gun.armor!.thickness[index] = thickness;
|
||||
} else {
|
||||
gun.thickness = thickness;
|
||||
}
|
||||
@@ -162,15 +156,15 @@ export function ArmorPlateDisplay() {
|
||||
initialTank.tracks[protagonist.track.id];
|
||||
|
||||
if (highlightArmor.name.startsWith("hull_")) {
|
||||
tank.armor.thickness[index] =
|
||||
initialTank.armor.thickness[index];
|
||||
tank.armor!.thickness[index] =
|
||||
initialTank.armor!.thickness[index];
|
||||
} else if (highlightArmor.name.startsWith("turret_")) {
|
||||
turret.armor.thickness[index] =
|
||||
initialTurret.armor.thickness[index];
|
||||
turret.armor!.thickness[index] =
|
||||
initialTurret.armor!.thickness[index];
|
||||
} else if (highlightArmor.name.startsWith("gun_")) {
|
||||
if (highlightArmor.name.includes("_armor_")) {
|
||||
gun.armor.thickness[index] =
|
||||
initialGun.armor.thickness[index];
|
||||
gun.armor!.thickness[index] =
|
||||
initialGun.armor!.thickness[index];
|
||||
} else {
|
||||
gun.thickness = initialGun.thickness;
|
||||
}
|
||||
|
||||
@@ -24,11 +24,11 @@ export const PrimaryArmorScene = memo(() => {
|
||||
const trackModelDefinition = tankModelDefinition.tracks[track.id];
|
||||
const turretModelDefinition = tankModelDefinition.turrets[turret.id];
|
||||
const gunModelDefinition = turretModelDefinition.guns[gun.id];
|
||||
const hullOrigin = correctZYTuple(trackModelDefinition.origin);
|
||||
const turretOrigin = correctZYTuple(tankModelDefinition.turret_origin);
|
||||
const gunOrigin = correctZYTuple(turretModelDefinition.gun_origin);
|
||||
const hullOrigin = correctZYTuple(trackModelDefinition.origin!);
|
||||
const turretOrigin = correctZYTuple(tankModelDefinition.turret_origin!);
|
||||
const gunOrigin = correctZYTuple(turretModelDefinition.gun_origin!);
|
||||
const isDynamicArmorActive = Duel.use((state) =>
|
||||
state.protagonist.consumables.includes(73)
|
||||
state.protagonist.consumables.includes(73),
|
||||
);
|
||||
|
||||
useTankTransform(track, turret, turretContainer, gunContainer);
|
||||
@@ -41,8 +41,8 @@ export const PrimaryArmorScene = memo(() => {
|
||||
const isVisible = isHull;
|
||||
const armorId = nameToArmorId(node.name);
|
||||
const { spaced, thickness } = resolveArmor(
|
||||
tankModelDefinition.armor,
|
||||
armorId
|
||||
tankModelDefinition.armor!,
|
||||
armorId,
|
||||
);
|
||||
|
||||
if (
|
||||
@@ -69,13 +69,13 @@ export const PrimaryArmorScene = memo(() => {
|
||||
const isCurrentTurret = node.name.startsWith(
|
||||
`turret_${turretModelDefinition.model_id
|
||||
.toString()
|
||||
.padStart(2, "0")}`
|
||||
.padStart(2, "0")}`,
|
||||
);
|
||||
const isVisible = isCurrentTurret;
|
||||
const armorId = nameToArmorId(node.name);
|
||||
const { spaced, thickness } = resolveArmor(
|
||||
turretModelDefinition.armor,
|
||||
armorId
|
||||
turretModelDefinition.armor!,
|
||||
armorId,
|
||||
);
|
||||
|
||||
if (
|
||||
@@ -103,13 +103,13 @@ export const PrimaryArmorScene = memo(() => {
|
||||
<group ref={gunContainer}>
|
||||
{armorNodes.map((node) => {
|
||||
const isCurrentGun = node.name.startsWith(
|
||||
`gun_${gunModelDefinition.model_id.toString().padStart(2, "0")}`
|
||||
`gun_${gunModelDefinition.model_id.toString().padStart(2, "0")}`,
|
||||
);
|
||||
const isVisible = isCurrentGun;
|
||||
const armorId = nameToArmorId(node.name);
|
||||
const { spaced, thickness } = resolveArmor(
|
||||
gunModelDefinition.armor,
|
||||
armorId
|
||||
gunModelDefinition.armor!,
|
||||
armorId,
|
||||
);
|
||||
|
||||
if (
|
||||
|
||||
+15
-15
@@ -88,10 +88,10 @@ export function PrimaryArmorSceneComponent({
|
||||
|
||||
material.uniforms.caliber.value = shell.caliber;
|
||||
material.uniforms.ricochet.value = degToRad(
|
||||
isExplosive(shell.type) ? 90 : shell.ricochet!
|
||||
isExplosive(shell.type) ? 90 : shell.ricochet!,
|
||||
);
|
||||
material.uniforms.normalization.value = degToRad(
|
||||
shell.normalization ?? 0
|
||||
shell.normalization ?? 0,
|
||||
);
|
||||
material.uniforms.isExplosive.value = isExplosive(shell.type);
|
||||
material.uniforms.canSplash.value = canSplash(shell.type);
|
||||
@@ -100,11 +100,11 @@ export function PrimaryArmorSceneComponent({
|
||||
|
||||
handleProtagonistEquipmentChange(
|
||||
Duel.state.protagonist.equipmentMatrix,
|
||||
true
|
||||
true,
|
||||
);
|
||||
handleAntagonistEquipmentChange(
|
||||
Duel.state.antagonist.equipmentMatrix,
|
||||
true
|
||||
true,
|
||||
);
|
||||
|
||||
invalidate();
|
||||
@@ -124,12 +124,12 @@ export function PrimaryArmorSceneComponent({
|
||||
}
|
||||
function handleProtagonistEquipmentChange(
|
||||
equipment: EquipmentMatrix,
|
||||
noInvalidate = false
|
||||
noInvalidate = false,
|
||||
) {
|
||||
const hasEnhancedArmor = hasEquipment(
|
||||
110,
|
||||
Duel.state.protagonist.tank.equipment_preset,
|
||||
equipment
|
||||
equipment,
|
||||
);
|
||||
material.uniforms.thickness.value = hasEnhancedArmor
|
||||
? thickness * 1.03
|
||||
@@ -139,16 +139,16 @@ export function PrimaryArmorSceneComponent({
|
||||
}
|
||||
function handleAntagonistEquipmentChange(
|
||||
equipment: EquipmentMatrix,
|
||||
noInvalidate = false
|
||||
noInvalidate = false,
|
||||
) {
|
||||
const tankopediaEphemeral = Tankopedia.state;
|
||||
const shell =
|
||||
tankopediaEphemeral.customShell ?? Duel.state.antagonist.shell;
|
||||
const penetration = shell.penetration.near;
|
||||
const penetration = shell.penetration!.near;
|
||||
const hasCalibratedShells = hasEquipment(
|
||||
103,
|
||||
Duel.state.antagonist.tank.equipment_preset,
|
||||
equipment
|
||||
equipment,
|
||||
);
|
||||
|
||||
material.uniforms.penetration.value =
|
||||
@@ -165,7 +165,7 @@ export function PrimaryArmorSceneComponent({
|
||||
handleShellChange();
|
||||
handleGreenPenetrationChange(TankopediaPersistent.state.greenPenetration);
|
||||
handleAdvancedHighlightingChange(
|
||||
TankopediaPersistent.state.advancedHighlighting
|
||||
TankopediaPersistent.state.advancedHighlighting,
|
||||
);
|
||||
handleOpaqueChange(TankopediaPersistent.state.opaque);
|
||||
handleWireframeChange(TankopediaPersistent.state.wireframe);
|
||||
@@ -179,20 +179,20 @@ export function PrimaryArmorSceneComponent({
|
||||
Tankopedia.on((state) => state.customShell, handleShellChange),
|
||||
TankopediaPersistent.on(
|
||||
(state) => state.greenPenetration,
|
||||
handleGreenPenetrationChange
|
||||
handleGreenPenetrationChange,
|
||||
),
|
||||
TankopediaPersistent.on(
|
||||
(state) => state.advancedHighlighting,
|
||||
handleAdvancedHighlightingChange
|
||||
handleAdvancedHighlightingChange,
|
||||
),
|
||||
TankopediaPersistent.on((state) => state.opaque, handleOpaqueChange),
|
||||
Duel.on(
|
||||
(state) => state.protagonist.equipmentMatrix,
|
||||
(equipment) => handleProtagonistEquipmentChange(equipment)
|
||||
(equipment) => handleProtagonistEquipmentChange(equipment),
|
||||
),
|
||||
Duel.on(
|
||||
(state) => state.antagonist.equipmentMatrix,
|
||||
(equipment) => handleAntagonistEquipmentChange(equipment)
|
||||
(equipment) => handleAntagonistEquipmentChange(equipment),
|
||||
),
|
||||
() => transitionEvent.off(handleTransitionEvent),
|
||||
];
|
||||
@@ -204,7 +204,7 @@ export function PrimaryArmorSceneComponent({
|
||||
|
||||
useFrame(({ gl, camera }) => {
|
||||
gl.getSize(material.uniforms.resolution.value).multiplyScalar(
|
||||
gl.getPixelRatio()
|
||||
gl.getPixelRatio(),
|
||||
);
|
||||
material.uniforms.spacedArmorBuffer.value = spacedArmorRenderTarget.texture;
|
||||
material.uniforms.spacedArmorDepth.value =
|
||||
|
||||
@@ -37,15 +37,15 @@ export const SpacedArmorScene = memo<SpacedArmorSceneProps>(({ scene }) => {
|
||||
const trackModelDefinition = tankModelDefinition.tracks[track.id];
|
||||
const turretModelDefinition = tankModelDefinition.turrets[turret.id];
|
||||
const gunModelDefinition = turretModelDefinition.guns[gun.id];
|
||||
const hullOrigin = correctZYTuple(trackModelDefinition.origin);
|
||||
const turretOrigin = correctZYTuple(tankModelDefinition.turret_origin);
|
||||
const gunOrigin = correctZYTuple(turretModelDefinition.gun_origin);
|
||||
const hullOrigin = correctZYTuple(trackModelDefinition.origin!);
|
||||
const turretOrigin = correctZYTuple(tankModelDefinition.turret_origin!);
|
||||
const gunOrigin = correctZYTuple(turretModelDefinition.gun_origin!);
|
||||
const maskOrigin =
|
||||
gunModelDefinition.mask === undefined
|
||||
? undefined
|
||||
: gunModelDefinition.mask + hullOrigin.y + turretOrigin.y + gunOrigin.y;
|
||||
const isDynamicArmorActive = Duel.use((state) =>
|
||||
state.protagonist.consumables.includes(73)
|
||||
state.protagonist.consumables.includes(73),
|
||||
);
|
||||
|
||||
useTankTransform(track, turret, turretContainer, gunContainer);
|
||||
@@ -58,8 +58,8 @@ export const SpacedArmorScene = memo<SpacedArmorSceneProps>(({ scene }) => {
|
||||
const isVisible = isHull;
|
||||
const armorId = nameToArmorId(node.name);
|
||||
const { spaced, thickness } = resolveArmor(
|
||||
tankModelDefinition.armor,
|
||||
armorId
|
||||
tankModelDefinition.armor!,
|
||||
armorId,
|
||||
);
|
||||
|
||||
if (
|
||||
@@ -104,13 +104,13 @@ export const SpacedArmorScene = memo<SpacedArmorSceneProps>(({ scene }) => {
|
||||
<group ref={turretContainer}>
|
||||
{armorNodes.map((node) => {
|
||||
const isCurrentTurret = node.name.startsWith(
|
||||
`turret_${turretModelDefinition.model_id.toString().padStart(2, "0")}`
|
||||
`turret_${turretModelDefinition.model_id.toString().padStart(2, "0")}`,
|
||||
);
|
||||
const isVisible = isCurrentTurret;
|
||||
const armorId = nameToArmorId(node.name);
|
||||
const { spaced, thickness } = resolveArmor(
|
||||
turretModelDefinition.armor,
|
||||
armorId
|
||||
turretModelDefinition.armor!,
|
||||
armorId,
|
||||
);
|
||||
|
||||
if (
|
||||
@@ -139,13 +139,13 @@ export const SpacedArmorScene = memo<SpacedArmorSceneProps>(({ scene }) => {
|
||||
<group ref={gunContainer}>
|
||||
{armorNodes.map((node) => {
|
||||
const isCurrentGun = node.name.startsWith(
|
||||
`gun_${gunModelDefinition.model_id.toString().padStart(2, "0")}`
|
||||
`gun_${gunModelDefinition.model_id.toString().padStart(2, "0")}`,
|
||||
);
|
||||
const isVisible = isCurrentGun;
|
||||
const armorId = nameToArmorId(node.name);
|
||||
const { spaced, thickness } = resolveArmor(
|
||||
gunModelDefinition.armor,
|
||||
armorId
|
||||
gunModelDefinition.armor!,
|
||||
armorId,
|
||||
);
|
||||
|
||||
if (
|
||||
|
||||
+6
-6
@@ -50,13 +50,13 @@ export function SpacedArmorSubExternal({
|
||||
const tankopediaEphemeral = Tankopedia.state;
|
||||
const shell =
|
||||
tankopediaEphemeral.customShell ?? Duel.state.antagonist.shell;
|
||||
material.uniforms.penetration.value = shell.penetration.near;
|
||||
material.uniforms.penetration.value = shell.penetration!.near;
|
||||
}
|
||||
function handleProtagonistEquipmentChange(equipment: EquipmentMatrix) {
|
||||
const hasEnhancedArmor = hasEquipment(
|
||||
110,
|
||||
Duel.state.protagonist.tank.equipment_preset,
|
||||
equipment
|
||||
equipment,
|
||||
);
|
||||
material.uniforms.thickness.value = hasEnhancedArmor
|
||||
? thickness * 1.03
|
||||
@@ -66,11 +66,11 @@ export function SpacedArmorSubExternal({
|
||||
const tankopediaEphemeral = Tankopedia.state;
|
||||
const shell =
|
||||
tankopediaEphemeral.customShell ?? Duel.state.antagonist.shell;
|
||||
const penetration = shell.penetration.near;
|
||||
const penetration = shell.penetration!.near;
|
||||
const hasCalibratedShells = hasEquipment(
|
||||
103,
|
||||
Duel.state.antagonist.tank.equipment_preset,
|
||||
equipment
|
||||
equipment,
|
||||
);
|
||||
|
||||
material.uniforms.penetration.value =
|
||||
@@ -87,11 +87,11 @@ export function SpacedArmorSubExternal({
|
||||
Tankopedia.on((state) => state.customShell, handleShellChange),
|
||||
Duel.on(
|
||||
(state) => state.protagonist.equipmentMatrix,
|
||||
handleProtagonistEquipmentChange
|
||||
handleProtagonistEquipmentChange,
|
||||
),
|
||||
Duel.on(
|
||||
(state) => state.antagonist.equipmentMatrix,
|
||||
handleAntagonistEquipmentChange
|
||||
handleAntagonistEquipmentChange,
|
||||
),
|
||||
];
|
||||
|
||||
|
||||
+8
-8
@@ -53,20 +53,20 @@ export function SpacedArmorSubSpaced({
|
||||
const shell =
|
||||
tankopediaEphemeral.customShell ?? Duel.state.antagonist.shell;
|
||||
|
||||
material.uniforms.penetration.value = shell.penetration.near;
|
||||
material.uniforms.penetration.value = shell.penetration!.near;
|
||||
material.uniforms.caliber.value = shell.caliber;
|
||||
material.uniforms.ricochet.value = degToRad(
|
||||
isExplosive(shell.type) ? 90 : shell.ricochet!
|
||||
isExplosive(shell.type) ? 90 : shell.ricochet!,
|
||||
);
|
||||
material.uniforms.normalization.value = degToRad(
|
||||
shell.normalization ?? 0
|
||||
shell.normalization ?? 0,
|
||||
);
|
||||
}
|
||||
function handleProtagonistEquipmentChange(equipment: EquipmentMatrix) {
|
||||
const hasEnhancedArmor = hasEquipment(
|
||||
110,
|
||||
Duel.state.protagonist.tank.equipment_preset,
|
||||
equipment
|
||||
equipment,
|
||||
);
|
||||
material.uniforms.thickness.value = hasEnhancedArmor
|
||||
? thickness * 1.03
|
||||
@@ -76,11 +76,11 @@ export function SpacedArmorSubSpaced({
|
||||
const tankopediaEphemeral = Tankopedia.state;
|
||||
const shell =
|
||||
tankopediaEphemeral.customShell ?? Duel.state.antagonist.shell;
|
||||
const penetration = shell.penetration.near;
|
||||
const penetration = shell.penetration!.near;
|
||||
const hasCalibratedShells = hasEquipment(
|
||||
103,
|
||||
Duel.state.antagonist.tank.equipment_preset,
|
||||
equipment
|
||||
equipment,
|
||||
);
|
||||
|
||||
material.uniforms.penetration.value =
|
||||
@@ -97,11 +97,11 @@ export function SpacedArmorSubSpaced({
|
||||
Tankopedia.on((state) => state.customShell, handleShellChange),
|
||||
Duel.on(
|
||||
(state) => state.protagonist.equipmentMatrix,
|
||||
handleProtagonistEquipmentChange
|
||||
handleProtagonistEquipmentChange,
|
||||
),
|
||||
Duel.on(
|
||||
(state) => state.antagonist.equipmentMatrix,
|
||||
handleAntagonistEquipmentChange
|
||||
handleAntagonistEquipmentChange,
|
||||
),
|
||||
];
|
||||
|
||||
|
||||
+30
-24
@@ -83,16 +83,18 @@ export function SpacedArmorSceneComponent({
|
||||
point: Vector3,
|
||||
intersections: Intersection[],
|
||||
allowRicochet: boolean,
|
||||
remainingPenetrationInput?: number
|
||||
remainingPenetrationInput?: number,
|
||||
) => {
|
||||
const { customShell } = Tankopedia.state;
|
||||
const shell = customShell ?? Duel.state.antagonist.shell;
|
||||
const cameraNormal = camera.position.clone().sub(point).normalize();
|
||||
const shot: Shot = {
|
||||
splashRadius:
|
||||
shell.type === ShellType.HE ? shell.explosion_radius : undefined,
|
||||
shell.type === ShellType.SHELL_TYPE_HE
|
||||
? shell.explosion_radius
|
||||
: undefined,
|
||||
damage: -1,
|
||||
containsGaps: shell.type === ShellType.HEAT,
|
||||
containsGaps: shell.type === ShellType.SHELL_TYPE_HEAT,
|
||||
in: {
|
||||
surfaceNormal: cameraNormal,
|
||||
layers: [],
|
||||
@@ -103,21 +105,21 @@ export function SpacedArmorSceneComponent({
|
||||
const hasCalibratedShells = hasEquipment(
|
||||
103,
|
||||
Duel.state.antagonist.tank.equipment_preset,
|
||||
Duel.state.antagonist.equipmentMatrix
|
||||
Duel.state.antagonist.equipmentMatrix,
|
||||
);
|
||||
const hasEnhancedArmor = hasEquipment(
|
||||
110,
|
||||
Duel.state.protagonist.tank.equipment_preset,
|
||||
Duel.state.protagonist.equipmentMatrix
|
||||
Duel.state.protagonist.equipmentMatrix,
|
||||
);
|
||||
const penetration =
|
||||
shell.penetration.near *
|
||||
shell.penetration!.near *
|
||||
resolvePenetrationCoefficient(hasCalibratedShells, shell.type);
|
||||
const thicknessCoefficient = hasEnhancedArmor ? 1.03 : 1;
|
||||
const filteredIntersections = intersections.filter(
|
||||
(intersection) =>
|
||||
"type" in intersection.object.userData &&
|
||||
!discardClippingPlane(intersection.object, intersection.point)
|
||||
!discardClippingPlane(intersection.object, intersection.point),
|
||||
) as unknown as Intersection<Object3D & { userData: ArmorUserData }>[];
|
||||
const encounteredExternalModuleVariants: ExternalModuleVariant[] = [];
|
||||
const noDuplicateIntersections: typeof filteredIntersections = [];
|
||||
@@ -126,12 +128,12 @@ export function SpacedArmorSceneComponent({
|
||||
if (intersection.object.userData.type === ArmorType.External) {
|
||||
if (
|
||||
!encounteredExternalModuleVariants.includes(
|
||||
intersection.object.userData.variant
|
||||
intersection.object.userData.variant,
|
||||
)
|
||||
) {
|
||||
noDuplicateIntersections.push(intersection);
|
||||
encounteredExternalModuleVariants.push(
|
||||
intersection.object.userData.variant
|
||||
intersection.object.userData.variant,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
@@ -159,10 +161,10 @@ export function SpacedArmorSceneComponent({
|
||||
const surfaceNormal = intersection
|
||||
.normal!.clone()
|
||||
.applyQuaternion(
|
||||
intersection.object.getWorldQuaternion(new Quaternion())
|
||||
intersection.object.getWorldQuaternion(new Quaternion()),
|
||||
);
|
||||
|
||||
if (shell.type === ShellType.HEAT && index > 0) {
|
||||
if (shell.type === ShellType.SHELL_TYPE_HEAT && index > 0) {
|
||||
const lastLayer = shot.in.layers.at(-1) as ShotLayerBase;
|
||||
const distance = lastLayer.point.distanceTo(intersection.point);
|
||||
remainingPenetration -= 0.5 * remainingPenetration * distance;
|
||||
@@ -181,7 +183,7 @@ export function SpacedArmorSceneComponent({
|
||||
const thickness = layer.thickness * thicknessCoefficient;
|
||||
remainingPenetration -= thickness;
|
||||
const blocked =
|
||||
remainingPenetration <= 0 || shell.type === ShellType.HE;
|
||||
remainingPenetration <= 0 || shell.type === ShellType.SHELL_TYPE_HE;
|
||||
|
||||
shot.in.layers.push({
|
||||
type: ArmorType.External,
|
||||
@@ -190,7 +192,7 @@ export function SpacedArmorSceneComponent({
|
||||
point: intersection.point,
|
||||
surfaceNormal,
|
||||
status:
|
||||
shell.type === ShellType.HE
|
||||
shell.type === ShellType.SHELL_TYPE_HE
|
||||
? "blocked"
|
||||
: blocked
|
||||
? "blocked"
|
||||
@@ -200,7 +202,7 @@ export function SpacedArmorSceneComponent({
|
||||
} else {
|
||||
const thickness = layer.thickness * thicknessCoefficient;
|
||||
const ricochet = degToRad(
|
||||
isExplosive(shell.type) ? 90 : shell.ricochet!
|
||||
isExplosive(shell.type) ? 90 : shell.ricochet!,
|
||||
);
|
||||
const angle = surfaceNormal.angleTo(cameraNormal);
|
||||
|
||||
@@ -241,7 +243,8 @@ export function SpacedArmorSceneComponent({
|
||||
point: intersection.point,
|
||||
surfaceNormal: intersection.face!.normal,
|
||||
status:
|
||||
shell.type === ShellType.HE && layer.type !== ArmorType.Primary
|
||||
shell.type === ShellType.SHELL_TYPE_HE &&
|
||||
layer.type !== ArmorType.Primary
|
||||
? "blocked"
|
||||
: blocked
|
||||
? "blocked"
|
||||
@@ -251,7 +254,10 @@ export function SpacedArmorSceneComponent({
|
||||
}
|
||||
}
|
||||
|
||||
if (shell.type !== ShellType.HE && remainingPenetration <= 0) {
|
||||
if (
|
||||
shell.type !== ShellType.SHELL_TYPE_HE &&
|
||||
remainingPenetration <= 0
|
||||
) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -266,7 +272,7 @@ export function SpacedArmorSceneComponent({
|
||||
if (!lastLayer || !firstLayer) {
|
||||
return null;
|
||||
} else {
|
||||
if (shell.type === ShellType.HE) {
|
||||
if (shell.type === ShellType.SHELL_TYPE_HE) {
|
||||
const totalSpacedArmorThickness = shot.in.layers.reduce(
|
||||
(acc, layer) => {
|
||||
if (layer.type === null || layer.type === ArmorType.Primary) {
|
||||
@@ -279,10 +285,10 @@ export function SpacedArmorSceneComponent({
|
||||
|
||||
return acc + layer.thicknessAngled;
|
||||
},
|
||||
0
|
||||
0,
|
||||
);
|
||||
const distanceFromSpacedArmor = firstLayer.point.distanceTo(
|
||||
lastLayer.point
|
||||
lastLayer.point,
|
||||
);
|
||||
const finalDamage = Math.max(
|
||||
0,
|
||||
@@ -291,7 +297,7 @@ export function SpacedArmorSceneComponent({
|
||||
(1 - distanceFromSpacedArmor / shell.explosion_radius!) -
|
||||
1.1 *
|
||||
(lastLayer.thicknessAngled +
|
||||
Math.min(penetration, totalSpacedArmorThickness))
|
||||
Math.min(penetration, totalSpacedArmorThickness)),
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -328,20 +334,20 @@ export function SpacedArmorSceneComponent({
|
||||
.sub(
|
||||
lastLayer.surfaceNormal
|
||||
.clone()
|
||||
.multiplyScalar(2 * shellNormal.dot(lastLayer.surfaceNormal))
|
||||
.multiplyScalar(2 * shellNormal.dot(lastLayer.surfaceNormal)),
|
||||
);
|
||||
|
||||
caster.set(lastLayer.point, ricochetNormal);
|
||||
|
||||
const ricochetIntersections = caster.intersectObjects(
|
||||
props.scene.children,
|
||||
true
|
||||
true,
|
||||
);
|
||||
const outShot = shoot(
|
||||
lastLayer.point,
|
||||
ricochetIntersections,
|
||||
false,
|
||||
remainingPenetration
|
||||
remainingPenetration,
|
||||
);
|
||||
|
||||
shot.in.status = "ricochet";
|
||||
@@ -363,7 +369,7 @@ export function SpacedArmorSceneComponent({
|
||||
|
||||
return shot;
|
||||
},
|
||||
[camera]
|
||||
[camera],
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -43,9 +43,9 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
|
||||
const trackModelDefinition = tankModelDefinition.tracks[track.id];
|
||||
const turretModelDefinition = tankModelDefinition.turrets[turret.id];
|
||||
const gunModelDefinition = turretModelDefinition.guns[gun.id];
|
||||
const hullOrigin = correctZYTuple(trackModelDefinition.origin);
|
||||
const turretOrigin = correctZYTuple(tankModelDefinition.turret_origin);
|
||||
const gunOrigin = correctZYTuple(turretModelDefinition.gun_origin);
|
||||
const hullOrigin = correctZYTuple(trackModelDefinition.origin!);
|
||||
const turretOrigin = correctZYTuple(tankModelDefinition.turret_origin!);
|
||||
const gunOrigin = correctZYTuple(turretModelDefinition.gun_origin!);
|
||||
const canvas = useThree((state) => state.gl.domElement);
|
||||
|
||||
const maskOrigin =
|
||||
@@ -53,16 +53,16 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
|
||||
? undefined
|
||||
: gunModelDefinition.mask + hullOrigin.y + turretOrigin.y + gunOrigin.y;
|
||||
const showPrimaryArmor = TankopediaPersistent.use(
|
||||
(state) => state.showPrimaryArmor
|
||||
(state) => state.showPrimaryArmor,
|
||||
);
|
||||
const showSpacedArmor = TankopediaPersistent.use(
|
||||
(state) => state.showSpacedArmor
|
||||
(state) => state.showSpacedArmor,
|
||||
);
|
||||
const showExternalModules = TankopediaPersistent.use(
|
||||
(state) => state.showExternalModules
|
||||
(state) => state.showExternalModules,
|
||||
);
|
||||
const isDynamicArmorActive = Duel.use((state) =>
|
||||
state.protagonist.consumables.includes(73)
|
||||
state.protagonist.consumables.includes(73),
|
||||
);
|
||||
|
||||
useTankTransform(track, turret, turretContainer, gunContainer);
|
||||
@@ -75,8 +75,8 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
|
||||
const isVisible = isHull;
|
||||
const armorId = nameToArmorId(node.name);
|
||||
const { spaced, thickness } = resolveArmor(
|
||||
tankModelDefinition.armor,
|
||||
armorId
|
||||
tankModelDefinition.armor!,
|
||||
armorId,
|
||||
);
|
||||
|
||||
if (
|
||||
@@ -127,13 +127,13 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
|
||||
<group ref={turretContainer}>
|
||||
{armorNodes.map((node) => {
|
||||
const isCurrentTurret = node.name.startsWith(
|
||||
`turret_${turretModelDefinition.model_id.toString().padStart(2, "0")}`
|
||||
`turret_${turretModelDefinition.model_id.toString().padStart(2, "0")}`,
|
||||
);
|
||||
const isVisible = isCurrentTurret;
|
||||
const armorId = nameToArmorId(node.name);
|
||||
const { spaced, thickness } = resolveArmor(
|
||||
turretModelDefinition.armor,
|
||||
armorId
|
||||
turretModelDefinition.armor!,
|
||||
armorId,
|
||||
);
|
||||
|
||||
if (
|
||||
@@ -168,12 +168,12 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
|
||||
const hasImprovedVerticalStabilizer = hasEquipment(
|
||||
122,
|
||||
Duel.state.protagonist.tank.equipment_preset,
|
||||
Duel.state.protagonist.equipmentMatrix
|
||||
Duel.state.protagonist.equipmentMatrix,
|
||||
);
|
||||
const hasDownImprovedVerticalStabilizer = hasEquipment(
|
||||
124,
|
||||
Duel.state.protagonist.tank.equipment_preset,
|
||||
Duel.state.protagonist.equipmentMatrix
|
||||
Duel.state.protagonist.equipmentMatrix,
|
||||
);
|
||||
const boundingRect = canvas.getBoundingClientRect();
|
||||
|
||||
@@ -184,10 +184,10 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
|
||||
modelTransformEvent.last!.pitch,
|
||||
modelTransformEvent.last!.yaw +
|
||||
delta.x * (Math.PI / boundingRect.width),
|
||||
gunModelDefinition.pitch,
|
||||
gunModelDefinition.pitch!,
|
||||
turretModelDefinition.yaw,
|
||||
hasImprovedVerticalStabilizer,
|
||||
hasDownImprovedVerticalStabilizer
|
||||
hasDownImprovedVerticalStabilizer,
|
||||
);
|
||||
modelTransformEvent.dispatch({ pitch, yaw });
|
||||
}
|
||||
@@ -216,13 +216,13 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
|
||||
<group ref={gunContainer}>
|
||||
{armorNodes.map((node) => {
|
||||
const isCurrentGun = node.name.startsWith(
|
||||
`gun_${gunModelDefinition.model_id.toString().padStart(2, "0")}`
|
||||
`gun_${gunModelDefinition.model_id.toString().padStart(2, "0")}`,
|
||||
);
|
||||
const isVisible = isCurrentGun;
|
||||
const armorId = nameToArmorId(node.name);
|
||||
const { spaced, thickness } = resolveArmor(
|
||||
gunModelDefinition.armor,
|
||||
armorId
|
||||
gunModelDefinition.armor!,
|
||||
armorId,
|
||||
);
|
||||
|
||||
if (
|
||||
@@ -256,12 +256,12 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
|
||||
const hasImprovedVerticalStabilizer = hasEquipment(
|
||||
122,
|
||||
Duel.state.protagonist.tank.equipment_preset,
|
||||
Duel.state.protagonist.equipmentMatrix
|
||||
Duel.state.protagonist.equipmentMatrix,
|
||||
);
|
||||
const hasDownImprovedVerticalStabilizer = hasEquipment(
|
||||
124,
|
||||
Duel.state.protagonist.tank.equipment_preset,
|
||||
Duel.state.protagonist.equipmentMatrix
|
||||
Duel.state.protagonist.equipmentMatrix,
|
||||
);
|
||||
const boundingRect = canvas.getBoundingClientRect();
|
||||
delta.set(event.clientX, event.clientY).sub(position);
|
||||
@@ -272,10 +272,10 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
|
||||
delta.y * (Math.PI / boundingRect.height),
|
||||
modelTransformEvent.last!.yaw +
|
||||
delta.x * (Math.PI / boundingRect.width),
|
||||
gunModelDefinition.pitch,
|
||||
gunModelDefinition.pitch!,
|
||||
turretModelDefinition.yaw,
|
||||
hasImprovedVerticalStabilizer,
|
||||
hasDownImprovedVerticalStabilizer
|
||||
hasDownImprovedVerticalStabilizer,
|
||||
);
|
||||
modelTransformEvent.dispatch({ pitch, yaw });
|
||||
}
|
||||
@@ -332,12 +332,12 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
|
||||
const hasImprovedVerticalStabilizer = hasEquipment(
|
||||
122,
|
||||
Duel.state.protagonist.tank.equipment_preset,
|
||||
Duel.state.protagonist.equipmentMatrix
|
||||
Duel.state.protagonist.equipmentMatrix,
|
||||
);
|
||||
const hasDownImprovedVerticalStabilizer = hasEquipment(
|
||||
124,
|
||||
Duel.state.protagonist.tank.equipment_preset,
|
||||
Duel.state.protagonist.equipmentMatrix
|
||||
Duel.state.protagonist.equipmentMatrix,
|
||||
);
|
||||
const boundingRect = canvas.getBoundingClientRect();
|
||||
delta.set(event.clientX, event.clientY).sub(position);
|
||||
@@ -348,10 +348,10 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
|
||||
delta.y * (Math.PI / boundingRect.height),
|
||||
modelTransformEvent.last!.yaw +
|
||||
delta.x * (Math.PI / boundingRect.width),
|
||||
gunModelDefinition.pitch,
|
||||
gunModelDefinition.pitch!,
|
||||
turretModelDefinition.yaw,
|
||||
hasImprovedVerticalStabilizer,
|
||||
hasDownImprovedVerticalStabilizer
|
||||
hasDownImprovedVerticalStabilizer,
|
||||
);
|
||||
modelTransformEvent.dispatch({ pitch, yaw });
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { TankClass } from '@blitzkit/core';
|
||||
import type { ComponentProps, ReactNode } from 'react';
|
||||
import { ClassHeavy } from './components/ClassHeavy';
|
||||
import { ClassLight } from './components/ClassLight';
|
||||
import { ClassMedium } from './components/ClassMedium';
|
||||
import { ClassTankDestroyer } from './components/ClassTankDestroyer';
|
||||
import { TankClass } from "@blitzkit/core";
|
||||
import type { ComponentProps, ReactNode } from "react";
|
||||
import { ClassHeavy } from "./components/ClassHeavy";
|
||||
import { ClassLight } from "./components/ClassLight";
|
||||
import { ClassMedium } from "./components/ClassMedium";
|
||||
import { ClassTankDestroyer } from "./components/ClassTankDestroyer";
|
||||
|
||||
export const classIcons: Record<
|
||||
TankClass,
|
||||
(props: ComponentProps<'svg'>) => ReactNode
|
||||
(props: ComponentProps<"svg">) => ReactNode
|
||||
> = {
|
||||
[TankClass.TANK_DESTROYER]: ClassTankDestroyer,
|
||||
[TankClass.HEAVY]: ClassHeavy,
|
||||
[TankClass.LIGHT]: ClassLight,
|
||||
[TankClass.MEDIUM]: ClassMedium,
|
||||
[TankClass.TANK_CLASS_TANK_DESTROYER]: ClassTankDestroyer,
|
||||
[TankClass.TANK_CLASS_HEAVY]: ClassHeavy,
|
||||
[TankClass.TANK_CLASS_LIGHT]: ClassLight,
|
||||
[TankClass.TANK_CLASS_MEDIUM]: ClassMedium,
|
||||
};
|
||||
@@ -15,7 +15,7 @@ interface BodyProps {
|
||||
export function Body({ stats }: BodyProps) {
|
||||
const members = CompareEphemeral.use((state) => state.members);
|
||||
const hasNonRegularGun = members.some(
|
||||
({ gun }) => gun.gun_type!.$case !== "regular"
|
||||
({ gun }) => gun.gun_type!.$case !== "regular",
|
||||
);
|
||||
const { unwrap, strings } = useLocale();
|
||||
|
||||
@@ -55,7 +55,7 @@ export function Body({ stats }: BodyProps) {
|
||||
}}
|
||||
>
|
||||
<img
|
||||
alt={unwrap(thisShell.name)}
|
||||
alt={unwrap(thisShell.name!)}
|
||||
width={16}
|
||||
height={16}
|
||||
src={asset(`icons/shells/${thisShell.icon}.webp`)}
|
||||
|
||||
@@ -294,7 +294,7 @@ export function CompareTable({ stats }: CompareTableProps) {
|
||||
<img
|
||||
alt={unwrap(
|
||||
provisionDefinitions.provisions[provision]
|
||||
.name,
|
||||
.name!,
|
||||
)}
|
||||
key={provision}
|
||||
src={asset(
|
||||
@@ -501,7 +501,7 @@ export function CompareTable({ stats }: CompareTableProps) {
|
||||
alt={unwrap(
|
||||
consumableDefinitions.consumables[
|
||||
consumable
|
||||
].name,
|
||||
].name!,
|
||||
)}
|
||||
key={consumable}
|
||||
src={asset(
|
||||
|
||||
@@ -119,7 +119,7 @@ export function TankCard({ index, tank }: TankCardProps) {
|
||||
}}
|
||||
>
|
||||
<img
|
||||
alt={unwrap(tank.name)}
|
||||
alt={unwrap(tank.name!)}
|
||||
src={tankIcon(tank.id)}
|
||||
width={64}
|
||||
height={64}
|
||||
@@ -137,7 +137,7 @@ export function TankCard({ index, tank }: TankCardProps) {
|
||||
textOverflow: "ellipsis",
|
||||
}}
|
||||
>
|
||||
{unwrap(tank.name)}
|
||||
{unwrap(tank.name!)}
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
@@ -55,7 +55,7 @@ export const fakeCompositeStats = compositeStats(
|
||||
wins: 30,
|
||||
xp: 1230 * 50,
|
||||
},
|
||||
averageDefinitions.averages[7297].mu
|
||||
averageDefinitions.averages[7297].mu!,
|
||||
);
|
||||
|
||||
export function BreakdownPreview() {
|
||||
@@ -88,9 +88,9 @@ export function BreakdownRenderer() {
|
||||
const diff = useMemo(
|
||||
() =>
|
||||
deltaTankStats(tankStatsA, tankStatsB).sort(
|
||||
(a, b) => b.last_battle_time - a.last_battle_time
|
||||
(a, b) => b.last_battle_time - a.last_battle_time,
|
||||
),
|
||||
[tankStatsB]
|
||||
[tankStatsB],
|
||||
);
|
||||
const compositeStats = diff
|
||||
.map((diff) => {
|
||||
@@ -100,14 +100,14 @@ export function BreakdownRenderer() {
|
||||
|
||||
const compositeStats = calculateCompositeStats(
|
||||
{ battle_life_time: diff.battle_life_time, ...diff.all },
|
||||
average.mu
|
||||
average.mu!,
|
||||
);
|
||||
|
||||
return { id: diff.tank_id, compositeStats };
|
||||
})
|
||||
.filter((stats) => stats !== null);
|
||||
const sum = sumCompositeStats(
|
||||
compositeStats.map(({ compositeStats }) => compositeStats)
|
||||
compositeStats.map(({ compositeStats }) => compositeStats),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -19,7 +19,7 @@ type GalleryCardProps = MaybeSkeletonComponentProps<{
|
||||
|
||||
export function GalleryCard(props: GalleryCardProps) {
|
||||
const src = asset(
|
||||
`gallery/avatars/${!props.skeleton && `${props.avatar.id}${props.avatar.extension}`}`
|
||||
`gallery/avatars/${!props.skeleton && `${props.avatar.id}${props.avatar.extension}`}`,
|
||||
);
|
||||
const card = useRef<HTMLDivElement>(null!);
|
||||
const { unwrap } = useLocale();
|
||||
@@ -54,11 +54,11 @@ export function GalleryCard(props: GalleryCardProps) {
|
||||
objectFit: "cover",
|
||||
}}
|
||||
src={src}
|
||||
alt={unwrap(props.avatar.name)}
|
||||
alt={unwrap(props.avatar.name!)}
|
||||
/>
|
||||
|
||||
<Text size="1" align="center" style={{ maxWidth: "100%" }}>
|
||||
{unwrap(props.avatar.name)}
|
||||
{unwrap(props.avatar.name!)}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
@@ -90,12 +90,12 @@ export function GalleryCard(props: GalleryCardProps) {
|
||||
</Flex>
|
||||
|
||||
<Flex px="4" justify="center" mt="3">
|
||||
<img src={src} alt={unwrap(props.avatar.name)} />
|
||||
<img src={src} alt={unwrap(props.avatar.name!)} />
|
||||
</Flex>
|
||||
|
||||
<Flex justify="center" mt="4">
|
||||
<Dialog.Title align="center" style={{ maxWidth: "10rem" }}>
|
||||
{unwrap(props.avatar.name)}
|
||||
{unwrap(props.avatar.name!)}
|
||||
</Dialog.Title>
|
||||
</Flex>
|
||||
</Dialog.Content>
|
||||
|
||||
@@ -82,10 +82,10 @@ export function Guesser() {
|
||||
});
|
||||
|
||||
setResults(
|
||||
searchResults.map((result) => tankDefinitions.tanks[result.obj.id])
|
||||
searchResults.map((result) => tankDefinitions.tanks[result.obj.id]),
|
||||
);
|
||||
}, 500),
|
||||
[]
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -126,11 +126,11 @@ export function Guesser() {
|
||||
onClick={() => {
|
||||
if (!input.current) return;
|
||||
|
||||
input.current.value = unwrap(result.name);
|
||||
input.current.value = unwrap(result.name!);
|
||||
setSelected(result);
|
||||
setResults(null);
|
||||
}}
|
||||
text={unwrap(result.name)}
|
||||
text={unwrap(result.name!)}
|
||||
prefix={
|
||||
<img
|
||||
style={{ width: "1em", height: "1em" }}
|
||||
@@ -321,7 +321,7 @@ export function Guesser() {
|
||||
return tiers.includes(tank.tier);
|
||||
});
|
||||
const id = Number(
|
||||
filteredIds[Math.floor(Math.random() * filteredIds.length)]
|
||||
filteredIds[Math.floor(Math.random() * filteredIds.length)],
|
||||
);
|
||||
const tank = tankDefinitions.tanks[id];
|
||||
|
||||
|
||||
@@ -118,16 +118,16 @@ export function Model() {
|
||||
ref={turretGroup}
|
||||
rotation={[0, 0, modelTransformEvent.last!.yaw]}
|
||||
position={[
|
||||
tankModel.turret_origin.x + trackModel.origin.x,
|
||||
tankModel.turret_origin.z + trackModel.origin.z,
|
||||
tankModel.turret_origin.y + trackModel.origin.y,
|
||||
tankModel.turret_origin!.x + trackModel.origin!.x,
|
||||
tankModel.turret_origin!.z + trackModel.origin!.z,
|
||||
tankModel.turret_origin!.y + trackModel.origin!.y,
|
||||
]}
|
||||
>
|
||||
<group
|
||||
position={[
|
||||
-turretTankModel.turret_origin.x - turretTrackModel.origin.x,
|
||||
-turretTankModel.turret_origin.z - turretTrackModel.origin.z,
|
||||
-turretTankModel.turret_origin.y - turretTrackModel.origin.y,
|
||||
-turretTankModel.turret_origin!.x - turretTrackModel.origin!.x,
|
||||
-turretTankModel.turret_origin!.z - turretTrackModel.origin!.z,
|
||||
-turretTankModel.turret_origin!.y - turretTrackModel.origin!.y,
|
||||
]}
|
||||
>
|
||||
{turretNodes.map((node) => {
|
||||
@@ -160,28 +160,28 @@ export function Model() {
|
||||
ref={gunGroup}
|
||||
rotation={[modelTransformEvent.last!.pitch, 0, 0]}
|
||||
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,
|
||||
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,
|
||||
]}
|
||||
>
|
||||
<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,
|
||||
-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) => {
|
||||
@@ -216,15 +216,15 @@ export function Model() {
|
||||
|
||||
<group
|
||||
position={[
|
||||
-gunTankModel.turret_origin.x -
|
||||
gunTrackModel.origin.x -
|
||||
gunTurretModel.gun_origin.x,
|
||||
-gunTankModel.turret_origin.z -
|
||||
gunTrackModel.origin.z -
|
||||
gunTurretModel.gun_origin.z,
|
||||
-gunTankModel.turret_origin.y -
|
||||
gunTrackModel.origin.y -
|
||||
gunTurretModel.gun_origin.y,
|
||||
-gunTankModel.turret_origin!.x -
|
||||
gunTrackModel.origin!.x -
|
||||
gunTurretModel.gun_origin!.x,
|
||||
-gunTankModel.turret_origin!.z -
|
||||
gunTrackModel.origin!.z -
|
||||
gunTurretModel.gun_origin!.z,
|
||||
-gunTankModel.turret_origin!.y -
|
||||
gunTrackModel.origin!.y -
|
||||
gunTurretModel.gun_origin!.y,
|
||||
]}
|
||||
>
|
||||
{gunNodes.map((node) => {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { asset } from '@blitzkit/core';
|
||||
import { ClockIcon, ReloadIcon } from '@radix-ui/react-icons';
|
||||
import { Flex, Text } from '@radix-ui/themes';
|
||||
import { awaitableConsumableDefinitions } from '../../core/awaitables/consumableDefinitions';
|
||||
import { useDelta } from '../../hooks/useDelta';
|
||||
import { useLocale } from '../../hooks/useLocale';
|
||||
import { GenericTankComponentButton } from './GenericTankComponentButton';
|
||||
import type { TankComponentButtonProps } from './TankComponentButton';
|
||||
import { asset } from "@blitzkit/core";
|
||||
import { ClockIcon, ReloadIcon } from "@radix-ui/react-icons";
|
||||
import { Flex, Text } from "@radix-ui/themes";
|
||||
import { awaitableConsumableDefinitions } from "../../core/awaitables/consumableDefinitions";
|
||||
import { useDelta } from "../../hooks/useDelta";
|
||||
import { useLocale } from "../../hooks/useLocale";
|
||||
import { GenericTankComponentButton } from "./GenericTankComponentButton";
|
||||
import type { TankComponentButtonProps } from "./TankComponentButton";
|
||||
|
||||
interface ConsumableButtonProps extends TankComponentButtonProps {
|
||||
consumable: number;
|
||||
@@ -27,8 +27,8 @@ export function ConsumableButton({
|
||||
|
||||
return (
|
||||
<GenericTankComponentButton
|
||||
style={{ width: '6rem' }}
|
||||
tooltip={unwrap(consumableDefinitions.consumables[consumable].name)}
|
||||
style={{ width: "6rem" }}
|
||||
tooltip={unwrap(consumableDefinitions.consumables[consumable].name!)}
|
||||
icon={asset(`icons/consumables/${consumable}.webp`)}
|
||||
{...props}
|
||||
>
|
||||
@@ -39,9 +39,9 @@ export function ConsumableButton({
|
||||
size="1"
|
||||
color={
|
||||
durationDelta > 0
|
||||
? 'green'
|
||||
? "green"
|
||||
: durationDelta < 0
|
||||
? 'tomato'
|
||||
? "tomato"
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
@@ -56,9 +56,9 @@ export function ConsumableButton({
|
||||
size="1"
|
||||
color={
|
||||
cooldownDelta < 0
|
||||
? 'green'
|
||||
? "green"
|
||||
: cooldownDelta > 0
|
||||
? 'tomato'
|
||||
? "tomato"
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { asset } from '@blitzkit/core';
|
||||
import { awaitableEquipmentDefinitions } from '../../core/awaitables/equipmentDefinitions';
|
||||
import { useLocale } from '../../hooks/useLocale';
|
||||
import { GenericTankComponentButton } from './GenericTankComponentButton';
|
||||
import type { TankComponentButtonProps } from './TankComponentButton';
|
||||
import { asset } from "@blitzkit/core";
|
||||
import { awaitableEquipmentDefinitions } from "../../core/awaitables/equipmentDefinitions";
|
||||
import { useLocale } from "../../hooks/useLocale";
|
||||
import { GenericTankComponentButton } from "./GenericTankComponentButton";
|
||||
import type { TankComponentButtonProps } from "./TankComponentButton";
|
||||
|
||||
interface EquipmentButtonProps extends TankComponentButtonProps {
|
||||
equipment: number;
|
||||
@@ -15,7 +15,7 @@ export function EquipmentButton({ equipment, ...props }: EquipmentButtonProps) {
|
||||
|
||||
return (
|
||||
<GenericTankComponentButton
|
||||
tooltip={unwrap(equipmentDefinitions.equipments[equipment].name)}
|
||||
tooltip={unwrap(equipmentDefinitions.equipments[equipment].name!)}
|
||||
icon={asset(`icons/equipment/${equipment}.webp`)}
|
||||
{...props}
|
||||
/>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { asset } from '@blitzkit/core';
|
||||
import { awaitableProvisionDefinitions } from '../../core/awaitables/provisionDefinitions';
|
||||
import { useLocale } from '../../hooks/useLocale';
|
||||
import { GenericTankComponentButton } from './GenericTankComponentButton';
|
||||
import type { TankComponentButtonProps } from './TankComponentButton';
|
||||
import { asset } from "@blitzkit/core";
|
||||
import { awaitableProvisionDefinitions } from "../../core/awaitables/provisionDefinitions";
|
||||
import { useLocale } from "../../hooks/useLocale";
|
||||
import { GenericTankComponentButton } from "./GenericTankComponentButton";
|
||||
import type { TankComponentButtonProps } from "./TankComponentButton";
|
||||
|
||||
interface ProvisionButtonProps extends TankComponentButtonProps {
|
||||
provision: number;
|
||||
@@ -15,7 +15,7 @@ export function ProvisionButton({ provision, ...props }: ProvisionButtonProps) {
|
||||
|
||||
return (
|
||||
<GenericTankComponentButton
|
||||
tooltip={unwrap(provisionDefinitions.provisions[provision].name)}
|
||||
tooltip={unwrap(provisionDefinitions.provisions[provision].name!)}
|
||||
icon={asset(`icons/provisions/${provision}.webp`)}
|
||||
{...props}
|
||||
/>
|
||||
|
||||
@@ -6,11 +6,11 @@ import {
|
||||
type TankDefinition,
|
||||
type TrackDefinition,
|
||||
type TurretDefinition,
|
||||
} from '@blitzkit/core';
|
||||
import { DropdownMenu, Flex } from '@radix-ui/themes';
|
||||
import { useState } from 'react';
|
||||
import { useLocale } from '../hooks/useLocale';
|
||||
import { ModuleButton } from './ModuleButtons/ModuleButton';
|
||||
} from "@blitzkit/core";
|
||||
import { DropdownMenu, Flex } from "@radix-ui/themes";
|
||||
import { useState } from "react";
|
||||
import { useLocale } from "../hooks/useLocale";
|
||||
import { ModuleButton } from "./ModuleButtons/ModuleButton";
|
||||
|
||||
interface ModuleManagerProps {
|
||||
tank: TankDefinition;
|
||||
@@ -72,8 +72,8 @@ export function ModuleManager({
|
||||
variant="ghost"
|
||||
module="turret"
|
||||
discriminator={TIER_ROMAN_NUMERALS[turret.tier]}
|
||||
/>{' '}
|
||||
{unwrap(turret.name)}
|
||||
/>{" "}
|
||||
{unwrap(turret.name!)}
|
||||
</DropdownMenu.RadioItem>
|
||||
);
|
||||
})}
|
||||
@@ -112,8 +112,8 @@ export function ModuleManager({
|
||||
variant="ghost"
|
||||
module="gun"
|
||||
discriminator={TIER_ROMAN_NUMERALS[gun.tier]}
|
||||
/>{' '}
|
||||
{unwrap(gun.name)}
|
||||
/>{" "}
|
||||
{unwrap(gun.name!)}
|
||||
</DropdownMenu.RadioItem>
|
||||
);
|
||||
})}
|
||||
@@ -151,8 +151,8 @@ export function ModuleManager({
|
||||
variant="ghost"
|
||||
module="engine"
|
||||
discriminator={TIER_ROMAN_NUMERALS[engine.tier]}
|
||||
/>{' '}
|
||||
{unwrap(engine.name)}
|
||||
/>{" "}
|
||||
{unwrap(engine.name!)}
|
||||
</DropdownMenu.RadioItem>
|
||||
);
|
||||
})}
|
||||
@@ -190,8 +190,8 @@ export function ModuleManager({
|
||||
variant="ghost"
|
||||
module="chassis"
|
||||
discriminator={TIER_ROMAN_NUMERALS[track.tier]}
|
||||
/>{' '}
|
||||
{unwrap(track.name)}
|
||||
/>{" "}
|
||||
{unwrap(track.name!)}
|
||||
</DropdownMenu.RadioItem>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -20,7 +20,7 @@ export const TankRow = memo<TankRowProps>(
|
||||
const averages = averageDefinitions.averages[tank.id];
|
||||
const ratio = useAveragesExclusionRatio();
|
||||
const playerCountPeriod = Performance.use(
|
||||
(state) => state.playerCountPeriod
|
||||
(state) => state.playerCountPeriod,
|
||||
);
|
||||
const { locale } = useLocale();
|
||||
|
||||
@@ -29,107 +29,109 @@ export const TankRow = memo<TankRowProps>(
|
||||
<TankRowHeaderCell tank={tank} />
|
||||
|
||||
<CompareCell
|
||||
value={averages.mu.wins / averages.mu.battles}
|
||||
value={averages.mu!.wins / averages.mu!.battles}
|
||||
truth={average.winrate}
|
||||
align="center"
|
||||
>
|
||||
{((averages.mu.wins / averages.mu.battles) * 100).toFixed(1)}%
|
||||
{((averages.mu!.wins / averages.mu!.battles) * 100).toFixed(1)}%
|
||||
</CompareCell>
|
||||
<Table.Cell align="center">
|
||||
{formatCompact(
|
||||
locale,
|
||||
Math.round(ratio * averages.samples[playerCountPeriod])
|
||||
Math.round(ratio * averages.samples![playerCountPeriod]),
|
||||
)}
|
||||
</Table.Cell>
|
||||
<CompareCell
|
||||
value={averages.mu.damage_dealt / averages.mu.battles}
|
||||
value={averages.mu!.damage_dealt / averages.mu!.battles}
|
||||
truth={average.damage}
|
||||
align="center"
|
||||
>
|
||||
{Math.round(
|
||||
averages.mu.damage_dealt / averages.mu.battles
|
||||
averages.mu!.damage_dealt / averages.mu!.battles,
|
||||
).toLocaleString(locale)}
|
||||
</CompareCell>
|
||||
<CompareCell
|
||||
value={averages.mu.survived_battles / averages.mu.battles}
|
||||
value={averages.mu!.survived_battles / averages.mu!.battles}
|
||||
truth={average.survival}
|
||||
align="center"
|
||||
>
|
||||
{Math.round(
|
||||
(averages.mu.survived_battles / averages.mu.battles) * 100
|
||||
(averages.mu!.survived_battles / averages.mu!.battles) * 100,
|
||||
)}
|
||||
%
|
||||
</CompareCell>
|
||||
<CompareCell
|
||||
value={averages.mu.xp / averages.mu.battles}
|
||||
value={averages.mu!.xp / averages.mu!.battles}
|
||||
truth={average.xp}
|
||||
align="center"
|
||||
>
|
||||
{Math.round(averages.mu.xp / averages.mu.battles).toLocaleString(
|
||||
locale
|
||||
{Math.round(averages.mu!.xp / averages.mu!.battles).toLocaleString(
|
||||
locale,
|
||||
)}
|
||||
</CompareCell>
|
||||
<CompareCell
|
||||
value={averages.mu.frags / averages.mu.battles}
|
||||
value={averages.mu!.frags / averages.mu!.battles}
|
||||
truth={average.kills}
|
||||
align="center"
|
||||
>
|
||||
{(averages.mu.frags / averages.mu.battles).toFixed(2)}
|
||||
{(averages.mu!.frags / averages.mu!.battles).toFixed(2)}
|
||||
</CompareCell>
|
||||
<CompareCell
|
||||
value={averages.mu.spotted / averages.mu.battles}
|
||||
value={averages.mu!.spotted / averages.mu!.battles}
|
||||
truth={average.spots}
|
||||
align="center"
|
||||
>
|
||||
{(averages.mu.spotted / averages.mu.battles).toFixed(2)}
|
||||
{(averages.mu!.spotted / averages.mu!.battles).toFixed(2)}
|
||||
</CompareCell>
|
||||
<CompareCell
|
||||
value={averages.mu.hits / averages.mu.shots}
|
||||
value={averages.mu!.hits / averages.mu!.shots}
|
||||
truth={average.accuracy}
|
||||
align="center"
|
||||
>
|
||||
{Math.round((averages.mu.hits / averages.mu.shots) * 100)}%
|
||||
{Math.round((averages.mu!.hits / averages.mu!.shots) * 100)}%
|
||||
</CompareCell>
|
||||
<CompareCell
|
||||
value={averages.mu.shots / averages.mu.battles}
|
||||
value={averages.mu!.shots / averages.mu!.battles}
|
||||
truth={average.shots}
|
||||
align="center"
|
||||
>
|
||||
{(averages.mu.shots / averages.mu.battles).toFixed(1)}
|
||||
{(averages.mu!.shots / averages.mu!.battles).toFixed(1)}
|
||||
</CompareCell>
|
||||
<CompareCell
|
||||
value={averages.mu.hits / averages.mu.battles}
|
||||
value={averages.mu!.hits / averages.mu!.battles}
|
||||
truth={average.hits}
|
||||
align="center"
|
||||
>
|
||||
{(averages.mu.hits / averages.mu.battles).toFixed(1)}
|
||||
{(averages.mu!.hits / averages.mu!.battles).toFixed(1)}
|
||||
</CompareCell>
|
||||
<CompareCell
|
||||
value={averages.mu.damage_dealt / averages.mu.damage_received}
|
||||
value={averages.mu!.damage_dealt / averages.mu!.damage_received}
|
||||
truth={average.damageRatio}
|
||||
align="center"
|
||||
>
|
||||
{(averages.mu.damage_dealt / averages.mu.damage_received).toFixed(2)}
|
||||
{(averages.mu!.damage_dealt / averages.mu!.damage_received).toFixed(
|
||||
2,
|
||||
)}
|
||||
</CompareCell>
|
||||
<CompareCell
|
||||
value={averages.mu.damage_received / averages.mu.battles}
|
||||
value={averages.mu!.damage_received / averages.mu!.battles}
|
||||
truth={average.damageReceived}
|
||||
direction={CompareCellDirection.LOWER_IS_BETTER}
|
||||
align="center"
|
||||
>
|
||||
{Math.round(
|
||||
averages.mu.damage_received / averages.mu.battles
|
||||
averages.mu!.damage_received / averages.mu!.battles,
|
||||
).toLocaleString(locale)}
|
||||
</CompareCell>
|
||||
<CompareCell
|
||||
value={averages.mu.capture_points / averages.mu.battles}
|
||||
value={averages.mu!.capture_points / averages.mu!.battles}
|
||||
truth={average.capturePoints}
|
||||
align="center"
|
||||
>
|
||||
{(averages.mu.capture_points / averages.mu.battles).toFixed(2)}
|
||||
{(averages.mu!.capture_points / averages.mu!.battles).toFixed(2)}
|
||||
</CompareCell>
|
||||
</Table.Row>
|
||||
);
|
||||
},
|
||||
(prev, next) => prev.tank.id === next.tank.id
|
||||
(prev, next) => prev.tank.id === next.tank.id,
|
||||
);
|
||||
@@ -49,10 +49,10 @@ export function Tanks({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
const filteredTanks = usePromise(
|
||||
() =>
|
||||
filterTanks(filters, tankList).then((tanks) =>
|
||||
tanks.filter(({ id }) => id in averageDefinitions.averages)
|
||||
tanks.filter(({ id }) => id in averageDefinitions.averages),
|
||||
),
|
||||
// react-promise-suspense has awful type annotations
|
||||
[filters] as any
|
||||
[filters] as any,
|
||||
);
|
||||
const tanksMapped = useMemo(
|
||||
() =>
|
||||
@@ -60,7 +60,7 @@ export function Tanks({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
id,
|
||||
...averageDefinitions.averages[id],
|
||||
})),
|
||||
[filters]
|
||||
[filters],
|
||||
);
|
||||
const tanksSorted = useMemo(() => {
|
||||
const { playerCountPeriod } = Performance.state;
|
||||
@@ -69,83 +69,85 @@ export function Tanks({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
case "accuracy":
|
||||
return tanksMapped.sort(
|
||||
(a, b) =>
|
||||
sort.direction * (a.mu.hits / a.mu.shots - b.mu.hits / b.mu.shots)
|
||||
sort.direction *
|
||||
(a.mu!.hits / a.mu!.shots - b.mu!.hits / b.mu!.shots),
|
||||
);
|
||||
case "capture_points":
|
||||
return tanksMapped.sort(
|
||||
(a, b) =>
|
||||
sort.direction *
|
||||
(a.mu.capture_points / a.mu.battles -
|
||||
b.mu.capture_points / b.mu.battles)
|
||||
(a.mu!.capture_points / a.mu!.battles -
|
||||
b.mu!.capture_points / b.mu!.battles),
|
||||
);
|
||||
case "damage":
|
||||
return tanksMapped.sort(
|
||||
(a, b) =>
|
||||
sort.direction *
|
||||
(a.mu.damage_dealt / a.mu.battles -
|
||||
b.mu.damage_dealt / b.mu.battles)
|
||||
(a.mu!.damage_dealt / a.mu!.battles -
|
||||
b.mu!.damage_dealt / b.mu!.battles),
|
||||
);
|
||||
case "damage_ratio":
|
||||
return tanksMapped.sort(
|
||||
(a, b) =>
|
||||
sort.direction *
|
||||
(a.mu.damage_dealt / a.mu.damage_received -
|
||||
b.mu.damage_dealt / b.mu.damage_received)
|
||||
(a.mu!.damage_dealt / a.mu!.damage_received -
|
||||
b.mu!.damage_dealt / b.mu!.damage_received),
|
||||
);
|
||||
case "damage_taken":
|
||||
return tanksMapped.sort(
|
||||
(a, b) =>
|
||||
sort.direction *
|
||||
(a.mu.damage_received / a.mu.battles -
|
||||
b.mu.damage_received / b.mu.battles)
|
||||
(a.mu!.damage_received / a.mu!.battles -
|
||||
b.mu!.damage_received / b.mu!.battles),
|
||||
);
|
||||
case "hits":
|
||||
return tanksMapped.sort(
|
||||
(a, b) =>
|
||||
sort.direction *
|
||||
(a.mu.hits / a.mu.battles - b.mu.hits / b.mu.battles)
|
||||
(a.mu!.hits / a.mu!.battles - b.mu!.hits / b.mu!.battles),
|
||||
);
|
||||
case "kills":
|
||||
return tanksMapped.sort(
|
||||
(a, b) =>
|
||||
sort.direction *
|
||||
(a.mu.frags / a.mu.battles - b.mu.frags / b.mu.battles)
|
||||
(a.mu!.frags / a.mu!.battles - b.mu!.frags / b.mu!.battles),
|
||||
);
|
||||
case "shots":
|
||||
return tanksMapped.sort(
|
||||
(a, b) =>
|
||||
sort.direction *
|
||||
(a.mu.shots / a.mu.battles - b.mu.shots / b.mu.battles)
|
||||
(a.mu!.shots / a.mu!.battles - b.mu!.shots / b.mu!.battles),
|
||||
);
|
||||
case "players":
|
||||
return tanksMapped.sort(
|
||||
(a, b) =>
|
||||
sort.direction *
|
||||
(a.samples[playerCountPeriod] - b.samples[playerCountPeriod])
|
||||
(a.samples![playerCountPeriod] - b.samples![playerCountPeriod]),
|
||||
);
|
||||
case "spots":
|
||||
return tanksMapped.sort(
|
||||
(a, b) =>
|
||||
sort.direction *
|
||||
(a.mu.spotted / a.mu.battles - b.mu.spotted / b.mu.battles)
|
||||
(a.mu!.spotted / a.mu!.battles - b.mu!.spotted / b.mu!.battles),
|
||||
);
|
||||
case "survival":
|
||||
return tanksMapped.sort(
|
||||
(a, b) =>
|
||||
sort.direction *
|
||||
(a.mu.survived_battles / a.mu.battles -
|
||||
b.mu.survived_battles / b.mu.battles)
|
||||
(a.mu!.survived_battles / a.mu!.battles -
|
||||
b.mu!.survived_battles / b.mu!.battles),
|
||||
);
|
||||
case "winrate":
|
||||
return tanksMapped.sort(
|
||||
(a, b) =>
|
||||
sort.direction *
|
||||
(a.mu.wins / a.mu.battles - b.mu.wins / b.mu.battles)
|
||||
(a.mu!.wins / a.mu!.battles - b.mu!.wins / b.mu!.battles),
|
||||
);
|
||||
case "xp":
|
||||
return tanksMapped.sort(
|
||||
(a, b) =>
|
||||
sort.direction * (a.mu.xp / a.mu.battles - b.mu.xp / b.mu.battles)
|
||||
sort.direction *
|
||||
(a.mu!.xp / a.mu!.battles - b.mu!.xp / b.mu!.battles),
|
||||
);
|
||||
}
|
||||
}, [filters, sort]);
|
||||
@@ -161,11 +163,11 @@ export function Tanks({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
const sum = useCallback(
|
||||
(slice: (tank: BlitzkitStats) => number) => {
|
||||
return tanksSorted.reduce(
|
||||
(acc, tank) => acc + tank.samples[playerCountPeriod] * slice(tank.mu),
|
||||
0
|
||||
(acc, tank) => acc + tank.samples![playerCountPeriod] * slice(tank.mu!),
|
||||
0,
|
||||
);
|
||||
},
|
||||
[tanksSorted]
|
||||
[tanksSorted],
|
||||
);
|
||||
|
||||
const battles = sum(({ battles }) => battles);
|
||||
@@ -209,7 +211,7 @@ export function Tanks({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
<Table.Cell align="center">
|
||||
{formatCompact(
|
||||
locale,
|
||||
ratio * averageDefinitions.samples[playerCountPeriod]
|
||||
ratio * averageDefinitions.samples![playerCountPeriod],
|
||||
)}
|
||||
</Table.Cell>
|
||||
<Table.Cell align="center">
|
||||
@@ -255,12 +257,12 @@ export function Tanks({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
key={index}
|
||||
onIntersection={() => {
|
||||
setLoadedRows((state) =>
|
||||
Math.min(state + 2, tanksSorted.length)
|
||||
Math.min(state + 2, tanksSorted.length),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
},
|
||||
)}
|
||||
</Table.Body>
|
||||
);
|
||||
|
||||
@@ -33,7 +33,7 @@ export const TankCard = forwardRef<HTMLSpanElement, TankCardProps>(
|
||||
style,
|
||||
...props
|
||||
}: TankCardProps,
|
||||
ref
|
||||
ref,
|
||||
) => {
|
||||
const { unwrap, locale } = useLocale();
|
||||
const provideLink = !noLink && onSelect === undefined;
|
||||
@@ -46,13 +46,13 @@ export const TankCard = forwardRef<HTMLSpanElement, TankCardProps>(
|
||||
tabIndex={onSelect ? 0 : undefined}
|
||||
size="2"
|
||||
color={
|
||||
tank.type === TankType.COLLECTOR
|
||||
tank.type === TankType.TANK_TYPE_COLLECTOR
|
||||
? "blue"
|
||||
: tank.type === TankType.PREMIUM
|
||||
: tank.type === TankType.TANK_TYPE_PREMIUM
|
||||
? "amber"
|
||||
: "gray"
|
||||
}
|
||||
highContrast={tank.type === TankType.RESEARCHABLE}
|
||||
highContrast={tank.type === TankType.TANK_TYPE_RESEARCHABLE}
|
||||
onClick={() => {
|
||||
onSelect?.(tank);
|
||||
TankopediaPersistent.mutate((draft) => {
|
||||
@@ -79,7 +79,7 @@ export const TankCard = forwardRef<HTMLSpanElement, TankCardProps>(
|
||||
}}
|
||||
>
|
||||
<img
|
||||
alt={unwrap(tank.name)}
|
||||
alt={unwrap(tank.name!)}
|
||||
src={asset(`icons/tanks/big/${tank.id}.webp`)}
|
||||
className="image"
|
||||
draggable={false}
|
||||
@@ -95,7 +95,7 @@ export const TankCard = forwardRef<HTMLSpanElement, TankCardProps>(
|
||||
>
|
||||
<Icon className="class-icon" />
|
||||
<Text align="center" className="name">
|
||||
{unwrap(tank.name)}
|
||||
{unwrap(tank.name!)}
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
@@ -115,5 +115,5 @@ export const TankCard = forwardRef<HTMLSpanElement, TankCardProps>(
|
||||
</LinkI18n>
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -19,13 +19,13 @@ export function TankRowHeaderCell({ tank }: TankRowHeaderCellProps) {
|
||||
<LinkI18n
|
||||
locale={locale}
|
||||
color={
|
||||
tank.type === TankType.COLLECTOR
|
||||
tank.type === TankType.TANK_TYPE_COLLECTOR
|
||||
? "blue"
|
||||
: tank.type === TankType.PREMIUM
|
||||
: tank.type === TankType.TANK_TYPE_PREMIUM
|
||||
? "amber"
|
||||
: "gray"
|
||||
}
|
||||
highContrast={tank.type === TankType.RESEARCHABLE}
|
||||
highContrast={tank.type === TankType.TANK_TYPE_RESEARCHABLE}
|
||||
underline="hover"
|
||||
wrap="nowrap"
|
||||
href={`/tanks/${tank.slug}`}
|
||||
@@ -55,7 +55,7 @@ export function TankRowHeaderCell({ tank }: TankRowHeaderCellProps) {
|
||||
<ExperimentIcon style={{ width: "1em", height: "1em" }} />
|
||||
)}
|
||||
|
||||
{unwrap(tank.name)}
|
||||
{unwrap(tank.name!)}
|
||||
</Flex>
|
||||
</LinkI18n>
|
||||
</StickyRowHeaderCell>
|
||||
|
||||
@@ -53,11 +53,11 @@ export function TankSearchCard({ tank, onSelect }: TankSearchCardProps) {
|
||||
case "fire.reload":
|
||||
return resolveReload(gun).toFixed(2);
|
||||
case "fire.standardPenetration":
|
||||
return shell0?.penetration.near.toFixed(0);
|
||||
return shell0?.penetration!.near.toFixed(0);
|
||||
case "fire.premiumPenetration":
|
||||
return shell1 ? shell1?.penetration.near.toFixed(0) : "--";
|
||||
return shell1 ? shell1?.penetration!.near.toFixed(0) : "--";
|
||||
case "fire.tertiaryPenetration":
|
||||
return shell1 ? shell2?.penetration.near.toFixed(0) : "--";
|
||||
return shell1 ? shell2?.penetration!.near.toFixed(0) : "--";
|
||||
case "fire.shellVelocity":
|
||||
return shell0.velocity.toFixed(0);
|
||||
case "fire.shellCapacity":
|
||||
@@ -66,12 +66,12 @@ export function TankSearchCard({ tank, onSelect }: TankSearchCardProps) {
|
||||
return shell0.range.toFixed(0);
|
||||
case "fire.gunDepression":
|
||||
return (
|
||||
gunModelDefinition.pitch.max +
|
||||
gunModelDefinition.pitch!.max +
|
||||
(tankModelDefinition.initial_turret_rotation?.pitch ?? 0)
|
||||
).toFixed(1);
|
||||
case "fire.gunElevation":
|
||||
return (
|
||||
-gunModelDefinition.pitch.min -
|
||||
-gunModelDefinition.pitch!.min -
|
||||
(tankModelDefinition.initial_turret_rotation?.pitch ?? 0)
|
||||
).toFixed(1);
|
||||
case "maneuverability.forwardsSpeed":
|
||||
@@ -113,8 +113,8 @@ export function TankSearchCard({ tank, onSelect }: TankSearchCardProps) {
|
||||
case "survivability.volume": {
|
||||
const dimensions = normalizeBoundingBox(
|
||||
unionBoundingBox(
|
||||
tankModelDefinition.bounding_box,
|
||||
turretModelDefinition.bounding_box,
|
||||
tankModelDefinition.bounding_box!,
|
||||
turretModelDefinition.bounding_box!,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -124,8 +124,8 @@ export function TankSearchCard({ tank, onSelect }: TankSearchCardProps) {
|
||||
case "survivability.length": {
|
||||
const bounds = normalizeBoundingBox(
|
||||
unionBoundingBox(
|
||||
tankModelDefinition.bounding_box,
|
||||
turretModelDefinition.bounding_box,
|
||||
tankModelDefinition.bounding_box!,
|
||||
turretModelDefinition.bounding_box!,
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -85,11 +85,11 @@ for (const gameModeIdString in gameModeRoleSets) {
|
||||
|
||||
outerLoop: for (const id of role.consumables) {
|
||||
const name =
|
||||
consumableDefinitions.consumables[id].name.locales[locales.default];
|
||||
consumableDefinitions.consumables[id].name!.locales[locales.default];
|
||||
|
||||
for (const otherConsumable of consumables.values()) {
|
||||
const otherName =
|
||||
consumableDefinitions.consumables[otherConsumable].name.locales[
|
||||
consumableDefinitions.consumables[otherConsumable].name!.locales[
|
||||
locales.default
|
||||
];
|
||||
|
||||
@@ -102,11 +102,11 @@ for (const gameModeIdString in gameModeRoleSets) {
|
||||
|
||||
outerLoop: for (const id of role.provisions) {
|
||||
const name =
|
||||
provisionDefinitions.provisions[id].name.locales[locales.default];
|
||||
provisionDefinitions.provisions[id].name!.locales[locales.default];
|
||||
|
||||
for (const otherProvision of provisions.values()) {
|
||||
const otherName =
|
||||
provisionDefinitions.provisions[otherProvision].name.locales[
|
||||
provisionDefinitions.provisions[otherProvision].name!.locales[
|
||||
locales.default
|
||||
];
|
||||
|
||||
@@ -129,7 +129,7 @@ const consumablesArray = Object.values(consumableDefinitions.consumables)
|
||||
.filter(
|
||||
(consumable) =>
|
||||
!consumable.game_mode_exclusive &&
|
||||
consumable.name.locales[locales.default],
|
||||
consumable.name!.locales[locales.default],
|
||||
)
|
||||
.map((consumable) => consumable.id);
|
||||
const provisionsArray = Array.from(
|
||||
@@ -137,11 +137,11 @@ const provisionsArray = Array.from(
|
||||
.filter(
|
||||
(provision) =>
|
||||
!provision.game_mode_exclusive &&
|
||||
provision.name.locales[locales.default],
|
||||
provision.name!.locales[locales.default],
|
||||
)
|
||||
.reduce((uniqueMap, consumable) => {
|
||||
if (!uniqueMap.has(consumable.name.locales[locales.default])) {
|
||||
uniqueMap.set(consumable.name.locales[locales.default], consumable.id);
|
||||
if (!uniqueMap.has(consumable.name!.locales[locales.default])) {
|
||||
uniqueMap.set(consumable.name!.locales[locales.default], consumable.id);
|
||||
}
|
||||
|
||||
return uniqueMap;
|
||||
@@ -149,10 +149,10 @@ const provisionsArray = Array.from(
|
||||
.values(),
|
||||
);
|
||||
const shellTypeIcons: Record<ShellType, string> = {
|
||||
[ShellType.AP]: "ap",
|
||||
[ShellType.APCR]: "ap_cr",
|
||||
[ShellType.HE]: "he",
|
||||
[ShellType.HEAT]: "hc",
|
||||
[ShellType.SHELL_TYPE_AP]: "ap",
|
||||
[ShellType.SHELL_TYPE_APCR]: "ap_cr",
|
||||
[ShellType.SHELL_TYPE_HE]: "he",
|
||||
[ShellType.SHELL_TYPE_HEAT]: "hc",
|
||||
};
|
||||
|
||||
const GUN_TYPE_ICONS: Record<
|
||||
@@ -165,9 +165,9 @@ const GUN_TYPE_ICONS: Record<
|
||||
};
|
||||
|
||||
const TANK_TYPE_COLORS: Record<TankType, string> = {
|
||||
[TankType.RESEARCHABLE]: "gray",
|
||||
[TankType.PREMIUM]: "amber",
|
||||
[TankType.COLLECTOR]: "blue",
|
||||
[TankType.TANK_TYPE_RESEARCHABLE]: "gray",
|
||||
[TankType.TANK_TYPE_PREMIUM]: "amber",
|
||||
[TankType.TANK_TYPE_COLLECTOR]: "blue",
|
||||
};
|
||||
|
||||
const GUN_TYPES = Object.keys(
|
||||
@@ -566,12 +566,12 @@ function TypeFilter() {
|
||||
<DropdownMenu.Trigger>
|
||||
<Button color="gray" variant="surface">
|
||||
<Flex gap="1">
|
||||
{types.map((tankType, index) => {
|
||||
{types.map((tankType) => {
|
||||
return (
|
||||
<ResearchedIcon
|
||||
style={{
|
||||
color: `var(--${TANK_TYPE_COLORS[tankType]}-${
|
||||
tankType === TankType.RESEARCHABLE ? "12" : "11"
|
||||
tankType === TankType.TANK_TYPE_RESEARCHABLE ? "12" : "11"
|
||||
})`,
|
||||
filter: "drop-shadow(0 0 var(--space-1) var(--black-a11))",
|
||||
opacity: 1,
|
||||
@@ -609,7 +609,7 @@ function TypeFilter() {
|
||||
<ResearchedIcon
|
||||
style={{
|
||||
color: `var(--${TANK_TYPE_COLORS[tankType]}-${
|
||||
tankType === TankType.RESEARCHABLE ? "12" : "11"
|
||||
tankType === TankType.TANK_TYPE_RESEARCHABLE ? "12" : "11"
|
||||
})`,
|
||||
opacity: 1,
|
||||
width: "1.25em",
|
||||
@@ -941,7 +941,7 @@ function ConsumablesFilter() {
|
||||
src={asset(`icons/consumables/${consumable}.webp`)}
|
||||
/>
|
||||
|
||||
{unwrap(consumableDefinition.name)}
|
||||
{unwrap(consumableDefinition.name!)}
|
||||
</DropdownMenu.CheckboxItem>
|
||||
);
|
||||
})}
|
||||
@@ -1035,7 +1035,7 @@ function ProvisionsFilter() {
|
||||
src={asset(`icons/provisions/${provision}.webp`)}
|
||||
/>
|
||||
|
||||
{unwrap(provisionDefinition.name)}
|
||||
{unwrap(provisionDefinition.name!)}
|
||||
</DropdownMenu.CheckboxItem>
|
||||
);
|
||||
})}
|
||||
@@ -1125,9 +1125,9 @@ function GameModeAbilitiesFilter() {
|
||||
<DropdownMenu.Label>
|
||||
{hasBoth
|
||||
? literals(strings.website.common.tank_search.active, {
|
||||
name: unwrap(gameMode.name),
|
||||
name: unwrap(gameMode.name!),
|
||||
})
|
||||
: unwrap(gameMode.name)}
|
||||
: unwrap(gameMode.name!)}
|
||||
</DropdownMenu.Label>
|
||||
)}
|
||||
|
||||
@@ -1164,7 +1164,7 @@ function GameModeAbilitiesFilter() {
|
||||
src={asset(`icons/consumables/${consumable}.webp`)}
|
||||
/>
|
||||
|
||||
{unwrap(abilityDefinition.name)}
|
||||
{unwrap(abilityDefinition.name!)}
|
||||
</DropdownMenu.CheckboxItem>
|
||||
);
|
||||
}
|
||||
@@ -1174,9 +1174,9 @@ function GameModeAbilitiesFilter() {
|
||||
<DropdownMenu.Label>
|
||||
{hasBoth
|
||||
? literals(strings.website.common.tank_search.passive, {
|
||||
name: unwrap(gameMode.name),
|
||||
name: unwrap(gameMode.name!),
|
||||
})
|
||||
: unwrap(gameMode.name)}
|
||||
: unwrap(gameMode.name!)}
|
||||
</DropdownMenu.Label>
|
||||
)}
|
||||
|
||||
@@ -1213,7 +1213,7 @@ function GameModeAbilitiesFilter() {
|
||||
src={asset(`icons/provisions/${provision}.webp`)}
|
||||
/>
|
||||
|
||||
{unwrap(powerDefinition.name)}
|
||||
{unwrap(powerDefinition.name!)}
|
||||
</DropdownMenu.CheckboxItem>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ export function QuickLink({ topResult }: QuickLinkProps) {
|
||||
<TextField.Slot>
|
||||
<LinkI18n locale={locale} href={`/tanks/${topResult.slug}`}>
|
||||
<Button variant="ghost">
|
||||
{unwrap(topResult.name)} <CaretRightIcon />
|
||||
{unwrap(topResult.name!)} <CaretRightIcon />
|
||||
</Button>
|
||||
</LinkI18n>
|
||||
</TextField.Slot>
|
||||
|
||||
@@ -67,7 +67,7 @@ export const TankSearch = memo<TankSearchProps>(
|
||||
return filterTanks(
|
||||
tankFilters,
|
||||
awaitedTanksDefinitionsArray,
|
||||
wargaming?.id
|
||||
wargaming?.id,
|
||||
);
|
||||
} else {
|
||||
return Promise.resolve(
|
||||
@@ -75,12 +75,12 @@ export const TankSearch = memo<TankSearchProps>(
|
||||
.go(tankFilters.search, tankNames, {
|
||||
keys: SEARCH_KEYS,
|
||||
})
|
||||
.map((result) => tankDefinitions.tanks[result.obj.id])
|
||||
.map((result) => tankDefinitions.tanks[result.obj.id]),
|
||||
);
|
||||
}
|
||||
},
|
||||
// react-promise-suspense has awful type annotations
|
||||
[tankFilters] as any
|
||||
[tankFilters] as any,
|
||||
);
|
||||
const sorted = useMemo(() => {
|
||||
if (tankFilters.search === null) {
|
||||
@@ -104,19 +104,19 @@ export const TankSearch = memo<TankSearchProps>(
|
||||
case "survivability.viewRange":
|
||||
sorted = filtered.sort(
|
||||
(a, b) =>
|
||||
a.turrets.at(-1)!.view_range - b.turrets.at(-1)!.view_range
|
||||
a.turrets.at(-1)!.view_range - b.turrets.at(-1)!.view_range,
|
||||
);
|
||||
break;
|
||||
|
||||
case "survivability.camouflageStill":
|
||||
sorted = filtered.sort(
|
||||
(a, b) => a.camouflage_still - b.camouflage_still
|
||||
(a, b) => a.camouflage_still - b.camouflage_still,
|
||||
);
|
||||
break;
|
||||
|
||||
case "survivability.camouflageMoving":
|
||||
sorted = filtered.sort(
|
||||
(a, b) => a.camouflage_moving - b.camouflage_moving
|
||||
(a, b) => a.camouflage_moving - b.camouflage_moving,
|
||||
);
|
||||
break;
|
||||
|
||||
@@ -126,7 +126,7 @@ export const TankSearch = memo<TankSearchProps>(
|
||||
a.camouflage_still *
|
||||
a.turrets.at(-1)!.guns.at(-1)!.camouflage_loss -
|
||||
b.camouflage_still *
|
||||
b.turrets.at(-1)!.guns.at(-1)!.camouflage_loss
|
||||
b.turrets.at(-1)!.guns.at(-1)!.camouflage_loss,
|
||||
);
|
||||
break;
|
||||
|
||||
@@ -140,15 +140,15 @@ export const TankSearch = memo<TankSearchProps>(
|
||||
bTankModelDefinition.turrets[b.turrets.at(-1)!.id];
|
||||
const aSize = normalizeBoundingBox(
|
||||
unionBoundingBox(
|
||||
aTankModelDefinition.bounding_box,
|
||||
aTurretModelDefinition.bounding_box
|
||||
)
|
||||
aTankModelDefinition.bounding_box!,
|
||||
aTurretModelDefinition.bounding_box!,
|
||||
),
|
||||
);
|
||||
const bSize = normalizeBoundingBox(
|
||||
unionBoundingBox(
|
||||
bTankModelDefinition.bounding_box,
|
||||
bTurretModelDefinition.bounding_box
|
||||
)
|
||||
bTankModelDefinition.bounding_box!,
|
||||
bTurretModelDefinition.bounding_box!,
|
||||
),
|
||||
);
|
||||
const aVolume = aSize.x * aSize.y * aSize.z;
|
||||
const bVolume = bSize.x * bSize.y * bSize.z;
|
||||
@@ -167,15 +167,15 @@ export const TankSearch = memo<TankSearchProps>(
|
||||
bTankModelDefinition.turrets[b.turrets.at(-1)!.id];
|
||||
const aSize = normalizeBoundingBox(
|
||||
unionBoundingBox(
|
||||
aTankModelDefinition.bounding_box,
|
||||
aTurretModelDefinition.bounding_box
|
||||
)
|
||||
aTankModelDefinition.bounding_box!,
|
||||
aTurretModelDefinition.bounding_box!,
|
||||
),
|
||||
);
|
||||
const bSize = normalizeBoundingBox(
|
||||
unionBoundingBox(
|
||||
bTankModelDefinition.bounding_box,
|
||||
bTurretModelDefinition.bounding_box
|
||||
)
|
||||
bTankModelDefinition.bounding_box!,
|
||||
bTurretModelDefinition.bounding_box!,
|
||||
),
|
||||
);
|
||||
const aLength = Math.max(aSize.x, aSize.y, aSize.z);
|
||||
const bLength = Math.max(bSize.x, bSize.y, bSize.z);
|
||||
@@ -189,12 +189,12 @@ export const TankSearch = memo<TankSearchProps>(
|
||||
(a, b) =>
|
||||
resolveDpm(
|
||||
a.turrets.at(-1)!.guns.at(-1)!,
|
||||
a.turrets.at(-1)!.guns.at(-1)!.shells[0]
|
||||
a.turrets.at(-1)!.guns.at(-1)!.shells[0],
|
||||
) -
|
||||
resolveDpm(
|
||||
b.turrets.at(-1)!.guns.at(-1)!,
|
||||
b.turrets.at(-1)!.guns.at(-1)!.shells[0]
|
||||
)
|
||||
b.turrets.at(-1)!.guns.at(-1)!.shells[0],
|
||||
),
|
||||
);
|
||||
break;
|
||||
|
||||
@@ -204,15 +204,15 @@ export const TankSearch = memo<TankSearchProps>(
|
||||
(a.turrets.at(-1)!.guns.at(-1)!.shells[1]
|
||||
? resolveDpm(
|
||||
a.turrets.at(-1)!.guns.at(-1)!,
|
||||
a.turrets.at(-1)!.guns.at(-1)!.shells[1]
|
||||
a.turrets.at(-1)!.guns.at(-1)!.shells[1],
|
||||
)
|
||||
: 0) -
|
||||
(b.turrets.at(-1)!.guns.at(-1)!.shells[1]
|
||||
? resolveDpm(
|
||||
b.turrets.at(-1)!.guns.at(-1)!,
|
||||
b.turrets.at(-1)!.guns.at(-1)!.shells[1]
|
||||
b.turrets.at(-1)!.guns.at(-1)!.shells[1],
|
||||
)
|
||||
: 0)
|
||||
: 0),
|
||||
);
|
||||
break;
|
||||
|
||||
@@ -220,7 +220,7 @@ export const TankSearch = memo<TankSearchProps>(
|
||||
sorted = filtered.sort(
|
||||
(a, b) =>
|
||||
resolveReload(a.turrets.at(-1)!.guns.at(-1)!) -
|
||||
resolveReload(b.turrets.at(-1)!.guns.at(-1)!)
|
||||
resolveReload(b.turrets.at(-1)!.guns.at(-1)!),
|
||||
);
|
||||
break;
|
||||
|
||||
@@ -228,35 +228,35 @@ export const TankSearch = memo<TankSearchProps>(
|
||||
sorted = filtered.sort(
|
||||
(a, b) =>
|
||||
a.turrets.at(-1)!.guns.at(-1)!.shells[0].caliber -
|
||||
b.turrets.at(-1)!.guns.at(-1)!.shells[0].caliber
|
||||
b.turrets.at(-1)!.guns.at(-1)!.shells[0].caliber,
|
||||
);
|
||||
break;
|
||||
|
||||
case "fire.standardPenetration":
|
||||
sorted = filtered.sort(
|
||||
(a, b) =>
|
||||
a.turrets.at(-1)!.guns.at(-1)!.shells[0].penetration.near -
|
||||
b.turrets.at(-1)!.guns.at(-1)!.shells[0].penetration.near
|
||||
a.turrets.at(-1)!.guns.at(-1)!.shells[0].penetration!.near -
|
||||
b.turrets.at(-1)!.guns.at(-1)!.shells[0].penetration!.near,
|
||||
);
|
||||
break;
|
||||
|
||||
case "fire.premiumPenetration":
|
||||
sorted = filtered.sort(
|
||||
(a, b) =>
|
||||
(a.turrets.at(-1)!.guns.at(-1)!.shells[1]?.penetration.near ??
|
||||
(a.turrets.at(-1)!.guns.at(-1)!.shells[1]?.penetration!.near ??
|
||||
0) -
|
||||
(b.turrets.at(-1)!.guns.at(-1)!.shells[1]?.penetration.near ??
|
||||
0)
|
||||
(b.turrets.at(-1)!.guns.at(-1)!.shells[1]?.penetration!.near ??
|
||||
0),
|
||||
);
|
||||
break;
|
||||
|
||||
case "fire.tertiaryPenetration":
|
||||
sorted = filtered.sort(
|
||||
(a, b) =>
|
||||
(a.turrets.at(-1)!.guns.at(-1)!.shells[2]?.penetration.near ??
|
||||
(a.turrets.at(-1)!.guns.at(-1)!.shells[2]?.penetration!.near ??
|
||||
0) -
|
||||
(b.turrets.at(-1)!.guns.at(-1)!.shells[2]?.penetration.near ??
|
||||
0)
|
||||
(b.turrets.at(-1)!.guns.at(-1)!.shells[2]?.penetration!.near ??
|
||||
0),
|
||||
);
|
||||
break;
|
||||
|
||||
@@ -264,7 +264,7 @@ export const TankSearch = memo<TankSearchProps>(
|
||||
sorted = filtered.sort(
|
||||
(a, b) =>
|
||||
a.turrets.at(-1)!.guns.at(-1)!.shells[0].armor_damage -
|
||||
b.turrets.at(-1)!.guns.at(-1)!.shells[0].armor_damage
|
||||
b.turrets.at(-1)!.guns.at(-1)!.shells[0].armor_damage,
|
||||
);
|
||||
break;
|
||||
|
||||
@@ -272,7 +272,7 @@ export const TankSearch = memo<TankSearchProps>(
|
||||
sorted = filtered.sort(
|
||||
(a, b) =>
|
||||
a.turrets.at(-1)!.guns.at(-1)!.shells[0].module_damage -
|
||||
b.turrets.at(-1)!.guns.at(-1)!.shells[0].module_damage
|
||||
b.turrets.at(-1)!.guns.at(-1)!.shells[0].module_damage,
|
||||
);
|
||||
break;
|
||||
|
||||
@@ -280,7 +280,7 @@ export const TankSearch = memo<TankSearchProps>(
|
||||
sorted = filtered.sort(
|
||||
(a, b) =>
|
||||
a.turrets.at(-1)!.guns.at(-1)!.shells[0].velocity -
|
||||
b.turrets.at(-1)!.guns.at(-1)!.shells[0].velocity
|
||||
b.turrets.at(-1)!.guns.at(-1)!.shells[0].velocity,
|
||||
);
|
||||
break;
|
||||
|
||||
@@ -288,7 +288,7 @@ export const TankSearch = memo<TankSearchProps>(
|
||||
sorted = filtered.sort(
|
||||
(a, b) =>
|
||||
a.turrets.at(-1)!.guns.at(-1)!.shells[0].range -
|
||||
b.turrets.at(-1)!.guns.at(-1)!.shells[0].range
|
||||
b.turrets.at(-1)!.guns.at(-1)!.shells[0].range,
|
||||
);
|
||||
break;
|
||||
|
||||
@@ -296,7 +296,7 @@ export const TankSearch = memo<TankSearchProps>(
|
||||
sorted = filtered.sort(
|
||||
(a, b) =>
|
||||
a.turrets.at(-1)!.guns.at(-1)!.shell_capacity -
|
||||
b.turrets.at(-1)!.guns.at(-1)!.shell_capacity
|
||||
b.turrets.at(-1)!.guns.at(-1)!.shell_capacity,
|
||||
);
|
||||
break;
|
||||
|
||||
@@ -304,7 +304,7 @@ export const TankSearch = memo<TankSearchProps>(
|
||||
sorted = filtered.sort(
|
||||
(a, b) =>
|
||||
a.turrets.at(-1)!.guns.at(-1)!.aim_time -
|
||||
b.turrets.at(-1)!.guns.at(-1)!.aim_time
|
||||
b.turrets.at(-1)!.guns.at(-1)!.aim_time,
|
||||
);
|
||||
break;
|
||||
|
||||
@@ -312,7 +312,7 @@ export const TankSearch = memo<TankSearchProps>(
|
||||
sorted = filtered.sort(
|
||||
(a, b) =>
|
||||
a.turrets.at(-1)!.guns.at(-1)!.dispersion_base -
|
||||
b.turrets.at(-1)!.guns.at(-1)!.dispersion_base
|
||||
b.turrets.at(-1)!.guns.at(-1)!.dispersion_base,
|
||||
);
|
||||
break;
|
||||
|
||||
@@ -320,7 +320,7 @@ export const TankSearch = memo<TankSearchProps>(
|
||||
sorted = filtered.sort(
|
||||
(a, b) =>
|
||||
a.tracks.at(-1)!.dispersion_move -
|
||||
b.tracks.at(-1)!.dispersion_move
|
||||
b.tracks.at(-1)!.dispersion_move,
|
||||
);
|
||||
break;
|
||||
|
||||
@@ -328,13 +328,13 @@ export const TankSearch = memo<TankSearchProps>(
|
||||
sorted = filtered.sort(
|
||||
(a, b) =>
|
||||
modelDefinitions.models[a.id].turrets[a.turrets.at(-1)!.id]
|
||||
.guns[a.turrets.at(-1)!.guns.at(-1)!.id].pitch.max +
|
||||
.guns[a.turrets.at(-1)!.guns.at(-1)!.id].pitch!.max +
|
||||
(modelDefinitions.models[a.id].initial_turret_rotation?.pitch ??
|
||||
0) -
|
||||
modelDefinitions.models[b.id].turrets[b.turrets.at(-1)!.id]
|
||||
.guns[b.turrets.at(-1)!.guns.at(-1)!.id].pitch.max -
|
||||
.guns[b.turrets.at(-1)!.guns.at(-1)!.id].pitch!.max -
|
||||
(modelDefinitions.models[b.id].initial_turret_rotation?.pitch ??
|
||||
0)
|
||||
0),
|
||||
);
|
||||
break;
|
||||
|
||||
@@ -342,31 +342,31 @@ export const TankSearch = memo<TankSearchProps>(
|
||||
sorted = filtered.sort(
|
||||
(a, b) =>
|
||||
modelDefinitions.models[b.id].turrets[b.turrets.at(-1)!.id]
|
||||
.guns[b.turrets.at(-1)!.guns.at(-1)!.id].pitch.min +
|
||||
.guns[b.turrets.at(-1)!.guns.at(-1)!.id].pitch!.min +
|
||||
(modelDefinitions.models[b.id].initial_turret_rotation?.pitch ??
|
||||
0) -
|
||||
modelDefinitions.models[a.id].turrets[a.turrets.at(-1)!.id]
|
||||
.guns[a.turrets.at(-1)!.guns.at(-1)!.id].pitch.min -
|
||||
.guns[a.turrets.at(-1)!.guns.at(-1)!.id].pitch!.min -
|
||||
(modelDefinitions.models[a.id].initial_turret_rotation?.pitch ??
|
||||
0)
|
||||
0),
|
||||
);
|
||||
break;
|
||||
|
||||
case "maneuverability.forwardsSpeed":
|
||||
sorted = filtered.sort(
|
||||
(a, b) => a.speed_forwards - b.speed_forwards
|
||||
(a, b) => a.speed_forwards - b.speed_forwards,
|
||||
);
|
||||
break;
|
||||
|
||||
case "maneuverability.backwardsSpeed":
|
||||
sorted = filtered.sort(
|
||||
(a, b) => a.speed_backwards - b.speed_backwards
|
||||
(a, b) => a.speed_backwards - b.speed_backwards,
|
||||
);
|
||||
break;
|
||||
|
||||
case "maneuverability.power":
|
||||
sorted = filtered.sort(
|
||||
(a, b) => a.engines.at(-1)!.power - b.engines.at(-1)!.power
|
||||
(a, b) => a.engines.at(-1)!.power - b.engines.at(-1)!.power,
|
||||
);
|
||||
break;
|
||||
|
||||
@@ -384,7 +384,7 @@ export const TankSearch = memo<TankSearchProps>(
|
||||
b.engines.at(-1)!.weight +
|
||||
b.tracks.at(-1)!.weight +
|
||||
b.turrets.at(-1)!.weight +
|
||||
b.turrets.at(-1)!.guns.at(-1)!.weight)
|
||||
b.turrets.at(-1)!.guns.at(-1)!.weight),
|
||||
);
|
||||
break;
|
||||
|
||||
@@ -400,7 +400,7 @@ export const TankSearch = memo<TankSearchProps>(
|
||||
b.engines.at(-1)!.weight +
|
||||
b.tracks.at(-1)!.weight +
|
||||
b.turrets.at(-1)!.weight +
|
||||
b.turrets.at(-1)!.guns.at(-1)!.weight)
|
||||
b.turrets.at(-1)!.guns.at(-1)!.weight),
|
||||
);
|
||||
break;
|
||||
|
||||
@@ -408,7 +408,7 @@ export const TankSearch = memo<TankSearchProps>(
|
||||
sorted = filtered.sort(
|
||||
(a, b) =>
|
||||
a.tracks.at(-1)!.traverse_speed -
|
||||
b.tracks.at(-1)!.traverse_speed
|
||||
b.tracks.at(-1)!.traverse_speed,
|
||||
);
|
||||
break;
|
||||
}
|
||||
@@ -520,12 +520,12 @@ export const TankSearch = memo<TankSearchProps>(
|
||||
key={index}
|
||||
onIntersection={() => {
|
||||
setLoadedCards((state) =>
|
||||
Math.min(state + 2, sorted.length)
|
||||
Math.min(state + 2, sorted.length),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
},
|
||||
)}
|
||||
</TankCardWrapper>
|
||||
)}
|
||||
@@ -538,15 +538,15 @@ export const TankSearch = memo<TankSearchProps>(
|
||||
<TankCardWrapper>
|
||||
{times(
|
||||
Math.round(
|
||||
skeleton ? DEFAULT_LOADED_CARDS : 10 + 10 * Math.random()
|
||||
skeleton ? DEFAULT_LOADED_CARDS : 10 + 10 * Math.random(),
|
||||
),
|
||||
(index) => (
|
||||
<SkeletonTankCard key={index} />
|
||||
)
|
||||
),
|
||||
)}
|
||||
</TankCardWrapper>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
+3
-3
@@ -24,7 +24,7 @@ export function Crew({ stats }: StatsAcceptorProps) {
|
||||
provisionDefinitions.provisions[provision].crew
|
||||
? total + provisionDefinitions.provisions[provision].crew! / 100
|
||||
: total,
|
||||
0
|
||||
0,
|
||||
) + (hasImprovedVentilation ? 0.08 : 0);
|
||||
const commanderMastery = 1 + provisionCrewBonus;
|
||||
|
||||
@@ -71,7 +71,7 @@ export function Crew({ stats }: StatsAcceptorProps) {
|
||||
decimals={0}
|
||||
noRanking
|
||||
value={() =>
|
||||
(member.type === CrewType.COMMANDER
|
||||
(member.type === CrewType.CREW_TYPE_COMMANDER
|
||||
? commanderMastery
|
||||
: commanderMastery * 1.1) * 100
|
||||
}
|
||||
@@ -93,7 +93,7 @@ export function Crew({ stats }: StatsAcceptorProps) {
|
||||
? strings.website.tools.tankopedia.crew[
|
||||
CREW_MEMBER_NAMES[sub]
|
||||
]
|
||||
: sub
|
||||
: sub,
|
||||
)
|
||||
.join(", ")}
|
||||
</Flex>
|
||||
|
||||
+6
-6
@@ -38,7 +38,7 @@ export function Firepower({
|
||||
const selected = thisShell.id === shell.id;
|
||||
|
||||
return (
|
||||
<Tooltip content={unwrap(thisShell.name)} key={thisShell.id}>
|
||||
<Tooltip content={unwrap(thisShell.name!)} key={thisShell.id}>
|
||||
<IconButton
|
||||
color={selected ? undefined : "gray"}
|
||||
variant={selected ? "solid" : "soft"}
|
||||
@@ -59,7 +59,7 @@ export function Firepower({
|
||||
}}
|
||||
>
|
||||
<img
|
||||
alt={unwrap(thisShell.name)}
|
||||
alt={unwrap(thisShell.name!)}
|
||||
width={16}
|
||||
height={16}
|
||||
src={asset(`icons/shells/${thisShell.icon}.webp`)}
|
||||
@@ -119,7 +119,7 @@ export function Firepower({
|
||||
name={literals(
|
||||
strings.website.tools.tankopedia.characteristics.values
|
||||
.shell_index,
|
||||
{ index: index + 1 }
|
||||
{ index: index + 1 },
|
||||
)}
|
||||
decimals={2}
|
||||
deltaType="lowerIsBetter"
|
||||
@@ -191,7 +191,7 @@ export function Firepower({
|
||||
name={literals(
|
||||
strings.website.tools.tankopedia.characteristics.values
|
||||
.dispersion_at_distance,
|
||||
{ distance: 100 }
|
||||
{ distance: 100 },
|
||||
)}
|
||||
/>
|
||||
<InfoWithDelta
|
||||
@@ -249,7 +249,7 @@ export function Firepower({
|
||||
/>
|
||||
<InfoWithDelta value="gunDepression" stats={stats} decimals={1} indent />
|
||||
<InfoWithDelta stats={stats} decimals={1} indent value="gunElevation" />
|
||||
{gunModelDefinition.pitch.front && (
|
||||
{gunModelDefinition.pitch!.front && (
|
||||
<>
|
||||
<InfoWithDelta
|
||||
stats={stats}
|
||||
@@ -265,7 +265,7 @@ export function Firepower({
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{gunModelDefinition.pitch.back && (
|
||||
{gunModelDefinition.pitch!.back && (
|
||||
<>
|
||||
<InfoWithDelta
|
||||
stats={stats}
|
||||
|
||||
+9
-9
@@ -1,13 +1,13 @@
|
||||
import type { QuicklimeEvent } from 'quicklime';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import type { Group } from 'three';
|
||||
import { awaitableModelDefinitions } from '../../../../../../../../core/awaitables/modelDefinitions';
|
||||
import type { QuicklimeEvent } from "quicklime";
|
||||
import { useEffect, useRef } from "react";
|
||||
import type { Group } from "three";
|
||||
import { awaitableModelDefinitions } from "../../../../../../../../core/awaitables/modelDefinitions";
|
||||
import {
|
||||
modelTransformEvent,
|
||||
type ModelTransformEventData,
|
||||
} from '../../../../../../../../core/blitzkit/modelTransform';
|
||||
import { Duel } from '../../../../../../../../stores/duel';
|
||||
import { ModelChunk } from './ModelChunk';
|
||||
} from "../../../../../../../../core/blitzkit/modelTransform";
|
||||
import { Duel } from "../../../../../../../../stores/duel";
|
||||
import { ModelChunk } from "./ModelChunk";
|
||||
|
||||
const modelDefinitions = await awaitableModelDefinitions;
|
||||
|
||||
@@ -39,8 +39,8 @@ export function CanvasContent() {
|
||||
}, []);
|
||||
return (
|
||||
<>
|
||||
<group ref={hullWrapper} position={[0, 0, turretModel.gun_origin.z]}>
|
||||
<group position={[0, 0, -turretModel.gun_origin.z]}>
|
||||
<group ref={hullWrapper} position={[0, 0, turretModel.gun_origin!.z]}>
|
||||
<group position={[0, 0, -turretModel.gun_origin!.z]}>
|
||||
<ModelChunk only="body" />
|
||||
</group>
|
||||
</group>
|
||||
|
||||
+20
-20
@@ -1,13 +1,13 @@
|
||||
import { mauveDark } from '@radix-ui/colors';
|
||||
import { LineBasicMaterial, MeshBasicMaterial } from 'three';
|
||||
import { awaitableModelDefinitions } from '../../../../../../../../core/awaitables/modelDefinitions';
|
||||
import { jsxTree } from '../../../../../../../../core/blitzkit/jsxTree';
|
||||
import { useModel } from '../../../../../../../../hooks/useModel';
|
||||
import { Duel } from '../../../../../../../../stores/duel';
|
||||
import { mauveDark } from "@radix-ui/colors";
|
||||
import { LineBasicMaterial, MeshBasicMaterial } from "three";
|
||||
import { awaitableModelDefinitions } from "../../../../../../../../core/awaitables/modelDefinitions";
|
||||
import { jsxTree } from "../../../../../../../../core/blitzkit/jsxTree";
|
||||
import { useModel } from "../../../../../../../../hooks/useModel";
|
||||
import { Duel } from "../../../../../../../../stores/duel";
|
||||
|
||||
const modelDefinitions = await awaitableModelDefinitions;
|
||||
|
||||
const surfaceMaterial: Record<Props['only'], MeshBasicMaterial> = {
|
||||
const surfaceMaterial: Record<Props["only"], MeshBasicMaterial> = {
|
||||
gun: new MeshBasicMaterial({
|
||||
color: mauveDark.mauve8,
|
||||
toneMapped: false,
|
||||
@@ -23,7 +23,7 @@ const surfaceMaterial: Record<Props['only'], MeshBasicMaterial> = {
|
||||
opacity: 0.5,
|
||||
}),
|
||||
};
|
||||
const outlineMaterial: Record<Props['only'], LineBasicMaterial> = {
|
||||
const outlineMaterial: Record<Props["only"], LineBasicMaterial> = {
|
||||
gun: new LineBasicMaterial({
|
||||
color: mauveDark.mauve11,
|
||||
toneMapped: false,
|
||||
@@ -41,7 +41,7 @@ const outlineMaterial: Record<Props['only'], LineBasicMaterial> = {
|
||||
};
|
||||
|
||||
interface Props {
|
||||
only: 'gun' | 'turret' | 'body';
|
||||
only: "gun" | "turret" | "body";
|
||||
}
|
||||
|
||||
export function ModelChunk({ only }: Props) {
|
||||
@@ -53,8 +53,8 @@ export function ModelChunk({ only }: Props) {
|
||||
const turretModel = tankModel.turrets[turret.id];
|
||||
const gunModel = turretModel.guns[gun.id];
|
||||
|
||||
const originZ = tankModel.turret_origin.z + turretModel.gun_origin.z;
|
||||
const originY = tankModel.turret_origin.y + turretModel.gun_origin.y;
|
||||
const originZ = tankModel.turret_origin!.z + turretModel.gun_origin!.z;
|
||||
const originY = tankModel.turret_origin!.y + turretModel.gun_origin!.y;
|
||||
|
||||
const { gltf } = useModel(tank.id);
|
||||
const nodes = Object.values(gltf.nodes);
|
||||
@@ -64,25 +64,25 @@ export function ModelChunk({ only }: Props) {
|
||||
{nodes.map((node) => {
|
||||
const isCurrentMantlet =
|
||||
node.name ===
|
||||
`gun_${gunModel.model_id.toString().padStart(2, '0')}_mask`;
|
||||
`gun_${gunModel.model_id.toString().padStart(2, "0")}_mask`;
|
||||
const isCurrentGun =
|
||||
node.name === `gun_${gunModel.model_id.toString().padStart(2, '0')}`;
|
||||
node.name === `gun_${gunModel.model_id.toString().padStart(2, "0")}`;
|
||||
const isGun = isCurrentGun || isCurrentMantlet;
|
||||
|
||||
const isCurrentTurret =
|
||||
node.name ===
|
||||
`turret_${turretModel.model_id.toString().padStart(2, '0')}`;
|
||||
`turret_${turretModel.model_id.toString().padStart(2, "0")}`;
|
||||
const isTurret = isCurrentTurret;
|
||||
|
||||
const isHull = node.name === 'hull';
|
||||
const isWheel = node.name.startsWith('chassis_wheel_');
|
||||
const isTrack = node.name.startsWith('chassis_track_');
|
||||
const isHull = node.name === "hull";
|
||||
const isWheel = node.name.startsWith("chassis_wheel_");
|
||||
const isTrack = node.name.startsWith("chassis_track_");
|
||||
const isBody = isHull || isWheel || isTrack;
|
||||
|
||||
if (
|
||||
(only === 'gun' && !isGun) ||
|
||||
(only === 'turret' && !isTurret) ||
|
||||
(only === 'body' && !isBody)
|
||||
(only === "gun" && !isGun) ||
|
||||
(only === "turret" && !isTurret) ||
|
||||
(only === "body" && !isBody)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
+8
-8
@@ -51,21 +51,21 @@ export function GunFlexibilityVisualizer({
|
||||
const [yaw, setYaw] = useState(0);
|
||||
const [pitch, setPitch] = useState(0);
|
||||
const [minPitch, setMinPitch] = useState(
|
||||
-degToRad(gunModel.pitch.front?.max ?? gunModel.pitch.max)
|
||||
-degToRad(gunModel.pitch!.front?.max ?? gunModel.pitch!.max),
|
||||
);
|
||||
const [maxPitch, setMaxPitch] = useState(
|
||||
-degToRad(gunModel.pitch.front?.min ?? gunModel.pitch.min)
|
||||
-degToRad(gunModel.pitch!.front?.min ?? gunModel.pitch!.min),
|
||||
);
|
||||
|
||||
const container = useRef<HTMLDivElement>(null);
|
||||
const highlighter = useRef<HTMLDivElement>(null);
|
||||
const pointer = useRef<HTMLDivElement>(null);
|
||||
|
||||
const p = gunModel.pitch;
|
||||
const p = gunModel.pitch!;
|
||||
const y = turretModel.yaw;
|
||||
const f = p.front;
|
||||
const b = p.back;
|
||||
const t = gunModel.pitch.transition ?? DEFAULT_PITCH_TRANSITION;
|
||||
const t = gunModel.pitch!.transition ?? DEFAULT_PITCH_TRANSITION;
|
||||
|
||||
let d = "";
|
||||
|
||||
@@ -223,12 +223,12 @@ export function GunFlexibilityVisualizer({
|
||||
const hasImprovedVerticalStabilizer = hasEquipment(
|
||||
122,
|
||||
Duel.state.protagonist.tank.equipment_preset,
|
||||
Duel.state.protagonist.equipmentMatrix
|
||||
Duel.state.protagonist.equipmentMatrix,
|
||||
);
|
||||
const hasDownImprovedVerticalStabilizer = hasEquipment(
|
||||
124,
|
||||
Duel.state.protagonist.tank.equipment_preset,
|
||||
Duel.state.protagonist.equipmentMatrix
|
||||
Duel.state.protagonist.equipmentMatrix,
|
||||
);
|
||||
|
||||
const min = applyPitchYawLimits(
|
||||
@@ -237,7 +237,7 @@ export function GunFlexibilityVisualizer({
|
||||
p,
|
||||
y,
|
||||
hasImprovedVerticalStabilizer,
|
||||
hasDownImprovedVerticalStabilizer
|
||||
hasDownImprovedVerticalStabilizer,
|
||||
);
|
||||
const max = applyPitchYawLimits(
|
||||
Math.PI,
|
||||
@@ -245,7 +245,7 @@ export function GunFlexibilityVisualizer({
|
||||
p,
|
||||
y,
|
||||
hasImprovedVerticalStabilizer,
|
||||
hasDownImprovedVerticalStabilizer
|
||||
hasDownImprovedVerticalStabilizer,
|
||||
);
|
||||
|
||||
yaw = min[1];
|
||||
|
||||
+2
-2
@@ -15,7 +15,7 @@ export function ShellSwitcher() {
|
||||
const selected = thisShell.id === shell.id;
|
||||
|
||||
return (
|
||||
<Tooltip content={unwrap(thisShell.name)} key={thisShell.id}>
|
||||
<Tooltip content={unwrap(thisShell.name!)} key={thisShell.id}>
|
||||
<IconButton
|
||||
size="3"
|
||||
color={selected ? undefined : "gray"}
|
||||
@@ -36,7 +36,7 @@ export function ShellSwitcher() {
|
||||
}}
|
||||
>
|
||||
<img
|
||||
alt={unwrap(thisShell.name)}
|
||||
alt={unwrap(thisShell.name!)}
|
||||
style={{ width: "1.125rem", height: "1.125rem" }}
|
||||
src={asset(`icons/shells/${thisShell.icon}.webp`)}
|
||||
/>
|
||||
|
||||
+6
-6
@@ -26,13 +26,13 @@ export function Characteristics({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
const consumables = Duel.use((state) => state.protagonist.consumables);
|
||||
const camouflage = Duel.use((state) => state.protagonist.camouflage);
|
||||
const assaultDistance = Duel.use(
|
||||
(state) => state.protagonist.assaultDistance
|
||||
(state) => state.protagonist.assaultDistance,
|
||||
);
|
||||
const equipmentMatrix = Duel.use(
|
||||
(state) => state.protagonist.equipmentMatrix
|
||||
(state) => state.protagonist.equipmentMatrix,
|
||||
);
|
||||
const { tank, turret, gun, engine, track, shell } = Duel.use(
|
||||
(state) => state.protagonist
|
||||
(state) => state.protagonist,
|
||||
);
|
||||
const stockEngine = tank.engines[0];
|
||||
const stockTrack = tank.tracks[0];
|
||||
@@ -67,7 +67,7 @@ export function Characteristics({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
tankModelDefinition,
|
||||
equipmentDefinitions: equipmentDefinitions,
|
||||
provisionDefinitions: provisionDefinitions,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const hasImprovedVerticalStabilizer = useEquipment(122);
|
||||
@@ -77,10 +77,10 @@ export function Characteristics({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
const [pitch, yaw] = applyPitchYawLimits(
|
||||
modelTransformEvent.last!.pitch,
|
||||
modelTransformEvent.last!.yaw,
|
||||
gunModelDefinition.pitch,
|
||||
gunModelDefinition.pitch!,
|
||||
turretModelDefinition.yaw,
|
||||
hasImprovedVerticalStabilizer,
|
||||
hasDownImprovedVerticalStabilizer
|
||||
hasDownImprovedVerticalStabilizer,
|
||||
);
|
||||
|
||||
modelTransformEvent.dispatch({ pitch, yaw });
|
||||
|
||||
+46
-46
@@ -43,11 +43,11 @@ interface ModuleWithUnlocks {
|
||||
}
|
||||
|
||||
const moduleTypeTypeIconName: Record<ModuleType, string> = {
|
||||
[ModuleType.VEHICLE]: "",
|
||||
[ModuleType.ENGINE]: "engine",
|
||||
[ModuleType.TRACKS]: "chassis",
|
||||
[ModuleType.TURRET]: "turret",
|
||||
[ModuleType.GUN]: "gun",
|
||||
[ModuleType.MODULE_TYPE_VEHICLE]: "",
|
||||
[ModuleType.MODULE_TYPE_ENGINE]: "engine",
|
||||
[ModuleType.MODULE_TYPE_TRACKS]: "chassis",
|
||||
[ModuleType.MODULE_TYPE_TURRET]: "turret",
|
||||
[ModuleType.MODULE_TYPE_GUN]: "gun",
|
||||
};
|
||||
|
||||
function ModuleButton({
|
||||
@@ -63,7 +63,7 @@ function ModuleButton({
|
||||
top: boolean;
|
||||
onClick: () => void;
|
||||
}) {
|
||||
const isTank = unlock.type === ModuleType.VEHICLE;
|
||||
const isTank = unlock.type === ModuleType.MODULE_TYPE_VEHICLE;
|
||||
const { locale } = useLocale();
|
||||
|
||||
const button = (
|
||||
@@ -91,7 +91,7 @@ function ModuleButton({
|
||||
{TIER_ROMAN_NUMERALS[tier]}
|
||||
</Text>
|
||||
|
||||
{unlock.cost.value > 0 && (
|
||||
{unlock.cost!.value > 0 && (
|
||||
<Flex
|
||||
style={{
|
||||
position: "absolute",
|
||||
@@ -103,12 +103,12 @@ function ModuleButton({
|
||||
gap={isTank ? "1" : undefined}
|
||||
>
|
||||
<Text size="1" color="gray">
|
||||
{formatCompact(locale, unlock.cost.value)}
|
||||
{formatCompact(locale, unlock.cost!.value)}
|
||||
</Text>
|
||||
|
||||
<img
|
||||
alt={unlock.cost.type}
|
||||
src={asset(`icons/currencies/${unlock.cost.type}.webp`)}
|
||||
alt={unlock.cost!.type}
|
||||
src={asset(`icons/currencies/${unlock.cost!.type}.webp`)}
|
||||
style={{
|
||||
width: 12,
|
||||
height: 12,
|
||||
@@ -171,16 +171,16 @@ export function Modules() {
|
||||
|
||||
function setByUnlock(unlock: Unlock) {
|
||||
Duel.mutate((draft) => {
|
||||
if (unlock.type === ModuleType.TURRET) {
|
||||
if (unlock.type === ModuleType.MODULE_TYPE_TURRET) {
|
||||
draft.protagonist.turret = draft.protagonist.tank.turrets.find(
|
||||
(turret) => turret.id === unlock.id
|
||||
(turret) => turret.id === unlock.id,
|
||||
)!;
|
||||
|
||||
// Check if current gun exists in new turret
|
||||
const gunInNewTurret = draft.protagonist.turret.guns.find(
|
||||
(gun) => gun.id === draft.protagonist.gun.id
|
||||
(gun) => gun.id === draft.protagonist.gun.id,
|
||||
);
|
||||
|
||||
|
||||
if (gunInNewTurret) {
|
||||
// Gun exists - get it FROM this turret
|
||||
draft.protagonist.gun = gunInNewTurret;
|
||||
@@ -190,9 +190,9 @@ export function Modules() {
|
||||
draft.protagonist.gun = draft.protagonist.turret.guns.at(-1)!;
|
||||
draft.protagonist.shell = draft.protagonist.gun.shells[0];
|
||||
}
|
||||
} else if (unlock.type === ModuleType.GUN) {
|
||||
} else if (unlock.type === ModuleType.MODULE_TYPE_GUN) {
|
||||
const gunInTurret = draft.protagonist.turret.guns.find(
|
||||
(gun) => gun.id === unlock.id
|
||||
(gun) => gun.id === unlock.id,
|
||||
);
|
||||
if (gunInTurret) {
|
||||
draft.protagonist.gun = gunInTurret;
|
||||
@@ -200,23 +200,23 @@ export function Modules() {
|
||||
} else {
|
||||
// TODO: warn somehow?
|
||||
const suitableTurret = draft.protagonist.tank.turrets.find((turret) =>
|
||||
turret.guns.some((gun) => gun.id === unlock.id)
|
||||
turret.guns.some((gun) => gun.id === unlock.id),
|
||||
)!;
|
||||
const gunInSuitableTurret = suitableTurret.guns.find(
|
||||
(gun) => gun.id === unlock.id
|
||||
(gun) => gun.id === unlock.id,
|
||||
)!;
|
||||
|
||||
draft.protagonist.turret = suitableTurret;
|
||||
draft.protagonist.gun = gunInSuitableTurret;
|
||||
draft.protagonist.shell = gunInSuitableTurret.shells[0];
|
||||
}
|
||||
} else if (unlock.type === ModuleType.ENGINE) {
|
||||
} else if (unlock.type === ModuleType.MODULE_TYPE_ENGINE) {
|
||||
draft.protagonist.engine = draft.protagonist.tank.engines.find(
|
||||
(engine) => engine.id === unlock.id
|
||||
(engine) => engine.id === unlock.id,
|
||||
)!;
|
||||
} else if (unlock.type === ModuleType.TRACKS) {
|
||||
} else if (unlock.type === ModuleType.MODULE_TYPE_TRACKS) {
|
||||
draft.protagonist.track = draft.protagonist.tank.tracks.find(
|
||||
(track) => track.id === unlock.id
|
||||
(track) => track.id === unlock.id,
|
||||
)!;
|
||||
}
|
||||
});
|
||||
@@ -231,19 +231,19 @@ export function Modules() {
|
||||
const central = !first && !last;
|
||||
let module: ModuleDefinition | TankDefinition | undefined = undefined;
|
||||
|
||||
if (unlock.type === ModuleType.TRACKS) {
|
||||
if (unlock.type === ModuleType.MODULE_TYPE_TRACKS) {
|
||||
module = tank.tracks.find((track) => track.id === unlock.id)!;
|
||||
} else if (unlock.type === ModuleType.ENGINE) {
|
||||
} else if (unlock.type === ModuleType.MODULE_TYPE_ENGINE) {
|
||||
module = tank.engines.find((engine) => engine.id === unlock.id)!;
|
||||
} else if (unlock.type === ModuleType.TURRET) {
|
||||
} else if (unlock.type === ModuleType.MODULE_TYPE_TURRET) {
|
||||
module = tank.turrets.find((turret) => turret.id === unlock.id)!;
|
||||
} else if (unlock.type === ModuleType.GUN) {
|
||||
} else if (unlock.type === ModuleType.MODULE_TYPE_GUN) {
|
||||
module = tank.turrets
|
||||
.find((turret) =>
|
||||
turret.guns.some((gun) => gun.id === unlock.id)
|
||||
turret.guns.some((gun) => gun.id === unlock.id),
|
||||
)!
|
||||
.guns.find((gun) => gun.id === unlock.id)!;
|
||||
} else if (unlock.type === ModuleType.VEHICLE) {
|
||||
} else if (unlock.type === ModuleType.MODULE_TYPE_VEHICLE) {
|
||||
module = tankDefinitions.tanks[unlock.id];
|
||||
}
|
||||
|
||||
@@ -257,7 +257,7 @@ export function Modules() {
|
||||
gap="2"
|
||||
position="relative"
|
||||
>
|
||||
{unlock.cost.value > 0 && (
|
||||
{unlock.cost!.value > 0 && (
|
||||
<>
|
||||
{unlocks.length > 1 && (
|
||||
<div
|
||||
@@ -302,24 +302,24 @@ export function Modules() {
|
||||
unlock={unlock}
|
||||
tier={extractModule(module).tier}
|
||||
selected={
|
||||
(unlock.type === ModuleType.TURRET
|
||||
(unlock.type === ModuleType.MODULE_TYPE_TURRET
|
||||
? turret.id
|
||||
: unlock.type === ModuleType.GUN
|
||||
: unlock.type === ModuleType.MODULE_TYPE_GUN
|
||||
? gun.id
|
||||
: unlock.type === ModuleType.ENGINE
|
||||
: unlock.type === ModuleType.MODULE_TYPE_ENGINE
|
||||
? engine.id
|
||||
: unlock.type === ModuleType.TRACKS
|
||||
: unlock.type === ModuleType.MODULE_TYPE_TRACKS
|
||||
? track.id
|
||||
: -1) === unlock.id
|
||||
}
|
||||
top={
|
||||
unlock.type === ModuleType.TURRET
|
||||
unlock.type === ModuleType.MODULE_TYPE_TURRET
|
||||
? extractModule(module).id == topTurret.id
|
||||
: unlock.type === ModuleType.GUN
|
||||
: unlock.type === ModuleType.MODULE_TYPE_GUN
|
||||
? extractModule(module).id === topGun.id
|
||||
: unlock.type === ModuleType.ENGINE
|
||||
: unlock.type === ModuleType.MODULE_TYPE_ENGINE
|
||||
? extractModule(module).id === topEngine.id
|
||||
: unlock.type === ModuleType.TRACKS
|
||||
: unlock.type === ModuleType.MODULE_TYPE_TRACKS
|
||||
? extractModule(module).id === topTrack.id
|
||||
: false
|
||||
}
|
||||
@@ -342,7 +342,7 @@ export function Modules() {
|
||||
{(extractModule(module) as ModuleWithUnlocks).unlocks &&
|
||||
tree(
|
||||
type,
|
||||
(extractModule(module) as ModuleWithUnlocks).unlocks!
|
||||
(extractModule(module) as ModuleWithUnlocks).unlocks!,
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
@@ -397,32 +397,32 @@ export function Modules() {
|
||||
</Flex>
|
||||
|
||||
<Flex gap="2" wrap="wrap" gapY="6">
|
||||
{tree(ModuleType.ENGINE, [
|
||||
{tree(ModuleType.MODULE_TYPE_ENGINE, [
|
||||
{
|
||||
cost: { type: "xp", value: 0 },
|
||||
id: engine0.id,
|
||||
type: ModuleType.ENGINE,
|
||||
type: ModuleType.MODULE_TYPE_ENGINE,
|
||||
},
|
||||
])}
|
||||
{tree(ModuleType.TRACKS, [
|
||||
{tree(ModuleType.MODULE_TYPE_TRACKS, [
|
||||
{
|
||||
cost: { type: "xp", value: 0 },
|
||||
id: track0.id,
|
||||
type: ModuleType.TRACKS,
|
||||
type: ModuleType.MODULE_TYPE_TRACKS,
|
||||
},
|
||||
])}
|
||||
{tree(ModuleType.TURRET, [
|
||||
{tree(ModuleType.MODULE_TYPE_TURRET, [
|
||||
{
|
||||
cost: { type: "xp", value: 0 },
|
||||
id: turret0.id,
|
||||
type: ModuleType.TURRET,
|
||||
type: ModuleType.MODULE_TYPE_TURRET,
|
||||
},
|
||||
])}
|
||||
{tree(ModuleType.GUN, [
|
||||
{tree(ModuleType.MODULE_TYPE_GUN, [
|
||||
{
|
||||
cost: { type: "xp", value: 0 },
|
||||
id: gun0.id,
|
||||
type: ModuleType.GUN,
|
||||
type: ModuleType.MODULE_TYPE_GUN,
|
||||
},
|
||||
])}
|
||||
</Flex>
|
||||
|
||||
@@ -41,7 +41,7 @@ export function GameModeSection() {
|
||||
style={{
|
||||
borderRadius: "var(--radius-3)",
|
||||
background: `url(${asset(
|
||||
`icons/game_mode_banners/${id}.webp`
|
||||
`icons/game_mode_banners/${id}.webp`,
|
||||
)})`,
|
||||
backgroundSize: "cover",
|
||||
backgroundPosition: "center",
|
||||
@@ -51,7 +51,7 @@ export function GameModeSection() {
|
||||
flexGrow="1"
|
||||
style={{
|
||||
background: `url(${asset(
|
||||
`icons/game_mode_banners/${id}.webp`
|
||||
`icons/game_mode_banners/${id}.webp`,
|
||||
)})`,
|
||||
backgroundSize: "cover",
|
||||
backgroundPosition: "center",
|
||||
@@ -71,7 +71,7 @@ export function GameModeSection() {
|
||||
justify="center"
|
||||
>
|
||||
<Text size="4" weight="bold">
|
||||
{unwrap(gameMode.name)}
|
||||
{unwrap(gameMode.name!)}
|
||||
</Text>
|
||||
|
||||
<Flex direction="column">
|
||||
@@ -88,9 +88,9 @@ export function GameModeSection() {
|
||||
objectFit: "contain",
|
||||
}}
|
||||
src={asset(`icons/provisions/${id}.webp`)}
|
||||
alt={unwrap(provisions.name)}
|
||||
alt={unwrap(provisions.name!)}
|
||||
/>
|
||||
{unwrap(provisions.name)}
|
||||
{unwrap(provisions.name!)}
|
||||
</Flex>
|
||||
</Text>
|
||||
);
|
||||
@@ -109,9 +109,9 @@ export function GameModeSection() {
|
||||
objectFit: "contain",
|
||||
}}
|
||||
src={asset(`icons/consumables/${id}.webp`)}
|
||||
alt={unwrap(consumable.name)}
|
||||
alt={unwrap(consumable.name!)}
|
||||
/>
|
||||
{unwrap(consumable.name)}
|
||||
{unwrap(consumable.name!)}
|
||||
</Flex>
|
||||
</Text>
|
||||
);
|
||||
|
||||
+15
-15
@@ -68,29 +68,29 @@ export function CustomShellButton() {
|
||||
const modified = { ...customShell, type };
|
||||
|
||||
switch (type) {
|
||||
case ShellType.AP: {
|
||||
case ShellType.SHELL_TYPE_AP: {
|
||||
modified.normalization = 5;
|
||||
modified.ricochet = 70;
|
||||
modified.explosion_radius = undefined;
|
||||
break;
|
||||
}
|
||||
case ShellType.APCR: {
|
||||
case ShellType.SHELL_TYPE_APCR: {
|
||||
modified.normalization = 2;
|
||||
modified.ricochet = 70;
|
||||
modified.explosion_radius = undefined;
|
||||
break;
|
||||
}
|
||||
case ShellType.HEAT: {
|
||||
case ShellType.SHELL_TYPE_HEAT: {
|
||||
modified.normalization = undefined;
|
||||
modified.ricochet = undefined;
|
||||
modified.explosion_radius = undefined;
|
||||
}
|
||||
case ShellType.HE: {
|
||||
case ShellType.SHELL_TYPE_HE: {
|
||||
modified.normalization = undefined;
|
||||
modified.ricochet = undefined;
|
||||
modified.explosion_radius =
|
||||
gun.shells.find(
|
||||
(shell) => shell.type === ShellType.HE
|
||||
(shell) => shell.type === ShellType.SHELL_TYPE_HE,
|
||||
)?.explosion_radius ?? 1;
|
||||
break;
|
||||
}
|
||||
@@ -104,16 +104,16 @@ export function CustomShellButton() {
|
||||
<Select.Trigger variant="classic" />
|
||||
|
||||
<Select.Content>
|
||||
<Select.Item value={`${ShellType.AP}`}>
|
||||
<Select.Item value={`${ShellType.SHELL_TYPE_AP}`}>
|
||||
{strings.common.shells.ap}
|
||||
</Select.Item>
|
||||
<Select.Item value={`${ShellType.APCR}`}>
|
||||
<Select.Item value={`${ShellType.SHELL_TYPE_APCR}`}>
|
||||
{strings.common.shells.ap_cr}
|
||||
</Select.Item>
|
||||
<Select.Item value={`${ShellType.HEAT}`}>
|
||||
<Select.Item value={`${ShellType.SHELL_TYPE_HEAT}`}>
|
||||
{strings.common.shells.hc}
|
||||
</Select.Item>
|
||||
<Select.Item value={`${ShellType.HE}`}>
|
||||
<Select.Item value={`${ShellType.SHELL_TYPE_HE}`}>
|
||||
{strings.common.shells.he}
|
||||
</Select.Item>
|
||||
</Select.Content>
|
||||
@@ -166,11 +166,11 @@ export function CustomShellButton() {
|
||||
<TextField.Root
|
||||
variant="classic"
|
||||
style={{ maxWidth: "6rem" }}
|
||||
defaultValue={customShell.penetration.near}
|
||||
defaultValue={customShell.penetration!.near}
|
||||
type="number"
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.customShell!.penetration.near =
|
||||
draft.customShell!.penetration!.near =
|
||||
event.target.valueAsNumber;
|
||||
});
|
||||
}}
|
||||
@@ -182,7 +182,7 @@ export function CustomShellButton() {
|
||||
(event.target as HTMLInputElement).blur();
|
||||
} else if (event.key === "Escape") {
|
||||
(event.target as HTMLInputElement).valueAsNumber =
|
||||
customShell.penetration.near;
|
||||
customShell.penetration!.near;
|
||||
(event.target as HTMLInputElement).blur();
|
||||
}
|
||||
}}
|
||||
@@ -193,8 +193,8 @@ export function CustomShellButton() {
|
||||
</TextField.Root>
|
||||
</Flex>
|
||||
|
||||
{(customShell.type === ShellType.AP ||
|
||||
customShell.type === ShellType.APCR) && (
|
||||
{(customShell.type === ShellType.SHELL_TYPE_AP ||
|
||||
customShell.type === ShellType.SHELL_TYPE_APCR) && (
|
||||
<>
|
||||
<Flex align="center" justify="between" gap="4" py="1">
|
||||
<Text>
|
||||
@@ -264,7 +264,7 @@ export function CustomShellButton() {
|
||||
</>
|
||||
)}
|
||||
|
||||
{customShell.type === ShellType.HE && (
|
||||
{customShell.type === ShellType.SHELL_TYPE_HE && (
|
||||
<Flex align="center" justify="between" gap="4">
|
||||
<Text>Explosion radius</Text>
|
||||
<TextField.Root
|
||||
|
||||
+7
-7
@@ -38,7 +38,7 @@ export function QuickInputs() {
|
||||
}
|
||||
|
||||
function handleTransformEvent(
|
||||
event: QuicklimeEvent<ModelTransformEventData>
|
||||
event: QuicklimeEvent<ModelTransformEventData>,
|
||||
) {
|
||||
syncInputValues(event.data);
|
||||
}
|
||||
@@ -77,7 +77,7 @@ export function QuickInputs() {
|
||||
|
||||
if (isNaN(value)) {
|
||||
yawInput.current!.value = radToDeg(
|
||||
modelTransformEvent.last!.yaw
|
||||
modelTransformEvent.last!.yaw,
|
||||
).toFixed(1);
|
||||
return;
|
||||
}
|
||||
@@ -85,15 +85,15 @@ export function QuickInputs() {
|
||||
const [pitch, yaw] = applyPitchYawLimits(
|
||||
modelTransformEvent.last!.pitch,
|
||||
degToRad(value),
|
||||
gunModelDefinition.pitch,
|
||||
gunModelDefinition.pitch!,
|
||||
turretModelDefinition.yaw,
|
||||
hasImprovedVerticalStabilizer,
|
||||
hasDownImprovedVerticalStabilizer
|
||||
hasDownImprovedVerticalStabilizer,
|
||||
);
|
||||
|
||||
modelTransformEvent.dispatch({ pitch, yaw });
|
||||
yawInput.current!.value = radToDeg(
|
||||
modelTransformEvent.last!.yaw
|
||||
modelTransformEvent.last!.yaw,
|
||||
).toFixed(1);
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
@@ -129,10 +129,10 @@ export function QuickInputs() {
|
||||
const [pitch, yaw] = applyPitchYawLimits(
|
||||
degToRad(value + initialGunPitch),
|
||||
modelTransformEvent.last!.yaw,
|
||||
gunModelDefinition.pitch,
|
||||
gunModelDefinition.pitch!,
|
||||
turretModelDefinition.yaw,
|
||||
hasImprovedVerticalStabilizer,
|
||||
hasDownImprovedVerticalStabilizer
|
||||
hasDownImprovedVerticalStabilizer,
|
||||
);
|
||||
modelTransformEvent.dispatch({ pitch, yaw });
|
||||
pitchInput.current!.value = (
|
||||
|
||||
@@ -54,12 +54,12 @@ type OptionsProps = MaybeSkeletonComponentProps & {
|
||||
|
||||
export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
|
||||
const hasCustomShell = Tankopedia.use(
|
||||
(state) => state.customShell !== undefined
|
||||
(state) => state.customShell !== undefined,
|
||||
);
|
||||
const requestedDisplay = Tankopedia.use((state) => state.requestedDisplay);
|
||||
const isFullScreen = useFullScreen();
|
||||
const advancedHighlighting = TankopediaPersistent.use(
|
||||
(state) => state.advancedHighlighting
|
||||
(state) => state.advancedHighlighting,
|
||||
);
|
||||
const fullScreenAvailable = useFullscreenAvailability(true);
|
||||
const protagonistTank = Duel.use((state) => state.protagonist.tank);
|
||||
@@ -102,8 +102,8 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
|
||||
value: (
|
||||
resolvePenetrationCoefficient(
|
||||
hasCalibratedShells,
|
||||
antagonistShell.type
|
||||
) * antagonistShell.penetration.near
|
||||
antagonistShell.type,
|
||||
) * antagonistShell.penetration!.near
|
||||
).toFixed(0),
|
||||
})}
|
||||
</Text>
|
||||
@@ -203,7 +203,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
|
||||
}}
|
||||
>
|
||||
<img
|
||||
alt={unwrap(thisShell.name)}
|
||||
alt={unwrap(thisShell.name!)}
|
||||
src={asset(`icons/shells/${thisShell.icon}.webp`)}
|
||||
style={{
|
||||
width: "50%",
|
||||
@@ -456,7 +456,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
|
||||
<Dialog.Trigger>
|
||||
<Button variant="solid" highContrast>
|
||||
<Crosshair1Icon />
|
||||
{unwrap(antagonistTank.name)}
|
||||
{unwrap(antagonistTank.name!)}
|
||||
<SmallTankIcon id={antagonistTank.id} size={16} />
|
||||
</Button>
|
||||
</Dialog.Trigger>
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ export function Title() {
|
||||
const protagonist = Duel.use((state) => state.protagonist.tank);
|
||||
const revealed = Tankopedia.use((state) => state.revealed);
|
||||
const disturbed = Tankopedia.use((state) => state.disturbed);
|
||||
const name = unwrap(protagonist.name);
|
||||
const name = unwrap(protagonist.name!);
|
||||
const fontSize = revealed
|
||||
? disturbed
|
||||
? "1.5rem"
|
||||
|
||||
+14
-14
@@ -53,24 +53,24 @@ export function Controls({
|
||||
const protagonistGunModelDefinition =
|
||||
protagonistTurretModelDefinition.guns[protagonistGun.id];
|
||||
const protagonistHullOrigin = new Vector3(
|
||||
protagonistTrackModelDefinition.origin.x,
|
||||
protagonistTrackModelDefinition.origin.y,
|
||||
-protagonistTrackModelDefinition.origin.z,
|
||||
protagonistTrackModelDefinition.origin!.x,
|
||||
protagonistTrackModelDefinition.origin!.y,
|
||||
-protagonistTrackModelDefinition.origin!.z,
|
||||
);
|
||||
const protagonistTurretOrigin = new Vector3(
|
||||
protagonistModelDefinition.turret_origin.x,
|
||||
protagonistModelDefinition.turret_origin.y,
|
||||
-protagonistModelDefinition.turret_origin.z,
|
||||
protagonistModelDefinition.turret_origin!.x,
|
||||
protagonistModelDefinition.turret_origin!.y,
|
||||
-protagonistModelDefinition.turret_origin!.z,
|
||||
);
|
||||
const protagonistGunOrigin = new Vector3(
|
||||
protagonistTurretModelDefinition.gun_origin.x,
|
||||
protagonistTurretModelDefinition.gun_origin.y,
|
||||
-protagonistTurretModelDefinition.gun_origin.z,
|
||||
protagonistTurretModelDefinition.gun_origin!.x,
|
||||
protagonistTurretModelDefinition.gun_origin!.y,
|
||||
-protagonistTurretModelDefinition.gun_origin!.z,
|
||||
);
|
||||
const antagonistGunHeight =
|
||||
protagonistTrackModelDefinition.origin.y +
|
||||
antagonistModelDefinition.turret_origin.y +
|
||||
antagonistTurretModelDefinition.gun_origin.y;
|
||||
protagonistTrackModelDefinition.origin!.y +
|
||||
antagonistModelDefinition.turret_origin!.y +
|
||||
antagonistTurretModelDefinition.gun_origin!.y;
|
||||
const disturbed = Tankopedia.use((state) => state.disturbed);
|
||||
const doAutoRotate = autoRotate && !disturbed;
|
||||
const gunHeight =
|
||||
@@ -119,7 +119,7 @@ export function Controls({
|
||||
const [pitch] = applyPitchYawLimits(
|
||||
-Infinity,
|
||||
0,
|
||||
protagonistGunModelDefinition.pitch,
|
||||
protagonistGunModelDefinition.pitch!,
|
||||
protagonistTurretModelDefinition.yaw,
|
||||
hasImprovedVerticalStabilizer,
|
||||
hasDownImprovedVerticalStabilizer,
|
||||
@@ -152,7 +152,7 @@ export function Controls({
|
||||
const [pitch] = applyPitchYawLimits(
|
||||
0,
|
||||
0,
|
||||
protagonistGunModelDefinition.pitch,
|
||||
protagonistGunModelDefinition.pitch!,
|
||||
protagonistTurretModelDefinition.yaw,
|
||||
hasImprovedVerticalStabilizer,
|
||||
hasDownImprovedVerticalStabilizer,
|
||||
|
||||
+7
-7
@@ -1,9 +1,9 @@
|
||||
import { useEffect } from 'react';
|
||||
import { degToRad } from 'three/src/math/MathUtils.js';
|
||||
import { awaitableModelDefinitions } from '../../../../../../core/awaitables/modelDefinitions';
|
||||
import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits';
|
||||
import { modelTransformEvent } from '../../../../../../core/blitzkit/modelTransform';
|
||||
import { Duel } from '../../../../../../stores/duel';
|
||||
import { useEffect } from "react";
|
||||
import { degToRad } from "three/src/math/MathUtils.js";
|
||||
import { awaitableModelDefinitions } from "../../../../../../core/awaitables/modelDefinitions";
|
||||
import { applyPitchYawLimits } from "../../../../../../core/blitz/applyPitchYawLimits";
|
||||
import { modelTransformEvent } from "../../../../../../core/blitzkit/modelTransform";
|
||||
import { Duel } from "../../../../../../stores/duel";
|
||||
|
||||
const modelDefinitions = await awaitableModelDefinitions;
|
||||
|
||||
@@ -20,7 +20,7 @@ export function InitialAligner() {
|
||||
const [pitch, yaw] = applyPitchYawLimits(
|
||||
degToRad(8),
|
||||
degToRad(25),
|
||||
gunModel.pitch,
|
||||
gunModel.pitch!,
|
||||
turretModel.yaw,
|
||||
);
|
||||
|
||||
|
||||
+8
-8
@@ -134,12 +134,12 @@ export function TankModel() {
|
||||
const hasImprovedVerticalStabilizer = hasEquipment(
|
||||
122,
|
||||
Duel.state.protagonist.tank.equipment_preset,
|
||||
Duel.state.protagonist.equipmentMatrix
|
||||
Duel.state.protagonist.equipmentMatrix,
|
||||
);
|
||||
const hasDownImprovedVerticalStabilizer = hasEquipment(
|
||||
124,
|
||||
Duel.state.protagonist.tank.equipment_preset,
|
||||
Duel.state.protagonist.equipmentMatrix
|
||||
Duel.state.protagonist.equipmentMatrix,
|
||||
);
|
||||
const boundingRect = canvas.getBoundingClientRect();
|
||||
|
||||
@@ -150,10 +150,10 @@ export function TankModel() {
|
||||
modelTransformEvent.last!.pitch,
|
||||
modelTransformEvent.last!.yaw +
|
||||
delta.x * (Math.PI / boundingRect.width),
|
||||
gunModelDefinition.pitch,
|
||||
gunModelDefinition.pitch!,
|
||||
turretModelDefinition.yaw,
|
||||
hasImprovedVerticalStabilizer,
|
||||
hasDownImprovedVerticalStabilizer
|
||||
hasDownImprovedVerticalStabilizer,
|
||||
);
|
||||
modelTransformEvent.dispatch({ pitch, yaw });
|
||||
}
|
||||
@@ -217,12 +217,12 @@ export function TankModel() {
|
||||
const hasImprovedVerticalStabilizer = hasEquipment(
|
||||
122,
|
||||
Duel.state.protagonist.tank.equipment_preset,
|
||||
Duel.state.protagonist.equipmentMatrix
|
||||
Duel.state.protagonist.equipmentMatrix,
|
||||
);
|
||||
const hasDownImprovedVerticalStabilizer = hasEquipment(
|
||||
124,
|
||||
Duel.state.protagonist.tank.equipment_preset,
|
||||
Duel.state.protagonist.equipmentMatrix
|
||||
Duel.state.protagonist.equipmentMatrix,
|
||||
);
|
||||
const boundingRect = canvas.getBoundingClientRect();
|
||||
delta.set(event.clientX, event.clientY).sub(position);
|
||||
@@ -231,10 +231,10 @@ export function TankModel() {
|
||||
[pitch, yaw] = applyPitchYawLimits(
|
||||
pitch - delta.y * (Math.PI / boundingRect.height),
|
||||
yaw + delta.x * (Math.PI / boundingRect.width),
|
||||
gunModelDefinition.pitch,
|
||||
gunModelDefinition.pitch!,
|
||||
turretModelDefinition.yaw,
|
||||
hasImprovedVerticalStabilizer,
|
||||
hasDownImprovedVerticalStabilizer
|
||||
hasDownImprovedVerticalStabilizer,
|
||||
);
|
||||
modelTransformEvent.dispatch({ pitch, yaw });
|
||||
}
|
||||
|
||||
+5
-5
@@ -37,9 +37,9 @@ export function TransitionSkeleton() {
|
||||
const gunModelDefinition = turretModelDefinition.guns[gun.id];
|
||||
const trackModelDefinition = tankModelDefinition.tracks[track.id];
|
||||
const wrapper = useRef<Group>(null);
|
||||
const hullOrigin = correctZYTuple(trackModelDefinition.origin);
|
||||
const turretOrigin = correctZYTuple(tankModelDefinition.turret_origin);
|
||||
const gunOrigin = correctZYTuple(turretModelDefinition.gun_origin);
|
||||
const hullOrigin = correctZYTuple(trackModelDefinition.origin!);
|
||||
const turretOrigin = correctZYTuple(tankModelDefinition.turret_origin!);
|
||||
const gunOrigin = correctZYTuple(turretModelDefinition.gun_origin!);
|
||||
|
||||
useTankTransform(track, turret, turretContainer, gunContainer);
|
||||
|
||||
@@ -77,7 +77,7 @@ export function TransitionSkeleton() {
|
||||
<group ref={turretContainer}>
|
||||
{armorNodes.map((node) => {
|
||||
const isCurrentTurret = node.name.startsWith(
|
||||
`turret_${turretModelDefinition.model_id.toString().padStart(2, "0")}`
|
||||
`turret_${turretModelDefinition.model_id.toString().padStart(2, "0")}`,
|
||||
);
|
||||
const isVisible = isCurrentTurret;
|
||||
if (!isVisible) {
|
||||
@@ -102,7 +102,7 @@ export function TransitionSkeleton() {
|
||||
<group ref={gunContainer}>
|
||||
{armorNodes.map((node) => {
|
||||
const isCurrentGun = node.name.startsWith(
|
||||
`gun_${gunModelDefinition.model_id.toString().padStart(2, "0")}`
|
||||
`gun_${gunModelDefinition.model_id.toString().padStart(2, "0")}`,
|
||||
);
|
||||
const isVisible = isCurrentGun;
|
||||
|
||||
|
||||
+4
-4
@@ -73,7 +73,7 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
|
||||
const [pitch, yaw] = applyPitchYawLimits(
|
||||
-Infinity,
|
||||
0,
|
||||
gunModelDefinition.pitch,
|
||||
gunModelDefinition.pitch!,
|
||||
turretModelDefinition.yaw,
|
||||
hasImprovedVerticalStabilizer,
|
||||
hasDownImprovedVerticalStabilizer,
|
||||
@@ -88,7 +88,7 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
|
||||
const [pitch, yaw] = applyPitchYawLimits(
|
||||
Infinity,
|
||||
0,
|
||||
gunModelDefinition.pitch,
|
||||
gunModelDefinition.pitch!,
|
||||
turretModelDefinition.yaw,
|
||||
hasImprovedVerticalStabilizer,
|
||||
);
|
||||
@@ -102,7 +102,7 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
|
||||
const [pitch, yaw] = applyPitchYawLimits(
|
||||
0,
|
||||
0,
|
||||
gunModelDefinition.pitch,
|
||||
gunModelDefinition.pitch!,
|
||||
turretModelDefinition.yaw,
|
||||
hasImprovedVerticalStabilizer,
|
||||
);
|
||||
@@ -124,7 +124,7 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
|
||||
const [pitch, yaw] = applyPitchYawLimits(
|
||||
modelTransformEvent.last!.pitch,
|
||||
modelTransformEvent.last!.yaw,
|
||||
gunModelDefinition.pitch,
|
||||
gunModelDefinition.pitch!,
|
||||
turretModelDefinition.yaw,
|
||||
hasImprovedVerticalStabilizer,
|
||||
);
|
||||
|
||||
@@ -22,13 +22,13 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
const thicknessRange = useMemo(() => {
|
||||
const entries = Object.values(tankDefinitions.tanks);
|
||||
const filtered = entries.filter(
|
||||
(thisTank) => thisTank.tier === protagonist.tier
|
||||
(thisTank) => thisTank.tier === protagonist.tier,
|
||||
);
|
||||
const value =
|
||||
(filtered.reduce((accumulator, thisTank) => {
|
||||
return (
|
||||
accumulator +
|
||||
thisTank.turrets.at(-1)!.guns.at(-1)!.shells[0].penetration.near
|
||||
thisTank.turrets.at(-1)!.guns.at(-1)!.shells[0].penetration!.near
|
||||
);
|
||||
}, 0) /
|
||||
filtered.length) *
|
||||
@@ -99,11 +99,11 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
isFullScreen
|
||||
? "100vh"
|
||||
: disturbed
|
||||
? "calc(100svh - 8rem)"
|
||||
: {
|
||||
initial: "28rem",
|
||||
md: "calc(100svh - 14rem)",
|
||||
}
|
||||
? "calc(100svh - 8rem)"
|
||||
: {
|
||||
initial: "28rem",
|
||||
md: "calc(100svh - 14rem)",
|
||||
}
|
||||
}
|
||||
maxWidth={isFullScreen ? undefined : "120rem"}
|
||||
flexGrow="1"
|
||||
|
||||
+3
-3
@@ -36,7 +36,7 @@ export function VoteCaster() {
|
||||
</Dialog.Trigger>
|
||||
|
||||
<Dialog.Content maxWidth="23rem">
|
||||
<Dialog.Title>Voting for {unwrap(tank.name)}</Dialog.Title>
|
||||
<Dialog.Title>Voting for {unwrap(tank.name!)}</Dialog.Title>
|
||||
|
||||
<Flex direction="column" mt="4">
|
||||
<Flex align="center" justify="between">
|
||||
@@ -118,10 +118,10 @@ export function VoteCaster() {
|
||||
survivability === null
|
||||
? ""
|
||||
: `survivability=${survivability}&`
|
||||
}player=${wargaming.id}&token=${wargaming.token}`
|
||||
}player=${wargaming.id}&token=${wargaming.token}`,
|
||||
)
|
||||
.then(
|
||||
(response) => response.json() as Promise<BlitzkitResponse>
|
||||
(response) => response.json() as Promise<BlitzkitResponse>,
|
||||
)
|
||||
.then((json) => {
|
||||
setShowDialogue(false);
|
||||
|
||||
@@ -3,20 +3,20 @@ import {
|
||||
TankPriceType,
|
||||
TankType,
|
||||
TIER_ROMAN_NUMERALS,
|
||||
} from '@blitzkit/core';
|
||||
import { Box, Flex } from '@radix-ui/themes';
|
||||
import { Var } from '../../../core/radix/var';
|
||||
import { useLocale } from '../../../hooks/useLocale';
|
||||
import { App } from '../../../stores/app';
|
||||
import { Duel } from '../../../stores/duel';
|
||||
import { classIcons } from '../../ClassIcon';
|
||||
import { CopyableCode } from '../../CopyableCode';
|
||||
import { Listing } from './components/Listing';
|
||||
} from "@blitzkit/core";
|
||||
import { Box, Flex } from "@radix-ui/themes";
|
||||
import { Var } from "../../../core/radix/var";
|
||||
import { useLocale } from "../../../hooks/useLocale";
|
||||
import { App } from "../../../stores/app";
|
||||
import { Duel } from "../../../stores/duel";
|
||||
import { classIcons } from "../../ClassIcon";
|
||||
import { CopyableCode } from "../../CopyableCode";
|
||||
import { Listing } from "./components/Listing";
|
||||
|
||||
const NATIONAL_BANNER_POSITION_OVERRIDES: Record<string, string> = {
|
||||
germany: '50% 35%',
|
||||
france: '50% 35%',
|
||||
other: '50% 35%',
|
||||
germany: "50% 35%",
|
||||
france: "50% 35%",
|
||||
other: "50% 35%",
|
||||
};
|
||||
|
||||
export function MetaSection() {
|
||||
@@ -30,10 +30,10 @@ export function MetaSection() {
|
||||
<Box
|
||||
style={{
|
||||
background: `url(/assets/images/national-wallpapers/${protagonist.nation}.jpg)`,
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundSize: 'cover',
|
||||
backgroundRepeat: "no-repeat",
|
||||
backgroundSize: "cover",
|
||||
backgroundPosition:
|
||||
NATIONAL_BANNER_POSITION_OVERRIDES[protagonist.nation] ?? 'center',
|
||||
NATIONAL_BANNER_POSITION_OVERRIDES[protagonist.nation] ?? "center",
|
||||
}}
|
||||
flexGrow="1"
|
||||
maxWidth="120rem"
|
||||
@@ -41,9 +41,9 @@ export function MetaSection() {
|
||||
<Box
|
||||
style={{
|
||||
background: `url(${asset(`flags/scratched/${protagonist.nation}.webp`)})`,
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundSize: 'min(53rem, 80vw)',
|
||||
backgroundPosition: '-8rem 50%',
|
||||
backgroundRepeat: "no-repeat",
|
||||
backgroundSize: "min(53rem, 80vw)",
|
||||
backgroundPosition: "-8rem 50%",
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
@@ -51,19 +51,19 @@ export function MetaSection() {
|
||||
align="center"
|
||||
py="6rem"
|
||||
style={{
|
||||
background: `linear-gradient(${Var('black-a8')}, ${Var('black-a8')}, ${Var('gray-1')})`,
|
||||
background: `linear-gradient(${Var("black-a8")}, ${Var("black-a8")}, ${Var("gray-1")})`,
|
||||
}}
|
||||
>
|
||||
<Flex direction="column" align="center" gap="6">
|
||||
<Flex
|
||||
gap={{ initial: '2', sm: '8' }}
|
||||
direction={{ initial: 'column', sm: 'row' }}
|
||||
gap={{ initial: "2", sm: "8" }}
|
||||
direction={{ initial: "column", sm: "row" }}
|
||||
>
|
||||
<Flex direction="column" width="100%" gap="2">
|
||||
<Listing label={strings.website.tools.tankopedia.meta.nation}>
|
||||
<Flex align="center" gap="1">
|
||||
<img
|
||||
style={{ width: '1em', height: '1em' }}
|
||||
style={{ width: "1em", height: "1em" }}
|
||||
alt={protagonist.nation}
|
||||
src={asset(`flags/circle/${protagonist.nation}.webp`)}
|
||||
/>
|
||||
@@ -86,10 +86,10 @@ export function MetaSection() {
|
||||
<Listing
|
||||
label={strings.website.tools.tankopedia.meta.type}
|
||||
color={
|
||||
protagonist.type === TankType.COLLECTOR
|
||||
? 'blue'
|
||||
: protagonist.type === TankType.PREMIUM
|
||||
? 'amber'
|
||||
protagonist.type === TankType.TANK_TYPE_COLLECTOR
|
||||
? "blue"
|
||||
: protagonist.type === TankType.TANK_TYPE_PREMIUM
|
||||
? "amber"
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
@@ -117,7 +117,7 @@ export function MetaSection() {
|
||||
label={strings.website.tools.tankopedia.meta.dev_name}
|
||||
>
|
||||
<CopyableCode
|
||||
copy={devName}
|
||||
copy={protagonist.dev_name}
|
||||
variant="soft"
|
||||
highContrast
|
||||
color="gray"
|
||||
@@ -126,37 +126,38 @@ export function MetaSection() {
|
||||
</CopyableCode>
|
||||
</Listing>
|
||||
)}
|
||||
{protagonist.type === TankType.PREMIUM && (
|
||||
{protagonist.type === TankType.TANK_TYPE_PREMIUM && (
|
||||
<Listing
|
||||
label={strings.website.tools.tankopedia.meta.purchase}
|
||||
>
|
||||
<Flex align="center" gap="1">
|
||||
{protagonist.price.value / 400}
|
||||
{protagonist.price!.value / 400}
|
||||
<img
|
||||
style={{ width: '1em', height: '1em' }}
|
||||
style={{ width: "1em", height: "1em" }}
|
||||
alt="gold"
|
||||
src={asset('icons/currencies/gold.webp')}
|
||||
src={asset("icons/currencies/gold.webp")}
|
||||
/>
|
||||
</Flex>
|
||||
</Listing>
|
||||
)}
|
||||
<Listing
|
||||
label={
|
||||
protagonist.type === TankType.RESEARCHABLE
|
||||
protagonist.type === TankType.TANK_TYPE_RESEARCHABLE
|
||||
? strings.website.tools.tankopedia.meta.purchase
|
||||
: strings.website.tools.tankopedia.meta.restoration
|
||||
}
|
||||
>
|
||||
<Flex align="center" gap="1">
|
||||
{protagonist.price.value.toLocaleString(locale)}
|
||||
{protagonist.price!.value.toLocaleString(locale)}
|
||||
<img
|
||||
style={{ width: '1em', height: '1em' }}
|
||||
alt={TankPriceType[protagonist.price.type]}
|
||||
style={{ width: "1em", height: "1em" }}
|
||||
alt={TankPriceType[protagonist.price!.type]}
|
||||
src={asset(
|
||||
`icons/currencies/${
|
||||
protagonist.price.type === TankPriceType.GOLD
|
||||
? 'gold'
|
||||
: 'silver'
|
||||
protagonist.price!.type ===
|
||||
TankPriceType.TANK_PRICE_TYPE_GOLD
|
||||
? "gold"
|
||||
: "silver"
|
||||
}.webp`,
|
||||
)}
|
||||
/>
|
||||
@@ -164,15 +165,16 @@ export function MetaSection() {
|
||||
</Listing>
|
||||
<Listing label={strings.website.tools.tankopedia.meta.sale}>
|
||||
<Flex align="center" gap="1">
|
||||
{(protagonist.price.value / 2).toLocaleString(locale)}
|
||||
{(protagonist.price!.value / 2).toLocaleString(locale)}
|
||||
<img
|
||||
style={{ width: '1em', height: '1em' }}
|
||||
alt={TankPriceType[protagonist.price.type]}
|
||||
style={{ width: "1em", height: "1em" }}
|
||||
alt={TankPriceType[protagonist.price!.type]}
|
||||
src={asset(
|
||||
`icons/currencies/${
|
||||
protagonist.price.type === TankPriceType.GOLD
|
||||
? 'gold'
|
||||
: 'silver'
|
||||
protagonist.price!.type ===
|
||||
TankPriceType.TANK_PRICE_TYPE_GOLD
|
||||
? "gold"
|
||||
: "silver"
|
||||
}.webp`,
|
||||
)}
|
||||
/>
|
||||
@@ -188,9 +190,9 @@ export function MetaSection() {
|
||||
.value as number
|
||||
).toLocaleString(locale)}
|
||||
<img
|
||||
style={{ width: '1em', height: '1em' }}
|
||||
style={{ width: "1em", height: "1em" }}
|
||||
alt="xp"
|
||||
src={asset('icons/currencies/xp.webp')}
|
||||
src={asset("icons/currencies/xp.webp")}
|
||||
/>
|
||||
</Flex>
|
||||
</Listing>
|
||||
|
||||
@@ -36,7 +36,7 @@ export function Node({ id, highlight, nextIds, skeleton }: NodeProps) {
|
||||
? 0
|
||||
: (tank.research_cost.research_cost_type!.value as number) /
|
||||
xpMultiplier),
|
||||
0
|
||||
0,
|
||||
);
|
||||
const averages = averageDefinitions.averages[tank.id]?.mu;
|
||||
const averageXp = averages ? averages.xp / averages.battles : undefined;
|
||||
@@ -78,7 +78,7 @@ export function Node({ id, highlight, nextIds, skeleton }: NodeProps) {
|
||||
}}
|
||||
>
|
||||
<img
|
||||
alt={unwrap(tank.name)}
|
||||
alt={unwrap(tank.name!)}
|
||||
src={asset(`icons/tanks/big/${id}.webp`)}
|
||||
width={64}
|
||||
height={64}
|
||||
@@ -92,7 +92,7 @@ export function Node({ id, highlight, nextIds, skeleton }: NodeProps) {
|
||||
<Text color="gray" size="1">
|
||||
{TIER_ROMAN_NUMERALS[tank.tier]}
|
||||
</Text>
|
||||
<Text wrap="nowrap">{unwrap(tank.name)}</Text>
|
||||
<Text wrap="nowrap">{unwrap(tank.name!)}</Text>
|
||||
</Flex>
|
||||
<Flex gap="2" align="center">
|
||||
<Text color="gray" size="1" wrap="nowrap">
|
||||
@@ -122,7 +122,7 @@ export function Node({ id, highlight, nextIds, skeleton }: NodeProps) {
|
||||
objectPosition: "center",
|
||||
}}
|
||||
/>
|
||||
{formatCompact(locale, tank.price.value)}
|
||||
{formatCompact(locale, tank.price!.value)}
|
||||
</Flex>
|
||||
</Text>
|
||||
</Flex>
|
||||
@@ -135,7 +135,7 @@ export function Node({ id, highlight, nextIds, skeleton }: NodeProps) {
|
||||
? strings.website.tools.tankopedia.tech_tree.battle
|
||||
: literals(
|
||||
strings.website.tools.tankopedia.tech_tree.battles,
|
||||
{ count: games }
|
||||
{ count: games },
|
||||
))}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
@@ -26,14 +26,18 @@ export function TechTreeSection({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
function extend(line: Line): Line[] {
|
||||
const root = tankDefinitions.tanks[line.at(-1)!];
|
||||
|
||||
if (root.ancestors === undefined || root.ancestors.length === 0 || root.tier === 1) {
|
||||
if (
|
||||
root.ancestors === undefined ||
|
||||
root.ancestors.length === 0 ||
|
||||
root.tier === 1
|
||||
) {
|
||||
return [line];
|
||||
} else {
|
||||
if (root.ancestors.length === 1 || root.tier === 2) {
|
||||
line.push(
|
||||
root.ancestors.find(
|
||||
(ancestor) => !tankDefinitions.tanks[ancestor].deprecated
|
||||
) ?? root.ancestors[0]
|
||||
(ancestor) => !tankDefinitions.tanks[ancestor].deprecated,
|
||||
) ?? root.ancestors[0],
|
||||
);
|
||||
return extend(line);
|
||||
} else {
|
||||
@@ -52,7 +56,7 @@ export function TechTreeSection({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
const [lineIndex, setLineIndex] = useState(0);
|
||||
const line = useMemo(
|
||||
() => (lines.length === 0 ? [master.id] : [...lines[lineIndex]].reverse()),
|
||||
[master, lineIndex]
|
||||
[master, lineIndex],
|
||||
);
|
||||
const totalXp = line.reduce(
|
||||
(xp, id) =>
|
||||
@@ -62,11 +66,11 @@ export function TechTreeSection({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
? 0
|
||||
: (tankDefinitions.tanks[id].research_cost.research_cost_type!
|
||||
.value as number)),
|
||||
0
|
||||
0,
|
||||
);
|
||||
const totalCredits = line.reduce(
|
||||
(credits, id) => credits + tankDefinitions.tanks[id].price.value,
|
||||
0
|
||||
(credits, id) => credits + tankDefinitions.tanks[id].price!.value,
|
||||
0,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -75,7 +79,7 @@ export function TechTreeSection({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
});
|
||||
|
||||
if (
|
||||
master.type !== TankType.RESEARCHABLE ||
|
||||
master.type !== TankType.TANK_TYPE_RESEARCHABLE ||
|
||||
master.ancestors === undefined ||
|
||||
master.ancestors.length === 0
|
||||
) {
|
||||
|
||||
@@ -5,20 +5,20 @@ import {
|
||||
TankType,
|
||||
type CompositeStats,
|
||||
type CompositeStatsKey,
|
||||
} from '@blitzkit/core';
|
||||
import strings from '@blitzkit/i18n/strings/en.json';
|
||||
import { amberDark, blueDark } from '@radix-ui/colors';
|
||||
import { Flex, Text, type FlexProps } from '@radix-ui/themes';
|
||||
import { times } from 'lodash-es';
|
||||
import type { ReactNode } from 'react';
|
||||
import { breakdownConfig } from '../constants/embeds';
|
||||
import { toRadiusVar } from '../core/radix/utils';
|
||||
import { useLocale } from '../hooks/useLocale';
|
||||
import { useEmbedStateCurry } from '../stores/embedState/utilities';
|
||||
import { ClassHeavy } from './ClassIcon/components/ClassHeavy';
|
||||
import { ClassLight } from './ClassIcon/components/ClassLight';
|
||||
import { ClassMedium } from './ClassIcon/components/ClassMedium';
|
||||
import { ClassTankDestroyer } from './ClassIcon/components/ClassTankDestroyer';
|
||||
} from "@blitzkit/core";
|
||||
import strings from "@blitzkit/i18n/strings/en.json";
|
||||
import { amberDark, blueDark } from "@radix-ui/colors";
|
||||
import { Flex, Text, type FlexProps } from "@radix-ui/themes";
|
||||
import { times } from "lodash-es";
|
||||
import type { ReactNode } from "react";
|
||||
import { breakdownConfig } from "../constants/embeds";
|
||||
import { toRadiusVar } from "../core/radix/utils";
|
||||
import { useLocale } from "../hooks/useLocale";
|
||||
import { useEmbedStateCurry } from "../stores/embedState/utilities";
|
||||
import { ClassHeavy } from "./ClassIcon/components/ClassHeavy";
|
||||
import { ClassLight } from "./ClassIcon/components/ClassLight";
|
||||
import { ClassMedium } from "./ClassIcon/components/ClassMedium";
|
||||
import { ClassTankDestroyer } from "./ClassIcon/components/ClassTankDestroyer";
|
||||
|
||||
type BreakdownEmbedWrapperProps = FlexProps & {
|
||||
children: ReactNode;
|
||||
@@ -33,7 +33,7 @@ export function BreakdownEmbedWrapper({
|
||||
return (
|
||||
<Flex
|
||||
direction="column"
|
||||
gap={useEmbedState('listGap')}
|
||||
gap={useEmbedState("listGap")}
|
||||
height="100%"
|
||||
{...props}
|
||||
>
|
||||
@@ -60,65 +60,65 @@ export function BreakdownEmbedCard({
|
||||
flexShrink="0"
|
||||
direction="column"
|
||||
style={{
|
||||
borderRadius: toRadiusVar(useEmbedState('cardRadius')),
|
||||
overflow: 'hidden',
|
||||
backgroundColor: useEmbedState('cardBodyBackgroundColor'),
|
||||
borderRadius: toRadiusVar(useEmbedState("cardRadius")),
|
||||
overflow: "hidden",
|
||||
backgroundColor: useEmbedState("cardBodyBackgroundColor"),
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
justify="center"
|
||||
style={{
|
||||
backgroundColor: useEmbedState('cardHeaderBackgroundColor'),
|
||||
backgroundColor: useEmbedState("cardHeaderBackgroundColor"),
|
||||
}}
|
||||
p={useEmbedState('cardHeaderPadding')}
|
||||
p={useEmbedState("cardHeaderPadding")}
|
||||
>
|
||||
<Text
|
||||
{...{
|
||||
...useRichText('cardTitle'),
|
||||
...useRichText("cardTitle"),
|
||||
style:
|
||||
tank === null
|
||||
? undefined
|
||||
: tank === undefined ||
|
||||
tank.type === TankType.RESEARCHABLE ||
|
||||
!useEmbedState('cardTitleTypeColor')
|
||||
tank.type === TankType.TANK_TYPE_RESEARCHABLE ||
|
||||
!useEmbedState("cardTitleTypeColor")
|
||||
? undefined
|
||||
: {
|
||||
color:
|
||||
tank.type === TankType.PREMIUM
|
||||
tank.type === TankType.TANK_TYPE_PREMIUM
|
||||
? amberDark.amber11
|
||||
: blueDark.blue11,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Flex align="center" gap="1">
|
||||
{useEmbedState('cardTitleClassIcon') &&
|
||||
{useEmbedState("cardTitleClassIcon") &&
|
||||
tank !== null &&
|
||||
tank !== undefined && (
|
||||
<>
|
||||
{tank.class === TankClass.LIGHT && (
|
||||
{tank.class === TankClass.TANK_CLASS_LIGHT && (
|
||||
<ClassLight width="1em" height="1em" />
|
||||
)}
|
||||
{tank.class === TankClass.MEDIUM && (
|
||||
{tank.class === TankClass.TANK_CLASS_MEDIUM && (
|
||||
<ClassMedium width="1em" height="1em" />
|
||||
)}
|
||||
{tank.class === TankClass.HEAVY && (
|
||||
{tank.class === TankClass.TANK_CLASS_HEAVY && (
|
||||
<ClassHeavy width="1em" height="1em" />
|
||||
)}
|
||||
{tank.class === TankClass.TANK_DESTROYER && (
|
||||
{tank.class === TankClass.TANK_CLASS_TANK_DESTROYER && (
|
||||
<ClassTankDestroyer width="1em" height="1em" />
|
||||
)}
|
||||
</>
|
||||
)}{' '}
|
||||
)}{" "}
|
||||
{tank === null
|
||||
? 'Total'
|
||||
? "Total"
|
||||
: tank === undefined
|
||||
? `Unknown tank`
|
||||
: unwrap(tank.name)}
|
||||
: unwrap(tank.name!)}
|
||||
</Flex>
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
<Flex p={useEmbedState('cardBodyPadding')}>
|
||||
<Flex p={useEmbedState("cardBodyPadding")}>
|
||||
{times(4, (index) => {
|
||||
const column = (index + 1) as 1 | 2 | 3 | 4;
|
||||
const statKey = useEmbedState(`column${column}`) as CompositeStatsKey;
|
||||
@@ -129,13 +129,13 @@ export function BreakdownEmbedCard({
|
||||
key={column}
|
||||
direction="column"
|
||||
align="center"
|
||||
gap={useEmbedState('columnGap')}
|
||||
gap={useEmbedState("columnGap")}
|
||||
flexGrow="1"
|
||||
>
|
||||
<Text {...useRichText('columnValue')}>
|
||||
<Text {...useRichText("columnValue")}>
|
||||
{formatCompositeStat(composite[statKey], statKey, composite)}
|
||||
</Text>
|
||||
<Text {...useRichText('columnLabel')}>
|
||||
<Text {...useRichText("columnLabel")}>
|
||||
{customLabel.length === 0
|
||||
? strings.common.composite_stats[statKey]
|
||||
: customLabel}
|
||||
|
||||
Reference in new issue
Block a user