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({
...provision.script,
...provision.script.bonusValues,
}).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;
});
if (provision.script.bonusValues?.crewLevelIncrease !== undefined) {
provisionDefinitions[provision.id].crew =
provision.script.bonusValues?.crewLevelIncrease;
}
});
await commitAssets(
-3
View File
@@ -37,9 +37,6 @@ export default function RootLayout({ children }: RootLayoutProps) {
style={{
margin: 0,
paddingTop: isEmbed ? 0 : '3.25rem',
background: isEmbed
? 'transparent'
: 'url(https://i.imgur.com/PhS06NJ.png)',
}}
>
<Theme
@@ -39,6 +39,47 @@ export function Miscellaneous() {
<Heading size="4">Miscellaneous</Heading>
<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
align="center"
direction="column"
@@ -84,48 +125,6 @@ export function Miscellaneous() {
}}
/>
</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>
</ConfigurationChildWrapper>
);
@@ -1,20 +1,24 @@
import { AccessibilityIcon } from '@radix-ui/react-icons';
import { Flex, Heading, Slider, Text, TextField } from '@radix-ui/themes';
import { debounce } from 'lodash';
import { use, useEffect, useRef, useState } from 'react';
import { lerp } from 'three/src/math/MathUtils';
import { isExplosive } from '../../../../../../core/blitz/isExplosive';
import { resolveNearPenetration } from '../../../../../../core/blitz/resolveNearPenetration';
import { coefficient } from '../../../../../../core/blitzkrieg/coefficient';
import { modelDefinitions } from '../../../../../../core/blitzkrieg/modelDefinitions';
import { normalizeBoundingBox } from '../../../../../../core/blitzkrieg/normalizeBoundingBox';
import { resolveDpm } from '../../../../../../core/blitzkrieg/resolveDpm';
import { sum } from '../../../../../../core/blitzkrieg/sum';
import {
CREW_MEMBER_NAMES,
CREW_MEMBER_NAMES_PLURAL,
GUN_TYPE_NAMES,
} from '../../../../../../core/blitzkrieg/tankDefinitions';
import { unionBoundingBox } from '../../../../../../core/blitzkrieg/unionBoundingBox';
import { useConsumable } from '../../../../../../core/blitzkrieg/useConsumable';
import { useEquipment } from '../../../../../../core/blitzkrieg/useEquipment';
import { useProvision } from '../../../../../../core/blitzkrieg/useProvision';
import { useProvisions } from '../../../../../../core/blitzkrieg/useProvisions';
import { useDuel } from '../../../../../../stores/duel';
import { useTankopediaTemporary } from '../../../../../../stores/tankopedia';
import { Info } from './components/Info';
@@ -49,6 +53,8 @@ export function Characteristics() {
stockTurret.weight +
stockGun.weight;
const [penetrationDistance, setPenetrationDistance] = useState(250);
const provisions = useProvisions();
const camouflage = useTankopediaTemporary((state) => state.camouflage);
const hasRammer = useEquipment(100);
const hasCalibratedShells = useEquipment(103);
const hasEnhancedGunLayingDrive = useEquipment(104);
@@ -69,41 +75,131 @@ export function Characteristics() {
const hasTungsten = useConsumable(45);
const hasReticleCalibration = useConsumable(28);
const hasShellReloadBoost = useConsumable(29);
const camouflage = useTankopediaTemporary((state) => state.camouflage);
const camouflageBonus = camouflage
? tank.type === 'tankDestroyer'
? 0.04
: tank.type === 'heavy'
const hasStandardFuel = useProvision(18);
const hasImprovedFuel = useProvision(19);
const hasProtectiveKit = useProvision(22);
const hasSandbagArmor = useProvision(28);
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.02
: 0;
const shellReloadBoostBonus = hasShellReloadBoost ? 0.7 : 1;
const adrenalineBonus = hasAdrenaline && gun.type === 'regular' ? 0.8 : 1;
const enginePowerBoostBonus =
(hasEnginePowerBoost ? 1.2 : 1) * (hasImprovedEnginePowerBoost ? 1.4 : 1);
const camouflageNetBonus = hasCamouflageNet
? tank.type === 'heavy'
? 0.03
: tank.type === 'tankDestroyer'
? 0.07
: 0.05
: 0;
const calibratedShellsBonus = hasCalibratedShells
? isExplosive(shell.type)
? 1.1
: 1.05
: 1;
const engineAcceleratorBonus = hasEngineAccelerator
? tank.type === 'light' || tank.type === 'medium'
? 1.05
: 1.07
: 1;
const resolvedEnginePower =
engine.power * engineAcceleratorBonus * enginePowerBoostBonus;
const reticleCalibrationBonus = hasReticleCalibration ? 0.6 : 1;
const improvedEnginePowerBoostTraverseBonus = hasImprovedEnginePowerBoost
? 1.05
: 1;
: tank.type === 'tankDestroyer'
? 0.07
: 0.05,
],
[
camouflage,
tank.type === 'tankDestroyer'
? 0.04
: tank.type === 'heavy'
? 0.03
: 0.02,
],
);
const camouflageSumStill = sum(
[
hasCamouflageNet,
2 *
(tank.type === 'heavy'
? 0.03
: tank.type === 'tankDestroyer'
? 0.07
: 0.05),
],
[
camouflage,
tank.type === 'tankDestroyer'
? 0.04
: tank.type === 'heavy'
? 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(() => {
if (penetrationDistanceInput.current) {
@@ -117,14 +213,7 @@ export function Characteristics() {
<Heading size="5">Fire</Heading>
<Info name="Gun type">{GUN_TYPE_NAMES[gun.type]}</Info>
<InfoWithDelta name="DPM" decimals={0} unit="hp / min">
{resolveDpm(
gun,
shell,
hasRammer,
hasShellReloadBoost,
hasAdrenaline,
hasTungsten,
)}
{dpm}
</InfoWithDelta>
{gun.type === 'autoReloader' && (
<>
@@ -187,7 +276,7 @@ export function Characteristics() {
unit="s"
deltaType="lowerIsBetter"
>
{gun.reload * (hasRammer ? 0.93 : 1) * adrenalineBonus}
{gun.reload * reloadCoefficient}
</InfoWithDelta>
)}
{gun.type === 'autoLoader' && (
@@ -198,27 +287,18 @@ export function Characteristics() {
unit="s"
deltaType="lowerIsBetter"
>
{gun.interClip * shellReloadBoostBonus}
{gun.interClip * interClipCoefficient}
</InfoWithDelta>
)}
<InfoWithDelta name="Caliber" decimals={0} unit="mm">
{shell.caliber}
</InfoWithDelta>
<InfoWithDelta decimals={0} name="Penetration" unit="mm">
{resolveNearPenetration(shell.penetration) * calibratedShellsBonus}
{resolveNearPenetration(shell.penetration) * penetrationCoefficient}
</InfoWithDelta>
{typeof shell.penetration !== 'number' && (
<>
<Info
delta={
(lerp(
shell.penetration[0],
shell.penetration[1],
penetrationDistance / 500,
) -
shell.penetration[0]) *
calibratedShellsBonus
}
<InfoWithDelta
indent
decimals={0}
name={`At ${penetrationDistance}m`}
@@ -226,9 +306,10 @@ export function Characteristics() {
{lerp(
shell.penetration[0],
shell.penetration[1],
penetrationDistance / 500,
) * calibratedShellsBonus}
</Info>
(penetrationDistance * penetrationLossOverDistanceCoefficient) /
500,
) * penetrationCoefficient}
</InfoWithDelta>
<Flex align="center" gap="2" style={{ paddingLeft: 24 }}>
<Text>Distance</Text>
<Slider
@@ -265,13 +346,13 @@ export function Characteristics() {
</>
)}
<InfoWithDelta name="Damage" unit="hp" decimals={0}>
{shell.damage.armor * (hasTungsten ? 1.15 : 1)}
{shell.damage.armor * damageCoefficient}
</InfoWithDelta>
<InfoWithDelta name="Module damage" unit="hp">
{shell.damage.module}
<InfoWithDelta name="Module damage" unit="hp" decimals={0}>
{shell.damage.module * damageCoefficient}
</InfoWithDelta>
<InfoWithDelta name="Shell velocity" unit="m/s">
{shell.speed * (hasSupercharger ? 1.3 : 1)}
{shell.speed * shellVelocityCoefficient}
</InfoWithDelta>
<InfoWithDelta
decimals={2}
@@ -279,9 +360,7 @@ export function Characteristics() {
unit="s"
deltaType="lowerIsBetter"
>
{gun.aimTime *
(hasEnhancedGunLayingDrive ? 0.9 : 1) *
reticleCalibrationBonus}
{gun.aimTime * aimTimeCoefficient}
</InfoWithDelta>
<Info name="Dispersion at 100m" />
<InfoWithDelta
@@ -291,9 +370,7 @@ export function Characteristics() {
unit="m"
deltaType="lowerIsBetter"
>
{gun.dispersion.base *
(hasRefinedGun ? 0.9 : 1) *
reticleCalibrationBonus}
{gun.dispersion.base * dispersionCoefficient}
</InfoWithDelta>
<InfoWithDelta
prefix="+ "
@@ -303,9 +380,7 @@ export function Characteristics() {
unit="s"
deltaType="lowerIsBetter"
>
{track.dispersion.move *
(hasVerticalStabilizer ? 0.85 : 1) *
reticleCalibrationBonus}
{track.dispersion.move * dispersionCoefficient}
</InfoWithDelta>
<InfoWithDelta
decimals={3}
@@ -315,9 +390,7 @@ export function Characteristics() {
unit="°"
deltaType="lowerIsBetter"
>
{track.dispersion.traverse *
(hasVerticalStabilizer ? 0.85 : 1) *
reticleCalibrationBonus}
{track.dispersion.traverse * dispersionCoefficient}
</InfoWithDelta>
<InfoWithDelta
decimals={3}
@@ -327,9 +400,7 @@ export function Characteristics() {
unit="°"
deltaType="lowerIsBetter"
>
{gun.dispersion.traverse *
(hasVerticalStabilizer ? 0.85 : 1) *
reticleCalibrationBonus}
{gun.dispersion.traverse * dispersionCoefficient}
</InfoWithDelta>
<InfoWithDelta
decimals={3}
@@ -339,7 +410,7 @@ export function Characteristics() {
unit="m"
deltaType="lowerIsBetter"
>
{gun.dispersion.shot * reticleCalibrationBonus}
{gun.dispersion.shot * dispersionCoefficient}
</InfoWithDelta>
<InfoWithDelta
decimals={3}
@@ -349,7 +420,7 @@ export function Characteristics() {
unit="scalar"
deltaType="lowerIsBetter"
>
{gun.dispersion.damaged * reticleCalibrationBonus}
{gun.dispersion.damaged * dispersionCoefficient}
</InfoWithDelta>
<Info name="Gun flexibility" unit="°" />
<InfoWithDelta decimals={1} indent name="Depression">
@@ -396,10 +467,10 @@ export function Characteristics() {
<Heading size="5">Maneuverability</Heading>
<Info name="Speed" unit="km/hr" />
<InfoWithDelta decimals={0} indent name="Forwards">
{tank.speed.forwards + (hasImprovedEnginePowerBoost ? 5 : 0)}
{tank.speed.forwards + speedForwardsSum}
</InfoWithDelta>
<InfoWithDelta decimals={0} indent name="Backwards">
{tank.speed.backwards + (hasImprovedEnginePowerBoost ? 10 : 0)}
{tank.speed.backwards + speedBackwardsSum}
</InfoWithDelta>
<InfoWithDelta decimals={0} name="Power" unit="hp">
{resolvedEnginePower}
@@ -408,19 +479,17 @@ export function Characteristics() {
<InfoWithDelta decimals={1} indent name="On hard terrain">
{resolvedEnginePower /
weightTons /
(track.resistance.hard * (hasImprovedSuspension ? 0.75 : 1))}
(track.resistance.hard * resistanceCoefficient)}
</InfoWithDelta>
<InfoWithDelta decimals={1} indent name="On medium terrain">
{(resolvedEnginePower /
{resolvedEnginePower /
weightTons /
(track.resistance.medium * (hasImprovedSuspension ? 0.75 : 1))) *
engineAcceleratorBonus}
(track.resistance.medium * resistanceCoefficient)}
</InfoWithDelta>
<InfoWithDelta decimals={1} indent name="On soft terrain">
{(resolvedEnginePower /
{resolvedEnginePower /
weightTons /
(track.resistance.soft * (hasImprovedSuspension ? 0.75 : 1))) *
engineAcceleratorBonus}
(track.resistance.soft * resistanceCoefficient)}
</InfoWithDelta>
<InfoWithDelta
decimals={1}
@@ -430,72 +499,65 @@ export function Characteristics() {
>
{weightTons}
</InfoWithDelta>
<InfoWithDelta name="Turret traverse speed" unit="°/s" decimals={0}>
{turret.traverseSpeed * turretTraverseCoefficient}
</InfoWithDelta>
<Info name="Effective traverse speed" unit="°/s" />
<InfoWithDelta decimals={1} indent name="On hard terrain">
{(resolvedEnginePower / stockEngine.power) *
track.traverseSpeed *
(track.resistance.hard / track.resistance.hard) *
(stockWeight / weight) *
(hasImprovedControl ? 1.1 : 1) *
improvedEnginePowerBoostTraverseBonus}
hullTraverseCoefficient *
(track.resistance.hard /
(track.resistance.hard * resistanceCoefficient)) *
(stockWeight / weight)}
</InfoWithDelta>
<InfoWithDelta decimals={1} indent name="On medium terrain">
{(resolvedEnginePower / stockEngine.power) *
track.traverseSpeed *
(track.resistance.hard / track.resistance.medium) *
(stockWeight / weight) *
(hasImprovedControl ? 1.1 : 1) *
improvedEnginePowerBoostTraverseBonus}
hullTraverseCoefficient *
(track.resistance.hard /
(track.resistance.medium * resistanceCoefficient)) *
(stockWeight / weight)}
</InfoWithDelta>
<InfoWithDelta decimals={1} indent name="On soft terrain">
{(resolvedEnginePower / stockEngine.power) *
track.traverseSpeed *
(track.resistance.hard / track.resistance.soft) *
(stockWeight / weight) *
(hasImprovedControl ? 1.1 : 1) *
improvedEnginePowerBoostTraverseBonus}
hullTraverseCoefficient *
(track.resistance.hard /
(track.resistance.soft * resistanceCoefficient)) *
(stockWeight / weight)}
</InfoWithDelta>
</Flex>
<Flex direction="column" gap="2">
<Heading size="5">Survivability</Heading>
<InfoWithDelta name="Health" unit="hp">
{(tank.health + turret.health) * (hasImprovedAssembly ? 1.06 : 1)}
<InfoWithDelta name="Health" unit="hp" decimals={0}>
{(tank.health + turret.health) * healthCoefficient}
</InfoWithDelta>
<InfoWithDelta name="Fire chance" unit="%" deltaType="lowerIsBetter">
{Math.round(engine.fireChance * 100)}
{Math.round(engine.fireChance * fireChanceCoefficient * 100)}
</InfoWithDelta>
<InfoWithDelta name="View range" unit="m">
{turret.viewRange *
(hasImprovedOptics
? tank.type === 'tankDestroyer'
? 1.05
: tank.type === 'heavy'
? 1.07
: 1.1
: 1)}
{turret.viewRange * viewRangeCoefficient}
</InfoWithDelta>
<Info name="Camouflage" unit="%" />
<InfoWithDelta indent name="Still" decimals={2}>
{(tank.camouflage.still + camouflageNetBonus + camouflageBonus) * 100}
{(tank.camouflage.still + camouflageSumStill) * 100}
</InfoWithDelta>
<InfoWithDelta indent name="Moving" decimals={2}>
{(tank.camouflage.moving + camouflageNetBonus + camouflageBonus) *
100}
{(tank.camouflage.moving + camouflageSumMoving) * 100}
</InfoWithDelta>
<InfoWithDelta indent name="Shooting still" decimals={2}>
{(tank.camouflage.still + camouflageNetBonus + camouflageBonus) *
gun.camouflageLoss *
{(tank.camouflage.still * gun.camouflageLoss + camouflageSumStill) *
100}
</InfoWithDelta>
<InfoWithDelta indent name="Shooting on move" decimals={2}>
{(tank.camouflage.moving + camouflageNetBonus + camouflageBonus) *
gun.camouflageLoss *
{(tank.camouflage.moving * gun.camouflageLoss + camouflageSumMoving) *
100}
</InfoWithDelta>
<InfoWithDelta indent name="Caught on fire" decimals={2}>
{(tank.camouflage.onFire + camouflageNetBonus + camouflageBonus) *
tank.camouflage.still *
{(tank.camouflage.onFire * tank.camouflage.still +
camouflageSumStill) *
100}
</InfoWithDelta>
<Info name="Size" unit="m">
@@ -512,20 +574,31 @@ export function Characteristics() {
return (
<>
<InfoWithDelta
name={`${count > 1 ? `${member.count} ` : ''}${
(count > 1 ? CREW_MEMBER_NAMES_PLURAL : CREW_MEMBER_NAMES)[
member.type
]
}`}
name={`${CREW_MEMBER_NAMES[member.type]}${count > 1 ? ` x ${count}` : ''}`}
unit="%"
decimals={0}
>
{crew * (member.type === 'commander' ? 1 : 1.1) * 100}
{(crew + provisionCrewBonus) *
(member.type === 'commander' ? 1 : 1.1) *
100}
</InfoWithDelta>
{member.substitute &&
member.substitute.map((substitute) => (
<Info indent name={`Substitutes ${substitute}`} />
))}
{member.substitute && (
<Info
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 {
id: number;
name: string;
effects?: Record<string, number>;
include: TankFilterDefinition[];
exclude?: TankFilterDefinition[];
crew?: number;
}
export interface ProvisionDefinitions {
@@ -17,3 +17,5 @@ export interface ProvisionDefinitions {
export const provisionDefinitions = fetchCdonLz4<ProvisionDefinitions>(
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(
gun: GunDefinition,
shell: ShellDefinition,
hasRammer = false,
hasShellReloadBoost = false,
hasAdrenaline = false,
hasTungsten = false,
damageCoefficient = 1,
reloadCoefficient = 1,
interClipCoefficient = 1,
) {
const alpha = shell.damage.armor * (hasTungsten ? 1.15 : 1);
const alpha = shell.damage.armor * damageCoefficient;
if (gun.type === 'regular') {
return (
(alpha /
((hasRammer ? 0.93 : 1) * (hasAdrenaline ? 0.8 : 1) * gun.reload)) *
60
);
return (alpha / (reloadCoefficient * gun.reload)) * 60;
} else if (gun.type === 'autoLoader') {
return (
((alpha * gun.count) /
(gun.reload +
(gun.count - 1) * gun.interClip * (hasShellReloadBoost ? 0.7 : 1))) *
(gun.reload + (gun.count - 1) * gun.interClip * interClipCoefficient)) *
60
);
} 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',
loader: 'Loader',
};
export const CREW_MEMBER_NAMES_PLURAL: Record<CrewMember, string> = {
commander: 'Commanders',
radioman: 'Radiomen',
gunner: 'Gunners',
driver: 'Drivers',
loader: 'Loaders',
};
export interface Crew {
type: CrewMember;
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]);
}