feat: bring back stats

This commit is contained in:
TrèsAbhi committed 2024-05-04 13:46:57 -05:00
1 parent 42076c1317
commit 299ccef579
6 files changed
+355 -370

No files matched your search

@@ -1,16 +1,288 @@
import { Button, Flex, Heading } from '@radix-ui/themes';
import { ModuleManager } from '../../../../../../../components/ModuleManager';
import {
Button,
ChevronDownIcon,
Flex,
Heading,
IconButton,
Link,
Text,
} from '@radix-ui/themes';
import { use } from 'react';
import { asset } from '../../../../../../../core/blitzkit/asset';
import {
ModuleDefinition,
ModuleType,
TankDefinition,
tankDefinitions,
Tier,
Unlock,
} from '../../../../../../../core/blitzkit/tankDefinitions';
import { TIER_ROMAN_NUMERALS } from '../../../../../../../core/blitzkit/tankDefinitions/constants';
import { tankIcon } from '../../../../../../../core/blitzkit/tankIcon';
import { mutateDuel, useDuel } from '../../../../../../../stores/duel';
import { mutateTankopediaTemporary } from '../../../../../../../stores/tankopedia';
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
function ModuleButton({
tier,
selected,
unlock,
onClick,
}: {
unlock: Unlock;
tier: Tier;
selected: boolean;
onClick: () => void;
}) {
const numberFormat = Intl.NumberFormat(undefined, {
notation: 'compact',
maximumFractionDigits: 0,
});
const button = (
<IconButton
size="4"
radius="small"
variant={selected ? 'surface' : 'soft'}
color={selected ? undefined : 'gray'}
onClick={onClick}
style={{
position: 'relative',
}}
>
<Text
size="1"
color="gray"
style={{
position: 'absolute',
left: 4,
top: 4,
}}
>
{TIER_ROMAN_NUMERALS[tier]}
</Text>
{unlock.cost.value > 0 && (
<Flex
style={{
position: 'absolute',
left: '50%',
bottom: 4,
transform: 'translateX(-50%)',
}}
align="center"
>
<Text size="1" color="gray">
{numberFormat.format(unlock.cost.value)}
</Text>
<img
src={asset(`icons/currencies/${unlock.cost.type}.webp`)}
style={{
width: 12,
height: 12,
objectFit: 'contain',
}}
/>
</Flex>
)}
<img
src={
unlock.type === 'vehicle'
? tankIcon(unlock.id)
: asset(`icons/modules/${unlock.type}.webp`)
}
style={{
width: 32,
height: 32,
objectFit: 'contain',
}}
/>
</IconButton>
);
return unlock.type === 'vehicle' ? (
<Link href={`/tools/tankopedia/${unlock.id}`}>{button}</Link>
) : (
button
);
}
export function Modules() {
const protagonist = useDuel((state) => state.protagonist!);
const awaitedTankDefinitions = use(tankDefinitions);
const tank = useDuel((state) => state.protagonist!.tank);
const hasUpgrades =
protagonist.tank.turrets.length > 1 ||
protagonist.tank.turrets[0].guns.length > 1 ||
protagonist.tank.engines.length > 1 ||
protagonist.tank.tracks.length > 1;
tank.turrets.length > 1 ||
tank.turrets[0].guns.length > 1 ||
tank.engines.length > 1 ||
tank.tracks.length > 1;
const turret = useDuel((state) => state.protagonist!.turret);
const gun = useDuel((state) => state.protagonist!.gun);
const engine = useDuel((state) => state.protagonist!.engine);
const track = useDuel((state) => state.protagonist!.track);
const turret0 = tank.turrets[0];
const gun0 = turret0.guns[0];
const engine0 = tank.engines[0];
const track0 = tank.tracks[0];
function setByUnlock(unlock: Unlock) {
mutateDuel((draft) => {
if (unlock.type === 'turret') {
draft.protagonist!.turret = draft.protagonist!.tank.turrets.find(
(turret) => turret.id === unlock.id,
)!;
if (
!draft.protagonist!.turret.guns.some(
(gun) => gun.id === draft.protagonist!.gun.id,
)
) {
draft.protagonist!.gun = draft.protagonist!.turret.guns.at(-1)!;
draft.protagonist!.shell = draft.protagonist!.gun.shells.at(-1)!;
}
} else if (unlock.type === 'gun') {
const gunInTurret = draft.protagonist!.turret.guns.find(
(gun) => gun.id === unlock.id,
);
if (gunInTurret) {
draft.protagonist!.gun = gunInTurret;
draft.protagonist!.shell = gunInTurret.shells.at(-1)!;
} else {
// TODO: warn somehow?
const suitableTurret = draft.protagonist!.tank.turrets.find(
(turret) => turret.guns.some((gun) => gun.id === unlock.id),
)!;
const gunInSuitableTurret = suitableTurret.guns.find(
(gun) => gun.id === unlock.id,
)!;
draft.protagonist!.turret = suitableTurret;
draft.protagonist!.gun = gunInSuitableTurret;
draft.protagonist!.shell = gunInSuitableTurret.shells.at(-1)!;
}
} else if (unlock.type === 'engine') {
draft.protagonist!.engine = draft.protagonist!.tank.engines.find(
(engine) => engine.id === unlock.id,
)!;
} else if (unlock.type === 'chassis') {
draft.protagonist!.track = draft.protagonist!.tank.tracks.find(
(track) => track.id === unlock.id,
)!;
}
});
}
function tree(type: ModuleType, unlocks: Unlock[]) {
return (
<Flex gap="2">
{unlocks.map((unlock, index) => {
const first = index === 0;
const last = index === unlocks.length - 1;
const central = !first && !last;
let module: ModuleDefinition | TankDefinition | undefined = undefined;
if (unlock.type === 'chassis') {
module = tank.tracks.find((track) => track.id === unlock.id)!;
} else if (unlock.type === 'engine') {
module = tank.engines.find((engine) => engine.id === unlock.id)!;
} else if (unlock.type === 'turret') {
module = tank.turrets.find((turret) => turret.id === unlock.id)!;
} else if (unlock.type === 'gun') {
module = tank.turrets
.find((turret) =>
turret.guns.some((gun) => gun.id === unlock.id),
)!
.guns.find((gun) => gun.id === unlock.id)!;
} else if (unlock.type === 'vehicle') {
module = awaitedTankDefinitions[unlock.id];
}
if (module === undefined) return null;
return (
<Flex
align="center"
direction="column"
gap="2"
style={{
position: 'relative',
}}
>
{unlock.cost.value > 0 && (
<>
{unlocks.length > 1 && (
<div
style={{
backgroundColor: 'currentcolor',
width: `calc(${central ? 100 : 50}% + ${central ? 8 : 4}px)`,
height: 1,
position: 'absolute',
right: first ? 0 : undefined,
left: last ? 0 : undefined,
transform: `translateX(${first ? 4 : last ? -4 : 0}px)`,
}}
/>
)}
<Flex
direction="column"
align="center"
style={{
marginBottom: -7,
}}
>
<div
style={{
backgroundColor: 'currentcolor',
width: 1,
height: 8,
}}
/>
<ChevronDownIcon
style={{
transform: 'translateY(-7px)',
}}
/>
</Flex>
</>
)}
<ModuleButton
unlock={unlock}
tier={module.tier}
selected={
(unlock.type === 'turret'
? turret.id
: unlock.type === 'gun'
? gun.id
: unlock.type === 'engine'
? engine.id
: unlock.type === 'chassis'
? track.id
: -1) === unlock.id
}
onClick={() => setByUnlock(unlock)}
/>
{(module as ModuleDefinition).unlocks &&
(module as ModuleDefinition).unlocks!.length > 1 && (
<div
style={{
backgroundColor: 'currentcolor',
width: 1,
height: 8,
marginBottom: -8,
}}
/>
)}
{(module as ModuleDefinition).unlocks &&
tree(type, (module as ModuleDefinition).unlocks!)}
</Flex>
);
})}
</Flex>
);
}
return (
<ConfigurationChildWrapper>
@@ -58,22 +330,36 @@ export function Modules() {
)}
</Flex>
<ModuleManager
tank={protagonist.tank}
turret={protagonist.turret}
gun={protagonist.gun}
shell={protagonist.shell}
engine={protagonist.engine}
track={protagonist.track}
onChange={(modules) => {
mutateDuel((draft) => {
draft.protagonist = { ...draft.protagonist!, ...modules };
});
mutateTankopediaTemporary((draft) => {
draft.shot = undefined;
});
}}
/>
<Flex gap="2">
{tree('engine', [
{
cost: { type: 'xp', value: 0 },
id: engine0.id,
type: 'engine',
},
])}
{tree('chassis', [
{
cost: { type: 'xp', value: 0 },
id: track0.id,
type: 'chassis',
},
])}
{tree('turret', [
{
cost: { type: 'xp', value: 0 },
id: turret0.id,
type: 'turret',
},
])}
{tree('gun', [
{
cost: { type: 'xp', value: 0 },
id: gun0.id,
type: 'gun',
},
])}
</Flex>
</ConfigurationChildWrapper>
);
}
@@ -13,9 +13,9 @@ export function CharacteristicsSection() {
const wideFormat = useWideFormat(720);
return (
<PageWrapper>
<PageWrapper >
<Flex
gap={wideFormat ? '4' : '6'}
gap="6"
style={{ width: '100%' }}
direction={wideFormat ? 'row' : 'column'}
>
@@ -1,315 +0,0 @@
import {
ChevronDownIcon,
Flex,
Heading,
IconButton,
Text,
} from '@radix-ui/themes';
import Link from 'next/link';
import { use } from 'react';
import PageWrapper from '../../../../../../components/PageWrapper';
import { asset } from '../../../../../../core/blitzkit/asset';
import {
ModuleDefinition,
ModuleType,
TankDefinition,
tankDefinitions,
Tier,
Unlock,
} from '../../../../../../core/blitzkit/tankDefinitions';
import { TIER_ROMAN_NUMERALS } from '../../../../../../core/blitzkit/tankDefinitions/constants';
import { tankIcon } from '../../../../../../core/blitzkit/tankIcon';
import { mutateDuel, useDuel } from '../../../../../../stores/duel';
function ModuleButton({
tier,
selected,
unlock,
onClick,
}: {
unlock: Unlock;
tier: Tier;
selected: boolean;
onClick: () => void;
}) {
const numberFormat = Intl.NumberFormat(undefined, {
notation: 'compact',
maximumFractionDigits: 0,
});
const button = (
<IconButton
size="4"
radius="small"
variant={selected ? 'surface' : 'soft'}
color={selected ? undefined : 'gray'}
onClick={onClick}
style={{
position: 'relative',
}}
>
<Text
size="1"
color="gray"
style={{
position: 'absolute',
left: 4,
top: 4,
}}
>
{TIER_ROMAN_NUMERALS[tier]}
</Text>
{unlock.cost.value > 0 && (
<Flex
style={{
position: 'absolute',
left: '50%',
bottom: 4,
transform: 'translateX(-50%)',
}}
align="center"
>
<Text size="1" color="gray">
{numberFormat.format(unlock.cost.value)}
</Text>
<img
src={asset(`icons/currencies/${unlock.cost.type}.webp`)}
style={{
width: 12,
height: 12,
objectFit: 'contain',
}}
/>
</Flex>
)}
<img
src={
unlock.type === 'vehicle'
? tankIcon(unlock.id)
: asset(`icons/modules/${unlock.type}.webp`)
}
style={{
width: 32,
height: 32,
objectFit: 'contain',
}}
/>
</IconButton>
);
return unlock.type === 'vehicle' ? (
<Link href={`/tools/tankopedia/${unlock.id}`}>{button}</Link>
) : (
button
);
}
export function ModulesSection() {
const awaitedTankDefinitions = use(tankDefinitions);
const tank = useDuel((state) => state.protagonist!.tank);
const turret = useDuel((state) => state.protagonist!.turret);
const gun = useDuel((state) => state.protagonist!.gun);
const engine = useDuel((state) => state.protagonist!.engine);
const track = useDuel((state) => state.protagonist!.track);
const turret0 = tank.turrets[0];
const gun0 = turret0.guns[0];
const engine0 = tank.engines[0];
const track0 = tank.tracks[0];
function setByUnlock(unlock: Unlock) {
mutateDuel((draft) => {
if (unlock.type === 'turret') {
draft.protagonist!.turret = draft.protagonist!.tank.turrets.find(
(turret) => turret.id === unlock.id,
)!;
if (
!draft.protagonist!.turret.guns.some(
(gun) => gun.id === draft.protagonist!.gun.id,
)
) {
draft.protagonist!.gun = draft.protagonist!.turret.guns.at(-1)!;
draft.protagonist!.shell = draft.protagonist!.gun.shells.at(-1)!;
}
} else if (unlock.type === 'gun') {
const gunInTurret = draft.protagonist!.turret.guns.find(
(gun) => gun.id === unlock.id,
);
if (gunInTurret) {
draft.protagonist!.gun = gunInTurret;
draft.protagonist!.shell = gunInTurret.shells.at(-1)!;
} else {
// TODO: warn somehow?
const suitableTurret = draft.protagonist!.tank.turrets.find(
(turret) => turret.guns.some((gun) => gun.id === unlock.id),
)!;
const gunInSuitableTurret = suitableTurret.guns.find(
(gun) => gun.id === unlock.id,
)!;
draft.protagonist!.turret = suitableTurret;
draft.protagonist!.gun = gunInSuitableTurret;
draft.protagonist!.shell = gunInSuitableTurret.shells.at(-1)!;
}
} else if (unlock.type === 'engine') {
draft.protagonist!.engine = draft.protagonist!.tank.engines.find(
(engine) => engine.id === unlock.id,
)!;
} else if (unlock.type === 'chassis') {
draft.protagonist!.track = draft.protagonist!.tank.tracks.find(
(track) => track.id === unlock.id,
)!;
}
});
}
function tree(type: ModuleType, unlocks: Unlock[]) {
return (
<Flex gap="2">
{unlocks.map((unlock, index) => {
const direction = index < Math.floor(unlocks.length / 2) ? -1 : 1;
let module: ModuleDefinition | TankDefinition | undefined = undefined;
if (unlock.type === 'chassis') {
module = tank.tracks.find((track) => track.id === unlock.id)!;
} else if (unlock.type === 'engine') {
module = tank.engines.find((engine) => engine.id === unlock.id)!;
} else if (unlock.type === 'turret') {
module = tank.turrets.find((turret) => turret.id === unlock.id)!;
} else if (unlock.type === 'gun') {
module = tank.turrets
.find((turret) =>
turret.guns.some((gun) => gun.id === unlock.id),
)!
.guns.find((gun) => gun.id === unlock.id)!;
} else if (unlock.type === 'vehicle') {
module = awaitedTankDefinitions[unlock.id];
}
if (module === undefined) return null;
return (
<Flex
align="center"
direction="column"
gap="2"
style={{
position: 'relative',
}}
>
{unlock.cost.value > 0 && (
<>
{unlocks.length > 1 && (
<div
style={{
backgroundColor: 'currentcolor',
width: 'calc(50% + 4px)',
height: 1,
position: 'absolute',
right: direction < 0 ? 0 : undefined,
left: direction > 0 ? 0 : undefined,
transform: `translateX(${direction * -4}px)`,
}}
/>
)}
<Flex
direction="column"
align="center"
style={{
marginBottom: -7,
}}
>
<div
style={{
backgroundColor: 'currentcolor',
width: 1,
height: 8,
}}
/>
<ChevronDownIcon
style={{
transform: 'translateY(-7px)',
}}
/>
</Flex>
</>
)}
<ModuleButton
unlock={unlock}
tier={module.tier}
selected={
(unlock.type === 'turret'
? turret.id
: unlock.type === 'gun'
? gun.id
: unlock.type === 'engine'
? engine.id
: unlock.type === 'chassis'
? track.id
: -1) === unlock.id
}
onClick={() => setByUnlock(unlock)}
/>
{(module as ModuleDefinition).unlocks &&
(module as ModuleDefinition).unlocks!.length > 1 && (
<div
style={{
backgroundColor: 'currentcolor',
width: 1,
height: 8,
marginBottom: -8,
}}
/>
)}
{(module as ModuleDefinition).unlocks &&
tree(type, (module as ModuleDefinition).unlocks!)}
</Flex>
);
})}
</Flex>
);
}
return (
<PageWrapper>
<Heading>Modules</Heading>
<Flex gap="2">
{tree('turret', [
{
cost: { type: 'xp', value: 0 },
id: turret0.id,
type: 'turret',
},
])}
{tree('gun', [
{
cost: { type: 'xp', value: 0 },
id: gun0.id,
type: 'gun',
},
])}
{tree('engine', [
{
cost: { type: 'xp', value: 0 },
id: engine0.id,
type: 'engine',
},
])}
{tree('chassis', [
{
cost: { type: 'xp', value: 0 },
id: track0.id,
type: 'chassis',
},
])}
</Flex>
</PageWrapper>
);
}
@@ -1,5 +1,5 @@
import { ChevronRightIcon } from '@radix-ui/react-icons';
import { ChevronDownIcon, Flex, Heading, Text } from '@radix-ui/themes';
import { ChevronDownIcon, Flex, Text } from '@radix-ui/themes';
import Link from 'next/link';
import { ComponentProps, use } from 'react';
import PageWrapper from '../../../../../../components/PageWrapper';
@@ -121,10 +121,39 @@ export function TechTreeSection() {
}
return (
<PageWrapper>
<Heading>Tech tree</Heading>
<PageWrapper
style={{
position: 'relative',
overflow: 'hidden',
}}
size={tank.ancestors && tank.successors ? 880 : 480}
gap="0"
highlight
>
<img
src={asset(`flags/scratched/${tank.nation}.webp`)}
style={{
position: 'absolute',
top: '50%',
left: '50%',
height: '200%',
transform: 'translate(-40%, -48%)',
backgroundRepeat: 'no-repeat',
opacity: 1 / 4,
}}
/>
<Flex align="center" gap="6">
{/* <Flex justify="center">
<Heading>Tech tree</Heading>
</Flex> */}
<Flex
align="center"
gap="6"
style={{
zIndex: 1,
}}
>
{tank.ancestors && (
<>
<Flex
@@ -146,28 +175,8 @@ export function TechTreeSection() {
</>
)}
<Flex
direction="column"
style={{
position: 'relative',
}}
>
<Flex direction="column">
<Card id={tank.id} />
<img
src={asset(`flags/scratched/${tank.nation}.webp`)}
style={{
position: 'absolute',
top: '50%',
left: '50%',
height: 180,
transform: 'translate(-42%, -50%)',
backgroundRepeat: 'no-repeat',
zIndex: -1,
opacity: 1 / 4,
filter: 'blur(4px)',
}}
/>
</Flex>
{tank.successors && (
+3 -4
View File
@@ -5,9 +5,9 @@ import PageWrapper from '../../../../components/PageWrapper';
import { assignDuelMember } from '../../../../core/blitzkit/assignDuelMember';
import { mutateDuel, useDuel } from '../../../../stores/duel';
import { mutateTankopediaTemporary } from '../../../../stores/tankopedia';
import { CharacteristicsSection } from './components/Model/CharacteristicsSection';
import { HeroSection } from './components/Model/HeroSection';
import { HistoricalSection } from './components/Model/HistoricalSection';
import { ModulesSection } from './components/Model/ModulesSection';
import { TechTreeSection } from './components/Model/TechTreeSection';
import { Videos } from './components/Videos';
@@ -62,10 +62,9 @@ export default function Page({ params }: { params: { id: string } }) {
<>
<HeroSection />
{/* <TankopediaPlug /> */}
<ModulesSection />
<CharacteristicsSection />
<TechTreeSection />
{/* <CharacteristicsSection />
<TankopediaSeparator /> */}
{/* <TankopediaSeparator /> */}
<Videos />
<HistoricalSection />
</>
+6
View File
@@ -1,11 +1,13 @@
import { Flex, FlexProps, Theme } from '@radix-ui/themes';
import { ComponentProps } from 'react';
import { theme } from '../stitches.config';
type PageWrapperProps = FlexProps & {
color?: ComponentProps<typeof Theme>['accentColor'];
size?: number | string;
noPadding?: boolean;
noMaxWidth?: boolean;
highlight?: boolean;
};
export default function PageWrapper({
@@ -15,6 +17,7 @@ export default function PageWrapper({
children,
noPadding = false,
noMaxWidth = false,
highlight = false,
...props
}: PageWrapperProps) {
return (
@@ -24,6 +27,9 @@ export default function PageWrapper({
flex: 1,
display: 'flex',
flexDirection: 'column',
background: highlight
? `linear-gradient(-90deg, ${theme.colors.appBackground1}, ${theme.colors.appBackground2})`
: undefined,
}}
>
<Flex