fix: use non-compounding percentages

This commit is contained in:
TrèsAbhi committed 2024-02-16 23:08:20 -06:00
1 parent 2c8c04387b
commit 9174f3c9d0
11 files changed
+279 -215

No files matched your search

+4 -19
View File
@@ -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(
-3
View File
@@ -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
@@ -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>
</>
}
/>
)}
</> </>
); );
})} })}
+3
View File
@@ -0,0 +1,3 @@
export function coefficient(...effects: [boolean, number][]) {
return effects.reduce((accumulator, b) => accumulator + (b[0] ? b[1] : 0), 1);
}
+3 -1
View File
@@ -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);
+6 -12
View File
@@ -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 {
+3
View File
@@ -0,0 +1,3 @@
export function sum(...effects: [boolean, number][]) {
return effects.reduce((accumulator, b) => accumulator + (b[0] ? b[1] : 0), 0);
}
-7
View File
@@ -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;
+6
View File
@@ -0,0 +1,6 @@
import { useTankopediaTemporary } from '../../stores/tankopedia';
export function useProvision(id: number) {
const provisions = useTankopediaTemporary((state) => state.provisions);
return provisions.includes(id);
}
+9
View File
@@ -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]);
}