feat: relative stats

This commit is contained in:
tresabhi committed 2024-11-12 16:11:10 -06:00
1 parent bc60b4b42c
commit ffe6ccaed0
3 files changed
+123 -23

No files matched your search

@@ -1,5 +1,5 @@
import { CaretDownIcon, CaretUpIcon } from '@radix-ui/react-icons'; import { CaretDownIcon, CaretUpIcon } from '@radix-ui/react-icons';
import { Flex, Separator, Text } from '@radix-ui/themes'; import { Box, Flex, Text } from '@radix-ui/themes';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
export interface InfoProps { export interface InfoProps {
@@ -24,11 +24,7 @@ export function Info({
deltaType = 'higherIsBetter', deltaType = 'higherIsBetter',
}: InfoProps) { }: InfoProps) {
return ( return (
<Flex <Flex align="center" pl={indent ? '2' : '0'} width="100%" gap="4">
align="center"
style={{ width: '100%', paddingLeft: indent ? 24 : 0 }}
gap="4"
>
<Text color="gray"> <Text color="gray">
{name} {name}
{unit != undefined && ( {unit != undefined && (
@@ -41,7 +37,7 @@ export function Info({
)} )}
</Text> </Text>
{children !== undefined && <Separator style={{ flex: 1 }} />} <Box flexGrow="1" />
{children !== undefined && ( {children !== undefined && (
<Flex align="center" gap="1"> <Flex align="center" gap="1">
@@ -1,20 +1,115 @@
import type { TankCharacteristics } from '../../../../../../core/blitzkit/tankCharacteristics'; import {
createDefaultSkills,
fetchEquipmentDefinitions,
fetchModelDefinitions,
fetchProvisionDefinitions,
fetchSkillDefinitions,
fetchTankDefinitions,
} from '@blitzkit/core';
import { Flex, Progress, Text } from '@radix-ui/themes';
import { useMemo } from 'react';
import {
tankCharacteristics,
type TankCharacteristics,
} from '../../../../../../core/blitzkit/tankCharacteristics';
import { tankToDuelMember } from '../../../../../../core/blitzkit/tankToDuelMember';
import { useDelta } from '../../../../../../hooks/useDelta'; import { useDelta } from '../../../../../../hooks/useDelta';
import { type InfoProps, Info } from './Info'; import { Duel } from '../../../../../../stores/duel';
import { Info, type InfoProps } from './Info';
interface InfoWithDeltaProps extends InfoProps { interface InfoWithDeltaProps extends InfoProps {
stats: TankCharacteristics; stats: TankCharacteristics;
value: keyof TankCharacteristics | (() => number | undefined); value:
| keyof TankCharacteristics
| ((tank: TankCharacteristics) => number | undefined);
noRanking?: boolean;
} }
export function InfoWithDelta({ value, stats, ...props }: InfoWithDeltaProps) { const tankDefinitions = await fetchTankDefinitions();
const provisionDefinitions = await fetchProvisionDefinitions();
const equipmentDefinitions = await fetchEquipmentDefinitions();
const tankModelDefinitions = await fetchModelDefinitions();
const skillDefinitions = await fetchSkillDefinitions();
export function InfoWithDelta({
value,
stats,
indent,
noRanking,
deltaType,
...props
}: InfoWithDeltaProps) {
const uhWhatDoICallThisVariable = const uhWhatDoICallThisVariable =
typeof value === 'function' ? value()! : (stats[value] as number); typeof value === 'function' ? value(stats)! : (stats[value] as number);
const delta = useDelta(uhWhatDoICallThisVariable); const delta = useDelta(uhWhatDoICallThisVariable);
const protagonistTank = Duel.use((state) => state.protagonist.tank);
const others = useMemo(() => {
const defaultSkills = createDefaultSkills(skillDefinitions);
return Object.values(tankDefinitions.tanks)
.filter((tank) => tank.tier === protagonistTank.tier)
.map((tank) => {
const member = tankToDuelMember(tank, provisionDefinitions);
return tankCharacteristics(
{
applyDynamicArmor: false,
applyReactiveArmor: false,
applySpallLiner: false,
camouflage: member.camouflage,
consumables: member.consumables,
crewSkills: defaultSkills,
provisions: member.provisions,
engine: member.engine,
gun: member.gun,
equipmentMatrix: member.equipmentMatrix,
shell: member.shell,
stockEngine: tank.engines[0],
stockGun: tank.turrets[0].guns[0],
stockTrack: tank.tracks[0],
stockTurret: tank.turrets[0],
tank,
track: member.track,
turret: member.turret,
},
{
equipmentDefinitions,
provisionDefinitions,
tankModelDefinition: tankModelDefinitions.models[tank.id],
},
);
})
.filter((tank) => {
const othersValue =
typeof value === 'function' ? value(tank)! : (tank[value] as number);
return othersValue !== undefined;
});
}, []);
const betterTanks = others.filter((tank) => {
const othersValue =
typeof value === 'function' ? value(tank)! : (tank[value] as number);
if (othersValue === undefined) return false;
return deltaType === 'lowerIsBetter'
? othersValue < uhWhatDoICallThisVariable
: othersValue > uhWhatDoICallThisVariable;
});
return ( return (
<Info {...props} delta={delta}> <Flex direction="column">
<Info deltaType={deltaType} indent={indent} {...props} delta={delta}>
{uhWhatDoICallThisVariable} {uhWhatDoICallThisVariable}
</Info> </Info>
{!noRanking && (
<Flex pl={indent ? '2' : '0'} align="center" gap="2">
<Progress variant="soft" size="1" value={50} color="gray" />
<Text color="gray" size="1">
{betterTanks.length + 1} / {others.length}
</Text>
</Flex>
)}
</Flex>
); );
} }
@@ -138,7 +138,7 @@ export function Characteristics() {
}} }}
> >
<Flex direction="column" gap="8" style={{ flex: 1 }}> <Flex direction="column" gap="8" style={{ flex: 1 }}>
<Flex direction="column" gap="2"> <Flex direction="column" gap="4">
<Flex align="center" gap="4"> <Flex align="center" gap="4">
<Heading size="5">Firepower</Heading> <Heading size="5">Firepower</Heading>
@@ -215,6 +215,7 @@ export function Characteristics() {
name={`Shell ${index + 1}`} name={`Shell ${index + 1}`}
decimals={2} decimals={2}
deltaType="lowerIsBetter" deltaType="lowerIsBetter"
noRanking
value={() => reload} value={() => reload}
/> />
))} ))}
@@ -255,6 +256,7 @@ export function Characteristics() {
indent indent
decimals={0} decimals={0}
name={`At ${penetrationDistance}m`} name={`At ${penetrationDistance}m`}
noRanking
value={() => value={() =>
lerp( lerp(
shell.penetration.near, shell.penetration.near,
@@ -331,6 +333,7 @@ export function Characteristics() {
stats={stats} stats={stats}
name="Splash radius" name="Splash radius"
unit="m" unit="m"
noRanking
decimals={0} decimals={0}
value="explosionRadius" value="explosionRadius"
/> />
@@ -347,12 +350,14 @@ export function Characteristics() {
name="Normalization" name="Normalization"
decimals={0} decimals={0}
unit="°" unit="°"
noRanking
value="shellNormalization" value="shellNormalization"
/> />
{!isExplosive(shell.type) && ( {!isExplosive(shell.type) && (
<InfoWithDelta <InfoWithDelta
stats={stats} stats={stats}
name="Ricochet" name="Ricochet"
noRanking
decimals={0} decimals={0}
deltaType="lowerIsBetter" deltaType="lowerIsBetter"
unit="°" unit="°"
@@ -514,7 +519,8 @@ export function Characteristics() {
</> </>
)} )}
</Flex> </Flex>
<Flex direction="column" gap="2">
<Flex direction="column" gap="4">
<Flex gap="2" align="center"> <Flex gap="2" align="center">
<Heading size="5">Crew training</Heading> <Heading size="5">Crew training</Heading>
@@ -554,6 +560,7 @@ export function Characteristics() {
}`} }`}
unit="%" unit="%"
decimals={0} decimals={0}
noRanking
value={() => value={() =>
(member.type === CrewType.COMMANDER (member.type === CrewType.COMMANDER
? commanderMastery ? commanderMastery
@@ -580,6 +587,7 @@ export function Characteristics() {
</Flex> </Flex>
</> </>
} }
noRanking
value={() => commanderMastery * 1.05 * 100} value={() => commanderMastery * 1.05 * 100}
/> />
)} )}
@@ -590,7 +598,7 @@ export function Characteristics() {
</Flex> </Flex>
<Flex direction="column" gap="8" style={{ flex: 1 }}> <Flex direction="column" gap="8" style={{ flex: 1 }}>
<Flex direction="column" gap="2"> <Flex direction="column" gap="4">
<Heading size="5">Maneuverability</Heading> <Heading size="5">Maneuverability</Heading>
<Info name="Speed" unit="kph" /> <Info name="Speed" unit="kph" />
<InfoWithDelta <InfoWithDelta
@@ -726,7 +734,8 @@ export function Characteristics() {
value="hullTraverseSoftTerrain" value="hullTraverseSoftTerrain"
/> />
</Flex> </Flex>
<Flex direction="column" gap="2">
<Flex direction="column" gap="4">
<Heading size="5">Survivability</Heading> <Heading size="5">Survivability</Heading>
<InfoWithDelta <InfoWithDelta
value="health" value="health"
@@ -741,7 +750,7 @@ export function Characteristics() {
unit="%" unit="%"
deltaType="lowerIsBetter" deltaType="lowerIsBetter"
decimals={0} decimals={0}
value={() => stats.fireChance * 100} value={(stats) => stats.fireChance * 100}
/> />
<InfoWithDelta <InfoWithDelta
value="viewRange" value="viewRange"
@@ -752,14 +761,14 @@ export function Characteristics() {
/> />
<Info name="Camouflage" unit="%" /> <Info name="Camouflage" unit="%" />
<InfoWithDelta <InfoWithDelta
value={() => stats.camouflageStill * 100} value={(stats) => stats.camouflageStill * 100}
stats={stats} stats={stats}
indent indent
name="Still" name="Still"
decimals={2} decimals={2}
/> />
<InfoWithDelta <InfoWithDelta
value={() => stats.camouflageMoving * 100} value={(stats) => stats.camouflageMoving * 100}
stats={stats} stats={stats}
indent indent
name="Moving" name="Moving"
@@ -770,21 +779,21 @@ export function Characteristics() {
indent indent
name="Shooting still" name="Shooting still"
decimals={2} decimals={2}
value={() => stats.camouflageShootingStill * 100} value={(stats) => stats.camouflageShootingStill * 100}
/> />
<InfoWithDelta <InfoWithDelta
stats={stats} stats={stats}
indent indent
name="Shooting moving" name="Shooting moving"
decimals={2} decimals={2}
value={() => stats.camouflageShootingMoving * 100} value={(stats) => stats.camouflageShootingMoving * 100}
/> />
<InfoWithDelta <InfoWithDelta
stats={stats} stats={stats}
indent indent
name="Caught on fire" name="Caught on fire"
decimals={2} decimals={2}
value={() => stats.camouflageCaughtOnFire * 100} value={(stats) => stats.camouflageCaughtOnFire * 100}
/> />
<InfoWithDelta <InfoWithDelta
name="Width" name="Width"