feat: simplified duel state

This commit is contained in:
TrèsAbhi committed 2024-01-15 15:02:04 -06:00
1 parent 7314f1d9b1
commit 66016dedf0
13 files changed
+276 -246

No files matched your search

+1
View File
@@ -59,6 +59,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-fuzzysort": "^1.0.1",
"react-promise-suspense": "^0.3.4",
"react-spinners": "^0.13.8",
"satori": "^0.10.8",
"sharp": "^0.33.2",
@@ -16,8 +16,9 @@ import {
Tooltip,
} from '@radix-ui/themes';
import { go } from 'fuzzysort';
import { produce } from 'immer';
import { debounce } from 'lodash';
import { use, useRef, useState } from 'react';
import { Dispatch, SetStateAction, use, useRef, useState } from 'react';
import { ModuleButton } from '../../../../../components/ModuleButton';
import { SmallTankIcon } from '../../../../../components/SmallTankIcon';
import {
@@ -25,23 +26,26 @@ import {
tankDefinitions,
tankNamesDiacritics,
} from '../../../../../core/blitzkrieg/tankDefinitions';
import mutateTankopedia, {
useTankopedia,
} from '../../../../../stores/tankopedia';
import { useTankopedia } from '../../../../../stores/tankopedia';
import { Duel } from '../page';
export function AntagonistBar() {
interface AntagonistBarProps {
duel: Duel;
setDuel: Dispatch<SetStateAction<Duel>>;
}
export function AntagonistBar({
duel: { antagonist },
setDuel,
}: AntagonistBarProps) {
const awaitedTankDefinitions = use(tankDefinitions);
const mode = useTankopedia((state) => state.mode);
const awaitedTankNamesDiacritics = use(tankNamesDiacritics);
const [tab, setTab] = useState('search');
const searchInput = useRef<HTMLInputElement>(null);
const antagonist = useTankopedia((state) => {
if (!state.areTanksAssigned) return;
return state.antagonist;
});
const [searchResults, setSearchResults] = useState<number[]>([]);
if (!antagonist || mode !== 'armor') return null;
if (mode !== 'armor') return null;
return (
<Card>
@@ -58,10 +62,11 @@ export function AntagonistBar() {
key={shell.id}
last={index === antagonist.gun.shells.length - 1}
onClick={() => {
mutateTankopedia((draft) => {
if (!draft.areTanksAssigned) return;
draft.antagonist.shell = shell;
});
setDuel(
produce<Duel>((draft) => {
draft.antagonist.shell = shell;
}),
);
}}
/>
);
@@ -147,17 +152,17 @@ export function AntagonistBar() {
key={tank.id}
variant="ghost"
onClick={() => {
mutateTankopedia((draft) => {
if (!draft.areTanksAssigned) return;
draft.antagonist.tank = tank;
draft.antagonist.turret =
tank.turrets.at(-1)!;
draft.antagonist.gun =
draft.antagonist.turret.guns.at(-1)!;
draft.antagonist.shell =
draft.antagonist.gun.shells[0];
});
setDuel(
produce<Duel>((draft) => {
draft.antagonist.tank = tank;
draft.antagonist.turret =
tank.turrets.at(-1)!;
draft.antagonist.gun =
draft.antagonist.turret.guns.at(-1)!;
draft.antagonist.shell =
draft.antagonist.gun.shells[0];
}),
);
setSearchResults([]);
searchInput.current!.value = '';
setTab('configure');
@@ -200,15 +205,15 @@ export function AntagonistBar() {
}
key={turret.id}
onClick={() => {
mutateTankopedia((draft) => {
if (!draft.areTanksAssigned) return;
draft.antagonist.turret = turret;
draft.antagonist.gun =
turret.guns.at(-1)!;
draft.antagonist.shell =
draft.antagonist.gun.shells[0];
});
setDuel(
produce<Duel>((draft) => {
draft.antagonist.turret = turret;
draft.antagonist.gun =
turret.guns.at(-1)!;
draft.antagonist.shell =
draft.antagonist.gun.shells[0];
}),
);
}}
selected={antagonist.turret.id === turret.id}
tier={turret.tier}
@@ -228,11 +233,12 @@ export function AntagonistBar() {
index === antagonist.turret.guns.length - 1
}
onClick={() => {
mutateTankopedia((draft) => {
if (!draft.areTanksAssigned) return;
draft.antagonist.gun = gun;
draft.antagonist.shell = gun.shells[0];
});
setDuel(
produce<Duel>((draft) => {
draft.antagonist.gun = gun;
draft.antagonist.shell = gun.shells[0];
}),
);
}}
selected={antagonist.gun.id === gun.id}
tier={gun.tier}
@@ -277,14 +283,14 @@ export function AntagonistBar() {
<Button
variant="ghost"
onClick={() => {
mutateTankopedia((draft) => {
if (!draft.areTanksAssigned) return;
[draft.antagonist, draft.protagonist] = [
draft.protagonist,
draft.antagonist,
];
});
setDuel(
produce<Duel>((draft) => {
[draft.protagonist, draft.antagonist] = [
draft.antagonist,
draft.protagonist,
];
}),
);
}}
>
<ShuffleIcon /> Swap tanks
@@ -10,6 +10,7 @@ import mutateTankopedia, {
useTankopedia,
} from '../../../../../../stores/tankopedia';
import { Loader } from '../../../../components/Loader';
import { Duel } from '../../page';
import { Controls } from '../Control';
import { Lighting } from '../Lighting';
import { RotationInputs } from '../RotationInputs';
@@ -18,16 +19,16 @@ import { Options } from './components/Options';
import { TankArmor } from './components/TankArmor';
import { TankModel } from './components/TankModel';
export function TankDisplay() {
interface TankDisplayProps {
duel: Duel;
}
export function TankDisplay({ duel }: TankDisplayProps) {
const canvas = useRef<HTMLCanvasElement>(null);
const awaitedModelDefinitions = use(modelDefinitions);
const canvasWrapper = useRef<HTMLDivElement>(null);
const [isFullScreen, setIsFullScreen] = useState(false);
const mode = useTankopedia((state) => state.mode);
const protagonist = useTankopedia((state) => {
if (!state.areTanksAssigned) return;
return state.protagonist;
});
function handlePointerDown() {
window.addEventListener('pointermove', handlePointerMove);
@@ -56,12 +57,14 @@ export function TankDisplay() {
});
useEffect(() => {
if (!protagonist) return void 0;
if (!duel.protagonist) return void 0;
const tankModelDefinition = awaitedModelDefinitions[protagonist.tank.id];
const tankModelDefinition =
awaitedModelDefinitions[duel.protagonist.tank.id];
const turretModelDefinition =
tankModelDefinition.turrets[protagonist.turret.id];
const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id];
tankModelDefinition.turrets[duel.protagonist.turret.id];
const gunModelDefinition =
turretModelDefinition.guns[duel.protagonist.gun.id];
mutateTankopedia((draft) => {
[draft.model.physical.pitch, draft.model.physical.yaw] =
@@ -72,9 +75,7 @@ export function TankDisplay() {
turretModelDefinition.yaw,
);
});
}, [protagonist?.gun, protagonist?.turret]);
if (!protagonist) return null;
}, [duel.protagonist.gun, duel.protagonist.turret]);
return (
<Theme radius={isFullScreen ? 'none' : undefined}>
@@ -130,15 +131,15 @@ export function TankDisplay() {
</Html>
}
>
<TankModel />
<TankArmor />
<TankModel duel={duel} />
<TankArmor duel={duel} />
</Suspense>
</Canvas>
</div>
<Options canvas={canvasWrapper} isFullScreen={isFullScreen} />
<RotationInputs />
<RotationInputs duel={duel} />
</Flex>
</Theme>
</Card>
@@ -1,5 +1,5 @@
import { useLoader } from '@react-three/fiber';
import { memo, useEffect, useRef, useState } from 'react';
import { memo, useEffect, useRef } from 'react';
import { Euler, Group, Mesh, Vector3 } from 'three';
import { GLTFLoader } from 'three-stdlib';
import { degToRad } from 'three/src/math/MathUtils';
@@ -7,27 +7,23 @@ import { ArmorMesh } from '../../../../../../../components/ArmorMesh';
import { HeadsUpDisplay } from '../../../../../../../components/HeadsUpDisplay';
import { X_AXIS, Y_AXIS, Z_AXIS } from '../../../../../../../constants/axis';
import { asset } from '../../../../../../../core/blitzkrieg/asset';
import {
ModelDefinitions,
modelDefinitions,
} from '../../../../../../../core/blitzkrieg/modelDefinitions';
import {
ModelTransformEventData,
modelTransformEvent,
} from '../../../../../../../core/blitzkrieg/modelTransform';
import { nameToArmorId } from '../../../../../../../core/blitzkrieg/nameToArmorId';
import { resolveArmor } from '../../../../../../../core/blitzkrieg/resolveThickness';
import { useModelDefinitions } from '../../../../../../../core/hooks/useModelDefinitions';
import { useTankopedia } from '../../../../../../../stores/tankopedia';
import { Duel } from '../../../page';
export const TankArmor = memo(() => {
interface TankArmorProps {
duel: Duel;
}
export const TankArmor = memo<TankArmorProps>(({ duel }) => {
const wrapper = useRef<Group>(null);
const [awaitedModelDefinitions, setAwaitedModelDefinitions] = useState<
ModelDefinitions | undefined
>(undefined);
const protagonist = useTankopedia((state) => {
if (!state.areTanksAssigned) return;
return state.protagonist;
});
const awaitedModelDefinitions = useModelDefinitions();
const showSpacedArmor = useTankopedia(
(state) => state.model.visual.showSpacedArmor,
);
@@ -35,12 +31,6 @@ export const TankArmor = memo(() => {
const gunContainer = useRef<Group>(null);
const initialTankopediaState = useTankopedia.getState();
useEffect(() => {
(async () => {
setAwaitedModelDefinitions(await modelDefinitions);
})();
}, []);
useEffect(() => {
if (!awaitedModelDefinitions) return;
@@ -118,25 +108,22 @@ export const TankArmor = memo(() => {
return unsubscribe;
});
if (!protagonist) return null;
const armorGltf = useLoader(
GLTFLoader,
asset(`3d/tanks/armor/${protagonist.tank.id}.glb`),
asset(`3d/tanks/armor/${duel.protagonist.tank.id}.glb`),
);
const modelGltf = useLoader(
GLTFLoader,
asset(`3d/tanks/models/${protagonist.tank.id}.glb`),
asset(`3d/tanks/models/${duel.protagonist.tank.id}.glb`),
);
if (!awaitedModelDefinitions) return null;
const armorNodes = Object.values(armorGltf.nodes);
const modelNodes = Object.values(modelGltf.nodes);
const tankModelDefinition = awaitedModelDefinitions[protagonist.tank.id];
const tankModelDefinition = awaitedModelDefinitions[duel.protagonist.tank.id];
const turretModelDefinition =
tankModelDefinition.turrets[protagonist.turret.id];
const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id];
tankModelDefinition.turrets[duel.protagonist.turret.id];
const gunModelDefinition =
turretModelDefinition.guns[duel.protagonist.gun.id];
const turretOrigin = new Vector3(
tankModelDefinition.turretOrigin[0],
tankModelDefinition.turretOrigin[1],
@@ -173,6 +160,7 @@ export const TankArmor = memo(() => {
return (
<ArmorMesh
duel={duel}
key={node.uuid}
geometry={(node as Mesh).geometry}
isSpaced={spaced ?? false}
@@ -191,6 +179,7 @@ export const TankArmor = memo(() => {
return (
<ArmorMesh
duel={duel}
key={node.uuid}
geometry={(node as Mesh).geometry}
isSpaced
@@ -223,6 +212,7 @@ export const TankArmor = memo(() => {
return (
<ArmorMesh
duel={duel}
key={node.uuid}
geometry={(node as Mesh).geometry}
position={turretOrigin}
@@ -252,6 +242,7 @@ export const TankArmor = memo(() => {
return (
<ArmorMesh
duel={duel}
key={node.uuid}
geometry={(node as Mesh).geometry}
position={turretOrigin.clone().add(gunOrigin)}
@@ -272,6 +263,7 @@ export const TankArmor = memo(() => {
return (
<ArmorMesh
duel={duel}
key={node.uuid}
geometry={(node as Mesh).geometry}
isSpaced
@@ -26,17 +26,16 @@ import { normalizeAnglePI } from '../../../../../../../core/math/normalizeAngle1
import mutateTankopedia, {
useTankopedia,
} from '../../../../../../../stores/tankopedia';
import { Duel } from '../../../page';
export const TankModel = memo(() => {
interface TankModelProps {
duel: Duel;
}
export const TankModel = memo(({ duel: { protagonist } }: TankModelProps) => {
const [awaitedModelDefinitions, setAwaitedModelDefinitions] = useState<
ModelDefinitions | undefined
>(undefined);
const protagonist = useTankopedia((state) => {
if (!state.areTanksAssigned) return;
return state.protagonist;
});
if (!protagonist) return null;
const canvas = useThree((state) => state.gl.domElement);
const physical = useTankopedia((state) => state.model.physical);
@@ -0,0 +1,67 @@
import { Flex } from '@radix-ui/themes';
import { produce } from 'immer';
import { Dispatch, SetStateAction } from 'react';
import { ModuleButton } from '../../../../../components/ModuleButton';
import { Duel } from '../page';
interface ModulesProps {
duel: Duel;
setDuel: Dispatch<SetStateAction<Duel>>;
}
export function Modules({ duel, setDuel }: ModulesProps) {
return (
<Flex gap="2">
<Flex>
{duel.protagonist.tank.turrets.map((thisTurret, index) => {
return (
<ModuleButton
key={thisTurret.id}
selected={duel.protagonist.turret.id === thisTurret.id}
type="module"
module="turret"
tier={thisTurret.tier}
first={index === 0}
last={index === duel.protagonist.tank.turrets.length - 1}
rowChild
onClick={() => {
setDuel(
produce<Duel>((draft) => {
draft.protagonist.turret = thisTurret;
draft.protagonist.gun = thisTurret.guns.at(-1)!;
draft.protagonist.shell = thisTurret.guns.at(-1)!.shells[0];
}),
);
}}
/>
);
})}
</Flex>
<Flex>
{duel.protagonist.turret.guns.map((thisGun, index) => {
return (
<ModuleButton
key={thisGun.id}
type="module"
module="gun"
tier={thisGun.tier}
selected={duel.protagonist.gun.id === thisGun.id}
first={index === 0}
last={index === duel.protagonist.turret.guns.length - 1}
rowChild
onClick={() => {
setDuel(
produce<Duel>((draft) => {
draft.protagonist.gun = thisGun;
draft.protagonist.shell = thisGun.shells[0];
}),
);
}}
/>
);
})}
</Flex>
</Flex>
);
}
@@ -6,16 +6,17 @@ import { modelTransformEvent } from '../../../../../core/blitzkrieg/modelTransfo
import mutateTankopedia, {
useTankopedia,
} from '../../../../../stores/tankopedia';
import { Duel } from '../page';
export function RotationInputs() {
interface RotationInputsProps {
duel: Duel;
}
export function RotationInputs({ duel: { protagonist } }: RotationInputsProps) {
const physical = useTankopedia((state) => state.model.physical);
const awaitedModelDefinitions = use(modelDefinitions);
const turretYawInput = useRef<HTMLInputElement>(null);
const gunPitchInput = useRef<HTMLInputElement>(null);
const protagonist = useTankopedia((state) => {
if (!state.areTanksAssigned) return;
return state.protagonist;
});
useEffect(() => {
turretYawInput.current!.value = `${-Math.round(
@@ -28,8 +29,6 @@ export function RotationInputs() {
)}`;
}, [physical.pitch]);
if (!protagonist) return null;
const tankModelDefinition = awaitedModelDefinitions[protagonist.tank.id];
const turretModelDefinition =
tankModelDefinition.turrets[protagonist.turret.id];
@@ -0,0 +1,29 @@
import { ArrowLeftIcon } from '@radix-ui/react-icons';
import { Flex, Heading, Link, Text } from '@radix-ui/themes';
import { Flag } from '../../../../../components/Flag';
import { Duel } from '../page';
interface TitleProps {
duel: Duel;
}
export function Title({ duel }: TitleProps) {
return (
<Flex justify="between" align="center">
<Link
href="/tools/tankopedia"
style={{ color: 'unset', textDecoration: 'none' }}
>
<Flex gap="1" align="center">
<ArrowLeftIcon />
<Text>Back</Text>
</Flex>
</Link>
<Flex gap="2" align="center">
<Flag nation={duel.protagonist.tank.nation} />
<Heading>{duel.protagonist.tank.name}</Heading>
</Flex>
</Flex>
);
}
+48 -115
View File
@@ -1,154 +1,87 @@
'use client';
import { ArrowLeftIcon } from '@radix-ui/react-icons';
import { Flex, Heading, Text } from '@radix-ui/themes';
import Link from 'next/link';
import { use, useEffect } from 'react';
import { Flag } from '../../../../components/Flag';
import { ModuleButton } from '../../../../components/ModuleButton';
import { Flex } from '@radix-ui/themes';
import { produce } from 'immer';
import { use, useEffect, useState } from 'react';
import PageWrapper from '../../../../components/PageWrapper';
import { tankDefinitions } from '../../../../core/blitzkrieg/tankDefinitions';
import mutateTankopedia, { useTankopedia } from '../../../../stores/tankopedia';
import mutateTankopedia, { DuelMember } from '../../../../stores/tankopedia';
import { AntagonistBar } from './components/AntagonistBar';
import { TankDisplay } from './components/Model/TankDisplay';
import { Modules } from './components/Modules';
import { Title } from './components/Title';
export interface Duel {
protagonist: DuelMember;
antagonist: DuelMember;
}
export default function Page({ params }: { params: { id: string } }) {
const id = parseInt(params.id);
const awaitedTankDefinitions = use(tankDefinitions);
const [duel, setDuel] = useState<Duel>({
protagonist: {
tank: awaitedTankDefinitions[id],
turret: awaitedTankDefinitions[id].turrets.at(-1)!,
gun: awaitedTankDefinitions[id].turrets.at(-1)!.guns.at(-1)!,
shell: awaitedTankDefinitions[id].turrets.at(-1)!.guns.at(-1)!.shells[0],
},
antagonist: {
tank: awaitedTankDefinitions[id],
turret: awaitedTankDefinitions[id].turrets.at(-1)!,
gun: awaitedTankDefinitions[id].turrets.at(-1)!.guns.at(-1)!,
shell: awaitedTankDefinitions[id].turrets.at(-1)!.guns.at(-1)!.shells[0],
},
});
useEffect(() => {
mutateTankopedia((draft) => {
draft.model.physical.yaw = 0;
draft.model.physical.pitch = 0;
draft.mode = 'model';
draft.areTanksAssigned = true;
if (!draft.areTanksAssigned) return;
draft.protagonist = {} as typeof draft.protagonist;
draft.protagonist.tank = awaitedTankDefinitions[id];
draft.protagonist.turret = draft.protagonist.tank.turrets.at(-1)!;
draft.protagonist.gun = draft.protagonist.turret.guns.at(-1)!;
draft.protagonist.shell = draft.protagonist.gun.shells[0];
draft.antagonist = draft.protagonist;
});
function handleKeyDown(event: KeyboardEvent) {
if (event.key === '1') {
mutateTankopedia((draft) => {
if (!draft.areTanksAssigned) return;
draft.antagonist.shell = draft.antagonist.gun.shells[0];
});
setDuel(
produce<Duel>((draft) => {
draft.antagonist.shell = draft.antagonist.gun.shells[0];
}),
);
} else if (event.key === '2') {
mutateTankopedia((draft) => {
if (!draft.areTanksAssigned) return;
if (draft.antagonist.gun.shells[1])
draft.antagonist.shell = draft.antagonist.gun.shells[1];
});
setDuel(
produce<Duel>((draft) => {
if (draft.antagonist.gun.shells[1]) {
draft.antagonist.shell = draft.antagonist.gun.shells[1];
}
}),
);
} else if (event.key === '3') {
mutateTankopedia((draft) => {
if (!draft.areTanksAssigned) return;
if (draft.antagonist.gun.shells[2])
draft.antagonist.shell = draft.antagonist.gun.shells[2];
});
setDuel(
produce<Duel>((draft) => {
if (draft.antagonist.gun.shells[2]) {
draft.antagonist.shell = draft.antagonist.gun.shells[2];
}
}),
);
}
}
window.addEventListener('keydown', handleKeyDown);
return () => {
mutateTankopedia((draft) => {
draft.areTanksAssigned = false;
});
window.removeEventListener('keydown', handleKeyDown);
};
}, [id]);
const protagonist = useTankopedia((state) => {
if (!state.areTanksAssigned) return;
return state.protagonist;
});
if (!protagonist) return null;
return (
<PageWrapper color="purple">
<Flex gap="8" direction="column">
<Flex gap="4" direction="column">
<Flex justify="between" align="center">
<Link
href="/tools/tankopedia"
style={{ color: 'unset', textDecoration: 'none' }}
>
<Flex gap="1" align="center">
<ArrowLeftIcon />
<Text>Back</Text>
</Flex>
</Link>
<Flex gap="2" align="center">
<Flag nation={protagonist.tank.nation} />
<Heading>{protagonist.tank.name}</Heading>
</Flex>
</Flex>
<TankDisplay />
<AntagonistBar />
<Flex gap="2">
<Flex>
{protagonist.tank.turrets.map((thisTurret, index) => {
return (
<ModuleButton
key={thisTurret.id}
selected={protagonist.turret.id === thisTurret.id}
type="module"
module="turret"
tier={thisTurret.tier}
first={index === 0}
last={index === protagonist.tank.turrets.length - 1}
rowChild
onClick={() => {
mutateTankopedia((draft) => {
if (!draft.areTanksAssigned) return;
draft.protagonist.turret = thisTurret;
draft.protagonist.gun = thisTurret.guns.at(-1)!;
draft.protagonist.shell =
draft.protagonist.gun.shells[0];
});
}}
/>
);
})}
</Flex>
<Flex>
{protagonist.turret.guns.map((thisGun, index) => {
return (
<ModuleButton
key={thisGun.id}
type="module"
module="gun"
tier={thisGun.tier}
selected={protagonist.gun.id === thisGun.id}
first={index === 0}
last={index === protagonist.turret.guns.length - 1}
rowChild
onClick={() => {
mutateTankopedia((draft) => {
if (!draft.areTanksAssigned) return;
draft.protagonist.gun = thisGun;
draft.protagonist.shell = thisGun.shells[0];
});
}}
/>
);
})}
</Flex>
</Flex>
<Title duel={duel} />
<TankDisplay duel={duel} />
<AntagonistBar duel={duel} setDuel={setDuel} />
<Modules duel={duel} setDuel={setDuel} />
</Flex>
</Flex>
</PageWrapper>
+4 -7
View File
@@ -2,14 +2,15 @@ import { MeshProps } from '@react-three/fiber';
import { MeshStandardMaterial } from 'three';
import CustomShaderMaterial from 'three-custom-shader-material';
import { degToRad } from 'three/src/math/MathUtils';
import { Duel } from '../../app/tools/tankopedia/[id]/page';
import { canSplash } from '../../core/blitz/canSplash';
import { isExplosive } from '../../core/blitz/isExplosive';
import { resolveNearPenetration } from '../../core/blitz/resolveNearPenetration';
import { useTankopedia } from '../../stores/tankopedia';
import fragmentShader from './shaders/fragment.glsl';
import vertexShader from './shaders/vertex.glsl';
interface ArmorMeshProps extends MeshProps {
duel: Duel;
isSpaced: boolean;
isExternalModule?: boolean;
thickness: number;
@@ -19,14 +20,10 @@ export function ArmorMesh({
isSpaced,
isExternalModule = false,
thickness,
duel,
...props
}: ArmorMeshProps) {
const shell = useTankopedia((state) => {
if (!state.areTanksAssigned) return;
return state.antagonist.shell;
});
if (!shell) return null;
const { shell } = duel.antagonist;
return (
<>
+6
View File
@@ -0,0 +1,6 @@
import usePromise from 'react-promise-suspense';
import { modelDefinitions } from '../blitzkrieg/modelDefinitions';
export function useModelDefinitions() {
return usePromise(async () => await modelDefinitions, []);
}
+8 -20
View File
@@ -14,8 +14,13 @@ export type TankopediaSortBy = 'tier' | 'name';
export type TankopediaSortDirection = 'ascending' | 'descending';
export type TankopediaTestTankDisplay = 'include' | 'exclude' | 'only';
export type TankopediaMode = 'model' | 'armor';
type Tankopedia = {
export interface DuelMember {
tank: TankDefinition;
turret: TurretDefinition;
gun: GunDefinition;
shell: ShellDefinition;
}
interface Tankopedia {
sort: {
by: TankopediaSortBy;
direction: TankopediaSortDirection;
@@ -39,24 +44,7 @@ type Tankopedia = {
};
};
mode: TankopediaMode;
} & (
| {
areTanksAssigned: true;
protagonist: {
tank: TankDefinition;
turret: TurretDefinition;
gun: GunDefinition;
shell: ShellDefinition;
};
antagonist: {
tank: TankDefinition;
turret: TurretDefinition;
gun: GunDefinition;
shell: ShellDefinition;
};
}
| { areTanksAssigned: false }
);
}
export const useTankopedia = create<Tankopedia>()(
subscribeWithSelector<Tankopedia>(() => ({
+12
View File
@@ -3715,6 +3715,11 @@ [email protected], fast-deep-equal@^3.1.3:
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
fast-deep-equal@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
integrity sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==
[email protected], fast-glob@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129"
@@ -5264,6 +5269,13 @@ react-merge-refs@^1.1.0:
resolved "https://registry.yarnpkg.com/react-merge-refs/-/react-merge-refs-1.1.0.tgz#73d88b892c6c68cbb7a66e0800faa374f4c38b06"
integrity sha512-alTKsjEL0dKH/ru1Iyn7vliS2QRcBp9zZPGoWxUOvRGWPUYgjo+V01is7p04It6KhgrzhJGnIj9GgX8W4bZoCQ==
react-promise-suspense@^0.3.4:
version "0.3.4"
resolved "https://registry.yarnpkg.com/react-promise-suspense/-/react-promise-suspense-0.3.4.tgz#05d19a75703d71374674840056cfef2fcd38809d"
integrity sha512-I42jl7L3Ze6kZaq+7zXWSunBa3b1on5yfvUW6Eo/3fFOj6dZ5Bqmcd264nJbTK/gn1HjjILAjSwnZbV4RpSaNQ==
dependencies:
fast-deep-equal "^2.0.1"
react-reconciler@^0.27.0:
version "0.27.0"
resolved "https://registry.yarnpkg.com/react-reconciler/-/react-reconciler-0.27.0.tgz#360124fdf2d76447c7491ee5f0e04503ed9acf5b"