feat: optimized state handling x2

This commit is contained in:
TrèsAbhi committed 2024-01-13 16:48:20 -06:00
1 parent 230b2df5fd
commit 586cd84349
13 files changed
+138 -149

No files matched your search

@@ -17,7 +17,6 @@ import {
} from '@radix-ui/themes'; } from '@radix-ui/themes';
import { go } from 'fuzzysort'; import { go } from 'fuzzysort';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { useRouter } from 'next/navigation';
import { use, useRef, useState } from 'react'; import { use, useRef, useState } from 'react';
import { ModuleButton } from '../../../../../components/ModuleButton'; import { ModuleButton } from '../../../../../components/ModuleButton';
import { SmallTankIcon } from '../../../../../components/SmallTankIcon'; import { SmallTankIcon } from '../../../../../components/SmallTankIcon';
@@ -31,7 +30,6 @@ import mutateTankopedia, {
} from '../../../../../stores/tankopedia'; } from '../../../../../stores/tankopedia';
export function AntagonistBar() { export function AntagonistBar() {
const router = useRouter();
const awaitedTankDefinitions = use(tankDefinitions); const awaitedTankDefinitions = use(tankDefinitions);
const mode = useTankopedia((state) => state.mode); const mode = useTankopedia((state) => state.mode);
const awaitedTankNamesDiacritics = use(tankNamesDiacritics); const awaitedTankNamesDiacritics = use(tankNamesDiacritics);
@@ -7,17 +7,25 @@ import { useTankopedia } from '../../../../../stores/tankopedia';
export function Controls() { export function Controls() {
const camera = useThree((state) => state.camera); const camera = useThree((state) => state.camera);
const orbitControls = useRef<OrbitControlsClass>(null); const orbitControls = useRef<OrbitControlsClass>(null);
const enabled = useTankopedia((state) => state.model.controlsEnabled);
useEffect(() => { useEffect(() => {
camera.position.set(-5, 4, -20); camera.position.set(-5, 4, -20);
orbitControls.current?.target.set(0, 1.5, 0); 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 ( return (
<OrbitControls <OrbitControls
ref={orbitControls} ref={orbitControls}
enabled={enabled} enabled={useTankopedia.getState().model.visual.controlsEnabled}
rotateSpeed={0.25} rotateSpeed={0.25}
enableDamping={false} enableDamping={false}
/> />
@@ -1,6 +1,7 @@
import { Card, Flex, Tabs, Theme } from '@radix-ui/themes'; import { Card, Flex, Tabs, Theme } from '@radix-ui/themes';
import { Html } from '@react-three/drei'; import { Html } from '@react-three/drei';
import { Canvas } from '@react-three/fiber'; import { Canvas } from '@react-three/fiber';
import { useRouter } from 'next/navigation';
import { Suspense, use, useEffect, useRef, useState } from 'react'; import { Suspense, use, useEffect, useRef, useState } from 'react';
import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits'; import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits';
import { modelDefinitions } from '../../../../../../core/blitzkrieg/modelDefinitions'; import { modelDefinitions } from '../../../../../../core/blitzkrieg/modelDefinitions';
@@ -19,6 +20,7 @@ import { TankArmor } from './components/TankArmor';
import { TankModel } from './components/TankModel'; import { TankModel } from './components/TankModel';
export function TankDisplay() { export function TankDisplay() {
const router = useRouter();
const canvas = useRef<HTMLCanvasElement>(null); const canvas = useRef<HTMLCanvasElement>(null);
const awaitedModelDefinitions = use(modelDefinitions); const awaitedModelDefinitions = use(modelDefinitions);
const canvasWrapper = useRef<HTMLDivElement>(null); const canvasWrapper = useRef<HTMLDivElement>(null);
@@ -64,15 +66,24 @@ export function TankDisplay() {
const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id]; const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id];
mutateTankopedia((draft) => { mutateTankopedia((draft) => {
[draft.model.gunPitch, draft.model.turretYaw] = applyPitchYawLimits( [draft.model.physical.gunPitch, draft.model.physical.turretYaw] =
draft.model.gunPitch, applyPitchYawLimits(
draft.model.turretYaw, draft.model.physical.gunPitch,
gunModelDefinition.pitch, draft.model.physical.turretYaw,
turretModelDefinition.yaw, gunModelDefinition.pitch,
); turretModelDefinition.yaw,
);
}); });
}, [protagonist?.gun, protagonist?.turret]); }, [protagonist?.gun, protagonist?.turret]);
useEffect(() => {
if (location.hash.startsWith('#')) {
mutateTankopedia((draft) => {
draft.mode = location.hash.match(/#(.+)/)![1] as TankopediaMode;
});
}
}, []);
if (!protagonist) return null; if (!protagonist) return null;
return ( return (
@@ -96,6 +107,8 @@ export function TankDisplay() {
<Tabs.Root <Tabs.Root
value={mode} value={mode}
onValueChange={(mode) => { onValueChange={(mode) => {
router.push(`#${mode}`);
mutateTankopedia((draft) => { mutateTankopedia((draft) => {
draft.mode = mode as TankopediaMode; draft.mode = mode as TankopediaMode;
}); });
@@ -15,8 +15,10 @@ interface OptionsProps {
} }
export function Options({ isFullScreen, canvas }: OptionsProps) { export function Options({ isFullScreen, canvas }: OptionsProps) {
const showGrid = useTankopedia((state) => state.model.showGrid); const showGrid = useTankopedia((state) => state.model.visual.showGrid);
const showSpacedArmor = useTankopedia((state) => state.model.showSpacedArmor); const showSpacedArmor = useTankopedia(
(state) => state.model.visual.showSpacedArmor,
);
return ( return (
<Flex <Flex
@@ -39,7 +41,7 @@ export function Options({ isFullScreen, canvas }: OptionsProps) {
checked={showGrid} checked={showGrid}
onCheckedChange={(checked) => { onCheckedChange={(checked) => {
mutateTankopedia((draft) => { mutateTankopedia((draft) => {
draft.model.showGrid = checked; draft.model.visual.showGrid = checked;
}); });
}} }}
> >
@@ -50,7 +52,7 @@ export function Options({ isFullScreen, canvas }: OptionsProps) {
checked={showSpacedArmor} checked={showSpacedArmor}
onCheckedChange={(checked) => { onCheckedChange={(checked) => {
mutateTankopedia((draft) => { mutateTankopedia((draft) => {
draft.model.showSpacedArmor = checked; draft.model.visual.showSpacedArmor = checked;
}); });
}} }}
> >
@@ -13,11 +13,11 @@ import {
import { nameToArmorId } from '../../../../../../../core/blitzkrieg/nameToArmorId'; import { nameToArmorId } from '../../../../../../../core/blitzkrieg/nameToArmorId';
import { resolveArmor } from '../../../../../../../core/blitzkrieg/resolveThickness'; import { resolveArmor } from '../../../../../../../core/blitzkrieg/resolveThickness';
import { useTankopedia } from '../../../../../../../stores/tankopedia'; import { useTankopedia } from '../../../../../../../stores/tankopedia';
import { Lighting } from '../../Lighting';
interface TankArmorProps extends GroupProps {} interface TankArmorProps extends GroupProps {}
export function TankArmor({ ...props }: TankArmorProps) { export function TankArmor({ ...props }: TankArmorProps) {
const mode = useTankopedia((state) => state.mode);
const wrapper = useRef<Group>(null); const wrapper = useRef<Group>(null);
const [awaitedModelDefinitions, setAwaitedModelDefinitions] = useState< const [awaitedModelDefinitions, setAwaitedModelDefinitions] = useState<
ModelDefinitions | undefined ModelDefinitions | undefined
@@ -26,8 +26,10 @@ export function TankArmor({ ...props }: TankArmorProps) {
if (!state.areTanksAssigned) return; if (!state.areTanksAssigned) return;
return state.protagonist; return state.protagonist;
}); });
const showSpacedArmor = useTankopedia((state) => state.model.showSpacedArmor); const showSpacedArmor = useTankopedia(
const model = useTankopedia((state) => state.model); (state) => state.model.visual.showSpacedArmor,
);
const physical = useTankopedia((state) => state.model.physical);
useEffect(() => { useEffect(() => {
(async () => { (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 ok to have hooks after this early termination because this will never be true
// it's more for typescript to stop throwing a fit // it's more for typescript to stop throwing a fit
if (!protagonist) return null; if (!protagonist) return null;
@@ -79,8 +70,8 @@ export function TankArmor({ ...props }: TankArmorProps) {
).applyAxisAngle(X_AXIS, Math.PI / 2); ).applyAxisAngle(X_AXIS, Math.PI / 2);
const turretPosition = new Vector3() const turretPosition = new Vector3()
.sub(turretOrigin) .sub(turretOrigin)
.applyAxisAngle(new Vector3(0, 0, 1), model.turretYaw); .applyAxisAngle(new Vector3(0, 0, 1), physical.turretYaw);
const turretRotation = new Euler(0, 0, model.turretYaw); const turretRotation = new Euler(0, 0, physical.turretYaw);
if (tankModelDefinition.turretRotation) { if (tankModelDefinition.turretRotation) {
const pitch = -tankModelDefinition.turretRotation.pitch * (Math.PI / 180); const pitch = -tankModelDefinition.turretRotation.pitch * (Math.PI / 180);
@@ -100,12 +91,11 @@ export function TankArmor({ ...props }: TankArmorProps) {
return ( return (
<HeadsUpDisplay> <HeadsUpDisplay>
<Lighting />
<group <group
{...props} {...props}
visible={mode === 'armor'}
ref={wrapper} ref={wrapper}
rotation={[-Math.PI / 2, 0, model.hullYaw]} rotation={[-Math.PI / 2, 0, physical.hullYaw]}
> >
{armorNodes.map((node) => { {armorNodes.map((node) => {
const isHull = node.name.startsWith('hull_'); const isHull = node.name.startsWith('hull_');
@@ -187,10 +177,10 @@ export function TankArmor({ ...props }: TankArmorProps) {
position={new Vector3() position={new Vector3()
.sub(turretOrigin) .sub(turretOrigin)
.sub(gunOrigin) .sub(gunOrigin)
.applyAxisAngle(new Vector3(1, 0, 0), model.gunPitch) .applyAxisAngle(new Vector3(1, 0, 0), physical.gunPitch)
.add(turretOrigin) .add(turretOrigin)
.add(gunOrigin)} .add(gunOrigin)}
rotation={[model.gunPitch, 0, 0]} rotation={[physical.gunPitch, 0, 0]}
> >
{armorNodes.map((node) => { {armorNodes.map((node) => {
const isCurrentGun = node.name.startsWith( const isCurrentGun = node.name.startsWith(
@@ -33,7 +33,7 @@ export function TankModel() {
if (!state.areTanksAssigned) return; if (!state.areTanksAssigned) return;
return state.protagonist; return state.protagonist;
}); });
const model = useTankopedia((state) => state.model); const physical = useTankopedia((state) => state.model.physical);
const hullContainer = useRef<Group>(null); const hullContainer = useRef<Group>(null);
const turretContainer = useRef<Group>(null); const turretContainer = useRef<Group>(null);
const gunContainer = useRef<Group>(null); const gunContainer = useRef<Group>(null);
@@ -70,8 +70,8 @@ export function TankModel() {
).applyAxisAngle(X_AXIS, Math.PI / 2); ).applyAxisAngle(X_AXIS, Math.PI / 2);
const turretPosition = new Vector3() const turretPosition = new Vector3()
.sub(turretOrigin) .sub(turretOrigin)
.applyAxisAngle(new Vector3(0, 0, 1), model.turretYaw); .applyAxisAngle(new Vector3(0, 0, 1), physical.turretYaw);
const turretRotation = new Euler(0, 0, model.turretYaw); const turretRotation = new Euler(0, 0, physical.turretYaw);
if (tankModelDefinition.turretRotation) { if (tankModelDefinition.turretRotation) {
const pitch = -tankModelDefinition.turretRotation.pitch * (Math.PI / 180); const pitch = -tankModelDefinition.turretRotation.pitch * (Math.PI / 180);
@@ -90,7 +90,7 @@ export function TankModel() {
turretPosition.add(turretOrigin); turretPosition.add(turretOrigin);
return ( return (
<group ref={hullContainer} rotation={[-Math.PI / 2, 0, model.hullYaw]}> <group ref={hullContainer} rotation={[-Math.PI / 2, 0, physical.hullYaw]}>
{nodes.map((node) => { {nodes.map((node) => {
const isHull = node.name === 'hull'; const isHull = node.name === 'hull';
const isWheel = node.name.startsWith('chassis_wheel_'); const isWheel = node.name.startsWith('chassis_wheel_');
@@ -101,11 +101,11 @@ export function TankModel() {
function handlePointerDown(event: ThreeEvent<PointerEvent>) { function handlePointerDown(event: ThreeEvent<PointerEvent>) {
event.stopPropagation(); event.stopPropagation();
mutateTankopedia((draft) => { mutateTankopedia((draft) => {
draft.model.controlsEnabled = false; draft.model.visual.controlsEnabled = false;
}); });
if (isHull) { if (isHull) {
draftHullYaw = model.hullYaw; draftHullYaw = physical.hullYaw;
window.addEventListener('pointermove', handlePointerMoveHull); window.addEventListener('pointermove', handlePointerMoveHull);
window.addEventListener('pointerup', handlePointerUpHull); window.addEventListener('pointerup', handlePointerUpHull);
@@ -122,8 +122,8 @@ export function TankModel() {
} }
function handlePointerUpHull() { function handlePointerUpHull() {
mutateTankopedia((draft) => { mutateTankopedia((draft) => {
draft.model.controlsEnabled = true; draft.model.visual.controlsEnabled = true;
draft.model.hullYaw = normalizeAnglePI(draftHullYaw); draft.model.physical.hullYaw = normalizeAnglePI(draftHullYaw);
}); });
window.removeEventListener('pointermove', handlePointerMoveHull); window.removeEventListener('pointermove', handlePointerMoveHull);
window.removeEventListener('pointerup', handlePointerUpHull); window.removeEventListener('pointerup', handlePointerUpHull);
@@ -143,7 +143,7 @@ export function TankModel() {
} }
function handlePointerUpTrack() { function handlePointerUpTrack() {
mutateTankopedia((draft) => { mutateTankopedia((draft) => {
draft.model.controlsEnabled = true; draft.model.visual.controlsEnabled = true;
}); });
window.removeEventListener('pointermove', handlePointerMoveTrack); window.removeEventListener('pointermove', handlePointerMoveTrack);
window.removeEventListener('pointerup', handlePointerUpTrack); window.removeEventListener('pointerup', handlePointerUpTrack);
@@ -185,11 +185,11 @@ export function TankModel() {
event.stopPropagation(); event.stopPropagation();
if (isTurret) { if (isTurret) {
draftYaw = model.turretYaw; draftYaw = physical.turretYaw;
draftPitch = model.gunPitch; draftPitch = physical.gunPitch;
mutateTankopedia((draft) => { mutateTankopedia((draft) => {
draft.model.controlsEnabled = false; draft.model.visual.controlsEnabled = false;
}); });
window.addEventListener('pointermove', handlePointerMove); window.addEventListener('pointermove', handlePointerMove);
window.addEventListener('pointerup', handlePointerUp); window.addEventListener('pointerup', handlePointerUp);
@@ -240,9 +240,9 @@ export function TankModel() {
} }
function handlePointerUp() { function handlePointerUp() {
mutateTankopedia((state) => { mutateTankopedia((state) => {
state.model.controlsEnabled = true; state.model.visual.controlsEnabled = true;
state.model.gunPitch = normalizeAnglePI(draftPitch); state.model.physical.gunPitch = normalizeAnglePI(draftPitch);
state.model.turretYaw = normalizeAnglePI(draftYaw); state.model.physical.turretYaw = normalizeAnglePI(draftYaw);
}); });
window.removeEventListener('pointermove', handlePointerMove); window.removeEventListener('pointermove', handlePointerMove);
window.removeEventListener('pointerup', handlePointerUp); window.removeEventListener('pointerup', handlePointerUp);
@@ -271,10 +271,10 @@ export function TankModel() {
position={new Vector3() position={new Vector3()
.sub(turretOrigin) .sub(turretOrigin)
.sub(gunOrigin) .sub(gunOrigin)
.applyAxisAngle(new Vector3(1, 0, 0), model.gunPitch) .applyAxisAngle(new Vector3(1, 0, 0), physical.gunPitch)
.add(turretOrigin) .add(turretOrigin)
.add(gunOrigin)} .add(gunOrigin)}
rotation={[model.gunPitch, 0, 0]} rotation={[physical.gunPitch, 0, 0]}
ref={gunContainer} ref={gunContainer}
> >
{nodes.map((node) => { {nodes.map((node) => {
@@ -294,10 +294,10 @@ export function TankModel() {
event.stopPropagation(); event.stopPropagation();
mutateTankopedia((draft) => { mutateTankopedia((draft) => {
draft.model.controlsEnabled = false; draft.model.visual.controlsEnabled = false;
}); });
draftPitch = model.gunPitch; draftPitch = physical.gunPitch;
draftYaw = model.turretYaw; draftYaw = physical.turretYaw;
window.addEventListener('pointermove', handlePointerMove); window.addEventListener('pointermove', handlePointerMove);
window.addEventListener('pointerup', handlePointerUp); window.addEventListener('pointerup', handlePointerUp);
} }
@@ -346,9 +346,9 @@ export function TankModel() {
} }
function handlePointerUp() { function handlePointerUp() {
mutateTankopedia((state) => { mutateTankopedia((state) => {
state.model.controlsEnabled = true; state.model.visual.controlsEnabled = true;
state.model.gunPitch = normalizeAnglePI(draftPitch); state.model.physical.gunPitch = normalizeAnglePI(draftPitch);
state.model.turretYaw = normalizeAnglePI(draftYaw); state.model.physical.turretYaw = normalizeAnglePI(draftYaw);
}); });
window.removeEventListener('pointermove', handlePointerMove); window.removeEventListener('pointermove', handlePointerMove);
window.removeEventListener('pointerup', handlePointerUp); window.removeEventListener('pointerup', handlePointerUp);
@@ -8,7 +8,7 @@ import mutateTankopedia, {
} from '../../../../../stores/tankopedia'; } from '../../../../../stores/tankopedia';
export function RotationInputs() { export function RotationInputs() {
const model = useTankopedia((state) => state.model); const physical = useTankopedia((state) => state.model.physical);
const awaitedModelDefinitions = use(modelDefinitions); const awaitedModelDefinitions = use(modelDefinitions);
const turretYawInput = useRef<HTMLInputElement>(null); const turretYawInput = useRef<HTMLInputElement>(null);
const hullYawInput = useRef<HTMLInputElement>(null); const hullYawInput = useRef<HTMLInputElement>(null);
@@ -20,19 +20,19 @@ export function RotationInputs() {
useEffect(() => { useEffect(() => {
hullYawInput.current!.value = `${-Math.round( hullYawInput.current!.value = `${-Math.round(
model.hullYaw * (180 / Math.PI), physical.hullYaw * (180 / Math.PI),
)}`; )}`;
}, [model.hullYaw]); }, [physical.hullYaw]);
useEffect(() => { useEffect(() => {
turretYawInput.current!.value = `${-Math.round( turretYawInput.current!.value = `${-Math.round(
model.turretYaw * (180 / Math.PI), physical.turretYaw * (180 / Math.PI),
)}`; )}`;
}, [model.turretYaw]); }, [physical.turretYaw]);
useEffect(() => { useEffect(() => {
gunPitchInput.current!.value = `${-Math.round( gunPitchInput.current!.value = `${-Math.round(
model.gunPitch * (180 / Math.PI), physical.gunPitch * (180 / Math.PI),
)}`; )}`;
}, [model.gunPitch]); }, [physical.gunPitch]);
if (!protagonist) return null; if (!protagonist) return null;
@@ -65,13 +65,13 @@ export function RotationInputs() {
> >
<TextField.Slot>Hull</TextField.Slot> <TextField.Slot>Hull</TextField.Slot>
<TextField.Input <TextField.Input
defaultValue={Math.round(model.hullYaw * (180 / Math.PI))} defaultValue={Math.round(physical.hullYaw * (180 / Math.PI))}
onBlur={() => { onBlur={() => {
const normalized = normalizeAnglePI( const normalized = normalizeAnglePI(
-Number(hullYawInput.current?.value) * (Math.PI / 180), -Number(hullYawInput.current?.value) * (Math.PI / 180),
); );
mutateTankopedia((state) => { mutateTankopedia((state) => {
state.model.hullYaw = normalized; state.model.physical.hullYaw = normalized;
}); });
hullYawInput.current!.value = `${Math.round(normalized)}`; hullYawInput.current!.value = `${Math.round(normalized)}`;
}} }}
@@ -94,17 +94,17 @@ export function RotationInputs() {
> >
<TextField.Slot>Turret</TextField.Slot> <TextField.Slot>Turret</TextField.Slot>
<TextField.Input <TextField.Input
defaultValue={Math.round(model.turretYaw * (180 / Math.PI))} defaultValue={Math.round(physical.turretYaw * (180 / Math.PI))}
onBlur={() => { onBlur={() => {
const [pitch, yaw] = applyPitchYawLimits( const [pitch, yaw] = applyPitchYawLimits(
model.gunPitch, physical.gunPitch,
-Number(turretYawInput.current?.value) * (Math.PI / 180), -Number(turretYawInput.current?.value) * (Math.PI / 180),
gunModelDefinition.pitch, gunModelDefinition.pitch,
turretModelDefinition.yaw, turretModelDefinition.yaw,
); );
mutateTankopedia((state) => { mutateTankopedia((state) => {
state.model.gunPitch = pitch; state.model.physical.gunPitch = pitch;
state.model.turretYaw = yaw; state.model.physical.turretYaw = yaw;
}); });
turretYawInput.current!.value = `${Math.round( turretYawInput.current!.value = `${Math.round(
yaw * (180 / Math.PI), yaw * (180 / Math.PI),
@@ -129,17 +129,17 @@ export function RotationInputs() {
> >
<TextField.Slot>Gun</TextField.Slot> <TextField.Slot>Gun</TextField.Slot>
<TextField.Input <TextField.Input
defaultValue={-Math.round(model.gunPitch * (180 / Math.PI))} defaultValue={-Math.round(physical.gunPitch * (180 / Math.PI))}
onBlur={() => { onBlur={() => {
const [pitch, yaw] = applyPitchYawLimits( const [pitch, yaw] = applyPitchYawLimits(
-Number(gunPitchInput.current?.value) * (Math.PI / 180), -Number(gunPitchInput.current?.value) * (Math.PI / 180),
model.turretYaw, physical.turretYaw,
gunModelDefinition.pitch, gunModelDefinition.pitch,
turretModelDefinition.yaw, turretModelDefinition.yaw,
); );
mutateTankopedia((state) => { mutateTankopedia((state) => {
state.model.gunPitch = pitch; state.model.physical.gunPitch = pitch;
state.model.turretYaw = yaw; state.model.physical.turretYaw = yaw;
}); });
gunPitchInput.current!.value = `${Math.round( gunPitchInput.current!.value = `${Math.round(
pitch * (180 / Math.PI), pitch * (180 / Math.PI),
@@ -3,7 +3,7 @@ import InfiniteGridHelper from '../../../../../components/InfiniteGridHelper';
import { useTankopedia } from '../../../../../stores/tankopedia'; import { useTankopedia } from '../../../../../stores/tankopedia';
export function SceneProps() { export function SceneProps() {
const showGrid = useTankopedia((state) => state.model.showGrid); const showGrid = useTankopedia((state) => state.model.visual.showGrid);
if (!showGrid) return null; if (!showGrid) return null;
+3 -4
View File
@@ -18,9 +18,9 @@ export default function Page({ params }: { params: { id: string } }) {
useEffect(() => { useEffect(() => {
mutateTankopedia((draft) => { mutateTankopedia((draft) => {
draft.model.hullYaw = 0; draft.model.physical.hullYaw = 0;
draft.model.turretYaw = 0; draft.model.physical.turretYaw = 0;
draft.model.gunPitch = 0; draft.model.physical.gunPitch = 0;
draft.mode = 'model'; draft.mode = 'model';
draft.areTanksAssigned = true; draft.areTanksAssigned = true;
@@ -67,7 +67,6 @@ export default function Page({ params }: { params: { id: string } }) {
}, [id]); }, [id]);
const protagonist = useTankopedia((state) => { const protagonist = useTankopedia((state) => {
// goofy ahh typescript discriminator hack
if (!state.areTanksAssigned) return; if (!state.areTanksAssigned) return;
return state.protagonist; return state.protagonist;
}); });
@@ -24,7 +24,7 @@ import * as styles from './index.css';
export function Options() { export function Options() {
const nations = use(NATIONS); const nations = use(NATIONS);
const tankopediaState = useTankopedia(); const filters = useTankopedia((state) => state.filters);
return ( return (
<Flex justify="center" direction="column" gap="2"> <Flex justify="center" direction="column" gap="2">
@@ -54,7 +54,7 @@ export function Options() {
</DropdownMenu.Trigger> </DropdownMenu.Trigger>
<DropdownMenu.Content> <DropdownMenu.Content>
<DropdownMenu.RadioGroup <DropdownMenu.RadioGroup
value={tankopediaState.filters.test} value={filters.test}
onValueChange={(value) => onValueChange={(value) =>
mutateTankopedia((draft) => { mutateTankopedia((draft) => {
draft.filters.test = value as TankopediaTestTankDisplay; draft.filters.test = value as TankopediaTestTankDisplay;
@@ -75,11 +75,7 @@ export function Options() {
{TREE_TYPES.map((type, index) => ( {TREE_TYPES.map((type, index) => (
<Button <Button
key={type} key={type}
variant={ variant={filters.treeTypes.includes(type) ? 'solid' : 'soft'}
tankopediaState.filters.treeTypes.includes(type)
? 'solid'
: 'soft'
}
style={{ style={{
flex: 1, flex: 1,
margin: -0.5, margin: -0.5,
@@ -113,9 +109,7 @@ export function Options() {
{[...TANK_TYPES].reverse().map((type, index) => ( {[...TANK_TYPES].reverse().map((type, index) => (
<Button <Button
key={type} key={type}
variant={ variant={filters.types.includes(type) ? 'solid' : 'soft'}
tankopediaState.filters.types.includes(type) ? 'solid' : 'soft'
}
style={{ style={{
flex: 1, flex: 1,
margin: -0.5, margin: -0.5,
@@ -157,11 +151,7 @@ export function Options() {
.map((tier, index) => ( .map((tier, index) => (
<Button <Button
key={tier} key={tier}
variant={ variant={filters.tiers.includes(tier) ? 'solid' : 'soft'}
tankopediaState.filters.tiers.includes(tier)
? 'solid'
: 'soft'
}
style={{ style={{
flex: 1, flex: 1,
margin: -0.5, margin: -0.5,
@@ -196,11 +186,7 @@ export function Options() {
.map((tier, index) => ( .map((tier, index) => (
<Button <Button
key={tier} key={tier}
variant={ variant={filters.tiers.includes(tier) ? 'solid' : 'soft'}
tankopediaState.filters.tiers.includes(tier)
? 'solid'
: 'soft'
}
style={{ style={{
flex: 1, flex: 1,
margin: -0.5, margin: -0.5,
@@ -238,11 +224,7 @@ export function Options() {
{nations.slice(0, 5).map((nation, index) => ( {nations.slice(0, 5).map((nation, index) => (
<Button <Button
key={nation} key={nation}
variant={ variant={filters.nations.includes(nation) ? 'solid' : 'soft'}
tankopediaState.filters.nations.includes(nation)
? 'solid'
: 'soft'
}
style={{ style={{
flex: 1, flex: 1,
margin: -0.5, margin: -0.5,
@@ -276,11 +258,7 @@ export function Options() {
{nations.slice(5, 10).map((nation, index) => ( {nations.slice(5, 10).map((nation, index) => (
<Button <Button
key={nation} key={nation}
variant={ variant={filters.nations.includes(nation) ? 'solid' : 'soft'}
tankopediaState.filters.nations.includes(nation)
? 'solid'
: 'soft'
}
style={{ style={{
flex: 1, flex: 1,
margin: -0.5, margin: -0.5,
@@ -318,9 +296,7 @@ export function Options() {
{[...TIERS].reverse().map((tier, index) => ( {[...TIERS].reverse().map((tier, index) => (
<Button <Button
key={tier} key={tier}
variant={ variant={filters.tiers.includes(tier) ? 'solid' : 'soft'}
tankopediaState.filters.tiers.includes(tier) ? 'solid' : 'soft'
}
style={{ style={{
flex: 1, flex: 1,
margin: -0.5, margin: -0.5,
@@ -351,11 +327,7 @@ export function Options() {
{nations.map((nation, index) => ( {nations.map((nation, index) => (
<Button <Button
key={nation} key={nation}
variant={ variant={filters.nations.includes(nation) ? 'solid' : 'soft'}
tankopediaState.filters.nations.includes(nation)
? 'solid'
: 'soft'
}
style={{ style={{
flex: 1, flex: 1,
margin: -0.5, margin: -0.5,
+18 -17
View File
@@ -42,7 +42,8 @@ import * as styles from './page.css';
const TANKS_PER_PAGE = 3 * 2 ** 3; const TANKS_PER_PAGE = 3 * 2 ** 3;
export default function Page() { export default function Page() {
const tankopediaState = useTankopedia(); const filters = useTankopedia((state) => state.filters);
const sort = useTankopedia((state) => state.sort);
const awaitedTanks = use(tanksDefinitionsArray); const awaitedTanks = use(tanksDefinitionsArray);
const input = useRef<HTMLInputElement>(null); const input = useRef<HTMLInputElement>(null);
const searchableTanks = useMemo( const searchableTanks = useMemo(
@@ -50,37 +51,37 @@ export default function Page() {
awaitedTanks awaitedTanks
.filter( .filter(
(tank) => (tank) =>
(tankopediaState.filters.tiers.length === 0 (filters.tiers.length === 0
? true ? true
: tankopediaState.filters.tiers.includes(tank.tier)) && : filters.tiers.includes(tank.tier)) &&
(tankopediaState.filters.types.length === 0 (filters.types.length === 0
? true ? true
: tankopediaState.filters.types.includes(tank.type)) && : filters.types.includes(tank.type)) &&
(tankopediaState.filters.treeTypes.length === 0 (filters.treeTypes.length === 0
? true ? true
: tankopediaState.filters.treeTypes.includes(tank.tree_type)) && : filters.treeTypes.includes(tank.tree_type)) &&
(tankopediaState.filters.nations.length === 0 (filters.nations.length === 0
? true ? true
: tankopediaState.filters.nations.includes(tank.nation)) && : filters.nations.includes(tank.nation)) &&
(tankopediaState.filters.test === 'include' (filters.test === 'include'
? true ? true
: tankopediaState.filters.test === 'exclude' : filters.test === 'exclude'
? !tank.testing ? !tank.testing
: tank.testing), : tank.testing),
) )
.sort((a, b) => { .sort((a, b) => {
let diff = 0; let diff = 0;
if (tankopediaState.sort.by === 'tier') { if (sort.by === 'tier') {
diff = a.tier - b.tier; diff = a.tier - b.tier;
} }
if (tankopediaState.sort.by === 'name') { if (sort.by === 'name') {
diff = a.name.localeCompare(b.name); diff = a.name.localeCompare(b.name);
} }
return tankopediaState.sort.direction === 'ascending' ? diff : -diff; return sort.direction === 'ascending' ? diff : -diff;
}), }),
[tankopediaState.filters, tankopediaState.sort], [filters, sort],
); );
const [searchedList, setSearchedList] = useState(searchableTanks); const [searchedList, setSearchedList] = useState(searchableTanks);
const [page, setPage] = useState(0); const [page, setPage] = useState(0);
@@ -136,7 +137,7 @@ export default function Page() {
<DropdownMenu.Content> <DropdownMenu.Content>
<DropdownMenu.Label>By</DropdownMenu.Label> <DropdownMenu.Label>By</DropdownMenu.Label>
<DropdownMenu.RadioGroup <DropdownMenu.RadioGroup
value={tankopediaState.sort.by} value={sort.by}
onValueChange={(value) => onValueChange={(value) =>
mutateTankopedia((draft) => { mutateTankopedia((draft) => {
draft.sort.by = value as TankopediaSortBy; draft.sort.by = value as TankopediaSortBy;
@@ -155,7 +156,7 @@ export default function Page() {
<DropdownMenu.Label>Direction</DropdownMenu.Label> <DropdownMenu.Label>Direction</DropdownMenu.Label>
<DropdownMenu.RadioGroup <DropdownMenu.RadioGroup
value={tankopediaState.sort.direction} value={sort.direction}
onValueChange={(value) => onValueChange={(value) =>
mutateTankopedia((draft) => { mutateTankopedia((draft) => {
draft.sort.direction = value as TankopediaSortDirection; draft.sort.direction = value as TankopediaSortDirection;
@@ -1,5 +1,3 @@
// split shaders due to https://github.com/FarazzShaikh/THREE-CustomShaderMaterial/issues/48
varying vec3 vCSMViewPosition; varying vec3 vCSMViewPosition;
uniform bool isExplosive; uniform bool isExplosive;
uniform bool canSplash; uniform bool canSplash;
+20 -12
View File
@@ -28,12 +28,16 @@ type Tankopedia = {
test: TankopediaTestTankDisplay; test: TankopediaTestTankDisplay;
}; };
model: { model: {
hullYaw: number; physical: {
turretYaw: number; hullYaw: number;
gunPitch: number; turretYaw: number;
controlsEnabled: boolean; gunPitch: number;
showGrid: boolean; };
showSpacedArmor: boolean; visual: {
controlsEnabled: boolean;
showGrid: boolean;
showSpacedArmor: boolean;
};
}; };
mode: TankopediaMode; mode: TankopediaMode;
} & ( } & (
@@ -69,12 +73,16 @@ export const useTankopedia = create<Tankopedia>()(
test: 'include', test: 'include',
}, },
model: { model: {
gunPitch: 0, physical: {
hullYaw: 0, gunPitch: 0,
turretYaw: 0, hullYaw: 0,
controlsEnabled: true, turretYaw: 0,
showGrid: true, },
showSpacedArmor: true, visual: {
controlsEnabled: true,
showGrid: true,
showSpacedArmor: true,
},
}, },
areTanksAssigned: false, areTanksAssigned: false,
mode: 'model', mode: 'model',