feat: shells

This commit is contained in:
TrèsAbhi committed 2024-05-03 19:37:41 -05:00
1 parent cc1e3e1315
commit 98716b7f71
2 files changed
+253 -261

No files matched your search

@@ -1,251 +0,0 @@
import { Cross1Icon, ShuffleIcon } from '@radix-ui/react-icons';
import { Button, Card, Dialog, Flex, Heading, Tabs } from '@radix-ui/themes';
import { useState } from 'react';
import { ModuleButton } from '../../../../../components/ModuleButtons/ModuleButton';
import { ShellButton } from '../../../../../components/ModuleButtons/ShellButton';
import { SmallTankIcon } from '../../../../../components/SmallTankIcon';
import { resolveNearPenetration } from '../../../../../core/blitz/resolveNearPenetration';
import { pushTankopediaPath } from '../../../../../core/blitzkit/pushTankopediaPath';
import {
SHELL_NAMES,
TIER_ROMAN_NUMERALS,
} from '../../../../../core/blitzkit/tankDefinitions/constants';
import { mutateDuel, useDuel } from '../../../../../stores/duel';
import {
mutateTankopediaTemporary,
useTankopediaPersistent,
} from '../../../../../stores/tankopedia';
import { TankSearch } from '../../components/TankSearch';
interface AntagonistBarProps {
floating?: boolean;
}
export function AntagonistBar({ floating }: AntagonistBarProps) {
const antagonist = useDuel((state) => state.antagonist!);
const mode = useTankopediaPersistent((state) => state.mode);
const [tab, setTab] = useState('search');
const [antagonistSelectorOpen, setAntagonistSelectorVisible] =
useState(false);
if (mode !== 'armor') return null;
return (
<Flex justify="center">
<Card
style={
floating
? {
position: 'absolute',
bottom: 8,
left: '50%',
transform: 'translateX(-50%)',
}
: undefined
}
>
<Flex
align="center"
justify="center"
wrap="wrap"
style={{
gap: 'min(5vw, 32px)',
}}
>
<Flex>
{antagonist.gun.shells.map((shell, index) => {
return (
<ShellButton
selected={antagonist.shell.id === shell.id}
shell={shell.icon}
rowChild
first={index === 0}
key={shell.id}
last={index === antagonist.gun.shells.length - 1}
onClick={() => {
mutateDuel((draft) => {
draft.antagonist!.shell = shell;
mutateTankopediaTemporary((draft) => {
draft.shot = undefined;
});
});
}}
/>
);
})}
</Flex>
<Button
variant="ghost"
onClick={() => {
mutateDuel((draft) => {
[draft.protagonist, draft.antagonist] = [
draft.antagonist,
draft.protagonist,
];
pushTankopediaPath(draft.antagonist!.tank.id);
});
}}
>
<ShuffleIcon /> Swap
</Button>
<Flex align="center" gap="4">
<Dialog.Root
open={antagonistSelectorOpen}
onOpenChange={setAntagonistSelectorVisible}
>
<Dialog.Trigger>
<Button variant="ghost">
<Flex gap="2" align="center">
Versus {antagonist.tank.name}
<SmallTankIcon id={antagonist.tank.id} size={16} />
</Flex>
</Button>
</Dialog.Trigger>
<Dialog.Content>
<Tabs.Root
value={tab}
onValueChange={setTab}
style={{ position: 'relative' }}
>
<Dialog.Close>
<Button
variant="ghost"
style={{ position: 'absolute', right: 0, top: 8 }}
>
<Cross1Icon />
</Button>
</Dialog.Close>
<Flex gap="4" direction="column">
<Tabs.List>
<Tabs.Trigger value="search">Search</Tabs.Trigger>
<Tabs.Trigger value="configure">Configure</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="search">
<Flex
direction="column"
gap="4"
style={{ flex: 1 }}
justify="center"
>
<TankSearch
compact
onSelect={(tank) => {
mutateDuel((draft) => {
draft.antagonist!.tank = tank;
draft.antagonist!.engine = tank.engines.at(-1)!;
draft.antagonist!.track = tank.tracks.at(-1)!;
draft.antagonist!.turret = tank.turrets.at(-1)!;
draft.antagonist!.gun =
draft.antagonist!.turret.guns.at(-1)!;
draft.antagonist!.shell =
draft.antagonist!.gun.shells[0];
});
setAntagonistSelectorVisible(false);
}}
/>
</Flex>
</Tabs.Content>
<Tabs.Content value="configure">
<Flex direction="column" gap="4">
<Flex direction="column" gap="2" style={{ flex: 1 }}>
<Heading size="4">
{antagonist.tank.name} modules
</Heading>
<Flex gap="2" wrap="wrap">
<Flex>
{antagonist.tank.turrets.map((turret, index) => (
<ModuleButton
rowChild
first={index === 0}
last={
index === antagonist.tank.turrets.length - 1
}
key={turret.id}
onClick={() => {
mutateDuel((draft) => {
draft.antagonist!.turret = turret;
draft.antagonist!.gun =
turret.guns.at(-1)!;
draft.antagonist!.shell =
draft.antagonist!.gun.shells[0];
});
mutateTankopediaTemporary((draft) => {
draft.shot = undefined;
});
}}
selected={antagonist.turret.id === turret.id}
discriminator={
TIER_ROMAN_NUMERALS[turret.tier]
}
module="turret"
/>
))}
</Flex>
<Flex>
{antagonist.turret.guns.map((gun, index) => (
<ModuleButton
key={gun.id}
rowChild
first={index === 0}
last={
index === antagonist.turret.guns.length - 1
}
onClick={() => {
mutateDuel((draft) => {
draft.antagonist!.gun = gun;
draft.antagonist!.shell = gun.shells[0];
});
}}
selected={antagonist.gun.id === gun.id}
discriminator={TIER_ROMAN_NUMERALS[gun.tier]}
module="gun"
/>
))}
</Flex>
</Flex>
</Flex>
<Flex direction="column" style={{ flex: 1 }}>
<Heading size="4">Properties</Heading>
<ul>
<li>
Turret: <b>{antagonist.turret.name}</b>
</li>
<li>
Gun: <b>{antagonist.gun.name}</b>
</li>
<li>Shells:</li>
<ul>
{antagonist.gun.shells.map((shell) => (
<li key={shell.id}>
{SHELL_NAMES[shell.type]}:{' '}
<b>
{resolveNearPenetration(shell.penetration)}
mm
</b>
, {shell.damage.armor}HP
</li>
))}
</ul>
</ul>
</Flex>
</Flex>
</Tabs.Content>
</Flex>
</Tabs.Root>
</Dialog.Content>
</Dialog.Root>
</Flex>
</Flex>
</Card>
</Flex>
);
}
@@ -1,12 +1,32 @@
import { import {
Cross1Icon,
EnterFullScreenIcon, EnterFullScreenIcon,
ExitFullScreenIcon, ExitFullScreenIcon,
EyeOpenIcon, EyeOpenIcon,
GearIcon, GearIcon,
} from '@radix-ui/react-icons'; } from '@radix-ui/react-icons';
import { DropdownMenu, Flex, IconButton, Switch } from '@radix-ui/themes'; import {
Button,
Dialog,
DropdownMenu,
Flex,
Heading,
IconButton,
Switch,
Tabs,
Text,
} from '@radix-ui/themes';
import { useState } from 'react';
import { ModuleButton } from '../../../../../../../components/ModuleButtons/ModuleButton';
import { SmallTankIcon } from '../../../../../../../components/SmallTankIcon';
import { resolveNearPenetration } from '../../../../../../../core/blitz/resolveNearPenetration';
import { asset } from '../../../../../../../core/blitzkit/asset'; import { asset } from '../../../../../../../core/blitzkit/asset';
import { Pose, poseEvent } from '../../../../../../../core/blitzkit/pose'; import { Pose, poseEvent } from '../../../../../../../core/blitzkit/pose';
import {
SHELL_NAMES,
TIER_ROMAN_NUMERALS,
} from '../../../../../../../core/blitzkit/tankDefinitions/constants';
import { useEquipment } from '../../../../../../../hooks/useEquipment';
import { useFullScreen } from '../../../../../../../hooks/useFullScreen'; import { useFullScreen } from '../../../../../../../hooks/useFullScreen';
import { useApp } from '../../../../../../../stores/app'; import { useApp } from '../../../../../../../stores/app';
import { mutateDuel, useDuel } from '../../../../../../../stores/duel'; import { mutateDuel, useDuel } from '../../../../../../../stores/duel';
@@ -14,6 +34,7 @@ import mutateTankopediaPersistent, {
mutateTankopediaTemporary, mutateTankopediaTemporary,
useTankopediaPersistent, useTankopediaPersistent,
} from '../../../../../../../stores/tankopedia'; } from '../../../../../../../stores/tankopedia';
import { TankSearch } from '../../../../components/TankSearch';
import { ENVIRONMENTS } from '../../Lighting'; import { ENVIRONMENTS } from '../../Lighting';
export function Options() { export function Options() {
@@ -41,33 +62,45 @@ export function Options() {
(state) => state.model.visual.environment, (state) => state.model.visual.environment,
); );
const developerMode = useApp((state) => state.developerMode); const developerMode = useApp((state) => state.developerMode);
const gun = useDuel((state) => state.antagonist!.gun); const antagonistGun = useDuel((state) => state.antagonist!.gun);
const shell = useDuel((state) => state.antagonist!.shell); const antagonistShell = useDuel((state) => state.antagonist!.shell);
const [antagonistSelectorOpen, setAntagonistSelectorOpen] = useState(false);
const antagonistTank = useDuel((state) => state.antagonist!.tank);
const antagonistTurret = useDuel((state) => state.antagonist!.turret);
const hasCalibratedShells = useEquipment(103, true);
const [tab, setTab] = useState('search');
const hasEnhancedArmor = useEquipment(110);
return ( return (
<> <>
{mode === 'armor' && ( {mode === 'armor' && (
<Flex <Flex
gap="2"
direction="column"
style={{ style={{
position: 'absolute', position: 'absolute',
right: 16, right: 16,
top: '50%', top: '50%',
transform: 'translateY(-50%)', transform: 'translateY(-50%)',
}} }}
direction="column"
> >
{gun.shells.map((thisShell, shellIndex) => ( <Flex direction="column">
{antagonistGun.shells.map((thisShell, shellIndex) => (
<IconButton <IconButton
color={thisShell.id === shell.id ? undefined : 'gray'} color={thisShell.id === antagonistShell.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,
borderTopRightRadius: shellIndex === 0 ? undefined : 0, borderTopRightRadius: shellIndex === 0 ? undefined : 0,
borderBottomRightRadius: borderBottomRightRadius:
shellIndex === gun.shells.length - 1 ? undefined : 0, shellIndex === antagonistGun.shells.length - 1
? undefined
: 0,
borderBottomLeftRadius: borderBottomLeftRadius:
shellIndex === gun.shells.length - 1 ? undefined : 0, shellIndex === antagonistGun.shells.length - 1
? undefined
: 0,
marginTop: shellIndex === 0 ? 0 : -1, marginTop: shellIndex === 0 ? 0 : -1,
}} }}
onClick={() => { onClick={() => {
@@ -84,18 +117,227 @@ export function Options() {
</IconButton> </IconButton>
))} ))}
</Flex> </Flex>
<Flex direction="column" style={{ pointerEvents: 'auto' }}>
<IconButton
color={hasCalibratedShells ? undefined : 'gray'}
variant="soft"
style={{
borderBottomRightRadius: 0,
borderBottomLeftRadius: 0,
}}
onClick={() => {
mutateDuel((draft) => {
draft.antagonist!.equipmentMatrix[0][0] = hasCalibratedShells
? 0
: 1;
});
}}
>
<img
width={16}
height={16}
src={asset('icons/equipment/103.webp')}
/>
</IconButton>
<IconButton
color={hasEnhancedArmor ? undefined : 'gray'}
variant="soft"
style={{
borderTopRightRadius: 0,
borderTopLeftRadius: 0,
marginTop: -1,
}}
onClick={() => {
mutateDuel((draft) => {
draft.protagonist!.equipmentMatrix[1][1] = hasEnhancedArmor
? 0
: -1;
});
}}
>
<img
width={16}
height={16}
src={asset('icons/equipment/110.webp')}
/>
</IconButton>
</Flex>
</Flex>
)} )}
<Flex <Flex
gap="4"
align="center"
style={{ style={{
position: 'absolute', position: 'absolute',
bottom: 16, bottom: 16,
left: '50%', left: '50%',
transform: 'translateX(-50%)', transform: 'translateX(-50%)',
}} }}
direction="column"
align="center"
gap="2"
> >
{mode === 'armor' && (
<Flex align="center" gap="2">
<Text color="gray" size="2">
Shooter:
</Text>
<Dialog.Root
open={antagonistSelectorOpen}
onOpenChange={setAntagonistSelectorOpen}
>
<Dialog.Trigger>
<Button variant="ghost">
<Flex gap="2" align="center">
{antagonistTank.name}
<SmallTankIcon id={antagonistTank.id} size={16} />
</Flex>
</Button>
</Dialog.Trigger>
<Dialog.Content>
<Tabs.Root
value={tab}
onValueChange={setTab}
style={{ position: 'relative' }}
>
<Dialog.Close>
<Button
variant="ghost"
style={{ position: 'absolute', right: 0, top: 8 }}
>
<Cross1Icon />
</Button>
</Dialog.Close>
<Flex gap="4" direction="column">
<Tabs.List>
<Tabs.Trigger value="search">Search</Tabs.Trigger>
<Tabs.Trigger value="configure">Configure</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="search">
<Flex
direction="column"
gap="4"
style={{ flex: 1 }}
justify="center"
>
<TankSearch
compact
onSelect={(tank) => {
mutateDuel((draft) => {
draft.antagonist!.tank = tank;
draft.antagonist!.engine = tank.engines.at(-1)!;
draft.antagonist!.track = tank.tracks.at(-1)!;
draft.antagonist!.turret = tank.turrets.at(-1)!;
draft.antagonist!.gun =
draft.antagonist!.turret.guns.at(-1)!;
draft.antagonist!.shell =
draft.antagonist!.gun.shells[0];
});
setAntagonistSelectorOpen(false);
}}
/>
</Flex>
</Tabs.Content>
<Tabs.Content value="configure">
<Flex direction="column" gap="4">
<Flex direction="column" gap="2" style={{ flex: 1 }}>
<Heading size="4">
{antagonistTank.name} modules
</Heading>
<Flex gap="2" wrap="wrap">
<Flex>
{antagonistTank.turrets.map((turret, index) => (
<ModuleButton
rowChild
first={index === 0}
last={
index === antagonistTank.turrets.length - 1
}
key={turret.id}
onClick={() => {
mutateDuel((draft) => {
draft.antagonist!.turret = turret;
draft.antagonist!.gun =
turret.guns.at(-1)!;
draft.antagonist!.shell =
draft.antagonist!.gun.shells[0];
});
mutateTankopediaTemporary((draft) => {
draft.shot = undefined;
});
}}
selected={antagonistTurret.id === turret.id}
discriminator={
TIER_ROMAN_NUMERALS[turret.tier]
}
module="turret"
/>
))}
</Flex>
<Flex>
{antagonistTurret.guns.map((gun, index) => (
<ModuleButton
key={gun.id}
rowChild
first={index === 0}
last={
index === antagonistTurret.guns.length - 1
}
onClick={() => {
mutateDuel((draft) => {
draft.antagonist!.gun = gun;
draft.antagonist!.shell = gun.shells[0];
});
}}
selected={antagonistGun.id === gun.id}
discriminator={TIER_ROMAN_NUMERALS[gun.tier]}
module="gun"
/>
))}
</Flex>
</Flex>
</Flex>
<Flex direction="column" style={{ flex: 1 }}>
<Heading size="4">Properties</Heading>
<ul>
<li>
Turret: <b>{antagonistTurret.name}</b>
</li>
<li>
Gun: <b>{antagonistGun.name}</b>
</li>
<li>Shells:</li>
<ul>
{antagonistGun.shells.map((shell) => (
<li key={shell.id}>
{SHELL_NAMES[shell.type]}:{' '}
<b>
{resolveNearPenetration(shell.penetration)}
mm
</b>
, {shell.damage.armor}HP
</li>
))}
</ul>
</ul>
</Flex>
</Flex>
</Tabs.Content>
</Flex>
</Tabs.Root>
</Dialog.Content>
</Dialog.Root>
</Flex>
)}
<Flex gap="4" align="center">
<Flex <Flex
style={{ style={{
cursor: 'default', cursor: 'default',
@@ -262,6 +504,7 @@ export function Options() {
</IconButton> </IconButton>
)} )}
</Flex> </Flex>
</Flex>
</> </>
); );
} }