feat: persistant tankopedia

This commit is contained in:
TrèsAbhi committed 2024-01-21 10:34:52 -06:00
1 parent f51e365ec4
commit a70100c835
17 files changed
+156 -126

No files matched your search

@@ -25,7 +25,7 @@ import {
SHELL_NAMES, SHELL_NAMES,
tankNamesDiacritics, tankNamesDiacritics,
} from '../../../../../../core/blitzkrieg/tankDefinitions'; } from '../../../../../../core/blitzkrieg/tankDefinitions';
import { useTankopedia } from '../../../../../../stores/tankopedia'; import { useTankopediaTemporary } from '../../../../../../stores/tankopedia';
import { Duel } from '../../page'; import { Duel } from '../../page';
import { SearchResults } from './components/SearchResults'; import { SearchResults } from './components/SearchResults';
@@ -38,7 +38,7 @@ export function AntagonistBar({
duel: { antagonist }, duel: { antagonist },
setDuel, setDuel,
}: AntagonistBarProps) { }: AntagonistBarProps) {
const mode = useTankopedia((state) => state.mode); const mode = useTankopediaTemporary((state) => state.mode);
const awaitedTankNamesDiacritics = use(tankNamesDiacritics); const awaitedTankNamesDiacritics = use(tankNamesDiacritics);
const [tab, setTab] = useState('search'); const [tab, setTab] = useState('search');
const searchInput = useRef<HTMLInputElement>(null); const searchInput = useRef<HTMLInputElement>(null);
@@ -3,8 +3,9 @@ import { produce } from 'immer';
import { Dispatch, SetStateAction, use } from 'react'; import { Dispatch, SetStateAction, use } from 'react';
import { ModuleButton } from '../../../../../components/ModuleButton'; import { ModuleButton } from '../../../../../components/ModuleButton';
import { equipmentDefinitions } from '../../../../../core/blitzkrieg/equipmentDefinitions'; import { equipmentDefinitions } from '../../../../../core/blitzkrieg/equipmentDefinitions';
import mutateTankopedia, { import {
useTankopedia, mutateTankopediaTemporary,
useTankopediaTemporary,
} from '../../../../../stores/tankopedia'; } from '../../../../../stores/tankopedia';
import { Duel } from '../page'; import { Duel } from '../page';
@@ -17,7 +18,9 @@ export function Configure({ duel, setDuel }: ConfigureProps) {
const awaitedEquipmentDefinitions = use(equipmentDefinitions); const awaitedEquipmentDefinitions = use(equipmentDefinitions);
const equipmentRows = const equipmentRows =
awaitedEquipmentDefinitions.presets[duel.protagonist.tank.equipment]; awaitedEquipmentDefinitions.presets[duel.protagonist.tank.equipment];
const equipmentMatrix = useTankopedia((state) => state.equipmentMatrix); const equipmentMatrix = useTankopediaTemporary(
(state) => state.equipmentMatrix,
);
return ( return (
<Flex gap="4" direction="column"> <Flex gap="4" direction="column">
@@ -97,7 +100,7 @@ export function Configure({ duel, setDuel }: ConfigureProps) {
rowChild rowChild
selected={equipmentMatrix[rowIndex][columnIndex] === 0} selected={equipmentMatrix[rowIndex][columnIndex] === 0}
onClick={() => { onClick={() => {
mutateTankopedia((draft) => { mutateTankopediaTemporary((draft) => {
draft.equipmentMatrix[rowIndex][columnIndex] = 0; draft.equipmentMatrix[rowIndex][columnIndex] = 0;
}); });
}} }}
@@ -109,7 +112,7 @@ export function Configure({ duel, setDuel }: ConfigureProps) {
rowChild rowChild
selected={equipmentMatrix[rowIndex][columnIndex] === 1} selected={equipmentMatrix[rowIndex][columnIndex] === 1}
onClick={() => { onClick={() => {
mutateTankopedia((draft) => { mutateTankopediaTemporary((draft) => {
draft.equipmentMatrix[rowIndex][columnIndex] = 1; draft.equipmentMatrix[rowIndex][columnIndex] = 1;
}); });
}} }}
@@ -7,7 +7,7 @@ import { OrbitControls as OrbitControlsClass } from 'three-stdlib';
import { applyPitchYawLimits } from '../../../../../core/blitz/applyPitchYawLimits'; import { applyPitchYawLimits } from '../../../../../core/blitz/applyPitchYawLimits';
import { modelDefinitions } from '../../../../../core/blitzkrieg/modelDefinitions'; import { modelDefinitions } from '../../../../../core/blitzkrieg/modelDefinitions';
import { Pose, poseEvent } from '../../../../../core/blitzkrieg/pose'; import { Pose, poseEvent } from '../../../../../core/blitzkrieg/pose';
import { useTankopedia } from '../../../../../stores/tankopedia'; import { useTankopediaTemporary } from '../../../../../stores/tankopedia';
import { Duel } from '../page'; import { Duel } from '../page';
const poseDistances: Record<Pose, number> = { const poseDistances: Record<Pose, number> = {
@@ -55,7 +55,7 @@ export function Controls({ duel }: ControlsProps) {
camera.position.set(-4, 4, -16); camera.position.set(-4, 4, -16);
orbitControls.current?.target.set(0, antagonistGunHeight / 2, 0); orbitControls.current?.target.set(0, antagonistGunHeight / 2, 0);
const unsubscribeTankopedia = useTankopedia.subscribe( const unsubscribeTankopedia = useTankopediaTemporary.subscribe(
(state) => state.model.visual.controlsEnabled, (state) => state.model.visual.controlsEnabled,
(enabled) => { (enabled) => {
if (orbitControls.current) orbitControls.current.enabled = enabled; if (orbitControls.current) orbitControls.current.enabled = enabled;
@@ -144,7 +144,7 @@ export function Controls({ duel }: ControlsProps) {
return ( return (
<OrbitControls <OrbitControls
ref={orbitControls} ref={orbitControls}
enabled={useTankopedia.getState().model.visual.controlsEnabled} enabled={useTankopediaTemporary.getState().model.visual.controlsEnabled}
rotateSpeed={0.25} rotateSpeed={0.25}
enableDamping={false} enableDamping={false}
/> />
@@ -7,9 +7,10 @@ import { modelDefinitions } from '../../../../../../core/blitzkrieg/modelDefinit
import { modelTransformEvent } from '../../../../../../core/blitzkrieg/modelTransform'; import { modelTransformEvent } from '../../../../../../core/blitzkrieg/modelTransform';
import { Pose, poseEvent } from '../../../../../../core/blitzkrieg/pose'; import { Pose, poseEvent } from '../../../../../../core/blitzkrieg/pose';
import { theme } from '../../../../../../stitches.config'; import { theme } from '../../../../../../stitches.config';
import mutateTankopedia, { import {
TankopediaMode, TankopediaMode,
useTankopedia, mutateTankopediaTemporary,
useTankopediaTemporary,
} from '../../../../../../stores/tankopedia'; } from '../../../../../../stores/tankopedia';
import { Loader } from '../../../../components/Loader'; import { Loader } from '../../../../components/Loader';
import { Duel } from '../../page'; import { Duel } from '../../page';
@@ -30,7 +31,7 @@ export function TankSandbox({ duel }: TankSandboxProps) {
const awaitedModelDefinitions = use(modelDefinitions); const awaitedModelDefinitions = use(modelDefinitions);
const canvasWrapper = useRef<HTMLDivElement>(null); const canvasWrapper = useRef<HTMLDivElement>(null);
const [isFullScreen, setIsFullScreen] = useState(false); const [isFullScreen, setIsFullScreen] = useState(false);
const mode = useTankopedia((state) => state.mode); const mode = useTankopediaTemporary((state) => state.mode);
const tankModelDefinition = awaitedModelDefinitions[duel.protagonist.tank.id]; const tankModelDefinition = awaitedModelDefinitions[duel.protagonist.tank.id];
const turretModelDefinition = const turretModelDefinition =
tankModelDefinition.turrets[duel.protagonist.turret.id]; tankModelDefinition.turrets[duel.protagonist.turret.id];
@@ -59,7 +60,7 @@ export function TankSandbox({ duel }: TankSandboxProps) {
function handlePoseEvent(pose: Pose) { function handlePoseEvent(pose: Pose) {
switch (pose) { switch (pose) {
case Pose.HullDown: { case Pose.HullDown: {
mutateTankopedia((draft) => { mutateTankopediaTemporary((draft) => {
const [pitch, yaw] = applyPitchYawLimits( const [pitch, yaw] = applyPitchYawLimits(
-Infinity, -Infinity,
0, 0,
@@ -76,7 +77,7 @@ export function TankSandbox({ duel }: TankSandboxProps) {
} }
case Pose.FaceHug: { case Pose.FaceHug: {
mutateTankopedia((draft) => { mutateTankopediaTemporary((draft) => {
const [pitch, yaw] = applyPitchYawLimits( const [pitch, yaw] = applyPitchYawLimits(
Infinity, Infinity,
0, 0,
@@ -109,7 +110,7 @@ export function TankSandbox({ duel }: TankSandboxProps) {
useEffect(() => { useEffect(() => {
if (!duel.protagonist) return void 0; if (!duel.protagonist) return void 0;
mutateTankopedia((draft) => { mutateTankopediaTemporary((draft) => {
[draft.model.physical.pitch, draft.model.physical.yaw] = [draft.model.physical.pitch, draft.model.physical.yaw] =
applyPitchYawLimits( applyPitchYawLimits(
draft.model.physical.pitch, draft.model.physical.pitch,
@@ -141,7 +142,7 @@ export function TankSandbox({ duel }: TankSandboxProps) {
<Tabs.Root <Tabs.Root
value={mode} value={mode}
onValueChange={(mode) => { onValueChange={(mode) => {
mutateTankopedia((draft) => { mutateTankopediaTemporary((draft) => {
draft.mode = mode as TankopediaMode; draft.mode = mode as TankopediaMode;
}); });
}} }}
@@ -7,8 +7,9 @@ import {
import { Button, DropdownMenu, Flex } from '@radix-ui/themes'; import { Button, DropdownMenu, Flex } from '@radix-ui/themes';
import { RefObject } from 'react'; import { RefObject } from 'react';
import { Pose, poseEvent } from '../../../../../../../core/blitzkrieg/pose'; import { Pose, poseEvent } from '../../../../../../../core/blitzkrieg/pose';
import mutateTankopedia, { import {
useTankopedia, mutateTankopediaTemporary,
useTankopediaTemporary,
} from '../../../../../../../stores/tankopedia'; } from '../../../../../../../stores/tankopedia';
interface OptionsProps { interface OptionsProps {
@@ -17,8 +18,10 @@ interface OptionsProps {
} }
export function Options({ isFullScreen, canvas }: OptionsProps) { export function Options({ isFullScreen, canvas }: OptionsProps) {
const showGrid = useTankopedia((state) => state.model.visual.showGrid); const showGrid = useTankopediaTemporary(
const greenPenetration = useTankopedia( (state) => state.model.visual.showGrid,
);
const greenPenetration = useTankopediaTemporary(
(state) => state.model.visual.greenPenetration, (state) => state.model.visual.greenPenetration,
); );
const fullScreenAvailable = document.fullscreenEnabled; const fullScreenAvailable = document.fullscreenEnabled;
@@ -65,7 +68,7 @@ export function Options({ isFullScreen, canvas }: OptionsProps) {
<DropdownMenu.CheckboxItem <DropdownMenu.CheckboxItem
checked={showGrid} checked={showGrid}
onCheckedChange={(checked) => { onCheckedChange={(checked) => {
mutateTankopedia((draft) => { mutateTankopediaTemporary((draft) => {
draft.model.visual.showGrid = checked; draft.model.visual.showGrid = checked;
}); });
}} }}
@@ -76,7 +79,7 @@ export function Options({ isFullScreen, canvas }: OptionsProps) {
<DropdownMenu.CheckboxItem <DropdownMenu.CheckboxItem
checked={greenPenetration} checked={greenPenetration}
onCheckedChange={(checked) => { onCheckedChange={(checked) => {
mutateTankopedia((draft) => { mutateTankopediaTemporary((draft) => {
draft.model.visual.greenPenetration = checked; draft.model.visual.greenPenetration = checked;
}); });
}} }}
@@ -15,7 +15,7 @@ import { nameToArmorId } from '../../../../../../../../../core/blitzkrieg/nameTo
import { resolveArmor } from '../../../../../../../../../core/blitzkrieg/resolveThickness'; import { resolveArmor } from '../../../../../../../../../core/blitzkrieg/resolveThickness';
import { useArmor } from '../../../../../../../../../hooks/useArmor'; import { useArmor } from '../../../../../../../../../hooks/useArmor';
import { useModelDefinitions } from '../../../../../../../../../hooks/useModelDefinitions'; import { useModelDefinitions } from '../../../../../../../../../hooks/useModelDefinitions';
import { useTankopedia } from '../../../../../../../../../stores/tankopedia'; import { useTankopediaTemporary } from '../../../../../../../../../stores/tankopedia';
import { Duel } from '../../../../../page'; import { Duel } from '../../../../../page';
interface ArmorHighlightingProps { interface ArmorHighlightingProps {
@@ -27,7 +27,7 @@ export function ArmorHighlighting({ duel }: ArmorHighlightingProps) {
const modelDefinitions = useModelDefinitions(); const modelDefinitions = useModelDefinitions();
const turretContainer = useRef<Group>(null); const turretContainer = useRef<Group>(null);
const gunContainer = useRef<Group>(null); const gunContainer = useRef<Group>(null);
const initialTankopediaState = useTankopedia.getState(); const initialTankopediaState = useTankopediaTemporary.getState();
useEffect(() => { useEffect(() => {
const turretOrigin = new Vector3( const turretOrigin = new Vector3(
@@ -86,7 +86,7 @@ export function ArmorHighlighting({ duel }: ArmorHighlightingProps) {
turretContainer.current?.rotation.copy(turretRotation); turretContainer.current?.rotation.copy(turretRotation);
} }
handleModelTransform(useTankopedia.getState().model.physical); handleModelTransform(useTankopediaTemporary.getState().model.physical);
modelTransformEvent.on(handleModelTransform); modelTransformEvent.on(handleModelTransform);
return () => { return () => {
@@ -95,7 +95,7 @@ export function ArmorHighlighting({ duel }: ArmorHighlightingProps) {
}); });
useEffect(() => { useEffect(() => {
const unsubscribe = useTankopedia.subscribe( const unsubscribe = useTankopediaTemporary.subscribe(
(state) => state.mode, (state) => state.mode,
(mode) => { (mode) => {
if (wrapper.current) wrapper.current.visible = mode === 'armor'; if (wrapper.current) wrapper.current.visible = mode === 'armor';
@@ -16,7 +16,7 @@ import { resolveArmor } from '../../../../../../../../../core/blitzkrieg/resolve
import { useArmor } from '../../../../../../../../../hooks/useArmor'; import { useArmor } from '../../../../../../../../../hooks/useArmor';
import { useModel } from '../../../../../../../../../hooks/useModel'; import { useModel } from '../../../../../../../../../hooks/useModel';
import { useModelDefinitions } from '../../../../../../../../../hooks/useModelDefinitions'; import { useModelDefinitions } from '../../../../../../../../../hooks/useModelDefinitions';
import { useTankopedia } from '../../../../../../../../../stores/tankopedia'; import { useTankopediaTemporary } from '../../../../../../../../../stores/tankopedia';
import { Duel } from '../../../../../page'; import { Duel } from '../../../../../page';
interface ExternalModuleMaskProps { interface ExternalModuleMaskProps {
@@ -35,7 +35,7 @@ export const ExternalModuleMask = memo<ExternalModuleMaskProps>(({ duel }) => {
const modelDefinitions = useModelDefinitions(); const modelDefinitions = useModelDefinitions();
const turretContainer = useRef<Group>(null); const turretContainer = useRef<Group>(null);
const gunContainer = useRef<Group>(null); const gunContainer = useRef<Group>(null);
const initialTankopediaState = useTankopedia.getState(); const initialTankopediaState = useTankopediaTemporary.getState();
useEffect(() => { useEffect(() => {
const turretOrigin = new Vector3( const turretOrigin = new Vector3(
@@ -94,7 +94,7 @@ export const ExternalModuleMask = memo<ExternalModuleMaskProps>(({ duel }) => {
turretContainer.current?.rotation.copy(turretRotation); turretContainer.current?.rotation.copy(turretRotation);
} }
handleModelTransform(useTankopedia.getState().model.physical); handleModelTransform(useTankopediaTemporary.getState().model.physical);
modelTransformEvent.on(handleModelTransform); modelTransformEvent.on(handleModelTransform);
return () => { return () => {
@@ -103,7 +103,7 @@ export const ExternalModuleMask = memo<ExternalModuleMaskProps>(({ duel }) => {
}); });
useEffect(() => { useEffect(() => {
const unsubscribe = useTankopedia.subscribe( const unsubscribe = useTankopediaTemporary.subscribe(
(state) => state.mode, (state) => state.mode,
(mode) => { (mode) => {
if (wrapper.current) wrapper.current.visible = mode === 'armor'; if (wrapper.current) wrapper.current.visible = mode === 'armor';
@@ -16,7 +16,7 @@ import { resolveArmor } from '../../../../../../../../../core/blitzkrieg/resolve
import { useArmor } from '../../../../../../../../../hooks/useArmor'; import { useArmor } from '../../../../../../../../../hooks/useArmor';
import { useModel } from '../../../../../../../../../hooks/useModel'; import { useModel } from '../../../../../../../../../hooks/useModel';
import { useModelDefinitions } from '../../../../../../../../../hooks/useModelDefinitions'; import { useModelDefinitions } from '../../../../../../../../../hooks/useModelDefinitions';
import { useTankopedia } from '../../../../../../../../../stores/tankopedia'; import { useTankopediaTemporary } from '../../../../../../../../../stores/tankopedia';
import { Duel } from '../../../../../page'; import { Duel } from '../../../../../page';
interface SpacedArmorDepthProps { interface SpacedArmorDepthProps {
@@ -39,7 +39,7 @@ export const SpacedArmorDepth = memo<SpacedArmorDepthProps>(({ duel }) => {
const modelDefinitions = useModelDefinitions(); const modelDefinitions = useModelDefinitions();
const turretContainer = useRef<Group>(null); const turretContainer = useRef<Group>(null);
const gunContainer = useRef<Group>(null); const gunContainer = useRef<Group>(null);
const initialTankopediaState = useTankopedia.getState(); const initialTankopediaState = useTankopediaTemporary.getState();
useEffect(() => { useEffect(() => {
if (!modelDefinitions) return; if (!modelDefinitions) return;
@@ -100,7 +100,7 @@ export const SpacedArmorDepth = memo<SpacedArmorDepthProps>(({ duel }) => {
turretContainer.current?.rotation.copy(turretRotation); turretContainer.current?.rotation.copy(turretRotation);
} }
handleModelTransform(useTankopedia.getState().model.physical); handleModelTransform(useTankopediaTemporary.getState().model.physical);
modelTransformEvent.on(handleModelTransform); modelTransformEvent.on(handleModelTransform);
return () => { return () => {
@@ -109,7 +109,7 @@ export const SpacedArmorDepth = memo<SpacedArmorDepthProps>(({ duel }) => {
}); });
useEffect(() => { useEffect(() => {
const unsubscribe = useTankopedia.subscribe( const unsubscribe = useTankopediaTemporary.subscribe(
(state) => state.mode, (state) => state.mode,
(mode) => { (mode) => {
if (wrapper.current) wrapper.current.visible = mode === 'armor'; if (wrapper.current) wrapper.current.visible = mode === 'armor';
@@ -22,8 +22,9 @@ import {
import { resolveJsxTree } from '../../../../../../../core/blitzkrieg/resolveJsxTree'; import { resolveJsxTree } from '../../../../../../../core/blitzkrieg/resolveJsxTree';
import { normalizeAnglePI } from '../../../../../../../core/math/normalizeAngle180'; import { normalizeAnglePI } from '../../../../../../../core/math/normalizeAngle180';
import { useModel } from '../../../../../../../hooks/useModel'; import { useModel } from '../../../../../../../hooks/useModel';
import mutateTankopedia, { import {
useTankopedia, mutateTankopediaTemporary,
useTankopediaTemporary,
} from '../../../../../../../stores/tankopedia'; } from '../../../../../../../stores/tankopedia';
import { Duel } from '../../../page'; import { Duel } from '../../../page';
@@ -37,7 +38,7 @@ export const TankModel = memo(({ duel: { protagonist } }: TankModelProps) => {
>(undefined); >(undefined);
const canvas = useThree((state) => state.gl.domElement); const canvas = useThree((state) => state.gl.domElement);
const physical = useTankopedia((state) => state.model.physical); const physical = useTankopediaTemporary((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);
@@ -136,7 +137,7 @@ export const TankModel = memo(({ duel: { protagonist } }: TankModelProps) => {
event.stopPropagation(); event.stopPropagation();
if (isTrack) { if (isTrack) {
mutateTankopedia((draft) => { mutateTankopediaTemporary((draft) => {
draft.model.visual.controlsEnabled = false; draft.model.visual.controlsEnabled = false;
}); });
@@ -158,7 +159,7 @@ export const TankModel = memo(({ duel: { protagonist } }: TankModelProps) => {
material.normalMap?.offset.add(offset); material.normalMap?.offset.add(offset);
} }
function handlePointerUp() { function handlePointerUp() {
mutateTankopedia((draft) => { mutateTankopediaTemporary((draft) => {
draft.model.visual.controlsEnabled = true; draft.model.visual.controlsEnabled = true;
}); });
window.removeEventListener('pointermove', handlePointerMove); window.removeEventListener('pointermove', handlePointerMove);
@@ -200,7 +201,7 @@ export const TankModel = memo(({ duel: { protagonist } }: TankModelProps) => {
yaw = physical.yaw; yaw = physical.yaw;
pitch = physical.pitch; pitch = physical.pitch;
mutateTankopedia((draft) => { mutateTankopediaTemporary((draft) => {
draft.model.visual.controlsEnabled = false; draft.model.visual.controlsEnabled = false;
}); });
window.addEventListener('pointermove', handlePointerMove); window.addEventListener('pointermove', handlePointerMove);
@@ -217,7 +218,7 @@ export const TankModel = memo(({ duel: { protagonist } }: TankModelProps) => {
modelTransformEvent.emit({ pitch, yaw }); modelTransformEvent.emit({ pitch, yaw });
} }
function handlePointerUp() { function handlePointerUp() {
mutateTankopedia((state) => { mutateTankopediaTemporary((state) => {
state.model.visual.controlsEnabled = true; state.model.visual.controlsEnabled = true;
state.model.physical.pitch = normalizeAnglePI(pitch); state.model.physical.pitch = normalizeAnglePI(pitch);
state.model.physical.yaw = normalizeAnglePI(yaw); state.model.physical.yaw = normalizeAnglePI(yaw);
@@ -262,7 +263,7 @@ export const TankModel = memo(({ duel: { protagonist } }: TankModelProps) => {
function handlePointerDown(event: ThreeEvent<PointerEvent>) { function handlePointerDown(event: ThreeEvent<PointerEvent>) {
event.stopPropagation(); event.stopPropagation();
mutateTankopedia((draft) => { mutateTankopediaTemporary((draft) => {
draft.model.visual.controlsEnabled = false; draft.model.visual.controlsEnabled = false;
}); });
pitch = physical.pitch; pitch = physical.pitch;
@@ -280,7 +281,7 @@ export const TankModel = memo(({ duel: { protagonist } }: TankModelProps) => {
modelTransformEvent.emit({ pitch, yaw }); modelTransformEvent.emit({ pitch, yaw });
} }
function handlePointerUp() { function handlePointerUp() {
mutateTankopedia((state) => { mutateTankopediaTemporary((state) => {
state.model.visual.controlsEnabled = true; state.model.visual.controlsEnabled = true;
state.model.physical.pitch = normalizeAnglePI(pitch); state.model.physical.pitch = normalizeAnglePI(pitch);
state.model.physical.yaw = normalizeAnglePI(yaw); state.model.physical.yaw = normalizeAnglePI(yaw);
@@ -3,8 +3,9 @@ import { use, useEffect, useRef } 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';
import { modelTransformEvent } from '../../../../../core/blitzkrieg/modelTransform'; import { modelTransformEvent } from '../../../../../core/blitzkrieg/modelTransform';
import mutateTankopedia, { import {
useTankopedia, mutateTankopediaTemporary,
useTankopediaTemporary,
} from '../../../../../stores/tankopedia'; } from '../../../../../stores/tankopedia';
import { Duel } from '../page'; import { Duel } from '../page';
@@ -13,7 +14,7 @@ interface RotationInputsProps {
} }
export function RotationInputs({ duel: { protagonist } }: RotationInputsProps) { export function RotationInputs({ duel: { protagonist } }: RotationInputsProps) {
const physical = useTankopedia((state) => state.model.physical); const physical = useTankopediaTemporary((state) => state.model.physical);
const awaitedModelDefinitions = use(modelDefinitions); const awaitedModelDefinitions = use(modelDefinitions);
const turretYawInput = useRef<HTMLInputElement>(null); const turretYawInput = useRef<HTMLInputElement>(null);
const gunPitchInput = useRef<HTMLInputElement>(null); const gunPitchInput = useRef<HTMLInputElement>(null);
@@ -67,7 +68,7 @@ export function RotationInputs({ duel: { protagonist } }: RotationInputsProps) {
turretModelDefinition.yaw, turretModelDefinition.yaw,
); );
modelTransformEvent.emit({ pitch, yaw }); modelTransformEvent.emit({ pitch, yaw });
mutateTankopedia((state) => { mutateTankopediaTemporary((state) => {
state.model.physical.pitch = pitch; state.model.physical.pitch = pitch;
state.model.physical.yaw = yaw; state.model.physical.yaw = yaw;
}); });
@@ -103,7 +104,7 @@ export function RotationInputs({ duel: { protagonist } }: RotationInputsProps) {
turretModelDefinition.yaw, turretModelDefinition.yaw,
); );
modelTransformEvent.emit({ pitch, yaw }); modelTransformEvent.emit({ pitch, yaw });
mutateTankopedia((state) => { mutateTankopediaTemporary((state) => {
state.model.physical.pitch = pitch; state.model.physical.pitch = pitch;
state.model.physical.yaw = yaw; state.model.physical.yaw = yaw;
}); });
@@ -1,9 +1,11 @@
import { Color } from 'three'; import { Color } from 'three';
import InfiniteGridHelper from '../../../../../components/InfiniteGridHelper'; import InfiniteGridHelper from '../../../../../components/InfiniteGridHelper';
import { useTankopedia } from '../../../../../stores/tankopedia'; import { useTankopediaTemporary } from '../../../../../stores/tankopedia';
export function SceneProps() { export function SceneProps() {
const showGrid = useTankopedia((state) => state.model.visual.showGrid); const showGrid = useTankopediaTemporary(
(state) => state.model.visual.showGrid,
);
if (!showGrid) return null; if (!showGrid) return null;
+5 -2
View File
@@ -5,7 +5,10 @@ import { produce } from 'immer';
import { use, useEffect, useState } from 'react'; import { use, useEffect, useState } from 'react';
import PageWrapper from '../../../../components/PageWrapper'; import PageWrapper from '../../../../components/PageWrapper';
import { tankDefinitions } from '../../../../core/blitzkrieg/tankDefinitions'; import { tankDefinitions } from '../../../../core/blitzkrieg/tankDefinitions';
import mutateTankopedia, { DuelMember } from '../../../../stores/tankopedia'; import {
DuelMember,
mutateTankopediaTemporary,
} from '../../../../stores/tankopedia';
import { AntagonistBar } from './components/AntagonistBar'; import { AntagonistBar } from './components/AntagonistBar';
import { Configure } from './components/Configure'; import { Configure } from './components/Configure';
import { TankSandbox } from './components/Model/TankSandbox'; import { TankSandbox } from './components/Model/TankSandbox';
@@ -35,7 +38,7 @@ export default function Page({ params }: { params: { id: string } }) {
}); });
useEffect(() => { useEffect(() => {
mutateTankopedia((draft) => { mutateTankopediaTemporary((draft) => {
draft.model.physical.yaw = 0; draft.model.physical.yaw = 0;
draft.model.physical.pitch = 0; draft.model.physical.pitch = 0;
draft.mode = 'model'; draft.mode = 'model';
@@ -16,15 +16,15 @@ import {
TIERS, TIERS,
TIER_ROMAN_NUMERALS, TIER_ROMAN_NUMERALS,
} from '../../../../../core/blitzkrieg/tankDefinitions'; } from '../../../../../core/blitzkrieg/tankDefinitions';
import mutateTankopedia, { import mutateTankopediaPersistent, {
TankopediaTestTankDisplay, TankopediaTestTankDisplay,
useTankopedia, useTankopediaPersistent,
} from '../../../../../stores/tankopedia'; } from '../../../../../stores/tankopedia';
import * as styles from './index.css'; import * as styles from './index.css';
export function Options() { export function Options() {
const nations = use(NATIONS); const nations = use(NATIONS);
const filters = useTankopedia((state) => state.filters); const filters = useTankopediaPersistent((state) => state.filters);
return ( return (
<Flex justify="center" direction="column" gap="2"> <Flex justify="center" direction="column" gap="2">
@@ -32,13 +32,14 @@ export function Options() {
<Button <Button
color="red" color="red"
onClick={() => onClick={() =>
useTankopedia.setState({ useTankopediaPersistent.setState({
filters: { filters: {
nations: [], nations: [],
tiers: [], tiers: [],
treeTypes: [], treeTypes: [],
types: [], types: [],
test: 'include', test: 'include',
page: 0,
}, },
}) })
} }
@@ -56,7 +57,7 @@ export function Options() {
<DropdownMenu.RadioGroup <DropdownMenu.RadioGroup
value={filters.test} value={filters.test}
onValueChange={(value) => onValueChange={(value) =>
mutateTankopedia((draft) => { mutateTankopediaPersistent((draft) => {
draft.filters.test = value as TankopediaTestTankDisplay; draft.filters.test = value as TankopediaTestTankDisplay;
}) })
} }
@@ -88,7 +89,7 @@ export function Options() {
index === TREE_TYPES.length - 1 ? undefined : 0, index === TREE_TYPES.length - 1 ? undefined : 0,
}} }}
onClick={() => onClick={() =>
mutateTankopedia((draft) => { mutateTankopediaPersistent((draft) => {
if (draft.filters.treeTypes.includes(type)) { if (draft.filters.treeTypes.includes(type)) {
draft.filters.treeTypes = draft.filters.treeTypes.filter( draft.filters.treeTypes = draft.filters.treeTypes.filter(
(preexistingType) => preexistingType !== type, (preexistingType) => preexistingType !== type,
@@ -123,7 +124,7 @@ export function Options() {
index === TANK_TYPES.length - 1 ? undefined : 0, index === TANK_TYPES.length - 1 ? undefined : 0,
}} }}
onClick={() => onClick={() =>
mutateTankopedia((draft) => { mutateTankopediaPersistent((draft) => {
if (draft.filters.types.includes(type)) { if (draft.filters.types.includes(type)) {
draft.filters.types = draft.filters.types.filter( draft.filters.types = draft.filters.types.filter(
(preexistingType) => preexistingType !== type, (preexistingType) => preexistingType !== type,
@@ -167,7 +168,7 @@ export function Options() {
borderBottomRightRadius: 0, borderBottomRightRadius: 0,
}} }}
onClick={() => onClick={() =>
mutateTankopedia((draft) => { mutateTankopediaPersistent((draft) => {
if (draft.filters.tiers.includes(tier)) { if (draft.filters.tiers.includes(tier)) {
draft.filters.tiers = draft.filters.tiers.filter( draft.filters.tiers = draft.filters.tiers.filter(
(preexistingTier) => preexistingTier !== tier, (preexistingTier) => preexistingTier !== tier,
@@ -203,7 +204,7 @@ export function Options() {
: 0, : 0,
}} }}
onClick={() => onClick={() =>
mutateTankopedia((draft) => { mutateTankopediaPersistent((draft) => {
if (draft.filters.tiers.includes(tier)) { if (draft.filters.tiers.includes(tier)) {
draft.filters.tiers = draft.filters.tiers.filter( draft.filters.tiers = draft.filters.tiers.filter(
(preexistingTier) => preexistingTier !== tier, (preexistingTier) => preexistingTier !== tier,
@@ -243,7 +244,7 @@ export function Options() {
index === nations.length - 1 ? undefined : 0, index === nations.length - 1 ? undefined : 0,
}} }}
onClick={() => onClick={() =>
mutateTankopedia((draft) => { mutateTankopediaPersistent((draft) => {
if (draft.filters.nations.includes(nation)) { if (draft.filters.nations.includes(nation)) {
draft.filters.nations = draft.filters.nations.filter( draft.filters.nations = draft.filters.nations.filter(
(preexistingType) => preexistingType !== nation, (preexistingType) => preexistingType !== nation,
@@ -279,7 +280,7 @@ export function Options() {
: 0, : 0,
}} }}
onClick={() => onClick={() =>
mutateTankopedia((draft) => { mutateTankopediaPersistent((draft) => {
if (draft.filters.nations.includes(nation)) { if (draft.filters.nations.includes(nation)) {
draft.filters.nations = draft.filters.nations.filter( draft.filters.nations = draft.filters.nations.filter(
(preexistingType) => preexistingType !== nation, (preexistingType) => preexistingType !== nation,
@@ -315,7 +316,7 @@ export function Options() {
index === TIERS.length - 1 ? undefined : 0, index === TIERS.length - 1 ? undefined : 0,
}} }}
onClick={() => onClick={() =>
mutateTankopedia((draft) => { mutateTankopediaPersistent((draft) => {
if (draft.filters.tiers.includes(tier)) { if (draft.filters.tiers.includes(tier)) {
draft.filters.tiers = draft.filters.tiers.filter( draft.filters.tiers = draft.filters.tiers.filter(
(preexistingTier) => preexistingTier !== tier, (preexistingTier) => preexistingTier !== tier,
@@ -347,7 +348,7 @@ export function Options() {
index === nations.length - 1 ? undefined : 0, index === nations.length - 1 ? undefined : 0,
}} }}
onClick={() => onClick={() =>
mutateTankopedia((draft) => { mutateTankopediaPersistent((draft) => {
if (draft.filters.nations.includes(nation)) { if (draft.filters.nations.includes(nation)) {
draft.filters.nations = draft.filters.nations.filter( draft.filters.nations = draft.filters.nations.filter(
(preexistingType) => preexistingType !== nation, (preexistingType) => preexistingType !== nation,
@@ -2,20 +2,17 @@ import { CaretLeftIcon, CaretRightIcon } from '@radix-ui/react-icons';
import { Button, Flex, TextField } from '@radix-ui/themes'; import { Button, Flex, TextField } from '@radix-ui/themes';
import { useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react';
import { TankDefinition } from '../../../../core/blitzkrieg/tankDefinitions'; import { TankDefinition } from '../../../../core/blitzkrieg/tankDefinitions';
import mutateTankopediaPersistent, {
useTankopediaPersistent,
} from '../../../../stores/tankopedia';
interface PageTurnerProps { interface PageTurnerProps {
page: number;
setPage: (page: number) => void;
searchedList: TankDefinition[]; searchedList: TankDefinition[];
tanksPerPage: number; tanksPerPage: number;
} }
export function PageTurner({ export function PageTurner({ searchedList, tanksPerPage }: PageTurnerProps) {
page, const page = useTankopediaPersistent((state) => state.filters.page);
setPage,
searchedList,
tanksPerPage,
}: PageTurnerProps) {
const pageInput = useRef<HTMLInputElement>(null); const pageInput = useRef<HTMLInputElement>(null);
useEffect(() => { useEffect(() => {
@@ -27,7 +24,11 @@ export function PageTurner({
<Button <Button
variant="soft" variant="soft"
disabled={page === 0} disabled={page === 0}
onClick={() => setPage(Math.max(0, page - 1))} onClick={() => {
mutateTankopediaPersistent((draft) => {
draft.filters.page = Math.max(0, page - 1);
});
}}
> >
<CaretLeftIcon /> <CaretLeftIcon />
</Button> </Button>
@@ -41,15 +42,15 @@ export function PageTurner({
max={Math.floor(searchedList.length / tanksPerPage) + 1} max={Math.floor(searchedList.length / tanksPerPage) + 1}
style={{ width: 64, textAlign: 'center' }} style={{ width: 64, textAlign: 'center' }}
onBlur={(event) => { onBlur={(event) => {
setPage( mutateTankopediaPersistent((draft) => {
Math.max( draft.filters.page = Math.max(
0, 0,
Math.min( Math.min(
Math.floor(searchedList.length / tanksPerPage), Math.floor(searchedList.length / tanksPerPage),
event.target.valueAsNumber - 1, event.target.valueAsNumber - 1,
), ),
), );
); });
}} }}
onKeyDown={(event) => { onKeyDown={(event) => {
if (event.key === 'Enter') { if (event.key === 'Enter') {
@@ -64,11 +65,14 @@ export function PageTurner({
<Button <Button
variant="soft" variant="soft"
disabled={Math.floor(searchedList.length / tanksPerPage) === page} disabled={Math.floor(searchedList.length / tanksPerPage) === page}
onClick={() => onClick={() => {
setPage( mutateTankopediaPersistent((draft) => {
Math.min(Math.floor(searchedList.length / tanksPerPage), page + 1), draft.filters.page = Math.min(
) Math.floor(searchedList.length / tanksPerPage),
} page + 1,
);
});
}}
> >
<CaretRightIcon /> <CaretRightIcon />
</Button> </Button>
+13 -23
View File
@@ -26,10 +26,10 @@ import {
} from '../../../core/blitzkrieg/tankDefinitions'; } from '../../../core/blitzkrieg/tankDefinitions';
import { tankIcon } from '../../../core/blitzkrieg/tankIcon'; import { tankIcon } from '../../../core/blitzkrieg/tankIcon';
import { theme } from '../../../stitches.config'; import { theme } from '../../../stitches.config';
import mutateTankopedia, { import mutateTankopediaPersistent, {
TankopediaSortBy, TankopediaSortBy,
TankopediaSortDirection, TankopediaSortDirection,
useTankopedia, useTankopediaPersistent,
} from '../../../stores/tankopedia'; } from '../../../stores/tankopedia';
import { Options } from './components/Options'; import { Options } from './components/Options';
import { PageTurner } from './components/PageTurner'; import { PageTurner } from './components/PageTurner';
@@ -38,8 +38,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 filters = useTankopedia((state) => state.filters); const filters = useTankopediaPersistent((state) => state.filters);
const sort = useTankopedia((state) => state.sort); const sort = useTankopediaPersistent((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(
@@ -80,7 +80,7 @@ export default function Page() {
[filters, sort], [filters, sort],
); );
const [searchedList, setSearchedList] = useState(searchableTanks); const [searchedList, setSearchedList] = useState(searchableTanks);
const [page, setPage] = useState(0); const page = useTankopediaPersistent((state) => state.filters.page);
useEffect(() => { useEffect(() => {
setSearchedList(searchableTanks); setSearchedList(searchableTanks);
@@ -88,12 +88,12 @@ export default function Page() {
}, [searchableTanks]); }, [searchableTanks]);
useEffect(() => { useEffect(() => {
setPage( mutateTankopediaPersistent((draft) => {
Math.min( draft.filters.page = Math.min(
Math.max(0, page), Math.max(0, page),
Math.ceil(searchedList.length / TANKS_PER_PAGE) - 1, Math.ceil(searchedList.length / TANKS_PER_PAGE) - 1,
), );
); });
}, [searchedList]); }, [searchedList]);
return ( return (
@@ -134,7 +134,7 @@ export default function Page() {
<DropdownMenu.RadioGroup <DropdownMenu.RadioGroup
value={sort.by} value={sort.by}
onValueChange={(value) => onValueChange={(value) =>
mutateTankopedia((draft) => { mutateTankopediaPersistent((draft) => {
draft.sort.by = value as TankopediaSortBy; draft.sort.by = value as TankopediaSortBy;
}) })
} }
@@ -153,7 +153,7 @@ export default function Page() {
<DropdownMenu.RadioGroup <DropdownMenu.RadioGroup
value={sort.direction} value={sort.direction}
onValueChange={(value) => onValueChange={(value) =>
mutateTankopedia((draft) => { mutateTankopediaPersistent((draft) => {
draft.sort.direction = value as TankopediaSortDirection; draft.sort.direction = value as TankopediaSortDirection;
}) })
} }
@@ -176,12 +176,7 @@ export default function Page() {
</Card> </Card>
</Flex> </Flex>
<PageTurner <PageTurner tanksPerPage={TANKS_PER_PAGE} searchedList={searchedList} />
page={page}
setPage={setPage}
tanksPerPage={TANKS_PER_PAGE}
searchedList={searchedList}
/>
<Flex wrap="wrap" gap="3" justify="center"> <Flex wrap="wrap" gap="3" justify="center">
{searchedList {searchedList
@@ -302,12 +297,7 @@ export default function Page() {
.slice(page * TANKS_PER_PAGE, (page + 1) * TANKS_PER_PAGE)} .slice(page * TANKS_PER_PAGE, (page + 1) * TANKS_PER_PAGE)}
</Flex> </Flex>
<PageTurner <PageTurner tanksPerPage={TANKS_PER_PAGE} searchedList={searchedList} />
page={page}
setPage={setPage}
tanksPerPage={TANKS_PER_PAGE}
searchedList={searchedList}
/>
</Suspense> </Suspense>
</PageWrapper> </PageWrapper>
); );
@@ -7,7 +7,7 @@ import { Duel } from '../../../../app/tools/tankopedia/[id]/page';
import { canSplash } from '../../../../core/blitz/canSplash'; import { canSplash } from '../../../../core/blitz/canSplash';
import { isExplosive } from '../../../../core/blitz/isExplosive'; import { isExplosive } from '../../../../core/blitz/isExplosive';
import { resolveNearPenetration } from '../../../../core/blitz/resolveNearPenetration'; import { resolveNearPenetration } from '../../../../core/blitz/resolveNearPenetration';
import { useTankopedia } from '../../../../stores/tankopedia'; import { useTankopediaTemporary } from '../../../../stores/tankopedia';
import { externalModuleMaskRenderTarget } from '../ExternalModuleMask'; import { externalModuleMaskRenderTarget } from '../ExternalModuleMask';
import { spacedArmorDepthRenderTarget } from '../SpacedArmorDepth'; import { spacedArmorDepthRenderTarget } from '../SpacedArmorDepth';
import fragmentShader from './shaders/fragment.glsl'; import fragmentShader from './shaders/fragment.glsl';
@@ -27,7 +27,7 @@ export function ArmorMesh({
}: ArmorMeshProps) { }: ArmorMeshProps) {
const camera = useThree((state) => state.camera); const camera = useThree((state) => state.camera);
const { shell } = duel.antagonist; const { shell } = duel.antagonist;
const greenPenetration = useTankopedia( const greenPenetration = useTankopediaTemporary(
(state) => state.model.visual.greenPenetration, (state) => state.model.visual.greenPenetration,
); );
const material = useRef<ShaderMaterial>(null); const material = useRef<ShaderMaterial>(null);
+38 -17
View File
@@ -1,6 +1,6 @@
import { produce } from 'immer'; import { produce } from 'immer';
import { create } from 'zustand'; import { create } from 'zustand';
import { subscribeWithSelector } from 'zustand/middleware'; import { persist, subscribeWithSelector } from 'zustand/middleware';
import { TankType, TreeTypeString } from '../components/Tanks'; import { TankType, TreeTypeString } from '../components/Tanks';
import { import {
GunDefinition, GunDefinition,
@@ -20,7 +20,7 @@ export interface DuelMember {
gun: GunDefinition; gun: GunDefinition;
shell: ShellDefinition; shell: ShellDefinition;
} }
interface Tankopedia { interface TankopediaPersistent {
sort: { sort: {
by: TankopediaSortBy; by: TankopediaSortBy;
direction: TankopediaSortDirection; direction: TankopediaSortDirection;
@@ -31,7 +31,11 @@ interface Tankopedia {
treeTypes: TreeTypeString[]; treeTypes: TreeTypeString[];
nations: string[]; nations: string[];
test: TankopediaTestTankDisplay; test: TankopediaTestTankDisplay;
page: number;
}; };
}
interface TankopediaTemporary {
model: { model: {
physical: { physical: {
yaw: number; yaw: number;
@@ -47,19 +51,28 @@ interface Tankopedia {
mode: TankopediaMode; mode: TankopediaMode;
} }
export const useTankopedia = create<Tankopedia>()( export const useTankopediaPersistent = create<TankopediaPersistent>()(
subscribeWithSelector<Tankopedia>(() => ({ persist(
sort: { subscribeWithSelector<TankopediaPersistent>(() => ({
by: 'tier', sort: {
direction: 'descending', by: 'tier',
}, direction: 'descending',
filters: { },
tiers: [], filters: {
types: [], tiers: [],
treeTypes: [], types: [],
nations: [], treeTypes: [],
test: 'include', nations: [],
}, test: 'include',
page: 0,
},
})),
{ name: 'tankopedia' },
),
);
export const useTankopediaTemporary = create<TankopediaTemporary>()(
subscribeWithSelector<TankopediaTemporary>(() => ({
model: { model: {
physical: { physical: {
yaw: 0, yaw: 0,
@@ -80,6 +93,14 @@ export const useTankopedia = create<Tankopedia>()(
})), })),
); );
export default function mutateTankopedia(recipe: (draft: Tankopedia) => void) { export default function mutateTankopediaPersistent(
useTankopedia.setState(produce(recipe)); recipe: (draft: TankopediaPersistent) => void,
) {
useTankopediaPersistent.setState(produce(recipe));
}
export function mutateTankopediaTemporary(
recipe: (draft: TankopediaTemporary) => void,
) {
useTankopediaTemporary.setState(produce(recipe));
} }