Compare commits

...
13 Commits
Author SHA1 Message Date
tresabhi 5cae5d1583 models shit 2026-09-30 17:56:31 -05:00
tresabhi 5c88bda07a CanvasControl 2026-09-30 17:48:48 -05:00
tresabhi 19dbd8ca2e SpacedArmorSubSpaced 2026-09-30 17:47:23 -05:00
tresabhi f17ac2b575 SpacedArmorSubExternal 2026-09-30 17:45:11 -05:00
tresabhi 5d7482b158 SpacedArmorSceneComponent 2026-09-30 17:41:46 -05:00
tresabhi 22aff6188a SpacedArmorScene 2026-09-30 17:38:27 -05:00
tresabhi 1ee6cdabe5 SpacedArmorScene 2026-09-30 17:36:51 -05:00
tresabhi 480d2226f7 versions and shit 2026-09-30 17:32:39 -05:00
tresabhi c42b109974 Merge branch 'main' into data-driven 2026-09-30 15:52:42 -05:00
tresabhi 9537349664 add dependabot 2026-09-30 15:49:19 -05:00
tresabhi 8793c9a9c9 simplify readme 2026-09-30 15:44:51 -05:00
tresabhi 4d57b43912 logs 2026-09-30 15:41:08 -05:00
tresabhi ee146b8ed9 fix wgpkg dirs 2026-09-30 13:51:13 -05:00
26 changed files with 275 additions and 250 deletions

No files matched your search

