feat: deltas

This commit is contained in:
TrèsAbhi committed 2024-02-09 18:47:53 -06:00
1 parent 01dc3137d8
commit 6ef0448d9f
3 files changed
+230 -142

No files matched your search

@@ -1,13 +1,17 @@
import { CaretDownIcon, CaretUpIcon } from '@radix-ui/react-icons';
import { Flex, Text } from '@radix-ui/themes'; import { Flex, Text } from '@radix-ui/themes';
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import { theme } from '../../../../../../../stitches.config'; import { theme } from '../../../../../../../stitches.config';
interface InfoProps { export interface InfoProps {
name: ReactNode; name: ReactNode;
children?: ReactNode; children?: ReactNode;
unit?: string; unit?: string;
indent?: boolean; indent?: boolean;
highlight?: boolean; highlight?: boolean;
delta?: number;
decimals?: number;
prefix?: string;
} }
export function Info({ export function Info({
@@ -16,6 +20,9 @@ export function Info({
unit, unit,
indent = false, indent = false,
highlight, highlight,
delta,
decimals,
prefix,
}: InfoProps) { }: InfoProps) {
return ( return (
<Flex <Flex
@@ -48,7 +55,26 @@ export function Info({
)} )}
{children !== undefined && ( {children !== undefined && (
<Text color={highlight ? 'amber' : undefined}>{children}</Text> <Flex align="center" gap="1">
{delta !== undefined && delta !== 0 && (
<>
<Text color={delta > 0 ? 'green' : 'tomato'}>
{decimals !== undefined
? Math.abs(delta).toFixed(decimals)
: Math.abs(delta)}
</Text>
<Text color={delta > 0 ? 'green' : 'tomato'}>
{delta > 0 ? <CaretUpIcon /> : <CaretDownIcon />}
</Text>
</>
)}
<Text color={highlight ? 'amber' : undefined}>
{prefix}
{decimals !== undefined && typeof children === 'number'
? children.toFixed(decimals)
: children}
</Text>
</Flex>
)} )}
</Flex> </Flex>
); );
@@ -0,0 +1,20 @@
import { useEffect, useRef } from 'react';
import { Info, InfoProps } from './Info';
interface InfoWithDeltaProps extends InfoProps {
children: number;
}
export function InfoWithDelta({ children, ...props }: InfoWithDeltaProps) {
const lastValue = useRef(children);
useEffect(() => {
lastValue.current = children;
});
return (
<Info {...props} delta={children - lastValue.current}>
{children}
</Info>
);
}
@@ -12,6 +12,7 @@ import { unionBoundingBox } from '../../../../../../core/blitzkrieg/unionBoundin
import { useEquipment } from '../../../../../../core/blitzkrieg/useEquipmentEquipped'; import { useEquipment } from '../../../../../../core/blitzkrieg/useEquipmentEquipped';
import { useDuel } from '../../../../../../stores/duel'; import { useDuel } from '../../../../../../stores/duel';
import { Info } from './components/Info'; import { Info } from './components/Info';
import { InfoWithDelta } from './components/InfoWithDelta';
export function Characteristics() { export function Characteristics() {
const awaitedModelDefinitions = use(modelDefinitions); const awaitedModelDefinitions = use(modelDefinitions);
@@ -47,68 +48,65 @@ export function Characteristics() {
<Flex direction="column" gap="4" style={{ width: '100%' }}> <Flex direction="column" gap="4" style={{ width: '100%' }}>
<Flex direction="column" gap="2"> <Flex direction="column" gap="2">
<Heading size="5">Survivability</Heading> <Heading size="5">Survivability</Heading>
<Info name="Health" unit="hp"> <InfoWithDelta name="Health" unit="hp">
{tank.health + turret.health} {tank.health + turret.health}
</Info> </InfoWithDelta>
<Info name="Fire chance" unit="%"> <InfoWithDelta name="Fire chance" unit="%">
{Math.round(engine.fireChance * 100)} {Math.round(engine.fireChance * 100)}
</Info> </InfoWithDelta>
<Info name="View range" unit="m"> <InfoWithDelta name="View range" unit="m">
{turret.viewRange} {turret.viewRange}
</Info> </InfoWithDelta>
<Info name="Camouflage" unit="%" /> <Info name="Camouflage" unit="%" />
<Info indent name="Still"> <InfoWithDelta indent name="Still" decimals={2}>
{(tank.camouflage.still * 100).toFixed(2)} {tank.camouflage.still * 100}
</Info> </InfoWithDelta>
<Info indent name="Moving"> <InfoWithDelta indent name="Moving" decimals={2}>
{(tank.camouflage.moving * 100).toFixed(2)} {tank.camouflage.moving * 100}
</Info> </InfoWithDelta>
<Info indent name="Shooting still"> <InfoWithDelta indent name="Shooting still" decimals={2}>
{(tank.camouflage.still * gun.camouflageLoss * 100).toFixed(2)} {tank.camouflage.still * gun.camouflageLoss * 100}
</Info> </InfoWithDelta>
<Info indent name="Shooting on move"> <InfoWithDelta indent name="Shooting on move" decimals={2}>
{(tank.camouflage.moving * gun.camouflageLoss * 100).toFixed(2)} {tank.camouflage.moving * gun.camouflageLoss * 100}
</Info> </InfoWithDelta>
<Info indent name="On fire"> <InfoWithDelta indent name="On fire" decimals={2}>
{(tank.camouflage.onFire * tank.camouflage.still * 100).toFixed(2)} {tank.camouflage.onFire * tank.camouflage.still * 100}
</Info> </InfoWithDelta>
<Info name="Size" unit="m"> <Info name="Size" unit="m">
{size.map((component) => component.toFixed(2)).join(' x ')} {size[0].toFixed(2)} x {size[2].toFixed(2)} x {size[1].toFixed(2)}
</Info> </Info>
</Flex> </Flex>
<Flex direction="column" gap="2"> <Flex direction="column" gap="2">
<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>
<Info name="Damage per minute" unit="hp / min"> <InfoWithDelta name="Damage per minute" decimals={0} unit="hp / min">
{resolveDpm(gun, shell, hasRammer).toFixed(0)} {resolveDpm(gun, shell, hasRammer)}
</Info> </InfoWithDelta>
{gun.type === 'autoReloader' && ( {gun.type === 'autoReloader' && (
<> <>
<Info indent name="Maximum" unit="hp / min"> <InfoWithDelta decimals={0} indent name="Maximum" unit="hp / min">
{gun.reload.at(-1)! < gun.reload.at(-2)! {gun.reload.at(-1)! < gun.reload.at(-2)!
? ( ? (shell.damage.armor / (gun.reload.at(-1)! + gun.interClip)) *
(shell.damage.armor / 60
(gun.reload.at(-1)! + gun.interClip)) * : (shell.damage.armor / gun.reload[0]) * 60}
60 </InfoWithDelta>
).toFixed(0) <InfoWithDelta
: ((shell.damage.armor / gun.reload[0]) * 60).toFixed(0)} decimals={0}
</Info> indent
<Info indent name="Effective at 60s" unit="hp / min"> name="Effective at 60s"
unit="hp / min"
>
{gun.reload.at(-1)! < gun.reload.at(-2)! {gun.reload.at(-1)! < gun.reload.at(-2)!
? ( ? (shell.damage.armor / (gun.reload.at(-1)! + gun.interClip)) *
(shell.damage.armor / (60 -
(gun.reload.at(-1)! + gun.interClip)) * (gun.reload.slice(0, -1).length - 1) * gun.interClip) +
(60 - shell.damage.armor * gun.reload.slice(0, -1).length
(gun.reload.slice(0, -1).length - 1) * gun.interClip) + : (shell.damage.armor / (gun.reload[0] + gun.interClip)) *
shell.damage.armor * gun.reload.slice(0, -1).length (60 - (gun.reload.slice(1).length - 1) * gun.interClip) +
).toFixed(0) shell.damage.armor * gun.reload.slice(1).length}
: ( </InfoWithDelta>
(shell.damage.armor / (gun.reload[0] + gun.interClip)) *
(60 - (gun.reload.slice(1).length - 1) * gun.interClip) +
shell.damage.armor * gun.reload.slice(1).length
).toFixed(0)}
</Info>
<Info <Info
indent indent
name={ name={
@@ -127,153 +125,197 @@ export function Characteristics() {
)} )}
{gun.type === 'autoReloader' ? ( {gun.type === 'autoReloader' ? (
gun.reload.map((reload, index) => ( gun.reload.map((reload, index) => (
<Info <InfoWithDelta
key={index} key={index}
indent={index > 0} indent={index > 0}
name={index > 0 ? `Shell ${index + 1}` : 'Reload on shell 1'} name={index > 0 ? `Shell ${index + 1}` : 'Reload on shell 1'}
unit="s" unit="s"
decimals={2}
> >
{reload.toFixed(2)} {reload}
</Info> </InfoWithDelta>
)) ))
) : ( ) : (
<Info name="Reload" unit="s"> <InfoWithDelta decimals={2} name="Reload" unit="s">
{(gun.reload * (hasRammer ? 0.93 : 1)).toFixed(2)} {gun.reload * (hasRammer ? 0.93 : 1)}
</Info> </InfoWithDelta>
)} )}
<Info name="Caliber" unit="mm"> <InfoWithDelta name="Caliber" decimals={0} unit="mm">
{shell.caliber} {shell.caliber}
</Info> </InfoWithDelta>
<Info name="Penetration" unit="mm" /> <Info name="Penetration" unit="mm" />
{gun.shells.map((shell) => ( {gun.shells.map((shell) => (
<Info key={shell.type} indent name={SHELL_NAMES[shell.type]}> <InfoWithDelta
decimals={0}
key={shell.type}
indent
name={SHELL_NAMES[shell.type]}
>
{resolveNearPenetration(shell.penetration)} {resolveNearPenetration(shell.penetration)}
</Info> </InfoWithDelta>
))} ))}
<Info name="Damage" unit="hp" /> <Info name="Damage" unit="hp" />
{gun.shells.map((shell) => ( {gun.shells.map((shell) => (
<Info key={shell.type} indent name={SHELL_NAMES[shell.type]}> <InfoWithDelta
decimals={0}
key={shell.type}
indent
name={SHELL_NAMES[shell.type]}
>
{shell.damage.armor} {shell.damage.armor}
</Info> </InfoWithDelta>
))} ))}
<Info name="Module damage" unit="hp" /> <Info name="Module damage" unit="hp" />
{gun.shells.map((shell, index) => ( {gun.shells.map((shell, index) => (
<Info key={shell.type} indent name={SHELL_NAMES[shell.type]}> <InfoWithDelta
decimals={0}
key={shell.type}
indent
name={SHELL_NAMES[shell.type]}
>
{shell.damage.module} {shell.damage.module}
</Info> </InfoWithDelta>
))} ))}
<Info name="Shell velocity" unit="m/s" /> <Info name="Shell velocity" unit="m/s" />
{gun.shells.map((shell, index) => ( {gun.shells.map((shell) => (
<Info key={shell.type} indent name={SHELL_NAMES[shell.type]}> <InfoWithDelta
decimals={0}
key={shell.type}
indent
name={SHELL_NAMES[shell.type]}
>
{shell.speed} {shell.speed}
</Info> </InfoWithDelta>
))} ))}
<Info name="Aim time" unit="s"> <InfoWithDelta decimals={2} name="Aim time" unit="s">
{gun.aimTime.toFixed(2)} {gun.aimTime}
</Info> </InfoWithDelta>
<Info name="Dispersion at 100m" /> <Info name="Dispersion at 100m" />
<Info indent name="Still" unit="m"> <InfoWithDelta decimals={3} indent name="Still" unit="m">
{gun.dispersion.base.toFixed(3)} {gun.dispersion.base}
</Info> </InfoWithDelta>
<Info indent name="Moving" unit="s"> <InfoWithDelta prefix="+ " decimals={3} indent name="Moving" unit="s">
+ {track.dispersion.move.toFixed(3)} {track.dispersion.move}
</Info> </InfoWithDelta>
<Info indent name="Hull traversing" unit="°"> <InfoWithDelta
+ {track.dispersion.traverse.toFixed(3)} decimals={3}
</Info> prefix="+ "
<Info indent name="Turret traversing" unit="°"> indent
+ {gun.dispersion.traverse.toFixed(3)} name="Hull traversing"
</Info> unit="°"
<Info indent name="After shooting" unit="m"> >
+ {gun.dispersion.shot.toFixed(3)} {track.dispersion.traverse}
</Info> </InfoWithDelta>
<Info indent name="Gun damaged" unit="scalar"> <InfoWithDelta
x {gun.dispersion.damaged.toFixed(3)} decimals={0}
</Info> prefix="+ "
indent
name="Turret traversing"
unit="°"
>
{gun.dispersion.traverse}
</InfoWithDelta>
<InfoWithDelta
decimals={3}
prefix="+ "
indent
name="After shooting"
unit="m"
>
{gun.dispersion.shot}
</InfoWithDelta>
<InfoWithDelta
decimals={3}
prefix="x "
indent
name="Gun damaged"
unit="scalar"
>
{gun.dispersion.damaged}
</InfoWithDelta>
<Info name="Gun flexibility" unit="°" /> <Info name="Gun flexibility" unit="°" />
<Info indent name="Depression"> <InfoWithDelta decimals={1} indent name="Depression">
{gunModelDefinition.pitch.max.toFixed(1)} {gunModelDefinition.pitch.max}
</Info> </InfoWithDelta>
<Info indent name="Elevation"> <InfoWithDelta decimals={1} indent name="Elevation">
{(-gunModelDefinition.pitch.min).toFixed(1)} {-gunModelDefinition.pitch.min}
</Info> </InfoWithDelta>
{gunModelDefinition.pitch.front && ( {gunModelDefinition.pitch.front && (
<> <>
<Info indent name="Frontal depression"> <InfoWithDelta decimals={1} indent name="Frontal depression">
{gunModelDefinition.pitch.front.max} {gunModelDefinition.pitch.front.max}
</Info> </InfoWithDelta>
<Info indent name="Frontal elevation"> <InfoWithDelta decimals={1} indent name="Frontal elevation">
{-gunModelDefinition.pitch.front.min} {-gunModelDefinition.pitch.front.min}
</Info> </InfoWithDelta>
</> </>
)} )}
{gunModelDefinition.pitch.back && ( {gunModelDefinition.pitch.back && (
<> <>
<Info indent name="Rear depression"> <InfoWithDelta decimals={1} indent name="Rear depression">
{gunModelDefinition.pitch.back.max} {gunModelDefinition.pitch.back.max}
</Info> </InfoWithDelta>
<Info indent name="Rear elevation"> <InfoWithDelta decimals={1} indent name="Rear elevation">
{-gunModelDefinition.pitch.back.min} {-gunModelDefinition.pitch.back.min}
</Info> </InfoWithDelta>
</> </>
)} )}
{turretModelDefinition.yaw && ( {turretModelDefinition.yaw && (
<Info indent name="Azimuth"> <>
{-turretModelDefinition.yaw.min}, {turretModelDefinition.yaw.max} <InfoWithDelta decimals={1} indent name="Azimuth left">
</Info> {-turretModelDefinition.yaw.min}
</InfoWithDelta>
<InfoWithDelta decimals={1} indent name="Azimuth right">
{turretModelDefinition.yaw.max}
</InfoWithDelta>
</>
)} )}
</Flex> </Flex>
<Flex direction="column" gap="2"> <Flex direction="column" gap="2">
<Heading size="5">Maneuverability</Heading> <Heading size="5">Maneuverability</Heading>
<Info name="Speed" unit="km/hr" /> <Info name="Speed" unit="km/hr" />
<Info indent name="Forwards"> <InfoWithDelta decimals={0} indent name="Forwards">
{tank.speed.forwards.toFixed(0)} {tank.speed.forwards}
</Info> </InfoWithDelta>
<Info indent name="Backwards"> <InfoWithDelta decimals={0} indent name="Backwards">
{tank.speed.backwards.toFixed(0)} {tank.speed.backwards}
</Info> </InfoWithDelta>
<Info name="Power" unit="hp"> <InfoWithDelta decimals={0} name="Power" unit="hp">
{engine.power} {engine.power}
</Info> </InfoWithDelta>
<Info name="Power to weight ratio" unit="hp/mt" /> <Info name="Power to weight ratio" unit="hp/mt" />
<Info indent name="On hard terrain"> <InfoWithDelta decimals={1} indent name="On hard terrain">
{(engine.power / weightMt / track.resistance.hard).toFixed(1)} {engine.power / weightMt / track.resistance.hard}
</Info> </InfoWithDelta>
<Info indent name="On medium terrain"> <InfoWithDelta decimals={1} indent name="On medium terrain">
{(engine.power / weightMt / track.resistance.medium).toFixed(1)} {engine.power / weightMt / track.resistance.medium}
</Info> </InfoWithDelta>
<Info indent name="On soft terrain"> <InfoWithDelta decimals={1} indent name="On soft terrain">
{(engine.power / weightMt / track.resistance.soft).toFixed(1)} {engine.power / weightMt / track.resistance.soft}
</Info> </InfoWithDelta>
<Info name="Weight" unit="mt"> <InfoWithDelta decimals={1} name="Weight" unit="mt">
{weightMt.toFixed(1)} {weightMt}
</Info> </InfoWithDelta>
<Info name="Effective traverse speed" unit="°/s" /> <Info name="Effective traverse speed" unit="°/s" />
<Info indent name="On hard terrain"> <InfoWithDelta decimals={1} indent name="On hard terrain">
{( {(engine.power / stockEngine.power) *
(engine.power / stockEngine.power) *
track.traverseSpeed * track.traverseSpeed *
(track.resistance.hard / track.resistance.hard) * (track.resistance.hard / track.resistance.hard) *
(stockWeight / weight) (stockWeight / weight)}
).toFixed(1)} </InfoWithDelta>
</Info> <InfoWithDelta decimals={1} indent name="On medium terrain">
<Info indent name="On medium terrain"> {(engine.power / stockEngine.power) *
{(
(engine.power / stockEngine.power) *
track.traverseSpeed * track.traverseSpeed *
(track.resistance.hard / track.resistance.medium) * (track.resistance.hard / track.resistance.medium) *
(stockWeight / weight) (stockWeight / weight)}
).toFixed(1)} </InfoWithDelta>
</Info> <InfoWithDelta decimals={1} indent name="On soft terrain">
<Info indent name="On soft terrain"> {(engine.power / stockEngine.power) *
{(
(engine.power / stockEngine.power) *
track.traverseSpeed * track.traverseSpeed *
(track.resistance.hard / track.resistance.soft) * (track.resistance.hard / track.resistance.soft) *
(stockWeight / weight) (stockWeight / weight)}
).toFixed(1)} </InfoWithDelta>
</Info>
</Flex> </Flex>
</Flex> </Flex>
); );