diff --git a/bun.lock b/bun.lock index 068d732a4..fe5ea60ce 100644 --- a/bun.lock +++ b/bun.lock @@ -69,7 +69,6 @@ "@types/bun": "^1.3.1", "@types/lodash-es": "^4.17.12", "@types/lz4js": "^0.2.1", - "@types/steam-user": "^5.1.0", "@types/three": "^0.180.0", "adm-zip": "^0.5.16", "crc": "^4.3.2", @@ -79,7 +78,6 @@ "lz4js": "^0.2.0", "pickleparser": "^0.2.1", "protobufjs": "^7.5.4", - "steam-user": "^5.2.3", "three": "^0.180.0", "ts-proto": "^2.7.7", "yaml": "^2.8.1", @@ -107,6 +105,7 @@ "@types/lodash-es": "^4.17.12", "@types/lz4js": "^0.2.1", "@types/progress": "^2.0.7", + "@types/steam-user": "^5.1.0", "@types/three": "^0.180.0", "crc": "^4.3.2", "csv-parse": "^6.1.0", @@ -120,6 +119,7 @@ "progress": "^2.0.3", "puppeteer": "^24.22.3", "sharp": "^0.34.4", + "steam-user": "^5.2.3", "three": "^0.180.0", "ts-proto": "^2.7.7", "yaml": "^2.8.1", @@ -2613,10 +2613,6 @@ "@blitzkit/bot/prisma": ["prisma@6.17.1", "", { "dependencies": { "@prisma/config": "6.17.1", "@prisma/engines": "6.17.1" }, "peerDependencies": { "typescript": ">=5.1.0" }, "optionalPeers": ["typescript"], "bin": { "prisma": "build/index.js" } }, "sha512-ac6h0sM1Tg3zu8NInY+qhP/S9KhENVaw9n1BrGKQVFu05JT5yT5Qqqmb8tMRIE3ZXvVj4xcRA5yfrsy4X7Yy5g=="], - "@blitzkit/scripts/@types/bun": ["@types/bun@1.3.0", "", { "dependencies": { "bun-types": "1.3.0" } }, "sha512-+lAGCYjXjip2qY375xX/scJeVRmZ5cY0wyHYyCYxNcdEXrQ4AOe3gACgd4iQ8ksOslJtW4VNxBJ8llUwc3a6AA=="], - - "@blitzkit/scripts/fast-xml-parser": ["fast-xml-parser@5.2.5", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ=="], - "@blitzkit/website/vite": ["vite@7.1.9", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-4nVGliEpxmhCL8DslSAUdxlB6+SMrhB0a1v5ijlh1xB1nEPuy1mxaHxysVucLHuWryAxLWg6a5ei+U4TLn/rFg=="], "@discordjs/rest/@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="], @@ -2785,8 +2781,6 @@ "@blitzkit/bot/prisma/@prisma/engines": ["@prisma/engines@6.17.1", "", { "dependencies": { "@prisma/debug": "6.17.1", "@prisma/engines-version": "6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac", "@prisma/fetch-engine": "6.17.1", "@prisma/get-platform": "6.17.1" } }, "sha512-D95Ik3GYZkqZ8lSR4EyFOJ/tR33FcYRP8kK61o+WMsyD10UfJwd7+YielflHfKwiGodcqKqoraWw8ElAgMDbPw=="], - "@blitzkit/scripts/@types/bun/bun-types": ["bun-types@1.3.0", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-u8X0thhx+yJ0KmkxuEo9HAtdfgCBaM/aI9K90VQcQioAmkVp3SG3FkwWGibUFz3WdXAdcsqOcbU40lK7tbHdkQ=="], - "@octokit/endpoint/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@25.1.0", "", {}, "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA=="], "@octokit/graphql/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@25.1.0", "", {}, "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA=="], diff --git a/docs/changelogs/1.29.5.md b/docs/changelogs/1.29.5.md new file mode 100644 index 000000000..8fd2c1609 --- /dev/null +++ b/docs/changelogs/1.29.5.md @@ -0,0 +1,11 @@ +# BlitzKit 1.29.5 + +## Features + +- Added new lighting to be a bit softer on tanks +- Added a button with the sun icon to toggle high graphics +- Added a new reveal and transition animation between tabs + +## Fixes + +- Track wheels on some tanks no longer look translucent diff --git a/package.json b/package.json index 5913b4b5e..837da0e06 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "blitzkit", - "version": "1.29.4", + "version": "1.29.5", "license": "BlitzKit License", "private": true, "scripts": { diff --git a/packages/core/package.json b/packages/core/package.json index fd9e69734..32d782ce9 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -13,7 +13,6 @@ "@types/bun": "^1.3.1", "@types/lodash-es": "^4.17.12", "@types/lz4js": "^0.2.1", - "@types/steam-user": "^5.1.0", "@types/three": "^0.180.0", "adm-zip": "^0.5.16", "crc": "^4.3.2", @@ -23,7 +22,6 @@ "lz4js": "^0.2.0", "pickleparser": "^0.2.1", "protobufjs": "^7.5.4", - "steam-user": "^5.2.3", "three": "^0.180.0", "ts-proto": "^2.7.7", "yaml": "^2.8.1" diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index e34feae46..5550ecc1a 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -6,6 +6,5 @@ export * from "./patreon"; export * from "./protobuf"; export * from "./protos"; export * from "./statistics"; -export * from "./steam"; export * from "./streams"; export * from "./types"; diff --git a/packages/core/src/steam/index.ts b/packages/core/src/steam/index.ts deleted file mode 100644 index d4f7cee39..000000000 --- a/packages/core/src/steam/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./vfs"; diff --git a/packages/scripts/package.json b/packages/scripts/package.json index 05079a6fe..fd7d98399 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -14,6 +14,7 @@ "@types/lodash-es": "^4.17.12", "@types/lz4js": "^0.2.1", "@types/progress": "^2.0.7", + "@types/steam-user": "^5.1.0", "@types/three": "^0.180.0", "crc": "^4.3.2", "csv-parse": "^6.1.0", @@ -27,6 +28,7 @@ "progress": "^2.0.3", "puppeteer": "^24.22.3", "sharp": "^0.34.4", + "steam-user": "^5.2.3", "three": "^0.180.0", "ts-proto": "^2.7.7", "yaml": "^2.8.1" diff --git a/packages/scripts/src/buildAssets/constants.ts b/packages/scripts/src/buildAssets/constants.ts index e9013f625..e6814d55b 100644 --- a/packages/scripts/src/buildAssets/constants.ts +++ b/packages/scripts/src/buildAssets/constants.ts @@ -1,4 +1,5 @@ -import { assertSecret, SteamVFS } from "@blitzkit/core"; +import { assertSecret } from "@blitzkit/core"; +import { SteamVFS } from "../core/steam/vfs"; const APP = 444200; const DEPOT = 444202; diff --git a/packages/scripts/src/core/blitz/extractArmor.ts b/packages/scripts/src/core/blitz/extractArmor.ts index 2973e5a8f..ddfc3c21b 100644 --- a/packages/scripts/src/core/blitz/extractArmor.ts +++ b/packages/scripts/src/core/blitz/extractArmor.ts @@ -2,11 +2,11 @@ import { Hierarchy, Sc2ReadStream, ScgReadStream, - SteamVFS, VertexAttribute, } from "@blitzkit/core"; import { Accessor, Document, Node, Scene } from "@gltf-transform/core"; import { times } from "lodash-es"; +import { SteamVFS } from "../steam/vfs"; import { vertexAttributeGLTFName, vertexAttributeGltfVectorSizes, diff --git a/packages/scripts/src/core/blitz/extractModel/index.ts b/packages/scripts/src/core/blitz/extractModel/index.ts index 20b7ce82e..39340bf2a 100644 --- a/packages/scripts/src/core/blitz/extractModel/index.ts +++ b/packages/scripts/src/core/blitz/extractModel/index.ts @@ -2,7 +2,6 @@ import { Hierarchy, Sc2ReadStream, ScgReadStream, - SteamVFS, Textures, VertexAttribute, } from "@blitzkit/core"; @@ -10,6 +9,7 @@ import { Document, Material, Node, Scene } from "@gltf-transform/core"; import { dedup, prune } from "@gltf-transform/functions"; import { times } from "lodash-es"; import { dirname } from "path"; +import { SteamVFS } from "../../steam/vfs"; import { readBaseColor } from "../readBaseColor"; import { readNormal } from "../readNormal"; import { readRoughnessMetallic } from "../readRoughnessMetallic"; diff --git a/packages/core/src/steam/vfs.ts b/packages/scripts/src/core/steam/vfs.ts similarity index 98% rename from packages/core/src/steam/vfs.ts rename to packages/scripts/src/core/steam/vfs.ts index ec024dad4..13318d883 100644 --- a/packages/core/src/steam/vfs.ts +++ b/packages/scripts/src/core/steam/vfs.ts @@ -1,8 +1,8 @@ +import { readDVPL } from "@blitzkit/core/src/blitz"; import { XMLParser } from "fast-xml-parser"; import { normalize } from "path/posix"; import SteamUser, { EConnectionProtocol } from "steam-user"; import { parse } from "yaml"; -import { readDVPL } from "../blitz"; interface SteamManifestFile { chunks: { diff --git a/packages/website/public/PROPOUND.WAV b/packages/website/public/PROPOUND.WAV index aaece2351..eb717aa86 100644 Binary files a/packages/website/public/PROPOUND.WAV and b/packages/website/public/PROPOUND.WAV differ diff --git a/packages/website/src/components/Armor/components/PrimaryArmorScene.tsx b/packages/website/src/components/Armor/components/PrimaryArmorScene.tsx index 5d016e978..acc4b125c 100644 --- a/packages/website/src/components/Armor/components/PrimaryArmorScene.tsx +++ b/packages/website/src/components/Armor/components/PrimaryArmorScene.tsx @@ -67,7 +67,9 @@ export const PrimaryArmorScene = memo(() => { {armorNodes.map((node) => { const isCurrentTurret = node.name.startsWith( - `turret_${turretModelDefinition.model_id.toString().padStart(2, "0")}` + `turret_${turretModelDefinition.model_id + .toString() + .padStart(2, "0")}` ); const isVisible = isCurrentTurret; const armorId = nameToArmorId(node.name); diff --git a/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/index.tsx b/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/index.tsx index c1220e84d..866299ec5 100644 --- a/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/index.tsx +++ b/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/index.tsx @@ -4,6 +4,7 @@ import { resolvePenetrationCoefficient, } from "@blitzkit/core"; import { invalidate, useFrame } from "@react-three/fiber"; +import type { QuicklimeEvent } from "quicklime"; import { useEffect } from "react"; import { MeshBasicMaterial, @@ -21,6 +22,7 @@ import { jsxTree } from "../../../../core/blitzkit/jsxTree"; import { Duel, type EquipmentMatrix } from "../../../../stores/duel"; import { Tankopedia } from "../../../../stores/tankopedia"; import { TankopediaPersistent } from "../../../../stores/tankopediaPersistent"; +import { transitionEvent } from "../../../Tankopedia/HeroSection/components/TankSandbox/components/Lighting"; import fragmentShader from "./shaders/fragment.glsl?raw"; import vertexShader from "./shaders/vertex.glsl?raw"; import { spacedArmorRenderTarget } from "./target"; @@ -72,6 +74,8 @@ export function PrimaryArmorSceneComponent({ resolution: { value: new Vector2() }, spacedArmorBuffer: { value: null }, spacedArmorDepth: { value: null }, + + opacity: { value: 0 }, }, ]), }); @@ -154,6 +158,10 @@ export function PrimaryArmorSceneComponent({ if (!noInvalidate) invalidate(); } + function handleTransitionEvent(event: QuicklimeEvent) { + material.uniforms.opacity.value = event.data; + } + handleShellChange(); handleGreenPenetrationChange(TankopediaPersistent.state.greenPenetration); handleAdvancedHighlightingChange( @@ -164,6 +172,8 @@ export function PrimaryArmorSceneComponent({ handleProtagonistEquipmentChange(Duel.state.protagonist.equipmentMatrix); handleAntagonistEquipmentChange(Duel.state.antagonist.equipmentMatrix); + transitionEvent.on(handleTransitionEvent); + const unsubscribes = [ Duel.on((state) => state.antagonist.shell, handleShellChange), Tankopedia.on((state) => state.customShell, handleShellChange), @@ -184,12 +194,13 @@ export function PrimaryArmorSceneComponent({ (state) => state.antagonist.equipmentMatrix, (equipment) => handleAntagonistEquipmentChange(equipment) ), + () => transitionEvent.off(handleTransitionEvent), ]; return () => { unsubscribes.forEach((unsubscribe) => unsubscribe()); }; - }); + }, []); useFrame(({ gl, camera }) => { gl.getSize(material.uniforms.resolution.value).multiplyScalar( diff --git a/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/shaders/fragment.glsl b/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/shaders/fragment.glsl index b77093bb1..eb5b86d1d 100644 --- a/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/shaders/fragment.glsl +++ b/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/shaders/fragment.glsl @@ -1,12 +1,9 @@ precision mediump float; -#define USE_FOG - -#include - varying vec3 vNormal; varying vec3 vViewPosition; +uniform float opacity; uniform float thickness; uniform float penetration; uniform float caliber; @@ -119,5 +116,5 @@ void main() { gl_FragColor = vec4(baseColor, (1.0 - penetrationChance) * alpha); } - #include + gl_FragColor.a *= opacity; } \ No newline at end of file diff --git a/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/shaders/vertex.glsl b/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/shaders/vertex.glsl index 2c0140061..ebcd2593a 100644 --- a/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/shaders/vertex.glsl +++ b/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/shaders/vertex.glsl @@ -1,9 +1,5 @@ precision mediump float; -#define USE_FOG - -#include - varying vec3 vNormal; varying vec3 vViewPosition; @@ -14,6 +10,4 @@ void main() { vNormal = normalMatrix * normal; gl_Position = projectionMatrix * mvPosition; - - #include } diff --git a/packages/website/src/components/Armor/components/StaticArmorSceneComponent.tsx b/packages/website/src/components/Armor/components/StaticArmorSceneComponent.tsx index eef50a2da..79186e8bf 100644 --- a/packages/website/src/components/Armor/components/StaticArmorSceneComponent.tsx +++ b/packages/website/src/components/Armor/components/StaticArmorSceneComponent.tsx @@ -24,6 +24,7 @@ import { } from "../../../core/blitzkit/modelTransform"; import { discardClippingPlane } from "../../../core/three/discardClippingPlane"; import { Tankopedia } from "../../../stores/tankopedia"; +import { transitionEvent } from "../../Tankopedia/HeroSection/components/TankSandbox/components/Lighting"; import { ArmorType } from "./SpacedArmorScene"; import type { ArmorUserData, @@ -108,10 +109,11 @@ export function StaticArmorSceneComponent({ () => new MeshBasicMaterial({ color, - opacity, - transparent: opacity < 1, + transparent: true, depthWrite, ...(props.clip ? { clippingPlanes: [props.clip] } : {}), + opacity: 0, + userData: { opacity0: opacity }, }), [thickness] ); @@ -121,6 +123,8 @@ export function StaticArmorSceneComponent({ color: color .clone() .multiplyScalar(props.type === ArmorType.Spaced ? 2 ** 2 : 2 ** -1), + opacity: 0, + transparent: true, }), [thickness] ); @@ -163,11 +167,22 @@ export function StaticArmorSceneComponent({ surfaceMaterial.needsUpdate = true; } + function handleTransitionEvent(event: QuicklimeEvent) { + surfaceMaterial.opacity = surfaceMaterial.userData.opacity0 * event.data; + outlineMaterial.opacity = event.data; + + surfaceMaterial.transparent = surfaceMaterial.opacity < 1; + outlineMaterial.transparent = outlineMaterial.opacity < 1; + } + + transitionEvent.on(handleTransitionEvent); + const unsubscribes = [ Tankopedia.on( (state) => state.highlightArmor?.name, handleHighlightArmor ), + () => transitionEvent.off(handleTransitionEvent), ]; if (props.clip) { diff --git a/packages/website/src/components/HelpingSpotLight.tsx b/packages/website/src/components/HelpingSpotLight.tsx index 5250fa197..85e16a212 100644 --- a/packages/website/src/components/HelpingSpotLight.tsx +++ b/packages/website/src/components/HelpingSpotLight.tsx @@ -1,3 +1,5 @@ +import { Box, Text } from "@radix-ui/themes"; +import { Html } from "@react-three/drei"; import { memo, type ComponentProps } from "react"; interface Props extends ComponentProps<"spotLight"> { @@ -8,10 +10,20 @@ export const HelpingSpotLight = memo(({ debug, ...props }) => { return ( <> {debug && ( - - - - + + + + + + + + + + {props.intensity} @ {JSON.stringify(props.userData)} + + + + )} diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/Options/index.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/Options/index.tsx index c768e9c46..02becaed7 100644 --- a/packages/website/src/components/Tankopedia/HeroSection/components/Options/index.tsx +++ b/packages/website/src/components/Tankopedia/HeroSection/components/Options/index.tsx @@ -12,6 +12,7 @@ import { EyeOpenIcon, ShadowInnerIcon, ShadowNoneIcon, + SunIcon, } from "@radix-ui/react-icons"; import { Button, @@ -55,7 +56,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) { const hasCustomShell = Tankopedia.use( (state) => state.customShell !== undefined ); - const display = Tankopedia.use((state) => state.display); + const requestedDisplay = Tankopedia.use((state) => state.requestedDisplay); const isFullScreen = useFullScreen(); const advancedHighlighting = TankopediaPersistent.use( (state) => state.advancedHighlighting @@ -73,6 +74,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) { const { strings, unwrap } = useLocale(); const revealed = Tankopedia.use((state) => state.revealed); const disturbed = Tankopedia.use((state) => state.disturbed); + const highGraphics = TankopediaPersistent.use((state) => state.highGraphics); return ( <> @@ -84,7 +86,9 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) { gap="2" direction="column" top="50%" - right={display === TankopediaDisplay.DynamicArmor ? "3" : "-4rem"} + right={ + requestedDisplay === TankopediaDisplay.DynamicArmor ? "3" : "-4rem" + } style={{ position: "absolute", transform: "translateY(-50%)", @@ -296,7 +300,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) { > {disturbed && ( <> - {display === TankopediaDisplay.DynamicArmor && ( + {requestedDisplay === TankopediaDisplay.DynamicArmor && ( + { + TankopediaPersistent.mutate((draft) => { + draft.highGraphics = !draft.highGraphics; + }); + }} + color={highGraphics ? undefined : "gray"} + variant={highGraphics ? "solid" : "surface"} + > + + + { Tankopedia.mutate((draft) => { draft.disturbed = true; - draft.display = Number(value); + draft.requestedDisplay = Number(value); }); }} > @@ -430,7 +448,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) { - {display === TankopediaDisplay.DynamicArmor && ( + {requestedDisplay === TankopediaDisplay.DynamicArmor && ( state.scene.fog as Fog | null); - - useEffect(() => { - Tankopedia.mutate((draft) => { - draft.revealed = true; - }); - - if (!fog) return; - - const t0 = Date.now(); - - const interval = setInterval(() => { - const t = (Date.now() - t0) / 1000; - const x = t / fogAnimationTime; - const near = forNear1 + (forNear0 - forNear1) * x; - const far = fogFar1 + (fogFar0 - fogFar1) * x; - - fog.near = near; - fog.far = far; - invalidate(); - - if (x >= 1) { - fog.near = forNear0; - fog.far = fogFar0; - clearInterval(interval); - } - }, 1000 / 60); - - return () => clearInterval(interval); - }, []); - - return null; -} diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/Lighting.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/Lighting.tsx index 659c07b4d..7e69f49c6 100644 --- a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/Lighting.tsx +++ b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/Lighting.tsx @@ -1,82 +1,118 @@ -import { useHelper } from "@react-three/drei"; -import { useFrame } from "@react-three/fiber"; +import { useFrame, useThree } from "@react-three/fiber"; import { times } from "lodash-es"; -import { useRef, useState } from "react"; -import { Color, SpotLight, SpotLightHelper } from "three"; -import { degToRad } from "three/src/math/MathUtils.js"; -import { isHalloween } from "../../../../../../core/blitzkit/isHalloween"; +import { Quicklime } from "quicklime"; +import { useEffect, useRef } from "react"; +import { HemisphereLight, SpotLight, type Group } from "three"; +import { clamp, degToRad, lerp } from "three/src/math/MathUtils.js"; +import { Tankopedia } from "../../../../../../stores/tankopedia"; +import { TankopediaPersistent } from "../../../../../../stores/tankopediaPersistent"; import { HelpingSpotLight } from "../../../../../HelpingSpotLight"; +const ANGLE = Math.PI * 2 ** -2; +const REVEAL_ANIMATION_TIME = 3; +const TRANSITION_ANIMATION_TIME = 0.5; + const LIGHTS_COUNT = 5; +const THETA_OFFSET = degToRad(-152); const LIGHT_DISTANCE = 13; const LIGHT_HEIGHT_0 = 4; const LIGHT_HEIGHT_1 = 2; -const INTENSITY = 50; +const INTENSITY_0 = 3.5; +const INTENSITY_1 = 1.5; +const HEMISPHERE_INTENSITY = 2; -const N = 128; -const TARGET_FRAME_RATE = 30; +export const transitionEvent = new Quicklime(0); export function Lighting() { - const [cast, setCast] = useState(true); - + const wrapper = useRef(null!); const t0 = useRef(0); - const topLight = useRef(null!); - const frontLight = useRef(null!); + const transition = useRef(false); + const clock = useThree((state) => state.clock); + const display = Tankopedia.use((state) => state.display); + const requestedDisplay = Tankopedia.use((state) => state.requestedDisplay); + const animationTime = useRef(REVEAL_ANIMATION_TIME); + const highGraphics = TankopediaPersistent.use((state) => state.highGraphics); - useHelper(topLight, SpotLightHelper, "red"); - useHelper(frontLight, SpotLightHelper, "green"); + useEffect(() => { + Tankopedia.mutate((draft) => { + draft.revealed = true; + }); - const last = useRef(0); - const samples = useRef([]); + t0.current = clock.elapsedTime - REVEAL_ANIMATION_TIME; + }, []); - useFrame(({ clock, frameloop }) => { - if (frameloop === "never") { + useFrame(({ invalidate }) => { + if (transition.current) { + transition.current = false; t0.current = clock.elapsedTime; - samples.current = []; - return; - } - if (!cast || clock.elapsedTime - t0.current < TARGET_FRAME_RATE * N) { - return; } - const dt = clock.elapsedTime - last.current; - last.current = clock.elapsedTime; + const x = clamp( + (clock.elapsedTime - t0.current) / animationTime.current, + 0, + 2 + ); + const t = (0.5 * Math.sin(Math.PI * (x + 0.5)) + 0.5) ** 2; - const fps = 1 / dt; - samples.current.push(fps); + if (t !== transitionEvent.last) { + transitionEvent.dispatch(t); + } - if (samples.current.length > N) samples.current.shift(); + for (const child of wrapper.current.children) { + if (child instanceof SpotLight) { + child.angle = ANGLE * t; + } else if (child instanceof HemisphereLight) { + child.intensity = HEMISPHERE_INTENSITY * t; + } + } - const avg = - samples.current.reduce((a, b) => a + b, 0) / samples.current.length; + if (x < 2) invalidate(); - if (avg < TARGET_FRAME_RATE) setCast(false); + if ( + x > 1 && + Tankopedia.state.display !== Tankopedia.state.requestedDisplay + ) { + Tankopedia.mutate((draft) => { + draft.display = requestedDisplay; + }); + } }); + useEffect(() => { + if (requestedDisplay === display) return; + + transition.current = true; + animationTime.current = TRANSITION_ANIMATION_TIME; + }, [requestedDisplay]); + return ( - <> + + + {times(LIGHTS_COUNT, (index) => { const x = index / (LIGHTS_COUNT - 1); - const theta = 2 * Math.PI * (index / LIGHTS_COUNT) - degToRad(152); + const theta = 2 * Math.PI * (index / LIGHTS_COUNT) + THETA_OFFSET; const position = [ LIGHT_DISTANCE * Math.sin(theta), - LIGHT_HEIGHT_0 * (1 - x) + LIGHT_HEIGHT_1 * x, + lerp(LIGHT_HEIGHT_0, LIGHT_HEIGHT_1, x), LIGHT_DISTANCE * Math.cos(theta), ] as const; - const color = new Color(); + const intensity = lerp(INTENSITY_0, INTENSITY_1, x); return ( ); })} - + ); } diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/SceneProps/index.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/SceneProps/index.tsx index da145ccce..be871c5ef 100644 --- a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/SceneProps/index.tsx +++ b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/SceneProps/index.tsx @@ -4,7 +4,7 @@ import { Quicklime, type QuicklimeEvent } from "quicklime"; import { useEffect, useRef } from "react"; import { Mesh, MeshStandardMaterial } from "three"; -const SIZE = 2 ** 5; +const SIZE = 2 ** 6; export const screenshotReadyEvent = new Quicklime(false); @@ -36,6 +36,7 @@ export function SceneProps() { receiveShadow visible={!screenshotReadyEvent.last!} ref={mesh} + renderOrder={-1} > ); diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/TankModel.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/TankModel.tsx index b6f89c092..54835be29 100644 --- a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/TankModel.tsx +++ b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/TankModel.tsx @@ -106,7 +106,9 @@ export function TankModel() { const isTurret = node.name.startsWith("turret_"); const isCurrentTurret = node.name === - `turret_${turretModelDefinition.model_id.toString().padStart(2, "0")}`; + `turret_${turretModelDefinition.model_id + .toString() + .padStart(2, "0")}`; const isVisible = isCurrentTurret; const position = new Vector2(); const delta = new Vector2(); diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/index.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/index.tsx index b01d3c1bb..ff5b3e28d 100644 --- a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/index.tsx +++ b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/index.tsx @@ -1,12 +1,10 @@ import { BLITZKIT_TANK_ICON_SIZE } from "@blitzkit/core"; -import { invalidate } from "@react-three/fiber"; import { forwardRef, Suspense, useEffect, useImperativeHandle, useRef, - useState, } from "react"; import { Fog } from "three"; import { applyPitchYawLimits } from "../../../../../core/blitz/applyPitchYawLimits"; @@ -29,26 +27,17 @@ import { SmartCanvas } from "../../../../SmartCanvas"; import { AutoClear } from "./components/AutoClear"; import { Controls } from "./components/Control"; import { InitialAligner } from "./components/InitialAligner"; -import { InitialFogReveal } from "./components/InitialFogReveal"; import { Lighting } from "./components/Lighting"; import { SceneProps } from "./components/SceneProps"; import { TankModel } from "./components/TankModel"; -import { TransitionSkeleton } from "./components/TransitionSkeleton"; interface TankSandboxProps { thicknessRange: ThicknessRange; naked?: boolean; } -export const forNear0 = 30; -export const fogFar0 = 50; -export const forNear1 = 0; -export const fogFar1 = 0; -export const fogAnimationTime = 2.5; - export const TankSandbox = forwardRef( ({ thicknessRange, naked }, ref) => { - const fog = useRef(new Fog("black", forNear1, fogFar1)); const canvas = useRef(null); const hasImprovedVerticalStabilizer = useEquipment(122); const hasDownImprovedVerticalStabilizer = useEquipment(124); @@ -58,40 +47,11 @@ export const TankSandbox = forwardRef( const turretModelDefinition = tankModelDefinition.turrets[protagonistTurret.id]; const gunModelDefinition = turretModelDefinition.guns[protagonistGun.id]; - const rawDisplay = Tankopedia.use((state) => state.display); - const [display, setDisplay] = useState(rawDisplay); + const display = Tankopedia.use((state) => state.display); const hideTankModelUnderArmor = TankopediaPersistent.use( (state) => state.hideTankModelUnderArmor ); - useEffect(() => { - if (rawDisplay === display) return; - - const t0 = Date.now(); - const interval = setInterval(() => { - const t = (Date.now() - t0) / 1000; - const x = t / fogAnimationTime; - const y = Math.sin(Math.PI * x); - - const near = forNear0 + (forNear1 - forNear0) * y; - const far = fogFar0 + (fogFar1 - fogFar0) * y; - - fog.current.near = near; - fog.current.far = far; - - invalidate(); - - if (rawDisplay !== display && x >= 0.5) setDisplay(rawDisplay); - if (x >= 1) { - fog.current.near = forNear0; - fog.current.far = fogFar0; - clearInterval(interval); - } - }, 1000 / 60); - - return () => clearInterval(interval); - }, [rawDisplay]); - useImperativeHandle(ref, () => canvas.current!, []); function handlePointerDown() { @@ -188,10 +148,12 @@ export const TankSandbox = forwardRef( } }, [display]); + console.log(TankopediaDisplay[display]); + return ( ( outline: naked ? "1rem red solid" : undefined, }} > + + {!naked && } {(display === TankopediaDisplay.Model || (display === TankopediaDisplay.DynamicArmor && !hideTankModelUnderArmor)) && } - @@ -224,13 +187,11 @@ export const TankSandbox = forwardRef( {/* Controls within Suspense to allow for frame-perfect start of camera auto-rotate */} - {display === TankopediaDisplay.DynamicArmor && } {display === TankopediaDisplay.StaticArmor && ( )} - diff --git a/packages/website/src/components/Tankopedia/HeroSection/index.tsx b/packages/website/src/components/Tankopedia/HeroSection/index.tsx index c96a2d9e6..d40a25583 100644 --- a/packages/website/src/components/Tankopedia/HeroSection/index.tsx +++ b/packages/website/src/components/Tankopedia/HeroSection/index.tsx @@ -1,6 +1,6 @@ import { Box, Flex } from "@radix-ui/themes"; import { times } from "lodash-es"; -import { Suspense, useEffect, useMemo, useRef } from "react"; +import { useEffect, useMemo, useRef } from "react"; import { awaitableTankDefinitions } from "../../../core/awaitables/tankDefinitions"; import { useFullScreen } from "../../../hooks/useFullScreen"; import { Duel } from "../../../stores/duel"; @@ -99,11 +99,11 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) { isFullScreen ? "100vh" : disturbed - ? "calc(100svh - 8rem)" - : { - initial: "28rem", - md: "calc(100svh - 14rem)", - } + ? "calc(100svh - 8rem)" + : { + initial: "28rem", + md: "calc(100svh - 14rem)", + } } maxWidth={isFullScreen ? undefined : "120rem"} flexGrow="1" @@ -120,30 +120,17 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) { position="relative" > - + - - {!skeleton && ( - - )} - + {!skeleton && ( + + )} diff --git a/packages/website/src/stores/tankopedia.ts b/packages/website/src/stores/tankopedia.ts index b699dc977..d5f54b26c 100644 --- a/packages/website/src/stores/tankopedia.ts +++ b/packages/website/src/stores/tankopedia.ts @@ -100,6 +100,7 @@ interface Tankopedia { ); xpMultiplier: (typeof XP_MULTIPLIERS)[number]; customShell?: ShellDefinition; + requestedDisplay: TankopediaDisplay; display: TankopediaDisplay; statSearch?: string; } @@ -114,5 +115,6 @@ export const Tankopedia = new Varuna((model) => ({ skills: createDefaultSkills(skillDefinitions), model, xpMultiplier: 1, + requestedDisplay: TankopediaDisplay.Model, display: TankopediaDisplay.Model, })); diff --git a/packages/website/src/stores/tankopediaPersistent/index.ts b/packages/website/src/stores/tankopediaPersistent/index.ts index 1b1cc2839..ced9cf380 100644 --- a/packages/website/src/stores/tankopediaPersistent/index.ts +++ b/packages/website/src/stores/tankopediaPersistent/index.ts @@ -15,6 +15,7 @@ export interface TankopediaPersistent { showPrimaryArmor: boolean; recentlyViewed: number[]; hideTankModelUnderArmor: boolean; + highGraphics: boolean; sort: { by: TankopediaSortBy; @@ -33,6 +34,7 @@ export const TankopediaPersistent = new Varuna( showExternalModules: true, showPrimaryArmor: true, recentlyViewed: [], + highGraphics: true, sort: { by: "meta.none", direction: "ascending",