feat: armor toggle buttons

This commit is contained in:
TrèsAbhi committed 2024-07-29 14:08:52 -05:00
1 parent cddfef1b9f
commit f86a4022ea
9 files changed
+353 -215

No files matched your search

@@ -1,8 +1,11 @@
import { ChevronLeftIcon, MixIcon } from '@radix-ui/react-icons';
import { Box, Button, Flex, Heading, Text } from '@radix-ui/themes';
import Link from 'next/link';
import { Suspense, useEffect, useState } from 'react';
import { Suspense, use, useEffect, useMemo, useState } from 'react';
import { classIcons } from '../../../../../../components/ClassIcon';
import { ThicknessRange } from '../../../../../../components/StaticArmor/components/StaticArmorScene';
import { modelDefinitions } from '../../../../../../core/blitzkit/modelDefinitions';
import { tankDefinitions } from '../../../../../../core/blitzkit/tankDefinitions';
import { TIER_ROMAN_NUMERALS } from '../../../../../../core/blitzkit/tankDefinitions/constants';
import { useFullScreen } from '../../../../../../hooks/useFullScreen';
import strings from '../../../../../../lang/en-US.json';
@@ -11,7 +14,11 @@ import { Options } from './components/Options';
import { TankSandbox } from './TankSandbox';
import { TankSandboxLoader } from './TankSandboxLoader';
export function HeroSection({ id }: { id: number }) {
interface HeroSectionProps {
id: number;
}
export function HeroSection({ id }: HeroSectionProps) {
const protagonist = Duel.use((state) => state.protagonist.tank);
const antagonist = Duel.use((state) => state.antagonist.tank);
const compareTanks =
@@ -27,6 +34,50 @@ export function HeroSection({ id }: { id: number }) {
: protagonist.treeType === 'premium'
? 'amber'
: undefined;
const awaitedTankDefinitions = use(tankDefinitions);
const awaitedModelDefinitions = use(modelDefinitions);
const tank = awaitedTankDefinitions[id];
const thicknessRange = useMemo(() => {
const thicknesses = Object.values(awaitedTankDefinitions)
.filter((thisTank) => thisTank.tier === tank.tier)
.map((tank) => ({
model: awaitedModelDefinitions[tank.id],
id: tank.id,
}))
.filter(Boolean)
.map(({ model, id }) => {
const tankThicknesses = Object.values(model.armor.thickness);
const turretThicknesses = Object.values(model.turrets)
.map((turret) => {
const turretThicknesses = Object.values(turret.armor.thickness);
const gunThicknesses = Object.values(turret.guns)
.map((gun) => Object.values(gun.armor.thickness))
.flat();
return [...turretThicknesses, ...gunThicknesses];
})
.flat();
const totalThicknesses = [...tankThicknesses, ...turretThicknesses];
return totalThicknesses;
})
.flat();
const sortedThicknesses = thicknesses.sort((a, b) => a - b);
const quartileIndex = (1 - 2 ** -6) * (sortedThicknesses.length + 1) - 1;
let quartile: number;
if (Number.isInteger(quartileIndex)) {
quartile = sortedThicknesses[quartileIndex];
} else {
quartile =
(sortedThicknesses[Math.floor(quartileIndex)] +
sortedThicknesses[Math.ceil(quartileIndex)]) /
2;
}
return { quartile } satisfies ThicknessRange;
}, [tank.tier]);
useEffect(() => {
setDummyLoader(false);
@@ -129,11 +180,11 @@ export function HeroSection({ id }: { id: number }) {
/>
}
>
<TankSandbox />
<TankSandbox thicknessRange={thicknessRange} />
</Suspense>
</Box>
<Options />
<Options thicknessRange={thicknessRange} />
</Box>
</Box>
</Flex>
@@ -4,6 +4,7 @@ import { Armor } from '../../../../../../components/Armor';
import { ArmorPlateDisplay } from '../../../../../../components/Armor/components/ArmorPlateDisplay';
import { ShotDisplay } from '../../../../../../components/Armor/components/ShotDisplay';
import { StaticArmor } from '../../../../../../components/StaticArmor';
import { ThicknessRange } from '../../../../../../components/StaticArmor/components/StaticArmorScene';
import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits';
import { modelDefinitions } from '../../../../../../core/blitzkit/modelDefinitions';
import { modelTransformEvent } from '../../../../../../core/blitzkit/modelTransform';
@@ -20,7 +21,11 @@ import { SceneProps } from '../SceneProps';
import { ModelLoader } from './components/ModelLoader';
import { TankModel } from './components/TankModel';
export function TankSandbox() {
interface TankSandboxProps {
thicknessRange: ThicknessRange;
}
export function TankSandbox({ thicknessRange }: TankSandboxProps) {
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
const canvas = useRef<HTMLCanvasElement>(null);
const hasImprovedVerticalStabilizer = useEquipment(122);
@@ -165,7 +170,7 @@ export function TankSandbox() {
<Suspense fallback={<ModelLoader />}>
{display === TankopediaDisplay.DynamicArmor && <Armor />}
{display === TankopediaDisplay.StaticArmor && (
<StaticArmor awaitedTankDefinitions={awaitedTankDefinitions} />
<StaticArmor thicknessRange={thicknessRange} />
)}
<Lighting />
</Suspense>
@@ -22,6 +22,7 @@ import {
import { useEffect, useState } from 'react';
import { ModuleButton } from '../../../../../../../components/ModuleButtons/ModuleButton';
import { SmallTankIcon } from '../../../../../../../components/SmallTankIcon';
import { ThicknessRange } from '../../../../../../../components/StaticArmor/components/StaticArmorScene';
import { resolveNearPenetration } from '../../../../../../../core/blitz/resolveNearPenetration';
import { resolvePenetrationCoefficient } from '../../../../../../../core/blitz/resolvePenetrationCoefficient';
import { asset } from '../../../../../../../core/blitzkit/asset';
@@ -42,9 +43,14 @@ import * as TankopediaPersistent from '../../../../../../../stores/tankopediaPer
import { TankopediaDisplay } from '../../../../../../../stores/tankopediaPersistent/constants';
import { TankSearch } from '../../../../components/TankSearch';
import { ENVIRONMENTS } from '../../Lighting';
import { RotationInputs } from '../../QuickInputs';
import { QuickInputs } from '../../QuickInputs';
import { Thicknesses } from '../../Thicknesses';
export function Options() {
interface OptionsProps {
thicknessRange: ThicknessRange;
}
export function Options({ thicknessRange }: OptionsProps) {
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
const mutateTankopediaPersistent = TankopediaPersistent.useMutation();
const displayRaw = TankopediaPersistent.use((state) => state.display);
@@ -113,7 +119,11 @@ export function Options() {
</Box>
)}
<RotationInputs />
<QuickInputs />
{display === TankopediaDisplay.StaticArmor && (
<Thicknesses thicknessRange={thicknessRange} />
)}
{display === TankopediaDisplay.DynamicArmor && (
<Flex
@@ -483,17 +493,17 @@ export function Options() {
>
<SegmentedControl.Item value={`${TankopediaDisplay.Model}`}>
<Flex height="100%" align="center">
<img src={imgur('jAdYf0m')} style={{ height: '1.5em' }} />
<img src={imgur('jAdYf0m')} style={{ height: '1.25em' }} />
</Flex>
</SegmentedControl.Item>
<SegmentedControl.Item value={`${TankopediaDisplay.DynamicArmor}`}>
<Flex height="100%" align="center">
<img src={imgur('oe4Cq0g')} style={{ height: '1.5em' }} />
<img src={imgur('oe4Cq0g')} style={{ height: '1.25em' }} />
</Flex>
</SegmentedControl.Item>
<SegmentedControl.Item value={`${TankopediaDisplay.StaticArmor}`}>
<Flex height="100%" align="center">
<img src={imgur('VQ4uDno')} style={{ height: '1.5em' }} />
<img src={imgur('VQ4uDno')} style={{ height: '1.25em' }} />
</Flex>
</SegmentedControl.Item>
</SegmentedControl.Root>
@@ -9,12 +9,10 @@ import {
ModelTransformEventData,
} from '../../../../../../core/blitzkit/modelTransform';
import { useEquipment } from '../../../../../../hooks/useEquipment';
import { useFullScreen } from '../../../../../../hooks/useFullScreen';
import * as Duel from '../../../../../../stores/duel';
import * as TankopediaPersistent from '../../../../../../stores/tankopediaPersistent';
import * as styles from './index.css';
export function RotationInputs() {
export function QuickInputs() {
const mutateDuel = Duel.useMutation();
const protagonist = Duel.use((state) => state.protagonist);
const awaitedModelDefinitions = use(modelDefinitions);
@@ -26,8 +24,6 @@ export function RotationInputs() {
const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id];
const initialGunPitch = tankModelDefinition.turretRotation?.pitch ?? 0;
const hasImprovedVerticalStabilizer = useEquipment(122);
const isFullScreen = useFullScreen();
const mutateTankopediaPersistent = TankopediaPersistent.useMutation();
useEffect(() => {
yawInput.current!.value = radToDeg(protagonist.yaw).toFixed(1);
@@ -0,0 +1,130 @@
import { EyeClosedIcon, EyeOpenIcon } from '@radix-ui/react-icons';
import { Box, Flex, Text } from '@radix-ui/themes';
import { ThicknessRange } from '../../../../../components/StaticArmor/components/StaticArmorScene';
import * as TankopediaPersistent from '../../../../../stores/tankopediaPersistent';
interface ThicknessesProps {
thicknessRange: ThicknessRange;
}
export function Thicknesses({ thicknessRange }: ThicknessesProps) {
const showExternalModules = TankopediaPersistent.use(
(state) => state.model.visual.showExternalModules,
);
const showSpacedArmor = TankopediaPersistent.use(
(state) => state.model.visual.showSpacedArmor,
);
const showCoreArmor = TankopediaPersistent.use(
(state) => state.model.visual.showCoreArmor,
);
const mutateTankopediaPersistent = TankopediaPersistent.useMutation();
return (
<Flex
position="absolute"
right="0"
top="50%"
mr="4"
style={{ transform: 'translateY(-50%)' }}
direction="column"
gap="2"
align="end"
>
<Flex
height="64px"
gap="2"
style={{ opacity: showCoreArmor ? 1 : 0.5, cursor: 'pointer' }}
onClick={() => {
mutateTankopediaPersistent((draft) => {
draft.model.visual.showCoreArmor =
!draft.model.visual.showCoreArmor;
});
}}
>
<Flex direction="column" align="end" justify="between">
<Text color="gray" size="1">
{thicknessRange.quartile.toFixed(0)}
</Text>
<Flex gap="1">
{showCoreArmor ? <EyeOpenIcon /> : <EyeClosedIcon />}
<Text size="1">Core</Text>
</Flex>
<Text color="gray" size="1">
0
</Text>
</Flex>
<Box
width="16px"
style={{
borderRadius: 'var(--radius-full)',
overflow: 'hidden',
background: `linear-gradient(rgb(255, 0, 0), rgb(0, 255, 0))`,
}}
/>
</Flex>
<Flex
height="64px"
gap="2"
style={{ opacity: showSpacedArmor ? 1 : 0.5, cursor: 'pointer' }}
onClick={() => {
mutateTankopediaPersistent((draft) => {
draft.model.visual.showSpacedArmor =
!draft.model.visual.showSpacedArmor;
});
}}
>
<Flex direction="column" align="end" justify="between">
<Text color="gray" size="1">
{thicknessRange.quartile.toFixed(0)}
</Text>
<Flex gap="1">
{showSpacedArmor ? <EyeOpenIcon /> : <EyeClosedIcon />}
<Text size="1">Spaced</Text>
</Flex>
<Text color="gray" size="1">
0
</Text>
</Flex>
<Box
width="16px"
style={{
borderRadius: 'var(--radius-full)',
overflow: 'hidden',
background: `linear-gradient(rgb(32, 0, 223), rgb(255, 0, 255))`,
}}
/>
</Flex>
<Flex
gap="2"
style={{ opacity: showExternalModules ? 1 : 0.5, cursor: 'pointer' }}
onClick={() => {
mutateTankopediaPersistent((draft) => {
draft.model.visual.showExternalModules =
!draft.model.visual.showExternalModules;
});
}}
>
<Flex direction="column" align="end" justify="between">
<Flex gap="1">
{showExternalModules ? <EyeOpenIcon /> : <EyeClosedIcon />}
<Text size="1">External</Text>
</Flex>
</Flex>
<Box
width="16px"
height="16px"
style={{
borderRadius: 'var(--radius-full)',
overflow: 'hidden',
backgroundColor: 'rgb(0, 64, 128)',
}}
/>
</Flex>
</Flex>
);
}