diff --git a/src/app/tools/tankopedia/[id]/components/Model/TankDisplay.tsx b/src/app/tools/tankopedia/[id]/components/Model/TankDisplay.tsx index 5d0e18171..06a5571de 100644 --- a/src/app/tools/tankopedia/[id]/components/Model/TankDisplay.tsx +++ b/src/app/tools/tankopedia/[id]/components/Model/TankDisplay.tsx @@ -6,8 +6,10 @@ import { import { Button, Card, Flex, Tabs, Theme } from '@radix-ui/themes'; import { Html } from '@react-three/drei'; import { Canvas } from '@react-three/fiber'; -import { Suspense, useEffect, useRef, useState } from 'react'; +import { Suspense, use, useEffect, useRef, useState } from 'react'; import { Group } from 'three'; +import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits'; +import { modelDefinitions } from '../../../../../../core/blitzkrieg/modelDefinitions'; import { theme } from '../../../../../../stitches.config'; import mutateTankopedia, { TankopediaMode, @@ -19,19 +21,20 @@ import { RotationInputs } from '../RotationInputs'; import { SceneProps } from '../SceneProps'; import { TankModel } from './components/TankModel'; -interface TankDisplayProps { - tankId: number; - turretId: number; - gunId: number; -} - -export function TankDisplay({ tankId, turretId, gunId }: TankDisplayProps) { +export function TankDisplay() { const canvas = useRef(null); + const awaitedModelDefinitions = use(modelDefinitions); const canvasWrapper = useRef(null); const [isFullScreen, setIsFullScreen] = useState(false); const mode = useTankopedia((state) => state.mode); const hullContainer = useRef(null); const showGrid = useTankopedia((state) => state.model.showGrid); + const protagonist = useTankopedia((state) => { + if (!state.areTanksAssigned) return; + return state.protagonist; + }); + + if (!protagonist) return null; function handlePointerDown() { window.addEventListener('pointermove', handlePointerMove); @@ -59,6 +62,22 @@ export function TankDisplay({ tankId, turretId, gunId }: TankDisplayProps) { }; }); + useEffect(() => { + const tankModelDefinition = awaitedModelDefinitions[protagonist.tank.id]; + const turretModelDefinition = + tankModelDefinition.turrets[protagonist.turret.id]; + const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id]; + + mutateTankopedia((draft) => { + [draft.model.gunPitch, draft.model.turretYaw] = applyPitchYawLimits( + draft.model.gunPitch, + draft.model.turretYaw, + gunModelDefinition.pitch, + turretModelDefinition.yaw, + ); + }); + }, [protagonist.gun, protagonist.turret]); + return ( } > - + @@ -159,7 +173,7 @@ export function TankDisplay({ tankId, turretId, gunId }: TankDisplayProps) { - + diff --git a/src/app/tools/tankopedia/[id]/components/Model/components/TankModel.tsx b/src/app/tools/tankopedia/[id]/components/Model/components/TankModel.tsx index 34eb598f3..b3290c1fa 100644 --- a/src/app/tools/tankopedia/[id]/components/Model/components/TankModel.tsx +++ b/src/app/tools/tankopedia/[id]/components/Model/components/TankModel.tsx @@ -20,127 +20,127 @@ import { GunContainer } from '../../GunContainer'; import { HullContainer } from '../../HullContainer'; import { TurretContainer } from '../../TurretContainer'; -interface TankModelProps { - tankId: number; - turretId: number; - gunId: number; -} +export const TankModel = forwardRef((_props, ref) => { + // "TypeError: dispatcher.use is not a function" + // const awaitedModelDefinitions = use(modelDefinitions); + const [awaitedModelDefinitions, setAwaitedModelDefinitions] = useState< + ModelDefinitions | undefined + >(undefined); + const protagonist = useTankopedia((state) => { + if (!state.areTanksAssigned) return; + return state.protagonist; + }); -export const TankModel = forwardRef( - ({ tankId, turretId, gunId }, ref) => { - // "TypeError: dispatcher.use is not a function" - // const awaitedModelDefinitions = use(modelDefinitions); - const [awaitedModelDefinitions, setAwaitedModelDefinitions] = useState< - ModelDefinitions | undefined - >(undefined); - const gltf = useLoader(GLTFLoader, `/test/${tankId}.glb`); - const model = useTankopedia((state) => state.model); - const hullContainer = useRef(null); - const turretContainer = useRef(null); - const gunContainer = useRef(null); + if (!protagonist) return null; - useImperativeHandle(ref, () => hullContainer.current!); - useEffect(() => { - (async () => { - setAwaitedModelDefinitions(await modelDefinitions); - })(); - }, []); + const gltf = useLoader(GLTFLoader, `/test/${protagonist.tank.id}.glb`); + const model = useTankopedia((state) => state.model); + const hullContainer = useRef(null); + const turretContainer = useRef(null); + const gunContainer = useRef(null); - if (!awaitedModelDefinitions) return null; + useImperativeHandle(ref, () => hullContainer.current!); + useEffect(() => { + (async () => { + setAwaitedModelDefinitions(await modelDefinitions); + })(); + }, []); - const tankModelDefinition = awaitedModelDefinitions[tankId]; - const turretModelDefinition = tankModelDefinition.turrets[turretId]; - const gunModelDefinition = turretModelDefinition.guns[gunId]; - const turretOrigin = new Vector3( - tankModelDefinition.turretOrigin[0], - tankModelDefinition.turretOrigin[1], - -tankModelDefinition.turretOrigin[2], - ).applyAxisAngle(X_AXIS, Math.PI / 2); - const gunOrigin = new Vector3( - turretModelDefinition.gunOrigin[0], - turretModelDefinition.gunOrigin[1], - -turretModelDefinition.gunOrigin[2], - ).applyAxisAngle(X_AXIS, Math.PI / 2); + if (!awaitedModelDefinitions) return null; - return ( - + mutateTankopedia((draft) => { + draft.model.controlsEnabled = false; + }) + } + onYawEnd={(yaw) => { + mutateTankopedia((draft) => { + draft.model.controlsEnabled = true; + draft.model.hullYaw = yaw; + }); + }} + onTrackStart={() => { + mutateTankopedia((draft) => { + draft.model.controlsEnabled = false; + }); + }} + onTrackEnd={() => { + mutateTankopedia((draft) => { + draft.model.controlsEnabled = true; + }); + }} + > + - mutateTankopedia((draft) => { - draft.model.controlsEnabled = false; + mutateTankopedia((state) => { + state.model.controlsEnabled = false; }) } - onYawEnd={(yaw) => { - mutateTankopedia((draft) => { - draft.model.controlsEnabled = true; - draft.model.hullYaw = yaw; - }); - }} - onTrackStart={() => { - mutateTankopedia((draft) => { - draft.model.controlsEnabled = false; - }); - }} - onTrackEnd={() => { - mutateTankopedia((draft) => { - draft.model.controlsEnabled = true; + yawLimits={turretModelDefinition.yaw} + pitchLimits={gunModelDefinition.pitch} + pitch={model.gunPitch} + onYawEnd={(pitch, yaw) => { + mutateTankopedia((state) => { + state.model.controlsEnabled = true; + state.model.gunPitch = pitch; + state.model.turretYaw = yaw; }); }} + gunContainer={gunContainer} + turretOrigin={turretOrigin} + yaw={model.turretYaw} > - + onPitchStart={() => { mutateTankopedia((state) => { state.model.controlsEnabled = false; - }) - } - yawLimits={turretModelDefinition.yaw} + }); + }} pitchLimits={gunModelDefinition.pitch} - pitch={model.gunPitch} - onYawEnd={(pitch, yaw) => { + turretContainer={turretContainer} + yaw={model.turretYaw} + onPitchEnd={(pitch, yaw) => { mutateTankopedia((state) => { state.model.controlsEnabled = true; state.model.gunPitch = pitch; state.model.turretYaw = yaw; }); }} - gunContainer={gunContainer} + yawLimits={turretModelDefinition.yaw} + gunOrigin={gunOrigin} + pitch={model.gunPitch} + model={gunModelDefinition.model} + objects={Object.values(gltf.nodes)} turretOrigin={turretOrigin} - yaw={model.turretYaw} - > - { - mutateTankopedia((state) => { - state.model.controlsEnabled = false; - }); - }} - pitchLimits={gunModelDefinition.pitch} - turretContainer={turretContainer} - yaw={model.turretYaw} - onPitchEnd={(pitch, yaw) => { - mutateTankopedia((state) => { - state.model.controlsEnabled = true; - state.model.gunPitch = pitch; - state.model.turretYaw = yaw; - }); - }} - yawLimits={turretModelDefinition.yaw} - gunOrigin={gunOrigin} - pitch={model.gunPitch} - model={gunModelDefinition.model} - objects={Object.values(gltf.nodes)} - turretOrigin={turretOrigin} - /> - - - ); - }, -); + /> + + + ); +}); diff --git a/src/app/tools/tankopedia/[id]/components/RotationInputs.tsx b/src/app/tools/tankopedia/[id]/components/RotationInputs.tsx index d3c118022..387c7169c 100644 --- a/src/app/tools/tankopedia/[id]/components/RotationInputs.tsx +++ b/src/app/tools/tankopedia/[id]/components/RotationInputs.tsx @@ -7,26 +7,23 @@ import mutateTankopedia, { useTankopedia, } from '../../../../../stores/tankopedia'; -interface RotationInputsProps { - tankId: number; - gunId: number; - turretId: number; -} - -export function RotationInputs({ - tankId, - gunId, - turretId, -}: RotationInputsProps) { +export function RotationInputs() { const model = useTankopedia((state) => state.model); const awaitedModelDefinitions = use(modelDefinitions); const turretYawInput = useRef(null); const hullYawInput = useRef(null); const gunPitchInput = useRef(null); + const protagonist = useTankopedia((state) => { + if (!state.areTanksAssigned) return; + return state.protagonist; + }); - const tankModelDefinition = awaitedModelDefinitions[tankId]; - const turretModelDefinition = tankModelDefinition.turrets[turretId]; - const gunModelDefinition = turretModelDefinition.guns[gunId]; + if (!protagonist) return null; + + const tankModelDefinition = awaitedModelDefinitions[protagonist.tank.id]; + const turretModelDefinition = + tankModelDefinition.turrets[protagonist.turret.id]; + const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id]; useEffect(() => { hullYawInput.current!.value = `${-Math.round( diff --git a/src/app/tools/tankopedia/[id]/components/VersusBar.tsx b/src/app/tools/tankopedia/[id]/components/VersusBar.tsx new file mode 100644 index 000000000..ca5cd5f61 --- /dev/null +++ b/src/app/tools/tankopedia/[id]/components/VersusBar.tsx @@ -0,0 +1,254 @@ +import { CaretRightIcon, MagnifyingGlassIcon } from '@radix-ui/react-icons'; +import { + Button, + Card, + Checkbox, + Dialog, + Flex, + Heading, + Tabs, + Text, + TextField, + Tooltip, +} from '@radix-ui/themes'; +import { go } from 'fuzzysort'; +import { debounce } from 'lodash'; +import { use, useRef, useState } from 'react'; +import { ModuleButtons } from '../../../../../components/ModuleButton'; +import { asset } from '../../../../../core/blitzkrieg/asset'; +import { + tankDefinitions, + tankNamesDiacritics, +} from '../../../../../core/blitzkrieg/tankDefinitions'; +import mutateTankopedia, { + useTankopedia, +} from '../../../../../stores/tankopedia'; + +export function VersusBar() { + const awaitedTankDefinitions = use(tankDefinitions); + const mode = useTankopedia((state) => state.mode); + const awaitedTankNamesDiacritics = use(tankNamesDiacritics); + const [versusTankTab, setVersusTankTab] = useState('search'); + const versusTankSearchInput = useRef(null); + const antagonist = useTankopedia((state) => { + // goofy ahh typescript discriminator hack + if (!state.areTanksAssigned) return; + return state.antagonist; + }); + const [versusTankSearchResults, setVersusTankSearchResults] = useState< + number[] + >([]); + + if (!antagonist || mode !== 'armor') return null; + + return ( + + + + Versus + + + + + + + + + + + Search + Configure + + + + + + + + + { + setVersusTankSearchResults( + go( + versusTankSearchInput.current!.value, + awaitedTankNamesDiacritics, + { key: 'combined', limit: 8 }, + ).map((item) => item.obj.id), + ); + }, 500)} + /> + + + + {(versusTankSearchResults.length > 0 || + versusTankSearchInput.current?.value) && ( + + {versusTankSearchResults.map((id) => ( + + ))} + + {versusTankSearchResults.length === 0 && + (versusTankSearchInput.current + ? versusTankSearchInput.current.value.length > 0 + : false) && ( + + )} + + )} + + + + + + + + Configuration + + + + {antagonist.tank.turrets.map((turret, index) => ( + + { + mutateTankopedia((draft) => { + if (!draft.areTanksAssigned) return; + + draft.antagonist.turret = turret; + draft.antagonist.gun = + turret.guns.at(-1)!; + }); + }} + selected={antagonist.turret.id === turret.id} + tier={turret.tier} + type="turret" + style={{ + margin: -0.5, + borderTopLeftRadius: + index === 0 ? undefined : 0, + borderBottomLeftRadius: + index === 0 ? undefined : 0, + borderTopRightRadius: + index === + antagonist.tank.turrets.length - 1 + ? undefined + : 0, + borderBottomRightRadius: + index === + antagonist.tank.turrets.length - 1 + ? undefined + : 0, + }} + /> + + ))} + + + {antagonist.turret.guns.map((gun, index) => ( + + { + mutateTankopedia((draft) => { + if (!draft.areTanksAssigned) return; + draft.antagonist.gun = gun; + }); + }} + selected={antagonist.gun.id === gun.id} + tier={gun.tier} + type="gun" + style={{ + margin: -0.5, + borderTopLeftRadius: + index === 0 ? undefined : 0, + borderBottomLeftRadius: + index === 0 ? undefined : 0, + borderTopRightRadius: + index === + antagonist.turret.guns.length - 1 + ? undefined + : 0, + borderBottomRightRadius: + index === + antagonist.turret.guns.length - 1 + ? undefined + : 0, + }} + /> + + ))} + + + + + + Properties + +
    +
  • Penetration
  • +
+
+
+
+
+
+
+
+
+ + + + Calibrated + + + + + + + +
+
+ ); +} diff --git a/src/app/tools/tankopedia/[id]/page.tsx b/src/app/tools/tankopedia/[id]/page.tsx index 108fc237a..bb1a7f86f 100644 --- a/src/app/tools/tankopedia/[id]/page.tsx +++ b/src/app/tools/tankopedia/[id]/page.tsx @@ -1,59 +1,55 @@ 'use client'; -import { - ArrowLeftIcon, - CaretRightIcon, - MagnifyingGlassIcon, -} from '@radix-ui/react-icons'; -import { - Button, - Card, - Checkbox, - Dialog, - Flex, - Heading, - Tabs, - Text, - TextField, - Theme, - Tooltip, -} from '@radix-ui/themes'; -import { go } from 'fuzzysort'; -import { debounce } from 'lodash'; +import { ArrowLeftIcon } from '@radix-ui/react-icons'; +import { Button, Flex, Heading, Text, Theme, Tooltip } from '@radix-ui/themes'; import Link from 'next/link'; -import { use, useRef, useState } from 'react'; -import { Vector3 } from 'three'; +import { use, useEffect } from 'react'; import { Flag } from '../../../../components/Flag'; -import { ModuleButtons } from '../../../../components/ModuleButton'; import PageWrapper from '../../../../components/PageWrapper'; import { asset } from '../../../../core/blitzkrieg/asset'; import { TIER_ROMAN_NUMERALS, tankDefinitions, - tankNamesDiacritics, } from '../../../../core/blitzkrieg/tankDefinitions'; -import { useTankopedia } from '../../../../stores/tankopedia'; +import mutateTankopedia, { useTankopedia } from '../../../../stores/tankopedia'; import { TankDisplay } from './components/Model/TankDisplay'; - -const X_AXIS = new Vector3(1, 0, 0); +import { VersusBar } from './components/VersusBar'; export default function Page({ params }: { params: { id: string } }) { const id = parseInt(params.id); const awaitedTankDefinitions = use(tankDefinitions); - const awaitedTankNamesDiacritics = use(tankNamesDiacritics); - const tank = awaitedTankDefinitions[id]; - const [turret, setTurret] = useState(tank.turrets.at(-1)!); - const [gun, setGun] = useState(turret.guns.at(-1)!); - const [crew, setCrew] = useState(100); - const versusTankSearchInput = useRef(null); - const [versusTankSearchResults, setVersusTankSearchResults] = useState< - number[] - >([]); - const [versusTank, setVersusTank] = useState(tank); - const [versusTurret, setVersusTurret] = useState(versusTank.turrets.at(-1)!); - const [versusGun, setVersusGun] = useState(versusTurret.guns.at(-1)!); - const [versusTankTab, setVersusTankTab] = useState('search'); - const mode = useTankopedia((state) => state.mode); + + useEffect(() => { + mutateTankopedia((draft) => { + draft.model.hullYaw = 0; + draft.model.turretYaw = 0; + draft.model.gunPitch = 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.antagonist = draft.protagonist; + }); + + return () => { + mutateTankopedia((draft) => { + draft.areTanksAssigned = false; + }); + }; + }, [id]); + + const protagonist = useTankopedia((state) => { + // goofy ahh typescript discriminator hack + if (!state.areTanksAssigned) return; + return state.protagonist; + }); + + if (!protagonist) return null; return ( @@ -71,254 +67,33 @@ export default function Page({ params }: { params: { id: string } }) { - - {tank.name} + + {protagonist.tank.name} - - - {mode === 'armor' && ( - - - - Versus - - - - - - - - - - - Search - - Configure - - - - - - - - - - { - setVersusTankSearchResults( - go( - versusTankSearchInput.current!.value, - awaitedTankNamesDiacritics, - { key: 'combined', limit: 8 }, - ).map((item) => item.obj.id), - ); - }, 500)} - /> - - - - {(versusTankSearchResults.length > 0 || - versusTankSearchInput.current?.value) && ( - - {versusTankSearchResults.map((id) => ( - - ))} - - {versusTankSearchResults.length === 0 && - (versusTankSearchInput.current - ? versusTankSearchInput.current.value - .length > 0 - : false) && ( - - )} - - )} - - - - - - - - Configuration - - - - {versusTank.turrets.map((turret, index) => ( - - { - setVersusTurret(turret); - setVersusGun(turret.guns.at(-1)!); - }} - selected={versusTurret.id === id} - tier={turret.tier} - type="turret" - style={{ - margin: -0.5, - borderTopLeftRadius: - index === 0 ? undefined : 0, - borderBottomLeftRadius: - index === 0 ? undefined : 0, - borderTopRightRadius: - index === - versusTank.turrets.length - 1 - ? undefined - : 0, - borderBottomRightRadius: - index === - versusTank.turrets.length - 1 - ? undefined - : 0, - }} - /> - - ))} - - - {versusTurret.guns.map((gun, index) => ( - - setVersusGun(gun)} - selected={versusGun.id === id} - tier={gun.tier} - type="gun" - style={{ - margin: -0.5, - borderTopLeftRadius: - index === 0 ? undefined : 0, - borderBottomLeftRadius: - index === 0 ? undefined : 0, - borderTopRightRadius: - index === - versusTurret.guns.length - 1 - ? undefined - : 0, - borderBottomRightRadius: - index === - versusTurret.guns.length - 1 - ? undefined - : 0, - }} - /> - - ))} - - - - - - Properties - -
    -
  • Penetration
  • -
-
-
-
-
-
-
-
-
- - - - Calibrated - - - - - - - -
-
- )} + + - {tank.turrets.map((thisTurret) => { + {protagonist.tank.turrets.map((thisTurret) => { return (