mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-28 13:09:51 +01:00
port antagonist control
This commit is contained in:
1 parent
45811b5c18
commit
d251c31b92
8 files changed
+292
-281
No files matched your search
@@ -20,6 +20,16 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
"tanks": {
|
"tanks": {
|
||||||
|
"custom_shell": {
|
||||||
|
"type": "Type",
|
||||||
|
"caliber": "Caliber",
|
||||||
|
"penetration": "Penetration",
|
||||||
|
"normalization": "Normalization",
|
||||||
|
"ricochet": "Ricochet",
|
||||||
|
"exit": "Exit",
|
||||||
|
"reset": "Reset"
|
||||||
|
},
|
||||||
|
|
||||||
"tabs": {
|
"tabs": {
|
||||||
"model": "Model",
|
"model": "Model",
|
||||||
"dynamic_armor": "Dynamic armor",
|
"dynamic_armor": "Dynamic armor",
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
.antagonist-control {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: end;
|
||||||
|
|
||||||
|
gap: var(--space-4);
|
||||||
|
|
||||||
|
transition: var(--duration-brief);
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
right: var(--space-4);
|
||||||
|
|
||||||
|
transform: translate(calc(100% + var(--space-4)), -50%);
|
||||||
|
|
||||||
|
&[data-visible="true"] {
|
||||||
|
transform: translate(0, -50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,221 @@
|
|||||||
|
import { alias, romanize, uniqueGuns } from "@blitzkit/core";
|
||||||
|
import { literals } from "@blitzkit/i18n";
|
||||||
|
import { Popover } from "radix-ui";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
import { DuelModule, useDuel } from "../../hooks/useDuel";
|
||||||
|
import { useEquipment } from "../../hooks/useEquipment";
|
||||||
|
import { useStrings } from "../../hooks/useStrings";
|
||||||
|
import { useUnwrapper } from "../../hooks/useUnwrapper";
|
||||||
|
import { Tankopedia } from "../../stores/tankopedia";
|
||||||
|
import { TankopediaDisplay } from "../../stores/tankopediaPersistent/constants";
|
||||||
|
import type { ComputedCharacteristics } from "../../types/characteristics";
|
||||||
|
import { EqualIcon } from "../EqualIcon";
|
||||||
|
import { IconButton } from "../IconButton";
|
||||||
|
import { CustomShellButton } from "../TankopediaOptions/components/CustomShellButton";
|
||||||
|
import { Text } from "../Text";
|
||||||
|
import styles from "./index.module.css";
|
||||||
|
|
||||||
|
interface TankopediaAntagonistControlProps {
|
||||||
|
antagonistCharacteristics: ComputedCharacteristics;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TankopediaAntagonistControl({
|
||||||
|
antagonistCharacteristics,
|
||||||
|
}: TankopediaAntagonistControlProps) {
|
||||||
|
const strings = useStrings();
|
||||||
|
const unwrap = useUnwrapper();
|
||||||
|
|
||||||
|
const requestedDisplay = Tankopedia.use((state) => state.requestedDisplay);
|
||||||
|
const hasCustomShell = Tankopedia.use(
|
||||||
|
(state) => state.customShell !== undefined,
|
||||||
|
);
|
||||||
|
const equalize = Tankopedia.use((state) => state.environment.equalize);
|
||||||
|
|
||||||
|
const hasCalibratedShells = useEquipment("protagonist", 103);
|
||||||
|
const hasEnhancedArmor = useEquipment("protagonist", 110);
|
||||||
|
|
||||||
|
const tank = useDuel("antagonist", DuelModule.Tank);
|
||||||
|
const gun = useDuel("antagonist", DuelModule.Gun);
|
||||||
|
const shell = useDuel("antagonist", DuelModule.Shell);
|
||||||
|
|
||||||
|
const antagonistUniqueGuns = useMemo(
|
||||||
|
() => uniqueGuns(tank.turrets),
|
||||||
|
[tank.turrets],
|
||||||
|
);
|
||||||
|
|
||||||
|
const isVisible = requestedDisplay === TankopediaDisplay.DynamicArmor;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles["antagonist-control"]} data-visible={isVisible}>
|
||||||
|
<Text lowContrast>
|
||||||
|
{literals(strings.common.units.mm, {
|
||||||
|
value: (
|
||||||
|
antagonistCharacteristics.get("penetration") as number
|
||||||
|
).toFixed(0),
|
||||||
|
})}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
{antagonistUniqueGuns.length > 1 && (
|
||||||
|
<Popover.Root>
|
||||||
|
<Popover.Trigger>
|
||||||
|
<IconButton
|
||||||
|
radius="full"
|
||||||
|
variant="soft"
|
||||||
|
color="gray"
|
||||||
|
size={{ initial: "2", sm: "3" }}
|
||||||
|
style={{ position: "relative" }}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
size="1"
|
||||||
|
color="gray"
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
}}
|
||||||
|
mr="2"
|
||||||
|
mb="1"
|
||||||
|
>
|
||||||
|
{romanize(gun.tier)}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<img
|
||||||
|
alt="Antagonist Gun"
|
||||||
|
src={alias("api", "/icons/modules/gun.webp")}
|
||||||
|
style={{
|
||||||
|
width: "65%",
|
||||||
|
height: "65%",
|
||||||
|
objectFit: "contain",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</IconButton>
|
||||||
|
</Popover.Trigger>
|
||||||
|
|
||||||
|
<Popover.Content side="left" align="center">
|
||||||
|
<div>
|
||||||
|
{[...antagonistUniqueGuns].reverse().map(({ gun, turret }) => (
|
||||||
|
<IconButton key={gun.id}>
|
||||||
|
<img src={alias("api", "/icons/modules/gun.webp")} />
|
||||||
|
</IconButton>
|
||||||
|
// <ModuleButton
|
||||||
|
// module="gun"
|
||||||
|
// key={gun.id}
|
||||||
|
// onClick={() => {
|
||||||
|
// Tankopedia.mutate((draft) => {
|
||||||
|
// draft.antagonist.turret = turret.id;
|
||||||
|
// draft.antagonist.gun = gun.id;
|
||||||
|
// draft.antagonist.shell = gun.shells[0].id;
|
||||||
|
// });
|
||||||
|
// }}
|
||||||
|
// selected={gun.id === antagonistGun.id}
|
||||||
|
// discriminator={romanize(gun.tier)}
|
||||||
|
// secondaryDiscriminator={
|
||||||
|
// <Text style={{ fontSize: "0.75em" }}>
|
||||||
|
// {literals(strings.common.units.mm, {
|
||||||
|
// value: gun.shells[0].caliber.toFixed(0),
|
||||||
|
// })}
|
||||||
|
// </Text>
|
||||||
|
// }
|
||||||
|
// />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Popover.Content>
|
||||||
|
</Popover.Root>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{gun.shells.map((thisShell) => (
|
||||||
|
<IconButton
|
||||||
|
key={thisShell.id}
|
||||||
|
selected={thisShell.id === shell.id && !hasCustomShell}
|
||||||
|
alt={unwrap(thisShell.name!)}
|
||||||
|
src={alias("api", `/icons/shells/${thisShell.icon}.webp`)}
|
||||||
|
onClick={() => {
|
||||||
|
Tankopedia.mutate((draft) => {
|
||||||
|
draft.antagonist.shell = thisShell.id;
|
||||||
|
draft.shot = undefined;
|
||||||
|
draft.customShell = undefined;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<CustomShellButton />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
direction="column"
|
||||||
|
style={{
|
||||||
|
pointerEvents: "auto",
|
||||||
|
borderRadius: "var(--radius-full)",
|
||||||
|
}}
|
||||||
|
overflow="hidden"
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
color={hasCalibratedShells ? undefined : "gray"}
|
||||||
|
variant="soft"
|
||||||
|
size={{ initial: "2", sm: "3" }}
|
||||||
|
radius="none"
|
||||||
|
onClick={() => {
|
||||||
|
Tankopedia.mutate((draft) => {
|
||||||
|
draft.shot = undefined;
|
||||||
|
|
||||||
|
if (hasCalibratedShells) {
|
||||||
|
delete draft.protagonist.equipment[0];
|
||||||
|
} else {
|
||||||
|
draft.antagonist.equipment[0] = 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={alias("api", "/icons/equipment/103.webp")}
|
||||||
|
style={{
|
||||||
|
width: "50%",
|
||||||
|
height: "50%",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
color={hasEnhancedArmor ? undefined : "gray"}
|
||||||
|
variant="soft"
|
||||||
|
size={{ initial: "2", sm: "3" }}
|
||||||
|
radius="none"
|
||||||
|
onClick={() => {
|
||||||
|
Tankopedia.mutate((draft) => {
|
||||||
|
if (hasEnhancedArmor) {
|
||||||
|
delete draft.protagonist.equipment[4];
|
||||||
|
} else {
|
||||||
|
draft.antagonist.equipment[4] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
draft.shot = undefined;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
alt="Enhanced Armor"
|
||||||
|
src={alias("api", "/icons/equipment/110.webp")}
|
||||||
|
style={{
|
||||||
|
width: "50%",
|
||||||
|
height: "50%",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<IconButton
|
||||||
|
variant="soft"
|
||||||
|
size={{ initial: "2", sm: "3" }}
|
||||||
|
color={equalize ? undefined : "gray"}
|
||||||
|
onClick={() => {
|
||||||
|
Tankopedia.mutate((draft) => {
|
||||||
|
draft.environment.equalize = !draft.environment.equalize;
|
||||||
|
draft.shot = undefined;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<EqualIcon />
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
+12
-43
@@ -12,15 +12,15 @@ import {
|
|||||||
} from "@radix-ui/themes";
|
} from "@radix-ui/themes";
|
||||||
import { throttle } from "lodash-es";
|
import { throttle } from "lodash-es";
|
||||||
import { type ChangeEvent } from "react";
|
import { type ChangeEvent } from "react";
|
||||||
import { Duel } from "../../../../../../stores/duel";
|
import { DuelModule, useDuel } from "../../../hooks/useDuel";
|
||||||
import { useLocale } from "../../../hooks/useLocale";
|
import { useStrings } from "../../../hooks/useStrings";
|
||||||
import { Tankopedia } from "../../../stores/tankopedia";
|
import { Tankopedia } from "../../../stores/tankopedia";
|
||||||
|
|
||||||
export function CustomShellButton() {
|
export function CustomShellButton() {
|
||||||
const customShell = Tankopedia.use((state) => state.customShell);
|
const customShell = Tankopedia.use((state) => state.customShell);
|
||||||
const gun = Duel.use((state) => state.protagonist.gun);
|
const gun = useDuel("protagonist", DuelModule.Gun);
|
||||||
const antagonistShell = Duel.use((state) => state.antagonist.shell);
|
const antagonistShell = useDuel("antagonist", DuelModule.Shell);
|
||||||
const { strings } = useLocale();
|
const strings = useStrings();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover.Root
|
<Popover.Root
|
||||||
@@ -55,12 +55,7 @@ export function CustomShellButton() {
|
|||||||
<>
|
<>
|
||||||
<Flex direction="column" gap="2">
|
<Flex direction="column" gap="2">
|
||||||
<Flex align="center" justify="between" gap="4">
|
<Flex align="center" justify="between" gap="4">
|
||||||
<Text>
|
<Text>{strings.tanks.custom_shell.type}</Text>
|
||||||
{
|
|
||||||
strings.website.tools.tankopedia.sandbox.dynamic
|
|
||||||
.custom_shell.type
|
|
||||||
}
|
|
||||||
</Text>
|
|
||||||
<Select.Root
|
<Select.Root
|
||||||
value={`${customShell.type}`}
|
value={`${customShell.type}`}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
@@ -121,12 +116,7 @@ export function CustomShellButton() {
|
|||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Flex align="center" justify="between" gap="4">
|
<Flex align="center" justify="between" gap="4">
|
||||||
<Text>
|
<Text>{strings.tanks.custom_shell.caliber}</Text>
|
||||||
{
|
|
||||||
strings.website.tools.tankopedia.sandbox.dynamic
|
|
||||||
.custom_shell.caliber
|
|
||||||
}
|
|
||||||
</Text>
|
|
||||||
<TextField.Root
|
<TextField.Root
|
||||||
variant="classic"
|
variant="classic"
|
||||||
style={{ maxWidth: "6rem" }}
|
style={{ maxWidth: "6rem" }}
|
||||||
@@ -157,12 +147,7 @@ export function CustomShellButton() {
|
|||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Flex align="center" justify="between" gap="4">
|
<Flex align="center" justify="between" gap="4">
|
||||||
<Text>
|
<Text>{strings.tanks.custom_shell.penetration}</Text>
|
||||||
{
|
|
||||||
strings.website.tools.tankopedia.sandbox.dynamic
|
|
||||||
.custom_shell.penetration
|
|
||||||
}
|
|
||||||
</Text>
|
|
||||||
<TextField.Root
|
<TextField.Root
|
||||||
variant="classic"
|
variant="classic"
|
||||||
style={{ maxWidth: "6rem" }}
|
style={{ maxWidth: "6rem" }}
|
||||||
@@ -197,12 +182,7 @@ export function CustomShellButton() {
|
|||||||
customShell.type === ShellType.SHELL_TYPE_APCR) && (
|
customShell.type === ShellType.SHELL_TYPE_APCR) && (
|
||||||
<>
|
<>
|
||||||
<Flex align="center" justify="between" gap="4" py="1">
|
<Flex align="center" justify="between" gap="4" py="1">
|
||||||
<Text>
|
<Text>{strings.tanks.custom_shell.normalization}</Text>
|
||||||
{
|
|
||||||
strings.website.tools.tankopedia.sandbox.dynamic
|
|
||||||
.custom_shell.normalization
|
|
||||||
}
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Flex
|
<Flex
|
||||||
align="center"
|
align="center"
|
||||||
@@ -234,12 +214,7 @@ export function CustomShellButton() {
|
|||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Flex align="center" justify="between" gap="4" py="1">
|
<Flex align="center" justify="between" gap="4" py="1">
|
||||||
<Text>
|
<Text>{strings.tanks.custom_shell.ricochet}</Text>
|
||||||
{
|
|
||||||
strings.website.tools.tankopedia.sandbox.dynamic
|
|
||||||
.custom_shell.ricochet
|
|
||||||
}
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Flex align="center" gap="2" flexGrow="1" maxWidth="15rem">
|
<Flex align="center" gap="2" flexGrow="1" maxWidth="15rem">
|
||||||
<Text color="gray">
|
<Text color="gray">
|
||||||
@@ -300,10 +275,7 @@ export function CustomShellButton() {
|
|||||||
<Flex justify="end" mt="6" gap="2">
|
<Flex justify="end" mt="6" gap="2">
|
||||||
<Popover.Close>
|
<Popover.Close>
|
||||||
<Button color="red" variant="outline">
|
<Button color="red" variant="outline">
|
||||||
{
|
{strings.tanks.custom_shell.exit}
|
||||||
strings.website.tools.tankopedia.sandbox.dynamic
|
|
||||||
.custom_shell.exit
|
|
||||||
}
|
|
||||||
</Button>
|
</Button>
|
||||||
</Popover.Close>
|
</Popover.Close>
|
||||||
<Button
|
<Button
|
||||||
@@ -314,10 +286,7 @@ export function CustomShellButton() {
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{
|
{strings.tanks.custom_shell.reset}
|
||||||
strings.website.tools.tankopedia.sandbox.dynamic.custom_shell
|
|
||||||
.reset
|
|
||||||
}
|
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,28 +1,7 @@
|
|||||||
import { alias, romanize, uniqueGuns } from "@blitzkit/core";
|
import { alias, romanize, uniqueGuns } from "@blitzkit/core";
|
||||||
import { literals } from "@blitzkit/i18n/src/literals";
|
import { literals } from "@blitzkit/i18n/src/literals";
|
||||||
import {
|
import { Flex, IconButton, Popover, Text } from "@radix-ui/themes";
|
||||||
Crosshair1Icon,
|
import { useState, type RefObject } from "react";
|
||||||
EnterFullScreenIcon,
|
|
||||||
ExitFullScreenIcon,
|
|
||||||
EyeOpenIcon,
|
|
||||||
ShadowInnerIcon,
|
|
||||||
ShadowNoneIcon,
|
|
||||||
SunIcon,
|
|
||||||
} from "@radix-ui/react-icons";
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
Dialog,
|
|
||||||
DropdownMenu,
|
|
||||||
Flex,
|
|
||||||
IconButton,
|
|
||||||
Popover,
|
|
||||||
SegmentedControl,
|
|
||||||
Tabs,
|
|
||||||
Text,
|
|
||||||
Tooltip,
|
|
||||||
} from "@radix-ui/themes";
|
|
||||||
import { Suspense, useState, type RefObject } from "react";
|
|
||||||
import { Pose, poseEvent } from "../../core/blitzkit/pose";
|
|
||||||
import { DuelModule, useDuel } from "../../hooks/useDuel";
|
import { DuelModule, useDuel } from "../../hooks/useDuel";
|
||||||
import { useEquipment } from "../../hooks/useEquipment";
|
import { useEquipment } from "../../hooks/useEquipment";
|
||||||
import { useFullScreen } from "../../hooks/useFullScreen";
|
import { useFullScreen } from "../../hooks/useFullScreen";
|
||||||
@@ -32,25 +11,26 @@ import { useUnwrapper } from "../../hooks/useUnwrapper";
|
|||||||
import { Tankopedia } from "../../stores/tankopedia";
|
import { Tankopedia } from "../../stores/tankopedia";
|
||||||
import { TankopediaPersistent } from "../../stores/tankopediaPersistent";
|
import { TankopediaPersistent } from "../../stores/tankopediaPersistent";
|
||||||
import { TankopediaDisplay } from "../../stores/tankopediaPersistent/constants";
|
import { TankopediaDisplay } from "../../stores/tankopediaPersistent/constants";
|
||||||
|
import type { ComputedCharacteristics } from "../../types/characteristics";
|
||||||
import type { MaybeSkeletonComponentProps } from "../../types/maybeSkeletonComponentProps";
|
import type { MaybeSkeletonComponentProps } from "../../types/maybeSkeletonComponentProps";
|
||||||
import { EqualIcon } from "../EqualIcon";
|
import { EqualIcon } from "../EqualIcon";
|
||||||
import { ModuleButton } from "../ModuleButtons/ModuleButton";
|
import { ModuleButton } from "../ModuleButtons/ModuleButton";
|
||||||
import { ScreenshotButton } from "../ScreenshotButton";
|
|
||||||
import { SmallTankIcon } from "../SmallTankIcon";
|
|
||||||
import {
|
import {
|
||||||
SidebarIconButton,
|
SidebarIconButton,
|
||||||
SidebarIconGroup,
|
SidebarIconGroup,
|
||||||
} from "../TankopediaSidebarIconButtons";
|
} from "../TankopediaSidebarIconButtons";
|
||||||
import { TankSearch } from "../TankSearch";
|
|
||||||
import { CustomShellButton } from "./components/CustomShellButton";
|
import { CustomShellButton } from "./components/CustomShellButton";
|
||||||
import { DynamicArmorSwitcher } from "./components/DynamicArmorSwitcher";
|
|
||||||
import { SpallLinerSwitcher } from "./components/SpallLinerSwitcher";
|
|
||||||
|
|
||||||
type OptionsProps = MaybeSkeletonComponentProps & {
|
type OptionsProps = MaybeSkeletonComponentProps & {
|
||||||
canvas: RefObject<HTMLCanvasElement>;
|
canvas: RefObject<HTMLCanvasElement>;
|
||||||
|
antagonistCharacteristics: ComputedCharacteristics;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function TankopediaOptions({ canvas, skeleton }: OptionsProps) {
|
export function TankopediaOptions({
|
||||||
|
canvas,
|
||||||
|
skeleton,
|
||||||
|
antagonistCharacteristics,
|
||||||
|
}: OptionsProps) {
|
||||||
const strings = useStrings();
|
const strings = useStrings();
|
||||||
const unwrap = useUnwrapper();
|
const unwrap = useUnwrapper();
|
||||||
|
|
||||||
@@ -80,206 +60,9 @@ export function TankopediaOptions({ canvas, skeleton }: OptionsProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Flex
|
|
||||||
gap="2"
|
|
||||||
direction="column"
|
|
||||||
top="50%"
|
|
||||||
right={
|
|
||||||
requestedDisplay === TankopediaDisplay.DynamicArmor ? "3" : "-4rem"
|
|
||||||
}
|
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
transform: "translateY(-50%)",
|
|
||||||
transitionDuration: "200ms",
|
|
||||||
}}
|
|
||||||
align="end"
|
|
||||||
>
|
|
||||||
{!hasCustomShell && (
|
|
||||||
<Text color="gray" size={{ initial: "1", sm: "2" }}>
|
|
||||||
{/* {literals(strings.common.units.mm, {
|
|
||||||
value: (
|
|
||||||
resolvePenetrationCoefficient(
|
|
||||||
hasCalibratedShells,
|
|
||||||
equalize,
|
|
||||||
antagonistShell.type,
|
|
||||||
equalizer,
|
|
||||||
) * antagonistShell.penetration!.near
|
|
||||||
).toFixed(0),
|
|
||||||
})} */}
|
|
||||||
what
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{antagonistUniqueGuns.length > 1 && (
|
|
||||||
<Popover.Root>
|
|
||||||
<Popover.Trigger>
|
|
||||||
<IconButton
|
|
||||||
radius="full"
|
|
||||||
variant="soft"
|
|
||||||
color="gray"
|
|
||||||
size={{ initial: "2", sm: "3" }}
|
|
||||||
style={{ position: "relative" }}
|
|
||||||
>
|
|
||||||
<Text
|
|
||||||
size="1"
|
|
||||||
color="gray"
|
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
right: 0,
|
|
||||||
bottom: 0,
|
|
||||||
}}
|
|
||||||
mr="2"
|
|
||||||
mb="1"
|
|
||||||
>
|
|
||||||
{romanize(antagonistGun.tier)}
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<img
|
{/* <Flex
|
||||||
alt="Antagonist Gun"
|
|
||||||
src={alias("api", "/icons/modules/gun.webp")}
|
|
||||||
style={{
|
|
||||||
width: "65%",
|
|
||||||
height: "65%",
|
|
||||||
objectFit: "contain",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</IconButton>
|
|
||||||
</Popover.Trigger>
|
|
||||||
|
|
||||||
<Popover.Content side="left" align="center">
|
|
||||||
<Flex gap="2">
|
|
||||||
{[...antagonistUniqueGuns].reverse().map(({ gun, turret }) => (
|
|
||||||
<ModuleButton
|
|
||||||
module="gun"
|
|
||||||
key={gun.id}
|
|
||||||
onClick={() => {
|
|
||||||
Tankopedia.mutate((draft) => {
|
|
||||||
draft.antagonist.turret = turret.id;
|
|
||||||
draft.antagonist.gun = gun.id;
|
|
||||||
draft.antagonist.shell = gun.shells[0].id;
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
selected={gun.id === antagonistGun.id}
|
|
||||||
discriminator={romanize(gun.tier)}
|
|
||||||
secondaryDiscriminator={
|
|
||||||
<Text style={{ fontSize: "0.75em" }}>
|
|
||||||
{literals(strings.common.units.mm, {
|
|
||||||
value: gun.shells[0].caliber.toFixed(0),
|
|
||||||
})}
|
|
||||||
</Text>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Flex>
|
|
||||||
</Popover.Content>
|
|
||||||
</Popover.Root>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<SidebarIconGroup>
|
|
||||||
{antagonistGun.shells.map((thisShell) => (
|
|
||||||
<SidebarIconButton
|
|
||||||
key={thisShell.id}
|
|
||||||
selected={thisShell.id === antagonistShell.id && !hasCustomShell}
|
|
||||||
alt={unwrap(thisShell.name!)}
|
|
||||||
src={alias("api", `/icons/shells/${thisShell.icon}.webp`)}
|
|
||||||
onClick={() => {
|
|
||||||
Tankopedia.mutate((draft) => {
|
|
||||||
draft.antagonist.shell = thisShell.id;
|
|
||||||
draft.shot = undefined;
|
|
||||||
draft.customShell = undefined;
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
<CustomShellButton />
|
|
||||||
</SidebarIconGroup>
|
|
||||||
|
|
||||||
<Flex
|
|
||||||
direction="column"
|
|
||||||
style={{
|
|
||||||
pointerEvents: "auto",
|
|
||||||
borderRadius: "var(--radius-full)",
|
|
||||||
}}
|
|
||||||
overflow="hidden"
|
|
||||||
>
|
|
||||||
<IconButton
|
|
||||||
color={hasCalibratedShells ? undefined : "gray"}
|
|
||||||
variant="soft"
|
|
||||||
size={{ initial: "2", sm: "3" }}
|
|
||||||
radius="none"
|
|
||||||
onClick={() => {
|
|
||||||
Tankopedia.mutate((draft) => {
|
|
||||||
draft.shot = undefined;
|
|
||||||
|
|
||||||
if (hasCalibratedShells) {
|
|
||||||
delete draft.protagonist.equipment[0];
|
|
||||||
} else {
|
|
||||||
draft.antagonist.equipment[0] = 1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
alt="Calibrated Shells"
|
|
||||||
src={alias("api", "/icons/equipment/103.webp")}
|
|
||||||
style={{
|
|
||||||
width: "50%",
|
|
||||||
height: "50%",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</IconButton>
|
|
||||||
<IconButton
|
|
||||||
color={hasEnhancedArmor ? undefined : "gray"}
|
|
||||||
variant="soft"
|
|
||||||
size={{ initial: "2", sm: "3" }}
|
|
||||||
radius="none"
|
|
||||||
onClick={() => {
|
|
||||||
Tankopedia.mutate((draft) => {
|
|
||||||
if (hasEnhancedArmor) {
|
|
||||||
delete draft.protagonist.equipment[4];
|
|
||||||
} else {
|
|
||||||
draft.antagonist.equipment[4] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
draft.shot = undefined;
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
alt="Enhanced Armor"
|
|
||||||
src={alias("api", "/icons/equipment/110.webp")}
|
|
||||||
style={{
|
|
||||||
width: "50%",
|
|
||||||
height: "50%",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</IconButton>
|
|
||||||
|
|
||||||
<SpallLinerSwitcher />
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
{!skeleton && modelRequested && (
|
|
||||||
<Suspense>
|
|
||||||
<DynamicArmorSwitcher />
|
|
||||||
</Suspense>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<IconButton
|
|
||||||
variant="soft"
|
|
||||||
size={{ initial: "2", sm: "3" }}
|
|
||||||
color={equalize ? undefined : "gray"}
|
|
||||||
onClick={() => {
|
|
||||||
Tankopedia.mutate((draft) => {
|
|
||||||
draft.environment.equalize = !draft.environment.equalize;
|
|
||||||
draft.shot = undefined;
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<EqualIcon />
|
|
||||||
</IconButton>
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
<Flex
|
|
||||||
direction="column"
|
direction="column"
|
||||||
align="center"
|
align="center"
|
||||||
position="absolute"
|
position="absolute"
|
||||||
@@ -407,7 +190,7 @@ export function TankopediaOptions({ canvas, skeleton }: OptionsProps) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SegmentedControl.Item value={`${TankopediaDisplay.Model}`}>
|
<SegmentedControl.Item value={`${TankopediaDisplay.Model}`}>
|
||||||
<Tooltip content={strings.tanks.tabs.model}>
|
<Tooltip tooltip={strings.tanks.tabs.model}>
|
||||||
<Flex height="100%" align="center">
|
<Flex height="100%" align="center">
|
||||||
<img
|
<img
|
||||||
src="/assets/images/icons/tankopedia-model.png"
|
src="/assets/images/icons/tankopedia-model.png"
|
||||||
@@ -417,7 +200,7 @@ export function TankopediaOptions({ canvas, skeleton }: OptionsProps) {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</SegmentedControl.Item>
|
</SegmentedControl.Item>
|
||||||
<SegmentedControl.Item value={`${TankopediaDisplay.DynamicArmor}`}>
|
<SegmentedControl.Item value={`${TankopediaDisplay.DynamicArmor}`}>
|
||||||
<Tooltip content={strings.tanks.tabs.dynamic_armor}>
|
<Tooltip tooltip={strings.tanks.tabs.dynamic_armor}>
|
||||||
<Flex height="100%" align="center">
|
<Flex height="100%" align="center">
|
||||||
<img
|
<img
|
||||||
src="/assets/images/icons/tankopedia-dynamic-armor.png"
|
src="/assets/images/icons/tankopedia-dynamic-armor.png"
|
||||||
@@ -427,7 +210,7 @@ export function TankopediaOptions({ canvas, skeleton }: OptionsProps) {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</SegmentedControl.Item>
|
</SegmentedControl.Item>
|
||||||
<SegmentedControl.Item value={`${TankopediaDisplay.StaticArmor}`}>
|
<SegmentedControl.Item value={`${TankopediaDisplay.StaticArmor}`}>
|
||||||
<Tooltip content={strings.tanks.tabs.model}>
|
<Tooltip tooltip={strings.tanks.tabs.model}>
|
||||||
<Flex height="100%" align="center">
|
<Flex height="100%" align="center">
|
||||||
<img
|
<img
|
||||||
src="/assets/images/icons/tankopedia-static-armor.png"
|
src="/assets/images/icons/tankopedia-static-armor.png"
|
||||||
@@ -484,7 +267,7 @@ export function TankopediaOptions({ canvas, skeleton }: OptionsProps) {
|
|||||||
</Dialog.Root>
|
</Dialog.Root>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex> */}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -9,6 +9,7 @@ import { useModel } from "../../hooks/useModel";
|
|||||||
import { Tankopedia } from "../../stores/tankopedia";
|
import { Tankopedia } from "../../stores/tankopedia";
|
||||||
import { TankopediaPersistent } from "../../stores/tankopediaPersistent";
|
import { TankopediaPersistent } from "../../stores/tankopediaPersistent";
|
||||||
import { TankopediaDisplay } from "../../stores/tankopediaPersistent/constants";
|
import { TankopediaDisplay } from "../../stores/tankopediaPersistent/constants";
|
||||||
|
import type { ComputedCharacteristics } from "../../types/characteristics";
|
||||||
import type { MaybeSkeletonComponentProps } from "../../types/maybeSkeletonComponentProps";
|
import type { MaybeSkeletonComponentProps } from "../../types/maybeSkeletonComponentProps";
|
||||||
import { ArmorPlateDisplay } from "../ArmorPlateDisplay";
|
import { ArmorPlateDisplay } from "../ArmorPlateDisplay";
|
||||||
import { CanvasAutoClear } from "../CanvasAutoClear";
|
import { CanvasAutoClear } from "../CanvasAutoClear";
|
||||||
@@ -21,17 +22,19 @@ import { ShotDisplay } from "../ShotDisplay";
|
|||||||
import { SmartCanvas } from "../SmartCanvas";
|
import { SmartCanvas } from "../SmartCanvas";
|
||||||
import { StaticArmor, type ThicknessRange } from "../StaticArmor";
|
import { StaticArmor, type ThicknessRange } from "../StaticArmor";
|
||||||
import { TankModel } from "../TankModel";
|
import { TankModel } from "../TankModel";
|
||||||
import { TankopediaOptions } from "../TankopediaOptions";
|
import { TankopediaAntagonistControl } from "../TankopediaAntagonistControl";
|
||||||
import { TankopediaSkinSwitcher } from "../TankopediaSkinSwitcher";
|
import { TankopediaSkinSwitcher } from "../TankopediaSkinSwitcher";
|
||||||
import styles from "./index.module.css";
|
import styles from "./index.module.css";
|
||||||
|
|
||||||
type TankSandboxProps = MaybeSkeletonComponentProps & {
|
type TankSandboxProps = MaybeSkeletonComponentProps & {
|
||||||
thicknessRange: ThicknessRange;
|
thicknessRange: ThicknessRange;
|
||||||
|
antagonistCharacteristics: ComputedCharacteristics;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function TankopediaSandbox({
|
export function TankopediaSandbox({
|
||||||
skeleton,
|
skeleton,
|
||||||
thicknessRange,
|
thicknessRange,
|
||||||
|
antagonistCharacteristics,
|
||||||
}: TankSandboxProps) {
|
}: TankSandboxProps) {
|
||||||
const wrapper = useRef<HTMLDivElement>(null);
|
const wrapper = useRef<HTMLDivElement>(null);
|
||||||
const canvas = useRef<HTMLCanvasElement>(null);
|
const canvas = useRef<HTMLCanvasElement>(null);
|
||||||
@@ -172,7 +175,9 @@ export function TankopediaSandbox({
|
|||||||
</SmartCanvas>
|
</SmartCanvas>
|
||||||
|
|
||||||
<TankopediaSkinSwitcher />
|
<TankopediaSkinSwitcher />
|
||||||
<TankopediaOptions />
|
<TankopediaAntagonistControl
|
||||||
|
antagonistCharacteristics={antagonistCharacteristics}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -191,12 +191,14 @@ export function computeCharacteristics(
|
|||||||
return computed;
|
return computed;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useCharacteristics() {
|
export function useCharacteristics(side: DuelSide) {
|
||||||
const protagonist = Tankopedia.use((state) => state.protagonist);
|
const A = Tankopedia.use((state) => state[side]);
|
||||||
const antagonist = Tankopedia.use((state) => state.antagonist);
|
const B = Tankopedia.use(
|
||||||
|
(state) => state[side === "antagonist" ? "protagonist" : "antagonist"],
|
||||||
|
);
|
||||||
const environment = Tankopedia.use((state) => state.environment);
|
const environment = Tankopedia.use((state) => state.environment);
|
||||||
|
|
||||||
return computeCharacteristics({ protagonist, antagonist }, environment);
|
return computeCharacteristics({ protagonist: A, antagonist: B }, environment);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function computeDefaultCharacteristics(tank: TankDefinition) {
|
export function computeDefaultCharacteristics(tank: TankDefinition) {
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ export const Page = withErrorWrapper(
|
|||||||
const protagonist = Tankopedia.use((state) => state.protagonist);
|
const protagonist = Tankopedia.use((state) => state.protagonist);
|
||||||
const equalize = Tankopedia.use((state) => state.environment.equalize);
|
const equalize = Tankopedia.use((state) => state.environment.equalize);
|
||||||
|
|
||||||
const characteristics = useCharacteristics();
|
const characteristics = useCharacteristics("protagonist");
|
||||||
|
const antagonistCharacteristics = useCharacteristics("antagonist");
|
||||||
|
|
||||||
const thicknessRange = useMemo(() => {
|
const thicknessRange = useMemo(() => {
|
||||||
const entries = Object.values(tanks.tanks);
|
const entries = Object.values(tanks.tanks);
|
||||||
@@ -62,6 +63,7 @@ export const Page = withErrorWrapper(
|
|||||||
|
|
||||||
<div className={styles.sandbox}>
|
<div className={styles.sandbox}>
|
||||||
<TankopediaSandbox
|
<TankopediaSandbox
|
||||||
|
antagonistCharacteristics={antagonistCharacteristics}
|
||||||
skeleton={skeleton}
|
skeleton={skeleton}
|
||||||
thicknessRange={thicknessRange}
|
thicknessRange={thicknessRange}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in new issue
Block a user