diff --git a/bun.lockb b/bun.lockb index dbab5e111..8d88af904 100644 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index f25be3337..50161e684 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "start:bot": "bun dist/bot/index.js", "start:website": "next start", "host:docs": "vitepress preview docs", - "lint": "npx tsc", + "lint": "tsc", "lint:website": "next lint", "format": "prettier --write \"**/*.@(json|js|jsx|css|md|ts|tsx|html|xml|yml|gltf)\"", "knip": "knip", @@ -103,7 +103,7 @@ "knip": "^3.13.2", "prettier": "^3.3.2", "prisma": "^5.16.2", - "typescript": "^5.5.3" + "typescript": "^5.5.4" }, "packageManager": "yarn@1.22.1", "trustedDependencies": [ diff --git a/src/app/tools/tankopedia/[id]/components/Model/TankSandbox.tsx b/src/app/tools/tankopedia/[id]/components/Model/TankSandbox.tsx index 2440819e5..3dda8bec2 100644 --- a/src/app/tools/tankopedia/[id]/components/Model/TankSandbox.tsx +++ b/src/app/tools/tankopedia/[id]/components/Model/TankSandbox.tsx @@ -12,6 +12,7 @@ import { useEquipment } from '../../../../../../hooks/useEquipment'; import * as Duel from '../../../../../../stores/duel'; import * as TankopediaEphemeral from '../../../../../../stores/tankopediaEphemeral'; import * as TankopediaPersistent from '../../../../../../stores/tankopediaPersistent'; +import { TankopediaDisplay } from '../../../../../../stores/tankopediaPersistent/constants'; import { Controls } from '../Control'; import { Lighting } from '../Lighting'; import { SceneProps } from '../SceneProps'; @@ -30,7 +31,7 @@ export function TankSandbox() { const turretModelDefinition = tankModelDefinition.turrets[protagonist.turret.id]; const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id]; - const armorMode = TankopediaPersistent.use((state) => state.armorMode); + const display = TankopediaPersistent.use((state) => state.display); function handlePointerDown() { window.addEventListener('pointermove', handlePointerMove); @@ -123,6 +124,20 @@ export function TankSandbox() { }); }, [protagonist.gun, protagonist.turret]); + useEffect(() => { + if (display !== TankopediaDisplay.DynamicArmor) { + mutateTankopediaEphemeral((draft) => { + draft.shot = undefined; + }); + } + + if (display !== TankopediaDisplay.StaticArmor) { + mutateTankopediaEphemeral((draft) => { + draft.highlightArmor = undefined; + }); + } + }, [display]); + return ( - + {display !== TankopediaDisplay.StaticArmor && } {/* idk why the shot display doesn't work without suspense here lol */} }> - {armorMode === 'blitz' && } - {armorMode === 'static' && ( + {display === TankopediaDisplay.DynamicArmor && } + {display === TankopediaDisplay.StaticArmor && ( )} 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 cac44b1b1..46d1f6d53 100644 --- a/src/app/tools/tankopedia/[id]/components/Model/components/Options.tsx +++ b/src/app/tools/tankopedia/[id]/components/Model/components/Options.tsx @@ -14,8 +14,8 @@ import { Heading, IconButton, Popover, + SegmentedControl, Strong, - Switch, Tabs, Text, } from '@radix-ui/themes'; @@ -25,6 +25,7 @@ import { SmallTankIcon } from '../../../../../../../components/SmallTankIcon'; import { resolveNearPenetration } from '../../../../../../../core/blitz/resolveNearPenetration'; import { resolvePenetrationCoefficient } from '../../../../../../../core/blitz/resolvePenetrationCoefficient'; import { asset } from '../../../../../../../core/blitzkit/asset'; +import { imgur } from '../../../../../../../core/blitzkit/imgur'; import { Pose, poseEvent } from '../../../../../../../core/blitzkit/pose'; import { SHELL_NAMES, @@ -38,6 +39,7 @@ import * as App from '../../../../../../../stores/app'; import * as Duel from '../../../../../../../stores/duel'; import * as TankopediaEphemeral from '../../../../../../../stores/tankopediaEphemeral'; import * as TankopediaPersistent from '../../../../../../../stores/tankopediaPersistent'; +import { TankopediaDisplay } from '../../../../../../../stores/tankopediaPersistent/constants'; import { TankSearch } from '../../../../components/TankSearch'; import { ENVIRONMENTS } from '../../Lighting'; import { RotationInputs } from '../../QuickInputs'; @@ -45,9 +47,10 @@ import { RotationInputs } from '../../QuickInputs'; export function Options() { const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation(); const mutateTankopediaPersistent = TankopediaPersistent.useMutation(); - const modeRaw = TankopediaPersistent.use((state) => state.mode); - const [mode, setMode] = - useState('model'); + const displayRaw = TankopediaPersistent.use((state) => state.display); + const [display, setDisplay] = useState( + TankopediaDisplay.Model, + ); const isFullScreen = useFullScreen(); const showGrid = TankopediaPersistent.use( (state) => state.model.visual.showGrid, @@ -81,7 +84,7 @@ export function Options() { const hasEnhancedArmor = useEquipment(110); const antagonistUniqueGuns = uniqueGuns(antagonistTank.turrets); - useEffect(() => setMode(modeRaw), [modeRaw]); + useEffect(() => setDisplay(displayRaw), [displayRaw]); return ( <> @@ -112,7 +115,7 @@ export function Options() { - {mode === 'armor' && ( + {display === TankopediaDisplay.DynamicArmor && ( - {mode === 'armor' && ( + {display === TankopediaDisplay.DynamicArmor && ( Shooter: @@ -470,25 +473,30 @@ export function Options() { )} - { + { mutateTankopediaPersistent((draft) => { - draft.mode = draft.mode === 'armor' ? 'model' : 'armor'; + draft.display = Number(value); }); }} - mr="1" > - - - Armor - - + + + + + + + + + + + + + + + + @@ -511,7 +519,6 @@ export function Options() { - @@ -520,7 +527,7 @@ export function Options() { - {mode === 'armor' && ( + {display !== TankopediaDisplay.Model && ( <> Armor @@ -622,7 +629,6 @@ export function Options() { - {fullScreenAvailable && ( { const mutateDuel = Duel.useMutation(); @@ -38,32 +39,6 @@ export const TankModel = memo(() => { const { gltf } = useModel(protagonist.tank.id); const nodes = Object.values(gltf.nodes); - useEffect(() => { - function handleVisibility() { - if (!hullContainer.current) return; - - const state = tankopediaPersistentStore.getState(); - - hullContainer.current.visible = - state.mode === 'model' || state.armorMode === 'blitz'; - } - - const unsubscribes = [ - tankopediaPersistentStore.subscribe( - (state) => state.mode, - handleVisibility, - ), - tankopediaPersistentStore.subscribe( - (state) => state.armorMode, - handleVisibility, - ), - ]; - - handleVisibility(); - - return () => unsubscribes.forEach((unsubscribe) => unsubscribe()); - }, []); - return ( {nodes.map((node) => { @@ -92,7 +67,8 @@ export const TankModel = memo(() => { function onPointerDown(event: ThreeEvent) { if ( isTrack && - tankopediaPersistentStore.getState().mode === 'model' + tankopediaPersistentStore.getState().display === + TankopediaDisplay.Model ) { position.set(event.clientX, event.clientY); event.stopPropagation(); diff --git a/src/app/tools/tankopedia/[id]/components/QuickInputs/index.tsx b/src/app/tools/tankopedia/[id]/components/QuickInputs/index.tsx index bc29f25e2..3c04fd8e7 100644 --- a/src/app/tools/tankopedia/[id]/components/QuickInputs/index.tsx +++ b/src/app/tools/tankopedia/[id]/components/QuickInputs/index.tsx @@ -1,5 +1,5 @@ import { HeightIcon, WidthIcon } from '@radix-ui/react-icons'; -import { Box, Flex, TextField } from '@radix-ui/themes'; +import { Flex, TextField } from '@radix-ui/themes'; import { use, useEffect, useRef } from 'react'; import { degToRad, radToDeg } from 'three/src/math/MathUtils'; import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits'; @@ -27,8 +27,6 @@ export function RotationInputs() { const initialGunPitch = tankModelDefinition.turretRotation?.pitch ?? 0; const hasImprovedVerticalStabilizer = useEquipment(122); const isFullScreen = useFullScreen(); - const mode = TankopediaPersistent.use((state) => state.mode); - const armorMode = TankopediaPersistent.use((state) => state.armorMode); const mutateTankopediaPersistent = TankopediaPersistent.useMutation(); useEffect(() => { @@ -61,9 +59,7 @@ export function RotationInputs() { return ( - - { - const value = Number(yawInput.current!.value); - if (isNaN(value)) { - yawInput.current!.value = radToDeg(protagonist.yaw).toFixed(1); - return; - } - const [pitch, yaw] = applyPitchYawLimits( - protagonist.pitch, - degToRad(value), - gunModelDefinition.pitch, - turretModelDefinition.yaw, - hasImprovedVerticalStabilizer, - ); - modelTransformEvent.emit({ pitch, yaw }); - mutateDuel((state) => { - state.protagonist.pitch = pitch; - state.protagonist.yaw = yaw; - }); + { + const value = Number(yawInput.current!.value); + if (isNaN(value)) { yawInput.current!.value = radToDeg(protagonist.yaw).toFixed(1); - }} - onKeyDown={(event) => { - if (event.key === 'Enter') { - yawInput.current?.blur(); - } - }} - onFocus={() => yawInput.current?.focus()} - ref={yawInput} - > - - - - ° - - - { + state.protagonist.pitch = pitch; + state.protagonist.yaw = yaw; + }); + yawInput.current!.value = radToDeg(protagonist.yaw).toFixed(1); + }} + onKeyDown={(event) => { + if (event.key === 'Enter') { + yawInput.current?.blur(); + } + }} + onFocus={() => yawInput.current?.focus()} + ref={yawInput} + > + { - const value = Number(pitchInput.current!.value); - if (isNaN(value)) { - pitchInput.current!.value = ( - -radToDeg(protagonist.pitch) + initialGunPitch - ).toFixed(1); - return; - } - const [pitch, yaw] = applyPitchYawLimits( - degToRad(-value + initialGunPitch), - protagonist.yaw, - gunModelDefinition.pitch, - turretModelDefinition.yaw, - hasImprovedVerticalStabilizer, - ); - modelTransformEvent.emit({ pitch, yaw }); - mutateDuel((state) => { - state.protagonist.pitch = pitch; - state.protagonist.yaw = yaw; - }); + > + + + ° + + + { + const value = Number(pitchInput.current!.value); + if (isNaN(value)) { pitchInput.current!.value = ( -radToDeg(protagonist.pitch) + initialGunPitch ).toFixed(1); - }} - onKeyDown={(event) => { - if (event.key === 'Enter') { - pitchInput.current?.blur(); - } - }} - onFocus={() => pitchInput.current?.focus()} - ref={pitchInput} - > - - - - - ° - - - - - - { + state.protagonist.pitch = pitch; + state.protagonist.yaw = yaw; + }); + pitchInput.current!.value = ( + -radToDeg(protagonist.pitch) + initialGunPitch + ).toFixed(1); + }} + onKeyDown={(event) => { + if (event.key === 'Enter') { + pitchInput.current?.blur(); + } + }} + onFocus={() => pitchInput.current?.focus()} + ref={pitchInput} + > + { - mutateTankopediaPersistent((draft) => { - draft.armorMode = 'blitz'; - }); - }} - /> - { - mutateTankopediaPersistent((draft) => { - draft.armorMode = 'static'; - }); + userSelect: 'none', }} > - - - - + + + + ° + + ); } diff --git a/src/components/Armor/components/CoreArmorScene.tsx b/src/components/Armor/components/CoreArmorScene.tsx index 06e9b7cae..377294a63 100644 --- a/src/components/Armor/components/CoreArmorScene.tsx +++ b/src/components/Armor/components/CoreArmorScene.tsx @@ -1,4 +1,4 @@ -import { memo, useEffect, useRef } from 'react'; +import { memo, useRef } from 'react'; import { Group } from 'three'; import { correctZYTuple } from '../../../core/blitz/correctZYTuple'; import { nameToArmorId } from '../../../core/blitzkit/nameToArmorId'; @@ -31,26 +31,11 @@ export const CoreArmorScene = memo(() => { const turretOrigin = correctZYTuple(tankModelDefinition.turretOrigin); const gunOrigin = correctZYTuple(turretModelDefinition.gunOrigin); const tankopediaPersistentStore = TankopediaPersistent.useStore(); - const initialTankopediaState = tankopediaPersistentStore.getState(); useTankTransform(protagonist, turretContainer, gunContainer); - useEffect(() => { - const unsubscribe = tankopediaPersistentStore.subscribe( - (state) => state.mode, - (mode) => { - if (wrapper.current) wrapper.current.visible = mode === 'armor'; - }, - ); - - return unsubscribe; - }); - return ( - + {armorNodes.map((node) => { const isHull = node.name.startsWith('hull_'); diff --git a/src/components/StaticArmor/components/StaticArmorScene.tsx b/src/components/StaticArmor/components/StaticArmorScene.tsx index 299dd5193..6e2c481f0 100644 --- a/src/components/StaticArmor/components/StaticArmorScene.tsx +++ b/src/components/StaticArmor/components/StaticArmorScene.tsx @@ -1,4 +1,4 @@ -import { memo, useEffect, useMemo, useRef } from 'react'; +import { memo, useMemo, useRef } from 'react'; import { Group, Plane, Scene, Vector3 } from 'three'; import { correctZYTuple } from '../../../core/blitz/correctZYTuple'; import { nameToArmorId } from '../../../core/blitzkit/nameToArmorId'; @@ -9,7 +9,6 @@ import { useModel } from '../../../hooks/useModel'; import { useModelDefinitions } from '../../../hooks/useModelDefinitions'; import { useTankTransform } from '../../../hooks/useTankTransform'; import * as Duel from '../../../stores/duel'; -import * as TankopediaPersistent from '../../../stores/tankopediaPersistent'; import { ModelTankWrapper } from '../../Armor/components/ModelTankWrapper'; import { ArmorType } from '../../Armor/components/SpacedArmorScene'; import { SpacedArmorSceneComponent } from '../../Armor/components/SpacedArmorSceneComponent'; @@ -27,7 +26,6 @@ export interface ThicknessRange { export const StaticArmorScene = memo( ({ scene, awaitedTankDefinitions }) => { const wrapper = useRef(null); - const tankopediaPersistentStore = TankopediaPersistent.useStore(); const awaitedTankModelDefinitions = useModelDefinitions(); const turretContainer = useRef(null); const gunContainer = useRef(null); @@ -98,22 +96,6 @@ export const StaticArmorScene = memo( useTankTransform(protagonist, turretContainer, gunContainer); - useEffect(() => { - function handleMode(mode: TankopediaPersistent.TankopediaMode) { - if (!wrapper.current) return; - wrapper.current.visible = mode === 'armor'; - } - - const unsubscribe = tankopediaPersistentStore.subscribe( - (state) => state.mode, - handleMode, - ); - - handleMode(tankopediaPersistentStore.getState().mode); - - return unsubscribe; - }, []); - return ( diff --git a/src/stores/tankopediaPersistent/constants.ts b/src/stores/tankopediaPersistent/constants.ts index d0e039418..10d61cd54 100644 --- a/src/stores/tankopediaPersistent/constants.ts +++ b/src/stores/tankopediaPersistent/constants.ts @@ -58,3 +58,8 @@ export const SORT_NAMES = { 'maneuverability.weight': 'weight', 'maneuverability.traverseSpeed': 'traverse speed', } as const; +export enum TankopediaDisplay { + Model, + DynamicArmor, + StaticArmor, +} diff --git a/src/stores/tankopediaPersistent/index.ts b/src/stores/tankopediaPersistent/index.ts index 40b90dccc..3c036d878 100644 --- a/src/stores/tankopediaPersistent/index.ts +++ b/src/stores/tankopediaPersistent/index.ts @@ -7,14 +7,11 @@ import { ENVIRONMENTS } from '../../app/tools/tankopedia/[id]/components/Lightin import { TankClass, TreeType } from '../../components/Tanks'; import { Tier } from '../../core/blitzkit/tankDefinitions'; import { createNextSafeStore } from '../../core/zustand/createNextSafeStore'; -import { SORT_NAMES } from './constants'; +import { SORT_NAMES, TankopediaDisplay } from './constants'; export type TankopediaSortBy = keyof typeof SORT_NAMES; export type TankopediaSortDirection = 'ascending' | 'descending'; export type TankopediaTestTankDisplay = 'include' | 'exclude' | 'only'; -export type TankopediaMode = 'model' | 'armor'; -export type TankopediaArmorMode = 'blitz' | 'static'; - export interface TankopediaPersistent { model: { visual: { @@ -39,8 +36,7 @@ export interface TankopediaPersistent { test: TankopediaTestTankDisplay; page: number; }; - mode: TankopediaMode; - armorMode: TankopediaArmorMode; + display: TankopediaDisplay; } export const { Provider, use, useMutation, useStore } = createNextSafeStore( @@ -71,8 +67,7 @@ export const { Provider, use, useMutation, useStore } = createNextSafeStore( test: 'include', page: 0, }, - mode: 'model', - armorMode: 'blitz', + display: TankopediaDisplay.Model, })), { name: 'tankopedia', merge: (a, b) => merge(b, a) }, ), diff --git a/tsconfig.json b/tsconfig.json index c910f50ff..3cf8ab563 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -21,5 +21,5 @@ ] }, "include": ["src/**/*", "scripts/**/*", "dist/website/types/**/*.ts"], - "exclude": ["node_modules", "dist", "scripts"] + "exclude": ["node_modules/**/*", "dist"] }