mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-25 03:29:51 +01:00
fix: use non-compounding percentages
This commit is contained in:
1 parent
2c8c04387b
commit
9174f3c9d0
11 files changed
+279
-215
No files matched your search
@@ -975,25 +975,10 @@ export async function definitions(production: boolean) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.entries({
|
if (provision.script.bonusValues?.crewLevelIncrease !== undefined) {
|
||||||
...provision.script,
|
provisionDefinitions[provision.id].crew =
|
||||||
...provision.script.bonusValues,
|
provision.script.bonusValues?.crewLevelIncrease;
|
||||||
}).forEach(([effectName, effect]) => {
|
}
|
||||||
if (
|
|
||||||
!consumableProvisionEffectSuffixes.some((suffix) =>
|
|
||||||
effectName.endsWith(suffix),
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!provisionDefinitions[provision.id].effects) {
|
|
||||||
provisionDefinitions[provision.id].effects = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
provisionDefinitions[provision.id].effects![effectName] =
|
|
||||||
effect as number;
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await commitAssets(
|
await commitAssets(
|
||||||
|
|||||||
@@ -37,9 +37,6 @@ export default function RootLayout({ children }: RootLayoutProps) {
|
|||||||
style={{
|
style={{
|
||||||
margin: 0,
|
margin: 0,
|
||||||
paddingTop: isEmbed ? 0 : '3.25rem',
|
paddingTop: isEmbed ? 0 : '3.25rem',
|
||||||
background: isEmbed
|
|
||||||
? 'transparent'
|
|
||||||
: 'url(https://i.imgur.com/PhS06NJ.png)',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Theme
|
<Theme
|
||||||
|
|||||||
+41
-42
@@ -39,6 +39,47 @@ export function Miscellaneous() {
|
|||||||
<Heading size="4">Miscellaneous</Heading>
|
<Heading size="4">Miscellaneous</Heading>
|
||||||
|
|
||||||
<Flex gap="2" align="center">
|
<Flex gap="2" align="center">
|
||||||
|
<GenericTankComponentButton
|
||||||
|
icon={asset('icons/camo.webp')}
|
||||||
|
selected={camouflage}
|
||||||
|
iconStyles={{
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translate(calc(-50% + 4px), calc(-50% + 4px))',
|
||||||
|
}}
|
||||||
|
first
|
||||||
|
last
|
||||||
|
onClick={() => {
|
||||||
|
mutateTankopediaTemporary((draft) => {
|
||||||
|
draft.camouflage = !camouflage;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<GenericTankComponentButton
|
||||||
|
icon={asset('icons/boosters/equipment.webp')}
|
||||||
|
selected={cooldownBooster > 0}
|
||||||
|
first
|
||||||
|
last
|
||||||
|
banner={
|
||||||
|
cooldownBooster === 0
|
||||||
|
? undefined
|
||||||
|
: cooldownBooster === 1
|
||||||
|
? '#afb0abc0'
|
||||||
|
: cooldownBooster === 2
|
||||||
|
? '#5f72cbc0'
|
||||||
|
: '#9b3cc0c0'
|
||||||
|
}
|
||||||
|
iconStyles={{
|
||||||
|
transform: 'translate(-50%, -50%) scale(0.8)',
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
mutateTankopediaTemporary((draft) => {
|
||||||
|
draft.cooldownBooster++;
|
||||||
|
if (draft.cooldownBooster === 4) draft.cooldownBooster = 0;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
<Flex
|
<Flex
|
||||||
align="center"
|
align="center"
|
||||||
direction="column"
|
direction="column"
|
||||||
@@ -84,48 +125,6 @@ export function Miscellaneous() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<GenericTankComponentButton
|
|
||||||
icon={asset('icons/camo.webp')}
|
|
||||||
selected={camouflage}
|
|
||||||
iconStyles={{
|
|
||||||
top: '50%',
|
|
||||||
left: '50%',
|
|
||||||
transform: 'translate(calc(-50% + 4px), calc(-50% + 4px))',
|
|
||||||
}}
|
|
||||||
first
|
|
||||||
last
|
|
||||||
onClick={() => {
|
|
||||||
mutateTankopediaTemporary((draft) => {
|
|
||||||
draft.camouflage = !camouflage;
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<GenericTankComponentButton
|
|
||||||
icon={asset('icons/boosters/equipment.webp')}
|
|
||||||
selected={cooldownBooster > 0}
|
|
||||||
first
|
|
||||||
last
|
|
||||||
banner={
|
|
||||||
cooldownBooster === 0
|
|
||||||
? undefined
|
|
||||||
: cooldownBooster === 1
|
|
||||||
? '#afb0abc0'
|
|
||||||
: cooldownBooster === 2
|
|
||||||
? '#5f72cbc0'
|
|
||||||
: '#9b3cc0c0'
|
|
||||||
}
|
|
||||||
iconStyles={{
|
|
||||||
transform: 'translate(-50%, -50%) scale(0.8)',
|
|
||||||
}}
|
|
||||||
onClick={() => {
|
|
||||||
mutateTankopediaTemporary((draft) => {
|
|
||||||
draft.cooldownBooster++;
|
|
||||||
if (draft.cooldownBooster === 4) draft.cooldownBooster = 0;
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
</ConfigurationChildWrapper>
|
</ConfigurationChildWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,20 +1,24 @@
|
|||||||
|
import { AccessibilityIcon } from '@radix-ui/react-icons';
|
||||||
import { Flex, Heading, Slider, Text, TextField } from '@radix-ui/themes';
|
import { Flex, Heading, Slider, Text, TextField } from '@radix-ui/themes';
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
import { use, useEffect, useRef, useState } from 'react';
|
import { use, useEffect, useRef, useState } from 'react';
|
||||||
import { lerp } from 'three/src/math/MathUtils';
|
import { lerp } from 'three/src/math/MathUtils';
|
||||||
import { isExplosive } from '../../../../../../core/blitz/isExplosive';
|
import { isExplosive } from '../../../../../../core/blitz/isExplosive';
|
||||||
import { resolveNearPenetration } from '../../../../../../core/blitz/resolveNearPenetration';
|
import { resolveNearPenetration } from '../../../../../../core/blitz/resolveNearPenetration';
|
||||||
|
import { coefficient } from '../../../../../../core/blitzkrieg/coefficient';
|
||||||
import { modelDefinitions } from '../../../../../../core/blitzkrieg/modelDefinitions';
|
import { modelDefinitions } from '../../../../../../core/blitzkrieg/modelDefinitions';
|
||||||
import { normalizeBoundingBox } from '../../../../../../core/blitzkrieg/normalizeBoundingBox';
|
import { normalizeBoundingBox } from '../../../../../../core/blitzkrieg/normalizeBoundingBox';
|
||||||
import { resolveDpm } from '../../../../../../core/blitzkrieg/resolveDpm';
|
import { resolveDpm } from '../../../../../../core/blitzkrieg/resolveDpm';
|
||||||
|
import { sum } from '../../../../../../core/blitzkrieg/sum';
|
||||||
import {
|
import {
|
||||||
CREW_MEMBER_NAMES,
|
CREW_MEMBER_NAMES,
|
||||||
CREW_MEMBER_NAMES_PLURAL,
|
|
||||||
GUN_TYPE_NAMES,
|
GUN_TYPE_NAMES,
|
||||||
} from '../../../../../../core/blitzkrieg/tankDefinitions';
|
} from '../../../../../../core/blitzkrieg/tankDefinitions';
|
||||||
import { unionBoundingBox } from '../../../../../../core/blitzkrieg/unionBoundingBox';
|
import { unionBoundingBox } from '../../../../../../core/blitzkrieg/unionBoundingBox';
|
||||||
import { useConsumable } from '../../../../../../core/blitzkrieg/useConsumable';
|
import { useConsumable } from '../../../../../../core/blitzkrieg/useConsumable';
|
||||||
import { useEquipment } from '../../../../../../core/blitzkrieg/useEquipment';
|
import { useEquipment } from '../../../../../../core/blitzkrieg/useEquipment';
|
||||||
|
import { useProvision } from '../../../../../../core/blitzkrieg/useProvision';
|
||||||
|
import { useProvisions } from '../../../../../../core/blitzkrieg/useProvisions';
|
||||||
import { useDuel } from '../../../../../../stores/duel';
|
import { useDuel } from '../../../../../../stores/duel';
|
||||||
import { useTankopediaTemporary } from '../../../../../../stores/tankopedia';
|
import { useTankopediaTemporary } from '../../../../../../stores/tankopedia';
|
||||||
import { Info } from './components/Info';
|
import { Info } from './components/Info';
|
||||||
@@ -49,6 +53,8 @@ export function Characteristics() {
|
|||||||
stockTurret.weight +
|
stockTurret.weight +
|
||||||
stockGun.weight;
|
stockGun.weight;
|
||||||
const [penetrationDistance, setPenetrationDistance] = useState(250);
|
const [penetrationDistance, setPenetrationDistance] = useState(250);
|
||||||
|
const provisions = useProvisions();
|
||||||
|
const camouflage = useTankopediaTemporary((state) => state.camouflage);
|
||||||
const hasRammer = useEquipment(100);
|
const hasRammer = useEquipment(100);
|
||||||
const hasCalibratedShells = useEquipment(103);
|
const hasCalibratedShells = useEquipment(103);
|
||||||
const hasEnhancedGunLayingDrive = useEquipment(104);
|
const hasEnhancedGunLayingDrive = useEquipment(104);
|
||||||
@@ -69,41 +75,131 @@ export function Characteristics() {
|
|||||||
const hasTungsten = useConsumable(45);
|
const hasTungsten = useConsumable(45);
|
||||||
const hasReticleCalibration = useConsumable(28);
|
const hasReticleCalibration = useConsumable(28);
|
||||||
const hasShellReloadBoost = useConsumable(29);
|
const hasShellReloadBoost = useConsumable(29);
|
||||||
const camouflage = useTankopediaTemporary((state) => state.camouflage);
|
const hasStandardFuel = useProvision(18);
|
||||||
const camouflageBonus = camouflage
|
const hasImprovedFuel = useProvision(19);
|
||||||
? tank.type === 'tankDestroyer'
|
const hasProtectiveKit = useProvision(22);
|
||||||
? 0.04
|
const hasSandbagArmor = useProvision(28);
|
||||||
: tank.type === 'heavy'
|
const hasEnhancedSandbagArmor = useProvision(29);
|
||||||
|
const hasImprovedGunPowder = useProvision(46);
|
||||||
|
|
||||||
|
const healthCoefficient = coefficient(
|
||||||
|
[hasSandbagArmor, 0.03],
|
||||||
|
[hasEnhancedSandbagArmor, 0.06],
|
||||||
|
[hasImprovedAssembly, 0.06],
|
||||||
|
);
|
||||||
|
const reloadCoefficient = coefficient(
|
||||||
|
[hasRammer, -0.07],
|
||||||
|
[hasAdrenaline && gun.type === 'regular', -0.2],
|
||||||
|
);
|
||||||
|
const penetrationCoefficient = coefficient([
|
||||||
|
hasCalibratedShells,
|
||||||
|
isExplosive(shell.type) ? 0.1 : 0.05,
|
||||||
|
]);
|
||||||
|
const damageCoefficient = coefficient(
|
||||||
|
// TODO: add max rolls
|
||||||
|
[hasTungsten, 0.15],
|
||||||
|
);
|
||||||
|
const interClipCoefficient = coefficient([hasShellReloadBoost, -0.3]);
|
||||||
|
const shellVelocityCoefficient = coefficient(
|
||||||
|
[hasSupercharger, 0.3],
|
||||||
|
[hasImprovedGunPowder, 0.3],
|
||||||
|
);
|
||||||
|
const penetrationLossOverDistanceCoefficient = coefficient([
|
||||||
|
hasSupercharger,
|
||||||
|
-0.5,
|
||||||
|
]);
|
||||||
|
const aimTimeCoefficient = coefficient(
|
||||||
|
[hasEnhancedGunLayingDrive, -0.1],
|
||||||
|
[hasReticleCalibration, -0.4],
|
||||||
|
[hasVerticalStabilizer, -0.15],
|
||||||
|
);
|
||||||
|
const dispersionCoefficient = coefficient(
|
||||||
|
[hasRefinedGun, -0.1],
|
||||||
|
[hasReticleCalibration, -0.4],
|
||||||
|
);
|
||||||
|
const enginePowerCoefficient = coefficient(
|
||||||
|
[
|
||||||
|
hasEngineAccelerator && (tank.type === 'light' || tank.type === 'medium'),
|
||||||
|
0.05,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
hasEngineAccelerator &&
|
||||||
|
(tank.type === 'heavy' || tank.type === 'tankDestroyer'),
|
||||||
|
0.07,
|
||||||
|
],
|
||||||
|
[hasEnginePowerBoost, 0.2],
|
||||||
|
[hasImprovedEnginePowerBoost, 0.4],
|
||||||
|
[hasStandardFuel, 0.03],
|
||||||
|
[hasImprovedFuel, 0.1],
|
||||||
|
);
|
||||||
|
const turretTraverseCoefficient = coefficient(
|
||||||
|
[hasStandardFuel, 0.03],
|
||||||
|
[hasImprovedFuel, 0.1],
|
||||||
|
);
|
||||||
|
const hullTraverseCoefficient = coefficient(
|
||||||
|
[hasImprovedControl, 0.1],
|
||||||
|
[hasImprovedEnginePowerBoost, 0.05],
|
||||||
|
);
|
||||||
|
const resistanceCoefficient = coefficient([hasImprovedSuspension, -0.25]);
|
||||||
|
const viewRangeCoefficient = coefficient([
|
||||||
|
hasImprovedOptics,
|
||||||
|
tank.type === 'tankDestroyer' ? 0.05 : tank.type === 'heavy' ? 0.07 : 0.1,
|
||||||
|
]);
|
||||||
|
const fireChanceCoefficient = coefficient([hasProtectiveKit, -0.2]);
|
||||||
|
|
||||||
|
const speedForwardsSum = sum([hasImprovedEnginePowerBoost, 5]);
|
||||||
|
const speedBackwardsSum = sum([hasImprovedEnginePowerBoost, 10]);
|
||||||
|
const camouflageSumMoving = sum(
|
||||||
|
[
|
||||||
|
hasCamouflageNet,
|
||||||
|
tank.type === 'heavy'
|
||||||
? 0.03
|
? 0.03
|
||||||
: 0.02
|
: tank.type === 'tankDestroyer'
|
||||||
: 0;
|
? 0.07
|
||||||
const shellReloadBoostBonus = hasShellReloadBoost ? 0.7 : 1;
|
: 0.05,
|
||||||
const adrenalineBonus = hasAdrenaline && gun.type === 'regular' ? 0.8 : 1;
|
],
|
||||||
const enginePowerBoostBonus =
|
[
|
||||||
(hasEnginePowerBoost ? 1.2 : 1) * (hasImprovedEnginePowerBoost ? 1.4 : 1);
|
camouflage,
|
||||||
const camouflageNetBonus = hasCamouflageNet
|
tank.type === 'tankDestroyer'
|
||||||
? tank.type === 'heavy'
|
? 0.04
|
||||||
? 0.03
|
: tank.type === 'heavy'
|
||||||
: tank.type === 'tankDestroyer'
|
? 0.03
|
||||||
? 0.07
|
: 0.02,
|
||||||
: 0.05
|
],
|
||||||
: 0;
|
);
|
||||||
const calibratedShellsBonus = hasCalibratedShells
|
const camouflageSumStill = sum(
|
||||||
? isExplosive(shell.type)
|
[
|
||||||
? 1.1
|
hasCamouflageNet,
|
||||||
: 1.05
|
2 *
|
||||||
: 1;
|
(tank.type === 'heavy'
|
||||||
const engineAcceleratorBonus = hasEngineAccelerator
|
? 0.03
|
||||||
? tank.type === 'light' || tank.type === 'medium'
|
: tank.type === 'tankDestroyer'
|
||||||
? 1.05
|
? 0.07
|
||||||
: 1.07
|
: 0.05),
|
||||||
: 1;
|
],
|
||||||
const resolvedEnginePower =
|
[
|
||||||
engine.power * engineAcceleratorBonus * enginePowerBoostBonus;
|
camouflage,
|
||||||
const reticleCalibrationBonus = hasReticleCalibration ? 0.6 : 1;
|
tank.type === 'tankDestroyer'
|
||||||
const improvedEnginePowerBoostTraverseBonus = hasImprovedEnginePowerBoost
|
? 0.04
|
||||||
? 1.05
|
: tank.type === 'heavy'
|
||||||
: 1;
|
? 0.03
|
||||||
|
: 0.02,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
const provisionCrewBonus = provisions.reduce(
|
||||||
|
(total, provision) =>
|
||||||
|
provision.crew ? total + provision.crew / 100 : total,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
const resolvedEnginePower = engine.power * enginePowerCoefficient;
|
||||||
|
const dpm = resolveDpm(
|
||||||
|
gun,
|
||||||
|
shell,
|
||||||
|
damageCoefficient,
|
||||||
|
reloadCoefficient,
|
||||||
|
interClipCoefficient,
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (penetrationDistanceInput.current) {
|
if (penetrationDistanceInput.current) {
|
||||||
@@ -117,14 +213,7 @@ export function Characteristics() {
|
|||||||
<Heading size="5">Fire</Heading>
|
<Heading size="5">Fire</Heading>
|
||||||
<Info name="Gun type">{GUN_TYPE_NAMES[gun.type]}</Info>
|
<Info name="Gun type">{GUN_TYPE_NAMES[gun.type]}</Info>
|
||||||
<InfoWithDelta name="DPM" decimals={0} unit="hp / min">
|
<InfoWithDelta name="DPM" decimals={0} unit="hp / min">
|
||||||
{resolveDpm(
|
{dpm}
|
||||||
gun,
|
|
||||||
shell,
|
|
||||||
hasRammer,
|
|
||||||
hasShellReloadBoost,
|
|
||||||
hasAdrenaline,
|
|
||||||
hasTungsten,
|
|
||||||
)}
|
|
||||||
</InfoWithDelta>
|
</InfoWithDelta>
|
||||||
{gun.type === 'autoReloader' && (
|
{gun.type === 'autoReloader' && (
|
||||||
<>
|
<>
|
||||||
@@ -187,7 +276,7 @@ export function Characteristics() {
|
|||||||
unit="s"
|
unit="s"
|
||||||
deltaType="lowerIsBetter"
|
deltaType="lowerIsBetter"
|
||||||
>
|
>
|
||||||
{gun.reload * (hasRammer ? 0.93 : 1) * adrenalineBonus}
|
{gun.reload * reloadCoefficient}
|
||||||
</InfoWithDelta>
|
</InfoWithDelta>
|
||||||
)}
|
)}
|
||||||
{gun.type === 'autoLoader' && (
|
{gun.type === 'autoLoader' && (
|
||||||
@@ -198,27 +287,18 @@ export function Characteristics() {
|
|||||||
unit="s"
|
unit="s"
|
||||||
deltaType="lowerIsBetter"
|
deltaType="lowerIsBetter"
|
||||||
>
|
>
|
||||||
{gun.interClip * shellReloadBoostBonus}
|
{gun.interClip * interClipCoefficient}
|
||||||
</InfoWithDelta>
|
</InfoWithDelta>
|
||||||
)}
|
)}
|
||||||
<InfoWithDelta name="Caliber" decimals={0} unit="mm">
|
<InfoWithDelta name="Caliber" decimals={0} unit="mm">
|
||||||
{shell.caliber}
|
{shell.caliber}
|
||||||
</InfoWithDelta>
|
</InfoWithDelta>
|
||||||
<InfoWithDelta decimals={0} name="Penetration" unit="mm">
|
<InfoWithDelta decimals={0} name="Penetration" unit="mm">
|
||||||
{resolveNearPenetration(shell.penetration) * calibratedShellsBonus}
|
{resolveNearPenetration(shell.penetration) * penetrationCoefficient}
|
||||||
</InfoWithDelta>
|
</InfoWithDelta>
|
||||||
{typeof shell.penetration !== 'number' && (
|
{typeof shell.penetration !== 'number' && (
|
||||||
<>
|
<>
|
||||||
<Info
|
<InfoWithDelta
|
||||||
delta={
|
|
||||||
(lerp(
|
|
||||||
shell.penetration[0],
|
|
||||||
shell.penetration[1],
|
|
||||||
penetrationDistance / 500,
|
|
||||||
) -
|
|
||||||
shell.penetration[0]) *
|
|
||||||
calibratedShellsBonus
|
|
||||||
}
|
|
||||||
indent
|
indent
|
||||||
decimals={0}
|
decimals={0}
|
||||||
name={`At ${penetrationDistance}m`}
|
name={`At ${penetrationDistance}m`}
|
||||||
@@ -226,9 +306,10 @@ export function Characteristics() {
|
|||||||
{lerp(
|
{lerp(
|
||||||
shell.penetration[0],
|
shell.penetration[0],
|
||||||
shell.penetration[1],
|
shell.penetration[1],
|
||||||
penetrationDistance / 500,
|
(penetrationDistance * penetrationLossOverDistanceCoefficient) /
|
||||||
) * calibratedShellsBonus}
|
500,
|
||||||
</Info>
|
) * penetrationCoefficient}
|
||||||
|
</InfoWithDelta>
|
||||||
<Flex align="center" gap="2" style={{ paddingLeft: 24 }}>
|
<Flex align="center" gap="2" style={{ paddingLeft: 24 }}>
|
||||||
<Text>Distance</Text>
|
<Text>Distance</Text>
|
||||||
<Slider
|
<Slider
|
||||||
@@ -265,13 +346,13 @@ export function Characteristics() {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<InfoWithDelta name="Damage" unit="hp" decimals={0}>
|
<InfoWithDelta name="Damage" unit="hp" decimals={0}>
|
||||||
{shell.damage.armor * (hasTungsten ? 1.15 : 1)}
|
{shell.damage.armor * damageCoefficient}
|
||||||
</InfoWithDelta>
|
</InfoWithDelta>
|
||||||
<InfoWithDelta name="Module damage" unit="hp">
|
<InfoWithDelta name="Module damage" unit="hp" decimals={0}>
|
||||||
{shell.damage.module}
|
{shell.damage.module * damageCoefficient}
|
||||||
</InfoWithDelta>
|
</InfoWithDelta>
|
||||||
<InfoWithDelta name="Shell velocity" unit="m/s">
|
<InfoWithDelta name="Shell velocity" unit="m/s">
|
||||||
{shell.speed * (hasSupercharger ? 1.3 : 1)}
|
{shell.speed * shellVelocityCoefficient}
|
||||||
</InfoWithDelta>
|
</InfoWithDelta>
|
||||||
<InfoWithDelta
|
<InfoWithDelta
|
||||||
decimals={2}
|
decimals={2}
|
||||||
@@ -279,9 +360,7 @@ export function Characteristics() {
|
|||||||
unit="s"
|
unit="s"
|
||||||
deltaType="lowerIsBetter"
|
deltaType="lowerIsBetter"
|
||||||
>
|
>
|
||||||
{gun.aimTime *
|
{gun.aimTime * aimTimeCoefficient}
|
||||||
(hasEnhancedGunLayingDrive ? 0.9 : 1) *
|
|
||||||
reticleCalibrationBonus}
|
|
||||||
</InfoWithDelta>
|
</InfoWithDelta>
|
||||||
<Info name="Dispersion at 100m" />
|
<Info name="Dispersion at 100m" />
|
||||||
<InfoWithDelta
|
<InfoWithDelta
|
||||||
@@ -291,9 +370,7 @@ export function Characteristics() {
|
|||||||
unit="m"
|
unit="m"
|
||||||
deltaType="lowerIsBetter"
|
deltaType="lowerIsBetter"
|
||||||
>
|
>
|
||||||
{gun.dispersion.base *
|
{gun.dispersion.base * dispersionCoefficient}
|
||||||
(hasRefinedGun ? 0.9 : 1) *
|
|
||||||
reticleCalibrationBonus}
|
|
||||||
</InfoWithDelta>
|
</InfoWithDelta>
|
||||||
<InfoWithDelta
|
<InfoWithDelta
|
||||||
prefix="+ "
|
prefix="+ "
|
||||||
@@ -303,9 +380,7 @@ export function Characteristics() {
|
|||||||
unit="s"
|
unit="s"
|
||||||
deltaType="lowerIsBetter"
|
deltaType="lowerIsBetter"
|
||||||
>
|
>
|
||||||
{track.dispersion.move *
|
{track.dispersion.move * dispersionCoefficient}
|
||||||
(hasVerticalStabilizer ? 0.85 : 1) *
|
|
||||||
reticleCalibrationBonus}
|
|
||||||
</InfoWithDelta>
|
</InfoWithDelta>
|
||||||
<InfoWithDelta
|
<InfoWithDelta
|
||||||
decimals={3}
|
decimals={3}
|
||||||
@@ -315,9 +390,7 @@ export function Characteristics() {
|
|||||||
unit="°"
|
unit="°"
|
||||||
deltaType="lowerIsBetter"
|
deltaType="lowerIsBetter"
|
||||||
>
|
>
|
||||||
{track.dispersion.traverse *
|
{track.dispersion.traverse * dispersionCoefficient}
|
||||||
(hasVerticalStabilizer ? 0.85 : 1) *
|
|
||||||
reticleCalibrationBonus}
|
|
||||||
</InfoWithDelta>
|
</InfoWithDelta>
|
||||||
<InfoWithDelta
|
<InfoWithDelta
|
||||||
decimals={3}
|
decimals={3}
|
||||||
@@ -327,9 +400,7 @@ export function Characteristics() {
|
|||||||
unit="°"
|
unit="°"
|
||||||
deltaType="lowerIsBetter"
|
deltaType="lowerIsBetter"
|
||||||
>
|
>
|
||||||
{gun.dispersion.traverse *
|
{gun.dispersion.traverse * dispersionCoefficient}
|
||||||
(hasVerticalStabilizer ? 0.85 : 1) *
|
|
||||||
reticleCalibrationBonus}
|
|
||||||
</InfoWithDelta>
|
</InfoWithDelta>
|
||||||
<InfoWithDelta
|
<InfoWithDelta
|
||||||
decimals={3}
|
decimals={3}
|
||||||
@@ -339,7 +410,7 @@ export function Characteristics() {
|
|||||||
unit="m"
|
unit="m"
|
||||||
deltaType="lowerIsBetter"
|
deltaType="lowerIsBetter"
|
||||||
>
|
>
|
||||||
{gun.dispersion.shot * reticleCalibrationBonus}
|
{gun.dispersion.shot * dispersionCoefficient}
|
||||||
</InfoWithDelta>
|
</InfoWithDelta>
|
||||||
<InfoWithDelta
|
<InfoWithDelta
|
||||||
decimals={3}
|
decimals={3}
|
||||||
@@ -349,7 +420,7 @@ export function Characteristics() {
|
|||||||
unit="scalar"
|
unit="scalar"
|
||||||
deltaType="lowerIsBetter"
|
deltaType="lowerIsBetter"
|
||||||
>
|
>
|
||||||
{gun.dispersion.damaged * reticleCalibrationBonus}
|
{gun.dispersion.damaged * dispersionCoefficient}
|
||||||
</InfoWithDelta>
|
</InfoWithDelta>
|
||||||
<Info name="Gun flexibility" unit="°" />
|
<Info name="Gun flexibility" unit="°" />
|
||||||
<InfoWithDelta decimals={1} indent name="Depression">
|
<InfoWithDelta decimals={1} indent name="Depression">
|
||||||
@@ -396,10 +467,10 @@ export function Characteristics() {
|
|||||||
<Heading size="5">Maneuverability</Heading>
|
<Heading size="5">Maneuverability</Heading>
|
||||||
<Info name="Speed" unit="km/hr" />
|
<Info name="Speed" unit="km/hr" />
|
||||||
<InfoWithDelta decimals={0} indent name="Forwards">
|
<InfoWithDelta decimals={0} indent name="Forwards">
|
||||||
{tank.speed.forwards + (hasImprovedEnginePowerBoost ? 5 : 0)}
|
{tank.speed.forwards + speedForwardsSum}
|
||||||
</InfoWithDelta>
|
</InfoWithDelta>
|
||||||
<InfoWithDelta decimals={0} indent name="Backwards">
|
<InfoWithDelta decimals={0} indent name="Backwards">
|
||||||
{tank.speed.backwards + (hasImprovedEnginePowerBoost ? 10 : 0)}
|
{tank.speed.backwards + speedBackwardsSum}
|
||||||
</InfoWithDelta>
|
</InfoWithDelta>
|
||||||
<InfoWithDelta decimals={0} name="Power" unit="hp">
|
<InfoWithDelta decimals={0} name="Power" unit="hp">
|
||||||
{resolvedEnginePower}
|
{resolvedEnginePower}
|
||||||
@@ -408,19 +479,17 @@ export function Characteristics() {
|
|||||||
<InfoWithDelta decimals={1} indent name="On hard terrain">
|
<InfoWithDelta decimals={1} indent name="On hard terrain">
|
||||||
{resolvedEnginePower /
|
{resolvedEnginePower /
|
||||||
weightTons /
|
weightTons /
|
||||||
(track.resistance.hard * (hasImprovedSuspension ? 0.75 : 1))}
|
(track.resistance.hard * resistanceCoefficient)}
|
||||||
</InfoWithDelta>
|
</InfoWithDelta>
|
||||||
<InfoWithDelta decimals={1} indent name="On medium terrain">
|
<InfoWithDelta decimals={1} indent name="On medium terrain">
|
||||||
{(resolvedEnginePower /
|
{resolvedEnginePower /
|
||||||
weightTons /
|
weightTons /
|
||||||
(track.resistance.medium * (hasImprovedSuspension ? 0.75 : 1))) *
|
(track.resistance.medium * resistanceCoefficient)}
|
||||||
engineAcceleratorBonus}
|
|
||||||
</InfoWithDelta>
|
</InfoWithDelta>
|
||||||
<InfoWithDelta decimals={1} indent name="On soft terrain">
|
<InfoWithDelta decimals={1} indent name="On soft terrain">
|
||||||
{(resolvedEnginePower /
|
{resolvedEnginePower /
|
||||||
weightTons /
|
weightTons /
|
||||||
(track.resistance.soft * (hasImprovedSuspension ? 0.75 : 1))) *
|
(track.resistance.soft * resistanceCoefficient)}
|
||||||
engineAcceleratorBonus}
|
|
||||||
</InfoWithDelta>
|
</InfoWithDelta>
|
||||||
<InfoWithDelta
|
<InfoWithDelta
|
||||||
decimals={1}
|
decimals={1}
|
||||||
@@ -430,72 +499,65 @@ export function Characteristics() {
|
|||||||
>
|
>
|
||||||
{weightTons}
|
{weightTons}
|
||||||
</InfoWithDelta>
|
</InfoWithDelta>
|
||||||
|
<InfoWithDelta name="Turret traverse speed" unit="°/s" decimals={0}>
|
||||||
|
{turret.traverseSpeed * turretTraverseCoefficient}
|
||||||
|
</InfoWithDelta>
|
||||||
<Info name="Effective traverse speed" unit="°/s" />
|
<Info name="Effective traverse speed" unit="°/s" />
|
||||||
<InfoWithDelta decimals={1} indent name="On hard terrain">
|
<InfoWithDelta decimals={1} indent name="On hard terrain">
|
||||||
{(resolvedEnginePower / stockEngine.power) *
|
{(resolvedEnginePower / stockEngine.power) *
|
||||||
track.traverseSpeed *
|
track.traverseSpeed *
|
||||||
(track.resistance.hard / track.resistance.hard) *
|
hullTraverseCoefficient *
|
||||||
(stockWeight / weight) *
|
(track.resistance.hard /
|
||||||
(hasImprovedControl ? 1.1 : 1) *
|
(track.resistance.hard * resistanceCoefficient)) *
|
||||||
improvedEnginePowerBoostTraverseBonus}
|
(stockWeight / weight)}
|
||||||
</InfoWithDelta>
|
</InfoWithDelta>
|
||||||
<InfoWithDelta decimals={1} indent name="On medium terrain">
|
<InfoWithDelta decimals={1} indent name="On medium terrain">
|
||||||
{(resolvedEnginePower / stockEngine.power) *
|
{(resolvedEnginePower / stockEngine.power) *
|
||||||
track.traverseSpeed *
|
track.traverseSpeed *
|
||||||
(track.resistance.hard / track.resistance.medium) *
|
hullTraverseCoefficient *
|
||||||
(stockWeight / weight) *
|
(track.resistance.hard /
|
||||||
(hasImprovedControl ? 1.1 : 1) *
|
(track.resistance.medium * resistanceCoefficient)) *
|
||||||
improvedEnginePowerBoostTraverseBonus}
|
(stockWeight / weight)}
|
||||||
</InfoWithDelta>
|
</InfoWithDelta>
|
||||||
<InfoWithDelta decimals={1} indent name="On soft terrain">
|
<InfoWithDelta decimals={1} indent name="On soft terrain">
|
||||||
{(resolvedEnginePower / stockEngine.power) *
|
{(resolvedEnginePower / stockEngine.power) *
|
||||||
track.traverseSpeed *
|
track.traverseSpeed *
|
||||||
(track.resistance.hard / track.resistance.soft) *
|
hullTraverseCoefficient *
|
||||||
(stockWeight / weight) *
|
(track.resistance.hard /
|
||||||
(hasImprovedControl ? 1.1 : 1) *
|
(track.resistance.soft * resistanceCoefficient)) *
|
||||||
improvedEnginePowerBoostTraverseBonus}
|
(stockWeight / weight)}
|
||||||
</InfoWithDelta>
|
</InfoWithDelta>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Flex direction="column" gap="2">
|
<Flex direction="column" gap="2">
|
||||||
<Heading size="5">Survivability</Heading>
|
<Heading size="5">Survivability</Heading>
|
||||||
<InfoWithDelta name="Health" unit="hp">
|
<InfoWithDelta name="Health" unit="hp" decimals={0}>
|
||||||
{(tank.health + turret.health) * (hasImprovedAssembly ? 1.06 : 1)}
|
{(tank.health + turret.health) * healthCoefficient}
|
||||||
</InfoWithDelta>
|
</InfoWithDelta>
|
||||||
<InfoWithDelta name="Fire chance" unit="%" deltaType="lowerIsBetter">
|
<InfoWithDelta name="Fire chance" unit="%" deltaType="lowerIsBetter">
|
||||||
{Math.round(engine.fireChance * 100)}
|
{Math.round(engine.fireChance * fireChanceCoefficient * 100)}
|
||||||
</InfoWithDelta>
|
</InfoWithDelta>
|
||||||
<InfoWithDelta name="View range" unit="m">
|
<InfoWithDelta name="View range" unit="m">
|
||||||
{turret.viewRange *
|
{turret.viewRange * viewRangeCoefficient}
|
||||||
(hasImprovedOptics
|
|
||||||
? tank.type === 'tankDestroyer'
|
|
||||||
? 1.05
|
|
||||||
: tank.type === 'heavy'
|
|
||||||
? 1.07
|
|
||||||
: 1.1
|
|
||||||
: 1)}
|
|
||||||
</InfoWithDelta>
|
</InfoWithDelta>
|
||||||
<Info name="Camouflage" unit="%" />
|
<Info name="Camouflage" unit="%" />
|
||||||
<InfoWithDelta indent name="Still" decimals={2}>
|
<InfoWithDelta indent name="Still" decimals={2}>
|
||||||
{(tank.camouflage.still + camouflageNetBonus + camouflageBonus) * 100}
|
{(tank.camouflage.still + camouflageSumStill) * 100}
|
||||||
</InfoWithDelta>
|
</InfoWithDelta>
|
||||||
<InfoWithDelta indent name="Moving" decimals={2}>
|
<InfoWithDelta indent name="Moving" decimals={2}>
|
||||||
{(tank.camouflage.moving + camouflageNetBonus + camouflageBonus) *
|
{(tank.camouflage.moving + camouflageSumMoving) * 100}
|
||||||
100}
|
|
||||||
</InfoWithDelta>
|
</InfoWithDelta>
|
||||||
<InfoWithDelta indent name="Shooting still" decimals={2}>
|
<InfoWithDelta indent name="Shooting still" decimals={2}>
|
||||||
{(tank.camouflage.still + camouflageNetBonus + camouflageBonus) *
|
{(tank.camouflage.still * gun.camouflageLoss + camouflageSumStill) *
|
||||||
gun.camouflageLoss *
|
|
||||||
100}
|
100}
|
||||||
</InfoWithDelta>
|
</InfoWithDelta>
|
||||||
<InfoWithDelta indent name="Shooting on move" decimals={2}>
|
<InfoWithDelta indent name="Shooting on move" decimals={2}>
|
||||||
{(tank.camouflage.moving + camouflageNetBonus + camouflageBonus) *
|
{(tank.camouflage.moving * gun.camouflageLoss + camouflageSumMoving) *
|
||||||
gun.camouflageLoss *
|
|
||||||
100}
|
100}
|
||||||
</InfoWithDelta>
|
</InfoWithDelta>
|
||||||
<InfoWithDelta indent name="Caught on fire" decimals={2}>
|
<InfoWithDelta indent name="Caught on fire" decimals={2}>
|
||||||
{(tank.camouflage.onFire + camouflageNetBonus + camouflageBonus) *
|
{(tank.camouflage.onFire * tank.camouflage.still +
|
||||||
tank.camouflage.still *
|
camouflageSumStill) *
|
||||||
100}
|
100}
|
||||||
</InfoWithDelta>
|
</InfoWithDelta>
|
||||||
<Info name="Size" unit="m">
|
<Info name="Size" unit="m">
|
||||||
@@ -512,20 +574,31 @@ export function Characteristics() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<InfoWithDelta
|
<InfoWithDelta
|
||||||
name={`${count > 1 ? `${member.count} ` : ''}${
|
name={`${CREW_MEMBER_NAMES[member.type]}${count > 1 ? ` x ${count}` : ''}`}
|
||||||
(count > 1 ? CREW_MEMBER_NAMES_PLURAL : CREW_MEMBER_NAMES)[
|
|
||||||
member.type
|
|
||||||
]
|
|
||||||
}`}
|
|
||||||
unit="%"
|
unit="%"
|
||||||
decimals={0}
|
decimals={0}
|
||||||
>
|
>
|
||||||
{crew * (member.type === 'commander' ? 1 : 1.1) * 100}
|
{(crew + provisionCrewBonus) *
|
||||||
|
(member.type === 'commander' ? 1 : 1.1) *
|
||||||
|
100}
|
||||||
</InfoWithDelta>
|
</InfoWithDelta>
|
||||||
{member.substitute &&
|
{member.substitute && (
|
||||||
member.substitute.map((substitute) => (
|
<Info
|
||||||
<Info indent name={`Substitutes ${substitute}`} />
|
indent
|
||||||
))}
|
name={
|
||||||
|
<>
|
||||||
|
<Flex align="center" gap="1">
|
||||||
|
<AccessibilityIcon />
|
||||||
|
{member.substitute
|
||||||
|
.map((sub, index) =>
|
||||||
|
index === 0 ? CREW_MEMBER_NAMES[sub] : sub,
|
||||||
|
)
|
||||||
|
.join(', ')}
|
||||||
|
</Flex>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export function coefficient(...effects: [boolean, number][]) {
|
||||||
|
return effects.reduce((accumulator, b) => accumulator + (b[0] ? b[1] : 0), 1);
|
||||||
|
}
|
||||||
@@ -5,9 +5,9 @@ import { fetchCdonLz4 } from './fetchCdonLz4';
|
|||||||
export interface ProvisionEntry {
|
export interface ProvisionEntry {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
effects?: Record<string, number>;
|
|
||||||
include: TankFilterDefinition[];
|
include: TankFilterDefinition[];
|
||||||
exclude?: TankFilterDefinition[];
|
exclude?: TankFilterDefinition[];
|
||||||
|
crew?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProvisionDefinitions {
|
export interface ProvisionDefinitions {
|
||||||
@@ -17,3 +17,5 @@ export interface ProvisionDefinitions {
|
|||||||
export const provisionDefinitions = fetchCdonLz4<ProvisionDefinitions>(
|
export const provisionDefinitions = fetchCdonLz4<ProvisionDefinitions>(
|
||||||
asset('definitions/provisions.cdon.lz4'),
|
asset('definitions/provisions.cdon.lz4'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
provisionDefinitions.then(console.log);
|
||||||
@@ -3,24 +3,18 @@ import { GunDefinition, ShellDefinition } from './tankDefinitions';
|
|||||||
export function resolveDpm(
|
export function resolveDpm(
|
||||||
gun: GunDefinition,
|
gun: GunDefinition,
|
||||||
shell: ShellDefinition,
|
shell: ShellDefinition,
|
||||||
hasRammer = false,
|
damageCoefficient = 1,
|
||||||
hasShellReloadBoost = false,
|
reloadCoefficient = 1,
|
||||||
hasAdrenaline = false,
|
interClipCoefficient = 1,
|
||||||
hasTungsten = false,
|
|
||||||
) {
|
) {
|
||||||
const alpha = shell.damage.armor * (hasTungsten ? 1.15 : 1);
|
const alpha = shell.damage.armor * damageCoefficient;
|
||||||
|
|
||||||
if (gun.type === 'regular') {
|
if (gun.type === 'regular') {
|
||||||
return (
|
return (alpha / (reloadCoefficient * gun.reload)) * 60;
|
||||||
(alpha /
|
|
||||||
((hasRammer ? 0.93 : 1) * (hasAdrenaline ? 0.8 : 1) * gun.reload)) *
|
|
||||||
60
|
|
||||||
);
|
|
||||||
} else if (gun.type === 'autoLoader') {
|
} else if (gun.type === 'autoLoader') {
|
||||||
return (
|
return (
|
||||||
((alpha * gun.count) /
|
((alpha * gun.count) /
|
||||||
(gun.reload +
|
(gun.reload + (gun.count - 1) * gun.interClip * interClipCoefficient)) *
|
||||||
(gun.count - 1) * gun.interClip * (hasShellReloadBoost ? 0.7 : 1))) *
|
|
||||||
60
|
60
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export function sum(...effects: [boolean, number][]) {
|
||||||
|
return effects.reduce((accumulator, b) => accumulator + (b[0] ? b[1] : 0), 0);
|
||||||
|
}
|
||||||
@@ -27,13 +27,6 @@ export const CREW_MEMBER_NAMES: Record<CrewMember, string> = {
|
|||||||
driver: 'Driver',
|
driver: 'Driver',
|
||||||
loader: 'Loader',
|
loader: 'Loader',
|
||||||
};
|
};
|
||||||
export const CREW_MEMBER_NAMES_PLURAL: Record<CrewMember, string> = {
|
|
||||||
commander: 'Commanders',
|
|
||||||
radioman: 'Radiomen',
|
|
||||||
gunner: 'Gunners',
|
|
||||||
driver: 'Drivers',
|
|
||||||
loader: 'Loaders',
|
|
||||||
};
|
|
||||||
export interface Crew {
|
export interface Crew {
|
||||||
type: CrewMember;
|
type: CrewMember;
|
||||||
count?: number;
|
count?: number;
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { useTankopediaTemporary } from '../../stores/tankopedia';
|
||||||
|
|
||||||
|
export function useProvision(id: number) {
|
||||||
|
const provisions = useTankopediaTemporary((state) => state.provisions);
|
||||||
|
return provisions.includes(id);
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { use } from 'react';
|
||||||
|
import { useTankopediaTemporary } from '../../stores/tankopedia';
|
||||||
|
import { provisionDefinitions } from './provisionDefinitions';
|
||||||
|
|
||||||
|
export function useProvisions() {
|
||||||
|
const provisions = useTankopediaTemporary((state) => state.provisions);
|
||||||
|
const awaitedProvisionDefinitions = use(provisionDefinitions);
|
||||||
|
return provisions.map((id) => awaitedProvisionDefinitions[id]);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user