mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-23 19:49:50 +02:00
Merge branch 'main' into ue
This commit is contained in:
commit
496e127f64
9 files changed
+309
-76
No files matched your search
@@ -0,0 +1,19 @@
|
|||||||
|
# BlitzKit 1.29.8
|
||||||
|
|
||||||
|
Thank you to [Mark Greene](https://github.com/Hitjack007) for your contributions this update.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Added an option to clear provisions and consumables from all tanks in Compare
|
||||||
|
- Added the ability to choose stock turrets and guns in Mixer if a tank has any
|
||||||
|
- Added streaks to Guesser
|
||||||
|
- Introduced an option to partially reveal tanks in Guesser
|
||||||
|
- This removes the darkness effect but keeps the name hidden
|
||||||
|
- This will reset your streak to 0
|
||||||
|
- Regardless of getting the right guess on the tank's name, your response will be counted as incorrect if partially revealed
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
|
||||||
|
- The Tankopedia now smoothly resizes when enlarging on interaction
|
||||||
|
- [Mark Greene] Stock turrets now correctly mount the corresponding stock gun
|
||||||
|
- The consumable and provision buttons in Compare are no longer tiny
|
||||||
@@ -917,7 +917,13 @@
|
|||||||
"next": "Next",
|
"next": "Next",
|
||||||
"no_results": "No results"
|
"no_results": "No results"
|
||||||
},
|
},
|
||||||
"stats": "{correct} / {total} guessed correctly"
|
"stats": "{correct} / {total} correct, {streak} streak",
|
||||||
|
"help": {
|
||||||
|
"title": "Partially reveal tank?",
|
||||||
|
"description": "Reveals the tank without showing the name. This round will be considered incorrect and break your streak!",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"reveal": "Reveal"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"tankopedia": {
|
"tankopedia": {
|
||||||
@@ -1267,10 +1273,12 @@
|
|||||||
"upgrade": "Upgrade"
|
"upgrade": "Upgrade"
|
||||||
},
|
},
|
||||||
"provisions": {
|
"provisions": {
|
||||||
"clear": "Clear"
|
"clear": "Clear this tank",
|
||||||
|
"clear_all": "Clear all tanks"
|
||||||
},
|
},
|
||||||
"consumables": {
|
"consumables": {
|
||||||
"clear": "Clear"
|
"clear": "Clear this tank",
|
||||||
|
"clear_all": "Clear all tanks"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -277,14 +277,14 @@ export function CompareTable({ stats }: CompareTableProps) {
|
|||||||
</Popover.Content>
|
</Popover.Content>
|
||||||
</Popover.Root>
|
</Popover.Root>
|
||||||
|
|
||||||
<Flex align="center" justify="center" gap="3">
|
<Flex justify="center" gap="3">
|
||||||
<Popover.Root>
|
<Popover.Root>
|
||||||
<Popover.Trigger>
|
<Popover.Trigger>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
radius="large"
|
radius="large"
|
||||||
style={{
|
style={{
|
||||||
height: "100%",
|
height: "2rem",
|
||||||
width: 12,
|
width: 12,
|
||||||
position: "relative",
|
position: "relative",
|
||||||
}}
|
}}
|
||||||
@@ -345,7 +345,24 @@ export function CompareTable({ stats }: CompareTableProps) {
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Flex justify="end" mt="4">
|
|
||||||
|
<Flex justify="end" mt="4" gap="3">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
onClick={() => {
|
||||||
|
CompareEphemeral.mutate((draft) => {
|
||||||
|
draft.members.map((member) => {
|
||||||
|
member.provisions = [];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
strings.website.tools.compare.table.provisions
|
||||||
|
.clear_all
|
||||||
|
}
|
||||||
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -473,7 +490,7 @@ export function CompareTable({ stats }: CompareTableProps) {
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
radius="large"
|
radius="large"
|
||||||
style={{
|
style={{
|
||||||
height: "100%",
|
height: "2rem",
|
||||||
width: 12,
|
width: 12,
|
||||||
position: "relative",
|
position: "relative",
|
||||||
}}
|
}}
|
||||||
@@ -536,7 +553,23 @@ export function CompareTable({ stats }: CompareTableProps) {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Flex justify="end" mt="4">
|
<Flex justify="end" mt="4" gap="4">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
onClick={() => {
|
||||||
|
CompareEphemeral.mutate((draft) => {
|
||||||
|
draft.members.map((member) => {
|
||||||
|
member.consumables = [];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
strings.website.tools.compare.table.consumables
|
||||||
|
.clear_all
|
||||||
|
}
|
||||||
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|||||||
@@ -9,12 +9,15 @@ const CONTRAST = 2 ** 2;
|
|||||||
|
|
||||||
export function GuessRenderer() {
|
export function GuessRenderer() {
|
||||||
const guessState = Guess.use((state) => state.guessState);
|
const guessState = Guess.use((state) => state.guessState);
|
||||||
const isRevealed = guessState !== GuessState.NotGuessed;
|
const helpingReveal = Guess.use((state) => state.helpingReveal);
|
||||||
|
const isRevealed = helpingReveal || guessState !== GuessState.NotGuessed;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SmartCanvas
|
<SmartCanvas
|
||||||
style={{
|
style={{
|
||||||
filter: `grayscale(${isRevealed ? 0 : 1}) contrast(${isRevealed ? 1 : CONTRAST}) brightness(${isRevealed ? 1 : BRIGHTNESS})`,
|
filter: `grayscale(${isRevealed ? 0 : 1}) contrast(${
|
||||||
|
isRevealed ? 1 : CONTRAST
|
||||||
|
}) brightness(${isRevealed ? 1 : BRIGHTNESS})`,
|
||||||
transitionDuration: isRevealed ? "2s" : undefined,
|
transitionDuration: isRevealed ? "2s" : undefined,
|
||||||
transitionProperty: "filter",
|
transitionProperty: "filter",
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -7,14 +7,17 @@ import {
|
|||||||
import { literals } from "@blitzkit/i18n";
|
import { literals } from "@blitzkit/i18n";
|
||||||
import {
|
import {
|
||||||
ArrowRightIcon,
|
ArrowRightIcon,
|
||||||
|
EyeOpenIcon,
|
||||||
MagnifyingGlassIcon,
|
MagnifyingGlassIcon,
|
||||||
PaperPlaneIcon,
|
PaperPlaneIcon,
|
||||||
} from "@radix-ui/react-icons";
|
} from "@radix-ui/react-icons";
|
||||||
import {
|
import {
|
||||||
|
AlertDialog,
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
Flex,
|
Flex,
|
||||||
|
IconButton,
|
||||||
Spinner,
|
Spinner,
|
||||||
Text,
|
Text,
|
||||||
TextField,
|
TextField,
|
||||||
@@ -43,6 +46,8 @@ export function Guesser() {
|
|||||||
const guessState = Guess.use((state) => state.guessState);
|
const guessState = Guess.use((state) => state.guessState);
|
||||||
const correctGuesses = Guess.use((state) => state.correctGuesses);
|
const correctGuesses = Guess.use((state) => state.correctGuesses);
|
||||||
const totalGuesses = Guess.use((state) => state.totalGuesses);
|
const totalGuesses = Guess.use((state) => state.totalGuesses);
|
||||||
|
const helpingReveal = Guess.use((state) => state.helpingReveal);
|
||||||
|
const streak = Guess.use((state) => state.streak);
|
||||||
const { strings, unwrap } = useLocale();
|
const { strings, unwrap } = useLocale();
|
||||||
const input = useRef<HTMLInputElement>(null);
|
const input = useRef<HTMLInputElement>(null);
|
||||||
const [searching, setSearching] = useState(false);
|
const [searching, setSearching] = useState(false);
|
||||||
@@ -150,6 +155,7 @@ export function Guesser() {
|
|||||||
{literals(strings.website.tools.guess.stats, {
|
{literals(strings.website.tools.guess.stats, {
|
||||||
correct: correctGuesses,
|
correct: correctGuesses,
|
||||||
total: totalGuesses,
|
total: totalGuesses,
|
||||||
|
streak: streak,
|
||||||
})}
|
})}
|
||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
@@ -169,6 +175,48 @@ export function Guesser() {
|
|||||||
</TextField.Slot>
|
</TextField.Slot>
|
||||||
</TextField.Root>
|
</TextField.Root>
|
||||||
|
|
||||||
|
<AlertDialog.Root>
|
||||||
|
<AlertDialog.Trigger>
|
||||||
|
<IconButton
|
||||||
|
size="3"
|
||||||
|
disabled={guessState !== GuessState.NotGuessed}
|
||||||
|
>
|
||||||
|
<EyeOpenIcon />
|
||||||
|
</IconButton>
|
||||||
|
</AlertDialog.Trigger>
|
||||||
|
|
||||||
|
<AlertDialog.Content>
|
||||||
|
<AlertDialog.Title>
|
||||||
|
{strings.website.tools.guess.help.title}
|
||||||
|
</AlertDialog.Title>
|
||||||
|
<AlertDialog.Description>
|
||||||
|
{strings.website.tools.guess.help.description}
|
||||||
|
</AlertDialog.Description>
|
||||||
|
|
||||||
|
<Flex justify="end" gap="2">
|
||||||
|
<AlertDialog.Cancel>
|
||||||
|
<Button variant="outline">
|
||||||
|
{strings.website.tools.guess.help.cancel}
|
||||||
|
</Button>
|
||||||
|
</AlertDialog.Cancel>
|
||||||
|
|
||||||
|
<AlertDialog.Action>
|
||||||
|
<Button
|
||||||
|
color="tomato"
|
||||||
|
onClick={() => {
|
||||||
|
Guess.mutate((draft) => {
|
||||||
|
draft.helpingReveal = true;
|
||||||
|
draft.streak = 0;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{strings.website.tools.guess.help.reveal}
|
||||||
|
</Button>
|
||||||
|
</AlertDialog.Action>
|
||||||
|
</Flex>
|
||||||
|
</AlertDialog.Content>
|
||||||
|
</AlertDialog.Root>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
size="3"
|
size="3"
|
||||||
color={
|
color={
|
||||||
@@ -178,7 +226,8 @@ export function Guesser() {
|
|||||||
}
|
}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (guessState === GuessState.NotGuessed) {
|
if (guessState === GuessState.NotGuessed) {
|
||||||
const correct = selected !== null && selected.id === tank.id;
|
const correct =
|
||||||
|
selected !== null && selected.id === tank.id && !helpingReveal;
|
||||||
|
|
||||||
Guess.mutate((draft) => {
|
Guess.mutate((draft) => {
|
||||||
draft.guessState = correct
|
draft.guessState = correct
|
||||||
@@ -186,6 +235,7 @@ export function Guesser() {
|
|||||||
: GuessState.Incorrect;
|
: GuessState.Incorrect;
|
||||||
draft.totalGuesses++;
|
draft.totalGuesses++;
|
||||||
draft.correctGuesses += correct ? 1 : 0;
|
draft.correctGuesses += correct ? 1 : 0;
|
||||||
|
draft.streak = correct ? draft.streak + 1 : 0;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const id = Number(ids[Math.floor(Math.random() * ids.length)]);
|
const id = Number(ids[Math.floor(Math.random() * ids.length)]);
|
||||||
@@ -196,6 +246,7 @@ export function Guesser() {
|
|||||||
|
|
||||||
draft.tank = tank;
|
draft.tank = tank;
|
||||||
draft.guessState = GuessState.NotGuessed;
|
draft.guessState = GuessState.NotGuessed;
|
||||||
|
draft.helpingReveal = false;
|
||||||
|
|
||||||
input.current.value = "";
|
input.current.value = "";
|
||||||
});
|
});
|
||||||
|
|||||||
+11
-5
@@ -176,11 +176,17 @@ export function Modules() {
|
|||||||
(turret) => turret.id === unlock.id
|
(turret) => turret.id === unlock.id
|
||||||
)!;
|
)!;
|
||||||
|
|
||||||
if (
|
// Check if current gun exists in new turret
|
||||||
!draft.protagonist.turret.guns.some(
|
const gunInNewTurret = draft.protagonist.turret.guns.find(
|
||||||
(gun) => gun.id === draft.protagonist.gun.id
|
(gun) => gun.id === draft.protagonist.gun.id
|
||||||
)
|
);
|
||||||
) {
|
|
||||||
|
if (gunInNewTurret) {
|
||||||
|
// Gun exists - get it FROM this turret
|
||||||
|
draft.protagonist.gun = gunInNewTurret;
|
||||||
|
draft.protagonist.shell = gunInNewTurret.shells[0];
|
||||||
|
} else {
|
||||||
|
// Gun doesn't exist - select top gun from new turret
|
||||||
draft.protagonist.gun = draft.protagonist.turret.guns.at(-1)!;
|
draft.protagonist.gun = draft.protagonist.turret.guns.at(-1)!;
|
||||||
draft.protagonist.shell = draft.protagonist.gun.shells[0];
|
draft.protagonist.shell = draft.protagonist.gun.shells[0];
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -150,6 +150,7 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SmartCanvas
|
<SmartCanvas
|
||||||
|
resize={{ debounce: 0 }}
|
||||||
ref={canvas}
|
ref={canvas}
|
||||||
scene={{ fog: new Fog("black", 30, 50) }}
|
scene={{ fog: new Fog("black", 30, 50) }}
|
||||||
gl={{
|
gl={{
|
||||||
|
|||||||
@@ -1,23 +1,31 @@
|
|||||||
import { asset } from "@blitzkit/core";
|
import {
|
||||||
import { CaretRightIcon, UpdateIcon } from "@radix-ui/react-icons";
|
asset,
|
||||||
|
GunDefinition,
|
||||||
|
TankDefinition,
|
||||||
|
TIER_ROMAN_NUMERALS,
|
||||||
|
TurretDefinition,
|
||||||
|
} from "@blitzkit/core";
|
||||||
|
import { literals } from "@blitzkit/i18n";
|
||||||
|
import { CaretRightIcon, CaretUpIcon, UpdateIcon } from "@radix-ui/react-icons";
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
Dialog,
|
Dialog,
|
||||||
Flex,
|
Flex,
|
||||||
|
IconButton,
|
||||||
|
Popover,
|
||||||
Skeleton,
|
Skeleton,
|
||||||
Text,
|
Text,
|
||||||
type ButtonProps,
|
type ButtonProps,
|
||||||
} from "@radix-ui/themes";
|
} from "@radix-ui/themes";
|
||||||
import { useRef, useState } from "react";
|
import { useRef, useState } from "react";
|
||||||
import { MixerScene } from "../../../components/MixerScene";
|
import { MixerScene } from "../../../components/MixerScene";
|
||||||
|
import { ModuleButton } from "../../../components/ModuleButtons/ModuleButton";
|
||||||
import { PageWrapper } from "../../../components/PageWrapper";
|
import { PageWrapper } from "../../../components/PageWrapper";
|
||||||
import { ScreenshotButton } from "../../../components/ScreenshotButton";
|
import { ScreenshotButton } from "../../../components/ScreenshotButton";
|
||||||
import {
|
import { TankSearch } from "../../../components/TankSearch";
|
||||||
TankSearch,
|
|
||||||
type TankSearchProps,
|
|
||||||
} from "../../../components/TankSearch";
|
|
||||||
import { awaitableModelDefinitions } from "../../../core/awaitables/modelDefinitions";
|
import { awaitableModelDefinitions } from "../../../core/awaitables/modelDefinitions";
|
||||||
|
import { awaitableTankDefinitions } from "../../../core/awaitables/tankDefinitions";
|
||||||
import { curateMixer } from "../../../core/blitzkit/curateMixer";
|
import { curateMixer } from "../../../core/blitzkit/curateMixer";
|
||||||
import {
|
import {
|
||||||
LocaleProvider,
|
LocaleProvider,
|
||||||
@@ -31,6 +39,7 @@ import type { MaybeSkeletonComponentProps } from "../../../types/maybeSkeletonCo
|
|||||||
const modelDefinition = await awaitableModelDefinitions.then(
|
const modelDefinition = await awaitableModelDefinitions.then(
|
||||||
({ models }) => models[1]
|
({ models }) => models[1]
|
||||||
);
|
);
|
||||||
|
const tankDefinitions = await awaitableTankDefinitions;
|
||||||
|
|
||||||
export function Page({
|
export function Page({
|
||||||
locale,
|
locale,
|
||||||
@@ -50,57 +59,147 @@ export function Page({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ModuleButton({
|
type ModuleButtonProps = Omit<ButtonProps, "onSelect"> &
|
||||||
|
MaybeSkeletonComponentProps & {
|
||||||
|
tank: number;
|
||||||
|
onSelect: (
|
||||||
|
tank: TankDefinition,
|
||||||
|
turret?: TurretDefinition,
|
||||||
|
gun?: GunDefinition
|
||||||
|
) => void;
|
||||||
|
turret?: number;
|
||||||
|
gun?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
function ModuleChooser({
|
||||||
skeleton,
|
skeleton,
|
||||||
tank,
|
tank,
|
||||||
onSelect,
|
onSelect,
|
||||||
|
turret,
|
||||||
|
gun,
|
||||||
...props
|
...props
|
||||||
}: Omit<ButtonProps, "onSelect"> &
|
}: ModuleButtonProps) {
|
||||||
MaybeSkeletonComponentProps & {
|
|
||||||
tank: number;
|
|
||||||
onSelect: TankSearchProps["onSelect"];
|
|
||||||
}) {
|
|
||||||
const { strings } = useLocale();
|
const { strings } = useLocale();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
const tankDefinition = tankDefinitions.tanks[tank];
|
||||||
|
const turretDefinition = tankDefinition.turrets.find((t) => t.id === turret);
|
||||||
|
const gunDefinition = turretDefinition?.guns.find((g) => g.id === gun);
|
||||||
|
const subSelector =
|
||||||
|
turret !== undefined &&
|
||||||
|
(tankDefinition.turrets.length > 1 ||
|
||||||
|
(gun !== undefined && turretDefinition!.guns.length > 1));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog.Root open={open} onOpenChange={setOpen}>
|
<Flex>
|
||||||
<Dialog.Trigger>
|
<Dialog.Root open={open} onOpenChange={setOpen}>
|
||||||
<Button
|
<Dialog.Trigger>
|
||||||
variant="surface"
|
<Button
|
||||||
size="4"
|
variant="surface"
|
||||||
style={{ overflow: "clip" }}
|
size={{ initial: "3", md: "4" }}
|
||||||
{...props}
|
style={{
|
||||||
>
|
overflow: "clip",
|
||||||
{skeleton ? (
|
borderTopRightRadius: subSelector ? 0 : undefined,
|
||||||
<Skeleton width="4em" height="2em" />
|
borderBottomRightRadius: subSelector ? 0 : undefined,
|
||||||
) : (
|
}}
|
||||||
<img
|
{...props}
|
||||||
|
>
|
||||||
|
{skeleton ? (
|
||||||
|
<Skeleton width="4em" height="2em" />
|
||||||
|
) : (
|
||||||
|
<img
|
||||||
|
style={{
|
||||||
|
width: "4em",
|
||||||
|
height: "4em",
|
||||||
|
objectFit: "contain",
|
||||||
|
}}
|
||||||
|
src={asset(`icons/tanks/big/${tank}.webp`)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</Dialog.Trigger>
|
||||||
|
|
||||||
|
<Dialog.Content>
|
||||||
|
<Dialog.Title>
|
||||||
|
{strings.website.common.tank_search.select_dialog_title}
|
||||||
|
</Dialog.Title>
|
||||||
|
|
||||||
|
<TankSearch
|
||||||
|
compact
|
||||||
|
onSelect={(tank) => {
|
||||||
|
onSelect?.(tank);
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Dialog.Content>
|
||||||
|
</Dialog.Root>
|
||||||
|
|
||||||
|
{subSelector && (
|
||||||
|
<Popover.Root>
|
||||||
|
<Popover.Trigger>
|
||||||
|
<IconButton
|
||||||
|
variant="surface"
|
||||||
|
size={{ initial: "3", md: "4" }}
|
||||||
style={{
|
style={{
|
||||||
width: "4em",
|
overflow: "clip",
|
||||||
height: "4em",
|
borderTopLeftRadius: 0,
|
||||||
objectFit: "contain",
|
borderBottomLeftRadius: 0,
|
||||||
|
marginLeft: -1,
|
||||||
}}
|
}}
|
||||||
src={asset(`icons/tanks/big/${tank}.webp`)}
|
{...props}
|
||||||
/>
|
>
|
||||||
)}
|
<CaretUpIcon />
|
||||||
</Button>
|
</IconButton>
|
||||||
</Dialog.Trigger>
|
</Popover.Trigger>
|
||||||
|
|
||||||
<Dialog.Content>
|
<Popover.Content>
|
||||||
<Dialog.Title>
|
<Flex direction="column" gap="2">
|
||||||
{strings.website.common.tank_search.select_dialog_title}
|
{tankDefinition.turrets.length > 1 && (
|
||||||
</Dialog.Title>
|
<Flex gap="2">
|
||||||
|
{tankDefinition.turrets.map((turret) => (
|
||||||
|
<ModuleButton
|
||||||
|
variant={
|
||||||
|
turret.id === turretDefinition?.id ? "solid" : undefined
|
||||||
|
}
|
||||||
|
key={turret.id}
|
||||||
|
module="turret"
|
||||||
|
discriminator={TIER_ROMAN_NUMERALS[turret.tier]}
|
||||||
|
onClick={() => {
|
||||||
|
onSelect?.(tankDefinition, turret);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
|
||||||
<TankSearch
|
{turretDefinition && turretDefinition.guns.length > 1 && (
|
||||||
compact
|
<Flex gap="2">
|
||||||
onSelect={(tank) => {
|
{turretDefinition.guns.map((gun) => (
|
||||||
onSelect?.(tank);
|
<ModuleButton
|
||||||
setOpen(false);
|
variant={
|
||||||
}}
|
gun.id === gunDefinition?.id ? "solid" : undefined
|
||||||
/>
|
}
|
||||||
</Dialog.Content>
|
key={gun.id}
|
||||||
</Dialog.Root>
|
module="gun"
|
||||||
|
discriminator={TIER_ROMAN_NUMERALS[gun.tier]}
|
||||||
|
secondaryDiscriminator={
|
||||||
|
<Text style={{ fontSize: "0.75em" }}>
|
||||||
|
{literals(strings.common.units.mm, {
|
||||||
|
value: gun.shells[0].caliber.toFixed(0),
|
||||||
|
})}
|
||||||
|
</Text>
|
||||||
|
}
|
||||||
|
onClick={() => {
|
||||||
|
onSelect?.(tankDefinition, turretDefinition, gun);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</Popover.Content>
|
||||||
|
</Popover.Root>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,9 +212,12 @@ function ModuleSeparator() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function Content({ skeleton }: MaybeSkeletonComponentProps) {
|
function Content({ skeleton }: MaybeSkeletonComponentProps) {
|
||||||
const hull = Mixer.use((state) => state.hull);
|
const hullTankId = Mixer.use((state) => state.hull);
|
||||||
const turretId = Mixer.use((state) => state.turret.tank.id);
|
const turretTankId = Mixer.use((state) => state.turret.tank.id);
|
||||||
const gunId = Mixer.use((state) => state.gun.tank.id);
|
const turretTurretId = Mixer.use((state) => state.turret.turret.id);
|
||||||
|
const gunTankId = Mixer.use((state) => state.gun.tank.id);
|
||||||
|
const gunTurretId = Mixer.use((state) => state.gun.turret.id);
|
||||||
|
const gunGunId = Mixer.use((state) => state.gun.gun.id);
|
||||||
const canvas = useRef<HTMLCanvasElement>(null!);
|
const canvas = useRef<HTMLCanvasElement>(null!);
|
||||||
const { strings } = useLocale();
|
const { strings } = useLocale();
|
||||||
|
|
||||||
@@ -136,7 +238,7 @@ function Content({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
>
|
>
|
||||||
<Flex gap="2">
|
<Flex gap="2">
|
||||||
<ScreenshotButton
|
<ScreenshotButton
|
||||||
name={hull.slug}
|
name={hullTankId.slug}
|
||||||
canvas={canvas}
|
canvas={canvas}
|
||||||
highContrast
|
highContrast
|
||||||
variant="surface"
|
variant="surface"
|
||||||
@@ -160,9 +262,9 @@ function Content({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Flex gap="2" align="center">
|
<Flex gap="2" align="center">
|
||||||
<ModuleButton
|
<ModuleChooser
|
||||||
skeleton={skeleton}
|
skeleton={skeleton}
|
||||||
tank={hull.id}
|
tank={hullTankId.id}
|
||||||
onSelect={(tank) => {
|
onSelect={(tank) => {
|
||||||
Mixer.mutate((draft) => {
|
Mixer.mutate((draft) => {
|
||||||
draft.hull = tank;
|
draft.hull = tank;
|
||||||
@@ -170,28 +272,34 @@ function Content({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<ModuleSeparator />
|
<ModuleSeparator />
|
||||||
<ModuleButton
|
<ModuleChooser
|
||||||
skeleton={skeleton}
|
skeleton={skeleton}
|
||||||
tank={turretId}
|
tank={turretTankId}
|
||||||
onSelect={(tank) => {
|
turret={turretTurretId}
|
||||||
|
onSelect={(tank, turret) => {
|
||||||
Mixer.mutate((draft) => {
|
Mixer.mutate((draft) => {
|
||||||
draft.turret = {
|
draft.turret = {
|
||||||
tank,
|
tank,
|
||||||
turret: tank.turrets.at(-1)!,
|
turret: turret ?? tank.turrets.at(-1)!,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<ModuleSeparator />
|
<ModuleSeparator />
|
||||||
<ModuleButton
|
<ModuleChooser
|
||||||
skeleton={skeleton}
|
skeleton={skeleton}
|
||||||
tank={gunId}
|
tank={gunTankId}
|
||||||
onSelect={(tank) => {
|
turret={gunTurretId}
|
||||||
|
gun={gunGunId}
|
||||||
|
onSelect={(tank, turret, gun) => {
|
||||||
Mixer.mutate((draft) => {
|
Mixer.mutate((draft) => {
|
||||||
draft.gun = {
|
draft.gun = {
|
||||||
tank,
|
tank,
|
||||||
turret: tank.turrets.at(-1)!,
|
turret: turret ?? tank.turrets.at(-1)!,
|
||||||
gun: tank.turrets.at(-1)!.guns.at(-1)!,
|
gun:
|
||||||
|
gun ??
|
||||||
|
turret?.guns.at(-1) ??
|
||||||
|
tank.turrets.at(-1)!.guns.at(-1)!,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ export interface Guess {
|
|||||||
guessState: GuessState;
|
guessState: GuessState;
|
||||||
totalGuesses: number;
|
totalGuesses: number;
|
||||||
correctGuesses: number;
|
correctGuesses: number;
|
||||||
|
streak: number;
|
||||||
|
helpingReveal: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Guess = new Varuna<Guess, TankDefinition>((tank) => ({
|
export const Guess = new Varuna<Guess, TankDefinition>((tank) => ({
|
||||||
@@ -19,4 +21,6 @@ export const Guess = new Varuna<Guess, TankDefinition>((tank) => ({
|
|||||||
guessState: GuessState.NotGuessed,
|
guessState: GuessState.NotGuessed,
|
||||||
totalGuesses: 0,
|
totalGuesses: 0,
|
||||||
correctGuesses: 0,
|
correctGuesses: 0,
|
||||||
|
streak: 0,
|
||||||
|
helpingReveal: false,
|
||||||
}));
|
}));
|
||||||
Reference in new issue
Block a user