mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-25 11:39:51 +01:00
feat: upgrade rest of them
This commit is contained in:
1 parent
fe4afdcbf0
commit
30e87743fc
63 files changed
+824
-1123
No files matched your search
@@ -1,24 +1,23 @@
|
||||
import { assertSecret, fetchTankDefinitions } from '@blitzkit/core';
|
||||
import { MinusCircledIcon } from '@radix-ui/react-icons';
|
||||
import { Callout, Flex } from '@radix-ui/themes';
|
||||
import { uniq } from 'lodash-es';
|
||||
import { useEffect } from 'react';
|
||||
import { Duel } from '../../stores/duel';
|
||||
import { TankopediaPersistent } from '../../stores/tankopediaPersistent';
|
||||
import { AesonPlug } from '../AesonPlug';
|
||||
import { ExperimentIcon } from '../ExperimentIcon';
|
||||
import { MAX_RECENTLY_VIEWED } from '../TankSearch/constants';
|
||||
import { assertSecret, fetchTankDefinitions } from "@blitzkit/core";
|
||||
import { MinusCircledIcon } from "@radix-ui/react-icons";
|
||||
import { Callout, Flex } from "@radix-ui/themes";
|
||||
import { uniq } from "lodash-es";
|
||||
import { useEffect } from "react";
|
||||
import { Duel } from "../../stores/duel";
|
||||
import { TankopediaPersistent } from "../../stores/tankopediaPersistent";
|
||||
import { AesonPlug } from "../AesonPlug";
|
||||
import { ExperimentIcon } from "../ExperimentIcon";
|
||||
import { MAX_RECENTLY_VIEWED } from "../TankSearch/constants";
|
||||
|
||||
const tankDefinitions = await fetchTankDefinitions();
|
||||
|
||||
export function CalloutsSection() {
|
||||
const tank = Duel.use((state) => state.protagonist.tank);
|
||||
const mutateTankopediaPersistent = TankopediaPersistent.useMutation();
|
||||
const promoteAeson =
|
||||
assertSecret(import.meta.env.PUBLIC_BRANCH) === 'preview';
|
||||
assertSecret(import.meta.env.PUBLIC_BRANCH) === "preview";
|
||||
|
||||
useEffect(() => {
|
||||
mutateTankopediaPersistent((draft) => {
|
||||
TankopediaPersistent.mutate((draft) => {
|
||||
draft.recentlyViewed = uniq([tank.id, ...draft.recentlyViewed])
|
||||
.filter((id) => id in tankDefinitions.tanks)
|
||||
.slice(0, MAX_RECENTLY_VIEWED);
|
||||
@@ -45,7 +44,7 @@ export function CalloutsSection() {
|
||||
{tank.testing && (
|
||||
<Callout.Root color="red">
|
||||
<Callout.Icon>
|
||||
<ExperimentIcon style={{ width: '1em', height: '1em' }} />
|
||||
<ExperimentIcon style={{ width: "1em", height: "1em" }} />
|
||||
</Callout.Icon>
|
||||
<Callout.Text>
|
||||
Tanks in testing are subject to change and may not represent the
|
||||
|
||||
+28
-29
@@ -1,14 +1,14 @@
|
||||
import { DragHandleDots2Icon } from '@radix-ui/react-icons';
|
||||
import { Box, Flex, Text } from '@radix-ui/themes';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { clamp } from 'three/src/math/MathUtils.js';
|
||||
import { DragHandleDots2Icon } from "@radix-ui/react-icons";
|
||||
import { Box, Flex, Text } from "@radix-ui/themes";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { clamp } from "three/src/math/MathUtils.js";
|
||||
import {
|
||||
fakeLogSquishyHigh,
|
||||
fakeLogSquishyHighInverse,
|
||||
} from '../../../../../../../../../core/blitzkit/fakeLog';
|
||||
import { Var } from '../../../../../../../../../core/radix/var';
|
||||
import { Duel } from '../../../../../../../../../stores/duel';
|
||||
import './index.css';
|
||||
} from "../../../../../../../../../core/blitzkit/fakeLog";
|
||||
import { Var } from "../../../../../../../../../core/radix/var";
|
||||
import { Duel } from "../../../../../../../../../stores/duel";
|
||||
import "./index.css";
|
||||
|
||||
interface Props {
|
||||
maxDistance: number;
|
||||
@@ -17,11 +17,10 @@ interface Props {
|
||||
|
||||
export function AssaultCursor({ maxDistance, maxFakeDistance }: Props) {
|
||||
const assaultDistance = Duel.use(
|
||||
(state) => state.protagonist.assaultDistance,
|
||||
(state) => state.protagonist.assaultDistance
|
||||
);
|
||||
const [x, setX] = useState(fakeLogSquishyHigh(assaultDistance / maxDistance));
|
||||
const cursor = useRef<HTMLDivElement>(null);
|
||||
const mutateDuel = Duel.useMutation();
|
||||
|
||||
useEffect(() => {
|
||||
let localX = x;
|
||||
@@ -32,8 +31,8 @@ export function AssaultCursor({ maxDistance, maxFakeDistance }: Props) {
|
||||
|
||||
lastX = event.clientX;
|
||||
|
||||
window.addEventListener('pointermove', handlePointerMove);
|
||||
window.addEventListener('pointerup', handlePointerUp);
|
||||
window.addEventListener("pointermove", handlePointerMove);
|
||||
window.addEventListener("pointerup", handlePointerUp);
|
||||
}
|
||||
|
||||
function handlePointerMove(event: PointerEvent) {
|
||||
@@ -54,21 +53,21 @@ export function AssaultCursor({ maxDistance, maxFakeDistance }: Props) {
|
||||
}
|
||||
|
||||
function handlePointerUp() {
|
||||
mutateDuel((draft) => {
|
||||
Duel.mutate((draft) => {
|
||||
draft.protagonist.assaultDistance =
|
||||
fakeLogSquishyHighInverse(localX * maxFakeDistance) * maxDistance;
|
||||
});
|
||||
|
||||
window.removeEventListener('pointermove', handlePointerMove);
|
||||
window.removeEventListener('pointerup', handlePointerUp);
|
||||
window.removeEventListener("pointermove", handlePointerMove);
|
||||
window.removeEventListener("pointerup", handlePointerUp);
|
||||
}
|
||||
|
||||
cursor.current?.addEventListener('pointerdown', handlePointerDown);
|
||||
cursor.current?.addEventListener("pointerdown", handlePointerDown);
|
||||
|
||||
return () => {
|
||||
cursor.current?.removeEventListener('pointerdown', handlePointerDown);
|
||||
window.removeEventListener('pointermove', handlePointerMove);
|
||||
window.removeEventListener('pointerup', handlePointerUp);
|
||||
cursor.current?.removeEventListener("pointerdown", handlePointerDown);
|
||||
window.removeEventListener("pointermove", handlePointerMove);
|
||||
window.removeEventListener("pointerup", handlePointerUp);
|
||||
};
|
||||
}, []);
|
||||
|
||||
@@ -82,9 +81,9 @@ export function AssaultCursor({ maxDistance, maxFakeDistance }: Props) {
|
||||
width="2pt"
|
||||
id="assault-cursor"
|
||||
style={{
|
||||
touchAction: 'none',
|
||||
transform: 'translateX(-50%)',
|
||||
backgroundColor: Var('accent-11'),
|
||||
touchAction: "none",
|
||||
transform: "translateX(-50%)",
|
||||
backgroundColor: Var("accent-11"),
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
@@ -93,19 +92,19 @@ export function AssaultCursor({ maxDistance, maxFakeDistance }: Props) {
|
||||
position="absolute"
|
||||
left="50%"
|
||||
bottom="100%"
|
||||
style={{ transform: 'translateX(-50%)' }}
|
||||
style={{ transform: "translateX(-50%)" }}
|
||||
>
|
||||
<Flex
|
||||
align="center"
|
||||
justify="center"
|
||||
style={{
|
||||
width: '1.5rem',
|
||||
height: '1.5rem',
|
||||
backgroundColor: Var('accent-9'),
|
||||
borderRadius: `50% 50% ${Var('radius-1')} ${Var('radius-1')}`,
|
||||
width: "1.5rem",
|
||||
height: "1.5rem",
|
||||
backgroundColor: Var("accent-9"),
|
||||
borderRadius: `50% 50% ${Var("radius-1")} ${Var("radius-1")}`,
|
||||
}}
|
||||
>
|
||||
<Text style={{ display: 'flex' }} highContrast color="purple">
|
||||
<Text style={{ display: "flex" }} highContrast color="purple">
|
||||
<DragHandleDots2Icon />
|
||||
</Text>
|
||||
</Flex>
|
||||
@@ -114,7 +113,7 @@ export function AssaultCursor({ maxDistance, maxFakeDistance }: Props) {
|
||||
height="0.5rem"
|
||||
width="2pt"
|
||||
style={{
|
||||
backgroundColor: Var('accent-11'),
|
||||
backgroundColor: Var("accent-11"),
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
+26
-27
@@ -1,24 +1,23 @@
|
||||
import { asset, isExplosive } from '@blitzkit/core';
|
||||
import { literals } from '@blitzkit/i18n';
|
||||
import { Flex, Heading, IconButton, Tooltip } from '@radix-ui/themes';
|
||||
import { useLocale } from '../../../../../../hooks/useLocale';
|
||||
import { useTankModelDefinition } from '../../../../../../hooks/useTankModelDefinition';
|
||||
import { Duel } from '../../../../../../stores/duel';
|
||||
import type { MaybeSkeletonComponentProps } from '../../../../../../types/maybeSkeletonComponentProps';
|
||||
import { AssaultRangesVisualizer } from './AssaultRangesVisualizer';
|
||||
import { GunFlexibilityVisualizer } from './GunFlexibilityVisualizer';
|
||||
import { Info } from './Info';
|
||||
import { InfoWithDelta } from './InfoWithDelta';
|
||||
import { ReloadVisualizer } from './ReloadVisualizer';
|
||||
import { RicochetVisualizer } from './RicochetVisualizer';
|
||||
import { StatsTableWrapper } from './StatsTableWrapper';
|
||||
import type { StatsAcceptorProps } from './TraverseVisualizer';
|
||||
import { asset, isExplosive } from "@blitzkit/core";
|
||||
import { literals } from "@blitzkit/i18n";
|
||||
import { Flex, Heading, IconButton, Tooltip } from "@radix-ui/themes";
|
||||
import { useLocale } from "../../../../../../hooks/useLocale";
|
||||
import { useTankModelDefinition } from "../../../../../../hooks/useTankModelDefinition";
|
||||
import { Duel } from "../../../../../../stores/duel";
|
||||
import type { MaybeSkeletonComponentProps } from "../../../../../../types/maybeSkeletonComponentProps";
|
||||
import { AssaultRangesVisualizer } from "./AssaultRangesVisualizer";
|
||||
import { GunFlexibilityVisualizer } from "./GunFlexibilityVisualizer";
|
||||
import { Info } from "./Info";
|
||||
import { InfoWithDelta } from "./InfoWithDelta";
|
||||
import { ReloadVisualizer } from "./ReloadVisualizer";
|
||||
import { RicochetVisualizer } from "./RicochetVisualizer";
|
||||
import { StatsTableWrapper } from "./StatsTableWrapper";
|
||||
import type { StatsAcceptorProps } from "./TraverseVisualizer";
|
||||
|
||||
export function Firepower({
|
||||
stats,
|
||||
skeleton,
|
||||
}: StatsAcceptorProps & MaybeSkeletonComponentProps) {
|
||||
const mutateDuel = Duel.useMutation();
|
||||
const { strings, unwrap } = useLocale();
|
||||
const turret = Duel.use((state) => state.protagonist.turret);
|
||||
const gun = Duel.use((state) => state.protagonist.gun);
|
||||
@@ -41,8 +40,8 @@ export function Firepower({
|
||||
return (
|
||||
<Tooltip content={unwrap(thisShell.name)} key={thisShell.id}>
|
||||
<IconButton
|
||||
color={selected ? undefined : 'gray'}
|
||||
variant={selected ? 'solid' : 'soft'}
|
||||
color={selected ? undefined : "gray"}
|
||||
variant={selected ? "solid" : "soft"}
|
||||
highContrast={selected}
|
||||
style={{
|
||||
borderTopLeftRadius: shellIndex === 0 ? undefined : 0,
|
||||
@@ -54,7 +53,7 @@ export function Firepower({
|
||||
marginLeft: shellIndex === 0 ? 0 : -1,
|
||||
}}
|
||||
onClick={() => {
|
||||
mutateDuel((draft) => {
|
||||
Duel.mutate((draft) => {
|
||||
draft.protagonist.shell = thisShell;
|
||||
});
|
||||
}}
|
||||
@@ -79,7 +78,7 @@ export function Firepower({
|
||||
</Info>
|
||||
<InfoWithDelta stats={stats} decimals={0} value="dpm" />
|
||||
<InfoWithDelta stats={stats} decimals={0} value="damage" />
|
||||
{gun.gun_type!.$case !== 'regular' && (
|
||||
{gun.gun_type!.$case !== "regular" && (
|
||||
<InfoWithDelta stats={stats} indent decimals={0} value="clipDamage" />
|
||||
)}
|
||||
<InfoWithDelta stats={stats} decimals={0} value="moduleDamage" />
|
||||
@@ -89,13 +88,13 @@ export function Firepower({
|
||||
<AssaultRangesVisualizer stats={stats} ranges={gun.assault_ranges} />
|
||||
)}
|
||||
|
||||
{gun.gun_type!.$case === 'auto_reloader' && (
|
||||
{gun.gun_type!.$case === "auto_reloader" && (
|
||||
<InfoWithDelta stats={stats} decimals={0} indent value="dpmEffective" />
|
||||
)}
|
||||
{gun.gun_type!.$case !== 'regular' && (
|
||||
{gun.gun_type!.$case !== "regular" && (
|
||||
<InfoWithDelta stats={stats} value="shells" />
|
||||
)}
|
||||
{gun.gun_type!.$case === 'auto_reloader' ? (
|
||||
{gun.gun_type!.$case === "auto_reloader" ? (
|
||||
<>
|
||||
<Info
|
||||
indent
|
||||
@@ -120,7 +119,7 @@ export function Firepower({
|
||||
name={literals(
|
||||
strings.website.tools.tankopedia.characteristics.values
|
||||
.shell_index,
|
||||
[`${index + 1}`],
|
||||
[`${index + 1}`]
|
||||
)}
|
||||
decimals={2}
|
||||
deltaType="lowerIsBetter"
|
||||
@@ -137,8 +136,8 @@ export function Firepower({
|
||||
value="shellReload"
|
||||
/>
|
||||
)}
|
||||
{(gun.gun_type!.$case === 'auto_loader' ||
|
||||
gun.gun_type!.$case === 'auto_reloader') && (
|
||||
{(gun.gun_type!.$case === "auto_loader" ||
|
||||
gun.gun_type!.$case === "auto_reloader") && (
|
||||
<InfoWithDelta
|
||||
stats={stats}
|
||||
indent
|
||||
@@ -192,7 +191,7 @@ export function Firepower({
|
||||
name={literals(
|
||||
strings.website.tools.tankopedia.characteristics.values
|
||||
.dispersion_at_distance,
|
||||
['100'],
|
||||
["100"]
|
||||
)}
|
||||
/>
|
||||
<InfoWithDelta
|
||||
|
||||
+43
-48
@@ -1,22 +1,22 @@
|
||||
import { literals } from '@blitzkit/i18n';
|
||||
import { Box, Card, Flex } from '@radix-ui/themes';
|
||||
import { clamp } from 'lodash-es';
|
||||
import { useRef, useState } from 'react';
|
||||
import { degToRad, radToDeg } from 'three/src/math/MathUtils.js';
|
||||
import { awaitableModelDefinitions } from '../../../../../../../core/awaitables/modelDefinitions';
|
||||
import { literals } from "@blitzkit/i18n";
|
||||
import { Box, Card, Flex } from "@radix-ui/themes";
|
||||
import { clamp } from "lodash-es";
|
||||
import { useRef, useState } from "react";
|
||||
import { degToRad, radToDeg } from "three/src/math/MathUtils.js";
|
||||
import { awaitableModelDefinitions } from "../../../../../../../core/awaitables/modelDefinitions";
|
||||
import {
|
||||
DEFAULT_PITCH_TRANSITION,
|
||||
applyPitchYawLimits,
|
||||
} from '../../../../../../../core/blitz/applyPitchYawLimits';
|
||||
import { hasEquipment } from '../../../../../../../core/blitzkit/hasEquipment';
|
||||
import { modelTransformEvent } from '../../../../../../../core/blitzkit/modelTransform';
|
||||
import { Var } from '../../../../../../../core/radix/var';
|
||||
import { useLocale } from '../../../../../../../hooks/useLocale';
|
||||
import { Duel } from '../../../../../../../stores/duel';
|
||||
import type { MaybeSkeletonComponentProps } from '../../../../../../../types/maybeSkeletonComponentProps';
|
||||
import { VisualizerCard } from '../VisualizerCard';
|
||||
import { VisualizerCornerStat } from '../VisualizerCornerStat';
|
||||
import { FlexibilityCanvas } from './components/FlexibilityCanvas';
|
||||
} from "../../../../../../../core/blitz/applyPitchYawLimits";
|
||||
import { hasEquipment } from "../../../../../../../core/blitzkit/hasEquipment";
|
||||
import { modelTransformEvent } from "../../../../../../../core/blitzkit/modelTransform";
|
||||
import { Var } from "../../../../../../../core/radix/var";
|
||||
import { useLocale } from "../../../../../../../hooks/useLocale";
|
||||
import { Duel } from "../../../../../../../stores/duel";
|
||||
import type { MaybeSkeletonComponentProps } from "../../../../../../../types/maybeSkeletonComponentProps";
|
||||
import { VisualizerCard } from "../VisualizerCard";
|
||||
import { VisualizerCornerStat } from "../VisualizerCornerStat";
|
||||
import { FlexibilityCanvas } from "./components/FlexibilityCanvas";
|
||||
|
||||
const ANGLE_COEFFICIENT = 1 / 10;
|
||||
|
||||
@@ -40,7 +40,6 @@ export function GunFlexibilityVisualizer({
|
||||
}: MaybeSkeletonComponentProps) {
|
||||
const { strings } = useLocale();
|
||||
|
||||
const duelStore = Duel.useStore();
|
||||
const tank = Duel.use((state) => state.protagonist.tank);
|
||||
const turret = Duel.use((state) => state.protagonist.turret);
|
||||
const gun = Duel.use((state) => state.protagonist.gun);
|
||||
@@ -52,10 +51,10 @@ export function GunFlexibilityVisualizer({
|
||||
const [yaw, setYaw] = useState(0);
|
||||
const [pitch, setPitch] = useState(0);
|
||||
const [minPitch, setMinPitch] = useState(
|
||||
-degToRad(gunModel.pitch.front?.max ?? gunModel.pitch.max),
|
||||
-degToRad(gunModel.pitch.front?.max ?? gunModel.pitch.max)
|
||||
);
|
||||
const [maxPitch, setMaxPitch] = useState(
|
||||
-degToRad(gunModel.pitch.front?.min ?? gunModel.pitch.min),
|
||||
-degToRad(gunModel.pitch.front?.min ?? gunModel.pitch.min)
|
||||
);
|
||||
|
||||
const container = useRef<HTMLDivElement>(null);
|
||||
@@ -68,7 +67,7 @@ export function GunFlexibilityVisualizer({
|
||||
const b = p.back;
|
||||
const t = gunModel.pitch.transition ?? DEFAULT_PITCH_TRANSITION;
|
||||
|
||||
let d = '';
|
||||
let d = "";
|
||||
|
||||
// right
|
||||
const mo = mag(p.max);
|
||||
@@ -169,10 +168,10 @@ export function GunFlexibilityVisualizer({
|
||||
return (
|
||||
<Flex direction="column-reverse" gap="0">
|
||||
<Box mt="-3" px="2">
|
||||
<Card variant="classic" style={{ height: '10rem' }}>
|
||||
<Card variant="classic" style={{ height: "10rem" }}>
|
||||
<Box
|
||||
style={{
|
||||
background: 'linear-gradient(var(--gray-1), var(--gray-3))',
|
||||
background: "linear-gradient(var(--gray-1), var(--gray-3))",
|
||||
}}
|
||||
position="absolute"
|
||||
width="100%"
|
||||
@@ -195,8 +194,8 @@ export function GunFlexibilityVisualizer({
|
||||
transparent,
|
||||
transparent ${Math.PI / 2 - maxPitch}rad,
|
||||
|
||||
${Var('cyan-a4')} ${Math.PI / 2 - maxPitch}rad,
|
||||
${Var('jade-a4')} ${Math.PI / 2 - minPitch}rad,
|
||||
${Var("cyan-a4")} ${Math.PI / 2 - maxPitch}rad,
|
||||
${Var("jade-a4")} ${Math.PI / 2 - minPitch}rad,
|
||||
|
||||
transparent ${Math.PI / 2 - minPitch}rad,
|
||||
transparent 180deg
|
||||
@@ -209,7 +208,7 @@ export function GunFlexibilityVisualizer({
|
||||
|
||||
<VisualizerCard
|
||||
ref={container}
|
||||
style={{ touchAction: 'none' }}
|
||||
style={{ touchAction: "none" }}
|
||||
onPointerMove={(event) => {
|
||||
event.preventDefault();
|
||||
|
||||
@@ -221,19 +220,15 @@ export function GunFlexibilityVisualizer({
|
||||
const v = event.clientY - rect.top - rect.height / 2;
|
||||
let yaw = Math.atan2(u, -v);
|
||||
|
||||
const {
|
||||
equipmentMatrix,
|
||||
tank: { equipment_preset },
|
||||
} = duelStore.getState().protagonist;
|
||||
const hasImprovedVerticalStabilizer = hasEquipment(
|
||||
122,
|
||||
equipment_preset,
|
||||
equipmentMatrix,
|
||||
Duel.state.protagonist.tank.equipment_preset,
|
||||
Duel.state.protagonist.equipmentMatrix
|
||||
);
|
||||
const hasDownImprovedVerticalStabilizer = hasEquipment(
|
||||
124,
|
||||
equipment_preset,
|
||||
equipmentMatrix,
|
||||
Duel.state.protagonist.tank.equipment_preset,
|
||||
Duel.state.protagonist.equipmentMatrix
|
||||
);
|
||||
|
||||
const min = applyPitchYawLimits(
|
||||
@@ -242,7 +237,7 @@ export function GunFlexibilityVisualizer({
|
||||
p,
|
||||
y,
|
||||
hasImprovedVerticalStabilizer,
|
||||
hasDownImprovedVerticalStabilizer,
|
||||
hasDownImprovedVerticalStabilizer
|
||||
);
|
||||
const max = applyPitchYawLimits(
|
||||
Math.PI,
|
||||
@@ -250,7 +245,7 @@ export function GunFlexibilityVisualizer({
|
||||
p,
|
||||
y,
|
||||
hasImprovedVerticalStabilizer,
|
||||
hasDownImprovedVerticalStabilizer,
|
||||
hasDownImprovedVerticalStabilizer
|
||||
);
|
||||
|
||||
yaw = min[1];
|
||||
@@ -276,8 +271,8 @@ export function GunFlexibilityVisualizer({
|
||||
width="100%"
|
||||
height="100%"
|
||||
style={{
|
||||
borderRadius: Var('radius-2'),
|
||||
overflow: 'hidden',
|
||||
borderRadius: Var("radius-2"),
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
@@ -286,18 +281,18 @@ export function GunFlexibilityVisualizer({
|
||||
style={{
|
||||
opacity: 2 ** -3,
|
||||
backgroundImage: `url(/assets/images/tankopedia/visualizers/ricochet/armor-hash.png)`,
|
||||
boxShadow: 'inset 0 0 4rem 1rem black',
|
||||
boxShadow: "inset 0 0 4rem 1rem black",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<svg
|
||||
style={{
|
||||
position: 'absolute',
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: 'calc(100%)',
|
||||
height: 'calc(100%)',
|
||||
width: "calc(100%)",
|
||||
height: "calc(100%)",
|
||||
}}
|
||||
viewBox="-1 -1 2 2"
|
||||
>
|
||||
@@ -340,11 +335,11 @@ export function GunFlexibilityVisualizer({
|
||||
width="1pt"
|
||||
height="14rem"
|
||||
style={{
|
||||
borderRadius: Var('radius-1'),
|
||||
background: `linear-gradient(${Var('gray-a11')}, ${Var('gray-a6')})`,
|
||||
borderRadius: Var("radius-1"),
|
||||
background: `linear-gradient(${Var("gray-a11")}, ${Var("gray-a6")})`,
|
||||
boxShadow:
|
||||
'var(--white-a12) -0.25rem 0 1rem, var(--black-a12) 0.25rem 0 1rem',
|
||||
transform: 'translateX(-50%)',
|
||||
"var(--white-a12) -0.25rem 0 1rem, var(--black-a12) 0.25rem 0 1rem",
|
||||
transform: "translateX(-50%)",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
@@ -356,9 +351,9 @@ export function GunFlexibilityVisualizer({
|
||||
width="0.25rem"
|
||||
height="0.25rem"
|
||||
style={{
|
||||
borderRadius: '50%',
|
||||
backgroundColor: Var('gray-12'),
|
||||
transform: 'translate(-50%, 50%)',
|
||||
borderRadius: "50%",
|
||||
backgroundColor: Var("gray-12"),
|
||||
transform: "translate(-50%, 50%)",
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
+11
-11
@@ -1,12 +1,12 @@
|
||||
import { asset } from '@blitzkit/core';
|
||||
import { Flex, IconButton, Tooltip } from '@radix-ui/themes';
|
||||
import { useLocale } from '../../../../../../../../hooks/useLocale';
|
||||
import { Duel } from '../../../../../../../../stores/duel';
|
||||
import { asset } from "@blitzkit/core";
|
||||
import { Flex, IconButton, Tooltip } from "@radix-ui/themes";
|
||||
import { useLocale } from "../../../../../../../../hooks/useLocale";
|
||||
import { Duel } from "../../../../../../../../stores/duel";
|
||||
|
||||
export function ShellSwitcher() {
|
||||
const gun = Duel.use((state) => state.protagonist.gun);
|
||||
const shell = Duel.use((state) => state.protagonist.shell);
|
||||
const mutateDuel = Duel.useMutation();
|
||||
|
||||
const { unwrap } = useLocale();
|
||||
|
||||
return (
|
||||
@@ -18,26 +18,26 @@ export function ShellSwitcher() {
|
||||
<Tooltip content={unwrap(thisShell.name)} key={thisShell.id}>
|
||||
<IconButton
|
||||
size="3"
|
||||
color={selected ? undefined : 'gray'}
|
||||
variant={selected ? 'solid' : 'soft'}
|
||||
color={selected ? undefined : "gray"}
|
||||
variant={selected ? "solid" : "soft"}
|
||||
highContrast={selected}
|
||||
style={{
|
||||
borderTopLeftRadius: 0,
|
||||
borderBottomLeftRadius: shellIndex === 0 ? '50%' : 0,
|
||||
borderBottomLeftRadius: shellIndex === 0 ? "50%" : 0,
|
||||
borderTopRightRadius: 0,
|
||||
borderBottomRightRadius:
|
||||
shellIndex === gun.shells.length - 1 ? '50%' : 0,
|
||||
shellIndex === gun.shells.length - 1 ? "50%" : 0,
|
||||
marginLeft: shellIndex === 0 ? 0 : -1,
|
||||
}}
|
||||
onClick={() => {
|
||||
mutateDuel((draft) => {
|
||||
Duel.mutate((draft) => {
|
||||
draft.protagonist.shell = thisShell;
|
||||
});
|
||||
}}
|
||||
>
|
||||
<img
|
||||
alt={unwrap(thisShell.name)}
|
||||
style={{ width: '1.125rem', height: '1.125rem' }}
|
||||
style={{ width: "1.125rem", height: "1.125rem" }}
|
||||
src={asset(`icons/shells/${thisShell.icon}.webp`)}
|
||||
/>
|
||||
</IconButton>
|
||||
|
||||
+16
-17
@@ -1,5 +1,5 @@
|
||||
import { checkConsumableProvisionInclusivity } from '@blitzkit/core/src/blitzkit/checkConsumableProvisionInclusivity';
|
||||
import { InfoCircledIcon } from '@radix-ui/react-icons';
|
||||
import { checkConsumableProvisionInclusivity } from "@blitzkit/core/src/blitzkit/checkConsumableProvisionInclusivity";
|
||||
import { InfoCircledIcon } from "@radix-ui/react-icons";
|
||||
import {
|
||||
Button,
|
||||
Flex,
|
||||
@@ -7,31 +7,30 @@ import {
|
||||
IconButton,
|
||||
Popover,
|
||||
Text,
|
||||
} from '@radix-ui/themes';
|
||||
import { awaitableConsumableDefinitions } from '../../../../core/awaitables/consumableDefinitions';
|
||||
import { useEquipment } from '../../../../hooks/useEquipment';
|
||||
import { useLocale } from '../../../../hooks/useLocale';
|
||||
import { Duel } from '../../../../stores/duel';
|
||||
import { ConsumablesManager } from '../../../ConsumablesManager';
|
||||
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
|
||||
} from "@radix-ui/themes";
|
||||
import { awaitableConsumableDefinitions } from "../../../../core/awaitables/consumableDefinitions";
|
||||
import { useEquipment } from "../../../../hooks/useEquipment";
|
||||
import { useLocale } from "../../../../hooks/useLocale";
|
||||
import { Duel } from "../../../../stores/duel";
|
||||
import { ConsumablesManager } from "../../../ConsumablesManager";
|
||||
import { ConfigurationChildWrapper } from "./ConfigurationChildWrapper";
|
||||
|
||||
const consumableDefinitions = await awaitableConsumableDefinitions;
|
||||
|
||||
export function Consumables() {
|
||||
const mutateDuel = Duel.useMutation();
|
||||
const protagonist = Duel.use((state) => state.protagonist);
|
||||
const consumables = Duel.use((state) => state.protagonist.consumables);
|
||||
const consumablesList = Object.values(
|
||||
consumableDefinitions.consumables,
|
||||
consumableDefinitions.consumables
|
||||
).filter((consumable) =>
|
||||
checkConsumableProvisionInclusivity(
|
||||
consumable,
|
||||
protagonist.tank,
|
||||
protagonist.gun,
|
||||
),
|
||||
protagonist.gun
|
||||
)
|
||||
);
|
||||
const cooldownBooster = Duel.use(
|
||||
(state) => state.protagonist.cooldownBooster,
|
||||
(state) => state.protagonist.cooldownBooster
|
||||
);
|
||||
const hasConsumableDeliverySystem = useEquipment(118);
|
||||
const hasHighEndConsumables = useEquipment(101);
|
||||
@@ -67,7 +66,7 @@ export function Consumables() {
|
||||
variant="ghost"
|
||||
color="red"
|
||||
onClick={() => {
|
||||
mutateDuel((draft) => {
|
||||
Duel.mutate((draft) => {
|
||||
draft.protagonist.consumables = [];
|
||||
});
|
||||
}}
|
||||
@@ -85,11 +84,11 @@ export function Consumables() {
|
||||
hasHighEndConsumables={hasHighEndConsumables}
|
||||
timers
|
||||
onConsumablesChange={(consumables) => {
|
||||
mutateDuel((draft) => {
|
||||
Duel.mutate((draft) => {
|
||||
draft.protagonist.consumables = consumables;
|
||||
});
|
||||
}}
|
||||
justify={{ initial: 'center', sm: 'start' }}
|
||||
justify={{ initial: "center", sm: "start" }}
|
||||
/>
|
||||
</ConfigurationChildWrapper>
|
||||
);
|
||||
|
||||
+9
-10
@@ -1,19 +1,18 @@
|
||||
import { Button, Flex, Heading } from '@radix-ui/themes';
|
||||
import { awaitableEquipmentDefinitions } from '../../../../core/awaitables/equipmentDefinitions';
|
||||
import { useLocale } from '../../../../hooks/useLocale';
|
||||
import { Duel } from '../../../../stores/duel';
|
||||
import { EquipmentManager } from '../../../EquipmentManager';
|
||||
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
|
||||
import { Button, Flex, Heading } from "@radix-ui/themes";
|
||||
import { awaitableEquipmentDefinitions } from "../../../../core/awaitables/equipmentDefinitions";
|
||||
import { useLocale } from "../../../../hooks/useLocale";
|
||||
import { Duel } from "../../../../stores/duel";
|
||||
import { EquipmentManager } from "../../../EquipmentManager";
|
||||
import { ConfigurationChildWrapper } from "./ConfigurationChildWrapper";
|
||||
|
||||
const equipmentDefinitions = await awaitableEquipmentDefinitions;
|
||||
|
||||
export function Equipment() {
|
||||
const mutateDuel = Duel.useMutation();
|
||||
const protagonist = Duel.use((state) => state.protagonist);
|
||||
const equipmentPreset =
|
||||
equipmentDefinitions.presets[protagonist.tank.equipment_preset];
|
||||
const equipmentMatrix = Duel.use(
|
||||
(state) => state.protagonist.equipmentMatrix,
|
||||
(state) => state.protagonist.equipmentMatrix
|
||||
);
|
||||
const { strings } = useLocale();
|
||||
|
||||
@@ -27,7 +26,7 @@ export function Equipment() {
|
||||
variant="ghost"
|
||||
color="red"
|
||||
onClick={() => {
|
||||
mutateDuel((draft) => {
|
||||
Duel.mutate((draft) => {
|
||||
draft.protagonist.equipmentMatrix.forEach((row) => {
|
||||
row.forEach((_, index) => {
|
||||
row[index] = 0;
|
||||
@@ -44,7 +43,7 @@ export function Equipment() {
|
||||
matrix={equipmentMatrix}
|
||||
preset={equipmentPreset}
|
||||
onChange={(matrix) => {
|
||||
mutateDuel((draft) => {
|
||||
Duel.mutate((draft) => {
|
||||
draft.protagonist.equipmentMatrix = matrix;
|
||||
});
|
||||
}}
|
||||
|
||||
+18
-19
@@ -1,15 +1,14 @@
|
||||
import { asset } from '@blitzkit/core';
|
||||
import { Flex, Heading } from '@radix-ui/themes';
|
||||
import { useLocale } from '../../../../hooks/useLocale';
|
||||
import { Duel } from '../../../../stores/duel';
|
||||
import { GenericTankComponentButton } from '../../../ModuleButtons/GenericTankComponentButton';
|
||||
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
|
||||
import { asset } from "@blitzkit/core";
|
||||
import { Flex, Heading } from "@radix-ui/themes";
|
||||
import { useLocale } from "../../../../hooks/useLocale";
|
||||
import { Duel } from "../../../../stores/duel";
|
||||
import { GenericTankComponentButton } from "../../../ModuleButtons/GenericTankComponentButton";
|
||||
import { ConfigurationChildWrapper } from "./ConfigurationChildWrapper";
|
||||
|
||||
export function Miscellaneous() {
|
||||
const mutateDuel = Duel.useMutation();
|
||||
const camouflage = Duel.use((state) => state.protagonist.camouflage);
|
||||
const cooldownBooster = Duel.use(
|
||||
(state) => state.protagonist.cooldownBooster,
|
||||
(state) => state.protagonist.cooldownBooster
|
||||
);
|
||||
const { tank } = Duel.use((state) => state.protagonist);
|
||||
const { strings } = useLocale();
|
||||
@@ -23,37 +22,37 @@ export function Miscellaneous() {
|
||||
<Flex gap="2" align="center">
|
||||
{!tank.fixed_camouflage && (
|
||||
<GenericTankComponentButton
|
||||
icon={asset('icons/camo.webp')}
|
||||
icon={asset("icons/camo.webp")}
|
||||
selected={camouflage}
|
||||
iconStyles={{
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(calc(-50% + 4px), calc(-50% + 4px))',
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
transform: "translate(calc(-50% + 4px), calc(-50% + 4px))",
|
||||
}}
|
||||
onClick={() => {
|
||||
mutateDuel((draft) => {
|
||||
Duel.mutate((draft) => {
|
||||
draft.protagonist.camouflage = !camouflage;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<GenericTankComponentButton
|
||||
icon={asset('icons/boosters/equipment.webp')}
|
||||
icon={asset("icons/boosters/equipment.webp")}
|
||||
selected={cooldownBooster > 0}
|
||||
banner={
|
||||
cooldownBooster === 0
|
||||
? undefined
|
||||
: cooldownBooster === 1
|
||||
? '#afb0abc0'
|
||||
? "#afb0abc0"
|
||||
: cooldownBooster === 2
|
||||
? '#5f72cbc0'
|
||||
: '#9b3cc0c0'
|
||||
? "#5f72cbc0"
|
||||
: "#9b3cc0c0"
|
||||
}
|
||||
iconStyles={{
|
||||
transform: 'translate(-50%, -50%) scale(0.8)',
|
||||
transform: "translate(-50%, -50%) scale(0.8)",
|
||||
}}
|
||||
onClick={() => {
|
||||
mutateDuel((draft) => {
|
||||
Duel.mutate((draft) => {
|
||||
draft.protagonist.cooldownBooster++;
|
||||
if (draft.protagonist.cooldownBooster === 4)
|
||||
draft.protagonist.cooldownBooster = 0;
|
||||
|
||||
+44
-45
@@ -10,7 +10,7 @@ import {
|
||||
TurretDefinition,
|
||||
type TankDefinition,
|
||||
type Unlock,
|
||||
} from '@blitzkit/core';
|
||||
} from "@blitzkit/core";
|
||||
import {
|
||||
Button,
|
||||
ChevronDownIcon,
|
||||
@@ -18,12 +18,12 @@ import {
|
||||
Heading,
|
||||
IconButton,
|
||||
Text,
|
||||
} from '@radix-ui/themes';
|
||||
import { awaitableTankDefinitions } from '../../../../core/awaitables/tankDefinitions';
|
||||
import { useLocale } from '../../../../hooks/useLocale';
|
||||
import { Duel } from '../../../../stores/duel';
|
||||
import { LinkI18n } from '../../../LinkI18n';
|
||||
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
|
||||
} from "@radix-ui/themes";
|
||||
import { awaitableTankDefinitions } from "../../../../core/awaitables/tankDefinitions";
|
||||
import { useLocale } from "../../../../hooks/useLocale";
|
||||
import { Duel } from "../../../../stores/duel";
|
||||
import { LinkI18n } from "../../../LinkI18n";
|
||||
import { ConfigurationChildWrapper } from "./ConfigurationChildWrapper";
|
||||
|
||||
type ModuleDefinition =
|
||||
| TurretDefinition
|
||||
@@ -32,7 +32,7 @@ type ModuleDefinition =
|
||||
| EngineDefinition;
|
||||
|
||||
function extractModule(raw: ModuleDefinition | TankDefinition) {
|
||||
if ('gun_type' in raw) {
|
||||
if ("gun_type" in raw) {
|
||||
return raw;
|
||||
} else
|
||||
return raw as Exclude<ModuleDefinition | TankDefinition, GunDefinition>;
|
||||
@@ -43,11 +43,11 @@ interface ModuleWithUnlocks {
|
||||
}
|
||||
|
||||
const moduleTypeTypeIconName: Record<ModuleType, string> = {
|
||||
[ModuleType.VEHICLE]: '',
|
||||
[ModuleType.ENGINE]: 'engine',
|
||||
[ModuleType.TRACKS]: 'chassis',
|
||||
[ModuleType.TURRET]: 'turret',
|
||||
[ModuleType.GUN]: 'gun',
|
||||
[ModuleType.VEHICLE]: "",
|
||||
[ModuleType.ENGINE]: "engine",
|
||||
[ModuleType.TRACKS]: "chassis",
|
||||
[ModuleType.TURRET]: "turret",
|
||||
[ModuleType.GUN]: "gun",
|
||||
};
|
||||
|
||||
function ModuleButton({
|
||||
@@ -70,11 +70,11 @@ function ModuleButton({
|
||||
<IconButton
|
||||
size="4"
|
||||
radius="small"
|
||||
variant={selected ? 'surface' : 'soft'}
|
||||
color={top ? 'amber' : selected ? undefined : 'gray'}
|
||||
variant={selected ? "surface" : "soft"}
|
||||
color={top ? "amber" : selected ? undefined : "gray"}
|
||||
onClick={onClick}
|
||||
style={{
|
||||
position: 'relative',
|
||||
position: "relative",
|
||||
width: isTank ? 72 : undefined,
|
||||
height: isTank ? 72 : undefined,
|
||||
}}
|
||||
@@ -83,7 +83,7 @@ function ModuleButton({
|
||||
size="1"
|
||||
color="gray"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
position: "absolute",
|
||||
left: isTank ? 8 : 4,
|
||||
top: isTank ? 8 : 4,
|
||||
}}
|
||||
@@ -94,13 +94,13 @@ function ModuleButton({
|
||||
{unlock.cost.value > 0 && (
|
||||
<Flex
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: '50%',
|
||||
position: "absolute",
|
||||
left: "50%",
|
||||
bottom: 4,
|
||||
transform: 'translateX(-50%)',
|
||||
transform: "translateX(-50%)",
|
||||
}}
|
||||
align="center"
|
||||
gap={isTank ? '1' : undefined}
|
||||
gap={isTank ? "1" : undefined}
|
||||
>
|
||||
<Text size="1" color="gray">
|
||||
{formatCompact(locale, unlock.cost.value)}
|
||||
@@ -112,7 +112,7 @@ function ModuleButton({
|
||||
style={{
|
||||
width: 12,
|
||||
height: 12,
|
||||
objectFit: 'contain',
|
||||
objectFit: "contain",
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
@@ -128,7 +128,7 @@ function ModuleButton({
|
||||
style={{
|
||||
width: isTank ? 64 : 32,
|
||||
height: isTank ? 64 : 32,
|
||||
objectFit: 'contain',
|
||||
objectFit: "contain",
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
@@ -149,7 +149,6 @@ function ModuleButton({
|
||||
const tankDefinitions = await awaitableTankDefinitions;
|
||||
|
||||
export function Modules() {
|
||||
const mutateDuel = Duel.useMutation();
|
||||
const tank = Duel.use((state) => state.protagonist.tank);
|
||||
const hasUpgrades =
|
||||
tank.turrets.length > 1 ||
|
||||
@@ -171,15 +170,15 @@ export function Modules() {
|
||||
const { strings } = useLocale();
|
||||
|
||||
function setByUnlock(unlock: Unlock) {
|
||||
mutateDuel((draft) => {
|
||||
Duel.mutate((draft) => {
|
||||
if (unlock.type === ModuleType.TURRET) {
|
||||
draft.protagonist.turret = draft.protagonist.tank.turrets.find(
|
||||
(turret) => turret.id === unlock.id,
|
||||
(turret) => turret.id === unlock.id
|
||||
)!;
|
||||
|
||||
if (
|
||||
!draft.protagonist.turret.guns.some(
|
||||
(gun) => gun.id === draft.protagonist.gun.id,
|
||||
(gun) => gun.id === draft.protagonist.gun.id
|
||||
)
|
||||
) {
|
||||
draft.protagonist.gun = draft.protagonist.turret.guns.at(-1)!;
|
||||
@@ -187,7 +186,7 @@ export function Modules() {
|
||||
}
|
||||
} else if (unlock.type === ModuleType.GUN) {
|
||||
const gunInTurret = draft.protagonist.turret.guns.find(
|
||||
(gun) => gun.id === unlock.id,
|
||||
(gun) => gun.id === unlock.id
|
||||
);
|
||||
if (gunInTurret) {
|
||||
draft.protagonist.gun = gunInTurret;
|
||||
@@ -195,10 +194,10 @@ export function Modules() {
|
||||
} else {
|
||||
// TODO: warn somehow?
|
||||
const suitableTurret = draft.protagonist.tank.turrets.find((turret) =>
|
||||
turret.guns.some((gun) => gun.id === unlock.id),
|
||||
turret.guns.some((gun) => gun.id === unlock.id)
|
||||
)!;
|
||||
const gunInSuitableTurret = suitableTurret.guns.find(
|
||||
(gun) => gun.id === unlock.id,
|
||||
(gun) => gun.id === unlock.id
|
||||
)!;
|
||||
|
||||
draft.protagonist.turret = suitableTurret;
|
||||
@@ -207,11 +206,11 @@ export function Modules() {
|
||||
}
|
||||
} else if (unlock.type === ModuleType.ENGINE) {
|
||||
draft.protagonist.engine = draft.protagonist.tank.engines.find(
|
||||
(engine) => engine.id === unlock.id,
|
||||
(engine) => engine.id === unlock.id
|
||||
)!;
|
||||
} else if (unlock.type === ModuleType.TRACKS) {
|
||||
draft.protagonist.track = draft.protagonist.tank.tracks.find(
|
||||
(track) => track.id === unlock.id,
|
||||
(track) => track.id === unlock.id
|
||||
)!;
|
||||
}
|
||||
});
|
||||
@@ -235,7 +234,7 @@ export function Modules() {
|
||||
} else if (unlock.type === ModuleType.GUN) {
|
||||
module = tank.turrets
|
||||
.find((turret) =>
|
||||
turret.guns.some((gun) => gun.id === unlock.id),
|
||||
turret.guns.some((gun) => gun.id === unlock.id)
|
||||
)!
|
||||
.guns.find((gun) => gun.id === unlock.id)!;
|
||||
} else if (unlock.type === ModuleType.VEHICLE) {
|
||||
@@ -257,12 +256,12 @@ export function Modules() {
|
||||
{unlocks.length > 1 && (
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: 'currentcolor',
|
||||
backgroundColor: "currentcolor",
|
||||
width: `calc(${central ? 100 : 50}% + ${
|
||||
central ? 8 : 4
|
||||
}px)`,
|
||||
height: 1,
|
||||
position: 'absolute',
|
||||
position: "absolute",
|
||||
right: first ? 0 : undefined,
|
||||
left: last ? 0 : undefined,
|
||||
transform: `translateX(${first ? 4 : last ? -4 : 0}px)`,
|
||||
@@ -279,14 +278,14 @@ export function Modules() {
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: 'currentcolor',
|
||||
backgroundColor: "currentcolor",
|
||||
width: 1,
|
||||
height: 8,
|
||||
}}
|
||||
/>
|
||||
<ChevronDownIcon
|
||||
style={{
|
||||
transform: 'translateY(-7px)',
|
||||
transform: "translateY(-7px)",
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
@@ -326,7 +325,7 @@ export function Modules() {
|
||||
1 && (
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: 'currentcolor',
|
||||
backgroundColor: "currentcolor",
|
||||
width: 1,
|
||||
height: 8,
|
||||
marginBottom: -8,
|
||||
@@ -337,7 +336,7 @@ export function Modules() {
|
||||
{(extractModule(module) as ModuleWithUnlocks).unlocks &&
|
||||
tree(
|
||||
type,
|
||||
(extractModule(module) as ModuleWithUnlocks).unlocks!,
|
||||
(extractModule(module) as ModuleWithUnlocks).unlocks!
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
@@ -359,7 +358,7 @@ export function Modules() {
|
||||
variant="ghost"
|
||||
color="red"
|
||||
onClick={() => {
|
||||
mutateDuel((draft) => {
|
||||
Duel.mutate((draft) => {
|
||||
draft.protagonist.turret = draft.protagonist.tank.turrets[0];
|
||||
draft.protagonist.gun = draft.protagonist.turret.guns[0];
|
||||
draft.protagonist.shell = draft.protagonist.gun.shells[0];
|
||||
@@ -373,7 +372,7 @@ export function Modules() {
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
mutateDuel((draft) => {
|
||||
Duel.mutate((draft) => {
|
||||
draft.protagonist.turret =
|
||||
draft.protagonist.tank.turrets.at(-1)!;
|
||||
draft.protagonist.gun = draft.protagonist.turret.guns.at(-1)!;
|
||||
@@ -394,28 +393,28 @@ export function Modules() {
|
||||
<Flex gap="2" wrap="wrap" gapY="6">
|
||||
{tree(ModuleType.ENGINE, [
|
||||
{
|
||||
cost: { type: 'xp', value: 0 },
|
||||
cost: { type: "xp", value: 0 },
|
||||
id: engine0.id,
|
||||
type: ModuleType.ENGINE,
|
||||
},
|
||||
])}
|
||||
{tree(ModuleType.TRACKS, [
|
||||
{
|
||||
cost: { type: 'xp', value: 0 },
|
||||
cost: { type: "xp", value: 0 },
|
||||
id: track0.id,
|
||||
type: ModuleType.TRACKS,
|
||||
},
|
||||
])}
|
||||
{tree(ModuleType.TURRET, [
|
||||
{
|
||||
cost: { type: 'xp', value: 0 },
|
||||
cost: { type: "xp", value: 0 },
|
||||
id: turret0.id,
|
||||
type: ModuleType.TURRET,
|
||||
},
|
||||
])}
|
||||
{tree(ModuleType.GUN, [
|
||||
{
|
||||
cost: { type: 'xp', value: 0 },
|
||||
cost: { type: "xp", value: 0 },
|
||||
id: gun0.id,
|
||||
type: ModuleType.GUN,
|
||||
},
|
||||
|
||||
+9
-10
@@ -1,15 +1,14 @@
|
||||
import { availableProvisions } from '@blitzkit/core';
|
||||
import { Button, Flex, Heading } from '@radix-ui/themes';
|
||||
import { awaitableProvisionDefinitions } from '../../../../core/awaitables/provisionDefinitions';
|
||||
import { useLocale } from '../../../../hooks/useLocale';
|
||||
import { Duel } from '../../../../stores/duel';
|
||||
import { ProvisionsManager } from '../../../ProvisionsManager';
|
||||
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
|
||||
import { availableProvisions } from "@blitzkit/core";
|
||||
import { Button, Flex, Heading } from "@radix-ui/themes";
|
||||
import { awaitableProvisionDefinitions } from "../../../../core/awaitables/provisionDefinitions";
|
||||
import { useLocale } from "../../../../hooks/useLocale";
|
||||
import { Duel } from "../../../../stores/duel";
|
||||
import { ProvisionsManager } from "../../../ProvisionsManager";
|
||||
import { ConfigurationChildWrapper } from "./ConfigurationChildWrapper";
|
||||
|
||||
const provisionDefinitions = await awaitableProvisionDefinitions;
|
||||
|
||||
export function Provisions() {
|
||||
const mutateDuel = Duel.useMutation();
|
||||
const { tank, gun } = Duel.use((state) => state.protagonist);
|
||||
const provisions = Duel.use((state) => state.protagonist.provisions);
|
||||
const provisionsList = availableProvisions(tank, gun, provisionDefinitions);
|
||||
@@ -25,7 +24,7 @@ export function Provisions() {
|
||||
variant="ghost"
|
||||
color="red"
|
||||
onClick={() => {
|
||||
mutateDuel((draft) => {
|
||||
Duel.mutate((draft) => {
|
||||
draft.protagonist.provisions = [];
|
||||
});
|
||||
}}
|
||||
@@ -39,7 +38,7 @@ export function Provisions() {
|
||||
selected={provisions}
|
||||
disabled={tank.max_provisions === provisions.length}
|
||||
onChange={(provisions) => {
|
||||
mutateDuel((draft) => {
|
||||
Duel.mutate((draft) => {
|
||||
draft.protagonist.provisions = provisions;
|
||||
});
|
||||
}}
|
||||
|
||||
+1
-2
@@ -6,7 +6,6 @@ import { Tankopedia } from "../../../../../../stores/tankopedia";
|
||||
|
||||
export function DynamicArmorSwitcher() {
|
||||
const tank = Duel.use((state) => state.protagonist.tank);
|
||||
const mutateDuel = Duel.useMutation();
|
||||
const isDynamicArmorActive = Duel.use((state) =>
|
||||
state.protagonist.consumables.includes(73)
|
||||
);
|
||||
@@ -20,7 +19,7 @@ export function DynamicArmorSwitcher() {
|
||||
variant="soft"
|
||||
size={{ initial: "2", sm: "3" }}
|
||||
onClick={() => {
|
||||
mutateDuel((draft) => {
|
||||
Duel.mutate((draft) => {
|
||||
if (draft.protagonist.consumables.includes(73)) {
|
||||
draft.protagonist.consumables =
|
||||
draft.protagonist.consumables.filter((id) => id !== 73);
|
||||
|
||||
+3
-4
@@ -25,7 +25,6 @@ export function Thicknesses({ thicknessRange, skeleton }: ThicknessesProps) {
|
||||
const showPrimaryArmor = TankopediaPersistent.use(
|
||||
(state) => state.showPrimaryArmor
|
||||
);
|
||||
const mutateTankopediaPersistent = TankopediaPersistent.useMutation();
|
||||
const editStatic = Tankopedia.use((state) => state.editStatic);
|
||||
const { strings } = useLocale();
|
||||
|
||||
@@ -50,7 +49,7 @@ export function Thicknesses({ thicknessRange, skeleton }: ThicknessesProps) {
|
||||
gap="2"
|
||||
style={{ opacity: showPrimaryArmor ? 1 : 0.5, cursor: "pointer" }}
|
||||
onClick={() => {
|
||||
mutateTankopediaPersistent((draft) => {
|
||||
TankopediaPersistent.mutate((draft) => {
|
||||
draft.showPrimaryArmor = !draft.showPrimaryArmor;
|
||||
});
|
||||
Tankopedia.mutate((draft) => {
|
||||
@@ -91,7 +90,7 @@ export function Thicknesses({ thicknessRange, skeleton }: ThicknessesProps) {
|
||||
gap="2"
|
||||
style={{ opacity: showSpacedArmor ? 1 : 0.5, cursor: "pointer" }}
|
||||
onClick={() => {
|
||||
mutateTankopediaPersistent((draft) => {
|
||||
TankopediaPersistent.mutate((draft) => {
|
||||
draft.showSpacedArmor = !draft.showSpacedArmor;
|
||||
});
|
||||
Tankopedia.mutate((draft) => {
|
||||
@@ -130,7 +129,7 @@ export function Thicknesses({ thicknessRange, skeleton }: ThicknessesProps) {
|
||||
gap="2"
|
||||
style={{ opacity: showExternalModules ? 1 : 0.5, cursor: "pointer" }}
|
||||
onClick={() => {
|
||||
mutateTankopediaPersistent((draft) => {
|
||||
TankopediaPersistent.mutate((draft) => {
|
||||
draft.showExternalModules = !draft.showExternalModules;
|
||||
});
|
||||
Tankopedia.mutate((draft) => {
|
||||
|
||||
@@ -69,10 +69,8 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
|
||||
const antagonistTank = Duel.use((state) => state.antagonist.tank);
|
||||
const hasCalibratedShells = useEquipment(103, true);
|
||||
const [tab, setTab] = useState("search");
|
||||
const mutateDuel = Duel.useMutation();
|
||||
const hasEnhancedArmor = useEquipment(110);
|
||||
const antagonistUniqueGuns = uniqueGuns(antagonistTank.turrets);
|
||||
const mutateTankopediaPersistent = TankopediaPersistent.useMutation();
|
||||
const { strings, unwrap } = useLocale();
|
||||
const revealed = Tankopedia.use((state) => state.revealed);
|
||||
const disturbed = Tankopedia.use((state) => state.disturbed);
|
||||
@@ -151,7 +149,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
|
||||
module="gun"
|
||||
key={gun.id}
|
||||
onClick={() =>
|
||||
mutateDuel((draft) => {
|
||||
Duel.mutate((draft) => {
|
||||
draft.antagonist.turret = turret;
|
||||
draft.antagonist.gun = gun;
|
||||
draft.antagonist.shell = gun.shells[0];
|
||||
@@ -192,7 +190,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
|
||||
size={{ initial: "2", sm: "3" }}
|
||||
radius="none"
|
||||
onClick={() => {
|
||||
mutateDuel((draft) => {
|
||||
Duel.mutate((draft) => {
|
||||
draft.antagonist.shell = thisShell;
|
||||
});
|
||||
Tankopedia.mutate((draft) => {
|
||||
@@ -229,7 +227,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
|
||||
size={{ initial: "2", sm: "3" }}
|
||||
radius="none"
|
||||
onClick={() => {
|
||||
mutateDuel((draft) => {
|
||||
Duel.mutate((draft) => {
|
||||
draft.antagonist.equipmentMatrix[0][0] = hasCalibratedShells
|
||||
? 0
|
||||
: 1;
|
||||
@@ -254,7 +252,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
|
||||
size={{ initial: "2", sm: "3" }}
|
||||
radius="none"
|
||||
onClick={() => {
|
||||
mutateDuel((draft) => {
|
||||
Duel.mutate((draft) => {
|
||||
draft.protagonist.equipmentMatrix[1][1] = hasEnhancedArmor
|
||||
? 0
|
||||
: -1;
|
||||
@@ -303,7 +301,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
|
||||
mb="1"
|
||||
style={{ cursor: "pointer" }}
|
||||
onClick={() => {
|
||||
mutateTankopediaPersistent((draft) => {
|
||||
TankopediaPersistent.mutate((draft) => {
|
||||
draft.advancedHighlighting = !draft.advancedHighlighting;
|
||||
});
|
||||
}}
|
||||
@@ -343,7 +341,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
|
||||
<TankSearch
|
||||
compact
|
||||
onSelect={(tank) => {
|
||||
mutateDuel((draft) => {
|
||||
Duel.mutate((draft) => {
|
||||
draft.antagonist.tank = tank;
|
||||
draft.antagonist.engine = tank.engines.at(-1)!;
|
||||
draft.antagonist.track = tank.tracks.at(-1)!;
|
||||
|
||||
+4
-6
@@ -32,7 +32,6 @@ export function Controls({
|
||||
zoomable = true,
|
||||
enableRotate = true,
|
||||
}: ControlsProps) {
|
||||
const duelStore = Duel.useStore();
|
||||
const camera = useThree((state) => state.camera as PerspectiveCamera);
|
||||
const canvas = useThree((state) => state.gl.domElement);
|
||||
const orbitControls = useRef<OrbitControlsClass>(null);
|
||||
@@ -108,16 +107,15 @@ export function Controls({
|
||||
);
|
||||
|
||||
function handlePoseEvent(event: Pose) {
|
||||
const duel = duelStore.getState();
|
||||
const hasImprovedVerticalStabilizer = hasEquipment(
|
||||
122,
|
||||
duel.protagonist.tank.equipment_preset,
|
||||
duel.protagonist.equipmentMatrix
|
||||
Duel.state.protagonist.tank.equipment_preset,
|
||||
Duel.state.protagonist.equipmentMatrix
|
||||
);
|
||||
const hasDownImprovedVerticalStabilizer = hasEquipment(
|
||||
124,
|
||||
duel.protagonist.tank.equipment_preset,
|
||||
duel.protagonist.equipmentMatrix
|
||||
Duel.state.protagonist.tank.equipment_preset,
|
||||
Duel.state.protagonist.equipmentMatrix
|
||||
);
|
||||
|
||||
switch (event) {
|
||||
|
||||
+8
-11
@@ -14,7 +14,6 @@ import { TankopediaDisplay } from "../../../../../../stores/tankopediaPersistent
|
||||
import { ModelTankWrapper } from "../../../../../Armor/components/ModelTankWrapper";
|
||||
|
||||
export function TankModel() {
|
||||
const duelStore = Duel.useStore();
|
||||
const protagonist = Duel.use((draft) => draft.protagonist);
|
||||
const track = Duel.use((state) => state.protagonist.track);
|
||||
const turret = Duel.use((state) => state.protagonist.turret);
|
||||
@@ -135,16 +134,15 @@ export function TankModel() {
|
||||
window.addEventListener("pointerup", handlePointerUp);
|
||||
}
|
||||
function handlePointerMove(event: PointerEvent) {
|
||||
const duel = duelStore.getState();
|
||||
const hasImprovedVerticalStabilizer = hasEquipment(
|
||||
122,
|
||||
duel.protagonist.tank.equipment_preset,
|
||||
duel.protagonist.equipmentMatrix
|
||||
Duel.state.protagonist.tank.equipment_preset,
|
||||
Duel.state.protagonist.equipmentMatrix
|
||||
);
|
||||
const hasDownImprovedVerticalStabilizer = hasEquipment(
|
||||
124,
|
||||
duel.protagonist.tank.equipment_preset,
|
||||
duel.protagonist.equipmentMatrix
|
||||
Duel.state.protagonist.tank.equipment_preset,
|
||||
Duel.state.protagonist.equipmentMatrix
|
||||
);
|
||||
const boundingRect = canvas.getBoundingClientRect();
|
||||
|
||||
@@ -223,16 +221,15 @@ export function TankModel() {
|
||||
window.addEventListener("pointerup", handlePointerUp);
|
||||
}
|
||||
function handlePointerMove(event: PointerEvent) {
|
||||
const duel = duelStore.getState();
|
||||
const hasImprovedVerticalStabilizer = hasEquipment(
|
||||
122,
|
||||
duel.protagonist.tank.equipment_preset,
|
||||
duel.protagonist.equipmentMatrix
|
||||
Duel.state.protagonist.tank.equipment_preset,
|
||||
Duel.state.protagonist.equipmentMatrix
|
||||
);
|
||||
const hasDownImprovedVerticalStabilizer = hasEquipment(
|
||||
124,
|
||||
duel.protagonist.tank.equipment_preset,
|
||||
duel.protagonist.equipmentMatrix
|
||||
Duel.state.protagonist.tank.equipment_preset,
|
||||
Duel.state.protagonist.equipmentMatrix
|
||||
);
|
||||
const boundingRect = canvas.getBoundingClientRect();
|
||||
delta.set(event.clientX, event.clientY).sub(position);
|
||||
|
||||
@@ -36,8 +36,6 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
|
||||
return { value } satisfies ThicknessRange;
|
||||
}, [protagonist]);
|
||||
const duelStore = Duel.useStore();
|
||||
const mutateDuel = Duel.useMutation();
|
||||
|
||||
useEffect(() => {
|
||||
if (disturbed) {
|
||||
@@ -47,12 +45,13 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
|
||||
useEffect(() => {
|
||||
function handleKeyDown(event: KeyboardEvent) {
|
||||
const { shells } = duelStore.getState().antagonist.gun;
|
||||
|
||||
times(3, (index) => {
|
||||
if (event.key === `${index + 1}` && shells.length > index) {
|
||||
mutateDuel((draft) => {
|
||||
draft.antagonist.shell = shells[index];
|
||||
if (
|
||||
event.key === `${index + 1}` &&
|
||||
Duel.state.antagonist.gun.shells.length > index
|
||||
) {
|
||||
Duel.mutate((draft) => {
|
||||
draft.antagonist.shell = Duel.state.antagonist.gun.shells[index];
|
||||
});
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.customShell = undefined;
|
||||
|
||||
+26
-27
@@ -1,5 +1,5 @@
|
||||
import type { BlitzkitResponse, StarsInt } from '@blitzkit/core';
|
||||
import { PaperPlaneIcon } from '@radix-ui/react-icons';
|
||||
import type { BlitzkitResponse, StarsInt } from "@blitzkit/core";
|
||||
import { PaperPlaneIcon } from "@radix-ui/react-icons";
|
||||
import {
|
||||
AlertDialog,
|
||||
Button,
|
||||
@@ -7,15 +7,14 @@ import {
|
||||
Flex,
|
||||
Spinner,
|
||||
Text,
|
||||
} from '@radix-ui/themes';
|
||||
import { useState } from 'react';
|
||||
import { useLocale } from '../../../../../../hooks/useLocale';
|
||||
import { App } from '../../../../../../stores/app';
|
||||
import { Duel } from '../../../../../../stores/duel';
|
||||
import { Stars } from '../../Stars';
|
||||
} from "@radix-ui/themes";
|
||||
import { useState } from "react";
|
||||
import { useLocale } from "../../../../../../hooks/useLocale";
|
||||
import { App } from "../../../../../../stores/app";
|
||||
import { Duel } from "../../../../../../stores/duel";
|
||||
import { Stars } from "../../Stars";
|
||||
|
||||
export function VoteCaster() {
|
||||
const appStore = App.useStore();
|
||||
const tank = Duel.use((state) => state.protagonist.tank);
|
||||
const [easiness, setEasiness] = useState<StarsInt | null>(null);
|
||||
const [firepower, setFirepower] = useState<StarsInt | null>(null);
|
||||
@@ -42,28 +41,28 @@ export function VoteCaster() {
|
||||
<Flex direction="column" mt="4">
|
||||
<Flex align="center" justify="between">
|
||||
<Text>
|
||||
Easiness{' '}
|
||||
Easiness{" "}
|
||||
<Text color="gray" size="2">
|
||||
{easiness === null ? '(empty)' : `(${easiness})`}
|
||||
{easiness === null ? "(empty)" : `(${easiness})`}
|
||||
</Text>
|
||||
</Text>
|
||||
<Stars stars={easiness} size="5" onCast={setEasiness} />
|
||||
</Flex>
|
||||
<Flex align="center" justify="between">
|
||||
<Text>
|
||||
Firepower{' '}
|
||||
Firepower{" "}
|
||||
<Text color="gray" size="2">
|
||||
{firepower === null ? '(empty)' : `(${firepower})`}
|
||||
{firepower === null ? "(empty)" : `(${firepower})`}
|
||||
</Text>
|
||||
</Text>
|
||||
<Stars stars={firepower} size="5" onCast={setFirepower} />
|
||||
</Flex>
|
||||
<Flex align="center" justify="between">
|
||||
<Text>
|
||||
Maneuverability{' '}
|
||||
Maneuverability{" "}
|
||||
<Text color="gray" size="2">
|
||||
{maneuverability === null
|
||||
? '(empty)'
|
||||
? "(empty)"
|
||||
: `(${maneuverability})`}
|
||||
</Text>
|
||||
</Text>
|
||||
@@ -75,9 +74,9 @@ export function VoteCaster() {
|
||||
</Flex>
|
||||
<Flex align="center" justify="between">
|
||||
<Text>
|
||||
Survivability{' '}
|
||||
Survivability{" "}
|
||||
<Text color="gray" size="2">
|
||||
{survivability === null ? '(empty)' : `(${survivability})`}
|
||||
{survivability === null ? "(empty)" : `(${survivability})`}
|
||||
</Text>
|
||||
</Text>
|
||||
<Stars stars={survivability} size="5" onCast={setSurvivability} />
|
||||
@@ -100,7 +99,7 @@ export function VoteCaster() {
|
||||
survivability === null)
|
||||
}
|
||||
onClick={() => {
|
||||
const { wargaming } = appStore.getState().logins;
|
||||
const { wargaming } = App.state.logins;
|
||||
|
||||
if (!wargaming) {
|
||||
return setShowDialogue(false);
|
||||
@@ -110,26 +109,26 @@ export function VoteCaster() {
|
||||
|
||||
fetch(
|
||||
`/api/tank-voting/${tank.id}/cast?${
|
||||
easiness === null ? '' : `easiness=${easiness}&`
|
||||
}${firepower === null ? '' : `firepower=${firepower}&`}${
|
||||
easiness === null ? "" : `easiness=${easiness}&`
|
||||
}${firepower === null ? "" : `firepower=${firepower}&`}${
|
||||
maneuverability === null
|
||||
? ''
|
||||
? ""
|
||||
: `maneuverability=${maneuverability}&`
|
||||
}${
|
||||
survivability === null
|
||||
? ''
|
||||
? ""
|
||||
: `survivability=${survivability}&`
|
||||
}player=${wargaming.id}&token=${wargaming.token}`,
|
||||
}player=${wargaming.id}&token=${wargaming.token}`
|
||||
)
|
||||
.then(
|
||||
(response) => response.json() as Promise<BlitzkitResponse>,
|
||||
(response) => response.json() as Promise<BlitzkitResponse>
|
||||
)
|
||||
.then((json) => {
|
||||
setShowDialogue(false);
|
||||
setPostVote(json.status === 'ok');
|
||||
setPostVote(json.status === "ok");
|
||||
|
||||
if (json.status === 'error') {
|
||||
console.error('vote failed', json);
|
||||
if (json.status === "error") {
|
||||
console.error("vote failed", json);
|
||||
}
|
||||
});
|
||||
}}
|
||||
|
||||
Reference in new issue
Block a user