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