feat: burst data

This commit is contained in:
TrèsAbhi committed 2025-08-18 12:06:14 -05:00
1 parent 79c450798c
commit c20f8c5040
37 files changed
+973 -1378

No files matched your search

+3 -8
View File
@@ -197,15 +197,10 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
turret.guns.forEach((gun) => { turret.guns.forEach((gun) => {
if ( if (
gun.gun_type!.value.base.research_cost !== undefined && gun.research_cost !== undefined &&
typeof gun.gun_type!.value.base.research_cost typeof gun.research_cost.research_cost_type!.value === 'number'
.research_cost_type!.value === 'number'
) { ) {
gunXps.set( gunXps.set(gun.id, gun.research_cost.research_cost_type!.value);
gun.gun_type!.value.base.id,
gun.gun_type!.value.base.research_cost.research_cost_type!
.value,
);
} }
}); });
}); });
+21 -24
View File
@@ -1,6 +1,6 @@
import { import {
GunDefinition, GunDefinition,
GunDefinitionAutoReloaderProperties, GunDefinitionAutoReloader,
ShellDefinition, ShellDefinition,
} from '../protos'; } from '../protos';
@@ -15,34 +15,31 @@ export function resolveDpm(
let dps: number; let dps: number;
if (gun.gun_type!.$case === 'regular') { if (gun.gun_type!.$case === 'regular') {
dps = alpha / (reloadCoefficient * gun.gun_type!.value.extension.reload); dps = alpha / (reloadCoefficient * gun.gun_type!.value.reload);
} else if (gun.gun_type!.$case === 'auto_loader') { } else if (gun.gun_type!.$case === 'auto_loader') {
dps = dps =
(alpha * gun.gun_type!.value.extension.shell_count) / (alpha * gun.gun_type!.value.shell_count) /
(gun.gun_type!.value.extension.clip_reload * reloadCoefficient + (gun.gun_type!.value.clip_reload * reloadCoefficient +
(gun.gun_type!.value.extension.shell_count - 1) * (gun.gun_type!.value.shell_count - 1) *
gun.gun_type!.value.extension.intra_clip * gun.gun_type!.value.intra_clip *
intraClipCoefficient); intraClipCoefficient);
} else { } else {
const mostOptimalShell = const mostOptimalShell = gun.gun_type!.value.shell_reloads.reduce<null | {
gun.gun_type!.value.extension.shell_reloads.reduce<null | { index: number;
index: number; reload: number;
reload: number; }>((current, reloadRaw, index) => {
}>((current, reloadRaw, index) => { const reload =
const reload = reloadRaw * reloadCoefficient +
reloadRaw * reloadCoefficient + (index > 0
(index > 0 ? (gun.gun_type!.value as GunDefinitionAutoReloader).intra_clip *
? ( intraClipCoefficient
gun.gun_type!.value : 0);
.extension as GunDefinitionAutoReloaderProperties
).intra_clip * intraClipCoefficient
: 0);
if (current === null || reload < current.reload) { if (current === null || reload < current.reload) {
return { index, reload }; return { index, reload };
} }
return current; return current;
}, null)!; }, null)!;
dps = alpha / mostOptimalShell?.reload; dps = alpha / mostOptimalShell?.reload;
} }
+2 -2
View File
@@ -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.gun_type!.value.base.id)) { if (!gunIds.has(gun.id)) {
gunIds.add(gun.gun_type!.value.base.id); gunIds.add(gun.id);
guns.push({ turret, gun }); guns.push({ turret, gun });
} }
}); });
+24 -34
View File
@@ -108,58 +108,48 @@ message GunDefinition {
GunDefinitionAutoLoader auto_loader = 2; GunDefinitionAutoLoader auto_loader = 2;
GunDefinitionAutoReloader auto_reloader = 3; GunDefinitionAutoReloader auto_reloader = 3;
} }
required uint32 id = 4;
required float rotation_speed = 5;
optional ResearchCost research_cost = 6;
required uint32 weight = 7;
required I18nString name = 8;
required uint32 tier = 9;
repeated ShellDefinition shells = 10;
required float camouflage_loss = 11;
required float aim_time = 12;
required float dispersion_base = 13;
required float dispersion_traverse = 14;
required float dispersion_shot = 15;
required float dispersion_damaged = 16;
repeated Unlock unlocks = 17;
required uint32 shell_capacity = 18;
optional AssaultRanges assault_ranges = 19;
optional Burst burst = 20;
} }
message GunDefinitionRegular { message GunDefinitionRegular {
required GunDefinitionBase base = 1;
required GunDefinitionRegularProperties extension = 2;
}
message GunDefinitionRegularProperties {
required float reload = 1; required float reload = 1;
} }
message GunDefinitionAutoLoader { message GunDefinitionAutoLoader {
required GunDefinitionBase base = 1;
required GunDefinitionAutoLoaderProperties extension = 2;
}
message GunDefinitionAutoLoaderProperties {
required float clip_reload = 1; required float clip_reload = 1;
required float intra_clip = 2; required float intra_clip = 2;
required float shell_count = 3; required float shell_count = 3;
optional Burst burst = 4;
}
message Burst {
required uint32 count = 1;
required float interval = 2;
} }
message GunDefinitionAutoReloader { message GunDefinitionAutoReloader {
required GunDefinitionBase base = 1;
required GunDefinitionAutoReloaderProperties extension = 2;
}
message GunDefinitionAutoReloaderProperties {
repeated float shell_reloads = 1; repeated float shell_reloads = 1;
required float intra_clip = 2; required float intra_clip = 2;
required float shell_count = 3; required float shell_count = 3;
} }
message GunDefinitionBase {
required uint32 id = 1;
required float rotation_speed = 2;
optional ResearchCost research_cost = 3;
required uint32 weight = 4;
required I18nString name = 5;
required uint32 tier = 6;
repeated ShellDefinition shells = 7;
required float camouflage_loss = 8;
required float aim_time = 9;
required float dispersion_base = 10;
required float dispersion_traverse = 11;
required float dispersion_shot = 12;
required float dispersion_damaged = 13;
repeated Unlock unlocks = 14;
required uint32 shell_capacity = 15;
optional AssaultRanges assault_ranges = 16;
}
message AssaultRanges { message AssaultRanges {
repeated AssaultRange ranges = 1; repeated AssaultRange ranges = 1;
repeated ShellType types = 2; repeated ShellType types = 2;
File diff suppressed because it is too large. Load diff
+25 -24
View File
@@ -12,7 +12,6 @@ import {
EquipmentSlot, EquipmentSlot,
GameDefinitions, GameDefinitions,
GunDefinition, GunDefinition,
GunDefinitionBase,
I18nString, I18nString,
MapDefinitions, MapDefinitions,
ModelDefinitions, ModelDefinitions,
@@ -274,6 +273,7 @@ interface VehicleDefinitions {
pumpGunMode?: boolean; pumpGunMode?: boolean;
pumpGunReloadTimes?: string; pumpGunReloadTimes?: string;
clip?: { count: number; rate: number }; clip?: { count: number; rate: number };
burst?: { count: number; rate: number };
models: { undamaged: string }; models: { undamaged: string };
extras?: { extras?: {
trayShell?: { trayShell?: {
@@ -1304,7 +1304,12 @@ export async function definitions() {
} }
}); });
const base = { if (gun.burst) {
console.log(tankId);
console.log(gun.burst);
}
tankDefinitions.tanks[tankId].turrets[turretIndex].guns.push({
id: gunId, id: gunId,
weight: gunListEntry.weight, weight: gunListEntry.weight,
rotation_speed: gunListEntry.rotationSpeed, rotation_speed: gunListEntry.rotationSpeed,
@@ -1324,41 +1329,39 @@ export async function definitions() {
unlocks: resolveUnlocks(gun.unlocks), unlocks: resolveUnlocks(gun.unlocks),
shell_capacity: gun.maxAmmo ?? gunListEntry.maxAmmo, shell_capacity: gun.maxAmmo ?? gunListEntry.maxAmmo,
assault_ranges, assault_ranges,
} satisfies GunDefinitionBase;
tankDefinitions.tanks[tankId].turrets[turretIndex].guns.push({
gun_type: gun_type:
gunType === 'regular' gunType === 'regular'
? { ? {
$case: 'regular', $case: 'regular',
value: { value: {
base, reload: gun.reloadTime,
extension: { reload: gun.reloadTime },
}, },
} }
: gunType === 'autoReloader' : gunType === 'autoReloader'
? { ? {
$case: 'auto_reloader', $case: 'auto_reloader',
value: { value: {
base, intra_clip: 60 / gun.clip!.rate,
extension: { shell_count: gunClipCount,
intra_clip: 60 / gun.clip!.rate, shell_reloads: gun
shell_count: gunClipCount, .pumpGunReloadTimes!.split(' ')
shell_reloads: gun .map(Number),
.pumpGunReloadTimes!.split(' ')
.map(Number),
},
}, },
} }
: { : {
$case: 'auto_loader', $case: 'auto_loader',
value: { value: {
base, intra_clip: 60 / gun.clip!.rate,
extension: { clip_reload: gun.reloadTime,
intra_clip: 60 / gun.clip!.rate, shell_count: gunClipCount,
clip_reload: gun.reloadTime, burst:
shell_count: gunClipCount, gun.burst && gun.burst.count > 1
}, ? {
count: gun.burst.count,
interval: 60 / gun.burst.rate,
}
: undefined,
}, },
}, },
} satisfies GunDefinition); } satisfies GunDefinition);
@@ -1407,7 +1410,7 @@ export async function definitions() {
tankDefinitions.tanks[tankId].turrets[turretIndex].guns[ tankDefinitions.tanks[tankId].turrets[turretIndex].guns[
gunIndex gunIndex
].gun_type!.value.base!.shells.push({ ].shells.push({
id: shellId, id: shellId,
name: shellName, name: shellName,
velocity: gunShellEntry.speed, velocity: gunShellEntry.speed,
@@ -1495,9 +1498,7 @@ export async function definitions() {
turret.research_cost = turretXps.get(turret.id); turret.research_cost = turretXps.get(turret.id);
Object.values(turret.guns).forEach((gunRaw) => { Object.values(turret.guns).forEach((gunRaw) => {
gunRaw.gun_type!.value.base!.research_cost = gunXps.get( gunRaw.research_cost = gunXps.get(gunRaw.id);
gunRaw.gun_type!.value.base!.id,
);
}); });
}); });
@@ -100,8 +100,7 @@ export function ArmorPlateDisplay() {
const { protagonist } = duelStore.getState(); const { protagonist } = duelStore.getState();
const tank = draft.model; const tank = draft.model;
const turret = tank.turrets[protagonist.turret.id]; const turret = tank.turrets[protagonist.turret.id];
const gun = const gun = turret.guns[protagonist.gun.id];
turret.guns[protagonist.gun.gun_type!.value.base.id];
const track = tank.tracks[protagonist.track.id]; const track = tank.tracks[protagonist.track.id];
if (highlightArmor.name.startsWith('hull_')) { if (highlightArmor.name.startsWith('hull_')) {
@@ -154,12 +153,8 @@ export function ArmorPlateDisplay() {
const turret = tank.turrets[protagonist.turret.id]; const turret = tank.turrets[protagonist.turret.id];
const initialTurret = const initialTurret =
initialTank.turrets[protagonist.turret.id]; initialTank.turrets[protagonist.turret.id];
const gun = const gun = turret.guns[protagonist.gun.id];
turret.guns[protagonist.gun.gun_type!.value.base.id]; const initialGun = initialTurret.guns[protagonist.gun.id];
const initialGun =
initialTurret.guns[
protagonist.gun.gun_type!.value.base.id
];
const track = tank.tracks[protagonist.track.id]; const track = tank.tracks[protagonist.track.id];
const initialTrack = const initialTrack =
initialTank.tracks[protagonist.track.id]; initialTank.tracks[protagonist.track.id];
@@ -24,8 +24,7 @@ export const PrimaryArmorScene = memo(() => {
const tankModelDefinition = useTankModelDefinition(); const tankModelDefinition = useTankModelDefinition();
const trackModelDefinition = tankModelDefinition.tracks[track.id]; const trackModelDefinition = tankModelDefinition.tracks[track.id];
const turretModelDefinition = tankModelDefinition.turrets[turret.id]; const turretModelDefinition = tankModelDefinition.turrets[turret.id];
const gunModelDefinition = const gunModelDefinition = turretModelDefinition.guns[gun.id];
turretModelDefinition.guns[gun.gun_type!.value.base.id];
const hullOrigin = correctZYTuple(trackModelDefinition.origin); const hullOrigin = correctZYTuple(trackModelDefinition.origin);
const turretOrigin = correctZYTuple(tankModelDefinition.turret_origin); const turretOrigin = correctZYTuple(tankModelDefinition.turret_origin);
const gunOrigin = correctZYTuple(turretModelDefinition.gun_origin); const gunOrigin = correctZYTuple(turretModelDefinition.gun_origin);
@@ -36,8 +36,7 @@ export const SpacedArmorScene = memo<SpacedArmorSceneProps>(({ scene }) => {
const tankModelDefinition = useTankModelDefinition(); const tankModelDefinition = useTankModelDefinition();
const trackModelDefinition = tankModelDefinition.tracks[track.id]; const trackModelDefinition = tankModelDefinition.tracks[track.id];
const turretModelDefinition = tankModelDefinition.turrets[turret.id]; const turretModelDefinition = tankModelDefinition.turrets[turret.id];
const gunModelDefinition = const gunModelDefinition = turretModelDefinition.guns[gun.id];
turretModelDefinition.guns[gun.gun_type!.value.base.id];
const hullOrigin = correctZYTuple(trackModelDefinition.origin); const hullOrigin = correctZYTuple(trackModelDefinition.origin);
const turretOrigin = correctZYTuple(tankModelDefinition.turret_origin); const turretOrigin = correctZYTuple(tankModelDefinition.turret_origin);
const gunOrigin = correctZYTuple(turretModelDefinition.gun_origin); const gunOrigin = correctZYTuple(turretModelDefinition.gun_origin);
@@ -42,8 +42,7 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
const tankModelDefinition = useTankModelDefinition(); const tankModelDefinition = useTankModelDefinition();
const trackModelDefinition = tankModelDefinition.tracks[track.id]; const trackModelDefinition = tankModelDefinition.tracks[track.id];
const turretModelDefinition = tankModelDefinition.turrets[turret.id]; const turretModelDefinition = tankModelDefinition.turrets[turret.id];
const gunModelDefinition = const gunModelDefinition = turretModelDefinition.guns[gun.id];
turretModelDefinition.guns[gun.gun_type!.value.base.id];
const hullOrigin = correctZYTuple(trackModelDefinition.origin); const hullOrigin = correctZYTuple(trackModelDefinition.origin);
const turretOrigin = correctZYTuple(tankModelDefinition.turret_origin); const turretOrigin = correctZYTuple(tankModelDefinition.turret_origin);
const gunOrigin = correctZYTuple(turretModelDefinition.gun_origin); const gunOrigin = correctZYTuple(turretModelDefinition.gun_origin);
@@ -33,43 +33,35 @@ export function Body({ stats }: BodyProps) {
<StickyColumnHeaderCell key={key} top={137}> <StickyColumnHeaderCell key={key} top={137}>
<Flex justify="center"> <Flex justify="center">
<Flex> <Flex>
{gun.gun_type!.value.base.shells.map( {gun.shells.map((thisShell, shellIndex) => (
(thisShell, shellIndex) => ( <IconButton
<IconButton color={thisShell.id === shell.id ? undefined : 'gray'}
color={thisShell.id === shell.id ? undefined : 'gray'} variant="soft"
variant="soft" key={thisShell.id}
key={thisShell.id} style={{
style={{ borderTopLeftRadius: shellIndex === 0 ? undefined : 0,
borderTopLeftRadius: shellIndex === 0 ? undefined : 0, borderBottomLeftRadius:
borderBottomLeftRadius: shellIndex === 0 ? undefined : 0,
shellIndex === 0 ? undefined : 0, borderTopRightRadius:
borderTopRightRadius: shellIndex === gun.shells.length - 1 ? undefined : 0,
shellIndex === borderBottomRightRadius:
gun.gun_type!.value.base.shells.length - 1 shellIndex === gun.shells.length - 1 ? undefined : 0,
? undefined marginLeft: shellIndex === 0 ? 0 : -1,
: 0, }}
borderBottomRightRadius: onClick={() => {
shellIndex === mutateCompareEphemeral((draft) => {
gun.gun_type!.value.base.shells.length - 1 draft.members[index].shell = thisShell;
? undefined });
: 0, }}
marginLeft: shellIndex === 0 ? 0 : -1, >
}} <img
onClick={() => { alt={unwrap(thisShell.name)}
mutateCompareEphemeral((draft) => { width={16}
draft.members[index].shell = thisShell; height={16}
}); src={asset(`icons/shells/${thisShell.icon}.webp`)}
}} />
> </IconButton>
<img ))}
alt={unwrap(thisShell.name)}
width={16}
height={16}
src={asset(`icons/shells/${thisShell.icon}.webp`)}
/>
</IconButton>
),
)}
</Flex> </Flex>
</Flex> </Flex>
</StickyColumnHeaderCell> </StickyColumnHeaderCell>
@@ -250,8 +250,7 @@ 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.shell = member.gun.shells[0];
member.gun.gun_type!.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];
}); });
@@ -267,8 +266,7 @@ 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.shell = member.gun.shells[0];
member.gun.gun_type!.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)!;
}); });
@@ -62,7 +62,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.gun_type!.value.base.shells[0]; draft.shell = draft.gun.shells[0];
onChange?.(draft); onChange?.(draft);
setTurretMenuOpen(false); setTurretMenuOpen(false);
@@ -83,9 +83,7 @@ export function ModuleManager({
<ModuleButton <ModuleButton
module="gun" module="gun"
discriminator={ discriminator={TIER_ROMAN_NUMERALS[modules.gun.tier]}
TIER_ROMAN_NUMERALS[modules.gun.gun_type!.value.base.tier]
}
onClick={() => setGunMenuOpen(true)} onClick={() => setGunMenuOpen(true)}
/> />
@@ -95,18 +93,16 @@ export function ModuleManager({
</DropdownMenu.Trigger> </DropdownMenu.Trigger>
<DropdownMenu.Content> <DropdownMenu.Content>
<DropdownMenu.RadioGroup <DropdownMenu.RadioGroup value={`${modules.gun.id}`}>
value={`${modules.gun.gun_type!.value.base.id}`}
>
{[...modules.turret.guns].reverse().map((gun) => { {[...modules.turret.guns].reverse().map((gun) => {
return ( return (
<DropdownMenu.RadioItem <DropdownMenu.RadioItem
key={gun.gun_type!.value.base.id} key={gun.id}
value={`${gun.gun_type!.value.base.id}`} value={`${gun.id}`}
onClick={() => { onClick={() => {
const draft = { ...modules }; const draft = { ...modules };
draft.gun = gun; draft.gun = gun;
draft.shell = draft.gun.gun_type!.value.base.shells[0]; draft.shell = draft.gun.shells[0];
onChange?.(draft); onChange?.(draft);
setGunMenuOpen(false); setGunMenuOpen(false);
@@ -115,11 +111,9 @@ export function ModuleManager({
<ModuleButton <ModuleButton
variant="ghost" variant="ghost"
module="gun" module="gun"
discriminator={ discriminator={TIER_ROMAN_NUMERALS[gun.tier]}
TIER_ROMAN_NUMERALS[gun.gun_type!.value.base.tier]
}
/>{' '} />{' '}
{unwrap(gun.gun_type!.value.base.name)} {unwrap(gun.name)}
</DropdownMenu.RadioItem> </DropdownMenu.RadioItem>
); );
})} })}
@@ -25,19 +25,18 @@ export function TankSearchCard({ tank, onSelect }: TankSearchCardProps) {
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.gun_type!.value.base.shells[0]; const shell0 = gun.shells[0];
const shell1 = gun.gun_type!.value.base.shells[1]; const shell1 = gun.shells[1];
const shell2 = gun.gun_type!.value.base.shells[2]; const shell2 = gun.shells[2];
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 = modelDefinitions.models[tank.id]; const tankModelDefinition = modelDefinitions.models[tank.id];
const turretModelDefinition = tankModelDefinition.turrets[turret.id]; const turretModelDefinition = tankModelDefinition.turrets[turret.id];
const gunModelDefinition = const gunModelDefinition = turretModelDefinition.guns[gun.id];
turretModelDefinition.guns[gun.gun_type!.value.base.id];
switch (tankopediaSort.by) { switch (tankopediaSort.by) {
case 'fire.aimTime': case 'fire.aimTime':
return gun.gun_type!.value.base.aim_time.toFixed(2); return gun.aim_time.toFixed(2);
case 'fire.caliber': case 'fire.caliber':
return shell0.caliber.toFixed(0); return shell0.caliber.toFixed(0);
case 'fire.damage': case 'fire.damage':
@@ -47,7 +46,7 @@ export function TankSearchCard({ tank, onSelect }: TankSearchCardProps) {
case 'fire.dispersionMoving': case 'fire.dispersionMoving':
return tracks.dispersion_move.toFixed(3); return tracks.dispersion_move.toFixed(3);
case 'fire.dispersionStill': case 'fire.dispersionStill':
return gun.gun_type!.value.base.dispersion_base.toFixed(3); return gun.dispersion_base.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':
@@ -63,7 +62,7 @@ export function TankSearchCard({ tank, onSelect }: TankSearchCardProps) {
case 'fire.shellVelocity': case 'fire.shellVelocity':
return shell0.velocity.toFixed(0); return shell0.velocity.toFixed(0);
case 'fire.shellCapacity': case 'fire.shellCapacity':
return gun.gun_type!.value.base.shell_capacity.toFixed(0); return gun.shell_capacity.toFixed(0);
case 'fire.shellRange': case 'fire.shellRange':
return shell0.range.toFixed(0); return shell0.range.toFixed(0);
case 'fire.gunDepression': case 'fire.gunDepression':
@@ -89,7 +88,7 @@ export function TankSearchCard({ tank, onSelect }: TankSearchCardProps) {
engine.weight + engine.weight +
tracks.weight + tracks.weight +
turret.weight + turret.weight +
gun.gun_type!.value.base.weight) gun.weight)
).toFixed(1); ).toFixed(1);
case 'maneuverability.weight': case 'maneuverability.weight':
return ( return (
@@ -97,7 +96,7 @@ export function TankSearchCard({ tank, onSelect }: TankSearchCardProps) {
engine.weight + engine.weight +
tracks.weight + tracks.weight +
turret.weight + turret.weight +
gun.gun_type!.value.base.weight) / gun.weight) /
1000 1000
).toFixed(1); ).toFixed(1);
case 'maneuverability.traverseSpeed': case 'maneuverability.traverseSpeed':
@@ -111,11 +110,7 @@ export function TankSearchCard({ tank, onSelect }: TankSearchCardProps) {
case 'survivability.camouflageMoving': case 'survivability.camouflageMoving':
return (tank.camouflage_moving * 100).toFixed(0); return (tank.camouflage_moving * 100).toFixed(0);
case 'survivability.camouflageShooting': case 'survivability.camouflageShooting':
return ( return (tank.camouflage_still * gun.camouflage_loss * 100).toFixed(0);
tank.camouflage_still *
gun.gun_type!.value.base.camouflage_loss *
100
).toFixed(0);
case 'survivability.volume': { case 'survivability.volume': {
const dimensions = normalizeBoundingBox( const dimensions = normalizeBoundingBox(
unionBoundingBox( unionBoundingBox(
@@ -104,11 +104,9 @@ export const TankSearch = memo<TankSearchProps>(
sorted = filtered.sort( sorted = filtered.sort(
(a, b) => (a, b) =>
a.camouflage_still * a.camouflage_still *
a.turrets.at(-1)!.guns.at(-1)!.gun_type!.value.base a.turrets.at(-1)!.guns.at(-1)!.camouflage_loss -
.camouflage_loss -
b.camouflage_still * b.camouflage_still *
b.turrets.at(-1)!.guns.at(-1)!.gun_type!.value.base b.turrets.at(-1)!.guns.at(-1)!.camouflage_loss,
.camouflage_loss,
); );
break; break;
@@ -171,11 +169,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)!.gun_type!.value.base.shells[0], a.turrets.at(-1)!.guns.at(-1)!.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)!.gun_type!.value.base.shells[0], b.turrets.at(-1)!.guns.at(-1)!.shells[0],
), ),
); );
break; break;
@@ -183,18 +181,16 @@ 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)!.gun_type!.value.base.shells[1] (a.turrets.at(-1)!.guns.at(-1)!.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)!.gun_type!.value.base a.turrets.at(-1)!.guns.at(-1)!.shells[1],
.shells[1],
) )
: 0) - : 0) -
(b.turrets.at(-1)!.guns.at(-1)!.gun_type!.value.base.shells[1] (b.turrets.at(-1)!.guns.at(-1)!.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)!.gun_type!.value.base b.turrets.at(-1)!.guns.at(-1)!.shells[1],
.shells[1],
) )
: 0), : 0),
); );
@@ -211,108 +207,92 @@ 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)!.gun_type!.value.base.shells[0] a.turrets.at(-1)!.guns.at(-1)!.shells[0].caliber -
.caliber - b.turrets.at(-1)!.guns.at(-1)!.shells[0].caliber,
b.turrets.at(-1)!.guns.at(-1)!.gun_type!.value.base.shells[0]
.caliber,
); );
break; break;
case 'fire.standardPenetration': case 'fire.standardPenetration':
sorted = filtered.sort( sorted = filtered.sort(
(a, b) => (a, b) =>
a.turrets.at(-1)!.guns.at(-1)!.gun_type!.value.base.shells[0] a.turrets.at(-1)!.guns.at(-1)!.shells[0].penetration.near -
.penetration.near - b.turrets.at(-1)!.guns.at(-1)!.shells[0].penetration.near,
b.turrets.at(-1)!.guns.at(-1)!.gun_type!.value.base.shells[0]
.penetration.near,
); );
break; break;
case 'fire.premiumPenetration': case 'fire.premiumPenetration':
sorted = filtered.sort( sorted = filtered.sort(
(a, b) => (a, b) =>
(a.turrets.at(-1)!.guns.at(-1)!.gun_type!.value.base.shells[1] (a.turrets.at(-1)!.guns.at(-1)!.shells[1]?.penetration.near ??
?.penetration.near ?? 0) - 0) -
(b.turrets.at(-1)!.guns.at(-1)!.gun_type!.value.base.shells[1] (b.turrets.at(-1)!.guns.at(-1)!.shells[1]?.penetration.near ??
?.penetration.near ?? 0), 0),
); );
break; break;
case 'fire.tertiaryPenetration': case 'fire.tertiaryPenetration':
sorted = filtered.sort( sorted = filtered.sort(
(a, b) => (a, b) =>
(a.turrets.at(-1)!.guns.at(-1)!.gun_type!.value.base.shells[2] (a.turrets.at(-1)!.guns.at(-1)!.shells[2]?.penetration.near ??
?.penetration.near ?? 0) - 0) -
(b.turrets.at(-1)!.guns.at(-1)!.gun_type!.value.base.shells[2] (b.turrets.at(-1)!.guns.at(-1)!.shells[2]?.penetration.near ??
?.penetration.near ?? 0), 0),
); );
break; break;
case 'fire.damage': case 'fire.damage':
sorted = filtered.sort( sorted = filtered.sort(
(a, b) => (a, b) =>
a.turrets.at(-1)!.guns.at(-1)!.gun_type!.value.base.shells[0] a.turrets.at(-1)!.guns.at(-1)!.shells[0].armor_damage -
.armor_damage - b.turrets.at(-1)!.guns.at(-1)!.shells[0].armor_damage,
b.turrets.at(-1)!.guns.at(-1)!.gun_type!.value.base.shells[0]
.armor_damage,
); );
break; break;
case 'fire.moduleDamage': case 'fire.moduleDamage':
sorted = filtered.sort( sorted = filtered.sort(
(a, b) => (a, b) =>
a.turrets.at(-1)!.guns.at(-1)!.gun_type!.value.base.shells[0] a.turrets.at(-1)!.guns.at(-1)!.shells[0].module_damage -
.module_damage - b.turrets.at(-1)!.guns.at(-1)!.shells[0].module_damage,
b.turrets.at(-1)!.guns.at(-1)!.gun_type!.value.base.shells[0]
.module_damage,
); );
break; break;
case 'fire.shellVelocity': case 'fire.shellVelocity':
sorted = filtered.sort( sorted = filtered.sort(
(a, b) => (a, b) =>
a.turrets.at(-1)!.guns.at(-1)!.gun_type!.value.base.shells[0] a.turrets.at(-1)!.guns.at(-1)!.shells[0].velocity -
.velocity - b.turrets.at(-1)!.guns.at(-1)!.shells[0].velocity,
b.turrets.at(-1)!.guns.at(-1)!.gun_type!.value.base.shells[0]
.velocity,
); );
break; break;
case 'fire.shellRange': case 'fire.shellRange':
sorted = filtered.sort( sorted = filtered.sort(
(a, b) => (a, b) =>
a.turrets.at(-1)!.guns.at(-1)!.gun_type!.value.base.shells[0] a.turrets.at(-1)!.guns.at(-1)!.shells[0].range -
.range - b.turrets.at(-1)!.guns.at(-1)!.shells[0].range,
b.turrets.at(-1)!.guns.at(-1)!.gun_type!.value.base.shells[0]
.range,
); );
break; break;
case 'fire.shellCapacity': case 'fire.shellCapacity':
sorted = filtered.sort( sorted = filtered.sort(
(a, b) => (a, b) =>
a.turrets.at(-1)!.guns.at(-1)!.gun_type!.value.base a.turrets.at(-1)!.guns.at(-1)!.shell_capacity -
.shell_capacity - b.turrets.at(-1)!.guns.at(-1)!.shell_capacity,
b.turrets.at(-1)!.guns.at(-1)!.gun_type!.value.base
.shell_capacity,
); );
break; break;
case 'fire.aimTime': case 'fire.aimTime':
sorted = filtered.sort( sorted = filtered.sort(
(a, b) => (a, b) =>
a.turrets.at(-1)!.guns.at(-1)!.gun_type!.value.base.aim_time - a.turrets.at(-1)!.guns.at(-1)!.aim_time -
b.turrets.at(-1)!.guns.at(-1)!.gun_type!.value.base.aim_time, b.turrets.at(-1)!.guns.at(-1)!.aim_time,
); );
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)!.gun_type!.value.base a.turrets.at(-1)!.guns.at(-1)!.dispersion_base -
.dispersion_base - b.turrets.at(-1)!.guns.at(-1)!.dispersion_base,
b.turrets.at(-1)!.guns.at(-1)!.gun_type!.value.base
.dispersion_base,
); );
break; break;
@@ -328,13 +308,11 @@ 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)!.gun_type!.value.base.id] .guns[a.turrets.at(-1)!.guns.at(-1)!.id].pitch.max +
.pitch.max +
(modelDefinitions.models[a.id].initial_turret_rotation?.pitch ?? (modelDefinitions.models[a.id].initial_turret_rotation?.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)!.gun_type!.value.base.id] .guns[b.turrets.at(-1)!.guns.at(-1)!.id].pitch.max -
.pitch.max -
(modelDefinitions.models[b.id].initial_turret_rotation?.pitch ?? (modelDefinitions.models[b.id].initial_turret_rotation?.pitch ??
0), 0),
); );
@@ -344,13 +322,11 @@ 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)!.gun_type!.value.base.id] .guns[b.turrets.at(-1)!.guns.at(-1)!.id].pitch.min +
.pitch.min +
(modelDefinitions.models[b.id].initial_turret_rotation?.pitch ?? (modelDefinitions.models[b.id].initial_turret_rotation?.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)!.gun_type!.value.base.id] .guns[a.turrets.at(-1)!.guns.at(-1)!.id].pitch.min -
.pitch.min -
(modelDefinitions.models[a.id].initial_turret_rotation?.pitch ?? (modelDefinitions.models[a.id].initial_turret_rotation?.pitch ??
0), 0),
); );
@@ -382,14 +358,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)!.gun_type!.value.base a.turrets.at(-1)!.guns.at(-1)!.weight) -
.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)!.gun_type!.value.base.weight), b.turrets.at(-1)!.guns.at(-1)!.weight),
); );
break; break;
@@ -400,12 +375,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)!.gun_type!.value.base.weight - a.turrets.at(-1)!.guns.at(-1)!.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)!.gun_type!.value.base.weight), b.turrets.at(-1)!.guns.at(-1)!.weight),
); );
break; break;
@@ -26,6 +26,7 @@ import { AssaultRangesVisualizer } from './AssaultRangesVisualizer';
import { GunFlexibilityVisualizer } from './GunFlexibilityVisualizer'; import { GunFlexibilityVisualizer } from './GunFlexibilityVisualizer';
import { Info } from './Info'; import { Info } from './Info';
import { InfoWithDelta } from './InfoWithDelta'; import { InfoWithDelta } from './InfoWithDelta';
import { ReloadVisualizer } from './ReloadVisualizer';
import { RicochetVisualizer } from './RicochetVisualizer'; import { RicochetVisualizer } from './RicochetVisualizer';
import { StatsTableWrapper } from './StatsTableWrapper'; import { StatsTableWrapper } from './StatsTableWrapper';
import type { StatsAcceptorProps } from './TraverseVisualizer'; import type { StatsAcceptorProps } from './TraverseVisualizer';
@@ -54,8 +55,7 @@ export function Firepower({
]); ]);
const tankModelDefinition = useTankModelDefinition(); const tankModelDefinition = useTankModelDefinition();
const turretModelDefinition = tankModelDefinition.turrets[turret.id]; const turretModelDefinition = tankModelDefinition.turrets[turret.id];
const gunModelDefinition = const gunModelDefinition = turretModelDefinition.guns[gun.id];
turretModelDefinition.guns[gun.gun_type!.value.base.id];
useEffect(() => { useEffect(() => {
if (penetrationDistanceInput.current) { if (penetrationDistanceInput.current) {
@@ -71,7 +71,7 @@ export function Firepower({
</Heading> </Heading>
<Flex> <Flex>
{gun.gun_type!.value.base.shells.map((thisShell, shellIndex) => { {gun.shells.map((thisShell, shellIndex) => {
const selected = thisShell.id === shell.id; const selected = thisShell.id === shell.id;
return ( return (
@@ -84,13 +84,9 @@ export function Firepower({
borderTopLeftRadius: shellIndex === 0 ? undefined : 0, borderTopLeftRadius: shellIndex === 0 ? undefined : 0,
borderBottomLeftRadius: shellIndex === 0 ? undefined : 0, borderBottomLeftRadius: shellIndex === 0 ? undefined : 0,
borderTopRightRadius: borderTopRightRadius:
shellIndex === gun.gun_type!.value.base.shells.length - 1 shellIndex === gun.shells.length - 1 ? undefined : 0,
? undefined
: 0,
borderBottomRightRadius: borderBottomRightRadius:
shellIndex === gun.gun_type!.value.base.shells.length - 1 shellIndex === gun.shells.length - 1 ? undefined : 0,
? undefined
: 0,
marginLeft: shellIndex === 0 ? 0 : -1, marginLeft: shellIndex === 0 ? 0 : -1,
}} }}
onClick={() => { onClick={() => {
@@ -125,11 +121,8 @@ export function Firepower({
<InfoWithDelta stats={stats} decimals={0} value="moduleDamage" /> <InfoWithDelta stats={stats} decimals={0} value="moduleDamage" />
<InfoWithDelta stats={stats} decimals={0} value="penetration" /> <InfoWithDelta stats={stats} decimals={0} value="penetration" />
{gun.gun_type!.value.base.assault_ranges?.types.includes(shell.type) && ( {gun.assault_ranges?.types.includes(shell.type) && (
<AssaultRangesVisualizer <AssaultRangesVisualizer stats={stats} ranges={gun.assault_ranges} />
stats={stats}
ranges={gun.gun_type!.value.base.assault_ranges}
/>
)} )}
{gun.gun_type!.$case === 'auto_reloader' && ( {gun.gun_type!.$case === 'auto_reloader' && (
@@ -190,7 +183,7 @@ export function Firepower({
value="intraClip" value="intraClip"
/> />
)} )}
{/* <ReloadVisualizer stats={stats} /> */} <ReloadVisualizer stats={stats} />
{typeof shell.penetration !== 'number' && ( {typeof shell.penetration !== 'number' && (
<> <>
@@ -51,7 +51,7 @@ export function ModelChunk({ only }: Props) {
const tankModel = modelDefinitions.models[tank.id]; const tankModel = modelDefinitions.models[tank.id];
const turretModel = tankModel.turrets[turret.id]; const turretModel = tankModel.turrets[turret.id];
const gunModel = turretModel.guns[gun.gun_type!.value.base.id]; const gunModel = turretModel.guns[gun.id];
const originZ = tankModel.turret_origin.z + turretModel.gun_origin.z; const originZ = tankModel.turret_origin.z + turretModel.gun_origin.z;
const originY = tankModel.turret_origin.y + turretModel.gun_origin.y; const originY = tankModel.turret_origin.y + turretModel.gun_origin.y;
@@ -47,7 +47,7 @@ export function GunFlexibilityVisualizer({
const tankModel = modelDefinition.models[tank.id]; const tankModel = modelDefinition.models[tank.id];
const turretModel = tankModel.turrets[turret.id]; const turretModel = tankModel.turrets[turret.id];
const gunModel = turretModel.guns[gun.gun_type!.value.base.id]; const gunModel = turretModel.guns[gun.id];
const [yaw, setYaw] = useState(0); const [yaw, setYaw] = useState(0);
const [pitch, setPitch] = useState(0); const [pitch, setPitch] = useState(0);
@@ -61,9 +61,7 @@ export const InfoWithDelta = memo<InfoWithDeltaProps>(
const delta = useDelta(uhWhatDoICallThisVariable); const delta = useDelta(uhWhatDoICallThisVariable);
const protagonistTank = Duel.use((state) => state.protagonist.tank); const protagonistTank = Duel.use((state) => state.protagonist.tank);
const shellIndex = Duel.use((state) => const shellIndex = Duel.use((state) =>
state.protagonist.gun.gun_type!.value.base.shells.indexOf( state.protagonist.gun.shells.indexOf(state.protagonist.shell),
state.protagonist.shell,
),
); );
const equipmentMatrix = Duel.use( const equipmentMatrix = Duel.use(
(state) => state.protagonist.equipmentMatrix, (state) => state.protagonist.equipmentMatrix,
@@ -89,9 +87,7 @@ export const InfoWithDelta = memo<InfoWithDeltaProps>(
provisionDefinitions, provisionDefinitions,
); );
if ( if (member.gun.shells[shellIndex] === undefined) {
member.gun.gun_type!.value.base.shells[shellIndex] === undefined
) {
return undefined; return undefined;
} }
@@ -107,7 +103,7 @@ export const InfoWithDelta = memo<InfoWithDeltaProps>(
engine: member.engine, engine: member.engine,
gun: member.gun, gun: member.gun,
equipmentMatrix: equipmentMatrix, equipmentMatrix: equipmentMatrix,
shell: member.gun.gun_type!.value.base.shells[shellIndex], shell: member.gun.shells[shellIndex],
stockEngine: tank.engines[0], stockEngine: tank.engines[0],
stockGun: tank.turrets[0].guns[0], stockGun: tank.turrets[0].guns[0],
stockTrack: tank.tracks[0], stockTrack: tank.tracks[0],
@@ -11,7 +11,7 @@ export function ShellSwitcher() {
return ( return (
<Flex> <Flex>
{gun.gun_type!.value.base.shells.map((thisShell, shellIndex) => { {gun.shells.map((thisShell, shellIndex) => {
const selected = thisShell.id === shell.id; const selected = thisShell.id === shell.id;
return ( return (
@@ -26,9 +26,7 @@ export function ShellSwitcher() {
borderBottomLeftRadius: shellIndex === 0 ? '50%' : 0, borderBottomLeftRadius: shellIndex === 0 ? '50%' : 0,
borderTopRightRadius: 0, borderTopRightRadius: 0,
borderBottomRightRadius: borderBottomRightRadius:
shellIndex === gun.gun_type!.value.base.shells.length - 1 shellIndex === gun.shells.length - 1 ? '50%' : 0,
? '50%'
: 0,
marginLeft: shellIndex === 0 ? 0 : -1, marginLeft: shellIndex === 0 ? 0 : -1,
}} }}
onClick={() => { onClick={() => {
@@ -41,8 +41,7 @@ export function Characteristics({ skeleton }: MaybeSkeletonComponentProps) {
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 = const gunModelDefinition = turretModelDefinition.guns[gun.id];
turretModelDefinition.guns[gun.gun_type!.value.base.id];
const stats = tankCharacteristics( const stats = tankCharacteristics(
{ {
tank, tank,
@@ -33,7 +33,7 @@ type ModuleDefinition =
function extractModule(raw: ModuleDefinition | TankDefinition) { function extractModule(raw: ModuleDefinition | TankDefinition) {
if ('gun_type' in raw) { if ('gun_type' in raw) {
return raw.gun_type!.value.base; return raw;
} else } else
return raw as Exclude<ModuleDefinition | TankDefinition, GunDefinition>; return raw as Exclude<ModuleDefinition | TankDefinition, GunDefinition>;
} }
@@ -179,37 +179,31 @@ export function Modules() {
if ( if (
!draft.protagonist.turret.guns.some( !draft.protagonist.turret.guns.some(
(gun) => (gun) => gun.id === draft.protagonist.gun.id,
gun.gun_type!.value.base.id ===
draft.protagonist.gun.gun_type!.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.shell = draft.protagonist.gun.shells[0];
draft.protagonist.gun.gun_type!.value.base.shells[0];
} }
} else if (unlock.type === ModuleType.GUN) { } else if (unlock.type === ModuleType.GUN) {
const gunInTurret = draft.protagonist.turret.guns.find( const gunInTurret = draft.protagonist.turret.guns.find(
(gun) => gun.gun_type!.value.base.id === unlock.id, (gun) => gun.id === unlock.id,
); );
if (gunInTurret) { if (gunInTurret) {
draft.protagonist.gun = gunInTurret; draft.protagonist.gun = gunInTurret;
draft.protagonist.shell = gunInTurret.gun_type!.value.base.shells[0]; draft.protagonist.shell = gunInTurret.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( turret.guns.some((gun) => gun.id === unlock.id),
(gun) => gun.gun_type!.value.base.id === unlock.id,
),
)!; )!;
const gunInSuitableTurret = suitableTurret.guns.find( const gunInSuitableTurret = suitableTurret.guns.find(
(gun) => gun.gun_type!.value.base.id === unlock.id, (gun) => gun.id === unlock.id,
)!; )!;
draft.protagonist.turret = suitableTurret; draft.protagonist.turret = suitableTurret;
draft.protagonist.gun = gunInSuitableTurret; draft.protagonist.gun = gunInSuitableTurret;
draft.protagonist.shell = draft.protagonist.shell = gunInSuitableTurret.shells[0];
gunInSuitableTurret.gun_type!.value.base.shells[0];
} }
} else if (unlock.type === ModuleType.ENGINE) { } else if (unlock.type === ModuleType.ENGINE) {
draft.protagonist.engine = draft.protagonist.tank.engines.find( draft.protagonist.engine = draft.protagonist.tank.engines.find(
@@ -241,11 +235,9 @@ export function Modules() {
} else if (unlock.type === ModuleType.GUN) { } else if (unlock.type === ModuleType.GUN) {
module = tank.turrets module = tank.turrets
.find((turret) => .find((turret) =>
turret.guns.some( turret.guns.some((gun) => gun.id === unlock.id),
(gun) => gun.gun_type!.value.base.id === unlock.id,
),
)! )!
.guns.find((gun) => gun.gun_type!.value.base.id === unlock.id)!; .guns.find((gun) => gun.id === unlock.id)!;
} else if (unlock.type === ModuleType.VEHICLE) { } else if (unlock.type === ModuleType.VEHICLE) {
module = tankDefinitions.tanks[unlock.id]; module = tankDefinitions.tanks[unlock.id];
} }
@@ -308,7 +300,7 @@ export function Modules() {
(unlock.type === ModuleType.TURRET (unlock.type === ModuleType.TURRET
? turret.id ? turret.id
: unlock.type === ModuleType.GUN : unlock.type === ModuleType.GUN
? gun.gun_type!.value.base.id ? gun.id
: unlock.type === ModuleType.ENGINE : unlock.type === ModuleType.ENGINE
? engine.id ? engine.id
: unlock.type === ModuleType.TRACKS : unlock.type === ModuleType.TRACKS
@@ -319,8 +311,7 @@ export function Modules() {
unlock.type === ModuleType.TURRET unlock.type === ModuleType.TURRET
? extractModule(module).id == topTurret.id ? extractModule(module).id == topTurret.id
: unlock.type === ModuleType.GUN : unlock.type === ModuleType.GUN
? extractModule(module).id === ? extractModule(module).id === topGun.id
topGun.gun_type!.value.base.id
: unlock.type === ModuleType.ENGINE : unlock.type === ModuleType.ENGINE
? extractModule(module).id === topEngine.id ? extractModule(module).id === topEngine.id
: unlock.type === ModuleType.TRACKS : unlock.type === ModuleType.TRACKS
@@ -371,8 +362,7 @@ 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.shell = draft.protagonist.gun.shells[0];
draft.protagonist.gun.gun_type!.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];
}); });
@@ -387,8 +377,7 @@ 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.shell = draft.protagonist.gun.shells[0];
draft.protagonist.gun.gun_type!.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 =
@@ -427,7 +416,7 @@ export function Modules() {
{tree(ModuleType.GUN, [ {tree(ModuleType.GUN, [
{ {
cost: { type: 'xp', value: 0 }, cost: { type: 'xp', value: 0 },
id: gun0.gun_type!.value.base.id, id: gun0.id,
type: ModuleType.GUN, type: ModuleType.GUN,
}, },
])} ])}
@@ -90,7 +90,7 @@ export function CustomShellButton() {
modified.normalization = undefined; modified.normalization = undefined;
modified.ricochet = undefined; modified.ricochet = undefined;
modified.explosion_radius = modified.explosion_radius =
gun.gun_type!.value.base.shells.find( gun.shells.find(
(shell) => shell.type === ShellType.HE, (shell) => shell.type === ShellType.HE,
)?.explosion_radius ?? 1; )?.explosion_radius ?? 1;
break; break;
@@ -21,8 +21,7 @@ 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 = const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id];
turretModelDefinition.guns[protagonist.gun.gun_type!.value.base.id];
const initialGunPitch = const initialGunPitch =
tankModelDefinition.initial_turret_rotation?.pitch ?? 0; tankModelDefinition.initial_turret_rotation?.pitch ?? 0;
const hasImprovedVerticalStabilizer = useEquipment(122); const hasImprovedVerticalStabilizer = useEquipment(122);
@@ -141,7 +141,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
mr="2" mr="2"
mb="1" mb="1"
> >
{TIER_ROMAN_NUMERALS[antagonistGun.gun_type!.value.base.tier]} {TIER_ROMAN_NUMERALS[antagonistGun.tier]}
</Text> </Text>
<img <img
@@ -161,26 +161,20 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
{[...antagonistUniqueGuns].reverse().map(({ gun, turret }) => ( {[...antagonistUniqueGuns].reverse().map(({ gun, turret }) => (
<ModuleButton <ModuleButton
module="gun" module="gun"
key={gun.gun_type!.value.base.id} key={gun.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 = draft.antagonist.shell = gun.shells[0];
gun.gun_type!.value.base.shells[0];
}) })
} }
selected={ selected={gun.id === antagonistGun.id}
gun.gun_type!.value.base.id === discriminator={TIER_ROMAN_NUMERALS[gun.tier]}
antagonistGun.gun_type!.value.base.id
}
discriminator={
TIER_ROMAN_NUMERALS[gun.gun_type!.value.base.tier]
}
secondaryDiscriminator={ secondaryDiscriminator={
<Text style={{ fontSize: '0.75em' }}> <Text style={{ fontSize: '0.75em' }}>
{literals(strings.common.units.mm, [ {literals(strings.common.units.mm, [
gun.gun_type!.value.base.shells[0].caliber.toFixed(0), gun.shells[0].caliber.toFixed(0),
])} ])}
</Text> </Text>
} }
@@ -198,7 +192,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
}} }}
overflow="hidden" overflow="hidden"
> >
{antagonistGun.gun_type!.value.base.shells.map((thisShell) => ( {antagonistGun.shells.map((thisShell) => (
<IconButton <IconButton
color={ color={
thisShell.id === antagonistShell.id && !hasCustomShell thisShell.id === antagonistShell.id && !hasCustomShell
@@ -367,8 +361,7 @@ export function Options({ thicknessRange, canvas, skeleton }: 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.shell = draft.antagonist.gun.shells[0];
draft.antagonist.gun.gun_type!.value.base.shells[0];
}); });
setAntagonistSelectorOpen(false); setAntagonistSelectorOpen(false);
}} }}
@@ -56,9 +56,7 @@ export function Controls({
const antagonistTurretModelDefinition = const antagonistTurretModelDefinition =
antagonistModelDefinition.turrets[antagonistTurret.id]; antagonistModelDefinition.turrets[antagonistTurret.id];
const protagonistGunModelDefinition = const protagonistGunModelDefinition =
protagonistTurretModelDefinition.guns[ protagonistTurretModelDefinition.guns[protagonistGun.id];
protagonistGun.gun_type!.value.base.id
];
const protagonistHullOrigin = new Vector3( const protagonistHullOrigin = new Vector3(
protagonistTrackModelDefinition.origin.x, protagonistTrackModelDefinition.origin.x,
protagonistTrackModelDefinition.origin.y, protagonistTrackModelDefinition.origin.y,
@@ -14,7 +14,7 @@ export function InitialAligner() {
const tankModel = modelDefinitions.models[tank.id]; const tankModel = modelDefinitions.models[tank.id];
const turretModel = tankModel.turrets[turret.id]; const turretModel = tankModel.turrets[turret.id];
const gunModel = turretModel.guns[gun.gun_type!.value.base.id]; const gunModel = turretModel.guns[gun.id];
useEffect(() => { useEffect(() => {
const [pitch, yaw] = applyPitchYawLimits( const [pitch, yaw] = applyPitchYawLimits(
@@ -49,7 +49,7 @@ export function SceneProps() {
const trackModelDefinition = const trackModelDefinition =
modelDefinitions.models[tank.id].tracks[track.id]; modelDefinitions.models[tank.id].tracks[track.id];
const gunModelDefinition = const gunModelDefinition =
tankModelDefinition.turrets[turret.id].guns[gun.gun_type!.value.base.id]; tankModelDefinition.turrets[turret.id].guns[gun.id];
const turretModelDefinition = tankModelDefinition.turrets[turret.id]; const turretModelDefinition = tankModelDefinition.turrets[turret.id];
const hullOrigin = new Vector3( const hullOrigin = new Vector3(
trackModelDefinition.origin.x, trackModelDefinition.origin.x,
@@ -28,8 +28,7 @@ export function TankModel() {
const tankModelDefinition = useTankModelDefinition(); const tankModelDefinition = useTankModelDefinition();
const turretModelDefinition = const turretModelDefinition =
tankModelDefinition.turrets[protagonist.turret.id]; tankModelDefinition.turrets[protagonist.turret.id];
const gunModelDefinition = const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id];
turretModelDefinition.guns[protagonist.gun.gun_type!.value.base.id];
const { gltf } = useModel(protagonist.tank.id); const { gltf } = useModel(protagonist.tank.id);
const nodes = Object.values(gltf.nodes); const nodes = Object.values(gltf.nodes);
@@ -31,8 +31,7 @@ export function TransitionSkeleton() {
const tankModelDefinition = useTankModelDefinition(); const tankModelDefinition = useTankModelDefinition();
const turretModelDefinition = const turretModelDefinition =
tankModelDefinition.turrets[protagonist.turret.id]; tankModelDefinition.turrets[protagonist.turret.id];
const gunModelDefinition = const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id];
turretModelDefinition.guns[protagonist.gun.gun_type!.value.base.id];
const { gltf } = useModel(protagonist.tank.id); const { gltf } = useModel(protagonist.tank.id);
const nodes = Object.values(gltf.nodes); const nodes = Object.values(gltf.nodes);
const revealed = tankopediaEphemeralStore((state) => state.revealed); const revealed = tankopediaEphemeralStore((state) => state.revealed);
@@ -59,8 +59,7 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
const tankModelDefinition = useTankModelDefinition(); const tankModelDefinition = useTankModelDefinition();
const turretModelDefinition = const turretModelDefinition =
tankModelDefinition.turrets[protagonistTurret.id]; tankModelDefinition.turrets[protagonistTurret.id];
const gunModelDefinition = const gunModelDefinition = turretModelDefinition.guns[protagonistGun.id];
turretModelDefinition.guns[protagonistGun.gun_type!.value.base.id];
const rawDisplay = TankopediaEphemeral.use((state) => state.display); const rawDisplay = TankopediaEphemeral.use((state) => state.display);
const [display, setDisplay] = useState(rawDisplay); const [display, setDisplay] = useState(rawDisplay);
const hideTankModelUnderArmor = TankopediaPersistent.use( const hideTankModelUnderArmor = TankopediaPersistent.use(
@@ -29,8 +29,7 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
(filtered.reduce((accumulator, thisTank) => { (filtered.reduce((accumulator, thisTank) => {
return ( return (
accumulator + accumulator +
thisTank.turrets.at(-1)!.guns.at(-1)!.gun_type!.value.base.shells[0] thisTank.turrets.at(-1)!.guns.at(-1)!.shells[0].penetration.near
.penetration.near
); );
}, 0) / }, 0) /
filtered.length) * filtered.length) *
@@ -51,8 +50,7 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
useEffect(() => { useEffect(() => {
function handleKeyDown(event: KeyboardEvent) { function handleKeyDown(event: KeyboardEvent) {
const { shells } = const { shells } = duelStore.getState().antagonist.gun;
duelStore.getState().antagonist.gun.gun_type!.value.base;
times(3, (index) => { times(3, (index) => {
if (event.key === `${index + 1}` && shells.length > index) { if (event.key === `${index + 1}` && shells.length > index) {
@@ -31,9 +31,8 @@ export function filterTank(filters: TankFilters, tank: TankDefinition) {
SHELLS.every( SHELLS.every(
(index) => (index) =>
filters.shells[index] === null || filters.shells[index] === null ||
gun.gun_type!.value.base.shells[index] === undefined || gun.shells[index] === undefined ||
gun.gun_type!.value.base.shells[index].type === gun.shells[index].type === filters.shells[index],
filters.shells[index],
), ),
), ),
) )
@@ -2,18 +2,16 @@ import type { GunDefinition } from '@blitzkit/core';
export function resolveReload(gun: GunDefinition) { export function resolveReload(gun: GunDefinition) {
if (gun.gun_type!.$case === 'regular') { if (gun.gun_type!.$case === 'regular') {
return gun.gun_type!.value.extension.reload; return gun.gun_type!.value.reload;
} else if (gun.gun_type!.$case === 'auto_loader') { } else if (gun.gun_type!.$case === 'auto_loader') {
return ( return (
gun.gun_type!.value.extension.clip_reload + gun.gun_type!.value.clip_reload +
(gun.gun_type!.value.extension.shell_count - 1) * (gun.gun_type!.value.shell_count - 1) * gun.gun_type!.value.intra_clip
gun.gun_type!.value.extension.intra_clip
); );
} else { } else {
return ( return (
gun.gun_type!.value.extension.shell_reloads.reduce((a, b) => a + b, 0) + gun.gun_type!.value.shell_reloads.reduce((a, b) => a + b, 0) +
(gun.gun_type!.value.extension.shell_count - 1) * (gun.gun_type!.value.shell_count - 1) * gun.gun_type!.value.intra_clip
gun.gun_type!.value.extension.intra_clip
); );
} }
} }
@@ -80,8 +80,7 @@ export function tankCharacteristics(
) { ) {
const preset = equipmentDefinitions.presets[tank.equipment_preset]; const preset = equipmentDefinitions.presets[tank.equipment_preset];
const turretModelDefinition = tankModelDefinition.turrets[turret.id]; const turretModelDefinition = tankModelDefinition.turrets[turret.id];
const gunModelDefinition = const gunModelDefinition = turretModelDefinition.guns[gun.id];
turretModelDefinition.guns[gun.gun_type!.value.base.id];
function equipment(id: number) { function equipment(id: number) {
return preset.slots.some((slot, index) => { return preset.slots.some((slot, index) => {
@@ -160,9 +159,8 @@ export function tankCharacteristics(
[applySpallLiner && shell.type === ShellType.HE, -0.2], [applySpallLiner && shell.type === ShellType.HE, -0.2],
); );
const assaultDamageCoefficient = const assaultDamageCoefficient =
gun.gun_type!.value.base.assault_ranges && gun.assault_ranges && gun.assault_ranges.types.includes(shell.type)
gun.gun_type!.value.base.assault_ranges.types.includes(shell.type) ? (gun.assault_ranges.ranges.find(
? (gun.gun_type!.value.base.assault_ranges.ranges.find(
({ distance }) => distance >= assaultDistance, ({ distance }) => distance >= assaultDistance,
)?.factor ?? 0) )?.factor ?? 0)
: 1; : 1;
@@ -322,18 +320,14 @@ export function tankCharacteristics(
), ),
); );
const weightKg = const weightKg =
tank.weight + tank.weight + engine.weight + track.weight + turret.weight + gun.weight;
engine.weight +
track.weight +
turret.weight +
gun.gun_type!.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.gun_type!.value.base.weight; stockGun.weight;
const resolvedEnginePower = engine.power * enginePowerCoefficient; const resolvedEnginePower = engine.power * enginePowerCoefficient;
const damageCoefficientWithoutAssault = armorDamageCoefficient; const damageCoefficientWithoutAssault = armorDamageCoefficient;
const damageCoefficient = const damageCoefficient =
@@ -348,10 +342,10 @@ export function tankCharacteristics(
const intraClip = const intraClip =
gun.gun_type?.$case === 'regular' gun.gun_type?.$case === 'regular'
? undefined ? undefined
: gun.gun_type!.value.extension.intra_clip * intraClipCoefficient; : gun.gun_type!.value.intra_clip * intraClipCoefficient;
const mostOptimalShellIndex = const mostOptimalShellIndex =
gun.gun_type!.$case === 'auto_reloader' gun.gun_type!.$case === 'auto_reloader'
? gun.gun_type!.value.extension.shell_reloads.reduce<null | { ? gun.gun_type!.value.shell_reloads.reduce<null | {
index: number; index: number;
reload: number; reload: number;
}>((current, reloadRaw, index) => { }>((current, reloadRaw, index) => {
@@ -369,43 +363,34 @@ export function tankCharacteristics(
const damage = shell.armor_damage * damageCoefficient; const damage = shell.armor_damage * damageCoefficient;
const dpmEffective = const dpmEffective =
gun.gun_type!.$case === 'auto_reloader' gun.gun_type!.$case === 'auto_reloader'
? gun.gun_type!.value.extension.shell_reloads[0] > ? gun.gun_type!.value.shell_reloads[0] >
gun.gun_type!.value.extension.shell_reloads[1] + gun.gun_type!.value.shell_reloads[1] + gun.gun_type!.value.intra_clip
gun.gun_type!.value.extension.intra_clip
? ((armorDamageCoefficient * shell.armor_damage) / ? ((armorDamageCoefficient * shell.armor_damage) /
(gun.gun_type!.value.extension.shell_reloads.at(-1)! * (gun.gun_type!.value.shell_reloads.at(-1)! * reloadCoefficient +
reloadCoefficient + gun.gun_type!.value.intra_clip * intraClipCoefficient)) *
gun.gun_type!.value.extension.intra_clip *
intraClipCoefficient)) *
(60 - (60 -
(gun.gun_type!.value.extension.shell_reloads.slice(0, -1).length - (gun.gun_type!.value.shell_reloads.slice(0, -1).length - 1) *
1) * gun.gun_type!.value.intra_clip *
gun.gun_type!.value.extension.intra_clip *
intraClipCoefficient) + intraClipCoefficient) +
armorDamageCoefficient * armorDamageCoefficient *
shell.armor_damage * shell.armor_damage *
gun.gun_type!.value.extension.shell_reloads.slice(0, -1).length gun.gun_type!.value.shell_reloads.slice(0, -1).length
: ((armorDamageCoefficient * shell.armor_damage) / : ((armorDamageCoefficient * shell.armor_damage) /
(gun.gun_type!.value.extension.shell_reloads[0] * (gun.gun_type!.value.shell_reloads[0] * reloadCoefficient +
reloadCoefficient + gun.gun_type!.value.intra_clip * intraClipCoefficient)) *
gun.gun_type!.value.extension.intra_clip *
intraClipCoefficient)) *
(60 - (60 -
(gun.gun_type!.value.extension.shell_reloads.slice(1).length - (gun.gun_type!.value.shell_reloads.slice(1).length - 1) *
1) * gun.gun_type!.value.intra_clip *
gun.gun_type!.value.extension.intra_clip *
intraClipCoefficient) + intraClipCoefficient) +
armorDamageCoefficient * armorDamageCoefficient *
shell.armor_damage * shell.armor_damage *
gun.gun_type!.value.extension.shell_reloads.slice(1).length gun.gun_type!.value.shell_reloads.slice(1).length
: undefined; : undefined;
const shells = const shells =
gun.gun_type!.$case === 'regular' gun.gun_type!.$case === 'regular' ? 1 : gun.gun_type!.value.shell_count;
? 1
: gun.gun_type!.value.extension.shell_count;
const shellReloads = const shellReloads =
gun.gun_type!.$case === 'auto_reloader' gun.gun_type!.$case === 'auto_reloader'
? gun.gun_type!.value.extension.shell_reloads.map( ? gun.gun_type!.value.shell_reloads.map(
(reload) => reload * reloadCoefficient, (reload) => reload * reloadCoefficient,
) )
: undefined; : undefined;
@@ -413,30 +398,28 @@ export function tankCharacteristics(
gun.gun_type!.$case === 'auto_reloader' gun.gun_type!.$case === 'auto_reloader'
? undefined ? undefined
: (gun.gun_type!.$case === 'regular' : (gun.gun_type!.$case === 'regular'
? gun.gun_type!.value.extension.reload ? gun.gun_type!.value.reload
: gun.gun_type!.value.extension.clip_reload) * reloadCoefficient; : gun.gun_type!.value.clip_reload) * reloadCoefficient;
const caliber = shell.caliber; const caliber = shell.caliber;
const penetration = shell.penetration.near * penetrationCoefficient; const penetration = shell.penetration.near * penetrationCoefficient;
const clipDamage = const clipDamage =
gun.gun_type!.$case === 'regular' gun.gun_type!.$case === 'regular'
? undefined ? undefined
: gun.gun_type!.value.extension.shell_count * damage; : gun.gun_type!.value.shell_count * damage;
const moduleDamage = shell.module_damage * moduleDamageCoefficient; const moduleDamage = shell.module_damage * moduleDamageCoefficient;
const explosionRadius = shell.explosion_radius; const explosionRadius = shell.explosion_radius;
const shellVelocity = shell.velocity * shellVelocityCoefficient; const shellVelocity = shell.velocity * shellVelocityCoefficient;
const aimTime = gun.gun_type!.value.base.aim_time * aimTimeCoefficient; const aimTime = gun.aim_time * aimTimeCoefficient;
const dispersion = const dispersion = gun.dispersion_base * dispersionStillCoefficient;
gun.gun_type!.value.base.dispersion_base * dispersionStillCoefficient;
const dispersionMoving = track.dispersion_move * dispersionMovingCoefficient; const dispersionMoving = track.dispersion_move * dispersionMovingCoefficient;
const dispersionHullTraversing = const dispersionHullTraversing =
track.dispersion_traverse * dispersionHullTraverseCoefficient; track.dispersion_traverse * dispersionHullTraverseCoefficient;
const dispersionTurretTraversing = const dispersionTurretTraversing =
gun.gun_type!.value.base.dispersion_traverse * gun.dispersion_traverse * dispersionTurretTraverseCoefficient;
dispersionTurretTraverseCoefficient;
const dispersionShooting = const dispersionShooting =
gun.gun_type!.value.base.dispersion_shot * dispersionAfterShotCoefficient; gun.dispersion_shot * dispersionAfterShotCoefficient;
const dispersionGunDamaged = const dispersionGunDamaged =
gun.gun_type!.value.base.dispersion_damaged * dispersionDamagedCoefficient; gun.dispersion_damaged * dispersionDamagedCoefficient;
const gunDepression = const gunDepression =
gunModelDefinition.pitch.max + gunModelDefinition.pitch.max +
gunDefaultPitch + gunDefaultPitch +
@@ -478,8 +461,7 @@ export function tankCharacteristics(
resolvedEnginePower / weightTons / softTerrainCoefficientRaw; resolvedEnginePower / weightTons / softTerrainCoefficientRaw;
const weight = weightTons; const weight = weightTons;
const turretTraverseSpeed = turret.traverse_speed * turretTraverseCoefficient; const turretTraverseSpeed = turret.traverse_speed * turretTraverseCoefficient;
const gunTraverseSpeed = const gunTraverseSpeed = gun.rotation_speed * turretTraverseCoefficient;
gun.gun_type!.value.base.rotation_speed * turretTraverseCoefficient;
/** /**
* resistance_hard is used in all cases as this is the coefficient between * resistance_hard is used in all cases as this is the coefficient between
* the absolute best coefficient and the current * the absolute best coefficient and the current
@@ -511,14 +493,12 @@ export function tankCharacteristics(
? tank.camouflage_still * camouflageCoefficientStill ? tank.camouflage_still * camouflageCoefficientStill
: tank.camouflage_moving * camouflageCoefficientMoving; : tank.camouflage_moving * camouflageCoefficientMoving;
const camouflageShootingStill = const camouflageShootingStill =
tank.camouflage_still * tank.camouflage_still * gun.camouflage_loss * camouflageCoefficientStill;
gun.gun_type!.value.base.camouflage_loss *
camouflageCoefficientStill;
const camouflageShootingMoving = const camouflageShootingMoving =
(tank.class === TankClass.LIGHT (tank.class === TankClass.LIGHT
? tank.camouflage_still * camouflageCoefficientStill ? tank.camouflage_still * camouflageCoefficientStill
: tank.camouflage_moving * camouflageCoefficientMoving) * : tank.camouflage_moving * camouflageCoefficientMoving) *
gun.gun_type!.value.base.camouflage_loss; gun.camouflage_loss;
const camouflageCaughtOnFire = const camouflageCaughtOnFire =
tank.camouflage_onFire * tank.camouflage_still * camouflageCoefficientStill; tank.camouflage_onFire * tank.camouflage_still * camouflageCoefficientStill;
const width = size.z; const width = size.z;
@@ -529,7 +509,7 @@ export function tankCharacteristics(
const shellRicochet = isExplosive(shell.type) ? undefined : shell.ricochet; const shellRicochet = isExplosive(shell.type) ? undefined : shell.ricochet;
const crewCount = tank.crew.length; const crewCount = tank.crew.length;
const shellRange = shell.range; const shellRange = shell.range;
const shellCapacity = gun.gun_type!.value.base.shell_capacity; const shellCapacity = gun.shell_capacity;
const gunType = gun.gun_type!.$case; const gunType = gun.gun_type!.$case;
const penetrationAt250m = shell.penetration.far; const penetrationAt250m = shell.penetration.far;
@@ -23,11 +23,10 @@ export function tankToDuelMember(
provisions: createDefaultProvisions(tank, gun, provisionDefinitions), provisions: createDefaultProvisions(tank, gun, provisionDefinitions),
engine: tank.engines.at(-1)!, engine: tank.engines.at(-1)!,
gun, gun,
shell: tank.turrets.at(-1)!.guns.at(-1)!.gun_type!.value.base.shells[0], shell: tank.turrets.at(-1)!.guns.at(-1)!.shells[0],
tank, tank,
track: tank.tracks.at(-1)!, track: tank.tracks.at(-1)!,
turret, turret,
assaultDistance: assaultDistance: (gun.assault_ranges?.ranges[0].distance ?? 0) / 2,
(gun.gun_type!.value.base.assault_ranges?.ranges[0].distance ?? 0) / 2,
} satisfies DuelMember; } satisfies DuelMember;
} }
@@ -64,7 +64,7 @@ const url = import.meta.env.SITE + Astro.url.pathname;
const icon = asset(`icons/tanks/big/${id}.webp`); const icon = asset(`icons/tanks/big/${id}.webp`);
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 shell = gun.gun_type!.value.base.shells[0]; const shell = gun.shells[0];
const characteristics = tankCharacteristics( const characteristics = tankCharacteristics(
{ {
tank, tank,