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