fix: some linting for new proto 3

This commit is contained in:
TrèsAbhi committed 2024-10-05 16:44:07 -05:00
1 parent 79bc259565
commit f14ca394b5
29 files changed
+522 -418

No files matched your search

+1 -2
View File
@@ -24,8 +24,7 @@ export * from './rating';
export * from './regions';
export * from './regionSubdomainToRegion';
export * from './regionToRegionSubdomain';
export * from './resolveNearPenetration';
export * from './searchClansAcrossRegions';
export * from './searchCurrentRatingPlayers';
export * from './searchPlayersAcrossRegions';
export * from './toUniqueId';
export * from './toUniqueId';
@@ -1,5 +0,0 @@
import { Vector2Tuple } from 'three';
export function resolveNearPenetration(penetration: number | Vector2Tuple) {
return typeof penetration === 'number' ? penetration : penetration[0];
}
+3 -3
View File
@@ -1,5 +1,5 @@
import { forEachRight } from 'lodash-es';
import { GunDefinition, TurretDefinition } from './tankDefinitions';
import { GunDefinition, TurretDefinition } from '../protos';
export function uniqueGuns(turrets: TurretDefinition[]) {
const gunIds = new Set<number>();
@@ -7,8 +7,8 @@ export function uniqueGuns(turrets: TurretDefinition[]) {
forEachRight(turrets, (turret) => {
forEachRight(turret.guns, (gun) => {
if (!gunIds.has(gun.id)) {
gunIds.add(gun.id);
if (!gunIds.has(gun.gunType!.value.base.id)) {
gunIds.add(gun.gunType!.value.base.id);
guns.push({ turret, gun });
}
});
@@ -70,7 +70,7 @@ message EngineDefinition {
required string name = 2;
optional uint32 xp = 3;
required Tier tier = 4;
optional float fire_chance = 5;
required float fire_chance = 5;
required uint32 power = 6;
required uint32 weight = 7;
repeated Unlock unlocks = 8;
@@ -98,8 +98,8 @@ message GunDefinition {
}
message GunDefinitionRegular {
required GunDefinitionBase gun = 1;
required GunDefinitionRegularProperties regular = 2;
required GunDefinitionBase base = 1;
required GunDefinitionRegularProperties extension = 2;
}
message GunDefinitionRegularProperties {
@@ -107,8 +107,8 @@ message GunDefinitionRegularProperties {
}
message GunDefinitionAutoLoader {
required GunDefinitionBase gun = 1;
required GunDefinitionAutoLoaderProperties auto_loader = 2;
required GunDefinitionBase base = 1;
required GunDefinitionAutoLoaderProperties extension = 2;
}
message GunDefinitionAutoLoaderProperties {
@@ -118,8 +118,8 @@ message GunDefinitionAutoLoaderProperties {
}
message GunDefinitionAutoReloader {
required GunDefinitionBase gun = 1;
required GunDefinitionAutoReloaderProperties auto_reloader = 2;
required GunDefinitionBase base = 1;
required GunDefinitionAutoReloaderProperties extension = 2;
}
message GunDefinitionAutoReloaderProperties {
@@ -12,7 +12,9 @@ interface BodyProps {
export function Body({ stats }: BodyProps) {
const members = CompareEphemeral.use((state) => state.members);
const hasNonRegularGun = members.some(({ gun }) => gun.type !== 'regular');
const hasNonRegularGun = members.some(
({ gun }) => gun.gunType!.$case !== 'regular',
);
const mutateCompareEphemeral = CompareEphemeral.useMutation();
return (
@@ -29,35 +31,43 @@ export function Body({ stats }: BodyProps) {
<StickyColumnHeaderCell key={key} top={137}>
<Flex justify="center">
<Flex>
{gun.shells.map((thisShell, shellIndex) => (
<IconButton
color={thisShell.id === shell.id ? undefined : 'gray'}
variant="soft"
key={thisShell.id}
style={{
borderTopLeftRadius: shellIndex === 0 ? undefined : 0,
borderBottomLeftRadius:
shellIndex === 0 ? undefined : 0,
borderTopRightRadius:
shellIndex === gun.shells.length - 1 ? undefined : 0,
borderBottomRightRadius:
shellIndex === gun.shells.length - 1 ? undefined : 0,
marginLeft: shellIndex === 0 ? 0 : -1,
}}
onClick={() => {
mutateCompareEphemeral((draft) => {
draft.members[index].shell = thisShell;
});
}}
>
<img
alt={thisShell.name}
width={16}
height={16}
src={asset(`icons/shells/${thisShell.icon}.webp`)}
/>
</IconButton>
))}
{gun.gunType!.value.gun.shells.map(
(thisShell, shellIndex) => (
<IconButton
color={thisShell.id === shell.id ? undefined : 'gray'}
variant="soft"
key={thisShell.id}
style={{
borderTopLeftRadius: shellIndex === 0 ? undefined : 0,
borderBottomLeftRadius:
shellIndex === 0 ? undefined : 0,
borderTopRightRadius:
shellIndex ===
gun.gunType!.value.gun.shells.length - 1
? undefined
: 0,
borderBottomRightRadius:
shellIndex ===
gun.gunType!.value.gun.shells.length - 1
? undefined
: 0,
marginLeft: shellIndex === 0 ? 0 : -1,
}}
onClick={() => {
mutateCompareEphemeral((draft) => {
draft.members[index].shell = thisShell;
});
}}
>
<img
alt={thisShell.name}
width={16}
height={16}
src={asset(`icons/shells/${thisShell.icon}.webp`)}
/>
</IconButton>
),
)}
</Flex>
</Flex>
</StickyColumnHeaderCell>
@@ -1,11 +1,11 @@
import {
asset,
availableProvisions,
consumableDefinitions,
equipmentDefinitions,
fetchConsumableDefinitions,
fetchEquipmentDefinitions,
fetchProvisionDefinitions,
fetchSkillDefinitions,
permanentSkills,
provisionDefinitions,
skillDefinitions,
} from '@blitzkit/core';
import { checkConsumableProvisionInclusivity } from '@blitzkit/core/src/blitzkit/checkConsumableProvisionInclusivity';
import { ComponentPlaceholderIcon } from '@radix-ui/react-icons';
@@ -31,10 +31,10 @@ interface CompareTableProps {
stats: TankCharacteristics[];
}
const awaitedSkillDefinitions = await skillDefinitions;
const awaitedEquipmentDefinitions = await equipmentDefinitions;
const awaitedConsumableDefinitions = await consumableDefinitions;
const awaitedProvisionDefinitions = await provisionDefinitions;
const skillDefinitions = await fetchSkillDefinitions();
const equipmentDefinitions = await fetchEquipmentDefinitions();
const consumableDefinitions = await fetchConsumableDefinitions();
const provisionDefinitions = await fetchProvisionDefinitions();
export function CompareTable({ stats }: CompareTableProps) {
const crewSkills = CompareEphemeral.use((state) => state.crewSkills);
@@ -84,10 +84,10 @@ export function CompareTable({ stats }: CompareTableProps) {
<Popover.Trigger>
<Button variant="ghost" radius="large">
<Flex direction="column" style={{ gap: 2 }}>
{Object.entries(awaitedSkillDefinitions.classes).map(
{Object.entries(skillDefinitions.classes).map(
([tankClass, skills]) => (
<Flex key={tankClass} style={{ gap: 2 }}>
{skills.map((skill) => (
{skills.skills.map((skill) => (
<div
key={skill}
style={{
@@ -169,14 +169,14 @@ export function CompareTable({ stats }: CompareTableProps) {
index,
) => {
const equipmentPreset =
awaitedEquipmentDefinitions.presets[tank.equipment];
equipmentDefinitions.presets[tank.equipmentPreset];
const provisionsList = availableProvisions(
tank,
gun,
awaitedProvisionDefinitions,
provisionDefinitions,
);
const consumablesList = Object.values(
awaitedConsumableDefinitions,
consumableDefinitions,
).filter((consumable) =>
checkConsumableProvisionInclusivity(consumable, tank, gun),
);
@@ -238,7 +238,8 @@ export function CompareTable({ stats }: CompareTableProps) {
member.turret = member.tank.turrets[0];
member.gun = member.turret.guns[0];
member.shell = member.gun.shells[0];
member.shell =
member.gun.gunType!.value.base.shells[0];
member.engine = member.tank.engines[0];
member.track = member.tank.tracks[0];
});
@@ -254,7 +255,8 @@ export function CompareTable({ stats }: CompareTableProps) {
member.turret = member.tank.turrets.at(-1)!;
member.gun = member.turret.guns.at(-1)!;
member.shell = member.gun.shells[0];
member.shell =
member.gun.gunType!.value.base.shells[0];
member.engine = member.tank.engines.at(-1)!;
member.track = member.tank.tracks.at(-1)!;
});
@@ -282,7 +284,8 @@ export function CompareTable({ stats }: CompareTableProps) {
{provisions.map((provision, index) => (
<img
alt={
awaitedProvisionDefinitions[provision].name
provisionDefinitions.provisions[provision]
.name
}
key={provision}
src={asset(
@@ -326,7 +329,7 @@ export function CompareTable({ stats }: CompareTableProps) {
(provision) => provision.id,
)}
selected={provisions}
disabled={tank.provisions === provisions.length}
disabled={tank.maxProvisions === provisions.length}
onChange={(provisions) => {
mutateCompareEphemeral((draft) => {
draft.members[index].provisions = provisions;
@@ -462,8 +465,9 @@ export function CompareTable({ stats }: CompareTableProps) {
{consumables.map((consumable, index) => (
<img
alt={
awaitedConsumableDefinitions[consumable]
.name
consumableDefinitions.consumables[
consumable
].name
}
key={consumable}
src={asset(
@@ -510,7 +514,9 @@ export function CompareTable({ stats }: CompareTableProps) {
draft.members[index].consumables = consumables;
});
}}
disabled={tank.consumables === consumables.length}
disabled={
tank.maxConsumables === consumables.length
}
/>
<Flex justify="end" mt="4">
@@ -1,4 +1,4 @@
import { provisionDefinitions } from '@blitzkit/core';
import { fetchProvisionDefinitions } from '@blitzkit/core';
import { PlusIcon, TrashIcon } from '@radix-ui/react-icons';
import { Button, Dialog, Flex, SegmentedControl } from '@radix-ui/themes';
import { tankToCompareMember } from '../../core/blitzkit/tankToCompareMember';
@@ -14,7 +14,7 @@ interface ControlsProps {
onAddTankDialogOpenChange: (open: boolean) => void;
}
const awaitedProvisionDefinitions = await provisionDefinitions;
const provisionDefinitions = await fetchProvisionDefinitions();
export function Controls({
addTankDialogOpen,
@@ -51,7 +51,7 @@ export function Controls({
onSelect={(tank) => {
mutateCompareEphemeral((draft) => {
draft.members.push(
tankToCompareMember(tank, awaitedProvisionDefinitions),
tankToCompareMember(tank, provisionDefinitions),
);
draft.sorting = undefined;
});
@@ -61,7 +61,7 @@ export function Controls({
mutateCompareEphemeral((draft) => {
draft.members.push(
...tanks.map((tank) =>
tankToCompareMember(tank, awaitedProvisionDefinitions),
tankToCompareMember(tank, provisionDefinitions),
),
);
draft.sorting = undefined;
@@ -1,4 +1,4 @@
import { provisionDefinitions } from '@blitzkit/core';
import { fetchProvisionDefinitions } from '@blitzkit/core';
import { ExternalLinkIcon, LoopIcon, TrashIcon } from '@radix-ui/react-icons';
import { Dialog, Flex, IconButton } from '@radix-ui/themes';
import Link from 'next/link';
@@ -12,7 +12,7 @@ interface TankControlProps {
id: number;
}
const awaitedProvisionDefinitions = await provisionDefinitions;
const provisionDefinitions = await fetchProvisionDefinitions();
export function TankControl({ index, id }: TankControlProps) {
const [switchTankDialogOpen, setSwitchTankDialogOpen] = useState(false);
@@ -58,7 +58,7 @@ export function TankControl({ index, id }: TankControlProps) {
mutateCompareEphemeral((draft) => {
draft.members[index] = tankToCompareMember(
tank,
awaitedProvisionDefinitions,
provisionDefinitions,
);
draft.sorting = undefined;
});
@@ -1,6 +1,6 @@
import type { EquipmentPreset } from '@blitzkit/core';
import { Flex } from '@radix-ui/themes';
import { cloneDeep } from 'lodash-es';
import { chunk, cloneDeep } from 'lodash-es';
import type { EquipmentMatrix } from '../stores/duel';
import { EquipmentButton } from './ModuleButtons/EquipmentButton';
@@ -17,12 +17,12 @@ export function EquipmentManager({
}: EquipmentManagerProps) {
return (
<Flex direction="column" gap="2">
{preset.map((equipmentRow, rowIndex) => (
{chunk(preset.slots, 3).map((equipmentRow, rowIndex) => (
<Flex gap="2" key={rowIndex}>
{equipmentRow.map((equipment, columnIndex) => (
<Flex key={columnIndex}>
<EquipmentButton
equipment={equipment[0]}
equipment={equipment.left}
style={{
borderTopRightRadius: 0,
borderBottomRightRadius: 0,
@@ -41,7 +41,7 @@ export function EquipmentManager({
}}
/>
<EquipmentButton
equipment={equipment[1]}
equipment={equipment.right}
style={{
borderTopLeftRadius: 0,
borderBottomLeftRadius: 0,
@@ -1,4 +1,4 @@
import { asset, equipmentDefinitions } from '@blitzkit/core';
import { asset, fetchEquipmentDefinitions } from '@blitzkit/core';
import { GenericTankComponentButton } from './GenericTankComponentButton';
import type { TankComponentButtonProps } from './TankComponentButton';
@@ -6,12 +6,12 @@ interface EquipmentButtonProps extends TankComponentButtonProps {
equipment: number;
}
const awaitedEquipmentDefinitions = await equipmentDefinitions;
const equipmentDefinitions = await fetchEquipmentDefinitions();
export function EquipmentButton({ equipment, ...props }: EquipmentButtonProps) {
return (
<GenericTankComponentButton
tooltip={awaitedEquipmentDefinitions.equipments[equipment].name}
tooltip={equipmentDefinitions.equipments[equipment].name}
icon={asset(`icons/equipment/${equipment}.webp`)}
{...props}
/>
@@ -60,7 +60,7 @@ export function ModuleManager({
const draft = { ...modules };
draft.turret = turret;
draft.gun = draft.turret.guns.at(-1)!;
draft.shell = draft.gun.shells[0];
draft.shell = draft.gun.gunType!.value.base.shells[0];
onChange?.(draft);
setTurretMenuOpen(false);
@@ -81,7 +81,9 @@ export function ModuleManager({
<ModuleButton
module="gun"
discriminator={TIER_ROMAN_NUMERALS[modules.gun.tier]}
discriminator={
TIER_ROMAN_NUMERALS[modules.gun.gunType!.value.base.tier]
}
onClick={() => setGunMenuOpen(true)}
/>
@@ -91,16 +93,18 @@ export function ModuleManager({
</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.RadioGroup value={`${modules.gun.id}`}>
<DropdownMenu.RadioGroup
value={`${modules.gun.gunType!.value.base.id}`}
>
{[...modules.turret.guns].reverse().map((gun) => {
return (
<DropdownMenu.RadioItem
key={gun.id}
value={`${gun.id}`}
key={gun.gunType!.value.base.id}
value={`${gun.gunType!.value.base.id}`}
onClick={() => {
const draft = { ...modules };
draft.gun = gun;
draft.shell = draft.gun.shells[0];
draft.shell = draft.gun.gunType!.value.base.shells[0];
onChange?.(draft);
setGunMenuOpen(false);
@@ -109,9 +113,11 @@ export function ModuleManager({
<ModuleButton
variant="ghost"
module="gun"
discriminator={TIER_ROMAN_NUMERALS[gun.tier]}
discriminator={
TIER_ROMAN_NUMERALS[gun.gunType!.value.base.tier]
}
/>{' '}
{gun.name}
{gun.gunType!.value.base.name}
</DropdownMenu.RadioItem>
);
})}
@@ -1,10 +1,10 @@
import {
asset,
modelDefinitions,
fetchModelDefinitions,
normalizeBoundingBox,
resolveDpm,
resolveNearPenetration,
tankIcon,
TankType,
unionBoundingBox,
type TankDefinition,
} from '@blitzkit/core';
@@ -13,7 +13,6 @@ import { Flex, Link, Text } from '@radix-ui/themes';
import { assignInlineVars } from '@vanilla-extract/dynamic';
import { useMemo } from 'react';
import { resolveReload } from '../../../core/blitzkit/resolveReload';
import { useAwait } from '../../../hooks/useAwait';
import { $tankopediaSort } from '../../../stores/tankopediaSort';
import { classIcons } from '../../ClassIcon';
import * as styles from './TankCard.css';
@@ -23,8 +22,9 @@ interface TankCardProps {
onSelect?: (tank: TankDefinition) => void;
}
const modelDefinitions = await fetchModelDefinitions();
export function TankCard({ tank, onSelect }: TankCardProps) {
const awaitedModelDefinitions = useAwait(modelDefinitions);
const Icon = classIcons[tank.class];
const tankopediaSort = useStore($tankopediaSort);
const discriminator = useMemo(() => {
@@ -32,25 +32,26 @@ export function TankCard({ tank, onSelect }: TankCardProps) {
const turret = tank.turrets.at(-1)!;
const gun = turret.guns.at(-1)!;
const shell0 = gun.shells[0];
const shell1 = gun.shells[1];
const shell0 = gun.gunType!.value.base.shells[0];
const shell1 = gun.gunType!.value.base.shells[1];
const tracks = tank.tracks.at(-1)!;
const engine = tank.engines.at(-1)!;
const tankModelDefinition = awaitedModelDefinitions[tank.id];
const tankModelDefinition = modelDefinitions.models[tank.id];
const turretModelDefinition = tankModelDefinition.turrets[turret.id];
const gunModelDefinition = turretModelDefinition.guns[gun.id];
const gunModelDefinition =
turretModelDefinition.guns[gun.gunType!.value.base.id];
switch (tankopediaSort.by) {
case 'fire.aimTime':
return gun.aimTime.toFixed(2);
return gun.gunType!.value.base.aimTime.toFixed(2);
case 'fire.caliber':
return shell0.caliber.toFixed(0);
case 'fire.damage':
return shell0.damage.armor.toFixed(0);
return shell0.armorDamage.toFixed(0);
case 'fire.dispersionMoving':
return tracks.dispersion.move.toFixed(3);
return tracks.dispersionMove.toFixed(3);
case 'fire.dispersionStill':
return gun.dispersion.base.toFixed(3);
return gun.gunType!.value.base.dispersionBase.toFixed(3);
case 'fire.dpm':
return resolveDpm(gun, shell0).toFixed(0);
case 'fire.dpmPremium':
@@ -58,27 +59,25 @@ export function TankCard({ tank, onSelect }: TankCardProps) {
case 'fire.reload':
return resolveReload(gun).toFixed(2);
case 'fire.standardPenetration':
return resolveNearPenetration(shell0.penetration).toFixed(0);
return shell0.penetration.near.toFixed(0);
case 'fire.premiumPenetration':
return shell1
? resolveNearPenetration(shell1.penetration).toFixed(0)
: '--';
return shell1 ? shell1.penetration.near.toFixed(0) : '--';
case 'fire.shellVelocity':
return shell0.speed.toFixed(0);
return shell0.velocity.toFixed(0);
case 'fire.gunDepression':
return (
gunModelDefinition.pitch.max +
(tankModelDefinition.turretRotation?.pitch ?? 0)
(tankModelDefinition.initialTurretRotation?.pitch ?? 0)
).toFixed(1);
case 'fire.gunElevation':
return (
-gunModelDefinition.pitch.min -
(tankModelDefinition.turretRotation?.pitch ?? 0)
(tankModelDefinition.initialTurretRotation?.pitch ?? 0)
).toFixed(1);
case 'maneuverability.forwardsSpeed':
return tank.speed.forwards.toFixed(0);
return tank.speedForwards.toFixed(0);
case 'maneuverability.backwardsSpeed':
return tank.speed.backwards.toFixed(0);
return tank.speedBackwards.toFixed(0);
case 'maneuverability.power':
return engine.power.toFixed(0);
case 'maneuverability.powerToWeight':
@@ -88,7 +87,7 @@ export function TankCard({ tank, onSelect }: TankCardProps) {
engine.weight +
tracks.weight +
turret.weight +
gun.weight)
gun.gunType!.value.base.weight)
).toFixed(1);
case 'maneuverability.weight':
return (
@@ -96,7 +95,7 @@ export function TankCard({ tank, onSelect }: TankCardProps) {
engine.weight +
tracks.weight +
turret.weight +
gun.weight) /
gun.gunType!.value.base.weight) /
1000
).toFixed(1);
case 'maneuverability.traverseSpeed':
@@ -106,11 +105,15 @@ export function TankCard({ tank, onSelect }: TankCardProps) {
case 'survivability.viewRange':
return turret.viewRange.toFixed(0);
case 'survivability.camouflageStill':
return (tank.camouflage.still * 100).toFixed(0);
return (tank.camouflageStill * 100).toFixed(0);
case 'survivability.camouflageMoving':
return (tank.camouflage.moving * 100).toFixed(0);
return (tank.camouflageMoving * 100).toFixed(0);
case 'survivability.camouflageShooting':
return (tank.camouflage.still * gun.camouflageLoss * 100).toFixed(0);
return (
tank.camouflageStill *
gun.gunType!.value.base.camouflageLoss *
100
).toFixed(0);
case 'survivability.volume': {
const dimensions = normalizeBoundingBox(
unionBoundingBox(
@@ -139,13 +142,13 @@ export function TankCard({ tank, onSelect }: TankCardProps) {
tabIndex={onSelect ? 0 : undefined}
size="1"
color={
tank.treeType === 'collector'
tank.type === TankType.COLLECTOR
? 'blue'
: tank.treeType === 'premium'
: tank.type === TankType.PREMIUM
? 'amber'
: 'gray'
}
highContrast={tank.treeType === 'researchable'}
highContrast={tank.type === TankType.RESEARCHABLE}
onClick={onSelect ? () => onSelect(tank) : undefined}
className={styles.card}
style={assignInlineVars({
@@ -182,11 +182,11 @@ export const TankSearch = memo<TankSearchProps>(
(a, b) =>
resolveDpm(
a.turrets.at(-1)!.guns.at(-1)!,
a.turrets.at(-1)!.guns.at(-1)!.gunType!.value.gun.shells[0],
a.turrets.at(-1)!.guns.at(-1)!.gunType!.value.base.shells[0],
) -
resolveDpm(
b.turrets.at(-1)!.guns.at(-1)!,
b.turrets.at(-1)!.guns.at(-1)!.gunType!.value.gun.shells[0],
b.turrets.at(-1)!.guns.at(-1)!.gunType!.value.base.shells[0],
),
);
break;
@@ -194,17 +194,17 @@ export const TankSearch = memo<TankSearchProps>(
case 'fire.dpmPremium':
sorted = filtered.sort(
(a, b) =>
(a.turrets.at(-1)!.guns.at(-1)!.gunType!.value.gun.shells[1]
(a.turrets.at(-1)!.guns.at(-1)!.gunType!.value.base.shells[1]
? resolveDpm(
a.turrets.at(-1)!.guns.at(-1)!,
a.turrets.at(-1)!.guns.at(-1)!.gunType!.value.gun
a.turrets.at(-1)!.guns.at(-1)!.gunType!.value.base
.shells[1],
)
: 0) -
(b.turrets.at(-1)!.guns.at(-1)!.gunType!.value.gun.shells[1]
(b.turrets.at(-1)!.guns.at(-1)!.gunType!.value.base.shells[1]
? resolveDpm(
b.turrets.at(-1)!.guns.at(-1)!,
b.turrets.at(-1)!.guns.at(-1)!.gunType!.value.gun
b.turrets.at(-1)!.guns.at(-1)!.gunType!.value.base
.shells[1],
)
: 0),
@@ -222,9 +222,9 @@ export const TankSearch = memo<TankSearchProps>(
case 'fire.caliber':
sorted = filtered.sort(
(a, b) =>
a.turrets.at(-1)!.guns.at(-1)!.gunType!.value.gun.shells[0]
a.turrets.at(-1)!.guns.at(-1)!.gunType!.value.base.shells[0]
.caliber -
b.turrets.at(-1)!.guns.at(-1)!.gunType!.value.gun.shells[0]
b.turrets.at(-1)!.guns.at(-1)!.gunType!.value.base.shells[0]
.caliber,
);
break;
@@ -232,9 +232,9 @@ export const TankSearch = memo<TankSearchProps>(
case 'fire.standardPenetration':
sorted = filtered.sort(
(a, b) =>
a.turrets.at(-1)!.guns.at(-1)!.gunType!.value.gun.shells[0]
a.turrets.at(-1)!.guns.at(-1)!.gunType!.value.base.shells[0]
.penetration.near -
b.turrets.at(-1)!.guns.at(-1)!.gunType!.value.gun.shells[0]
b.turrets.at(-1)!.guns.at(-1)!.gunType!.value.base.shells[0]
.penetration.near,
);
break;
@@ -242,9 +242,9 @@ export const TankSearch = memo<TankSearchProps>(
case 'fire.premiumPenetration':
sorted = filtered.sort(
(a, b) =>
(a.turrets.at(-1)!.guns.at(-1)!.gunType!.value.gun.shells[1]
(a.turrets.at(-1)!.guns.at(-1)!.gunType!.value.base.shells[1]
?.penetration.near ?? 0) -
(b.turrets.at(-1)!.guns.at(-1)!.gunType!.value.gun.shells[1]
(b.turrets.at(-1)!.guns.at(-1)!.gunType!.value.base.shells[1]
?.penetration.near ?? 0),
);
break;
@@ -252,9 +252,9 @@ export const TankSearch = memo<TankSearchProps>(
case 'fire.damage':
sorted = filtered.sort(
(a, b) =>
a.turrets.at(-1)!.guns.at(-1)!.gunType!.value.gun.shells[0]
a.turrets.at(-1)!.guns.at(-1)!.gunType!.value.base.shells[0]
.armorDamage -
b.turrets.at(-1)!.guns.at(-1)!.gunType!.value.gun.shells[0]
b.turrets.at(-1)!.guns.at(-1)!.gunType!.value.base.shells[0]
.armorDamage,
);
break;
@@ -262,9 +262,9 @@ export const TankSearch = memo<TankSearchProps>(
case 'fire.shellVelocity':
sorted = filtered.sort(
(a, b) =>
a.turrets.at(-1)!.guns.at(-1)!.gunType!.value.gun.shells[0]
a.turrets.at(-1)!.guns.at(-1)!.gunType!.value.base.shells[0]
.velocity -
b.turrets.at(-1)!.guns.at(-1)!.gunType!.value.gun.shells[0]
b.turrets.at(-1)!.guns.at(-1)!.gunType!.value.base.shells[0]
.velocity,
);
break;
@@ -272,17 +272,17 @@ export const TankSearch = memo<TankSearchProps>(
case 'fire.aimTime':
sorted = filtered.sort(
(a, b) =>
a.turrets.at(-1)!.guns.at(-1)!.gunType!.value.gun.aimTime -
b.turrets.at(-1)!.guns.at(-1)!.gunType!.value.gun.aimTime,
a.turrets.at(-1)!.guns.at(-1)!.gunType!.value.base.aimTime -
b.turrets.at(-1)!.guns.at(-1)!.gunType!.value.base.aimTime,
);
break;
case 'fire.dispersionStill':
sorted = filtered.sort(
(a, b) =>
a.turrets.at(-1)!.guns.at(-1)!.gunType!.value.gun
a.turrets.at(-1)!.guns.at(-1)!.gunType!.value.base
.dispersionBase -
b.turrets.at(-1)!.guns.at(-1)!.gunType!.value.gun
b.turrets.at(-1)!.guns.at(-1)!.gunType!.value.base
.dispersionBase,
);
break;
@@ -299,12 +299,12 @@ 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)!.gunType!.value.gun.id]
.guns[a.turrets.at(-1)!.guns.at(-1)!.gunType!.value.base.id]
.pitch.max +
(modelDefinitions.models[a.id].initialTurretRotation?.pitch ??
0) -
modelDefinitions.models[b.id].turrets[b.turrets.at(-1)!.id]
.guns[b.turrets.at(-1)!.guns.at(-1)!.gunType!.value.gun.id]
.guns[b.turrets.at(-1)!.guns.at(-1)!.gunType!.value.base.id]
.pitch.max -
(modelDefinitions.models[b.id].initialTurretRotation?.pitch ??
0),
@@ -315,12 +315,12 @@ 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)!.gunType!.value.gun.id]
.guns[b.turrets.at(-1)!.guns.at(-1)!.gunType!.value.base.id]
.pitch.min +
(modelDefinitions.models[b.id].initialTurretRotation?.pitch ??
0) -
modelDefinitions.models[a.id].turrets[a.turrets.at(-1)!.id]
.guns[a.turrets.at(-1)!.guns.at(-1)!.gunType!.value.gun.id]
.guns[a.turrets.at(-1)!.guns.at(-1)!.gunType!.value.base.id]
.pitch.min -
(modelDefinitions.models[a.id].initialTurretRotation?.pitch ??
0),
@@ -351,13 +351,13 @@ export const TankSearch = memo<TankSearchProps>(
a.engines.at(-1)!.weight +
a.tracks.at(-1)!.weight +
a.turrets.at(-1)!.weight +
a.turrets.at(-1)!.guns.at(-1)!.gunType!.value.gun.weight) -
a.turrets.at(-1)!.guns.at(-1)!.gunType!.value.base.weight) -
b.engines.at(-1)!.power /
(b.weight +
b.engines.at(-1)!.weight +
b.tracks.at(-1)!.weight +
b.turrets.at(-1)!.weight +
b.turrets.at(-1)!.guns.at(-1)!.gunType!.value.gun.weight),
b.turrets.at(-1)!.guns.at(-1)!.gunType!.value.base.weight),
);
break;
@@ -368,12 +368,12 @@ export const TankSearch = memo<TankSearchProps>(
a.engines.at(-1)!.weight +
a.tracks.at(-1)!.weight +
a.turrets.at(-1)!.weight +
a.turrets.at(-1)!.guns.at(-1)!.gunType!.value.gun.weight -
a.turrets.at(-1)!.guns.at(-1)!.gunType!.value.base.weight -
(b.weight +
b.engines.at(-1)!.weight +
b.tracks.at(-1)!.weight +
b.turrets.at(-1)!.weight +
b.turrets.at(-1)!.guns.at(-1)!.gunType!.value.gun.weight),
b.turrets.at(-1)!.guns.at(-1)!.gunType!.value.base.weight),
);
break;
@@ -2,10 +2,11 @@ import {
asset,
coefficient,
CREW_MEMBER_NAMES,
equipmentDefinitions,
CrewType,
fetchEquipmentDefinitions,
fetchProvisionDefinitions,
GUN_TYPE_NAMES,
isExplosive,
provisionDefinitions,
resolvePenetrationCoefficient,
} from '@blitzkit/core';
import { AccessibilityIcon, InfoCircledIcon } from '@radix-ui/react-icons';
@@ -34,8 +35,8 @@ import { Ad, AdType } from '../../../../Ad';
import { Info } from './components/Info';
import { InfoWithDelta } from './components/InfoWithDelta';
const awaitedEquipmentDefinitions = await equipmentDefinitions;
const awaitedProvisionDefinitions = await provisionDefinitions;
const equipmentDefinitions = await fetchEquipmentDefinitions();
const provisionDefinitions = await fetchProvisionDefinitions();
export function Characteristics() {
const mutateDuel = Duel.useMutation();
@@ -53,8 +54,8 @@ export function Characteristics() {
const provisionCrewBonus =
provisions.reduce(
(total, provision) =>
awaitedProvisionDefinitions[provision].crew
? total + awaitedProvisionDefinitions[provision].crew! / 100
provisionDefinitions.provisions[provision].crew
? total + provisionDefinitions.provisions[provision].crew! / 100
: total,
0,
) + (hasImprovedVentilation ? 0.08 : 0);
@@ -73,7 +74,8 @@ export function Characteristics() {
const stockGun = stockTurret.guns[0];
const tankModelDefinition = useTankModelDefinition();
const turretModelDefinition = tankModelDefinition.turrets[turret.id];
const gunModelDefinition = turretModelDefinition.guns[gun.id];
const gunModelDefinition =
turretModelDefinition.guns[gun.gunType!.value.base.id];
const stats = tankCharacteristics(
{
tank,
@@ -98,8 +100,8 @@ export function Characteristics() {
},
{
tankModelDefinition,
equipmentDefinitions: awaitedEquipmentDefinitions,
provisionDefinitions: awaitedProvisionDefinitions,
equipmentDefinitions: equipmentDefinitions,
provisionDefinitions: provisionDefinitions,
},
);
@@ -146,7 +148,7 @@ export function Characteristics() {
<Heading size="5">Firepower</Heading>
<Flex>
{gun.shells.map((thisShell, shellIndex) => (
{gun.gunType!.value.base.shells.map((thisShell, shellIndex) => (
<Tooltip content={thisShell.name} key={thisShell.id}>
<IconButton
color={thisShell.id === shell.id ? undefined : 'gray'}
@@ -155,9 +157,13 @@ export function Characteristics() {
borderTopLeftRadius: shellIndex === 0 ? undefined : 0,
borderBottomLeftRadius: shellIndex === 0 ? undefined : 0,
borderTopRightRadius:
shellIndex === gun.shells.length - 1 ? undefined : 0,
shellIndex === gun.gunType!.value.base.shells.length - 1
? undefined
: 0,
borderBottomRightRadius:
shellIndex === gun.shells.length - 1 ? undefined : 0,
shellIndex === gun.gunType!.value.base.shells.length - 1
? undefined
: 0,
marginLeft: shellIndex === 0 ? 0 : -1,
}}
onClick={() => {
@@ -177,11 +183,11 @@ export function Characteristics() {
))}
</Flex>
</Flex>
<Info name="Gun type">{GUN_TYPE_NAMES[gun.type]}</Info>
<Info name="Gun type">{GUN_TYPE_NAMES[gun.gunType!.$case]}</Info>
<InfoWithDelta name="DPM" decimals={0} unit="hp / min">
{stats.dpm}
</InfoWithDelta>
{gun.type === 'autoReloader' && (
{gun.gunType!.$case === 'autoReloader' && (
<InfoWithDelta
decimals={0}
indent
@@ -191,10 +197,10 @@ export function Characteristics() {
{stats.dpmEffective!}
</InfoWithDelta>
)}
{gun.type !== 'regular' && (
{gun.gunType!.$case !== 'regular' && (
<InfoWithDelta name="Shells">{stats.shells}</InfoWithDelta>
)}
{gun.type === 'autoReloader' ? (
{gun.gunType!.$case === 'autoReloader' ? (
<>
<Info indent name="Most optimal shell index">
{stats.mostOptimalShellIndex! + 1}
@@ -222,7 +228,8 @@ export function Characteristics() {
{stats.shellReload!}
</InfoWithDelta>
)}
{(gun.type === 'autoLoader' || gun.type === 'autoReloader') && (
{(gun.gunType!.$case === 'autoLoader' ||
gun.gunType!.$case === 'autoReloader') && (
<InfoWithDelta
indent
decimals={2}
@@ -244,8 +251,8 @@ export function Characteristics() {
name={`At ${penetrationDistance}m`}
>
{lerp(
shell.penetration[0],
shell.penetration[1],
shell.penetration.near,
shell.penetration.far,
(penetrationDistance *
penetrationLossOverDistanceCoefficient) /
500,
@@ -293,7 +300,7 @@ export function Characteristics() {
<InfoWithDelta name="Damage" unit="hp" decimals={0}>
{stats.damage}
</InfoWithDelta>
{gun.type !== 'regular' && (
{gun.gunType!.$case !== 'regular' && (
<InfoWithDelta name="Clipping potential" indent>
{stats.clipDamage!}
</InfoWithDelta>
@@ -471,7 +478,7 @@ export function Characteristics() {
unit="%"
decimals={0}
>
{(member.type === 'commander'
{(member.type === CrewType.COMMANDER
? commanderMastery
: commanderMastery * 1.1) * 100}
</InfoWithDelta>
@@ -1,6 +1,5 @@
import {} from '@blitzkit/core';
import { fetchConsumableDefinitions } from '@blitzkit/core';
import { checkConsumableProvisionInclusivity } from '@blitzkit/core/src/blitzkit/checkConsumableProvisionInclusivity';
import { consumableDefinitions } from '@blitzkit/core/src/blitzkit/consumableDefinitions';
import { InfoCircledIcon } from '@radix-ui/react-icons';
import {
Button,
@@ -15,13 +14,13 @@ import { Duel } from '../../../../stores/duel';
import { ConsumablesManager } from '../../../ConsumablesManager';
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
const awaitedConsumableDefinitions = await consumableDefinitions;
const consumableDefinitions = await fetchConsumableDefinitions();
export function Consumables() {
const mutateDuel = Duel.useMutation();
const protagonist = Duel.use((state) => state.protagonist);
const consumables = Duel.use((state) => state.protagonist.consumables);
const consumablesList = Object.values(awaitedConsumableDefinitions).filter(
const consumablesList = Object.values(consumableDefinitions).filter(
(consumable) =>
checkConsumableProvisionInclusivity(
consumable,
@@ -74,7 +73,7 @@ export function Consumables() {
consumables={consumablesList}
selected={consumables}
cooldownBooster={cooldownBooster}
disabled={protagonist.tank.consumables === consumables.length}
disabled={protagonist.tank.maxConsumables === consumables.length}
hasConsumableDeliverySystem={hasConsumableDeliverySystem}
hasHighEndConsumables={hasHighEndConsumables}
timers
@@ -1,11 +1,14 @@
import {
asset,
EngineDefinition,
fetchTankDefinitions,
formatCompact,
tankDefinitions,
GunDefinition,
ModuleType,
tankIcon,
TIER_ROMAN_NUMERALS,
type ModuleDefinition,
type ModuleType,
TrackDefinition,
TurretDefinition,
type TankDefinition,
type Tier,
type Unlock,
@@ -19,8 +22,25 @@ import {
Link,
Text,
} from '@radix-ui/themes';
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
import { Duel } from '../../../../stores/duel';
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
type ModuleDefinition =
| TurretDefinition
| GunDefinition
| TrackDefinition
| EngineDefinition;
function extractModule(raw: ModuleDefinition | TankDefinition) {
if ('gunType' in raw) {
return raw.gunType!.value.base;
} else
return raw as Exclude<ModuleDefinition | TankDefinition, GunDefinition>;
}
interface ModuleWithUnlocks {
unlocks: Unlock[];
}
function ModuleButton({
tier,
@@ -35,7 +55,7 @@ function ModuleButton({
top: boolean;
onClick: () => void;
}) {
const isTank = unlock.type === 'vehicle';
const isTank = unlock.type === ModuleType.VEHICLE;
const button = (
<IconButton
size="4"
@@ -89,7 +109,7 @@ function ModuleButton({
)}
<img
alt={unlock.type}
alt={ModuleType[unlock.type]}
src={
isTank
? tankIcon(unlock.id)
@@ -111,7 +131,7 @@ function ModuleButton({
);
}
const awaitedTankDefinitions = await tankDefinitions;
const tankDefinitions = await fetchTankDefinitions();
export function Modules() {
const mutateDuel = Duel.useMutation();
@@ -136,44 +156,48 @@ export function Modules() {
function setByUnlock(unlock: Unlock) {
mutateDuel((draft) => {
if (unlock.type === 'turret') {
if (unlock.type === ModuleType.TURRET) {
draft.protagonist.turret = draft.protagonist.tank.turrets.find(
(turret) => turret.id === unlock.id,
)!;
if (
!draft.protagonist.turret.guns.some(
(gun) => gun.id === draft.protagonist.gun.id,
(gun) =>
gun.gunType!.value.base.id ===
draft.protagonist.gun.gunType!.value.base.id,
)
) {
draft.protagonist.gun = draft.protagonist.turret.guns.at(-1)!;
draft.protagonist.shell = draft.protagonist.gun.shells[0];
draft.protagonist.shell =
draft.protagonist.gun.gunType!.value.base.shells[0];
}
} else if (unlock.type === 'gun') {
} else if (unlock.type === ModuleType.GUN) {
const gunInTurret = draft.protagonist.turret.guns.find(
(gun) => gun.id === unlock.id,
(gun) => gun.gunType!.value.base.id === unlock.id,
);
if (gunInTurret) {
draft.protagonist.gun = gunInTurret;
draft.protagonist.shell = gunInTurret.shells[0];
draft.protagonist.shell = gunInTurret.gunType!.value.base.shells[0];
} 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.gunType!.value.base.id === unlock.id),
)!;
const gunInSuitableTurret = suitableTurret.guns.find(
(gun) => gun.id === unlock.id,
(gun) => gun.gunType!.value.base.id === unlock.id,
)!;
draft.protagonist.turret = suitableTurret;
draft.protagonist.gun = gunInSuitableTurret;
draft.protagonist.shell = gunInSuitableTurret.shells[0];
draft.protagonist.shell =
gunInSuitableTurret.gunType!.value.base.shells[0];
}
} else if (unlock.type === 'engine') {
} else if (unlock.type === ModuleType.ENGINE) {
draft.protagonist.engine = draft.protagonist.tank.engines.find(
(engine) => engine.id === unlock.id,
)!;
} else if (unlock.type === 'chassis') {
} else if (unlock.type === ModuleType.TRACKS) {
draft.protagonist.track = draft.protagonist.tank.tracks.find(
(track) => track.id === unlock.id,
)!;
@@ -190,20 +214,22 @@ export function Modules() {
const central = !first && !last;
let module: ModuleDefinition | TankDefinition | undefined = undefined;
if (unlock.type === 'chassis') {
if (unlock.type === ModuleType.TRACKS) {
module = tank.tracks.find((track) => track.id === unlock.id)!;
} else if (unlock.type === 'engine') {
} else if (unlock.type === ModuleType.ENGINE) {
module = tank.engines.find((engine) => engine.id === unlock.id)!;
} else if (unlock.type === 'turret') {
} else if (unlock.type === ModuleType.TURRET) {
module = tank.turrets.find((turret) => turret.id === unlock.id)!;
} else if (unlock.type === 'gun') {
} else if (unlock.type === ModuleType.GUN) {
module = tank.turrets
.find((turret) =>
turret.guns.some((gun) => gun.id === unlock.id),
turret.guns.some(
(gun) => gun.gunType!.value.base.id === unlock.id,
),
)!
.guns.find((gun) => gun.id === unlock.id)!;
} else if (unlock.type === 'vehicle') {
module = awaitedTankDefinitions[unlock.id];
.guns.find((gun) => gun.gunType!.value.base.id === unlock.id)!;
} else if (unlock.type === ModuleType.VEHICLE) {
module = tankDefinitions.tanks[unlock.id];
}
if (module === undefined) return null;
@@ -259,34 +285,36 @@ export function Modules() {
<ModuleButton
unlock={unlock}
tier={module.tier}
tier={extractModule(module).tier}
selected={
(unlock.type === 'turret'
(unlock.type === ModuleType.TURRET
? turret.id
: unlock.type === 'gun'
? gun.id
: unlock.type === 'engine'
: unlock.type === ModuleType.GUN
? gun.gunType!.value.base.id
: unlock.type === ModuleType.ENGINE
? engine.id
: unlock.type === 'chassis'
: unlock.type === ModuleType.TRACKS
? track.id
: -1) === unlock.id
}
top={
unlock.type === 'turret'
? module.id == topTurret.id
: unlock.type === 'gun'
? module.id === topGun.id
: unlock.type === 'engine'
? module.id === topEngine.id
: unlock.type === 'chassis'
? module.id === topTrack.id
unlock.type === ModuleType.TURRET
? extractModule(module).id == topTurret.id
: unlock.type === ModuleType.GUN
? extractModule(module).id ===
topGun.gunType!.value.base.id
: unlock.type === ModuleType.ENGINE
? extractModule(module).id === topEngine.id
: unlock.type === ModuleType.TRACKS
? extractModule(module).id === topTrack.id
: false
}
onClick={() => setByUnlock(unlock)}
/>
{(module as ModuleDefinition).unlocks &&
(module as ModuleDefinition).unlocks!.length > 1 && (
{(extractModule(module) as ModuleWithUnlocks).unlocks &&
(extractModule(module) as ModuleWithUnlocks).unlocks!.length >
1 && (
<div
style={{
backgroundColor: 'currentcolor',
@@ -297,8 +325,11 @@ export function Modules() {
/>
)}
{(module as ModuleDefinition).unlocks &&
tree(type, (module as ModuleDefinition).unlocks!)}
{(extractModule(module) as ModuleWithUnlocks).unlocks &&
tree(
type,
(extractModule(module) as ModuleWithUnlocks).unlocks!,
)}
</Flex>
);
})}
@@ -320,7 +351,8 @@ export function Modules() {
mutateDuel((draft) => {
draft.protagonist.turret = draft.protagonist.tank.turrets[0];
draft.protagonist.gun = draft.protagonist.turret.guns[0];
draft.protagonist.shell = draft.protagonist.gun.shells[0];
draft.protagonist.shell =
draft.protagonist.gun.gunType!.value.base.shells[0];
draft.protagonist.engine = draft.protagonist.tank.engines[0];
draft.protagonist.track = draft.protagonist.tank.tracks[0];
});
@@ -335,7 +367,8 @@ export function Modules() {
draft.protagonist.turret =
draft.protagonist.tank.turrets.at(-1)!;
draft.protagonist.gun = draft.protagonist.turret.guns.at(-1)!;
draft.protagonist.shell = draft.protagonist.gun.shells[0];
draft.protagonist.shell =
draft.protagonist.gun.gunType!.value.base.shells[0];
draft.protagonist.engine =
draft.protagonist.tank.engines.at(-1)!;
draft.protagonist.track =
@@ -350,32 +383,32 @@ export function Modules() {
</Flex>
<Flex gap="2" wrap="wrap" gapY="6">
{tree('engine', [
{tree(ModuleType.ENGINE, [
{
cost: { type: 'xp', value: 0 },
id: engine0.id,
type: 'engine',
type: ModuleType.ENGINE,
},
])}
{tree('chassis', [
{tree(ModuleType.TRACKS, [
{
cost: { type: 'xp', value: 0 },
id: track0.id,
type: 'chassis',
type: ModuleType.TRACKS,
},
])}
{tree('turret', [
{tree(ModuleType.TURRET, [
{
cost: { type: 'xp', value: 0 },
id: turret0.id,
type: 'turret',
type: ModuleType.TURRET,
},
])}
{tree('gun', [
{tree(ModuleType.GUN, [
{
cost: { type: 'xp', value: 0 },
id: gun0.id,
type: 'gun',
id: gun0.gunType!.value.base.id,
type: ModuleType.GUN,
},
])}
</Flex>
@@ -1,20 +1,16 @@
import { availableProvisions, provisionDefinitions } from '@blitzkit/core';
import { availableProvisions, fetchProvisionDefinitions } from '@blitzkit/core';
import { Button, Flex, Heading } from '@radix-ui/themes';
import { Duel } from '../../../../stores/duel';
import { ProvisionsManager } from '../../../ProvisionsManager';
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
const awaitedProvisionDefinitions = await provisionDefinitions;
const provisionDefinitions = await fetchProvisionDefinitions();
export function Provisions() {
const mutateDuel = Duel.useMutation();
const { tank, gun } = Duel.use((state) => state.protagonist);
const provisions = Duel.use((state) => state.protagonist.provisions);
const provisionsList = availableProvisions(
tank,
gun,
awaitedProvisionDefinitions,
);
const provisionsList = availableProvisions(tank, gun, provisionDefinitions);
return (
<ConfigurationChildWrapper>
@@ -36,7 +32,7 @@ export function Provisions() {
<ProvisionsManager
provisions={provisionsList.map((provision) => provision.id)}
selected={provisions}
disabled={tank.provisions === provisions.length}
disabled={tank.maxProvisions === provisions.length}
onChange={(provisions) => {
mutateDuel((draft) => {
draft.protagonist.provisions = provisions;
@@ -1,4 +1,4 @@
import { createDefaultSkills, skillDefinitions } from '@blitzkit/core';
import { createDefaultSkills, fetchSkillDefinitions } from '@blitzkit/core';
import { InfoCircledIcon } from '@radix-ui/react-icons';
import {
Button,
@@ -13,7 +13,7 @@ import { TankopediaEphemeral } from '../../../../stores/tankopediaEphemeral';
import { CrewSkillManager } from '../../../CrewSkillManager';
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
const awaitedSkillDefinitions = await skillDefinitions;
const skillDefinitions = await fetchSkillDefinitions();
export function Skills() {
const skillLevels = TankopediaEphemeral.use((state) => state.skills);
@@ -21,7 +21,7 @@ export function Skills() {
if (Object.keys(skillLevels).length === 0) {
mutateTankopediaTemporary((draft) => {
draft.skills = createDefaultSkills(awaitedSkillDefinitions);
draft.skills = createDefaultSkills(skillDefinitions);
});
return null;
@@ -1,15 +1,15 @@
import {
asset,
consumableDefinitions,
gameDefinitions,
provisionDefinitions,
fetchConsumableDefinitions,
fetchGameDefinitions,
fetchProvisionDefinitions,
} from '@blitzkit/core';
import { Box, Flex, Heading, Text } from '@radix-ui/themes';
import { Duel } from '../../stores/duel';
const awaitedGameDefinitions = await gameDefinitions;
const awaitedConsumableDefinitions = await consumableDefinitions;
const awaitedProvisionDefinitions = await provisionDefinitions;
const gameDefinitions = await fetchGameDefinitions();
const consumableDefinitions = await fetchConsumableDefinitions();
const provisionDefinitions = await fetchProvisionDefinitions();
export function GameModeSection() {
const tank = Duel.use((state) => state.protagonist.tank);
@@ -24,7 +24,7 @@ export function GameModeSection() {
<Flex justify="center" gap="4" wrap="wrap" px="4">
{roles.map(([key, value]) => {
const id = Number(key);
const gameMode = awaitedGameDefinitions.gameModes[id];
const gameMode = gameDefinitions.gameModes[id];
return (
<Flex
@@ -70,8 +70,8 @@ export function GameModeSection() {
</Text>
<Flex direction="column">
{awaitedGameDefinitions.roles[value].provisions.map((id) => {
const provisions = awaitedProvisionDefinitions[id];
{gameDefinitions.roles[value].provisions.map((id) => {
const provisions = provisionDefinitions.provisions[id];
return (
<Text color="gray" wrap="nowrap" key={id}>
@@ -90,8 +90,8 @@ export function GameModeSection() {
</Text>
);
})}
{awaitedGameDefinitions.roles[value].consumables.map((id) => {
const consumable = awaitedConsumableDefinitions[id];
{gameDefinitions.roles[value].consumables.map((id) => {
const consumable = consumableDefinitions.consumables[id];
return (
<Text color="gray" wrap="nowrap" key={id}>
@@ -28,8 +28,8 @@ export function DynamicArmorSwitcher() {
draft.protagonist.consumables =
draft.protagonist.consumables.filter((id) => id !== 73);
} else {
if (draft.protagonist.consumables.length === tank.consumables) {
draft.protagonist.consumables[tank.consumables - 1] = 73;
if (draft.protagonist.consumables.length === tank.maxConsumables) {
draft.protagonist.consumables[tank.maxConsumables - 1] = 73;
} else {
draft.protagonist.consumables.push(73);
}
@@ -20,8 +20,9 @@ export function QuickInputs() {
const tankModelDefinition = useTankModelDefinition();
const turretModelDefinition =
tankModelDefinition.turrets[protagonist.turret.id];
const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id];
const initialGunPitch = tankModelDefinition.turretRotation?.pitch ?? 0;
const gunModelDefinition =
turretModelDefinition.guns[protagonist.gun.gunType!.value.base.id];
const initialGunPitch = tankModelDefinition.initialTurretRotation?.pitch ?? 0;
const hasImprovedVerticalStabilizer = useEquipment(122);
useEffect(() => {
@@ -43,7 +44,8 @@ export function QuickInputs() {
if (!pitchInput.current || !yawInput.current) return;
pitchInput.current.value = (
-radToDeg(pitch) + (tankModelDefinition.turretRotation?.pitch ?? 0)
-radToDeg(pitch) +
(tankModelDefinition.initialTurretRotation?.pitch ?? 0)
).toFixed(1);
if (yaw === undefined) return;
@@ -1,7 +1,6 @@
import {
asset,
imgur,
resolveNearPenetration,
resolvePenetrationCoefficient,
TIER_ROMAN_NUMERALS,
uniqueGuns,
@@ -105,7 +104,7 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
resolvePenetrationCoefficient(
hasCalibratedShells,
antagonistShell.type,
) * resolveNearPenetration(antagonistShell.penetration)
) * antagonistShell.penetration.near
).toFixed(0)}
mm
</Text>
@@ -130,7 +129,11 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
mr="2"
mb="1"
>
{TIER_ROMAN_NUMERALS[antagonistGun.tier]}
{
TIER_ROMAN_NUMERALS[
antagonistGun.gunType!.value.base.tier
]
}
</Text>
<img
@@ -149,19 +152,25 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
<Flex gap="2">
{[...antagonistUniqueGuns]
.reverse()
.map(({ gun, turret }, index) => (
.map(({ gun, turret }) => (
<ModuleButton
module="gun"
key={gun.id}
key={gun.gunType!.value.base.id}
onClick={() =>
mutateDuel((draft) => {
draft.antagonist.turret = turret;
draft.antagonist.gun = gun;
draft.antagonist.shell = gun.shells[0];
draft.antagonist.shell =
gun.gunType!.value.base.shells[0];
})
}
selected={gun.id === antagonistGun.id}
discriminator={TIER_ROMAN_NUMERALS[gun.tier]}
selected={
gun.gunType!.value.base.id ===
antagonistGun.gunType!.value.base.id
}
discriminator={
TIER_ROMAN_NUMERALS[gun.gunType!.value.base.tier]
}
/>
))}
</Flex>
@@ -175,36 +184,40 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
}}
overflow="hidden"
>
{antagonistGun.shells.map((thisShell, shellIndex) => (
<IconButton
color={thisShell.id === antagonistShell.id ? undefined : 'gray'}
variant="soft"
key={thisShell.id}
size={{ initial: '2', sm: '3' }}
radius="none"
style={{
marginTop: shellIndex === 0 ? 0 : -1,
}}
onClick={() => {
invalidate();
mutateDuel((draft) => {
draft.antagonist.shell = thisShell;
});
mutateTankopediaEphemeral((draft) => {
draft.shot = undefined;
});
}}
>
<img
alt={thisShell.name}
src={asset(`icons/shells/${thisShell.icon}.webp`)}
{antagonistGun.gunType!.value.base.shells.map(
(thisShell, shellIndex) => (
<IconButton
color={
thisShell.id === antagonistShell.id ? undefined : 'gray'
}
variant="soft"
key={thisShell.id}
size={{ initial: '2', sm: '3' }}
radius="none"
style={{
width: '50%',
height: '50%',
marginTop: shellIndex === 0 ? 0 : -1,
}}
/>
</IconButton>
))}
onClick={() => {
invalidate();
mutateDuel((draft) => {
draft.antagonist.shell = thisShell;
});
mutateTankopediaEphemeral((draft) => {
draft.shot = undefined;
});
}}
>
<img
alt={thisShell.name}
src={asset(`icons/shells/${thisShell.icon}.webp`)}
style={{
width: '50%',
height: '50%',
}}
/>
</IconButton>
),
)}
</Flex>
<Flex
@@ -318,7 +331,8 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
draft.antagonist.turret = tank.turrets.at(-1)!;
draft.antagonist.gun =
draft.antagonist.turret.guns.at(-1)!;
draft.antagonist.shell = draft.antagonist.gun.shells[0];
draft.antagonist.shell =
draft.antagonist.gun.gunType!.value.base.shells[0];
});
setAntagonistSelectorOpen(false);
}}
@@ -1,4 +1,4 @@
import { modelDefinitions } from '@blitzkit/core';
import { fetchModelDefinitions } from '@blitzkit/core';
import { OrbitControls } from '@react-three/drei';
import { useThree } from '@react-three/fiber';
import { useEffect, useRef, useState } from 'react';
@@ -7,7 +7,6 @@ import { OrbitControls as OrbitControlsClass } from 'three-stdlib';
import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits';
import { hasEquipment } from '../../../../../../core/blitzkit/hasEquipment';
import { Pose, poseEvent } from '../../../../../../core/blitzkit/pose';
import { useAwait } from '../../../../../../hooks/useAwait';
import { Duel } from '../../../../../../stores/duel';
import { TankopediaEphemeral } from '../../../../../../stores/tankopediaEphemeral';
@@ -18,20 +17,21 @@ const poseDistances: Record<Pose, number> = {
[Pose.Default]: -1,
};
const modelDefinitions = await fetchModelDefinitions();
export function Controls() {
const duelStore = Duel.useStore();
const tankopediaEphemeralStore = TankopediaEphemeral.useStore();
const camera = useThree((state) => state.camera);
const canvas = useThree((state) => state.gl.domElement);
const orbitControls = useRef<OrbitControlsClass>(null);
const awaitedModelDefinitions = useAwait(modelDefinitions);
const protagonist = Duel.use((state) => state.protagonist);
const antagonist = Duel.use((state) => state.antagonist);
const protagonistModelDefinition =
awaitedModelDefinitions[protagonist.tank.id];
modelDefinitions.models[protagonist.tank.id];
const protagonistTrackModelDefinition =
awaitedModelDefinitions[protagonist.tank.id].tracks[protagonist.track.id];
const antagonistModelDefinition = awaitedModelDefinitions[antagonist.tank.id];
modelDefinitions.models[protagonist.tank.id].tracks[protagonist.track.id];
const antagonistModelDefinition = modelDefinitions.models[antagonist.tank.id];
const protagonistTurretModelDefinition =
protagonistModelDefinition.turrets[protagonist.turret.id];
const antagonistTurretModelDefinition =
@@ -1,12 +1,14 @@
import {
CrewType,
degressiveStat,
isExplosive,
normalizeBoundingBox,
progressiveStat,
resolveDpm,
resolveNearPenetration,
resolvePenetrationCoefficient,
ShellType,
sum,
TankClass,
unionBoundingBox,
type EngineDefinition,
type EquipmentDefinitions,
@@ -75,18 +77,17 @@ export function tankCharacteristics(
provisionDefinitions: ProvisionDefinitions;
},
) {
const presetRows = equipmentDefinitions.presets[tank.equipment];
const preset = equipmentDefinitions.presets[tank.equipmentPreset];
const turretModelDefinition = tankModelDefinition.turrets[turret.id];
const gunModelDefinition = turretModelDefinition.guns[gun.id];
const gunModelDefinition =
turretModelDefinition.guns[gun.gunType!.value.base.id];
function equipment(id: number) {
return presetRows.some((equipmentRow, rowIndex) => {
return equipmentRow.some((equipment, columnIndex) => {
const choice = equipmentMatrix[rowIndex][columnIndex];
if (choice === 0) return false;
const equipped = equipment[choice === -1 ? 0 : 1];
return equipped === id;
});
return preset.slots.some((slot, index) => {
const choice = equipmentMatrix[Math.floor(index / 3)][index % 3];
if (choice === 0) return false;
const equipped = slot[choice === -1 ? 'left' : 'right'];
return equipped === id;
});
}
function consumable(id: number) {
@@ -128,37 +129,37 @@ export function tankCharacteristics(
const hasGearOil = provision(44);
const hasImprovedGearOil = provision(45);
const gunDefaultPitch = tankModelDefinition.turretRotation?.pitch ?? 0;
const gunDefaultPitch = tankModelDefinition.initialTurretRotation?.pitch ?? 0;
const provisionCrewBonus =
provisions.reduce(
(total, provision) =>
provisionDefinitions[provision].crew
? total + provisionDefinitions[provision].crew! / 100
provisionDefinitions.provisions[provision].crew
? total + provisionDefinitions.provisions[provision].crew! / 100
: total,
0,
) + (hasImprovedVentilation ? 0.08 : 0);
const commanderMastery = crewMastery + provisionCrewBonus;
const loaderMastery =
commanderMastery *
(tank.crew.some(({ type }) => type === 'loader') ? 1.1 : 1.05);
(tank.crew.some(({ type }) => type === CrewType.LOADER) ? 1.1 : 1.05);
const gunnerMastery =
commanderMastery *
(tank.crew.some(({ type }) => type === 'gunner') ? 1.1 : 1.05);
(tank.crew.some(({ type }) => type === CrewType.GUNNER) ? 1.1 : 1.05);
const driverMastery =
commanderMastery *
(tank.crew.some(({ type }) => type === 'driver') ? 1.1 : 1.05);
(tank.crew.some(({ type }) => type === CrewType.DRIVER) ? 1.1 : 1.05);
const intraClipCoefficient = coefficient([hasShellReloadBoost, -0.3]);
const damageCoefficient =
coefficient([hasTungsten, 0.15]) *
coefficient(
[applyReactiveArmor, -0.27],
[applyDynamicArmor, -0.1],
[applySpallLiner && shell.type === 'he', -0.2],
[applySpallLiner && shell.type === ShellType.HE, -0.2],
);
const reloadCoefficient =
(coefficient([hasGunRammer, -0.05]) *
coefficient([true, degressiveStat(loaderMastery)])) /
coefficient([hasAdrenaline && gun.type === 'regular', 0.17]);
coefficient([hasAdrenaline && gun.gunType!.$case === 'regular', 0.17]);
const penetrationCoefficient = coefficient([
hasCalibratedShells,
resolvePenetrationCoefficient(true, shell.type) - 1,
@@ -207,7 +208,9 @@ export function tankCharacteristics(
const enginePowerCoefficient = coefficient(
[
hasEngineAccelerator,
tank.class === 'heavyTank' || tank.class === 'AT-SPG' ? 0.05 : 0.04,
tank.class === TankClass.HEAVY || tank.class === TankClass.TANK_DESTROYER
? 0.05
: 0.04,
],
[hasReducedEnginePowerBoost, 0.1],
[hasEnginePowerBoost, 0.2],
@@ -235,11 +238,11 @@ export function tankCharacteristics(
const viewRangeCoefficient =
coefficient([
hasImprovedOptics,
tank.class === 'heavyTank'
tank.class === TankClass.HEAVY
? 0.06
: tank.class === 'mediumTank'
: tank.class === TankClass.MEDIUM
? 0.08
: tank.class === 'lightTank'
: tank.class === TankClass.LIGHT
? 0.11
: 0.04,
]) * coefficient([true, progressiveStat(commanderMastery)]);
@@ -260,20 +263,20 @@ export function tankCharacteristics(
true,
coefficient([
camouflage,
tank.class === 'AT-SPG'
tank.class === TankClass.TANK_DESTROYER
? 0.04
: tank.class === 'heavyTank'
: tank.class === TankClass.HEAVY
? 0.03
: 0.02,
]),
],
[
hasCamouflageNet,
tank.class === 'heavyTank'
tank.class === TankClass.HEAVY
? 0.02
: tank.class === 'mediumTank'
: tank.class === TankClass.MEDIUM
? 0.04
: tank.class === 'lightTank'
: tank.class === TankClass.LIGHT
? 0.07
: 0.1,
],
@@ -284,9 +287,9 @@ export function tankCharacteristics(
coefficient(
[
camouflage,
tank.class === 'AT-SPG'
tank.class === TankClass.TANK_DESTROYER
? 0.04
: tank.class === 'heavyTank'
: tank.class === TankClass.HEAVY
? 0.03
: 0.02,
],
@@ -295,11 +298,11 @@ export function tankCharacteristics(
],
[
hasCamouflageNet,
tank.class === 'heavyTank'
tank.class === TankClass.HEAVY
? 0.04
: tank.class === 'mediumTank'
: tank.class === TankClass.MEDIUM
? 0.08
: tank.class === 'lightTank'
: tank.class === TankClass.LIGHT
? 0.14
: 0.2,
],
@@ -312,14 +315,18 @@ export function tankCharacteristics(
),
);
const weightKg =
tank.weight + engine.weight + track.weight + turret.weight + gun.weight;
tank.weight +
engine.weight +
track.weight +
turret.weight +
gun.gunType!.value.base.weight;
const weightTons = weightKg / 1000;
const stockWeight =
tank.weight +
stockEngine.weight +
stockTrack.weight +
stockTurret.weight +
stockGun.weight;
stockGun.gunType!.value.base.weight;
const resolvedEnginePower = engine.power * enginePowerCoefficient;
const dpm = resolveDpm(
gun,
@@ -329,78 +336,100 @@ export function tankCharacteristics(
intraClipCoefficient,
);
const intraClip =
gun.type === 'regular' ? undefined : gun.intraClip * intraClipCoefficient;
gun.gunType?.$case === 'regular'
? undefined
: gun.gunType!.value.extension.intraClip * intraClipCoefficient;
const mostOptimalShellIndex =
gun.type === 'autoReloader'
? gun.reload.reduce<null | { index: number; reload: number }>(
(current, reloadRaw, index) => {
const reload =
reloadRaw * reloadCoefficient + (index > 0 ? intraClip! : 0);
gun.gunType!.$case === 'autoReloader'
? gun.gunType!.value.extension.shellReloads.reduce<null | {
index: number;
reload: number;
}>((current, reloadRaw, index) => {
const reload =
reloadRaw * reloadCoefficient + (index > 0 ? intraClip! : 0);
if (current === null || reload < current.reload) {
return { index, reload };
}
return current;
},
null,
)!.index
if (current === null || reload < current.reload) {
return { index, reload };
}
return current;
}, null)!.index
: undefined;
const damage = shell.damage.armor * damageCoefficient;
const damage = shell.armorDamage * damageCoefficient;
const dpmEffective =
gun.type === 'autoReloader'
? gun.reload[0] > gun.reload[1] + gun.intraClip
? ((damageCoefficient * shell.damage.armor) /
(gun.reload.at(-1)! * reloadCoefficient +
gun.intraClip * intraClipCoefficient)) *
gun.gunType!.$case === 'autoReloader'
? gun.gunType!.value.extension.shellReloads[0] >
gun.gunType!.value.extension.shellReloads[1] +
gun.gunType!.value.extension.intraClip
? ((damageCoefficient * shell.armorDamage) /
(gun.gunType!.value.extension.shellReloads.at(-1)! *
reloadCoefficient +
gun.gunType!.value.extension.intraClip * intraClipCoefficient)) *
(60 -
(gun.reload.slice(0, -1).length - 1) *
gun.intraClip *
(gun.gunType!.value.extension.shellReloads.slice(0, -1).length -
1) *
gun.gunType!.value.extension.intraClip *
intraClipCoefficient) +
damageCoefficient *
shell.damage.armor *
gun.reload.slice(0, -1).length
: ((damageCoefficient * shell.damage.armor) /
(gun.reload[0] * reloadCoefficient +
gun.intraClip * intraClipCoefficient)) *
shell.armorDamage *
gun.gunType!.value.extension.shellReloads.slice(0, -1).length
: ((damageCoefficient * shell.armorDamage) /
(gun.gunType!.value.extension.shellReloads[0] * reloadCoefficient +
gun.gunType!.value.extension.intraClip * intraClipCoefficient)) *
(60 -
(gun.reload.slice(1).length - 1) *
gun.intraClip *
(gun.gunType!.value.extension.shellReloads.slice(1).length - 1) *
gun.gunType!.value.extension.intraClip *
intraClipCoefficient) +
damageCoefficient * shell.damage.armor * gun.reload.slice(1).length
damageCoefficient *
shell.armorDamage *
gun.gunType!.value.extension.shellReloads.slice(1).length
: undefined;
const shells = gun.type === 'regular' ? 1 : gun.count;
const shells =
gun.gunType!.$case === 'regular'
? 1
: gun.gunType!.value.extension.shellCount;
const shellReloads =
gun.type === 'autoReloader'
? gun.reload.map((reload) => reload * reloadCoefficient)
gun.gunType!.$case === 'autoReloader'
? gun.gunType!.value.extension.shellReloads.map(
(reload) => reload * reloadCoefficient,
)
: undefined;
const shellReload =
gun.type === 'autoReloader' ? undefined : gun.reload * reloadCoefficient;
gun.gunType!.$case === 'autoReloader'
? undefined
: (gun.gunType!.$case === 'regular'
? gun.gunType!.value.extension.reload
: gun.gunType!.value.extension.clipReload) * reloadCoefficient;
const caliber = shell.caliber;
const penetration =
resolveNearPenetration(shell.penetration) * penetrationCoefficient;
const clipDamage = gun.type === 'regular' ? undefined : gun.count * damage;
const moduleDamage = shell.damage.module * damageCoefficient;
const penetration = shell.penetration.near * penetrationCoefficient;
const clipDamage =
gun.gunType!.$case === 'regular'
? undefined
: gun.gunType!.value.extension.shellCount * damage;
const moduleDamage = shell.moduleDamage * damageCoefficient;
const explosionRadius = shell.explosionRadius;
const shellVelocity = shell.speed * shellVelocityCoefficient;
const aimTime = gun.aimTime * aimTimeCoefficient;
const dispersion = gun.dispersion.base * dispersionStillCoefficient;
const shellVelocity = shell.velocity * shellVelocityCoefficient;
const aimTime = gun.gunType!.value.base.aimTime * aimTimeCoefficient;
const dispersion =
gun.gunType!.value.base.dispersionBase * dispersionStillCoefficient;
const maxDispersion =
gun.dispersion.base *
gun.gunType!.value.base.dispersionBase *
Math.sqrt(
((gun.dispersion.traverse * turret.traverseSpeed) ** 2 +
(track.dispersion.traverse * track.traverseSpeed) ** 2 +
track.dispersion.move * tank.speed.forwards) **
((gun.gunType!.value.base.dispersionTraverse * turret.traverseSpeed) **
2 +
(track.traverseSpeed * track.traverseSpeed) ** 2 +
track.dispersionMove * tank.speedForwards) **
2,
);
const dispersionMoving = track.dispersion.move * dispersionMovingCoefficient;
const dispersionMoving = track.dispersionMove * dispersionMovingCoefficient;
const dispersionHullTraversing =
track.dispersion.traverse * dispersionHullTraverseCoefficient;
track.traverseSpeed * dispersionHullTraverseCoefficient;
const dispersionTurretTraversing =
gun.dispersion.traverse * dispersionTurretTraverseCoefficient;
gun.gunType!.value.base.dispersionTraverse *
dispersionTurretTraverseCoefficient;
const dispersionShooting =
gun.dispersion.shot * dispersionAfterShotCoefficient;
gun.gunType!.value.base.dispersionShot * dispersionAfterShotCoefficient;
const dispersionGunDamaged =
gun.dispersion.damaged * dispersionDamagedCoefficient;
gun.gunType!.value.base.dispersionDamaged * dispersionDamagedCoefficient;
const gunDepression =
gunModelDefinition.pitch.max +
gunDefaultPitch +
@@ -421,17 +450,17 @@ export function tankCharacteristics(
? -turretModelDefinition.yaw.min
: undefined;
const azimuthRight = turretModelDefinition.yaw?.max;
const speedForwards = tank.speed.forwards + speedForwardsSum;
const speedBackwards = tank.speed.backwards + speedBackwardsSum;
const speedForwards = tank.speedForwards + speedForwardsSum;
const speedBackwards = tank.speedBackwards + speedBackwardsSum;
const enginePower = resolvedEnginePower;
const hardTerrainCoefficientRaw =
track.resistance.hard * resistanceHardCoefficient;
track.resistanceHard * resistanceHardCoefficient;
const hardTerrainCoefficient = 100 / hardTerrainCoefficientRaw;
const mediumTerrainCoefficientRaw =
track.resistance.medium * resistanceMediumCoefficient;
track.resistanceMedium * resistanceMediumCoefficient;
const mediumTerrainCoefficient = 100 / mediumTerrainCoefficientRaw;
const softTerrainCoefficientRaw =
track.resistance.soft * resistanceSoftCoefficient;
track.resistanceSoft * resistanceSoftCoefficient;
const softTerrainCoefficient = 100 / softTerrainCoefficientRaw;
const powerToWeightRatioHardTerrain =
resolvedEnginePower / weightTons / hardTerrainCoefficientRaw;
@@ -445,37 +474,39 @@ export function tankCharacteristics(
(resolvedEnginePower / stockEngine.power) *
track.traverseSpeed *
hullTraverseCoefficient *
(track.resistance.hard / hardTerrainCoefficientRaw) *
(track.resistanceHard / hardTerrainCoefficientRaw) *
(stockWeight / weightKg);
const hullTraverseMediumTerrain =
(resolvedEnginePower / stockEngine.power) *
track.traverseSpeed *
hullTraverseCoefficient *
(track.resistance.hard / mediumTerrainCoefficientRaw) *
(track.resistanceHard / mediumTerrainCoefficientRaw) *
(stockWeight / weightKg);
const hullTraverseSoftTerrain =
(resolvedEnginePower / stockEngine.power) *
track.traverseSpeed *
hullTraverseCoefficient *
(track.resistance.hard / softTerrainCoefficientRaw) *
(track.resistanceHard / softTerrainCoefficientRaw) *
(stockWeight / weightKg);
const health = (tank.health + turret.health) * healthCoefficient;
const fireChance = engine.fireChance * fireChanceCoefficient;
const viewRange = turret.viewRange * viewRangeCoefficient;
const camouflageStill = tank.camouflage.still * camouflageCoefficientStill;
const camouflageStill = tank.camouflageStill * camouflageCoefficientStill;
const camouflageMoving =
tank.class === 'lightTank'
? tank.camouflage.still * camouflageCoefficientStill
: tank.camouflage.moving * camouflageCoefficientMoving;
tank.class === TankClass.LIGHT
? tank.camouflageStill * camouflageCoefficientStill
: tank.camouflageMoving * camouflageCoefficientMoving;
const camouflageShootingStill =
tank.camouflage.still * gun.camouflageLoss * camouflageCoefficientStill;
tank.camouflageStill *
gun.gunType!.value.base.camouflageLoss *
camouflageCoefficientStill;
const camouflageShootingMoving =
(tank.class === 'lightTank'
? tank.camouflage.still * camouflageCoefficientStill
: tank.camouflage.moving * camouflageCoefficientMoving) *
gun.camouflageLoss;
(tank.class === TankClass.LIGHT
? tank.camouflageStill * camouflageCoefficientStill
: tank.camouflageMoving * camouflageCoefficientMoving) *
gun.gunType!.value.base.camouflageLoss;
const camouflageCaughtOnFire =
tank.camouflage.onFire * tank.camouflage.still * camouflageCoefficientStill;
tank.camouflageOnFire * tank.camouflageStill * camouflageCoefficientStill;
const width = size[2];
const height = size[0];
const length = size[1];
+7 -4
View File
@@ -1,18 +1,21 @@
import {
BlitzkitResponse,
TankVotes,
unwrapBlitzkitResponse,
type BlitzkitResponse,
type TankVotes,
} from '@blitzkit/core';
import { useStore } from '@nanostores/react';
import { useEffect, useState } from 'react';
import * as App from '../stores/app';
import { $wargamingLogin } from '../stores/wargamingLogin';
const cache: Record<string, Promise<TankVotes> | TankVotes> = {};
export function useTankVotes(id: number) {
const wargaming = App.use((state) => state.logins.wargaming);
const [votes, setVotes] = useState<TankVotes | null>(null);
const wargaming = useStore($wargamingLogin);
useEffect(() => {
if (!wargaming.token) return;
if (id in cache) {
Promise.all([cache[id]]).then(([votes]) => setVotes(votes));
} else {
@@ -1,7 +1,7 @@
import {
equipmentDefinitions,
modelDefinitions,
provisionDefinitions,
fetchEquipmentDefinitions,
fetchModelDefinitions,
fetchProvisionDefinitions,
} from '@blitzkit/core';
import { PlusIcon } from '@radix-ui/react-icons';
import { Box, Flex, Heading, IconButton, Text } from '@radix-ui/themes';
@@ -14,9 +14,9 @@ import { tankCharacteristics } from '../../../core/blitzkit/tankCharacteristics'
import { CompareEphemeral } from '../../../stores/compareEphemeral';
import { ComparePersistent } from '../../../stores/comparePersistent';
const awaitedModelDefinitions = await modelDefinitions;
const awaitedEquipmentDefinitions = await equipmentDefinitions;
const awaitedProvisionDefinitions = await provisionDefinitions;
const modelDefinitions = await fetchModelDefinitions();
const equipmentDefinitions = await fetchEquipmentDefinitions();
const provisionDefinitions = await fetchProvisionDefinitions();
export function Page() {
return (
@@ -60,9 +60,9 @@ function Content() {
),
},
{
equipmentDefinitions: awaitedEquipmentDefinitions,
provisionDefinitions: awaitedProvisionDefinitions,
tankModelDefinition: awaitedModelDefinitions[thisMember.tank.id],
equipmentDefinitions: equipmentDefinitions,
provisionDefinitions: provisionDefinitions,
tankModelDefinition: modelDefinitions.models[thisMember.tank.id],
},
),
),
@@ -102,8 +102,8 @@ export type EmbedConfigItem = (
pad?: boolean;
};
export type EmbedState = Record<string, EmbedConfigItem['default']>;
export type EmbedStateStore = Record<string, EmbedConfigItem['default']>;
export const { Provider, use, useMutation, useStore } = createContextualStore(
(init: EmbedState) => create<EmbedState>()(() => init),
export const EmbedState = createContextualStore((init: EmbedStateStore) =>
create<EmbedStateStore>()(() => init),
);
@@ -1,11 +1,11 @@
import type { TextProps } from '@radix-ui/themes';
import type { EmbedConfig } from '.';
import { EmbedState, type EmbedConfig, type ExtractEmbedConfigTypes } from '.';
import type { EmbedConfigItemType, EmbedItemType } from './constants';
export function useEmbedStateCurry<Config extends EmbedConfig>() {
function useState<Key extends keyof Config>(key: Key) {
return EmbedState.use((state) => {
return (state as EmbedState.ExtractEmbedConfigTypes<Config>)[key];
return (state as ExtractEmbedConfigTypes<Config>)[key];
});
}
+2 -2
View File
@@ -1,11 +1,11 @@
import type { TankClass, Tier, TreeType } from '@blitzkit/core';
import type { TankClass, TankType, Tier } from '@blitzkit/core';
import { map } from 'nanostores';
export interface TankFilters {
tiers: Tier[];
nations: string[];
classes: TankClass[];
types: TreeType[];
types: TankType[];
testing: 'include' | 'exclude' | 'only';
search?: string;
searching: boolean;