diff --git a/src/app/tools/tankopedia/[id]/components/AntagonistBar.tsx b/src/app/tools/tankopedia/[id]/components/AntagonistBar.tsx index 09bbd553c..819856db7 100644 --- a/src/app/tools/tankopedia/[id]/components/AntagonistBar.tsx +++ b/src/app/tools/tankopedia/[id]/components/AntagonistBar.tsx @@ -17,7 +17,6 @@ import { } from '@radix-ui/themes'; import { go } from 'fuzzysort'; import { debounce } from 'lodash'; -import { useRouter } from 'next/navigation'; import { use, useRef, useState } from 'react'; import { ModuleButton } from '../../../../../components/ModuleButton'; import { SmallTankIcon } from '../../../../../components/SmallTankIcon'; @@ -31,7 +30,6 @@ import mutateTankopedia, { } from '../../../../../stores/tankopedia'; export function AntagonistBar() { - const router = useRouter(); const awaitedTankDefinitions = use(tankDefinitions); const mode = useTankopedia((state) => state.mode); const awaitedTankNamesDiacritics = use(tankNamesDiacritics); diff --git a/src/app/tools/tankopedia/[id]/components/Control.tsx b/src/app/tools/tankopedia/[id]/components/Control.tsx index 433a408d8..1f634cb0b 100644 --- a/src/app/tools/tankopedia/[id]/components/Control.tsx +++ b/src/app/tools/tankopedia/[id]/components/Control.tsx @@ -7,17 +7,25 @@ import { useTankopedia } from '../../../../../stores/tankopedia'; export function Controls() { const camera = useThree((state) => state.camera); const orbitControls = useRef(null); - const enabled = useTankopedia((state) => state.model.controlsEnabled); useEffect(() => { camera.position.set(-5, 4, -20); orbitControls.current?.target.set(0, 1.5, 0); + + const unsubscribe = useTankopedia.subscribe( + (state) => state.model.visual.controlsEnabled, + (enabled) => { + if (orbitControls.current) orbitControls.current.enabled = enabled; + }, + ); + + return unsubscribe; }, []); return ( diff --git a/src/app/tools/tankopedia/[id]/components/Model/TankDisplay.tsx b/src/app/tools/tankopedia/[id]/components/Model/TankDisplay.tsx index 5d0083941..b67e006e8 100644 --- a/src/app/tools/tankopedia/[id]/components/Model/TankDisplay.tsx +++ b/src/app/tools/tankopedia/[id]/components/Model/TankDisplay.tsx @@ -1,6 +1,7 @@ import { Card, Flex, Tabs, Theme } from '@radix-ui/themes'; import { Html } from '@react-three/drei'; import { Canvas } from '@react-three/fiber'; +import { useRouter } from 'next/navigation'; import { Suspense, use, useEffect, useRef, useState } from 'react'; import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits'; import { modelDefinitions } from '../../../../../../core/blitzkrieg/modelDefinitions'; @@ -19,6 +20,7 @@ import { TankArmor } from './components/TankArmor'; import { TankModel } from './components/TankModel'; export function TankDisplay() { + const router = useRouter(); const canvas = useRef(null); const awaitedModelDefinitions = use(modelDefinitions); const canvasWrapper = useRef(null); @@ -64,15 +66,24 @@ export function TankDisplay() { 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, - ); + [draft.model.physical.gunPitch, draft.model.physical.turretYaw] = + applyPitchYawLimits( + draft.model.physical.gunPitch, + draft.model.physical.turretYaw, + gunModelDefinition.pitch, + turretModelDefinition.yaw, + ); }); }, [protagonist?.gun, protagonist?.turret]); + useEffect(() => { + if (location.hash.startsWith('#')) { + mutateTankopedia((draft) => { + draft.mode = location.hash.match(/#(.+)/)![1] as TankopediaMode; + }); + } + }, []); + if (!protagonist) return null; return ( @@ -96,6 +107,8 @@ export function TankDisplay() { { + router.push(`#${mode}`); + mutateTankopedia((draft) => { draft.mode = mode as TankopediaMode; }); diff --git a/src/app/tools/tankopedia/[id]/components/Model/components/Options.tsx b/src/app/tools/tankopedia/[id]/components/Model/components/Options.tsx index d8f760aa8..6a66cb314 100644 --- a/src/app/tools/tankopedia/[id]/components/Model/components/Options.tsx +++ b/src/app/tools/tankopedia/[id]/components/Model/components/Options.tsx @@ -15,8 +15,10 @@ interface OptionsProps { } export function Options({ isFullScreen, canvas }: OptionsProps) { - const showGrid = useTankopedia((state) => state.model.showGrid); - const showSpacedArmor = useTankopedia((state) => state.model.showSpacedArmor); + const showGrid = useTankopedia((state) => state.model.visual.showGrid); + const showSpacedArmor = useTankopedia( + (state) => state.model.visual.showSpacedArmor, + ); return ( { mutateTankopedia((draft) => { - draft.model.showGrid = checked; + draft.model.visual.showGrid = checked; }); }} > @@ -50,7 +52,7 @@ export function Options({ isFullScreen, canvas }: OptionsProps) { checked={showSpacedArmor} onCheckedChange={(checked) => { mutateTankopedia((draft) => { - draft.model.showSpacedArmor = checked; + draft.model.visual.showSpacedArmor = checked; }); }} > diff --git a/src/app/tools/tankopedia/[id]/components/Model/components/TankArmor.tsx b/src/app/tools/tankopedia/[id]/components/Model/components/TankArmor.tsx index 6c90b15d5..27cbcbb20 100644 --- a/src/app/tools/tankopedia/[id]/components/Model/components/TankArmor.tsx +++ b/src/app/tools/tankopedia/[id]/components/Model/components/TankArmor.tsx @@ -13,11 +13,11 @@ import { import { nameToArmorId } from '../../../../../../../core/blitzkrieg/nameToArmorId'; import { resolveArmor } from '../../../../../../../core/blitzkrieg/resolveThickness'; import { useTankopedia } from '../../../../../../../stores/tankopedia'; -import { Lighting } from '../../Lighting'; interface TankArmorProps extends GroupProps {} export function TankArmor({ ...props }: TankArmorProps) { + const mode = useTankopedia((state) => state.mode); const wrapper = useRef(null); const [awaitedModelDefinitions, setAwaitedModelDefinitions] = useState< ModelDefinitions | undefined @@ -26,8 +26,10 @@ export function TankArmor({ ...props }: TankArmorProps) { if (!state.areTanksAssigned) return; return state.protagonist; }); - const showSpacedArmor = useTankopedia((state) => state.model.showSpacedArmor); - const model = useTankopedia((state) => state.model); + const showSpacedArmor = useTankopedia( + (state) => state.model.visual.showSpacedArmor, + ); + const physical = useTankopedia((state) => state.model.physical); useEffect(() => { (async () => { @@ -35,17 +37,6 @@ export function TankArmor({ ...props }: TankArmorProps) { })(); }, []); - useEffect(() => { - const unsubscribe = useTankopedia.subscribe( - (state) => state.model.controlsEnabled, - (controlsEnabled) => { - if (wrapper.current) wrapper.current.visible = controlsEnabled; - }, - ); - - return unsubscribe; - }); - // it's ok to have hooks after this early termination because this will never be true // it's more for typescript to stop throwing a fit if (!protagonist) return null; @@ -79,8 +70,8 @@ export function TankArmor({ ...props }: TankArmorProps) { ).applyAxisAngle(X_AXIS, Math.PI / 2); const turretPosition = new Vector3() .sub(turretOrigin) - .applyAxisAngle(new Vector3(0, 0, 1), model.turretYaw); - const turretRotation = new Euler(0, 0, model.turretYaw); + .applyAxisAngle(new Vector3(0, 0, 1), physical.turretYaw); + const turretRotation = new Euler(0, 0, physical.turretYaw); if (tankModelDefinition.turretRotation) { const pitch = -tankModelDefinition.turretRotation.pitch * (Math.PI / 180); @@ -100,12 +91,11 @@ export function TankArmor({ ...props }: TankArmorProps) { return ( - - {armorNodes.map((node) => { const isHull = node.name.startsWith('hull_'); @@ -187,10 +177,10 @@ export function TankArmor({ ...props }: TankArmorProps) { position={new Vector3() .sub(turretOrigin) .sub(gunOrigin) - .applyAxisAngle(new Vector3(1, 0, 0), model.gunPitch) + .applyAxisAngle(new Vector3(1, 0, 0), physical.gunPitch) .add(turretOrigin) .add(gunOrigin)} - rotation={[model.gunPitch, 0, 0]} + rotation={[physical.gunPitch, 0, 0]} > {armorNodes.map((node) => { const isCurrentGun = node.name.startsWith( 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 cf93c895a..8217d1b74 100644 --- a/src/app/tools/tankopedia/[id]/components/Model/components/TankModel.tsx +++ b/src/app/tools/tankopedia/[id]/components/Model/components/TankModel.tsx @@ -33,7 +33,7 @@ export function TankModel() { if (!state.areTanksAssigned) return; return state.protagonist; }); - const model = useTankopedia((state) => state.model); + const physical = useTankopedia((state) => state.model.physical); const hullContainer = useRef(null); const turretContainer = useRef(null); const gunContainer = useRef(null); @@ -70,8 +70,8 @@ export function TankModel() { ).applyAxisAngle(X_AXIS, Math.PI / 2); const turretPosition = new Vector3() .sub(turretOrigin) - .applyAxisAngle(new Vector3(0, 0, 1), model.turretYaw); - const turretRotation = new Euler(0, 0, model.turretYaw); + .applyAxisAngle(new Vector3(0, 0, 1), physical.turretYaw); + const turretRotation = new Euler(0, 0, physical.turretYaw); if (tankModelDefinition.turretRotation) { const pitch = -tankModelDefinition.turretRotation.pitch * (Math.PI / 180); @@ -90,7 +90,7 @@ export function TankModel() { turretPosition.add(turretOrigin); return ( - + {nodes.map((node) => { const isHull = node.name === 'hull'; const isWheel = node.name.startsWith('chassis_wheel_'); @@ -101,11 +101,11 @@ export function TankModel() { function handlePointerDown(event: ThreeEvent) { event.stopPropagation(); mutateTankopedia((draft) => { - draft.model.controlsEnabled = false; + draft.model.visual.controlsEnabled = false; }); if (isHull) { - draftHullYaw = model.hullYaw; + draftHullYaw = physical.hullYaw; window.addEventListener('pointermove', handlePointerMoveHull); window.addEventListener('pointerup', handlePointerUpHull); @@ -122,8 +122,8 @@ export function TankModel() { } function handlePointerUpHull() { mutateTankopedia((draft) => { - draft.model.controlsEnabled = true; - draft.model.hullYaw = normalizeAnglePI(draftHullYaw); + draft.model.visual.controlsEnabled = true; + draft.model.physical.hullYaw = normalizeAnglePI(draftHullYaw); }); window.removeEventListener('pointermove', handlePointerMoveHull); window.removeEventListener('pointerup', handlePointerUpHull); @@ -143,7 +143,7 @@ export function TankModel() { } function handlePointerUpTrack() { mutateTankopedia((draft) => { - draft.model.controlsEnabled = true; + draft.model.visual.controlsEnabled = true; }); window.removeEventListener('pointermove', handlePointerMoveTrack); window.removeEventListener('pointerup', handlePointerUpTrack); @@ -185,11 +185,11 @@ export function TankModel() { event.stopPropagation(); if (isTurret) { - draftYaw = model.turretYaw; - draftPitch = model.gunPitch; + draftYaw = physical.turretYaw; + draftPitch = physical.gunPitch; mutateTankopedia((draft) => { - draft.model.controlsEnabled = false; + draft.model.visual.controlsEnabled = false; }); window.addEventListener('pointermove', handlePointerMove); window.addEventListener('pointerup', handlePointerUp); @@ -240,9 +240,9 @@ export function TankModel() { } function handlePointerUp() { mutateTankopedia((state) => { - state.model.controlsEnabled = true; - state.model.gunPitch = normalizeAnglePI(draftPitch); - state.model.turretYaw = normalizeAnglePI(draftYaw); + state.model.visual.controlsEnabled = true; + state.model.physical.gunPitch = normalizeAnglePI(draftPitch); + state.model.physical.turretYaw = normalizeAnglePI(draftYaw); }); window.removeEventListener('pointermove', handlePointerMove); window.removeEventListener('pointerup', handlePointerUp); @@ -271,10 +271,10 @@ export function TankModel() { position={new Vector3() .sub(turretOrigin) .sub(gunOrigin) - .applyAxisAngle(new Vector3(1, 0, 0), model.gunPitch) + .applyAxisAngle(new Vector3(1, 0, 0), physical.gunPitch) .add(turretOrigin) .add(gunOrigin)} - rotation={[model.gunPitch, 0, 0]} + rotation={[physical.gunPitch, 0, 0]} ref={gunContainer} > {nodes.map((node) => { @@ -294,10 +294,10 @@ export function TankModel() { event.stopPropagation(); mutateTankopedia((draft) => { - draft.model.controlsEnabled = false; + draft.model.visual.controlsEnabled = false; }); - draftPitch = model.gunPitch; - draftYaw = model.turretYaw; + draftPitch = physical.gunPitch; + draftYaw = physical.turretYaw; window.addEventListener('pointermove', handlePointerMove); window.addEventListener('pointerup', handlePointerUp); } @@ -346,9 +346,9 @@ export function TankModel() { } function handlePointerUp() { mutateTankopedia((state) => { - state.model.controlsEnabled = true; - state.model.gunPitch = normalizeAnglePI(draftPitch); - state.model.turretYaw = normalizeAnglePI(draftYaw); + state.model.visual.controlsEnabled = true; + state.model.physical.gunPitch = normalizeAnglePI(draftPitch); + state.model.physical.turretYaw = normalizeAnglePI(draftYaw); }); window.removeEventListener('pointermove', handlePointerMove); window.removeEventListener('pointerup', handlePointerUp); diff --git a/src/app/tools/tankopedia/[id]/components/RotationInputs.tsx b/src/app/tools/tankopedia/[id]/components/RotationInputs.tsx index db0b06811..71f2257db 100644 --- a/src/app/tools/tankopedia/[id]/components/RotationInputs.tsx +++ b/src/app/tools/tankopedia/[id]/components/RotationInputs.tsx @@ -8,7 +8,7 @@ import mutateTankopedia, { } from '../../../../../stores/tankopedia'; export function RotationInputs() { - const model = useTankopedia((state) => state.model); + const physical = useTankopedia((state) => state.model.physical); const awaitedModelDefinitions = use(modelDefinitions); const turretYawInput = useRef(null); const hullYawInput = useRef(null); @@ -20,19 +20,19 @@ export function RotationInputs() { useEffect(() => { hullYawInput.current!.value = `${-Math.round( - model.hullYaw * (180 / Math.PI), + physical.hullYaw * (180 / Math.PI), )}`; - }, [model.hullYaw]); + }, [physical.hullYaw]); useEffect(() => { turretYawInput.current!.value = `${-Math.round( - model.turretYaw * (180 / Math.PI), + physical.turretYaw * (180 / Math.PI), )}`; - }, [model.turretYaw]); + }, [physical.turretYaw]); useEffect(() => { gunPitchInput.current!.value = `${-Math.round( - model.gunPitch * (180 / Math.PI), + physical.gunPitch * (180 / Math.PI), )}`; - }, [model.gunPitch]); + }, [physical.gunPitch]); if (!protagonist) return null; @@ -65,13 +65,13 @@ export function RotationInputs() { > Hull { const normalized = normalizeAnglePI( -Number(hullYawInput.current?.value) * (Math.PI / 180), ); mutateTankopedia((state) => { - state.model.hullYaw = normalized; + state.model.physical.hullYaw = normalized; }); hullYawInput.current!.value = `${Math.round(normalized)}`; }} @@ -94,17 +94,17 @@ export function RotationInputs() { > Turret { const [pitch, yaw] = applyPitchYawLimits( - model.gunPitch, + physical.gunPitch, -Number(turretYawInput.current?.value) * (Math.PI / 180), gunModelDefinition.pitch, turretModelDefinition.yaw, ); mutateTankopedia((state) => { - state.model.gunPitch = pitch; - state.model.turretYaw = yaw; + state.model.physical.gunPitch = pitch; + state.model.physical.turretYaw = yaw; }); turretYawInput.current!.value = `${Math.round( yaw * (180 / Math.PI), @@ -129,17 +129,17 @@ export function RotationInputs() { > Gun { const [pitch, yaw] = applyPitchYawLimits( -Number(gunPitchInput.current?.value) * (Math.PI / 180), - model.turretYaw, + physical.turretYaw, gunModelDefinition.pitch, turretModelDefinition.yaw, ); mutateTankopedia((state) => { - state.model.gunPitch = pitch; - state.model.turretYaw = yaw; + state.model.physical.gunPitch = pitch; + state.model.physical.turretYaw = yaw; }); gunPitchInput.current!.value = `${Math.round( pitch * (180 / Math.PI), diff --git a/src/app/tools/tankopedia/[id]/components/SceneProps.tsx b/src/app/tools/tankopedia/[id]/components/SceneProps.tsx index f27f34c4c..dc9f36c03 100644 --- a/src/app/tools/tankopedia/[id]/components/SceneProps.tsx +++ b/src/app/tools/tankopedia/[id]/components/SceneProps.tsx @@ -3,7 +3,7 @@ import InfiniteGridHelper from '../../../../../components/InfiniteGridHelper'; import { useTankopedia } from '../../../../../stores/tankopedia'; export function SceneProps() { - const showGrid = useTankopedia((state) => state.model.showGrid); + const showGrid = useTankopedia((state) => state.model.visual.showGrid); if (!showGrid) return null; diff --git a/src/app/tools/tankopedia/[id]/page.tsx b/src/app/tools/tankopedia/[id]/page.tsx index 8471f5140..975393200 100644 --- a/src/app/tools/tankopedia/[id]/page.tsx +++ b/src/app/tools/tankopedia/[id]/page.tsx @@ -18,9 +18,9 @@ export default function Page({ params }: { params: { id: string } }) { useEffect(() => { mutateTankopedia((draft) => { - draft.model.hullYaw = 0; - draft.model.turretYaw = 0; - draft.model.gunPitch = 0; + draft.model.physical.hullYaw = 0; + draft.model.physical.turretYaw = 0; + draft.model.physical.gunPitch = 0; draft.mode = 'model'; draft.areTanksAssigned = true; @@ -67,7 +67,6 @@ export default function Page({ params }: { params: { id: string } }) { }, [id]); const protagonist = useTankopedia((state) => { - // goofy ahh typescript discriminator hack if (!state.areTanksAssigned) return; return state.protagonist; }); diff --git a/src/app/tools/tankopedia/components/Options/index.tsx b/src/app/tools/tankopedia/components/Options/index.tsx index e63d4167e..7f07edb89 100644 --- a/src/app/tools/tankopedia/components/Options/index.tsx +++ b/src/app/tools/tankopedia/components/Options/index.tsx @@ -24,7 +24,7 @@ import * as styles from './index.css'; export function Options() { const nations = use(NATIONS); - const tankopediaState = useTankopedia(); + const filters = useTankopedia((state) => state.filters); return ( @@ -54,7 +54,7 @@ export function Options() { mutateTankopedia((draft) => { draft.filters.test = value as TankopediaTestTankDisplay; @@ -75,11 +75,7 @@ export function Options() { {TREE_TYPES.map((type, index) => (