+6
View File
@@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: "bun"
directory: "/"
schedule:
interval: "daily"
-33
View File
@@ -5,36 +5,3 @@ Tools for everything and anything, World of Tanks Blitz.
This GitHub repository contains the code for BlitzKit and all its services. If you are here, looking to use BlitzKit, go to https://blitzkit.app/.
If you intend to contribute to the code, note that you may need to check out sensitive, closed-source submodules to locally develop certain aspects of BlitzKit. Communicate with `@tresabhi` on Discord to get started or discuss sensitive content, such as environment variables or submodules.
## Packages
| Package | Title | Source | Memo |
| ------------------------------------ | -------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| [packages/core](packages/core) | | | Mutual internal code shared between all other packages. |
| [packages/i18n](packages/i18n) | | [Crowdin](https://crowdin.com/project/blitzkrieg) | Translations served as JSON shared across all other packages, including some utility code. |
| [packages/scripts](packages/scripts) | | | CLI utilities for the development and deployment of BlitzKit services. |
| [packages/website](packages/website) | BlitzKit | https://blitzkit.app | The BlitzKit website provides a wide range of tools, APIs, and interconnects all other services. |
## Submodules
| Submodule | Title | Source | Memo |
| ------------------------------------------------------ | --------- | ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [submodules/blitzkit-closed](packages/blitzkit-closed) | ClosedKit | [blitzkit/closed](https://github.com/blitzkit/closed) | Closed source, sensitive code hidden from the public to prevent bad actors from abusing the game. Discuss with `@tresabhi` on Discord for **special permission** to access. |
## Environment Variables
| Name | Source | BlitzKit Default | Memo |
| ------------------------------------------------------------- | -------------------------------------------------------------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `PUBLIC_WARGAMING_APPLICATION_ID` | [Wargaming Developer's Room](https://developers.wargaming.net/) | | Application ID needed to communicate with Wargaming APIs. |
| `PUBLIC_PROMOTE_OPENTEST` | | `false` | Optional boolean dictating whether the website advertises [the opentest subdomain of BlitzKit](https://opentest.blitzkit.app/). |
| `WOTB_GLOSSARY`, `WOTB_GLOSSARY_DOMAIN`, `WOTB_GLOSSARY_PATH` | | | |
| `PUBLIC_GOOGLE_TAG_MANAGER_ID` | [Google Tag Manager](https://tagmanager.google.com/) | `G-GL2JVHCGPQ` | Google Tag Manager's unique identifier for analytical purposes. |
| `PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID` | [Google Analytics](https://analytics.google.com/) | `430711767` | Google Analytics' unique property ID for [BlitzKit's apex domain](https://blitzkit.app/) and subdomains. |
| `GOOGLE_APPLICATION_CREDENTIALS` | [Google Cloud](https://cloud.google.com/) | | Google Cloud's API key, primarily used for ranking popular tanks and finding relevant tank review videos in Tankopedia. |
| `PUBLIC_DISCORD_SERVER_ID` | [BlitzKit Discord Server](https://discord.gg/nDt7AjGJQH) | `734786591205359697` | BlitzKit Discord Server's id. |
| `PUBLIC_ASSET_BASE` | [blitzkit/assets](https://github.com/blitzkit/assets) | `https://blitzkit.github.io/assets` | The root domain and path that serves the external assets like tank models and icons. |
| `PUBLIC_GUIDE_WEBSITE` | [GuidesBlitz](https://guidesblitz.com/) | `https://guidesblitz.com` | The website that serves the encyclopedia-like content in BlitzKit. |
| `SERVICING` | | `false` | Wether the website is down for servicing. |
| `GH_TOKEN` | [GitHub Fine-Grained Tokens](https://github.com/settings/personal-access-tokens) | | The GitHub API key used for checking out private submodules and pushing content to the asset repository. |
| `DATABASE_URL` | [CockroachDB](https://www.cockroachlabs.com/) | | The CockroachDB-SQL database URL used to link Wargaming and Discord users. |
| `WOTB_DLC_CDN` | | | |
+14 -16
View File
@@ -112,8 +112,8 @@
"@react-three/drei": "^10.7.6",
"@react-three/fiber": "^9.4.0",
"@types/lodash-es": "^4.17.12",
"@types/react": "^19.2.2",
"@types/react-dom": "^19.2.2",
"@types/react": "^19.3.0",
"@types/react-dom": "^19.3.0",
"@types/three": "^0.180.0",
"@vite-pwa/astro": "^1.1.0",
"astro": "^5.14.4",
@@ -128,11 +128,11 @@
"pwa-install-handler": "^2.6.3",
"quicklime": "^1.2.0",
"radix-ui": "^1.6.7",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react": "^19.3.0",
"react-dom": "^19.3.0",
"react-error-boundary": "^6.1.3",
"react-promise-suspense": "^0.3.4",
"soapstone": "^1.0.0",
"soapstone": "^1.0.1",
"three": "^0.180.0",
"three-stdlib": "^2.36.0",
"transliteration": "^2.6.1",
@@ -939,9 +939,9 @@
"@types/progress": ["@types/[email protected]", "", { "dependencies": { "@types/node": "*" } }, "sha512-iadjw02vte8qWx7U0YM++EybBha2CQLPGu9iJ97whVgJUT5Zq9MjAPYUnbfRI2Kpehimf1QjFJYxD0t8nqzu5w=="],
"@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="],
"@types/react": ["@types/react@19.3.0", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg=="],
"@types/react-dom": ["@types/react-dom@19.2.2", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw=="],
"@types/react-dom": ["@types/react-dom@19.3.0", "", { "peerDependencies": { "@types/react": "^19.3.0" } }, "sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q=="],
"@types/react-reconciler": ["@types/[email protected]", "", { "peerDependencies": { "@types/react": "*" } }, "sha512-RsqPttsBQ+6af0nATFXJJpemYQH7kL9+xLNm1z+0MjQFDKBZDM2R6SBrjdvRmHu9i9fM6povACj57Ft+pKRNOA=="],
@@ -1687,7 +1687,7 @@
"lodash": ["[email protected]", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
"lodash-es": ["[email protected]7.23", "", {}, "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg=="],
"lodash-es": ["[email protected]8.1", "", {}, "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A=="],
"lodash.debounce": ["[email protected]", "", {}, "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="],
@@ -2003,9 +2003,9 @@
"range-parser": ["[email protected]", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
"react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="],
"react": ["react@19.3.0", "", {}, "sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog=="],
"react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="],
"react-dom": ["react-dom@19.3.0", "", { "dependencies": { "scheduler": "^0.28.0" }, "peerDependencies": { "react": "^19.3.0" } }, "sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q=="],
"react-error-boundary": ["[email protected]", "", { "peerDependencies": { "@types/react": "^18.0.0 || ^19.0.0", "react": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-GnSKpCohFi2nQmJCWwP8O8wub7zexlePvpsejvQr35vS5RTouS1+utTNOmyc540yw5vyOXnSL1rBWsCQDmkyUA=="],
@@ -2153,7 +2153,7 @@
"smol-toml": ["[email protected]", "", {}, "sha512-rInDH6lCNiEyn3+hH8KVGFdbjc099j47+OSgbMrfDYX1CmXLfdKd7qi6IfcWj2wFxvSVkuI46M+wPGYfEOEj6g=="],
"soapstone": ["[email protected].0", "", { "dependencies": { "immer": "^11.1.8", "lodash-es": "^4.18.1", "react": "^19.2.7" }, "peerDependencies": { "typescript": "^5" } }, "sha512-Jlf8aCY9aCvgQR3y/aGhkXyC7vAox05J+dply+0wm567UIOYlCiwSR1MhAkZIRIXtOnoUERBrVz4mDYBrQYBDA=="],
"soapstone": ["[email protected].1", "", { "dependencies": { "immer": "^11.1.8", "lodash-es": "^4.18.1" }, "peerDependencies": { "react": "^19.3.0", "typescript": "^5" } }, "sha512-5f6iW2Hybjqcvmn+P+X1gH1NKDhry+ePk4F1rqK0yeFjNICx7tqy0X315tQD0Lhv/zUTNA9dV+8BLW9/o7Wlpw=="],
"socks": ["[email protected]", "", { "dependencies": { "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" } }, "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A=="],
@@ -2563,6 +2563,8 @@
"@babel/preset-env/semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"@blitzkit/scripts/lodash-es": ["[email protected]", "", {}, "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg=="],
"@jimp/core/file-type": ["[email protected]", "", { "dependencies": { "readable-web-to-node-stream": "^3.0.0", "strtok3": "^6.2.4", "token-types": "^4.1.1" } }, "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw=="],
"@octokit/endpoint/@octokit/types": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/openapi-types": "^25.1.0" } }, "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g=="],
@@ -2671,7 +2673,7 @@
"proxy-agent/socks-proxy-agent": ["[email protected]", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="],
"react-dom/scheduler": ["[email protected]7.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
"react-dom/scheduler": ["[email protected]8.0", "", {}, "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw=="],
"readable-stream/safe-buffer": ["[email protected]", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
@@ -2685,10 +2687,6 @@
"soapstone/immer": ["[email protected]", "", {}, "sha512-EQyQtLiYW029lyoczMl/Hh4Xu7cDecSc58JRYpHyL4tIAu3eqd1yJzQX04d2BZHDkzFFvm6qJEJWOtfDSWAXbQ=="],
"soapstone/lodash-es": ["[email protected]", "", {}, "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A=="],
"soapstone/react": ["[email protected]", "", {}, "sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog=="],
"source-map-support/source-map": ["[email protected]", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
"stats-gl/three": ["[email protected]", "", {}, "sha512-FQK+LEpYc0fBD+J8g6oSEyyNzjp+Q7Ks1C568WWaoMRLW+TkNNWmenWeGgJjV105Gd+p/2ql1ZcjYvNiPZBhuQ=="],
+14
View File
@@ -0,0 +1,14 @@
# BlitzKit 1.31.4
Thank you to [Mark Greene](https://github.com/Hitjack007) for your contributions this update.
## Fixes
- [Mark Greene] BlitzKit Session now properly handles tanks removed from the game
## Technical Fixes
- BlitzKit now handles the new ID system for game-modes in WoTB
- The directory which preview versions of the game are downloaded to are now created properly
- This was the last bug standing in the way of BlitzKit Previews
- Welcome back, BlitzKit Previews!
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "blitzkit",
"version": "1.31.3",
"version": "1.31.4",
"license": "BlitzKit License",
"private": true,
"scripts": {
@@ -33,6 +33,8 @@ import { parse as parseYaml } from "yaml";
import { AbstractBlitzKitAPI } from "../abstract";
export abstract class ServerBlitzKitAPI0 extends AbstractBlitzKitAPI {
// TODO: move most of these to their respective constructors
protected vfs: AbstractVFS;
protected parsePath?: typeof parse;
protected extname?: typeof extname;
@@ -5,17 +5,18 @@ import { merge } from "lodash-es";
import { Cache } from "./0_base";
import { ServerBlitzKitAPI11 } from "./11_scripts";
const globbedStrings = import.meta.glob("../../../../../i18n/strings/*.json", {
import: "default",
});
export abstract class ServerBlitzKitAPI12 extends ServerBlitzKitAPI11 {
private globbedStrings = import.meta.glob(
"../../../../../i18n/strings/*.json",
{ import: "default" },
);
@Cache()
async strings(locale: string) {
const localized = (await globbedStrings[
const localized = (await this.globbedStrings[
`../../../../../i18n/strings/${locale}.json`
]()) as DeepPartial<Strings>;
const defaults = (await globbedStrings[
const defaults = (await this.globbedStrings[
`../../../../../i18n/strings/${locales.default}.json`
]()) as Strings;
const strings = merge({}, defaults, localized);
+2 -1
View File
@@ -78,7 +78,8 @@ if (provider === "steam") {
console.log(`Downloading ${urls.length} packages...`);
await rm(WGPKG_DIR, { recursive: true });
await rm(WGPKG_DIR, { recursive: true, force: true });
await mkdir(WGPKG_DIR, { recursive: true });
const inputContent = urls
.map((url) => {
+5 -5
View File
@@ -25,8 +25,8 @@
"@react-three/drei": "^10.7.6",
"@react-three/fiber": "^9.4.0",
"@types/lodash-es": "^4.17.12",
"@types/react": "^19.2.2",
"@types/react-dom": "^19.2.2",
"@types/react": "^19.3.0",
"@types/react-dom": "^19.3.0",
"@types/three": "^0.180.0",
"@vite-pwa/astro": "^1.1.0",
"astro": "^5.14.4",
@@ -41,11 +41,11 @@
"pwa-install-handler": "^2.6.3",
"quicklime": "^1.2.0",
"radix-ui": "^1.6.7",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react": "^19.3.0",
"react-dom": "^19.3.0",
"react-error-boundary": "^6.1.3",
"react-promise-suspense": "^0.3.4",
"soapstone": "^1.0.0",
"soapstone": "^1.0.1",
"three": "^0.180.0",
"three-stdlib": "^2.36.0",
"transliteration": "^2.6.1",
@@ -12,8 +12,8 @@ import { useRef } from "react";
import { radToDeg } from "three/src/math/MathUtils.js";
import { resolveArmorIndex } from "../core/blitzkit/resolveArmorIndex";
import { LocaleProvider, useLocale } from "../hooks/useLocale";
import { useStrings } from "../hooks/useStrings";
import { App } from "../stores/app";
import { Duel } from "../stores/duel";
import { Tankopedia } from "../stores/tankopedia";
import { layerTypeNames } from "./ShotDisplayCard";
import { ArmorType } from "./SpacedArmorScene";
@@ -22,7 +22,8 @@ export function ArmorPlateDisplay() {
const highlightArmor = Tankopedia.use((state) => state.highlightArmor);
const developerMode = App.useDeferred((state) => state.developerMode, false);
const input = useRef<HTMLInputElement>(null);
const { locale, strings } = useLocale();
const locale = useLocale();
const strings = useStrings();
if (highlightArmor === undefined) return null;
@@ -5,12 +5,12 @@ import type { QuicklimeEvent } from "quicklime";
import { useEffect, useRef } from "react";
import { PerspectiveCamera, Vector3 } from "three";
import { OrbitControls as OrbitControlsClass } from "three-stdlib";
import { Duel } from "../../../../../../stores/duel";
import { api } from "../api/dynamic";
import { applyPitchYawLimits } from "../core/blitz/applyPitchYawLimits";
import { hasEquipment } from "../core/blitzkit/hasEquipment";
import { Pose, poseEvent } from "../core/blitzkit/pose";
import { controlsEnabledEvent } from "../core/controlsEnabled";
import { DuelModule, getDuel, useDuel } from "../hooks/useDuel";
import { hasEquipment } from "../hooks/useEquipment";
import { Tankopedia } from "../stores/tankopedia";
const poseDistances: Record<Pose, number> = {
@@ -37,12 +37,12 @@ export function CanvasControl({
const camera = useThree((state) => state.camera as PerspectiveCamera);
const canvas = useThree((state) => state.gl.domElement);
const orbitControls = useRef<OrbitControlsClass>(null);
const protagonistTurret = Duel.use((state) => state.protagonist.turret);
const antagonistTurret = Duel.use((state) => state.antagonist.turret);
const protagonistTrack = Duel.use((state) => state.protagonist.track);
const protagonistTank = Duel.use((state) => state.protagonist.tank);
const antagonistTank = Duel.use((state) => state.antagonist.tank);
const protagonistGun = Duel.use((state) => state.protagonist.gun);
const protagonistTurret = useDuel("protagonist", DuelModule.Turret);
const antagonistTurret = useDuel("antagonist", DuelModule.Turret);
const protagonistTrack = useDuel("protagonist", DuelModule.Track);
const protagonistTank = useDuel("protagonist", DuelModule.Tank);
const antagonistTank = useDuel("antagonist", DuelModule.Tank);
const protagonistGun = useDuel("protagonist", DuelModule.Gun);
const protagonistModelDefinition =
modelDefinitions.models[protagonistTank.id];
const protagonistTrackModelDefinition =
@@ -89,15 +89,17 @@ export function CanvasControl({
controlsEnabledEvent.on(handleControlsEnabled);
function handlePoseEvent(event: Pose) {
const protagonist = getDuel("protagonist", DuelModule.Tank);
const hasImprovedVerticalStabilizer = hasEquipment(
122,
Duel.state.protagonist.tank.equipment_preset,
Duel.state.protagonist.equipmentMatrix,
protagonist.equipment_preset,
Tankopedia.state.protagonist.equipment,
);
const hasDownImprovedVerticalStabilizer = hasEquipment(
124,
Duel.state.protagonist.tank.equipment_preset,
Duel.state.protagonist.equipmentMatrix,
protagonist.equipment_preset,
Tankopedia.state.protagonist.equipment,
);
switch (event) {
@@ -1,16 +1,16 @@
import { useEffect } from "react";
import { degToRad } from "three/src/math/MathUtils.js";
import { Duel } from "../../../../../../stores/duel";
import { api } from "../api/dynamic";
import { applyPitchYawLimits } from "../core/blitz/applyPitchYawLimits";
import { modelTransformEvent } from "../core/blitzkit/modelTransform";
import { DuelModule, useDuel } from "../hooks/useDuel";
const modelDefinitions = await api.models();
export function InitialCameraAligner() {
const tank = Duel.use((state) => state.protagonist.tank);
const turret = Duel.use((state) => state.protagonist.turret);
const gun = Duel.use((state) => state.protagonist.gun);
const tank = useDuel("protagonist", DuelModule.Tank);
const turret = useDuel("protagonist", DuelModule.Turret);
const gun = useDuel("protagonist", DuelModule.Gun);
const tankModel = modelDefinitions.models[tank.id];
const turretModel = tankModel.turrets[turret.id];
@@ -4,12 +4,13 @@ import { correctZYTuple } from "../core/blitz/correctZYTuple";
import { nameToArmorId } from "../core/blitzkit/nameToArmorId";
import { resolveArmor } from "../core/blitzkit/resolveThickness";
import { useArmor } from "../hooks/useArmor";
import { useConsumable } from "../hooks/useConsumable";
import { DuelModule, useDuel } from "../hooks/useDuel";
import { ModelTankWrapper } from "./ModelTankWrapper";
import { useDuelModel } from "../hooks/useDuelModel";
import { useTankTransform } from "../hooks/useTankTransform";
import { PrimaryArmorSceneComponent } from "./PrimaryArmorSceneComponent";
export const PrimaryArmorScene = memo(() => {
const wrapper = useRef<Group>(null);
const turretContainer = useRef<Group>(null!);
const gunContainer = useRef<Group>(null!);
const tank = useDuel("protagonist", DuelModule.Tank);
@@ -18,21 +19,19 @@ export const PrimaryArmorScene = memo(() => {
const gun = useDuel("protagonist", DuelModule.Gun);
const armorGltf = useArmor(tank.id).gltf;
const armorNodes = Object.values(armorGltf.nodes);
const tankModelDefinition = useTankModelDefinition();
const tankModelDefinition = useDuelModel("protagonist");
const trackModelDefinition = tankModelDefinition.tracks[track.id];
const turretModelDefinition = tankModelDefinition.turrets[turret.id];
const gunModelDefinition = turretModelDefinition.guns[gun.id];
const hullOrigin = correctZYTuple(trackModelDefinition.origin!);
const turretOrigin = correctZYTuple(tankModelDefinition.turret_origin!);
const gunOrigin = correctZYTuple(turretModelDefinition.gun_origin!);
const isDynamicArmorActive = Duel.use((state) =>
state.protagonist.consumables.includes(73),
);
const isDynamicArmorActive = useConsumable("protagonist", 73);
useTankTransform(track, turret, turretContainer, gunContainer);
return (
<ModelTankWrapper ref={wrapper}>
<>
<group position={hullOrigin}>
{armorNodes.map((node) => {
const isHull = node.name.startsWith("hull_");
@@ -132,6 +131,6 @@ export const PrimaryArmorScene = memo(() => {
})}
</group>
</group>
</ModelTankWrapper>
</>
);
});
@@ -23,9 +23,9 @@ import { hasEquipment } from "../../hooks/useEquipment";
import { Tankopedia } from "../../stores/tankopedia";
import { TankopediaPersistent } from "../../stores/tankopediaPersistent";
import { transitionEvent } from "../Lighting";
import { spacedArmorRenderTarget } from "../PrimaryArmorRenderTarget";
import fragmentShader from "./shaders/fragment.glsl?raw";
import vertexShader from "./shaders/vertex.glsl?raw";
import { spacedArmorRenderTarget } from "./PrimaryArmorRenderTarget";
interface PrimaryArmorSceneComponentProps {
thickness: number;
@@ -134,6 +134,8 @@ export function PrimaryArmorSceneComponent({
if (!noInvalidate) invalidate();
}
function handleAntagonistEquipmentChange(noInvalidate = false) {
const antagonist = getDuel("antagonist", DuelModule.Tank);
const equipment = Tankopedia.state.antagonist.equipment;
const tankopediaEphemeral = Tankopedia.state;
const shell =
@@ -142,7 +144,7 @@ export function PrimaryArmorSceneComponent({
const penetration = shell.penetration!.near;
const hasCalibratedShells = hasEquipment(
103,
Duel.state.antagonist.tank.equipment_preset,
antagonist.equipment_preset,
equipment,
);
@@ -150,9 +152,9 @@ export function PrimaryArmorSceneComponent({
penetration *
resolvePenetrationCoefficient(
hasCalibratedShells,
Duel.state.equalize,
Tankopedia.state.equalize,
shell.type,
Duel.state.antagonist.tank.equalizer,
antagonist.equalizer,
);
if (!noInvalidate) invalidate();
@@ -175,7 +177,7 @@ export function PrimaryArmorSceneComponent({
transitionEvent.on(handleTransitionEvent);
const unsubscribes = [
Duel.on((state) => state.antagonist.shell, handleShellChange),
Tankopedia.on((state) => state.antagonist.shell, handleShellChange),
Tankopedia.on((state) => state.customShell, handleShellChange),
TankopediaPersistent.on(
(state) => state.greenPenetration,
@@ -186,15 +188,15 @@ export function PrimaryArmorSceneComponent({
handleAdvancedHighlightingChange,
),
TankopediaPersistent.on((state) => state.opaque, handleOpaqueChange),
Duel.on(
(state) => state.protagonist.equipmentMatrix,
Tankopedia.on(
(state) => state.protagonist.equipment,
() => handleProtagonistEquipmentChange(),
),
Duel.on(
(state) => state.antagonist.equipmentMatrix,
Tankopedia.on(
(state) => state.antagonist.equipment,
() => handleAntagonistEquipmentChange(),
),
Duel.on(
Tankopedia.on(
(state) => state.equalize,
() => {
handleProtagonistEquipmentChange();
@@ -4,11 +4,11 @@ import { correctZYTuple } from "../core/blitz/correctZYTuple";
import { nameToArmorId } from "../core/blitzkit/nameToArmorId";
import { resolveArmor } from "../core/blitzkit/resolveThickness";
import { useArmor } from "../hooks/useArmor";
import { useConsumable } from "../hooks/useConsumable";
import { DuelModule, useDuel } from "../hooks/useDuel";
import { useDuelModel } from "../hooks/useDuelModel";
import { useModel } from "../hooks/useModel";
import { useTankModelDefinition } from "../hooks/useTankModelDefinition";
import { useTankTransform } from "../hooks/useTankTransform";
import { Duel } from "../stores/duel";
import { ModelTankWrapper } from "./ModelTankWrapper";
import { SpacedArmorSceneComponent } from "./SpacedArmorSceneComponent";
export enum ArmorType {
@@ -22,18 +22,17 @@ interface SpacedArmorSceneProps {
}
export const SpacedArmorScene = memo<SpacedArmorSceneProps>(({ scene }) => {
const wrapper = useRef<Group>(null);
const turretContainer = useRef<Group>(null!);
const gunContainer = useRef<Group>(null!);
const tank = Duel.use((state) => state.protagonist.tank);
const track = Duel.use((state) => state.protagonist.track);
const turret = Duel.use((state) => state.protagonist.turret);
const gun = Duel.use((state) => state.protagonist.gun);
const tank = useDuel("protagonist", DuelModule.Tank);
const track = useDuel("protagonist", DuelModule.Track);
const turret = useDuel("protagonist", DuelModule.Turret);
const gun = useDuel("protagonist", DuelModule.Gun);
const { gltf: armorGltf } = useArmor(tank.id);
const { gltf: modelGltf } = useModel(tank.id);
const armorNodes = Object.values(armorGltf.nodes);
const modelNodes = Object.values(modelGltf.nodes);
const tankModelDefinition = useTankModelDefinition();
const tankModelDefinition = useDuelModel("protagonist");
const trackModelDefinition = tankModelDefinition.tracks[track.id];
const turretModelDefinition = tankModelDefinition.turrets[turret.id];
const gunModelDefinition = turretModelDefinition.guns[gun.id];
@@ -44,14 +43,12 @@ export const SpacedArmorScene = memo<SpacedArmorSceneProps>(({ scene }) => {
gunModelDefinition.mask === undefined
? undefined
: gunModelDefinition.mask + hullOrigin.y + turretOrigin.y + gunOrigin.y;
const isDynamicArmorActive = Duel.use((state) =>
state.protagonist.consumables.includes(73),
);
const isDynamicArmorActive = useConsumable("protagonist", 73);
useTankTransform(track, turret, turretContainer, gunContainer);
return (
<ModelTankWrapper ref={wrapper}>
<>
<group position={hullOrigin}>
{armorNodes.map((node) => {
const isHull = node.name.startsWith("hull_");
@@ -199,6 +196,6 @@ export const SpacedArmorScene = memo<SpacedArmorSceneProps>(({ scene }) => {
})}
</group>
</group>
</ModelTankWrapper>
</>
);
});
@@ -13,11 +13,11 @@ import {
Vector3,
} from "three";
import { degToRad } from "three/src/math/MathUtils.js";
import { hasEquipment } from "../core/blitzkit/hasEquipment";
import { defaultEqualizer } from "../config/equalizer";
import { jsxTree } from "../core/blitzkit/jsxTree";
import { defaultEqualizer } from "../core/blitzkit/tankToDuelMember";
import { discardClippingPlane } from "../core/three/discardClippingPlane";
import { Duel } from "../stores/duel";
import { DuelModule, getDuel } from "../hooks/useDuel";
import { hasEquipment } from "../hooks/useEquipment";
import {
type Shot,
type ShotLayerBase,
@@ -83,15 +83,16 @@ export function SpacedArmorSceneComponent({
allowRicochet: boolean,
remainingPenetrationInput?: number,
) => {
const protagonist = getDuel("protagonist", DuelModule.Tank);
const antagonist = getDuel("antagonist", DuelModule.Tank);
const { customShell } = Tankopedia.state;
const shell = customShell ?? Duel.state.antagonist.shell;
const { equalize } = Duel.state;
const shell = customShell ?? getDuel("protagonist", DuelModule.Shell);
const { equalize } = Tankopedia.state;
const protagonistEqualizer =
(equalize ? Duel.state.protagonist.tank.equalizer : undefined) ??
defaultEqualizer;
(equalize ? protagonist.equalizer : undefined) ?? defaultEqualizer;
const antagonistEqualizer =
(equalize ? Duel.state.antagonist.tank.equalizer : undefined) ??
defaultEqualizer;
(equalize ? antagonist.equalizer : undefined) ?? defaultEqualizer;
const cameraNormal = camera.position.clone().sub(point).normalize();
const shot: Shot = {
splashRadius:
@@ -109,13 +110,13 @@ export function SpacedArmorSceneComponent({
const hasCalibratedShells = hasEquipment(
103,
Duel.state.antagonist.tank.equipment_preset,
Duel.state.antagonist.equipmentMatrix,
antagonist.equipment_preset,
Tankopedia.state.antagonist.equipment,
);
const hasEnhancedArmor = hasEquipment(
110,
Duel.state.protagonist.tank.equipment_preset,
Duel.state.protagonist.equipmentMatrix,
protagonist.equipment_preset,
Tankopedia.state.protagonist.equipment,
);
const penetration =
shell.penetration!.near *
@@ -7,12 +7,15 @@ import {
Plane,
ShaderMaterial,
} from "three";
import type { ArmorUserData, ExternalModuleVariant } from "../SpacedArmorSceneComponent";
import { hasEquipment } from "../../core/blitzkit/hasEquipment";
import { jsxTree } from "../../core/blitzkit/jsxTree";
import { Duel } from "../../stores/duel";
import { DuelModule, getDuel } from "../../hooks/useDuel";
import { hasEquipment } from "../../hooks/useEquipment";
import { Tankopedia } from "../../stores/tankopedia";
import { ArmorType } from "./SpacedArmorScene";
import { ArmorType } from "../SpacedArmorScene";
import type {
ArmorUserData,
ExternalModuleVariant,
} from "../SpacedArmorSceneComponent";
import fragmentShader from "./shaders/fragment.glsl?raw";
import vertexShader from "./shaders/vertex.glsl?raw";
@@ -49,47 +52,51 @@ export function SpacedArmorSubExternal({
function handleShellChange() {
const tankopediaEphemeral = Tankopedia.state;
const shell =
tankopediaEphemeral.customShell ?? Duel.state.antagonist.shell;
tankopediaEphemeral.customShell ??
getDuel("antagonist", DuelModule.Shell);
material.uniforms.penetration.value = shell.penetration!.near;
}
function handleProtagonistEquipmentChange() {
const equipment = Duel.state.protagonist.equipmentMatrix;
const protagonist = getDuel("protagonist", DuelModule.Tank);
const hasEnhancedArmor = hasEquipment(
110,
Duel.state.protagonist.tank.equipment_preset,
equipment,
protagonist.equipment_preset,
Tankopedia.state.protagonist.equipment,
);
const equalizer =
(Duel.state.equalize
? Duel.state.protagonist.tank.equalizer?.armor
(Tankopedia.state.equalize
? protagonist.equalizer?.armor
: undefined) ?? 1;
material.uniforms.thickness.value =
thickness * (hasEnhancedArmor ? 1.04 : 1) * equalizer;
}
function handleAntagonistEquipmentChange() {
const equipment = Duel.state.antagonist.equipmentMatrix;
const antagonist = getDuel("antagonist", DuelModule.Tank);
const tankopediaEphemeral = Tankopedia.state;
const shell =
tankopediaEphemeral.customShell ?? Duel.state.antagonist.shell;
tankopediaEphemeral.customShell ??
getDuel("antagonist", DuelModule.Shell);
const penetration = shell.penetration!.near;
const hasCalibratedShells = hasEquipment(
103,
Duel.state.antagonist.tank.equipment_preset,
equipment,
antagonist.equipment_preset,
Tankopedia.state.antagonist.equipment,
);
const equalize =
(Duel.state.equalize
? Duel.state.antagonist.tank.equalizer?.penetration
(Tankopedia.state.equalize
? antagonist.equalizer?.penetration
: undefined) ?? 1;
material.uniforms.penetration.value =
penetration *
resolvePenetrationCoefficient(
hasCalibratedShells,
Duel.state.equalize,
Tankopedia.state.equalize,
shell.type,
Duel.state.antagonist.tank.equalizer,
antagonist.equalizer,
) *
equalize;
}
@@ -99,17 +106,17 @@ export function SpacedArmorSubExternal({
handleAntagonistEquipmentChange();
const unsubscribes = [
Duel.on((state) => state.antagonist.shell, handleShellChange),
Tankopedia.on((state) => state.antagonist.shell, handleShellChange),
Tankopedia.on((state) => state.customShell, handleShellChange),
Duel.on(
(state) => state.protagonist.equipmentMatrix,
Tankopedia.on(
(state) => state.protagonist.equipment,
handleProtagonistEquipmentChange,
),
Duel.on(
(state) => state.antagonist.equipmentMatrix,
Tankopedia.on(
(state) => state.antagonist.equipment,
handleAntagonistEquipmentChange,
),
Duel.on(
Tankopedia.on(
(state) => state.equalize,
() => {
handleProtagonistEquipmentChange();
@@ -7,12 +7,12 @@ import {
type Object3D,
} from "three";
import { degToRad } from "three/src/math/MathUtils.js";
import type { ArmorUserData } from "../SpacedArmorSceneComponent";
import { hasEquipment } from "../../core/blitzkit/hasEquipment";
import { jsxTree } from "../../core/blitzkit/jsxTree";
import { Duel } from "../../stores/duel";
import { DuelModule, getDuel } from "../../hooks/useDuel";
import { hasEquipment } from "../../hooks/useEquipment";
import { Tankopedia } from "../../stores/tankopedia";
import { ArmorType } from "./SpacedArmorScene";
import { ArmorType } from "../SpacedArmorScene";
import type { ArmorUserData } from "../SpacedArmorSceneComponent";
import fragmentShader from "./shaders/fragment.glsl?raw";
import vertexShader from "./shaders/vertex.glsl?raw";
@@ -51,7 +51,8 @@ export function SpacedArmorSubSpaced({
function handleShellChange() {
const tankopediaEphemeral = Tankopedia.state;
const shell =
tankopediaEphemeral.customShell ?? Duel.state.antagonist.shell;
tankopediaEphemeral.customShell ??
getDuel("antagonist", DuelModule.Shell);
material.uniforms.penetration.value = shell.penetration!.near;
material.uniforms.caliber.value = shell.caliber;
@@ -63,43 +64,46 @@ export function SpacedArmorSubSpaced({
);
}
function handleProtagonistEquipmentChange() {
const equipment = Duel.state.protagonist.equipmentMatrix;
const protagonist = getDuel("protagonist", DuelModule.Tank);
const hasEnhancedArmor = hasEquipment(
110,
Duel.state.protagonist.tank.equipment_preset,
equipment,
protagonist.equipment_preset,
Tankopedia.state.protagonist.equipment,
);
const equalizer =
(Duel.state.equalize
? Duel.state.protagonist.tank.equalizer?.armor
(Tankopedia.state.equalize
? protagonist.equalizer?.armor
: undefined) ?? 1;
material.uniforms.thickness.value =
thickness * (hasEnhancedArmor ? 1.04 : 1) * equalizer;
}
function handleAntagonistEquipmentChange() {
const equipment = Duel.state.antagonist.equipmentMatrix;
const antagonist = getDuel("antagonist", DuelModule.Tank);
const tankopediaEphemeral = Tankopedia.state;
const shell =
tankopediaEphemeral.customShell ?? Duel.state.antagonist.shell;
tankopediaEphemeral.customShell ??
getDuel("antagonist", DuelModule.Shell);
const penetration = shell.penetration!.near;
const hasCalibratedShells = hasEquipment(
103,
Duel.state.antagonist.tank.equipment_preset,
equipment,
antagonist.equipment_preset,
Tankopedia.state.antagonist.equipment,
);
const equalize =
(Duel.state.equalize
? Duel.state.antagonist.tank.equalizer?.penetration
(Tankopedia.state.equalize
? antagonist.equalizer?.penetration
: undefined) ?? 1;
material.uniforms.penetration.value =
penetration *
resolvePenetrationCoefficient(
hasCalibratedShells,
Duel.state.equalize,
Tankopedia.state.equalize,
shell.type,
Duel.state.antagonist.tank.equalizer,
antagonist.equalizer,
) *
equalize;
}
@@ -109,17 +113,17 @@ export function SpacedArmorSubSpaced({
handleAntagonistEquipmentChange();
const unsubscribes = [
Duel.on((state) => state.antagonist.shell, handleShellChange),
Tankopedia.on((state) => state.antagonist.shell, handleShellChange),
Tankopedia.on((state) => state.customShell, handleShellChange),
Duel.on(
(state) => state.protagonist.equipmentMatrix,
Tankopedia.on(
(state) => state.protagonist.equipment,
handleProtagonistEquipmentChange,
),
Duel.on(
(state) => state.antagonist.equipmentMatrix,
Tankopedia.on(
(state) => state.antagonist.equipment,
handleAntagonistEquipmentChange,
),
Duel.on(
Tankopedia.on(
(state) => state.equalize,
() => {
handleProtagonistEquipmentChange();
+32 -27
View File
@@ -3,19 +3,19 @@ import { memo, useRef } from "react";
import { Group, Plane, Vector2, Vector3 } from "three";
import { applyPitchYawLimits } from "../core/blitz/applyPitchYawLimits";
import { correctZYTuple } from "../core/blitz/correctZYTuple";
import { hasEquipment } from "../core/blitzkit/hasEquipment";
import { modelTransformEvent } from "../core/blitzkit/modelTransform";
import { nameToArmorId } from "../core/blitzkit/nameToArmorId";
import { resolveArmor } from "../core/blitzkit/resolveThickness";
import { controlsEnabledEvent } from "../core/controlsEnabled";
import { useArmor } from "../hooks/useArmor";
import { useConsumable } from "../hooks/useConsumable";
import { DuelModule, getDuel, useDuel } from "../hooks/useDuel";
import { useDuelModel } from "../hooks/useDuelModel";
import { hasEquipment } from "../hooks/useEquipment";
import { useModel } from "../hooks/useModel";
import { useTankModelDefinition } from "../hooks/useTankModelDefinition";
import { useTankTransform } from "../hooks/useTankTransform";
import { Duel } from "../stores/duel";
import { Tankopedia } from "../stores/tankopedia";
import { TankopediaPersistent } from "../stores/tankopediaPersistent";
import { ModelTankWrapper } from "./ModelTankWrapper";
import { ArmorType } from "./SpacedArmorScene";
import { StaticArmorSceneComponent } from "./StaticArmorSceneComponent";
@@ -28,18 +28,19 @@ export interface ThicknessRange {
}
export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
const wrapper = useRef<Group>(null);
const turretContainer = useRef<Group>(null!);
const gunContainer = useRef<Group>(null!);
const tank = Duel.use((state) => state.protagonist.tank);
const track = Duel.use((state) => state.protagonist.track);
const turret = Duel.use((state) => state.protagonist.turret);
const gun = Duel.use((state) => state.protagonist.gun);
const tank = useDuel("protagonist", DuelModule.Tank);
const track = useDuel("protagonist", DuelModule.Track);
const turret = useDuel("protagonist", DuelModule.Turret);
const gun = useDuel("protagonist", DuelModule.Gun);
const { gltf: armorGltf } = useArmor(tank.id);
const { gltf: modelGltf } = useModel(tank.id);
const armorNodes = Object.values(armorGltf.nodes);
const modelNodes = Object.values(modelGltf.nodes);
const tankModelDefinition = useTankModelDefinition();
const tankModelDefinition = useDuelModel("protagonist");
const trackModelDefinition = tankModelDefinition.tracks[track.id];
const turretModelDefinition = tankModelDefinition.turrets[turret.id];
const gunModelDefinition = turretModelDefinition.guns[gun.id];
@@ -61,14 +62,12 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
const showExternalModules = TankopediaPersistent.use(
(state) => state.showExternalModules,
);
const isDynamicArmorActive = Duel.use((state) =>
state.protagonist.consumables.includes(73),
);
const isDynamicArmorActive = useConsumable("protagonist", 73);
useTankTransform(track, turret, turretContainer, gunContainer);
return (
<ModelTankWrapper ref={wrapper}>
<>
<group position={hullOrigin}>
{armorNodes.map((node) => {
const isHull = node.name.startsWith("hull_");
@@ -165,15 +164,17 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
window.addEventListener("pointerup", handlePointerUp);
}
function handlePointerMove(event: PointerEvent) {
const protagonist = getDuel("protagonist", DuelModule.Tank);
const hasImprovedVerticalStabilizer = hasEquipment(
122,
Duel.state.protagonist.tank.equipment_preset,
Duel.state.protagonist.equipmentMatrix,
protagonist.equipment_preset,
Tankopedia.state.protagonist.equipment,
);
const hasDownImprovedVerticalStabilizer = hasEquipment(
124,
Duel.state.protagonist.tank.equipment_preset,
Duel.state.protagonist.equipmentMatrix,
protagonist.equipment_preset,
Tankopedia.state.protagonist.equipment,
);
const boundingRect = canvas.getBoundingClientRect();
@@ -253,15 +254,17 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
window.addEventListener("pointerup", handlePointerUp);
}
function handlePointerMove(event: PointerEvent) {
const protagonist = getDuel("protagonist", DuelModule.Tank);
const hasImprovedVerticalStabilizer = hasEquipment(
122,
Duel.state.protagonist.tank.equipment_preset,
Duel.state.protagonist.equipmentMatrix,
protagonist.equipment_preset,
Tankopedia.state.protagonist.equipment,
);
const hasDownImprovedVerticalStabilizer = hasEquipment(
124,
Duel.state.protagonist.tank.equipment_preset,
Duel.state.protagonist.equipmentMatrix,
protagonist.equipment_preset,
Tankopedia.state.protagonist.equipment,
);
const boundingRect = canvas.getBoundingClientRect();
delta.set(event.clientX, event.clientY).sub(position);
@@ -329,15 +332,17 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
window.addEventListener("pointerup", handlePointerUp);
}
function handlePointerMove(event: PointerEvent) {
const protagonist = getDuel("protagonist", DuelModule.Tank);
const hasImprovedVerticalStabilizer = hasEquipment(
122,
Duel.state.protagonist.tank.equipment_preset,
Duel.state.protagonist.equipmentMatrix,
protagonist.equipment_preset,
Tankopedia.state.protagonist.equipment,
);
const hasDownImprovedVerticalStabilizer = hasEquipment(
124,
Duel.state.protagonist.tank.equipment_preset,
Duel.state.protagonist.equipmentMatrix,
protagonist.equipment_preset,
Tankopedia.state.protagonist.equipment,
);
const boundingRect = canvas.getBoundingClientRect();
delta.set(event.clientX, event.clientY).sub(position);
@@ -385,6 +390,6 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
})}
</group>
</group>
</ModelTankWrapper>
</>
);
});
@@ -16,16 +16,16 @@ import {
Quaternion,
Vector3,
} from "three";
import { defaultEqualizer } from "../config/equalizer";
import { unrotateDavaVector } from "../core/blitz/unrotateDavaVector";
import { jsxTree } from "../core/blitzkit/jsxTree";
import {
modelTransformEvent,
type ModelTransformEventData,
} from "../core/blitzkit/modelTransform";
import { defaultEqualizer } from "../core/blitzkit/tankToDuelMember";
import { discardClippingPlane } from "../core/three/discardClippingPlane";
import { DuelModule, useDuel } from "../hooks/useDuel";
import { useEquipment } from "../hooks/useEquipment";
import { Duel } from "../stores/duel";
import { Tankopedia } from "../stores/tankopedia";
import { transitionEvent } from "./Lighting";
import { ArmorType } from "./SpacedArmorScene";
@@ -75,8 +75,8 @@ export function StaticArmorSceneComponent({
}: StaticArmorSceneComponentProps) {
const camera = useThree((state) => state.camera);
const hasEnhancedArmor = useEquipment("protagonist", 110);
const tank = Duel.use((state) => state.protagonist.tank);
const equalize = Duel.use((state) => state.equalize);
const tank = useDuel("protagonist", DuelModule.Tank);
const equalize = Tankopedia.use((state) => state.equalize);
const equalizer = (equalize ? tank.equalizer : undefined) ?? defaultEqualizer;
const thicknessCoefficient = (hasEnhancedArmor ? 1.04 : 1) * equalizer.armor;
const thickness = thicknessRaw * thicknessCoefficient;
+25 -23
View File
@@ -1,38 +1,36 @@
import { invalidate, useThree, type ThreeEvent } from "@react-three/fiber";
import { useRef } from "react";
import { Group, Mesh, MeshStandardMaterial, Vector2 } from "three";
import { useTankModelDefinition } from "../../../../../../hooks/useTankModelDefinition";
import { Duel } from "../../../../../../stores/duel";
import { applyPitchYawLimits } from "../core/blitz/applyPitchYawLimits";
import { hasEquipment } from "../core/blitzkit/hasEquipment";
import { jsxTree } from "../core/blitzkit/jsxTree";
import { modelTransformEvent } from "../core/blitzkit/modelTransform";
import { controlsEnabledEvent } from "../core/controlsEnabled";
import { DuelModule, useDuel } from "../hooks/useDuel";
import { useDuelModel } from "../hooks/useDuelModel";
import { hasEquipment } from "../hooks/useEquipment";
import { useModel } from "../hooks/useModel";
import { useTankTransform } from "../hooks/useTankTransform";
import { Tankopedia } from "../stores/tankopedia";
import { TankopediaDisplay } from "../stores/tankopediaPersistent/constants";
import { ModelTankWrapper } from "./ModelTankWrapper";
export function TankModel() {
const protagonist = Duel.use((draft) => draft.protagonist);
const track = Duel.use((state) => state.protagonist.track);
const turret = Duel.use((state) => state.protagonist.turret);
const protagonist = Tankopedia.use((state) => state.protagonist);
const track = useDuel("protagonist", DuelModule.Track);
const turret = useDuel("protagonist", DuelModule.Turret);
const canvas = useThree((state) => state.gl.domElement);
const hullContainer = useRef<Group>(null);
const turretContainer = useRef<Group>(null!);
const gunContainer = useRef<Group>(null!);
const tankModelDefinition = useTankModelDefinition();
const turretModelDefinition =
tankModelDefinition.turrets[protagonist.turret.id];
const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id];
const { gltf } = useModel(protagonist.tank.id);
const tankModelDefinition = useDuelModel("protagonist");
const turretModelDefinition = tankModelDefinition.turrets[protagonist.turret];
const gunModelDefinition = turretModelDefinition.guns[protagonist.gun];
const { gltf } = useModel(protagonist.tank);
const nodes = Object.values(gltf.nodes);
useTankTransform(track, turret, turretContainer, gunContainer);
return (
<ModelTankWrapper ref={hullContainer}>
<>
{nodes.map((node) => {
const isHull = node.name === "hull";
const isWheel = node.name.startsWith("chassis_wheel_");
@@ -131,15 +129,17 @@ export function TankModel() {
window.addEventListener("pointerup", handlePointerUp);
}
function handlePointerMove(event: PointerEvent) {
const protagonist = useDuel("protagonist", DuelModule.Tank);
const hasImprovedVerticalStabilizer = hasEquipment(
122,
Duel.state.protagonist.tank.equipment_preset,
Duel.state.protagonist.equipmentMatrix,
protagonist.equipment_preset,
Tankopedia.state.protagonist.equipment,
);
const hasDownImprovedVerticalStabilizer = hasEquipment(
124,
Duel.state.protagonist.tank.equipment_preset,
Duel.state.protagonist.equipmentMatrix,
protagonist.equipment_preset,
Tankopedia.state.protagonist.equipment,
);
const boundingRect = canvas.getBoundingClientRect();
@@ -214,15 +214,17 @@ export function TankModel() {
window.addEventListener("pointerup", handlePointerUp);
}
function handlePointerMove(event: PointerEvent) {
const protagonist = useDuel("protagonist", DuelModule.Tank);
const hasImprovedVerticalStabilizer = hasEquipment(
122,
Duel.state.protagonist.tank.equipment_preset,
Duel.state.protagonist.equipmentMatrix,
protagonist.equipment_preset,
Tankopedia.state.protagonist.equipment,
);
const hasDownImprovedVerticalStabilizer = hasEquipment(
124,
Duel.state.protagonist.tank.equipment_preset,
Duel.state.protagonist.equipmentMatrix,
protagonist.equipment_preset,
Tankopedia.state.protagonist.equipment,
);
const boundingRect = canvas.getBoundingClientRect();
delta.set(event.clientX, event.clientY).sub(position);
@@ -260,6 +262,6 @@ export function TankModel() {
})}
</group>
</group>
</ModelTankWrapper>
</>
);
}
+7
View File
@@ -0,0 +1,7 @@
export const defaultEqualizer: Equalizer = {
armor: 1,
damage: 1,
health: 1,
module_health: 1,
penetration: 1,
};
@@ -0,0 +1,6 @@
import { Tankopedia } from "../stores/tankopedia";
import type { DuelSide } from "./useEquipment";
export function useConsumable(side: DuelSide, id: number) {
return Tankopedia.use((state) => state[side].consumables.includes(id));
}
@@ -0,0 +1,6 @@
import { Tankopedia } from "../stores/tankopedia";
import type { DuelSide } from "./useEquipment";
export function useDuelModel(side: DuelSide) {
return Tankopedia.use((state) => state[side].model);
}
@@ -10,7 +10,7 @@ import {
modelTransformEvent,
type ModelTransformEventData,
} from "../core/blitzkit/modelTransform";
import { useTankModelDefinition } from "./useTankModelDefinition";
import { useDuelModel } from "./useDuelModel";
export function useTankTransform(
track: TrackDefinition,
@@ -18,7 +18,7 @@ export function useTankTransform(
turretContainer: RefObject<Group>,
gunContainer: RefObject<Group>,
) {
const tankModelDefinition = useTankModelDefinition();
const tankModelDefinition = useDuelModel("protagonist");
useEffect(() => {
const trackModelDefinition = tankModelDefinition.tracks[track.id];
+18 -21
View File
@@ -1,11 +1,7 @@
import { createDefaultSkills } from "@blitzkit/core";
import type {
ModelDefinition,
ShellDefinition,
TankDefinition,
} from "@blitzkit/protos";
import type { Vector3 } from "three";
import type { ShellDefinition, TankDefinition } from "@blitzkit/protos";
import { Soapstone } from "soapstone";
import type { Vector3 } from "three";
import { api } from "../api/dynamic";
import type { ArmorType } from "../components/SpacedArmorScene";
import type { ExternalModuleVariant } from "../components/SpacedArmorSceneComponent";
@@ -85,7 +81,6 @@ interface Tankopedia {
protagonist: TankState;
antagonist: TankState;
model: ModelDefinition;
equalize: boolean;
@@ -117,20 +112,22 @@ interface Tankopedia {
const skills = await api.skills();
const models = await api.models();
export const Tankopedia = new Soapstone<Tankopedia, [TankDefinition]>((tank) => ({
disturbed: false,
revealed: false,
export const Tankopedia = new Soapstone<Tankopedia, [TankDefinition]>(
(tank) => ({
disturbed: false,
revealed: false,
protagonist: createTankState(tank),
antagonist: createTankState(tank),
model: models.models[tank.id],
protagonist: createTankState(tank),
antagonist: createTankState(tank),
model: models.models[tank.id],
equalize: false,
equalize: false,
relativeAgainst: TankopediaRelativeAgainst.Class,
editStatic: false,
skills: createDefaultSkills(skills),
xpMultiplier: 1,
requestedDisplay: TankopediaDisplay.Model,
display: TankopediaDisplay.Model,
}));
relativeAgainst: TankopediaRelativeAgainst.Class,
editStatic: false,
skills: createDefaultSkills(skills),
xpMultiplier: 1,
requestedDisplay: TankopediaDisplay.Model,
display: TankopediaDisplay.Model,
}),
);