mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 20:19:49 +02:00
feat: better layout
This commit is contained in:
1 parent
299ccef579
commit
ab510361b5
6 files changed
+463
-435
No files matched your search
@@ -32,7 +32,7 @@ export function Info({
|
|||||||
style={{ width: '100%', paddingLeft: indent ? 24 : 0 }}
|
style={{ width: '100%', paddingLeft: indent ? 24 : 0 }}
|
||||||
gap="4"
|
gap="4"
|
||||||
>
|
>
|
||||||
<Text color={highlight ? 'amber' : undefined}>
|
<Text color={highlight ? 'amber' : 'gray'}>
|
||||||
{name}
|
{name}
|
||||||
{unit != undefined && (
|
{unit != undefined && (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -25,12 +25,14 @@ import {
|
|||||||
} from '../../../../../../core/blitzkit/tankDefinitions/constants';
|
} from '../../../../../../core/blitzkit/tankDefinitions/constants';
|
||||||
import { useEquipment } from '../../../../../../hooks/useEquipment';
|
import { useEquipment } from '../../../../../../hooks/useEquipment';
|
||||||
import { useFullScreen } from '../../../../../../hooks/useFullScreen';
|
import { useFullScreen } from '../../../../../../hooks/useFullScreen';
|
||||||
|
import { useWideFormat } from '../../../../../../hooks/useWideFormat';
|
||||||
import { mutateDuel, useDuel } from '../../../../../../stores/duel';
|
import { mutateDuel, useDuel } from '../../../../../../stores/duel';
|
||||||
import { useTankopediaTemporary } from '../../../../../../stores/tankopedia';
|
import { useTankopediaTemporary } from '../../../../../../stores/tankopedia';
|
||||||
import { Info } from './components/Info';
|
import { Info } from './components/Info';
|
||||||
import { InfoWithDelta } from './components/InfoWithDelta';
|
import { InfoWithDelta } from './components/InfoWithDelta';
|
||||||
|
|
||||||
export function Characteristics() {
|
export function Characteristics() {
|
||||||
|
const wideFormat = useWideFormat(960);
|
||||||
const awaitedEquipmentDefinitions = use(equipmentDefinitions);
|
const awaitedEquipmentDefinitions = use(equipmentDefinitions);
|
||||||
const awaitedModelDefinitions = use(modelDefinitions);
|
const awaitedModelDefinitions = use(modelDefinitions);
|
||||||
const awaitedProvisionDefinitions = use(provisionDefinitions);
|
const awaitedProvisionDefinitions = use(provisionDefinitions);
|
||||||
@@ -128,445 +130,450 @@ export function Characteristics() {
|
|||||||
if (isFullScreen) return null;
|
if (isFullScreen) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex direction="column" gap="8">
|
<Flex direction={wideFormat ? 'row' : 'column'} gap="8" wrap="wrap">
|
||||||
<Flex direction="column" gap="2">
|
<Flex direction="column" gap="8" style={{ flex: 1 }}>
|
||||||
<Flex align="center" gap="4">
|
<Flex direction="column" gap="2">
|
||||||
<Heading size="5">Fire</Heading>
|
<Flex align="center" gap="4">
|
||||||
|
<Heading size="5">Fire</Heading>
|
||||||
|
|
||||||
<Flex>
|
<Flex>
|
||||||
{gun.shells.map((thisShell, shellIndex) => (
|
{gun.shells.map((thisShell, shellIndex) => (
|
||||||
<IconButton
|
<IconButton
|
||||||
color={thisShell.id === shell.id ? undefined : 'gray'}
|
color={thisShell.id === shell.id ? undefined : 'gray'}
|
||||||
variant="soft"
|
variant="soft"
|
||||||
key={thisShell.id}
|
key={thisShell.id}
|
||||||
style={{
|
style={{
|
||||||
borderTopLeftRadius: shellIndex === 0 ? undefined : 0,
|
borderTopLeftRadius: shellIndex === 0 ? undefined : 0,
|
||||||
borderBottomLeftRadius: shellIndex === 0 ? undefined : 0,
|
borderBottomLeftRadius: shellIndex === 0 ? undefined : 0,
|
||||||
borderTopRightRadius:
|
borderTopRightRadius:
|
||||||
shellIndex === gun.shells.length - 1 ? undefined : 0,
|
shellIndex === gun.shells.length - 1 ? undefined : 0,
|
||||||
borderBottomRightRadius:
|
borderBottomRightRadius:
|
||||||
shellIndex === gun.shells.length - 1 ? undefined : 0,
|
shellIndex === gun.shells.length - 1 ? undefined : 0,
|
||||||
marginLeft: shellIndex === 0 ? 0 : -1,
|
marginLeft: shellIndex === 0 ? 0 : -1,
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
mutateDuel((draft) => {
|
mutateDuel((draft) => {
|
||||||
draft.protagonist!.shell = thisShell;
|
draft.protagonist!.shell = thisShell;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
|
||||||
<img
|
|
||||||
width={16}
|
|
||||||
height={16}
|
|
||||||
src={asset(`icons/shells/${thisShell.icon}.webp`)}
|
|
||||||
/>
|
|
||||||
</IconButton>
|
|
||||||
))}
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
<Info name="Gun type">{GUN_TYPE_NAMES[gun.type]}</Info>
|
|
||||||
<InfoWithDelta name="DPM" decimals={0} unit="hp / min">
|
|
||||||
{stats.dpm}
|
|
||||||
</InfoWithDelta>
|
|
||||||
{gun.type === 'autoReloader' && (
|
|
||||||
<>
|
|
||||||
<InfoWithDelta decimals={0} indent name="Maximum" unit="hp / min">
|
|
||||||
{stats.dpmMaximum!}
|
|
||||||
</InfoWithDelta>
|
|
||||||
<InfoWithDelta
|
|
||||||
decimals={0}
|
|
||||||
indent
|
|
||||||
name="Effective at 60s"
|
|
||||||
unit="hp / min"
|
|
||||||
>
|
|
||||||
{stats.dpmEffective!}
|
|
||||||
</InfoWithDelta>
|
|
||||||
<Info
|
|
||||||
indent
|
|
||||||
name={
|
|
||||||
<a
|
|
||||||
target="_blank"
|
|
||||||
href="https://tresabhi.github.io/blitzkit/guide/dpm.html"
|
|
||||||
>
|
>
|
||||||
What's the difference?
|
<img
|
||||||
</a>
|
width={16}
|
||||||
}
|
height={16}
|
||||||
/>
|
src={asset(`icons/shells/${thisShell.icon}.webp`)}
|
||||||
</>
|
/>
|
||||||
)}
|
</IconButton>
|
||||||
{gun.type !== 'regular' && (
|
))}
|
||||||
<InfoWithDelta name="Shells">{stats.shells}</InfoWithDelta>
|
</Flex>
|
||||||
)}
|
</Flex>
|
||||||
{gun.type === 'autoReloader' ? (
|
<Info name="Gun type">{GUN_TYPE_NAMES[gun.type]}</Info>
|
||||||
<>
|
<InfoWithDelta name="DPM" decimals={0} unit="hp / min">
|
||||||
<Info indent name="Most optimal">
|
{stats.dpm}
|
||||||
{stats.mostOptimalShellIndex}
|
</InfoWithDelta>
|
||||||
</Info>
|
{gun.type === 'autoReloader' && (
|
||||||
<Info name="Shell reloads" unit="s" />
|
<>
|
||||||
{stats.shellReloads!.map((reload, index) => (
|
<InfoWithDelta decimals={0} indent name="Maximum" unit="hp / min">
|
||||||
<InfoWithDelta
|
{stats.dpmMaximum!}
|
||||||
key={index}
|
|
||||||
indent
|
|
||||||
name={`Shell ${index + 1}`}
|
|
||||||
decimals={2}
|
|
||||||
deltaType="lowerIsBetter"
|
|
||||||
>
|
|
||||||
{reload}
|
|
||||||
</InfoWithDelta>
|
</InfoWithDelta>
|
||||||
))}
|
<InfoWithDelta
|
||||||
</>
|
decimals={0}
|
||||||
) : (
|
indent
|
||||||
<InfoWithDelta
|
name="Effective at 60s"
|
||||||
decimals={2}
|
unit="hp / min"
|
||||||
name="Reload"
|
>
|
||||||
unit="s"
|
{stats.dpmEffective!}
|
||||||
deltaType="lowerIsBetter"
|
</InfoWithDelta>
|
||||||
>
|
<Info
|
||||||
{stats.shellReload!}
|
indent
|
||||||
</InfoWithDelta>
|
name={
|
||||||
)}
|
<a
|
||||||
{(gun.type === 'autoLoader' || gun.type === 'autoReloader') && (
|
target="_blank"
|
||||||
<InfoWithDelta
|
href="https://tresabhi.github.io/blitzkit/guide/dpm.html"
|
||||||
indent
|
>
|
||||||
decimals={2}
|
What's the difference?
|
||||||
name="Intra-clip"
|
</a>
|
||||||
unit="s"
|
}
|
||||||
deltaType="lowerIsBetter"
|
/>
|
||||||
>
|
</>
|
||||||
{stats.intraClip!}
|
)}
|
||||||
</InfoWithDelta>
|
{gun.type !== 'regular' && (
|
||||||
)}
|
<InfoWithDelta name="Shells">{stats.shells}</InfoWithDelta>
|
||||||
<InfoWithDelta decimals={0} name="Penetration" unit="mm">
|
)}
|
||||||
{stats.penetration}
|
{gun.type === 'autoReloader' ? (
|
||||||
</InfoWithDelta>
|
<>
|
||||||
{typeof shell.penetration !== 'number' && (
|
<Info indent name="Most optimal">
|
||||||
<>
|
{stats.mostOptimalShellIndex}
|
||||||
|
</Info>
|
||||||
|
<Info name="Shell reloads" unit="s" />
|
||||||
|
{stats.shellReloads!.map((reload, index) => (
|
||||||
|
<InfoWithDelta
|
||||||
|
key={index}
|
||||||
|
indent
|
||||||
|
name={`Shell ${index + 1}`}
|
||||||
|
decimals={2}
|
||||||
|
deltaType="lowerIsBetter"
|
||||||
|
>
|
||||||
|
{reload}
|
||||||
|
</InfoWithDelta>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<InfoWithDelta
|
||||||
|
decimals={2}
|
||||||
|
name="Reload"
|
||||||
|
unit="s"
|
||||||
|
deltaType="lowerIsBetter"
|
||||||
|
>
|
||||||
|
{stats.shellReload!}
|
||||||
|
</InfoWithDelta>
|
||||||
|
)}
|
||||||
|
{(gun.type === 'autoLoader' || gun.type === 'autoReloader') && (
|
||||||
<InfoWithDelta
|
<InfoWithDelta
|
||||||
indent
|
indent
|
||||||
decimals={0}
|
decimals={2}
|
||||||
name={`At ${penetrationDistance}m`}
|
name="Intra-clip"
|
||||||
|
unit="s"
|
||||||
|
deltaType="lowerIsBetter"
|
||||||
>
|
>
|
||||||
{lerp(
|
{stats.intraClip!}
|
||||||
shell.penetration[0],
|
|
||||||
shell.penetration[1],
|
|
||||||
(penetrationDistance * penetrationLossOverDistanceCoefficient) /
|
|
||||||
500,
|
|
||||||
) * penetrationCoefficient}
|
|
||||||
</InfoWithDelta>
|
</InfoWithDelta>
|
||||||
<Flex align="center" gap="2" style={{ paddingLeft: 24 }}>
|
)}
|
||||||
<Text>Distance</Text>
|
<InfoWithDelta decimals={0} name="Penetration" unit="mm">
|
||||||
<Slider
|
{stats.penetration}
|
||||||
key={penetrationDistance}
|
|
||||||
min={0}
|
|
||||||
max={500}
|
|
||||||
style={{ flex: 1 }}
|
|
||||||
defaultValue={[penetrationDistance]}
|
|
||||||
onValueChange={debounce(([value]) => {
|
|
||||||
setPenetrationDistance(value);
|
|
||||||
}, 500)}
|
|
||||||
/>
|
|
||||||
<TextField.Root
|
|
||||||
style={{ width: 64 }}
|
|
||||||
ref={penetrationDistanceInput}
|
|
||||||
defaultValue={penetrationDistance}
|
|
||||||
onBlur={() => {
|
|
||||||
setPenetrationDistance(
|
|
||||||
Math.min(
|
|
||||||
parseInt(penetrationDistanceInput.current!.value),
|
|
||||||
500,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
onKeyDown={(event) => {
|
|
||||||
if (event.key === 'Enter') {
|
|
||||||
event.currentTarget.blur();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<TextField.Slot side="right">m</TextField.Slot>
|
|
||||||
</TextField.Root>
|
|
||||||
</Flex>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<InfoWithDelta name="Damage" unit="hp" decimals={0}>
|
|
||||||
{stats.damage}
|
|
||||||
</InfoWithDelta>
|
|
||||||
<InfoWithDelta name="Module damage" unit="hp" decimals={0}>
|
|
||||||
{stats.moduleDamage}
|
|
||||||
</InfoWithDelta>
|
|
||||||
{isExplosive(shell.type) && (
|
|
||||||
<InfoWithDelta name="Splash radius" unit="m" decimals={0}>
|
|
||||||
{stats.explosionRadius!}
|
|
||||||
</InfoWithDelta>
|
</InfoWithDelta>
|
||||||
)}
|
{typeof shell.penetration !== 'number' && (
|
||||||
<InfoWithDelta name="Caliber" decimals={0} unit="mm">
|
<>
|
||||||
{stats.caliber}
|
|
||||||
</InfoWithDelta>
|
|
||||||
<InfoWithDelta name="Normalization" decimals={0} unit="°">
|
|
||||||
{stats.shellNormalization}
|
|
||||||
</InfoWithDelta>
|
|
||||||
{shell.type !== 'he' && (
|
|
||||||
<InfoWithDelta
|
|
||||||
name="Ricochet"
|
|
||||||
decimals={0}
|
|
||||||
deltaType="lowerIsBetter"
|
|
||||||
unit="°"
|
|
||||||
>
|
|
||||||
{stats.shellRicochet!}
|
|
||||||
</InfoWithDelta>
|
|
||||||
)}
|
|
||||||
<InfoWithDelta name="Shell velocity" unit="m/s" decimals={0}>
|
|
||||||
{stats.shellVelocity}
|
|
||||||
</InfoWithDelta>
|
|
||||||
<InfoWithDelta
|
|
||||||
decimals={2}
|
|
||||||
name="Aim time"
|
|
||||||
unit="s"
|
|
||||||
deltaType="lowerIsBetter"
|
|
||||||
>
|
|
||||||
{stats.aimTime}
|
|
||||||
</InfoWithDelta>
|
|
||||||
<Info name="Dispersion at 100m" />
|
|
||||||
<InfoWithDelta
|
|
||||||
decimals={3}
|
|
||||||
indent
|
|
||||||
name="Still"
|
|
||||||
unit="m"
|
|
||||||
deltaType="lowerIsBetter"
|
|
||||||
>
|
|
||||||
{stats.dispersion}
|
|
||||||
</InfoWithDelta>
|
|
||||||
<InfoWithDelta
|
|
||||||
prefix="+ "
|
|
||||||
decimals={3}
|
|
||||||
indent
|
|
||||||
name="Moving"
|
|
||||||
deltaType="lowerIsBetter"
|
|
||||||
>
|
|
||||||
{stats.dispersionMoving}
|
|
||||||
</InfoWithDelta>
|
|
||||||
<InfoWithDelta
|
|
||||||
decimals={3}
|
|
||||||
prefix="+ "
|
|
||||||
indent
|
|
||||||
name="Hull traversing"
|
|
||||||
deltaType="lowerIsBetter"
|
|
||||||
>
|
|
||||||
{stats.dispersionHullTraversing}
|
|
||||||
</InfoWithDelta>
|
|
||||||
<InfoWithDelta
|
|
||||||
decimals={3}
|
|
||||||
prefix="+ "
|
|
||||||
indent
|
|
||||||
name="Turret traversing"
|
|
||||||
deltaType="lowerIsBetter"
|
|
||||||
>
|
|
||||||
{stats.dispersionTurretTraversing}
|
|
||||||
</InfoWithDelta>
|
|
||||||
<InfoWithDelta
|
|
||||||
decimals={3}
|
|
||||||
prefix="+ "
|
|
||||||
indent
|
|
||||||
name="After shooting"
|
|
||||||
deltaType="lowerIsBetter"
|
|
||||||
>
|
|
||||||
{stats.dispersionShooting}
|
|
||||||
</InfoWithDelta>
|
|
||||||
<InfoWithDelta
|
|
||||||
decimals={1}
|
|
||||||
prefix="x "
|
|
||||||
indent
|
|
||||||
name="Gun damaged"
|
|
||||||
unit="scalar"
|
|
||||||
deltaType="lowerIsBetter"
|
|
||||||
>
|
|
||||||
{stats.dispersionGunDamaged}
|
|
||||||
</InfoWithDelta>
|
|
||||||
<Info name="Gun flexibility" unit="°" />
|
|
||||||
<InfoWithDelta decimals={1} indent name="Depression">
|
|
||||||
{stats.gunDepression}
|
|
||||||
</InfoWithDelta>
|
|
||||||
<InfoWithDelta decimals={1} indent name="Elevation">
|
|
||||||
{stats.gunElevation}
|
|
||||||
</InfoWithDelta>
|
|
||||||
{gunModelDefinition.pitch.front && (
|
|
||||||
<>
|
|
||||||
<InfoWithDelta decimals={1} indent name="Frontal depression">
|
|
||||||
{stats.gunFrontalDepression!}
|
|
||||||
</InfoWithDelta>
|
|
||||||
<InfoWithDelta decimals={1} indent name="Frontal elevation">
|
|
||||||
{stats.gunFrontalElevation!}
|
|
||||||
</InfoWithDelta>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{gunModelDefinition.pitch.back && (
|
|
||||||
<>
|
|
||||||
<InfoWithDelta decimals={1} indent name="Rear depression">
|
|
||||||
{stats.gunRearDepression!}
|
|
||||||
</InfoWithDelta>
|
|
||||||
<InfoWithDelta decimals={1} indent name="Rear elevation">
|
|
||||||
{stats.gunRearElevation!}
|
|
||||||
</InfoWithDelta>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{turretModelDefinition.yaw && (
|
|
||||||
<>
|
|
||||||
<InfoWithDelta decimals={1} indent name="Azimuth left">
|
|
||||||
{stats.azimuthLeft!}
|
|
||||||
</InfoWithDelta>
|
|
||||||
<InfoWithDelta decimals={1} indent name="Azimuth right">
|
|
||||||
{stats.azimuthRight!}
|
|
||||||
</InfoWithDelta>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
<Flex direction="column" gap="2">
|
|
||||||
<Heading size="5">Maneuverability</Heading>
|
|
||||||
<Info name="Speed" unit="km/hr" />
|
|
||||||
<InfoWithDelta decimals={0} indent name="Forwards">
|
|
||||||
{stats.speedForwards}
|
|
||||||
</InfoWithDelta>
|
|
||||||
<InfoWithDelta decimals={0} indent name="Backwards">
|
|
||||||
{stats.speedBackwards}
|
|
||||||
</InfoWithDelta>
|
|
||||||
<InfoWithDelta decimals={0} name="Engine power" unit="hp">
|
|
||||||
{stats.enginePower}
|
|
||||||
</InfoWithDelta>
|
|
||||||
<InfoWithDelta
|
|
||||||
decimals={1}
|
|
||||||
name="Weight"
|
|
||||||
unit="tn"
|
|
||||||
deltaType="lowerIsBetter"
|
|
||||||
>
|
|
||||||
{stats.weight}
|
|
||||||
</InfoWithDelta>
|
|
||||||
<Info name="Power to weight ratio" unit="hp/tn" />
|
|
||||||
<InfoWithDelta decimals={1} indent name="On hard terrain">
|
|
||||||
{stats.powerToWeightRatioHardTerrain}
|
|
||||||
</InfoWithDelta>
|
|
||||||
<InfoWithDelta decimals={1} indent name="On medium terrain">
|
|
||||||
{stats.powerToWeightRatioMediumTerrain}
|
|
||||||
</InfoWithDelta>
|
|
||||||
<InfoWithDelta decimals={1} indent name="On soft terrain">
|
|
||||||
{stats.powerToWeightRatioSoftTerrain}
|
|
||||||
</InfoWithDelta>
|
|
||||||
<InfoWithDelta name="Turret traverse speed" unit="°/s" decimals={1}>
|
|
||||||
{stats.turretTraverseSpeed}
|
|
||||||
</InfoWithDelta>
|
|
||||||
<Info name="Hull traverse speed" unit="°/s" />
|
|
||||||
<InfoWithDelta decimals={1} indent name="On hard terrain">
|
|
||||||
{stats.hullTraverseHardTerrain}
|
|
||||||
</InfoWithDelta>
|
|
||||||
<InfoWithDelta decimals={1} indent name="On medium terrain">
|
|
||||||
{stats.hullTraverseMediumTerrain}
|
|
||||||
</InfoWithDelta>
|
|
||||||
<InfoWithDelta decimals={1} indent name="On soft terrain">
|
|
||||||
{stats.hullTraverseSoftTerrain}
|
|
||||||
</InfoWithDelta>
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
<Flex direction="column" gap="2">
|
|
||||||
<Heading size="5">Survivability</Heading>
|
|
||||||
<InfoWithDelta name="Health" unit="hp" decimals={0}>
|
|
||||||
{stats.health}
|
|
||||||
</InfoWithDelta>
|
|
||||||
<InfoWithDelta
|
|
||||||
name="Fire chance"
|
|
||||||
unit="%"
|
|
||||||
deltaType="lowerIsBetter"
|
|
||||||
decimals={0}
|
|
||||||
>
|
|
||||||
{stats.fireChance * 100}
|
|
||||||
</InfoWithDelta>
|
|
||||||
<InfoWithDelta name="View range" unit="m" decimals={0}>
|
|
||||||
{stats.viewRange}
|
|
||||||
</InfoWithDelta>
|
|
||||||
<Info name="Camouflage" unit="%" />
|
|
||||||
<InfoWithDelta indent name="Still" decimals={0}>
|
|
||||||
{stats.camouflageStill * 100}
|
|
||||||
</InfoWithDelta>
|
|
||||||
<InfoWithDelta indent name="Moving" decimals={0}>
|
|
||||||
{stats.camouflageMoving * 100}
|
|
||||||
</InfoWithDelta>
|
|
||||||
<InfoWithDelta indent name="Shooting still" decimals={0}>
|
|
||||||
{stats.camouflageShootingStill * 100}
|
|
||||||
</InfoWithDelta>
|
|
||||||
<InfoWithDelta indent name="Shooting moving" decimals={0}>
|
|
||||||
{stats.camouflageShootingMoving * 100}
|
|
||||||
</InfoWithDelta>
|
|
||||||
<InfoWithDelta indent name="Caught on fire" decimals={0}>
|
|
||||||
{stats.camouflageCaughtOnFire * 100}
|
|
||||||
</InfoWithDelta>
|
|
||||||
<Info name="Width" unit="m" decimals={0} deltaType="lowerIsBetter">
|
|
||||||
{stats.width}
|
|
||||||
</Info>
|
|
||||||
<Info name="Height" unit="m" decimals={0} deltaType="lowerIsBetter">
|
|
||||||
{stats.height}
|
|
||||||
</Info>
|
|
||||||
<Info name="Length" unit="m" decimals={0} deltaType="lowerIsBetter">
|
|
||||||
{stats.length}
|
|
||||||
</Info>
|
|
||||||
<Info name="Volume" unit="m" decimals={0} deltaType="lowerIsBetter">
|
|
||||||
{stats.volume}
|
|
||||||
</Info>
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
<Flex direction="column" gap="2">
|
|
||||||
<Flex gap="2" align="center">
|
|
||||||
<Heading size="5">Crew training</Heading>
|
|
||||||
|
|
||||||
<Popover.Root>
|
|
||||||
<Popover.Trigger>
|
|
||||||
<IconButton variant="ghost">
|
|
||||||
<InfoCircledIcon />
|
|
||||||
</IconButton>
|
|
||||||
</Popover.Trigger>
|
|
||||||
|
|
||||||
<Popover.Content>
|
|
||||||
<Flex gap="1" align="center">
|
|
||||||
<AccessibilityIcon />
|
|
||||||
<Text>represents the other roles a crew member can take.</Text>
|
|
||||||
</Flex>
|
|
||||||
</Popover.Content>
|
|
||||||
</Popover.Root>
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
{tank.crew.map((member) => {
|
|
||||||
const count = member.count ?? 1;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Fragment key={member.type}>
|
|
||||||
<InfoWithDelta
|
<InfoWithDelta
|
||||||
key={`${member.type}-root`}
|
indent
|
||||||
name={`${CREW_MEMBER_NAMES[member.type]}${count > 1 ? ` x ${count}` : ''}`}
|
|
||||||
unit="%"
|
|
||||||
decimals={0}
|
decimals={0}
|
||||||
|
name={`At ${penetrationDistance}m`}
|
||||||
>
|
>
|
||||||
{(member.type === 'commander'
|
{lerp(
|
||||||
? commanderMastery
|
shell.penetration[0],
|
||||||
: crewMemberMastery) * 100}
|
shell.penetration[1],
|
||||||
|
(penetrationDistance *
|
||||||
|
penetrationLossOverDistanceCoefficient) /
|
||||||
|
500,
|
||||||
|
) * penetrationCoefficient}
|
||||||
</InfoWithDelta>
|
</InfoWithDelta>
|
||||||
{member.substitute && (
|
<Flex align="center" gap="2" style={{ paddingLeft: 24 }}>
|
||||||
<Info
|
<Text>Distance</Text>
|
||||||
key={`${member.type}-substitute`}
|
<Slider
|
||||||
indent
|
key={penetrationDistance}
|
||||||
name={
|
min={0}
|
||||||
<>
|
max={500}
|
||||||
<Flex align="center" gap="1">
|
style={{ flex: 1 }}
|
||||||
<AccessibilityIcon />
|
defaultValue={[penetrationDistance]}
|
||||||
{member.substitute
|
onValueChange={debounce(([value]) => {
|
||||||
.map((sub, index) =>
|
setPenetrationDistance(value);
|
||||||
index === 0 ? CREW_MEMBER_NAMES[sub] : sub,
|
}, 500)}
|
||||||
)
|
|
||||||
.join(', ')}
|
|
||||||
</Flex>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
)}
|
<TextField.Root
|
||||||
</Fragment>
|
style={{ width: 64 }}
|
||||||
);
|
ref={penetrationDistanceInput}
|
||||||
})}
|
defaultValue={penetrationDistance}
|
||||||
|
onBlur={() => {
|
||||||
|
setPenetrationDistance(
|
||||||
|
Math.min(
|
||||||
|
parseInt(penetrationDistanceInput.current!.value),
|
||||||
|
500,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
event.currentTarget.blur();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TextField.Slot side="right">m</TextField.Slot>
|
||||||
|
</TextField.Root>
|
||||||
|
</Flex>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<InfoWithDelta name="Damage" unit="hp" decimals={0}>
|
||||||
|
{stats.damage}
|
||||||
|
</InfoWithDelta>
|
||||||
|
<InfoWithDelta name="Module damage" unit="hp" decimals={0}>
|
||||||
|
{stats.moduleDamage}
|
||||||
|
</InfoWithDelta>
|
||||||
|
{isExplosive(shell.type) && (
|
||||||
|
<InfoWithDelta name="Splash radius" unit="m" decimals={0}>
|
||||||
|
{stats.explosionRadius!}
|
||||||
|
</InfoWithDelta>
|
||||||
|
)}
|
||||||
|
<InfoWithDelta name="Caliber" decimals={0} unit="mm">
|
||||||
|
{stats.caliber}
|
||||||
|
</InfoWithDelta>
|
||||||
|
<InfoWithDelta name="Normalization" decimals={0} unit="°">
|
||||||
|
{stats.shellNormalization}
|
||||||
|
</InfoWithDelta>
|
||||||
|
{shell.type !== 'he' && (
|
||||||
|
<InfoWithDelta
|
||||||
|
name="Ricochet"
|
||||||
|
decimals={0}
|
||||||
|
deltaType="lowerIsBetter"
|
||||||
|
unit="°"
|
||||||
|
>
|
||||||
|
{stats.shellRicochet!}
|
||||||
|
</InfoWithDelta>
|
||||||
|
)}
|
||||||
|
<InfoWithDelta name="Shell velocity" unit="m/s" decimals={0}>
|
||||||
|
{stats.shellVelocity}
|
||||||
|
</InfoWithDelta>
|
||||||
|
<InfoWithDelta
|
||||||
|
decimals={2}
|
||||||
|
name="Aim time"
|
||||||
|
unit="s"
|
||||||
|
deltaType="lowerIsBetter"
|
||||||
|
>
|
||||||
|
{stats.aimTime}
|
||||||
|
</InfoWithDelta>
|
||||||
|
<Info name="Dispersion at 100m" />
|
||||||
|
<InfoWithDelta
|
||||||
|
decimals={3}
|
||||||
|
indent
|
||||||
|
name="Still"
|
||||||
|
unit="m"
|
||||||
|
deltaType="lowerIsBetter"
|
||||||
|
>
|
||||||
|
{stats.dispersion}
|
||||||
|
</InfoWithDelta>
|
||||||
|
<InfoWithDelta
|
||||||
|
prefix="+ "
|
||||||
|
decimals={3}
|
||||||
|
indent
|
||||||
|
name="Moving"
|
||||||
|
deltaType="lowerIsBetter"
|
||||||
|
>
|
||||||
|
{stats.dispersionMoving}
|
||||||
|
</InfoWithDelta>
|
||||||
|
<InfoWithDelta
|
||||||
|
decimals={3}
|
||||||
|
prefix="+ "
|
||||||
|
indent
|
||||||
|
name="Hull traversing"
|
||||||
|
deltaType="lowerIsBetter"
|
||||||
|
>
|
||||||
|
{stats.dispersionHullTraversing}
|
||||||
|
</InfoWithDelta>
|
||||||
|
<InfoWithDelta
|
||||||
|
decimals={3}
|
||||||
|
prefix="+ "
|
||||||
|
indent
|
||||||
|
name="Turret traversing"
|
||||||
|
deltaType="lowerIsBetter"
|
||||||
|
>
|
||||||
|
{stats.dispersionTurretTraversing}
|
||||||
|
</InfoWithDelta>
|
||||||
|
<InfoWithDelta
|
||||||
|
decimals={3}
|
||||||
|
prefix="+ "
|
||||||
|
indent
|
||||||
|
name="After shooting"
|
||||||
|
deltaType="lowerIsBetter"
|
||||||
|
>
|
||||||
|
{stats.dispersionShooting}
|
||||||
|
</InfoWithDelta>
|
||||||
|
<InfoWithDelta
|
||||||
|
decimals={1}
|
||||||
|
prefix="x "
|
||||||
|
indent
|
||||||
|
name="Gun damaged"
|
||||||
|
unit="scalar"
|
||||||
|
deltaType="lowerIsBetter"
|
||||||
|
>
|
||||||
|
{stats.dispersionGunDamaged}
|
||||||
|
</InfoWithDelta>
|
||||||
|
<Info name="Gun flexibility" unit="°" />
|
||||||
|
<InfoWithDelta decimals={1} indent name="Depression">
|
||||||
|
{stats.gunDepression}
|
||||||
|
</InfoWithDelta>
|
||||||
|
<InfoWithDelta decimals={1} indent name="Elevation">
|
||||||
|
{stats.gunElevation}
|
||||||
|
</InfoWithDelta>
|
||||||
|
{gunModelDefinition.pitch.front && (
|
||||||
|
<>
|
||||||
|
<InfoWithDelta decimals={1} indent name="Frontal depression">
|
||||||
|
{stats.gunFrontalDepression!}
|
||||||
|
</InfoWithDelta>
|
||||||
|
<InfoWithDelta decimals={1} indent name="Frontal elevation">
|
||||||
|
{stats.gunFrontalElevation!}
|
||||||
|
</InfoWithDelta>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{gunModelDefinition.pitch.back && (
|
||||||
|
<>
|
||||||
|
<InfoWithDelta decimals={1} indent name="Rear depression">
|
||||||
|
{stats.gunRearDepression!}
|
||||||
|
</InfoWithDelta>
|
||||||
|
<InfoWithDelta decimals={1} indent name="Rear elevation">
|
||||||
|
{stats.gunRearElevation!}
|
||||||
|
</InfoWithDelta>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{turretModelDefinition.yaw && (
|
||||||
|
<>
|
||||||
|
<InfoWithDelta decimals={1} indent name="Azimuth left">
|
||||||
|
{stats.azimuthLeft!}
|
||||||
|
</InfoWithDelta>
|
||||||
|
<InfoWithDelta decimals={1} indent name="Azimuth right">
|
||||||
|
{stats.azimuthRight!}
|
||||||
|
</InfoWithDelta>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
<Flex direction="column" gap="2">
|
||||||
|
<Flex gap="2" align="center">
|
||||||
|
<Heading size="5">Crew training</Heading>
|
||||||
|
|
||||||
|
<Popover.Root>
|
||||||
|
<Popover.Trigger>
|
||||||
|
<IconButton variant="ghost">
|
||||||
|
<InfoCircledIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Popover.Trigger>
|
||||||
|
|
||||||
|
<Popover.Content>
|
||||||
|
<Flex gap="1" align="center">
|
||||||
|
<AccessibilityIcon />
|
||||||
|
<Text>
|
||||||
|
represents the other roles a crew member can take.
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
</Popover.Content>
|
||||||
|
</Popover.Root>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
{tank.crew.map((member) => {
|
||||||
|
const count = member.count ?? 1;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment key={member.type}>
|
||||||
|
<InfoWithDelta
|
||||||
|
key={`${member.type}-root`}
|
||||||
|
name={`${CREW_MEMBER_NAMES[member.type]}${count > 1 ? ` x ${count}` : ''}`}
|
||||||
|
unit="%"
|
||||||
|
decimals={0}
|
||||||
|
>
|
||||||
|
{(member.type === 'commander'
|
||||||
|
? commanderMastery
|
||||||
|
: crewMemberMastery) * 100}
|
||||||
|
</InfoWithDelta>
|
||||||
|
{member.substitute && (
|
||||||
|
<Info
|
||||||
|
key={`${member.type}-substitute`}
|
||||||
|
indent
|
||||||
|
name={
|
||||||
|
<>
|
||||||
|
<Flex align="center" gap="1">
|
||||||
|
<AccessibilityIcon />
|
||||||
|
{member.substitute
|
||||||
|
.map((sub, index) =>
|
||||||
|
index === 0 ? CREW_MEMBER_NAMES[sub] : sub,
|
||||||
|
)
|
||||||
|
.join(', ')}
|
||||||
|
</Flex>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<Flex direction="column" gap="8" style={{ flex: 1 }}>
|
||||||
|
<Flex direction="column" gap="2">
|
||||||
|
<Heading size="5">Maneuverability</Heading>
|
||||||
|
<Info name="Speed" unit="km/hr" />
|
||||||
|
<InfoWithDelta decimals={0} indent name="Forwards">
|
||||||
|
{stats.speedForwards}
|
||||||
|
</InfoWithDelta>
|
||||||
|
<InfoWithDelta decimals={0} indent name="Backwards">
|
||||||
|
{stats.speedBackwards}
|
||||||
|
</InfoWithDelta>
|
||||||
|
<InfoWithDelta decimals={0} name="Engine power" unit="hp">
|
||||||
|
{stats.enginePower}
|
||||||
|
</InfoWithDelta>
|
||||||
|
<InfoWithDelta
|
||||||
|
decimals={1}
|
||||||
|
name="Weight"
|
||||||
|
unit="tn"
|
||||||
|
deltaType="lowerIsBetter"
|
||||||
|
>
|
||||||
|
{stats.weight}
|
||||||
|
</InfoWithDelta>
|
||||||
|
<Info name="Power to weight ratio" unit="hp/tn" />
|
||||||
|
<InfoWithDelta decimals={1} indent name="On hard terrain">
|
||||||
|
{stats.powerToWeightRatioHardTerrain}
|
||||||
|
</InfoWithDelta>
|
||||||
|
<InfoWithDelta decimals={1} indent name="On medium terrain">
|
||||||
|
{stats.powerToWeightRatioMediumTerrain}
|
||||||
|
</InfoWithDelta>
|
||||||
|
<InfoWithDelta decimals={1} indent name="On soft terrain">
|
||||||
|
{stats.powerToWeightRatioSoftTerrain}
|
||||||
|
</InfoWithDelta>
|
||||||
|
<InfoWithDelta name="Turret traverse speed" unit="°/s" decimals={1}>
|
||||||
|
{stats.turretTraverseSpeed}
|
||||||
|
</InfoWithDelta>
|
||||||
|
<Info name="Hull traverse speed" unit="°/s" />
|
||||||
|
<InfoWithDelta decimals={1} indent name="On hard terrain">
|
||||||
|
{stats.hullTraverseHardTerrain}
|
||||||
|
</InfoWithDelta>
|
||||||
|
<InfoWithDelta decimals={1} indent name="On medium terrain">
|
||||||
|
{stats.hullTraverseMediumTerrain}
|
||||||
|
</InfoWithDelta>
|
||||||
|
<InfoWithDelta decimals={1} indent name="On soft terrain">
|
||||||
|
{stats.hullTraverseSoftTerrain}
|
||||||
|
</InfoWithDelta>
|
||||||
|
</Flex>
|
||||||
|
<Flex direction="column" gap="2">
|
||||||
|
<Heading size="5">Survivability</Heading>
|
||||||
|
<InfoWithDelta name="Health" unit="hp" decimals={0}>
|
||||||
|
{stats.health}
|
||||||
|
</InfoWithDelta>
|
||||||
|
<InfoWithDelta
|
||||||
|
name="Fire chance"
|
||||||
|
unit="%"
|
||||||
|
deltaType="lowerIsBetter"
|
||||||
|
decimals={0}
|
||||||
|
>
|
||||||
|
{stats.fireChance * 100}
|
||||||
|
</InfoWithDelta>
|
||||||
|
<InfoWithDelta name="View range" unit="m" decimals={0}>
|
||||||
|
{stats.viewRange}
|
||||||
|
</InfoWithDelta>
|
||||||
|
<Info name="Camouflage" unit="%" />
|
||||||
|
<InfoWithDelta indent name="Still" decimals={0}>
|
||||||
|
{stats.camouflageStill * 100}
|
||||||
|
</InfoWithDelta>
|
||||||
|
<InfoWithDelta indent name="Moving" decimals={0}>
|
||||||
|
{stats.camouflageMoving * 100}
|
||||||
|
</InfoWithDelta>
|
||||||
|
<InfoWithDelta indent name="Shooting still" decimals={0}>
|
||||||
|
{stats.camouflageShootingStill * 100}
|
||||||
|
</InfoWithDelta>
|
||||||
|
<InfoWithDelta indent name="Shooting moving" decimals={0}>
|
||||||
|
{stats.camouflageShootingMoving * 100}
|
||||||
|
</InfoWithDelta>
|
||||||
|
<InfoWithDelta indent name="Caught on fire" decimals={0}>
|
||||||
|
{stats.camouflageCaughtOnFire * 100}
|
||||||
|
</InfoWithDelta>
|
||||||
|
<Info name="Width" unit="m" decimals={0} deltaType="lowerIsBetter">
|
||||||
|
{stats.width}
|
||||||
|
</Info>
|
||||||
|
<Info name="Height" unit="m" decimals={0} deltaType="lowerIsBetter">
|
||||||
|
{stats.height}
|
||||||
|
</Info>
|
||||||
|
<Info name="Length" unit="m" decimals={0} deltaType="lowerIsBetter">
|
||||||
|
{stats.length}
|
||||||
|
</Info>
|
||||||
|
<Info name="Volume" unit="m" decimals={0} deltaType="lowerIsBetter">
|
||||||
|
{stats.volume}
|
||||||
|
</Info>
|
||||||
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Flex, Heading } from '@radix-ui/themes';
|
import { Flex, Heading } from '@radix-ui/themes';
|
||||||
import PageWrapper from '../../../../../../components/PageWrapper';
|
|
||||||
import { useWideFormat } from '../../../../../../hooks/useWideFormat';
|
import { useWideFormat } from '../../../../../../hooks/useWideFormat';
|
||||||
import { Characteristics } from '../Characteristics';
|
import { Characteristics } from '../Characteristics';
|
||||||
import { Consumables } from '../Characteristics/components/Consumables';
|
import { Consumables } from '../Characteristics/components/Consumables';
|
||||||
@@ -10,16 +9,25 @@ import { Provisions } from '../Characteristics/components/Provisions';
|
|||||||
import { Skills } from '../Characteristics/components/Skills';
|
import { Skills } from '../Characteristics/components/Skills';
|
||||||
|
|
||||||
export function CharacteristicsSection() {
|
export function CharacteristicsSection() {
|
||||||
const wideFormat = useWideFormat(720);
|
const wideFormat = useWideFormat(700);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageWrapper >
|
<Flex
|
||||||
|
align="center"
|
||||||
|
px="4"
|
||||||
|
style={{
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
}}
|
||||||
|
direction={wideFormat ? 'row' : 'column'}
|
||||||
|
mt="4"
|
||||||
|
>
|
||||||
<Flex
|
<Flex
|
||||||
gap="6"
|
gap="6"
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
direction={wideFormat ? 'row' : 'column'}
|
direction={wideFormat ? 'row' : 'column'}
|
||||||
|
justify="center"
|
||||||
>
|
>
|
||||||
<Flex style={{ flex: 1, maxWidth: 480 }} direction="column" gap="4">
|
<Flex style={{ flex: 1, maxWidth: 380 }} direction="column" gap="4">
|
||||||
<Heading>Configure</Heading>
|
<Heading>Configure</Heading>
|
||||||
<Modules />
|
<Modules />
|
||||||
<Equipment />
|
<Equipment />
|
||||||
@@ -30,7 +38,11 @@ export function CharacteristicsSection() {
|
|||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Flex
|
<Flex
|
||||||
style={{ flex: 1, width: wideFormat ? undefined : '100%' }}
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
maxWidth: wideFormat ? 640 : undefined,
|
||||||
|
minWidth: 320,
|
||||||
|
}}
|
||||||
direction="column"
|
direction="column"
|
||||||
gap="4"
|
gap="4"
|
||||||
>
|
>
|
||||||
@@ -38,6 +50,6 @@ export function CharacteristicsSection() {
|
|||||||
<Characteristics />
|
<Characteristics />
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
</PageWrapper>
|
</Flex>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -25,7 +25,7 @@ export function HeroSection() {
|
|||||||
style={{
|
style={{
|
||||||
background: `linear-gradient(${wideFormat ? -90 : 180}deg, ${theme.colors.appBackground1}, ${theme.colors.appBackground2})`,
|
background: `linear-gradient(${wideFormat ? -90 : 180}deg, ${theme.colors.appBackground1}, ${theme.colors.appBackground2})`,
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
height: wideFormat ? undefined : 'calc(87.5vh - 64px)',
|
height: wideFormat ? undefined : 'calc(75vh - 64px)',
|
||||||
}}
|
}}
|
||||||
gap="4"
|
gap="4"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -124,7 +124,12 @@ export function TechTreeSection() {
|
|||||||
<PageWrapper
|
<PageWrapper
|
||||||
style={{
|
style={{
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
overflow: 'hidden',
|
}}
|
||||||
|
containerProps={{
|
||||||
|
style: {
|
||||||
|
overflow: 'hidden',
|
||||||
|
marginTop: 16,
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
size={tank.ancestors && tank.successors ? 880 : 480}
|
size={tank.ancestors && tank.successors ? 880 : 480}
|
||||||
gap="0"
|
gap="0"
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ type PageWrapperProps = FlexProps & {
|
|||||||
noPadding?: boolean;
|
noPadding?: boolean;
|
||||||
noMaxWidth?: boolean;
|
noMaxWidth?: boolean;
|
||||||
highlight?: boolean;
|
highlight?: boolean;
|
||||||
|
containerProps?: ComponentProps<typeof Theme>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function PageWrapper({
|
export default function PageWrapper({
|
||||||
@@ -18,6 +19,7 @@ export default function PageWrapper({
|
|||||||
noPadding = false,
|
noPadding = false,
|
||||||
noMaxWidth = false,
|
noMaxWidth = false,
|
||||||
highlight = false,
|
highlight = false,
|
||||||
|
containerProps,
|
||||||
...props
|
...props
|
||||||
}: PageWrapperProps) {
|
}: PageWrapperProps) {
|
||||||
return (
|
return (
|
||||||
@@ -30,7 +32,9 @@ export default function PageWrapper({
|
|||||||
background: highlight
|
background: highlight
|
||||||
? `linear-gradient(-90deg, ${theme.colors.appBackground1}, ${theme.colors.appBackground2})`
|
? `linear-gradient(-90deg, ${theme.colors.appBackground1}, ${theme.colors.appBackground2})`
|
||||||
: undefined,
|
: undefined,
|
||||||
|
...containerProps?.style,
|
||||||
}}
|
}}
|
||||||
|
{...containerProps}
|
||||||
>
|
>
|
||||||
<Flex
|
<Flex
|
||||||
direction="column"
|
direction="column"
|
||||||
|
|||||||
Reference in new issue
Block a user