From 43b59e4f02764313c30bc332b722a801597821c8 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Fri, 31 Jul 2026 20:12:54 -0500 Subject: [PATCH 001/147] update workflows to work with production as the deploy branch --- .github/workflows/assets.yml | 5 +-- .github/workflows/build-bot.yml | 34 ----------------- .github/workflows/discover-ids.yml | 36 ------------------ .github/workflows/host-bot.yml | 61 ------------------------------ .github/workflows/rating-asia.yml | 46 ---------------------- .github/workflows/rating-eu.yml | 46 ---------------------- .github/workflows/rating-na.yml | 46 ---------------------- .github/workflows/website.yml | 2 +- 8 files changed, 2 insertions(+), 274 deletions(-) delete mode 100644 .github/workflows/build-bot.yml delete mode 100644 .github/workflows/discover-ids.yml delete mode 100644 .github/workflows/host-bot.yml delete mode 100644 .github/workflows/rating-asia.yml delete mode 100644 .github/workflows/rating-eu.yml delete mode 100644 .github/workflows/rating-na.yml diff --git a/.github/workflows/assets.yml b/.github/workflows/assets.yml index f71f06861..e9280a7a9 100644 --- a/.github/workflows/assets.yml +++ b/.github/workflows/assets.yml @@ -2,7 +2,7 @@ name: Assets on: push: - branches: main + branches: production workflow_dispatch: inputs: @@ -22,9 +22,6 @@ on: description: "Archive URL or Steam to download (example: steam:APP_ID/DEPOT_ID or zip:URL_TO_ZIP)" default: "steam:444200/444202" - schedule: - - cron: "0 2 * * *" - concurrency: group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.inputs.asset_repo }} cancel-in-progress: true diff --git a/.github/workflows/build-bot.yml b/.github/workflows/build-bot.yml deleted file mode 100644 index 501cd703f..000000000 --- a/.github/workflows/build-bot.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: Bot - -on: - push: - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - build: - name: Build bot - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v6 - with: - submodules: recursive - token: ${{ secrets.GH_TOKEN }} - - uses: oven-sh/setup-bun@v2 - - uses: arduino/setup-protoc@v3 - with: - repo-token: ${{ secrets.GH_TOKEN }} - - run: bun run setup - - - name: Build bot - run: bun run build:bot - - - name: Archive build - uses: actions/upload-artifact@v7 - with: - name: bot - path: dist/bot diff --git a/.github/workflows/discover-ids.yml b/.github/workflows/discover-ids.yml deleted file mode 100644 index 3d5d76a9b..000000000 --- a/.github/workflows/discover-ids.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: IDs - -on: - workflow_dispatch: - schedule: - - cron: "0 0 * * 1" - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -env: - DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }} - DATABASE_URL: ${{ secrets.DATABASE_URL }} - PUBLIC_ASSET_BRANCH: ${{ secrets.PUBLIC_ASSET_BRANCH }} - PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }} - PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }} - GH_TOKEN: ${{ secrets.GH_TOKEN }} - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v6 - with: - submodules: recursive - token: ${{ secrets.GH_TOKEN }} - - uses: oven-sh/setup-bun@v2 - - uses: arduino/setup-protoc@v3 - with: - repo-token: ${{ secrets.GH_TOKEN }} - - run: bun run setup - - - name: Discover IDs - run: bun build:ids --production diff --git a/.github/workflows/host-bot.yml b/.github/workflows/host-bot.yml deleted file mode 100644 index e7c47fb7c..000000000 --- a/.github/workflows/host-bot.yml +++ /dev/null @@ -1,61 +0,0 @@ -name: Host - -on: - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -env: - DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }} - DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }} - DATABASE_URL: ${{ secrets.DATABASE_URL }} - PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }} - PUBLIC_ASSET_BRANCH: ${{ secrets.PUBLIC_ASSET_BRANCH }} - PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }} - PUBLIC_ASSET_FAST_USER: ${{ secrets.PUBLIC_ASSET_FAST_USER }} - PUBLIC_ASSET_FAST_REPO: ${{ secrets.PUBLIC_ASSET_FAST_REPO }} - PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }} - ALLOW_ROBOTS: ${{ secrets.ALLOW_ROBOTS }} - WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }} - PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }} - PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }} - GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }} - WOTB_GLOSSARY_DOMAIN: ${{ secrets.WOTB_GLOSSARY_DOMAIN }} - WOTB_GLOSSARY_PATH: ${{ secrets.WOTB_GLOSSARY_PATH }} - PUBLIC_DISCORD_SERVER_ID: ${{ secrets.PUBLIC_DISCORD_SERVER_ID }} - PUBLIC_ASSET_BASE: ${{ secrets.PUBLIC_ASSET_BASE }} - PUBLIC_BRANCH: ${{ secrets.PUBLIC_BRANCH }} - PUBLIC_GUIDE_WEBSITE: ${{ secrets.PUBLIC_GUIDE_WEBSITE }} - SERVICING: ${{ secrets.SERVICING }} - GH_TOKEN: ${{ secrets.GH_TOKEN }} - -jobs: - host: - runs-on: ubuntu-latest - - steps: - - uses: oven-sh/setup-bun@v2 - - uses: arduino/setup-protoc@v3 - with: - repo-token: ${{ secrets.GH_TOKEN }} - - - name: Download release assets - run: | - DOWNLOAD_URL=$(curl -s https://api.github.com/repos/blitzkit/blitzkit/releases/latest \ - | grep "browser_download_url.*bot.zip" \ - | cut -d : -f 2,3 \ - | tr -d \") - curl -L -o bot.zip $DOWNLOAD_URL - mkdir -p dist/bot - unzip bot.zip -d dist/bot - - - name: Install sharp - run: bun init -y && bun add sharp - - - name: Move directories - run: mv dist/bot/prisma . - - - name: Run bot - run: bun run dist/bot/main.js diff --git a/.github/workflows/rating-asia.yml b/.github/workflows/rating-asia.yml deleted file mode 100644 index 1c85cb67b..000000000 --- a/.github/workflows/rating-asia.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: Rating Asia - -on: - workflow_dispatch: - schedule: - - cron: "0 17 * * *" - -env: - PUBLIC_ASSET_BRANCH: main - PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }} - DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }} - DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }} - DATABASE_URL: ${{ secrets.DATABASE_URL }} - PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }} - PUBLIC_ASSET_FAST_USER: ${{ secrets.PUBLIC_ASSET_FAST_USER }} - PUBLIC_ASSET_FAST_REPO: ${{ secrets.PUBLIC_ASSET_FAST_REPO }} - PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }} - ALLOW_ROBOTS: "true" - WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }} - PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }} - PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }} - GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }} - WOTB_GLOSSARY_DOMAIN: ${{ secrets.WOTB_GLOSSARY_DOMAIN }} - WOTB_GLOSSARY_PATH: ${{ secrets.WOTB_GLOSSARY_PATH }} - PUBLIC_DISCORD_SERVER_ID: ${{ secrets.PUBLIC_DISCORD_SERVER_ID }} - PUBLIC_ASSET_BASE: ${{ secrets.PUBLIC_ASSET_BASE }} - PUBLIC_BRANCH: ${{ secrets.PUBLIC_BRANCH }} - GH_TOKEN: ${{ secrets.GH_TOKEN }} - -jobs: - host: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v6 - with: - submodules: recursive - token: ${{ secrets.GH_TOKEN }} - - uses: oven-sh/setup-bun@v2 - - uses: arduino/setup-protoc@v3 - with: - repo-token: ${{ secrets.GH_TOKEN }} - - run: bun run setup - - - name: Generate leaderboard - run: bun build:ratingleaderboard --region=asia diff --git a/.github/workflows/rating-eu.yml b/.github/workflows/rating-eu.yml deleted file mode 100644 index e8c27e389..000000000 --- a/.github/workflows/rating-eu.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: Rating Europe - -on: - workflow_dispatch: - schedule: - - cron: "0 23 * * *" - -env: - PUBLIC_ASSET_BRANCH: main - PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }} - DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }} - DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }} - DATABASE_URL: ${{ secrets.DATABASE_URL }} - PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }} - PUBLIC_ASSET_FAST_USER: ${{ secrets.PUBLIC_ASSET_FAST_USER }} - PUBLIC_ASSET_FAST_REPO: ${{ secrets.PUBLIC_ASSET_FAST_REPO }} - PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }} - ALLOW_ROBOTS: "true" - WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }} - PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }} - PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }} - GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }} - WOTB_GLOSSARY_DOMAIN: ${{ secrets.WOTB_GLOSSARY_DOMAIN }} - WOTB_GLOSSARY_PATH: ${{ secrets.WOTB_GLOSSARY_PATH }} - PUBLIC_DISCORD_SERVER_ID: ${{ secrets.PUBLIC_DISCORD_SERVER_ID }} - PUBLIC_ASSET_BASE: ${{ secrets.PUBLIC_ASSET_BASE }} - PUBLIC_BRANCH: ${{ secrets.PUBLIC_BRANCH }} - GH_TOKEN: ${{ secrets.GH_TOKEN }} - -jobs: - host: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v6 - with: - submodules: recursive - token: ${{ secrets.GH_TOKEN }} - - uses: oven-sh/setup-bun@v2 - - uses: arduino/setup-protoc@v3 - with: - repo-token: ${{ secrets.GH_TOKEN }} - - run: bun run setup - - - name: Generate leaderboard - run: bun build:ratingleaderboard --region=eu diff --git a/.github/workflows/rating-na.yml b/.github/workflows/rating-na.yml deleted file mode 100644 index 1c3ff2a45..000000000 --- a/.github/workflows/rating-na.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: Rating North America - -on: - workflow_dispatch: - schedule: - - cron: "0 5 * * *" - -env: - PUBLIC_ASSET_BRANCH: main - PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }} - DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }} - DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }} - DATABASE_URL: ${{ secrets.DATABASE_URL }} - PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }} - PUBLIC_ASSET_FAST_USER: ${{ secrets.PUBLIC_ASSET_FAST_USER }} - PUBLIC_ASSET_FAST_REPO: ${{ secrets.PUBLIC_ASSET_FAST_REPO }} - PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }} - ALLOW_ROBOTS: "true" - WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }} - PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }} - PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }} - GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }} - WOTB_GLOSSARY_DOMAIN: ${{ secrets.WOTB_GLOSSARY_DOMAIN }} - WOTB_GLOSSARY_PATH: ${{ secrets.WOTB_GLOSSARY_PATH }} - PUBLIC_DISCORD_SERVER_ID: ${{ secrets.PUBLIC_DISCORD_SERVER_ID }} - PUBLIC_ASSET_BASE: ${{ secrets.PUBLIC_ASSET_BASE }} - PUBLIC_BRANCH: ${{ secrets.PUBLIC_BRANCH }} - GH_TOKEN: ${{ secrets.GH_TOKEN }} - -jobs: - host: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v6 - with: - submodules: recursive - token: ${{ secrets.GH_TOKEN }} - - uses: oven-sh/setup-bun@v2 - - uses: arduino/setup-protoc@v3 - with: - repo-token: ${{ secrets.GH_TOKEN }} - - run: bun run setup - - - name: Generate leaderboard - run: bun build:ratingleaderboard --region=com diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index 1c739206c..6c2254b23 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -58,7 +58,7 @@ jobs: path: dist/website deploy-github-pages: - if: ${{ github.ref == 'refs/heads/main' }} + if: ${{ github.ref == 'refs/heads/production' }} name: Deploy to GitHub Pages runs-on: ubuntu-latest needs: build From 50d21918b4a2339ad13ca04d53a27b1c46a695de Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Fri, 31 Jul 2026 20:14:03 -0500 Subject: [PATCH 002/147] update logs --- docs/changelogs/1.30.8.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelogs/1.30.8.md b/docs/changelogs/1.30.8.md index 3fd4b999c..fe7bb9ac0 100644 --- a/docs/changelogs/1.30.8.md +++ b/docs/changelogs/1.30.8.md @@ -6,3 +6,4 @@ Support for tanks with Chinese names. - The URL generator for BlitzKit now handles names that have only Chinese characters - The Mixed Virtual File System now handles non-existent directories correctly +- Updated equipments to match the game again From 514999e5884a387e13bdc70d5ce9af4e24a55750 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Fri, 31 Jul 2026 20:17:57 -0500 Subject: [PATCH 003/147] remove bot --- .gitignore | 1 - README.md | 19 +- docs/guides/introduction.md | 11 - docs/guides/invite.md | 3 - package.json | 3 - packages/bot/package.json | 35 - packages/bot/src/commands/about.ts | 68 -- packages/bot/src/commands/breakdown/index.tsx | 65 -- .../bot/src/commands/breakdown/render.tsx | 295 --------- packages/bot/src/commands/debug.ts | 37 -- packages/bot/src/commands/evolution.tsx | 182 ----- packages/bot/src/commands/fullStats.tsx | 117 ---- packages/bot/src/commands/inactive.tsx | 96 --- packages/bot/src/commands/link.tsx | 193 ------ packages/bot/src/commands/ownedTanks.tsx | 90 --- packages/bot/src/commands/permissions.ts | 46 -- packages/bot/src/commands/ping.ts | 34 - packages/bot/src/commands/playerInfo.ts | 69 -- packages/bot/src/commands/rating.tsx | 535 --------------- .../bot/src/commands/ratingLeaderboard.tsx | 468 ------------- packages/bot/src/commands/replay.tsx | 592 ----------------- packages/bot/src/commands/research.tsx | 626 ------------------ packages/bot/src/commands/searchClans.ts | 74 --- packages/bot/src/commands/searchPlayers.ts | 83 --- packages/bot/src/commands/stats.tsx | 102 --- packages/bot/src/commands/today.ts | 60 -- .../components/HeroStat/components/Glow.tsx | 50 -- .../components/HeroStat/index.tsx | 72 -- .../components/WN8SurroundingStat.tsx | 47 -- .../src/components/AllStatsOverview/index.tsx | 130 ---- .../components/Breakdown/components/Root.tsx | 16 - .../components/Breakdown/components/Row.tsx | 126 ---- .../Breakdown/components/RowStat.tsx | 89 --- .../bot/src/components/Breakdown/index.ts | 3 - .../bot/src/components/CommandWrapper.tsx | 26 - packages/bot/src/components/DeltaCaret.tsx | 21 - packages/bot/src/components/Flag.tsx | 19 - .../GenericAllStats/components/Card.tsx | 95 --- .../GenericAllStats/components/Root.tsx | 17 - .../GenericAllStats/components/Row.tsx | 7 - .../src/components/GenericAllStats/index.tsx | 149 ----- .../components/GenericStatsColumn.tsx | 16 - .../components/GenericStatsRow.tsx | 46 -- .../bot/src/components/GenericStats/index.tsx | 34 - .../Graph/components/Line/constants.ts | 11 - .../Graph/components/Line/index.tsx | 65 -- .../Graph/components/Margin/constants.ts | 4 - .../Graph/components/Margin/index.tsx | 49 -- .../Graph/components/Root/constants.ts | 2 - .../Graph/components/Root/index.tsx | 109 --- packages/bot/src/components/Graph/index.ts | 3 - .../components/Leaderboard/components/Gap.tsx | 29 - .../Leaderboard/components/Item.tsx | 171 ----- .../Leaderboard/components/Root.tsx | 13 - .../bot/src/components/Leaderboard/index.ts | 3 - .../bot/src/components/NoData/constants.ts | 5 - packages/bot/src/components/NoData/index.tsx | 30 - .../PercentileIndicator/constants.ts | 26 - .../components/PercentileIndicator/index.tsx | 19 - .../components/Tanks/components/Column.tsx | 20 - .../Tanks/components/Item/index.tsx | 75 --- .../src/components/Tanks/components/Root.tsx | 19 - .../src/components/Tanks/components/Row.tsx | 18 - .../src/components/Tanks/components/Title.tsx | 19 - packages/bot/src/components/Tanks/index.ts | 5 - packages/bot/src/components/TierWeights.tsx | 32 - packages/bot/src/components/TitleBar.tsx | 54 -- .../bot/src/core/blitz/normalizeLeagueIcon.ts | 3 - packages/bot/src/core/blitz/resolveTankId.ts | 48 -- .../searchClansAcrossRegionsBotWrapper.ts | 23 - .../searchPlayersAcrossRegionsBotWrapper.ts | 23 - .../src/core/blitzkit/buildTechTreeLine.ts | 20 - .../src/core/blitzkit/filtersToDescription.ts | 30 - .../src/core/blitzkit/getBlitzFromDiscord.ts | 16 - .../bot/src/core/blitzkit/getPeriodNow.ts | 3 - .../bot/src/core/blitzkit/getPeriodStart.ts | 8 - packages/bot/src/core/blitzkit/iconPng.ts | 16 - packages/bot/src/core/blitzkit/jsxToPng.ts | 9 - .../bot/src/core/blitzkit/jsxToPngThreaded.ts | 12 - .../core/blitzkit/jsxToSvg/Roboto-Black.ttf | Bin 129228 -> 0 bytes .../core/blitzkit/jsxToSvg/Roboto-Bold.ttf | Bin 128676 -> 0 bytes .../bot/src/core/blitzkit/jsxToSvg/Roboto.ttf | Bin 129584 -> 0 bytes .../bot/src/core/blitzkit/jsxToSvg/index.ts | 23 - .../src/core/blitzkit/nonBlockingPromises.ts | 15 - packages/bot/src/core/blitzkit/processes.ts | 37 -- .../src/core/blitzkit/renderConfiguration.ts | 11 - .../bot/src/core/blitzkit/resolveAncestry.ts | 25 - packages/bot/src/core/blitzkit/svgToPng.ts | 5 - .../bot/src/core/blitzkit/svgToPngThreaded.ts | 46 -- packages/bot/src/core/blitzkit/tankIconPng.ts | 6 - packages/bot/src/core/blitzkit/userError.ts | 6 - .../bot/src/core/blitzstars/filterStats.ts | 89 --- .../blitzstars/getBlitzStarsLinkButton.ts | 17 - .../src/core/blitzstars/getPlayerHistories.ts | 62 -- .../bot/src/core/blitzstars/getPlayerStats.ts | 37 -- .../src/core/blitzstars/getStatsInPeriod.ts | 103 --- .../src/core/blitzstars/getTankHistories.ts | 122 ---- packages/bot/src/core/db/flagUserActivity.ts | 9 - .../bot/src/core/db/linkBlitzAndDiscord.ts | 11 - packages/bot/src/core/db/users.ts | 3 - .../bot/src/core/discord/addClanChoices.ts | 26 - .../bot/src/core/discord/addFilterOptions.ts | 126 ---- .../src/core/discord/addPeriodSubCommands.ts | 150 ----- .../core/discord/addPeriodicFilterOptions.ts | 25 - .../bot/src/core/discord/addRegionChoices.ts | 41 -- .../bot/src/core/discord/addTankChoices.ts | 25 - .../bot/src/core/discord/addTierChoices.ts | 47 -- .../src/core/discord/addUsernameChoices.ts | 24 - .../discord/autocompleteClan/constants.ts | 2 - .../core/discord/autocompleteClan/index.ts | 40 -- .../bot/src/core/discord/autocompleteTanks.ts | 57 -- .../src/core/discord/autocompleteUsername.ts | 48 -- packages/bot/src/core/discord/buttonLink.ts | 20 - .../bot/src/core/discord/buttonPrimary.ts | 20 - .../bot/src/core/discord/buttonRefresh.ts | 19 - packages/bot/src/core/discord/chunkLines.ts | 17 - packages/bot/src/core/discord/client.ts | 9 - packages/bot/src/core/discord/colors.ts | 4 - packages/bot/src/core/discord/commandToURL.ts | 27 - .../core/discord/createLocalizedCommand.ts | 125 ---- packages/bot/src/core/discord/embedInfo.ts | 12 - .../bot/src/core/discord/embedNegative.ts | 9 - .../bot/src/core/discord/embedPositive.ts | 9 - packages/bot/src/core/discord/embedWarning.ts | 9 - .../src/core/discord/getCustomPeriodParams.ts | 19 - .../src/core/discord/getFiltersFromButton.ts | 19 - .../src/core/discord/getFiltersFromCommand.ts | 24 - .../src/core/discord/getPeriodOptionName.ts | 11 - .../src/core/discord/localizationObject.ts | 35 - .../bot/src/core/discord/markdownTable.ts | 9 - .../normalizeInteractionReturnable.tsx | 64 -- packages/bot/src/core/discord/psa.ts | 36 - .../core/discord/resolveClanFromCommand.ts | 36 - .../core/discord/resolvePeriodFromButton.ts | 40 -- .../core/discord/resolvePeriodFromCommand.ts | 52 -- .../core/discord/resolvePlayerFromButton.ts | 12 - .../discord/resolvePlayerFromCommand/index.ts | 53 -- .../core/localization/strings/constants.ts | 25 - .../bot/src/core/localization/translator.ts | 20 - packages/bot/src/core/radix/radixColors.ts | 55 -- packages/bot/src/core/streams/wotbreplay.ts | 57 -- .../handlers/autocomplete.ts | 15 - .../interactionCreate/handlers/button.tsx | 27 - .../handlers/chatInputCommand.ts | 151 ----- .../bot/src/events/interactionCreate/index.ts | 135 ---- packages/bot/src/events/ready.ts | 5 - packages/bot/src/main.ts | 81 --- packages/bot/src/stitches.config.ts | 54 -- packages/bot/src/types.d.ts | 4 - packages/bot/src/types/histories.ts | 24 - packages/bot/src/types/possiblyPromise.ts | 1 - packages/bot/src/types/ttf.d.ts | 4 - packages/bot/src/types/writable.ts | 1 - packages/bot/src/workers/bot.ts | 13 - packages/bot/src/workers/render.ts | 11 - packages/bot/tsconfig.json | 4 - packages/i18n/strings/en.json | 530 --------------- packages/scripts/package.json | 1 - packages/scripts/src/buildBot.ts | 54 -- prisma/schema.prisma | 24 - 160 files changed, 8 insertions(+), 9075 deletions(-) delete mode 100644 docs/guides/invite.md delete mode 100644 packages/bot/package.json delete mode 100644 packages/bot/src/commands/about.ts delete mode 100644 packages/bot/src/commands/breakdown/index.tsx delete mode 100644 packages/bot/src/commands/breakdown/render.tsx delete mode 100644 packages/bot/src/commands/debug.ts delete mode 100644 packages/bot/src/commands/evolution.tsx delete mode 100644 packages/bot/src/commands/fullStats.tsx delete mode 100644 packages/bot/src/commands/inactive.tsx delete mode 100644 packages/bot/src/commands/link.tsx delete mode 100644 packages/bot/src/commands/ownedTanks.tsx delete mode 100644 packages/bot/src/commands/permissions.ts delete mode 100644 packages/bot/src/commands/ping.ts delete mode 100644 packages/bot/src/commands/playerInfo.ts delete mode 100644 packages/bot/src/commands/rating.tsx delete mode 100644 packages/bot/src/commands/ratingLeaderboard.tsx delete mode 100644 packages/bot/src/commands/replay.tsx delete mode 100644 packages/bot/src/commands/research.tsx delete mode 100644 packages/bot/src/commands/searchClans.ts delete mode 100644 packages/bot/src/commands/searchPlayers.ts delete mode 100644 packages/bot/src/commands/stats.tsx delete mode 100644 packages/bot/src/commands/today.ts delete mode 100644 packages/bot/src/components/AllStatsOverview/components/HeroStat/components/Glow.tsx delete mode 100644 packages/bot/src/components/AllStatsOverview/components/HeroStat/index.tsx delete mode 100644 packages/bot/src/components/AllStatsOverview/components/WN8SurroundingStat.tsx delete mode 100644 packages/bot/src/components/AllStatsOverview/index.tsx delete mode 100644 packages/bot/src/components/Breakdown/components/Root.tsx delete mode 100644 packages/bot/src/components/Breakdown/components/Row.tsx delete mode 100644 packages/bot/src/components/Breakdown/components/RowStat.tsx delete mode 100644 packages/bot/src/components/Breakdown/index.ts delete mode 100644 packages/bot/src/components/CommandWrapper.tsx delete mode 100644 packages/bot/src/components/DeltaCaret.tsx delete mode 100644 packages/bot/src/components/Flag.tsx delete mode 100644 packages/bot/src/components/GenericAllStats/components/Card.tsx delete mode 100644 packages/bot/src/components/GenericAllStats/components/Root.tsx delete mode 100644 packages/bot/src/components/GenericAllStats/components/Row.tsx delete mode 100644 packages/bot/src/components/GenericAllStats/index.tsx delete mode 100644 packages/bot/src/components/GenericStats/components/GenericStatsColumn.tsx delete mode 100644 packages/bot/src/components/GenericStats/components/GenericStatsRow.tsx delete mode 100644 packages/bot/src/components/GenericStats/index.tsx delete mode 100644 packages/bot/src/components/Graph/components/Line/constants.ts delete mode 100644 packages/bot/src/components/Graph/components/Line/index.tsx delete mode 100644 packages/bot/src/components/Graph/components/Margin/constants.ts delete mode 100644 packages/bot/src/components/Graph/components/Margin/index.tsx delete mode 100644 packages/bot/src/components/Graph/components/Root/constants.ts delete mode 100644 packages/bot/src/components/Graph/components/Root/index.tsx delete mode 100644 packages/bot/src/components/Graph/index.ts delete mode 100644 packages/bot/src/components/Leaderboard/components/Gap.tsx delete mode 100644 packages/bot/src/components/Leaderboard/components/Item.tsx delete mode 100644 packages/bot/src/components/Leaderboard/components/Root.tsx delete mode 100644 packages/bot/src/components/Leaderboard/index.ts delete mode 100644 packages/bot/src/components/NoData/constants.ts delete mode 100644 packages/bot/src/components/NoData/index.tsx delete mode 100644 packages/bot/src/components/PercentileIndicator/constants.ts delete mode 100644 packages/bot/src/components/PercentileIndicator/index.tsx delete mode 100644 packages/bot/src/components/Tanks/components/Column.tsx delete mode 100644 packages/bot/src/components/Tanks/components/Item/index.tsx delete mode 100644 packages/bot/src/components/Tanks/components/Root.tsx delete mode 100644 packages/bot/src/components/Tanks/components/Row.tsx delete mode 100644 packages/bot/src/components/Tanks/components/Title.tsx delete mode 100644 packages/bot/src/components/Tanks/index.ts delete mode 100644 packages/bot/src/components/TierWeights.tsx delete mode 100644 packages/bot/src/components/TitleBar.tsx delete mode 100644 packages/bot/src/core/blitz/normalizeLeagueIcon.ts delete mode 100644 packages/bot/src/core/blitz/resolveTankId.ts delete mode 100644 packages/bot/src/core/blitz/searchClansAcrossRegionsBotWrapper.ts delete mode 100644 packages/bot/src/core/blitz/searchPlayersAcrossRegionsBotWrapper.ts delete mode 100644 packages/bot/src/core/blitzkit/buildTechTreeLine.ts delete mode 100644 packages/bot/src/core/blitzkit/filtersToDescription.ts delete mode 100644 packages/bot/src/core/blitzkit/getBlitzFromDiscord.ts delete mode 100644 packages/bot/src/core/blitzkit/getPeriodNow.ts delete mode 100644 packages/bot/src/core/blitzkit/getPeriodStart.ts delete mode 100644 packages/bot/src/core/blitzkit/iconPng.ts delete mode 100644 packages/bot/src/core/blitzkit/jsxToPng.ts delete mode 100644 packages/bot/src/core/blitzkit/jsxToPngThreaded.ts delete mode 100644 packages/bot/src/core/blitzkit/jsxToSvg/Roboto-Black.ttf delete mode 100644 packages/bot/src/core/blitzkit/jsxToSvg/Roboto-Bold.ttf delete mode 100644 packages/bot/src/core/blitzkit/jsxToSvg/Roboto.ttf delete mode 100644 packages/bot/src/core/blitzkit/jsxToSvg/index.ts delete mode 100644 packages/bot/src/core/blitzkit/nonBlockingPromises.ts delete mode 100644 packages/bot/src/core/blitzkit/processes.ts delete mode 100644 packages/bot/src/core/blitzkit/renderConfiguration.ts delete mode 100644 packages/bot/src/core/blitzkit/resolveAncestry.ts delete mode 100644 packages/bot/src/core/blitzkit/svgToPng.ts delete mode 100644 packages/bot/src/core/blitzkit/svgToPngThreaded.ts delete mode 100644 packages/bot/src/core/blitzkit/tankIconPng.ts delete mode 100644 packages/bot/src/core/blitzkit/userError.ts delete mode 100644 packages/bot/src/core/blitzstars/filterStats.ts delete mode 100644 packages/bot/src/core/blitzstars/getBlitzStarsLinkButton.ts delete mode 100644 packages/bot/src/core/blitzstars/getPlayerHistories.ts delete mode 100644 packages/bot/src/core/blitzstars/getPlayerStats.ts delete mode 100644 packages/bot/src/core/blitzstars/getStatsInPeriod.ts delete mode 100644 packages/bot/src/core/blitzstars/getTankHistories.ts delete mode 100644 packages/bot/src/core/db/flagUserActivity.ts delete mode 100644 packages/bot/src/core/db/linkBlitzAndDiscord.ts delete mode 100644 packages/bot/src/core/db/users.ts delete mode 100644 packages/bot/src/core/discord/addClanChoices.ts delete mode 100644 packages/bot/src/core/discord/addFilterOptions.ts delete mode 100644 packages/bot/src/core/discord/addPeriodSubCommands.ts delete mode 100644 packages/bot/src/core/discord/addPeriodicFilterOptions.ts delete mode 100644 packages/bot/src/core/discord/addRegionChoices.ts delete mode 100644 packages/bot/src/core/discord/addTankChoices.ts delete mode 100644 packages/bot/src/core/discord/addTierChoices.ts delete mode 100644 packages/bot/src/core/discord/addUsernameChoices.ts delete mode 100644 packages/bot/src/core/discord/autocompleteClan/constants.ts delete mode 100644 packages/bot/src/core/discord/autocompleteClan/index.ts delete mode 100644 packages/bot/src/core/discord/autocompleteTanks.ts delete mode 100644 packages/bot/src/core/discord/autocompleteUsername.ts delete mode 100644 packages/bot/src/core/discord/buttonLink.ts delete mode 100644 packages/bot/src/core/discord/buttonPrimary.ts delete mode 100644 packages/bot/src/core/discord/buttonRefresh.ts delete mode 100644 packages/bot/src/core/discord/chunkLines.ts delete mode 100644 packages/bot/src/core/discord/client.ts delete mode 100644 packages/bot/src/core/discord/colors.ts delete mode 100644 packages/bot/src/core/discord/commandToURL.ts delete mode 100644 packages/bot/src/core/discord/createLocalizedCommand.ts delete mode 100644 packages/bot/src/core/discord/embedInfo.ts delete mode 100644 packages/bot/src/core/discord/embedNegative.ts delete mode 100644 packages/bot/src/core/discord/embedPositive.ts delete mode 100644 packages/bot/src/core/discord/embedWarning.ts delete mode 100644 packages/bot/src/core/discord/getCustomPeriodParams.ts delete mode 100644 packages/bot/src/core/discord/getFiltersFromButton.ts delete mode 100644 packages/bot/src/core/discord/getFiltersFromCommand.ts delete mode 100644 packages/bot/src/core/discord/getPeriodOptionName.ts delete mode 100644 packages/bot/src/core/discord/localizationObject.ts delete mode 100644 packages/bot/src/core/discord/markdownTable.ts delete mode 100644 packages/bot/src/core/discord/normalizeInteractionReturnable.tsx delete mode 100644 packages/bot/src/core/discord/psa.ts delete mode 100644 packages/bot/src/core/discord/resolveClanFromCommand.ts delete mode 100644 packages/bot/src/core/discord/resolvePeriodFromButton.ts delete mode 100644 packages/bot/src/core/discord/resolvePeriodFromCommand.ts delete mode 100644 packages/bot/src/core/discord/resolvePlayerFromButton.ts delete mode 100644 packages/bot/src/core/discord/resolvePlayerFromCommand/index.ts delete mode 100644 packages/bot/src/core/localization/strings/constants.ts delete mode 100644 packages/bot/src/core/localization/translator.ts delete mode 100644 packages/bot/src/core/radix/radixColors.ts delete mode 100644 packages/bot/src/core/streams/wotbreplay.ts delete mode 100644 packages/bot/src/events/interactionCreate/handlers/autocomplete.ts delete mode 100644 packages/bot/src/events/interactionCreate/handlers/button.tsx delete mode 100644 packages/bot/src/events/interactionCreate/handlers/chatInputCommand.ts delete mode 100644 packages/bot/src/events/interactionCreate/index.ts delete mode 100644 packages/bot/src/events/ready.ts delete mode 100644 packages/bot/src/main.ts delete mode 100644 packages/bot/src/stitches.config.ts delete mode 100644 packages/bot/src/types.d.ts delete mode 100644 packages/bot/src/types/histories.ts delete mode 100644 packages/bot/src/types/possiblyPromise.ts delete mode 100644 packages/bot/src/types/ttf.d.ts delete mode 100644 packages/bot/src/types/writable.ts delete mode 100644 packages/bot/src/workers/bot.ts delete mode 100644 packages/bot/src/workers/render.ts delete mode 100644 packages/bot/tsconfig.json delete mode 100644 packages/scripts/src/buildBot.ts delete mode 100644 prisma/schema.prisma diff --git a/.gitignore b/.gitignore index 9e20f4a27..2124182fd 100644 --- a/.gitignore +++ b/.gitignore @@ -27,7 +27,6 @@ target .astro .vercel /src -/packages/bot/src/prisma dev-dist # Logs logs diff --git a/README.md b/README.md index 8e1b06629..4a7de2108 100644 --- a/README.md +++ b/README.md @@ -8,15 +8,14 @@ If you intend to contribute to the code, note that you may need to check out sen ## Packages -| Package | Title | Source | Memo | -| ------------------------------------ | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------ | -| [packages/app](packages/app) | BlitzKit | [Play Store](https://play.google.com/store/apps/details?id=com.tresabhi.blitzkit), [App Store](https://apps.apple.com/app/4321), [Microsoft Store](https://apps.microsoft.com/detail/zyxw) | The native app of BlitzKit for all platforms. Unavailable from stores at this time. | -| [packages/bot](packages/bot) | BlitzKit Discord Bot | [Discord Application Directory](https://discord.com/discovery/applications/1097673957865443370) | The one-in-all Discord bot for statistics. | -| [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/varuna](packages/varuna) | Varuna | [NPM](https://www.npmjs.com/package/varuna) | React and Astro-oriented state management library, awaiting separation from BlitzKit. Unavailable from NPM at this time. | -| [packages/website](packages/website) | BlitzKit | https://blitzkit.app | The BlitzKit website provides a wide range of tools, APIs, and interconnects all other services. | +| Package | Title | Source | Memo | +| ------------------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------ | +| [packages/app](packages/app) | BlitzKit | [Play Store](https://play.google.com/store/apps/details?id=com.tresabhi.blitzkit), [App Store](https://apps.apple.com/app/4321), [Microsoft Store](https://apps.microsoft.com/detail/zyxw) | The native app of BlitzKit for all platforms. Unavailable from stores at this time. | +| [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/varuna](packages/varuna) | Varuna | [NPM](https://www.npmjs.com/package/varuna) | React and Astro-oriented state management library, awaiting separation from BlitzKit. Unavailable from NPM at this time. | +| [packages/website](packages/website) | BlitzKit | https://blitzkit.app | The BlitzKit website provides a wide range of tools, APIs, and interconnects all other services. | ## Submodules @@ -40,8 +39,6 @@ If you intend to contribute to the code, note that you may need to check out sen | `PUBLIC_BRANCH` | | `main` | The branch that the website is operating on. This is completely nominal. | | `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. | -| `DISCORD_CLIENT_ID` | [Discord Developer Portal](https://discord.com/developers/applications) | `1097673957865443370` | The BlitzKit Discord Bot's user ID. | | `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. | -| `DISCORD_TOKEN` | [Discord Developer Portal](https://discord.com/developers/applications) | | The Discord API Key for the BlitzKit Discord Bot. | | `DATABASE_URL` | [CockroachDB](https://www.cockroachlabs.com/) | | The CockroachDB-SQL database URL used to link Wargaming and Discord users. | | `WOTB_DLC_CDN` | | | | diff --git a/docs/guides/introduction.md b/docs/guides/introduction.md index f56f1c1ae..712323555 100644 --- a/docs/guides/introduction.md +++ b/docs/guides/introduction.md @@ -2,17 +2,6 @@ 🎉 Services for everything and anything World of Tanks Blitz -## Bot - -BlitzKit is available as a Discord bot and can be [installed very easily](/guide/invite). - -- Track statistics -- Find inactive players in a clan -- Lookup owned tanks -- Create embeds for streams -- Track a player/tanks statistical evolution -- ...and a variety of other similar things - ## Website Provides a bunch of very helpful tools; https://blitzkit.app/ diff --git a/docs/guides/invite.md b/docs/guides/invite.md deleted file mode 100644 index 7a1af2ff1..000000000 --- a/docs/guides/invite.md +++ /dev/null @@ -1,3 +0,0 @@ -# Inviting the Bot - -Go to [application directory for BlitzKit](https://discord.com/application-directory/1097673957865443370) and click "Add to Server." Voila! You're ready to go. diff --git a/package.json b/package.json index a61810c71..8064764f0 100644 --- a/package.json +++ b/package.json @@ -6,15 +6,12 @@ "scripts": { "build:assets": "cd packages/scripts && bun run build:assets", "build:averages": "bun --filter=@blitzkit/scripts run build:averages", - "build:bot": "cd packages/scripts && bun run build:bot", "build:website": "cd packages/website && bun run build", "build:prisma": "prisma generate", "build:proxyclient": "cd packages/scripts && bun run build:proxyclient", "build:catalogitemaccesssor": "cd packages/scripts && bun run build:catalogitemaccesssor", "build:ratingleaderboard": "cd packages/scripts && bun run build:ratingleaderboard", "build:app": "cd packages/app && bun run build", - "dev:bot": "bun run build:bot --dev && bun start:bot --dev", - "start:bot": "bun dist/bot/main.js", "dev:docs": "vitepress dev docs", "dev:website": "cd packages/website && bun run dev", "dev:app": "cd packages/app && bun run dev", diff --git a/packages/bot/package.json b/packages/bot/package.json deleted file mode 100644 index 6e18e80ef..000000000 --- a/packages/bot/package.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "@blitzkit/bot", - "dependencies": { - "@blitzkit/core": "workspace:*", - "@blitzkit/i18n": "workspace:*", - "@radix-ui/colors": "^3.0.0", - "@radix-ui/themes": "^3.3.0", - "@resvg/resvg-js": "^2.6.2", - "@types/adm-zip": "^0.5.7", - "@types/bun": "^1.3.9", - "@types/lodash-es": "^4.17.12", - "@types/markdown-escape": "^1.1.3", - "@types/react": "^19.2.14", - "adm-zip": "^0.5.16", - "bepaint": "^0.1.0-beta.3", - "discord.js": "^14.25.1", - "dotenv": "^17.3.1", - "fuzzysort": "^3.1.0", - "lodash-es": "^4.17.23", - "markdown-escape": "^2.0.0", - "pickleparser": "^0.2.1", - "prisma": "^6.19.2", - "protobufjs": "^7.5.4", - "react": "^19.2.4", - "satori": "^0.18.4", - "sharp": "^0.34.5" - }, - "private": true, - "scripts": { - "dev": "bun --watch src/main.ts", - "start": "bun src/main.ts" - }, - "type": "module", - "types": "src/types.d.ts" -} diff --git a/packages/bot/src/commands/about.ts b/packages/bot/src/commands/about.ts deleted file mode 100644 index 82552eca3..000000000 --- a/packages/bot/src/commands/about.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { translator } from "../core/localization/translator"; -import { - commandsArray, - type CommandRegistry, -} from "../events/interactionCreate"; - -const subcommands = [ - "embeds", - "introduction", - "invite", - "timezones", - "credits", -] as const; -type Subcommand = (typeof subcommands)[number]; - -const DOCS: Record = { - embeds: "guide/embeds", - introduction: "guide/introduction", - invite: "guide/invite", - timezones: "guide/timezones", - credits: "about/credits", -}; - -export const aboutCommand = new Promise(async (resolve) => { - resolve({ - command: createLocalizedCommand("about", [ - { subcommand: "commands" }, - ...subcommands.map((subcommand) => ({ subcommand })), - ]), - - async handler(interaction) { - const subcommand = interaction.options.getSubcommand(); - const { strings } = translator(interaction.locale); - - if (subcommand === "commands") { - return `${strings.bot.commands.about.subcommands.commands.body}${( - await Promise.all(commandsArray) - ) - .sort((a, b) => - (a.command.name_localizations?.[interaction.locale] ?? - a.command.name) < - (b.command.name_localizations?.[interaction.locale] ?? - b.command.name) - ? -1 - : 1, - ) - .map( - (registry) => - `- \`/${ - registry.command.name_localizations?.[interaction.locale] ?? - registry.command.name - }\`: ${ - registry.command.description_localizations?.[ - interaction.locale - ] ?? registry.command.description - }`, - ) - .join("\n")}`; - } - - const url = `https://raw.githubusercontent.com/tresabhi/blitzkit/main/docs/${DOCS[subcommand as Subcommand]}.md`; - const content = fetch(url).then((response) => response.text()); - - return content; - }, - }); -}); diff --git a/packages/bot/src/commands/breakdown/index.tsx b/packages/bot/src/commands/breakdown/index.tsx deleted file mode 100644 index 958e8abd2..000000000 --- a/packages/bot/src/commands/breakdown/index.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { addPeriodicFilterOptions } from "../../core/discord/addPeriodicFilterOptions"; -import { addUsernameChoices } from "../../core/discord/addUsernameChoices"; -import { autocompleteTanks } from "../../core/discord/autocompleteTanks"; -import { autocompleteUsername } from "../../core/discord/autocompleteUsername"; -import { createLocalizedCommand } from "../../core/discord/createLocalizedCommand"; -import { getFiltersFromButton } from "../../core/discord/getFiltersFromButton"; -import { resolvePeriodFromButton } from "../../core/discord/resolvePeriodFromButton"; -import { resolvePlayerFromButton } from "../../core/discord/resolvePlayerFromButton"; -import { translator } from "../../core/localization/translator"; -import type { CommandRegistry } from "../../events/interactionCreate"; -import { renderBreakdown } from "./render"; - -export const breakdownCommand = new Promise( - async (resolve) => { - resolve({ - command: await addPeriodicFilterOptions( - createLocalizedCommand("breakdown"), - (option) => option.addStringOption(addUsernameChoices), - ), - - async handler(interaction) { - const { strings } = translator(interaction.locale); - return strings.bot.commands.dead; - - // const player = await resolvePlayerFromCommand(interaction); - // const period = resolvePeriodFromCommand(player.region, interaction); - // const filters = await getFiltersFromCommand(interaction); - // const path = commandToURL(interaction, { - // ...player, - // ...getCustomPeriodParams(interaction), - // ...filters, - // }); - - // return [ - // ...(await renderBreakdown( - // player, - // period, - // filters, - // interaction.locale, - // )), - // buttonRefresh(interaction, path), - // await getBlitzStarsLinkButton(player.region, player.id), - // ]; - }, - - autocomplete: (interaction) => { - autocompleteUsername(interaction); - autocompleteTanks(interaction); - }, - - async button(interaction) { - const player = await resolvePlayerFromButton(interaction); - const period = resolvePeriodFromButton(player.region, interaction); - const filters = getFiltersFromButton(interaction); - - return await renderBreakdown( - player, - period, - filters, - interaction.locale, - ); - }, - }); - }, -); diff --git a/packages/bot/src/commands/breakdown/render.tsx b/packages/bot/src/commands/breakdown/render.tsx deleted file mode 100644 index 91fda4e2b..000000000 --- a/packages/bot/src/commands/breakdown/render.tsx +++ /dev/null @@ -1,295 +0,0 @@ -import { - type BlitzStats, - calculateWN8, - getAccountInfo, - getClanAccountInfo, - getTankStats, - getWN8Percentile, - TankDefinition, -} from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { Locale } from "discord.js"; -import { chunk } from "lodash-es"; -import * as Breakdown from "../../components/Breakdown"; -import { CommandWrapper } from "../../components/CommandWrapper"; -import { NoData } from "../../components/NoData"; -import { TitleBar } from "../../components/TitleBar"; -import { filtersToDescription } from "../../core/blitzkit/filtersToDescription"; -import { - averageDefinitions, - tankDefinitions, -} from "../../core/blitzkit/nonBlockingPromises"; -import { UserError } from "../../core/blitzkit/userError"; -import { - filterStats, - type StatFilters, -} from "../../core/blitzstars/filterStats"; -import { getStatsInPeriod } from "../../core/blitzstars/getStatsInPeriod"; -import type { ResolvedPeriod } from "../../core/discord/resolvePeriodFromCommand"; -import type { ResolvedPlayer } from "../../core/discord/resolvePlayerFromCommand"; -import { translator } from "../../core/localization/translator"; - -const ROWS_PER_PAGE = 8; -const MAX_PAGES = 9; - -export async function renderBreakdown( - { region, id }: ResolvedPlayer, - { start, end, name }: ResolvedPeriod, - filters: StatFilters, - locale: Locale, -) { - const { strings, unwrap } = translator(locale); - const awaitedTankDefinitions = await tankDefinitions; - const awaitedTankAverages = await averageDefinitions; - const statsInPeriod = await getStatsInPeriod(region, id, start, end, locale); - const { filteredOrder } = await filterStats(statsInPeriod, filters); - const accountInfo = await getAccountInfo(region, id); - const clanData = await getClanAccountInfo(region, id, ["clan"]); - const tankStats = await getTankStats(region, id); - const filterDescriptions = await filtersToDescription(filters, locale); - const orderedCurrentStats: BlitzStats[] = []; - const orderedCareerStats: BlitzStats[] = []; - const orderedCurrentWN8: (number | undefined)[] = []; - const orderedCareerWN8: (number | undefined)[] = []; - const orderedCareerWN8Full: (number | undefined)[] = []; - - if (tankStats === null) { - throw new UserError(strings.bot.common.errors.no_tank_stats); - } - - filteredOrder.forEach((id) => { - const tankAveragesEntry = awaitedTankAverages.averages[id]; - const current = statsInPeriod.diff[id]; - const career = tankStats.find((tank) => tank.tank_id === id)!.all; - const currentWN8 = tankAveragesEntry - ? calculateWN8(tankAveragesEntry.mu, current) - : undefined; - const careerWN8 = tankAveragesEntry - ? calculateWN8(tankAveragesEntry.mu, career) - : undefined; - - orderedCurrentStats.push(current); - orderedCareerStats.push(career); - orderedCurrentWN8.push(currentWN8); - orderedCareerWN8.push(careerWN8); - }); - tankStats.forEach((tank) => { - const tankAveragesEntry = awaitedTankAverages.averages[tank.tank_id]; - - orderedCareerWN8Full.push( - tankAveragesEntry - ? calculateWN8(tankAveragesEntry.mu, tank.all) - : undefined, - ); - }); - - const currentBattles = orderedCurrentStats.reduce( - (accumulator, current) => accumulator + current.battles, - 0, - ); - const careerBattles = tankStats.reduce( - (accumulator, current) => accumulator + current.all.battles, - 0, - ); - const currentWinrate = - orderedCurrentStats.reduce( - (accumulator, current) => accumulator + current.wins, - 0, - ) / currentBattles; - const careerWinrate = - tankStats.reduce( - (accumulator, current) => accumulator + current.all.wins, - 0, - ) / careerBattles; - const currentWN8 = - orderedCurrentWN8.reduce( - (accumulator, current, index) => - accumulator + (current ?? 0) * orderedCurrentStats[index].battles, - 0, - ) / - orderedCurrentStats.reduce( - (accumulator, current, index) => - accumulator + - (orderedCurrentWN8[index] === undefined ? 0 : current.battles), - 0, - ); - const careerWN8 = - orderedCareerWN8Full.reduce( - (accumulator, current) => accumulator + (current ?? 0), - 0, - ) / - tankStats.reduce( - (accumulator, current, index) => - accumulator + - (orderedCareerWN8Full[index] === undefined ? 0 : current.all.battles), - 0, - ); - const currentDamage = - orderedCurrentStats.reduce( - (accumulator, current) => accumulator + current.damage_dealt, - 0, - ) / currentBattles; - const careerDamage = - tankStats.reduce( - (accumulator, current) => accumulator + current.all.damage_dealt, - 0, - ) / careerBattles; - - const children: React.JSX.Element[] = []; - - if (filteredOrder.length > 0) { - children.push( - , - ); - - filteredOrder.forEach((id, index) => { - const tankDefinition = awaitedTankDefinitions.tanks[id] as - | TankDefinition - | undefined; - const current = orderedCurrentStats[index]; - const career = orderedCareerStats[index]; - const currentWN8 = orderedCurrentWN8[index]; - const careerWN8 = orderedCareerWN8[index]; - - children.push( - , - ); - }); - } - - const pages = chunk( - children, - Math.max(Math.ceil(children.length / MAX_PAGES), ROWS_PER_PAGE), - ); - - if (filteredOrder.length > 0) { - return pages - .map((page, index) => ( - - {index === 0 && ( - - )} - - {page} - - )) - .reverse(); - } else { - return [ - - - - - , - ]; - } -} diff --git a/packages/bot/src/commands/debug.ts b/packages/bot/src/commands/debug.ts deleted file mode 100644 index 08f43e8ba..000000000 --- a/packages/bot/src/commands/debug.ts +++ /dev/null @@ -1,37 +0,0 @@ -import packageJSON from "../../../../package.json"; -import { client } from "../core/discord/client"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { translator } from "../core/localization/translator"; -import type { CommandRegistry } from "../events/interactionCreate"; - -const executionStart = new Date().getTime(); - -export const debugCommand = new Promise((resolve) => { - resolve({ - command: createLocalizedCommand("debug"), - - async handler(interaction) { - const { strings } = translator(interaction.locale); - const currentTime = new Date().getTime(); - const uptime = currentTime - executionStart; - const list = [ - [strings.bot.commands.debug.body.version, packageJSON.version], - [ - strings.bot.commands.debug.body.shard.label, - client.shard?.ids[0] ?? strings.bot.commands.debug.body.shard.default, - ], - [ - strings.bot.commands.debug.body.uptime, - `${Math.floor((uptime / 1000 / 60 / 60) % 24)}h ${Math.floor( - (uptime / 1000 / 60) % 60 - )}m ${Math.floor((uptime / 1000) % 60)}s ${Math.floor( - uptime % 1000 - )}ms`, - ], - ]; - return `${strings.bot.commands.debug.body.title}${list - .map(([key, value]) => `- ${key}: ${value}`) - .join("\n")}`; - }, - }); -}); diff --git a/packages/bot/src/commands/evolution.tsx b/packages/bot/src/commands/evolution.tsx deleted file mode 100644 index 9717938f9..000000000 --- a/packages/bot/src/commands/evolution.tsx +++ /dev/null @@ -1,182 +0,0 @@ -import { emblemURL, getAccountInfo, getClanAccountInfo } from "@blitzkit/core"; -import { Locale, SlashCommandSubcommandGroupBuilder } from "discord.js"; -import { CommandWrapper } from "../components/CommandWrapper"; -import * as Graph from "../components/Graph"; -import { LineColor } from "../components/Graph/components/Line/constants"; -import { NoData } from "../components/NoData"; -import { TitleBar } from "../components/TitleBar"; -import { getPlayerHistories } from "../core/blitzstars/getPlayerHistories"; -import { getTankHistories } from "../core/blitzstars/getTankHistories"; -import { addPeriodSubCommands } from "../core/discord/addPeriodSubCommands"; -import { addTankChoices } from "../core/discord/addTankChoices"; -import { addUsernameChoices } from "../core/discord/addUsernameChoices"; -import { autocompleteTanks } from "../core/discord/autocompleteTanks"; -import { autocompleteUsername } from "../core/discord/autocompleteUsername"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { resolvePeriodFromButton } from "../core/discord/resolvePeriodFromButton"; -import { type ResolvedPeriod } from "../core/discord/resolvePeriodFromCommand"; -import { resolvePlayerFromButton } from "../core/discord/resolvePlayerFromButton"; -import { type ResolvedPlayer } from "../core/discord/resolvePlayerFromCommand"; -import { translator } from "../core/localization/translator"; -import type { CommandRegistry } from "../events/interactionCreate"; - -type EvolutionStatType = "player" | "tank"; - -async function render( - { region, id }: ResolvedPlayer, - { start, end, name }: ResolvedPeriod, - type: "player" | "tank", - tankId: number | null, - locale: Locale, -) { - const accountInfo = await getAccountInfo(region, id); - const clan = (await getClanAccountInfo(region, id, ["clan"]))?.clan; - const logo = clan ? emblemURL(clan.emblem_set_id) : undefined; - const histories = await (type === "player" - ? getPlayerHistories(region, id, { - start, - end, - includeLatestHistories: true, - includePreviousHistories: true, - }) - : getTankHistories(region, id, locale, { - tankId: tankId!, - start, - end, - includeLatestHistories: true, - includePreviousHistories: true, - })); - const plot = histories.map( - (history) => - [ - history.all.battles, - (history.all.wins / history.all.battles) * 100, - ] as Graph.PlotItem, - ); - const times = histories.map((history) => history.last_battle_time); - const minTime = Math.min(...times); - const maxTime = Math.max(...times); - const ys = plot.map(([, y]) => y); - const xs = plot.map(([x]) => x); - const maxY = Math.max(...ys); - const minY = Math.min(...ys); - const maxX = Math.max(...xs); - const minX = Math.min(...xs); - - return ( - - - - {plot.length > 0 && ( - - {Graph.Line({ - color: LineColor.Red, - plot: plot, - minY, - maxY, - })} - - )} - - {plot.length === 0 && } - - ); -} - -export const evolutionCommand = new Promise( - async (resolve) => { - resolve({ - command: createLocalizedCommand("evolution", [ - { - group: "player", - modify(option: SlashCommandSubcommandGroupBuilder) { - addPeriodSubCommands(option, (option) => - option.addStringOption(addUsernameChoices), - ); - }, - }, - { - group: "tank", - modify(option: SlashCommandSubcommandGroupBuilder) { - addPeriodSubCommands(option, (option) => - option - .addStringOption(addTankChoices) - .addStringOption(addUsernameChoices), - ); - }, - }, - ]), - - async handler(interaction) { - const { strings } = translator(interaction.locale); - return strings.bot.commands.dead; - - // const commandGroup = - // interaction.options.getSubcommandGroup() as EvolutionStatType; - // const player = await resolvePlayerFromCommand(interaction); - // const period = resolvePeriodFromCommand(player.region, interaction); - // const tankIdRaw = interaction.options.getString("tank")!; - // const tankId = - // commandGroup === "tank" - // ? await resolveTankId(tankIdRaw, interaction.locale) - // : null; - // const path = commandToURL(interaction, { - // ...player, - // ...getCustomPeriodParams(interaction), - // tankId, - // }); - - // return [ - // await render( - // player, - // period, - // commandGroup, - // tankId, - // interaction.locale - // ), - // buttonRefresh(interaction, path), - // await getBlitzStarsLinkButton( - // player.region, - // player.id, - // tankId ?? undefined - // ), - // ]; - }, - - autocomplete: (interaction) => { - autocompleteUsername(interaction); - autocompleteTanks(interaction); - }, - - async button(interaction) { - const url = new URL(`https://example.com/${interaction.customId}`); - const path = url.pathname.split("/"); - const commandGroup = path[2] as EvolutionStatType; - const player = await resolvePlayerFromButton(interaction); - const period = resolvePeriodFromButton(player.region, interaction); - - return await render( - player, - period, - commandGroup, - parseInt(url.searchParams.get("tankId") ?? "0") || null, - interaction.locale, - ); - }, - }); - }, -); diff --git a/packages/bot/src/commands/fullStats.tsx b/packages/bot/src/commands/fullStats.tsx deleted file mode 100644 index 3cc13ce3d..000000000 --- a/packages/bot/src/commands/fullStats.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import { - emblemURL, - getAccountInfo, - getClanAccountInfo, - getTierWeights, -} from "@blitzkit/core"; -import { Locale } from "discord.js"; -import { CommandWrapper } from "../components/CommandWrapper"; -import { GenericAllStats } from "../components/GenericAllStats"; -import { NoData } from "../components/NoData"; -import { TierWeights } from "../components/TierWeights"; -import { TitleBar } from "../components/TitleBar"; -import { filtersToDescription } from "../core/blitzkit/filtersToDescription"; -import { filterStats, type StatFilters } from "../core/blitzstars/filterStats"; -import { getStatsInPeriod } from "../core/blitzstars/getStatsInPeriod"; -import { addPeriodicFilterOptions } from "../core/discord/addPeriodicFilterOptions"; -import { addUsernameChoices } from "../core/discord/addUsernameChoices"; -import { autocompleteTanks } from "../core/discord/autocompleteTanks"; -import { autocompleteUsername } from "../core/discord/autocompleteUsername"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { getFiltersFromButton } from "../core/discord/getFiltersFromButton"; -import { resolvePeriodFromButton } from "../core/discord/resolvePeriodFromButton"; -import { type ResolvedPeriod } from "../core/discord/resolvePeriodFromCommand"; -import { resolvePlayerFromButton } from "../core/discord/resolvePlayerFromButton"; -import { type ResolvedPlayer } from "../core/discord/resolvePlayerFromCommand"; -import { translator } from "../core/localization/translator"; -import type { CommandRegistry } from "../events/interactionCreate"; - -async function render( - { region, id }: ResolvedPlayer, - { start, end, name }: ResolvedPeriod, - filters: StatFilters, - locale: Locale, -) { - const { nickname } = await getAccountInfo(region, id); - const clan = (await getClanAccountInfo(region, id, ["clan"]))?.clan; - const clanImage = clan ? emblemURL(clan.emblem_set_id) : undefined; - const diffedTankStats = await getStatsInPeriod( - region, - id, - start, - end, - locale, - ); - const { stats, supplementary, filteredOrder } = await filterStats( - diffedTankStats, - filters, - ); - const filterDescriptions = await filtersToDescription(filters, locale); - const tierWeights = await getTierWeights(diffedTankStats.diff, filteredOrder); - - return ( - - - - {stats.battles === 0 && ( - - )} - {stats.battles > 0 && } - {stats.battles > 0 && ( - - )} - - ); -} - -export const fullStatsCommand = new Promise( - async (resolve) => { - resolve({ - command: await addPeriodicFilterOptions( - createLocalizedCommand("full-stats"), - (option) => option.addStringOption(addUsernameChoices), - ), - - async handler(interaction) { - const { strings } = translator(interaction.locale); - return strings.bot.commands.dead; - - // const player = await resolvePlayerFromCommand(interaction); - // const period = resolvePeriodFromCommand(player.region, interaction); - // const filters = await getFiltersFromCommand(interaction); - // const path = commandToURL(interaction, { - // ...player, - // ...getCustomPeriodParams(interaction), - // ...filters, - // }); - - // return Promise.all([ - // render(player, period, filters, interaction.locale), - // buttonRefresh(interaction, path), - // getBlitzStarsLinkButton(player.region, player.id), - // ]); - }, - - autocomplete: (interaction) => { - autocompleteUsername(interaction); - autocompleteTanks(interaction); - }, - - async button(interaction) { - const player = await resolvePlayerFromButton(interaction); - const period = resolvePeriodFromButton(player.region, interaction); - const filters = getFiltersFromButton(interaction); - - return await render(player, period, filters, interaction.locale); - }, - }); - }, -); diff --git a/packages/bot/src/commands/inactive.tsx b/packages/bot/src/commands/inactive.tsx deleted file mode 100644 index 128384a9a..000000000 --- a/packages/bot/src/commands/inactive.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import { getAccountInfo, getClanInfo } from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { Locale } from "discord.js"; -import { CommandWrapper } from "../components/CommandWrapper"; -import { GenericStats } from "../components/GenericStats"; -import { NoData } from "../components/NoData"; -import { TitleBar } from "../components/TitleBar"; -import { addClanChoices } from "../core/discord/addClanChoices"; -import { autocompleteClan } from "../core/discord/autocompleteClan"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { localizationObject } from "../core/discord/localizationObject"; -import { resolveClanFromCommand } from "../core/discord/resolveClanFromCommand"; -import { translator } from "../core/localization/translator"; -import type { CommandRegistry } from "../events/interactionCreate"; - -const DEFAULT_THRESHOLD = 7; - -export const inactiveCommand = new Promise((resolve) => { - resolve({ - command: createLocalizedCommand("inactive") - .addStringOption(addClanChoices) - .addNumberOption((option) => { - const { strings } = translator(Locale.EnglishUS); - - return option - .setName("threshold") - .setNameLocalizations( - localizationObject( - (strings) => strings.bot.commands.inactive.options.threshold.name, - undefined, - true - ) - ) - .setDescription( - strings.bot.commands.inactive.options.threshold.description - ) - .setDescriptionLocalizations( - localizationObject( - (strings) => - strings.bot.commands.inactive.options.threshold.description - ) - ) - .setMinValue(0); - }), - - async handler(interaction) { - const { strings } = translator(interaction.locale); - const { region, id } = await resolveClanFromCommand(interaction); - const threshold = - interaction.options.getNumber("threshold")! ?? DEFAULT_THRESHOLD; - const time = new Date().getTime() / 1000; - const clanInfo = await getClanInfo(region, id); - const accountInfo = await getAccountInfo(region, clanInfo.members_ids); - const inactive = accountInfo - .map( - (account) => - [ - account.nickname, - (time - account.last_battle_time) / 60 / 60 / 24, - ] as [string, number] - ) - .filter(([, inactiveDays]) => inactiveDays >= threshold) - .sort((a, b) => b[1] - a[1]) - .map( - ([name, days]) => - [ - name, - literals(strings.bot.commands.inactive.body.listing, { - days: days.toFixed(0), - }), - ] as [string, string] - ); - const hasInactiveMembers = inactive.length > 0; - - return ( - - - - {!hasInactiveMembers && ( - - )} - {hasInactiveMembers && } - - ); - }, - - autocomplete: autocompleteClan, - }); -}); diff --git a/packages/bot/src/commands/link.tsx b/packages/bot/src/commands/link.tsx deleted file mode 100644 index 3961a6687..000000000 --- a/packages/bot/src/commands/link.tsx +++ /dev/null @@ -1,193 +0,0 @@ -import { getAccountInfo, getClanAccountInfo } from '@blitzkit/core'; -import { teal } from '@radix-ui/colors'; -import { Glow } from '../components/AllStatsOverview/components/HeroStat/components/Glow'; -import { CommandWrapper } from '../components/CommandWrapper'; -import { linkBlitzAndDiscord } from '../core/db/linkBlitzAndDiscord'; -import { addUsernameChoices } from '../core/discord/addUsernameChoices'; -import { autocompleteUsername } from '../core/discord/autocompleteUsername'; -import { createLocalizedCommand } from '../core/discord/createLocalizedCommand'; -import { embedInfo } from '../core/discord/embedInfo'; -import { resolvePlayerFromCommand } from '../core/discord/resolvePlayerFromCommand'; -import { translator } from '../core/localization/translator'; -import type { CommandRegistry } from '../events/interactionCreate'; -import { theme } from '../stitches.config'; - -export const verifyCommand = new Promise((resolve) => { - resolve({ - command: createLocalizedCommand('link').addStringOption((option) => - addUsernameChoices(option).setRequired(true), - ), - - async handler(interaction) { - const { strings } = translator(interaction.locale); - const { id, region } = await resolvePlayerFromCommand(interaction); - const discordId = BigInt(interaction.user.id); - const accountInfo = await getAccountInfo(region, id); - const clanAccountInfo = await getClanAccountInfo(region, id, ['clan']); - - await linkBlitzAndDiscord(discordId, id); - - return [ - -
- - -
-
- avatar - -
- - {interaction.user.displayName} - - - @{interaction.user.username} - -
-
- -
- accounts linked - - {strings.bot.commands.link.body.accounts_linked} - -
- -
- {clanAccountInfo?.clan ? ( - clan emblem - ) : null} -
- - {accountInfo.nickname} - - - {clanAccountInfo?.clan - ? `[${clanAccountInfo?.clan?.tag}] • ` - : ''} - {strings.common.regions.normal[region]} - -
-
-
- - -
-
, - - embedInfo( - strings.bot.commands.link.embed.title, - strings.bot.commands.link.embed.description, - ), - ]; - }, - - autocomplete: autocompleteUsername, - }); -}); diff --git a/packages/bot/src/commands/ownedTanks.tsx b/packages/bot/src/commands/ownedTanks.tsx deleted file mode 100644 index ba4b6e549..000000000 --- a/packages/bot/src/commands/ownedTanks.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { - TANK_CLASSES, - TIER_ROMAN_NUMERALS, - TankDefinition, - flags, - getAccountInfo, - getTankStats, -} from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import markdownEscape from "markdown-escape"; -import { - gameDefinitions, - tankDefinitions, -} from "../core/blitzkit/nonBlockingPromises"; -import { addTierChoices } from "../core/discord/addTierChoices"; -import { addUsernameChoices } from "../core/discord/addUsernameChoices"; -import { autocompleteUsername } from "../core/discord/autocompleteUsername"; -import { chunkLines } from "../core/discord/chunkLines"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { resolvePlayerFromCommand } from "../core/discord/resolvePlayerFromCommand"; -import { translator } from "../core/localization/translator"; -import { type CommandRegistry } from "../events/interactionCreate"; - -export const ownedTanksCommand = new Promise((resolve) => { - resolve({ - command: createLocalizedCommand("owned-tanks") - .addStringOption(addTierChoices) - .addStringOption(addUsernameChoices), - - async handler(interaction) { - const { strings, unwrap } = translator(interaction.locale); - const tier = Number(interaction.options.getString("tier")); - const { id, region } = await resolvePlayerFromCommand(interaction); - const accountInfo = await getAccountInfo(region, id); - const tankStats = await getTankStats(region, id); - - if (tankStats === null) { - return strings.bot.common.errors.no_tank_stats; - } - - const filteredTanks = ( - await Promise.all( - tankStats.map(async (tankData) => ({ - tankDefinitions: (await tankDefinitions).tanks[tankData.tank_id]!, - id: tankData.tank_id, - })), - ) - ).filter((tank) => tank.tankDefinitions?.tier === tier); - const groupedTanks: Record = {}; - const nations: string[] = []; - - filteredTanks.forEach((tank) => { - if (groupedTanks[tank.tankDefinitions.nation] === undefined) { - groupedTanks[tank.tankDefinitions.nation] = [tank.tankDefinitions]; - nations.push(tank.tankDefinitions.nation); - } else { - groupedTanks[tank.tankDefinitions.nation].push(tank.tankDefinitions); - } - }); - - const awaitedTankDefinitions = await tankDefinitions; - const awaitedGameDefinitions = await gameDefinitions; - const title = literals(strings.bot.commands.owned_tanks.body.title, { - name: markdownEscape(accountInfo.nickname), - tier: TIER_ROMAN_NUMERALS[tier], - }); - const tankList = tankStats - .map(({ tank_id }) => awaitedTankDefinitions.tanks[tank_id]) - .filter((tank) => tank.tier === tier) - .sort( - (a, b) => - TANK_CLASSES.indexOf(a.class) - TANK_CLASSES.indexOf(b.class), - ) - .sort( - (a, b) => - awaitedGameDefinitions.nations.indexOf(a.nation) - - awaitedGameDefinitions.nations.indexOf(b.nation), - ) - .map( - (tank) => - `[${flags[tank.nation]} ${markdownEscape(unwrap(tank.name!))}]()`, - ); - const lines = [title, ...tankList]; - - return chunkLines(lines); - }, - - autocomplete: autocompleteUsername, - }); -}); diff --git a/packages/bot/src/commands/permissions.ts b/packages/bot/src/commands/permissions.ts deleted file mode 100644 index 1885c699e..000000000 --- a/packages/bot/src/commands/permissions.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { literals } from "@blitzkit/i18n"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { translator } from "../core/localization/translator"; -import { CommandRegistry } from "../events/interactionCreate"; - -export const permissionsCommand = new Promise((resolve) => { - resolve({ - command: createLocalizedCommand("permissions"), - - async handler(interaction) { - const { strings } = translator(interaction.locale); - // friday the 13th permissions easter egg - const isFridayThe13th = - new Date().getDay() === 5 && new Date().getDate() === 13; - const permissions = [ - [ - interaction.appPermissions?.has("ViewChannel"), - strings.bot.commands.permissions.body.view_channels, - ], - [ - interaction.appPermissions?.has("ReadMessageHistory"), - strings.bot.commands.permissions.body.read_message_history, - ], - [ - interaction.appPermissions?.has("AttachFiles"), - strings.bot.commands.permissions.body.attach_files, - ], - [ - interaction.appPermissions?.has("UseExternalEmojis"), - strings.bot.commands.permissions.body.external_emojis, - ], - ]; - const body = permissions - .map(([hasPermission, description]) => { - if (hasPermission) { - return `${isFridayThe13th ? "😇" : "✅"} ${description}`; - } else { - return `${isFridayThe13th ? "💀" : "❌"} ${description}`; - } - }) - .join("\n"); - - return literals(strings.bot.commands.permissions.body.title, { body }); - }, - }); -}); diff --git a/packages/bot/src/commands/ping.ts b/packages/bot/src/commands/ping.ts deleted file mode 100644 index e73a09fef..000000000 --- a/packages/bot/src/commands/ping.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { literals } from "@blitzkit/i18n"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { translator } from "../core/localization/translator"; -import type { CommandRegistry } from "../events/interactionCreate"; - -export const pingCommand = new Promise((resolve) => { - resolve({ - handlesInteraction: true, - - command: createLocalizedCommand("ping", [ - { subcommand: "blitzkit" }, - { subcommand: "wotb" }, - ]), - - async handler(interaction) { - const { strings } = translator(interaction.locale); - const subcommand = interaction.options.getSubcommand(); - const executionStart = Date.now(); - - if (subcommand === "blitzkit") { - await interaction.editReply(strings.bot.commands.ping.body.pong); - } else { - await fetch("https://api.wotblitz.com/"); - } - - const executionTime = Date.now() - executionStart; - interaction.editReply( - literals(strings.bot.commands.ping.body.pong_with_time, { - time: executionTime.toFixed(0), - }) - ); - }, - }); -}); diff --git a/packages/bot/src/commands/playerInfo.ts b/packages/bot/src/commands/playerInfo.ts deleted file mode 100644 index 2b4c61268..000000000 --- a/packages/bot/src/commands/playerInfo.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { getAccountInfo } from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import markdownEscape from "markdown-escape"; -import { addUsernameChoices } from "../core/discord/addUsernameChoices"; -import { autocompleteUsername } from "../core/discord/autocompleteUsername"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { embedInfo } from "../core/discord/embedInfo"; -import { markdownTable } from "../core/discord/markdownTable"; -import { resolvePlayerFromCommand } from "../core/discord/resolvePlayerFromCommand"; -import { translator } from "../core/localization/translator"; -import { CommandRegistry } from "../events/interactionCreate"; - -export const playerInfoCommand = new Promise((resolve) => { - resolve({ - command: - createLocalizedCommand("player-info").addStringOption(addUsernameChoices), - - async handler(interaction) { - const { strings } = translator(interaction.locale); - const account = await resolvePlayerFromCommand(interaction); - const { id, region: region } = account; - const accountInfo = await getAccountInfo(region, id); - - return embedInfo( - literals(strings.bot.commands.player_info.body.title, { - name: markdownEscape(accountInfo.nickname), - }), - - markdownTable([ - [ - strings.bot.commands.player_info.body.nickname, - `${accountInfo.nickname}`, - ], - [ - strings.bot.commands.player_info.body.battles, - `${accountInfo.statistics.all.battles}`, - ], - [ - strings.bot.commands.player_info.body.winrate, - `${( - 100 * - (accountInfo.statistics.all.wins / - accountInfo.statistics.all.battles) - ).toFixed(2)}%`, - ], - [], - [ - strings.bot.commands.player_info.body.account_id, - `${accountInfo.account_id}`, - ], - [ - strings.bot.commands.player_info.body.created, - new Date(accountInfo.created_at * 1000).toLocaleDateString( - interaction.locale - ), - ], - [ - strings.bot.commands.player_info.body.last_battle, - new Date(accountInfo.last_battle_time * 1000).toLocaleDateString( - interaction.locale - ), - ], - ]) - ); - }, - - autocomplete: autocompleteUsername, - }); -}); diff --git a/packages/bot/src/commands/rating.tsx b/packages/bot/src/commands/rating.tsx deleted file mode 100644 index 16b1c7feb..000000000 --- a/packages/bot/src/commands/rating.tsx +++ /dev/null @@ -1,535 +0,0 @@ -import { - deltaBkrlBlitzStats, - emblemURL, - getAccountInfo, - getArchivedRatingLeaderboard, - getClanAccountInfo, - getLeagueFromScore, - getRatingInfo, - getRatingNeighbors, - imgur, -} from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { SlashCommandSubcommandBuilder } from "discord.js"; -import markdownEscape from "markdown-escape"; -import { Glow } from "../components/AllStatsOverview/components/HeroStat/components/Glow"; -import { CommandWrapper } from "../components/CommandWrapper"; -import { DeltaCaret } from "../components/DeltaCaret"; -import { TitleBar } from "../components/TitleBar"; -import { normalizeLeagueIcon } from "../core/blitz/normalizeLeagueIcon"; -import { iconPng } from "../core/blitzkit/iconPng"; -import { addUsernameChoices } from "../core/discord/addUsernameChoices"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { resolvePlayerFromCommand } from "../core/discord/resolvePlayerFromCommand"; -import { translator } from "../core/localization/translator"; -import type { CommandRegistry } from "../events/interactionCreate"; -import { theme } from "../stitches.config"; - -const LEAGUE_ACCENT = [ - "_purple", - "_sand", - "_amber", - "_mauve", - "_brown", -] as const; - -export const ratingCommand = new Promise((resolve) => { - resolve({ - command: createLocalizedCommand("rating", [ - { - subcommand: "today", - modify(option: SlashCommandSubcommandBuilder) { - option.addStringOption(addUsernameChoices); - }, - }, - { - subcommand: "season", - modify(option: SlashCommandSubcommandBuilder) { - option.addStringOption(addUsernameChoices); - }, - }, - ]), - - async handler(interaction) { - const subcommand = interaction.options.getSubcommand(true) as - | "season" - | "today"; - const { strings } = translator(interaction.locale); - const { id, region } = await resolvePlayerFromCommand(interaction); - const clan = (await getClanAccountInfo(region, id, ["clan"]))?.clan; - const clanImage = clan ? emblemURL(clan.emblem_set_id) : undefined; - - const ratingInfo = await getRatingInfo(region); - - if (ratingInfo.detail) { - throw new Error(`Rating info not available for ${region}`); - } - - const leaderboard = await getArchivedRatingLeaderboard( - region, - ratingInfo.current_season - (subcommand === "today" ? 0 : 1) - ); - - if (leaderboard.version!.$case === "v1") { - throw new Error( - "Encountered bkrl minimal format in latest season. Wait till next cron?" - ); - } - - const accountInfo = await getAccountInfo(region, id, [ - "statistics.rating", - ]); - const statsIndex = leaderboard.version!.value.entries.findIndex( - (entry) => entry.id === id - ); - const statsA = leaderboard - ? leaderboard.version!.value.entries[statsIndex!] - : null; - const positionA = leaderboard ? statsIndex! + 1 : undefined; - const statsB1 = accountInfo.statistics.rating!; - const statsB2Array = (await getRatingNeighbors(region, id, 0)).neighbors; - - if (statsB2Array === undefined) { - return literals(strings.bot.commands.rating.errors.no_participation, { - name: markdownEscape(accountInfo.nickname), - }); - } - - const [statsB2] = statsB2Array; - const league = getLeagueFromScore(statsB2.score); - - if (!statsA) { - return strings.bot.commands.rating.errors.unrecorded_stats; - } - if (!statsB1) { - return literals(strings.bot.commands.rating.errors.no_participation, { - name: markdownEscape(accountInfo.nickname), - }); - } - - const reward = ratingInfo.rewards.find( - (reward) => - statsB2.number >= reward.from_position && - statsB2.number <= reward.to_position - ); - const positionDelta = leaderboard - ? statsB2.number - positionA! - : undefined; - const delta = deltaBkrlBlitzStats( - statsA, - accountInfo.statistics.rating!, - statsB2 - ); - const leagueTheme = LEAGUE_ACCENT[league.index]; - let rewardImage = reward - ? reward.type === "vehicle" - ? reward.vehicle.preview_image_url - : reward.stuff.image_url - : undefined; - - if (rewardImage) { - const { ok } = await fetch(rewardImage); - - if (!ok) { - rewardImage = imgur("kP88vqr"); - } - } - - return ( - - - -
-
- {reward === undefined && ( - - )} - -
- {`League - -
-
- - {statsB2.score.toLocaleString(interaction.locale)} - - {delta.score !== 0 && ( - <> - - 0 - ? theme.colors.textLowContrast_green - : theme.colors.textLowContrast_red, - fontSize: 16, - }} - > - {Math.abs(delta.score).toLocaleString( - interaction.locale - )} - - - )} -
- -
- - #{statsB2.number.toLocaleString(interaction.locale)} - - {positionDelta !== undefined && positionDelta !== 0 && ( - <> - - 0 - ? theme.colors.textLowContrast_red - : theme.colors.textLowContrast_green, - fontSize: 16, - }} - > - {Math.abs(positionDelta).toLocaleString( - interaction.locale - )} - - - )} -
-
-
- - {reward && ( -
-
-
- - {reward.type === "vehicle" - ? reward.vehicle.user_string - : reward.stuff.title} - -
- -
- - Reward - {reward.type === "stuff" ? ` x${reward.count}` : ""} - -
-
- - -
- )} - - {reward === undefined && ( - - )} -
- - {/* separated because fragment middle man invalidates relative parent: https://github.com/vercel/satori/issues/605 */} - {reward && ( - - )} - {reward && ( - - )} -
- - {delta.battles > 0 && ( -
-
- - {delta.battles.toLocaleString(interaction.locale)} - - - Battles - -
-
- - {(100 * (delta.wins / delta.battles)).toFixed(0)}% - - - Winrate - -
-
- - {Math.round(delta.damage / delta.battles).toLocaleString( - interaction.locale - )} - - - Damage - -
-
- - {(100 * (delta.survived / delta.battles)).toFixed(0)}% - - - Survival - -
-
- - {(delta.kills / delta.battles).toFixed(2)} - - - Kills - -
-
- )} -
- ); - }, - }); -}); diff --git a/packages/bot/src/commands/ratingLeaderboard.tsx b/packages/bot/src/commands/ratingLeaderboard.tsx deleted file mode 100644 index 458e70d52..000000000 --- a/packages/bot/src/commands/ratingLeaderboard.tsx +++ /dev/null @@ -1,468 +0,0 @@ -import { - FIRST_MINIMAL_ARCHIVED_RATING_SEASON, - getAccountInfo, - getArchivedLatestSeasonNumber, - getArchivedRatingLeaderboard, - getClanAccountInfo, - getRatingInfo, - getRatingLeague, - getRatingNeighbors, - imgur, - isOnGoingRatingSeason, - LEAGUES, - type RatingInfo, - RatingLeaderboard, - type Region, -} from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { - type APIApplicationCommandOptionChoice, - Locale, - SlashCommandSubcommandBuilder, -} from "discord.js"; -import { range } from "lodash-es"; -import markdownEscape from "markdown-escape"; -import { CommandWrapper } from "../components/CommandWrapper"; -import * as Leaderboard from "../components/Leaderboard"; -import { TitleBar } from "../components/TitleBar"; -import { UserError } from "../core/blitzkit/userError"; -import { addRegionChoices } from "../core/discord/addRegionChoices"; -import { addUsernameChoices } from "../core/discord/addUsernameChoices"; -import { autocompleteUsername } from "../core/discord/autocompleteUsername"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { localizationObject } from "../core/discord/localizationObject"; -import { resolvePlayerFromCommand } from "../core/discord/resolvePlayerFromCommand"; -import { translator } from "../core/localization/translator"; -import type { CommandRegistry } from "../events/interactionCreate"; - -interface SimplifiedPlayer { - id: number; - score: number; - position: number; - clan: string | undefined; - nickname: string; -} - -const DEFAULT_LIMIT = 10; - -export const ratingLeaderboardCommand = new Promise( - async (resolve) => { - const latestArchivedSeasonNumber = await getArchivedLatestSeasonNumber(); - const onGoingSeason = await isOnGoingRatingSeason(); - const seasonNumbers = range( - FIRST_MINIMAL_ARCHIVED_RATING_SEASON, - latestArchivedSeasonNumber + (onGoingSeason ? 1 : 0) + 1 - ).slice(-25); - - function addOptions(option: SlashCommandSubcommandBuilder) { - const { strings } = translator(Locale.EnglishUS); - - return option - .addIntegerOption((option) => - option - .setName(strings.bot.commands.rating_leaderboard.options.limit.name) - .setNameLocalizations( - localizationObject( - (strings) => - strings.bot.commands.rating_leaderboard.options.limit.name, - undefined, - true - ) - ) - .setDescription( - literals( - strings.bot.commands.rating_leaderboard.options.limit - .description, - { count: DEFAULT_LIMIT } - ) - ) - .setDescriptionLocalizations( - localizationObject( - (strings) => - strings.bot.commands.rating_leaderboard.options.limit - .description, - { count: DEFAULT_LIMIT } - ) - ) - .setRequired(false) - .setMinValue(5) - .setMaxValue(30) - ) - .addStringOption((option) => - option - .setName( - strings.bot.commands.rating_leaderboard.options.season.name - ) - .setNameLocalizations( - localizationObject( - (strings) => - strings.bot.commands.rating_leaderboard.options.season.name, - undefined, - true - ) - ) - .setDescription( - strings.bot.commands.rating_leaderboard.options.season.description - ) - .setDescriptionLocalizations( - localizationObject( - (strings) => - strings.bot.commands.rating_leaderboard.options.season - .description - ) - ) - .setRequired(false) - .addChoices( - ...seasonNumbers - .map((number, index) => { - const current = - index === seasonNumbers.length - 1 && onGoingSeason; - - return { - name: current - ? literals( - strings.bot.commands.rating_leaderboard.options.season - .choices.current, - { season: number } - ) - : `${number}`, - value: `${number}`, - name_localizations: current - ? localizationObject( - (strings) => - strings.bot.commands.rating_leaderboard.options - .season.choices.current, - { season: number } - ) - : undefined, - } satisfies APIApplicationCommandOptionChoice; - }) - .reverse() - ) - ); - } - - resolve({ - command: createLocalizedCommand("rating-leaderboard", [ - { - subcommand: "neighbors", - modify(option: SlashCommandSubcommandBuilder) { - option.addStringOption(addUsernameChoices); - addOptions(option); - }, - }, - { - subcommand: "league", - modify(option: SlashCommandSubcommandBuilder) { - option - .addStringOption((option) => { - const { strings } = translator(Locale.EnglishUS); - - return option - .setName( - strings.bot.commands.rating_leaderboard.options.league.name - ) - .setNameLocalizations( - localizationObject( - (strings) => - strings.bot.commands.rating_leaderboard.options.league - .name, - undefined, - true - ) - ) - .setDescription( - strings.bot.commands.rating_leaderboard.options.league - .description - ) - .setDescriptionLocalizations( - localizationObject( - (strings) => - strings.bot.commands.rating_leaderboard.options.league - .description - ) - ) - .addChoices( - ...LEAGUES.map( - ({ name }, value) => - ({ - name: ( - strings.common.leagues as Record - )[name], - value: `${value}`, - name_localizations: localizationObject( - (strings) => - ( - strings.common.leagues as Record - )[name] - ), - } satisfies APIApplicationCommandOptionChoice) - ) - ) - .setRequired(true); - }) - .addStringOption(addRegionChoices); - addOptions(option); - }, - }, - ]), - - // BIG TODO: title seems to be in english - - async handler(interaction) { - const { strings } = translator(interaction.locale); - const subcommand = interaction.options.getSubcommand(true) as - | "league" - | "neighbors"; - const limit = interaction.options.getInteger("limit") ?? DEFAULT_LIMIT; - const seasonOption = interaction.options.getString("season"); - const season = seasonOption - ? parseInt(seasonOption) - : onGoingSeason - ? latestArchivedSeasonNumber + 1 - : latestArchivedSeasonNumber; - const isCurrentSeason = season === latestArchivedSeasonNumber + 1; - let titleName: string; - let titleImage: string | undefined; - let titleDescription: string; - let playersAfter: number; - let players: SimplifiedPlayer[] | undefined; - let archivedLeaderboard: RatingLeaderboard | null; - let playerId: number | undefined; - let regionRatingInfo: RatingInfo | null; - - if (subcommand === "league") { - const leagueIndex = parseInt( - interaction.options.getString("league")! - ); - const region = interaction.options.getString("region") as Region; - regionRatingInfo = await (isCurrentSeason - ? getRatingInfo(region) - : null); - - if ( - regionRatingInfo !== null && - regionRatingInfo.detail !== undefined - ) { - return strings.bot.commands.rating_leaderboard.body - .no_ongoing_season; - } - - const result = isCurrentSeason - ? await getRatingLeague(region, leagueIndex).then(async (result) => - result.slice(0, limit).map( - (player) => - ({ - id: player.spa_id, - score: player.score, - position: player.number, - clan: player.clan_tag, - nickname: player.nickname, - } satisfies SimplifiedPlayer) - ) - ) - : await getArchivedRatingLeaderboard(region, season!).then( - async (players) => { - const firstPlayerIndex = - leagueIndex === 0 - ? 0 - : players.version!.value.entries.findIndex( - (player) => - player.score < LEAGUES[leagueIndex - 1].minScore - ); - const lastPlayerIndex = firstPlayerIndex + limit - 1; - const trimmed = players.version!.value.entries.slice( - firstPlayerIndex, - lastPlayerIndex + 1 - ); - const clanData = await getClanAccountInfo( - region, - trimmed.map(({ id }) => id), - ["clan"] - ); - - return trimmed.map( - (player, index) => - ({ - id: player.id, - score: player.score, - position: firstPlayerIndex + index + 1, - clan: clanData[player.id]?.clan?.tag, - nickname: - clanData[index]?.account_name ?? - literals( - strings.bot.commands.rating_leaderboard.body - .deleted_player, - { id: player.id } - ), - } satisfies SimplifiedPlayer) - ); - } - ); - const leagueInfo = LEAGUES[leagueIndex]; - archivedLeaderboard = await getArchivedRatingLeaderboard( - region, - season - ); - - players = result; - playersAfter = - (regionRatingInfo === null - ? archivedLeaderboard.version!.value.entries.length - : regionRatingInfo.count) - result[result.length - 1].position; - titleName = `${ - leagueInfo - ? (strings.common.leagues as Record)[ - LEAGUES[leagueInfo.index].name - ] - : "" - } - ${strings.common.regions.short[region]}`; - titleImage = imgur(leagueInfo.icon); - titleDescription = literals( - strings.bot.commands.rating_leaderboard.body.top_players, - { count: limit } - ); - } else { - const { region, id } = await resolvePlayerFromCommand(interaction); - regionRatingInfo = isCurrentSeason - ? await getRatingInfo(region) - : null; - - if ( - regionRatingInfo !== null && - regionRatingInfo.detail !== undefined - ) { - return strings.bot.commands.rating_leaderboard.body - .no_ongoing_season; - } - - const accountInfo = await getAccountInfo(region, id); - const clan = (await getClanAccountInfo(region, id, ["clan"]))?.clan; - const neighbors = isCurrentSeason - ? await getRatingNeighbors(region, id, Math.round(limit / 2)).then( - (players) => - players.neighbors.map( - (player) => - ({ - id: player.spa_id, - position: player.number, - score: player.score, - clan: player.clan_tag, - nickname: player.nickname, - } satisfies SimplifiedPlayer) - ) - ) - : await getArchivedRatingLeaderboard(region, season!).then( - async (players) => { - const playerIndex = players.version!.value.entries.findIndex( - (player) => player.id === id - ); - - if (playerIndex === -1) { - throw new UserError( - literals( - strings.bot.commands.rating_leaderboard.body - .no_participation, - { player: markdownEscape(accountInfo.nickname), season } - ) - ); - } - - const halfRange = Math.round(limit / 2); - const trimmed = players.version!.value.entries.slice( - playerIndex - halfRange, - playerIndex + halfRange + 1 - ); - const clanData = await getClanAccountInfo( - region, - trimmed.map((player) => player.id), - ["clan"] - ); - - return trimmed.map((player, index) => ({ - id: player.id, - score: player.score, - position: playerIndex + index + 1, - clan: clanData[index]?.clan?.tag, - nickname: clanData[index] - ? clanData[index]!.account_name - : `Deleted ${player.id}`, - })); - } - ); - archivedLeaderboard = await getArchivedRatingLeaderboard( - region, - season - ); - - players = neighbors; - titleImage = clan - ? `https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clan.emblem_set_id}.png` - : undefined; - titleName = neighbors ? accountInfo.nickname : ""; - titleDescription = - strings.bot.commands.rating_leaderboard.body.neighbors; - playersAfter = - (regionRatingInfo === null - ? archivedLeaderboard.version!.value.entries.length - : regionRatingInfo.count) - - neighbors[neighbors.length - 1].position; - playerId = id; - } - - const items = players.map((player) => { - const midnightIndex = - archivedLeaderboard?.version!.value.entries.findIndex( - (item) => item.id === player.id - ); - const midnightScore = - midnightIndex === undefined - ? player.score - : archivedLeaderboard!.version!.value.entries[midnightIndex] - .score; - const midnightPosition = - midnightIndex === undefined ? player.position : midnightIndex + 1; - - return ( - - ); - }); - - return ( - - - - {items && ( - - {items} - {playersAfter > 0 && ( - - )} - - )} - - ); - }, - - autocomplete: autocompleteUsername, - }); - } -); diff --git a/packages/bot/src/commands/replay.tsx b/packages/bot/src/commands/replay.tsx deleted file mode 100644 index ed6da65fc..000000000 --- a/packages/bot/src/commands/replay.tsx +++ /dev/null @@ -1,592 +0,0 @@ -import { - calculateWN8, - emblemURL, - getClanAccountInfo, - idToRegion, - tankIcon, - TIER_ROMAN_NUMERALS, -} from "@blitzkit/core"; -import { Locale } from "discord.js"; -import { clamp } from "lodash-es"; -import { CommandWrapper } from "../components/CommandWrapper"; -import { TitleBar } from "../components/TitleBar"; -import { iconPng } from "../core/blitzkit/iconPng"; -import { - averageDefinitions, - mapDefinitions, - tankDefinitions, -} from "../core/blitzkit/nonBlockingPromises"; -import { RenderConfiguration } from "../core/blitzkit/renderConfiguration"; -import { buttonLink } from "../core/discord/buttonLink"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { localizationObject } from "../core/discord/localizationObject"; -import { translator } from "../core/localization/translator"; -import { type CommandRegistry } from "../events/interactionCreate"; -import { theme } from "../stitches.config"; - -type WotInspectorReplaySubmission = - | { status: "error"; data: unknown; error: { message: string } } - | { - status: "ok"; - data: { - download_url: string; - view_url: string; - summary: { - battle_start_time: string; - map_name: string; - battle_start_timestamp: number; - battle_duration: number; - title: string; - arena_unique_id: string; - allies: number[]; - enemies: number[]; - protagonist: number; - player_name: string; - vehicle: string; - exp_base: number; - exp_total: number; - credits_base: number; - credits_total: number; - room_type: number; - battle_type: number; - mastery_badge: number; - battle_result: number; - vehicle_tier: number; - vehicle_type: number; - protagonist_team: number; - winner_team: number; - details: { - achievements: unknown[]; - base_capture_points: number; - base_defend_points: number; - chassis_id: unknown | null; - clan_tag: string; - clanid: number; - credits: number; - damage_assisted: number; - damage_assisted_track: number; - damage_blocked: unknown | null; - damage_made: number; - damage_received: number; - dbid: number; - death_reason: number; - distance_travelled: number; - enemies_damaged: number; - enemies_destroyed: number; - enemies_spotted: number; - entity_id: number; - exp: number; - exp_for_assist: number; - exp_for_damage: number; - exp_team_bonus: number; - gun_id: unknown | null; - hero_bonus_credits: number; - hero_bonus_exp: number; - hitpoints_left: number; - hits_bounced: number; - hits_pen: number; - hits_received: number; - hits_splash: number; - killed_by: number; - name: string; - shots_hit: number; - shots_made: number; - shots_pen: number; - shots_splash: number; - squad_index: number; - team: number; - time_alive: number; - turret_id: unknown | null; - vehicle_descr: number; - wp_points_earned: number; - wp_points_stolen: number; - }; - }; - }; - error: {}; - }; - -interface WotInspectorReplay { - id: string; - map_id: number; - battle_duration: number; - title: unknown | null; - player_name: string; - protagonist: number; - vehicle_descr: number; - mastery_badge: number; - exp_base: number; - enemies_spotted: number; - enemies_destroyed: number; - damage_assisted: number; - damage_made: number; - details_url: string; - download_url: string; - game_version: { - name: string; - package: string; - }; - arena_unique_id: string; - download_count: number; - data_version: number; - private: boolean; - private_clan: boolean; - battle_start_time: Date; - upload_time: Date; - allies: number[]; - enemies: number[]; - protagonist_clan: number; - protagonist_team: number; - battle_result: number; - credits_base: number; - tags: number[]; - battle_type: number; - room_type: number; - last_accessed_time: Date; - winner_team: number; - finish_reason: number; - players_data: WotInspectorReplayPlayerData[]; - exp_total: number; - credits_total: number; - repair_cost: number; - exp_free: number; - exp_free_base: number; - exp_penalty: number; - credits_penalty: number; - credits_contribution_in: number; - credits_contribution_out: number; - camouflage_id: number; -} - -export interface WotInspectorReplayPlayerData { - team: number; - name: string; - entity_id: number; - dbid: number; - clanid: number | null; - clan_tag: null | string; - hitpoints_left: number; - credits: number; - exp: number; - shots_made: number; - shots_hit: number; - shots_splash: number; - shots_pen: number; - damage_made: number; - damage_received: number; - damage_assisted: number; - damage_assisted_track: number; - hits_received: number; - hits_bounced: number; - hits_splash: number; - hits_pen: number; - enemies_spotted: number; - enemies_damaged: number; - enemies_destroyed: number; - time_alive: number; - distance_travelled: number; - killed_by: number; - base_capture_points: number; - base_defend_points: number; - exp_for_damage: number; - exp_for_assist: number; - exp_team_bonus: number; - wp_points_earned: number; - wp_points_stolen: number; - hero_bonus_credits: number; - hero_bonus_exp: number; - death_reason: number; - achievements: any[]; - vehicle_descr: number; - turret_id: number; - gun_id: number; - chassis_id: number; - squad_index: number; - damage_blocked: number; -} - -function stat(value: string | number, flex = 2) { - return ( -
- {value} -
- ); -} - -async function playerListing( - locale: Locale, - player: WotInspectorReplayPlayerData, - protagonistTeam: boolean, - winningTeam: number, - recordingPlayer = false, -) { - const { unwrap } = translator(locale); - const awaitedTankAverages = await averageDefinitions; - const awaitedTankDefinitions = await tankDefinitions; - const tank = awaitedTankDefinitions.tanks[player.vehicle_descr]; - const blockAccent = recordingPlayer ? "_amber" : ""; - const healthAccent = protagonistTeam - ? recordingPlayer - ? "_amber" - : "_green" - : "_red"; - - // our turret ids are always off by 1 or 2 lol so imma just grab the closest - // TODO: 💀 - const turret = tank.turrets.sort( - (a, b) => - Math.abs(a.id - player.turret_id) - Math.abs(b.id - player.turret_id), - )[0]; - const totalHealth = turret.health + tank.health; - const healthLeft = clamp(player.hitpoints_left / totalHealth, 0, 1); - const tankAveragesEntry = awaitedTankAverages.averages[player.vehicle_descr]; - const wn8 = - tankAveragesEntry === undefined - ? -1 - : calculateWN8(tankAveragesEntry.mu, { - battles: 1, - damage_dealt: player.damage_made, - dropped_capture_points: player.base_defend_points, - frags: player.enemies_destroyed, - spotted: player.enemies_spotted, - wins: winningTeam === player.team ? 1 : 0, - }); - - return ( -
-
- {unwrap(tank.name!)} - -
-
- - {TIER_ROMAN_NUMERALS[tank.tier]} - - - {unwrap(tank.name!)} - -
- - - {player.name} - {player.clan_tag ? ` [${player.clan_tag}]` : ""} - -
-
- -
- {stat(player.damage_made.toLocaleString())} - {stat(wn8 === -1 ? "--" : Math.round(wn8).toLocaleString())} - {stat(player.exp.toLocaleString())} - {stat(player.enemies_destroyed, 1)} -
- -
-
-
-
- ); -} - -function Header() { - return ( -
-
- -
-
- - Damage - -
-
- - WN8 - -
-
- - XP - -
-
- - Kills - -
-
-
- ); -} - -export const replayCommand = new Promise((resolve) => { - const { strings } = translator(Locale.EnglishUS); - - resolve({ - command: createLocalizedCommand("replay").addAttachmentOption((option) => - option - .setName(strings.bot.commands.replay.options.file.name) - .setNameLocalizations( - localizationObject( - (strings) => strings.bot.commands.replay.options.file.name, - undefined, - true, - ), - ) - .setDescription(strings.bot.commands.replay.options.file.description) - .setDescriptionLocalizations( - localizationObject( - (strings) => strings.bot.commands.replay.options.file.description, - ), - ) - .setRequired(true), - ), - - async handler(interaction) { - const awaitedMapDefinitions = await mapDefinitions; - const file = interaction.options.getAttachment("file")!; - const submissionData = (await fetch( - `https://wotinspector.com/api/replay/upload/?url=${encodeURIComponent( - file.url, - )}`, - ).then((response) => response.json())) as WotInspectorReplaySubmission; - - if (submissionData.status === "error") { - return `# Uh oh! WoT Inspector didn't like that.\n\nThere was an error processing your replay. Get help on [the official Discord server](https://discord.gg/nDt7AjGJQH).`; - } - - const replayId = submissionData.data.download_url.split("/").at(-1)!; - const replayData = (await fetch( - `https://api.wotinspector.com/v2/blitz/replays/${replayId}/`, - ).then((response) => response.json())) as WotInspectorReplay; - const antagonistTeamId = 3 - replayData.protagonist_team; - const players = replayData.players_data.sort( - (a, b) => b.damage_made - a.damage_made, - ); - const clan = ( - await getClanAccountInfo( - idToRegion(replayData.protagonist), - replayData.protagonist, - ["clan"], - ) - )?.clan; - const clanImage = clan ? emblemURL(clan.emblem_set_id) : undefined; - - return [ - new RenderConfiguration({ width: 960 }), - - - -
-
-
- - {await Promise.all( - players - .filter( - (player) => player.team === replayData.protagonist_team, - ) - .map((player) => - playerListing( - interaction.locale, - player, - true, - replayData.winner_team, - player.dbid === replayData.protagonist, - ), - ), - )} -
- -
-
- - {await Promise.all( - players - .filter((player) => player.team === antagonistTeamId) - .map((player) => - playerListing( - interaction.locale, - player, - false, - replayData.winner_team, - ), - ), - )} -
-
-
, - buttonLink(submissionData.data.view_url, "WoT Inspector"), - buttonLink(submissionData.data.download_url, "Download"), - ]; - }, - }); -}); diff --git a/packages/bot/src/commands/research.tsx b/packages/bot/src/commands/research.tsx deleted file mode 100644 index e0a987044..000000000 --- a/packages/bot/src/commands/research.tsx +++ /dev/null @@ -1,626 +0,0 @@ -import { - asset, - emblemURL, - equipmentPriceMatrix, - getAccountInfo, - getClanAccountInfo, - getTankStats, - TANK_ICONS, - TankType, - TIER_ROMAN_NUMERALS, -} from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { escapeMarkdown, Locale } from "discord.js"; -import { CommandWrapper } from "../components/CommandWrapper"; -import { TitleBar } from "../components/TitleBar"; -import { resolveTankId } from "../core/blitz/resolveTankId"; -import { buildTechTreeLine } from "../core/blitzkit/buildTechTreeLine"; -import { iconPng } from "../core/blitzkit/iconPng"; -import { tankDefinitions } from "../core/blitzkit/nonBlockingPromises"; -import { resolveAncestry } from "../core/blitzkit/resolveAncestry"; -import { tankIconPng } from "../core/blitzkit/tankIconPng"; -import { addUsernameChoices } from "../core/discord/addUsernameChoices"; -import { autocompleteTanks } from "../core/discord/autocompleteTanks"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { localizationObject } from "../core/discord/localizationObject"; -import { resolvePlayerFromCommand } from "../core/discord/resolvePlayerFromCommand"; -import { translator } from "../core/localization/translator"; -import { type CommandRegistry } from "../events/interactionCreate"; -import { theme } from "../stitches.config"; - -export const researchCommand = new Promise((resolve) => { - const { strings } = translator(Locale.EnglishUS); - - resolve({ - command: createLocalizedCommand("research") - .addStringOption((option) => - option - .setName(strings.bot.commands.research.options.target_tank.name!) - .setNameLocalizations( - localizationObject( - (strings) => - strings.bot.commands.research.options.target_tank.name, - undefined, - true, - ), - ) - .setDescription( - strings.bot.commands.research.options.target_tank.description, - ) - .setDescriptionLocalizations( - localizationObject( - (strings) => - strings.bot.commands.research.options.target_tank.description, - ), - ) - .setAutocomplete(true) - .setRequired(true), - ) - .addStringOption((option) => - option - .setName(strings.bot.commands.research.options.starting_tank.name!) - .setNameLocalizations( - localizationObject( - (strings) => - strings.bot.commands.research.options.starting_tank.name, - undefined, - true, - ), - ) - .setDescription( - strings.bot.commands.research.options.starting_tank.description, - ) - .setDescriptionLocalizations( - localizationObject( - (strings) => - strings.bot.commands.research.options.starting_tank.description, - ), - ) - .setAutocomplete(true) - .setRequired(false), - ) - .addStringOption(addUsernameChoices), - - async handler(interaction) { - const { strings, unwrap } = translator(interaction.locale); - const awaitedTankDefinitions = await tankDefinitions; - const targetTankId = await resolveTankId( - interaction.options.getString("target-tank", true), - interaction.locale, - true, - ); - const startingTankRaw = interaction.options.getString("starting-tank"); - const targetTank = awaitedTankDefinitions.tanks[targetTankId]; - const { id, region } = await resolvePlayerFromCommand(interaction); - let startingTankId: number; - const targetTankAncestry = await resolveAncestry(targetTankId); - - if (startingTankRaw) { - startingTankId = await resolveTankId( - startingTankRaw, - interaction.locale, - true, - ); - - if (targetTankId === startingTankId) { - return literals( - strings.bot.commands.research.errors.start_end_equal, - { tank: escapeMarkdown(unwrap(targettank.name!)) }, - ); - } - - const startingTank = awaitedTankDefinitions.tanks[startingTankId]; - - if (startingTank.tier > targetTank.tier) { - return literals(strings.bot.commands.research.errors.unordered, { - tank_1: escapeMarkdown(unwrap(startingtank.name!)), - tank_2: escapeMarkdown(unwrap(targettank.name!)), - }); - } - - if (!targetTankAncestry.includes(startingTankId)) { - return literals( - strings.bot.commands.research.errors.tanks_not_on_line, - { - tank_1: escapeMarkdown(unwrap(targettank.name!)), - tank_2: escapeMarkdown(unwrap(startingtank.name!)), - }, - ); - } - } else { - if (targetTank.type !== TankType.TANK_TYPE_RESEARCHABLE) { - return literals(strings.bot.commands.research.errors.non_tech_tree, { - tank: escapeMarkdown(unwrap(targettank.name!)), - }); - } - - if (targetTank.tier === 1) { - return literals(strings.bot.commands.research.errors.tier_1, { - tank: escapeMarkdown(unwrap(targettank.name!)), - }); - } - - const tankStats = await getTankStats(region, id); - - if (tankStats === null) { - return strings.bot.common.errors.no_tank_stats; - } - - if (tankStats.some(({ tank_id }) => tank_id === targetTankId)) { - return literals( - strings.bot.commands.research.errors.already_researched, - { tank: escapeMarkdown(unwrap(targettank.name!)) }, - ); - } - - const foundAncestor = targetTankAncestry.find( - (id) => - awaitedTankDefinitions.tanks[id].tier === 1 || - tankStats.some(({ tank_id }) => tank_id === id), - ); - - if (foundAncestor === undefined) { - throw new Error("No first owned tech tree found."); - } - - startingTankId = foundAncestor; - } - - const techTreeLine = await buildTechTreeLine( - startingTankId, - targetTankId, - ); - - const line = [...techTreeLine, startingTankId].reverse(); - const { nickname } = await getAccountInfo(region, id); - const clan = (await getClanAccountInfo(region, id, ["clan"]))?.clan; - const clanImage = clan ? emblemURL(clan.emblem_set_id) : undefined; - const costs = await Promise.all( - line.map(async (id) => { - const tank = awaitedTankDefinitions.tanks[id]; - const turretXps = new Map(); - const gunXps = new Map(); - const engineXps = new Map(); - const trackXps = new Map(); - - tank.turrets.forEach((turret) => { - if ( - turret.research_cost !== undefined && - typeof turret.research_cost.research_cost_type!.value === "number" - ) { - turretXps.set( - turret.id, - turret.research_cost.research_cost_type!.value, - ); - } - - turret.guns.forEach((gun) => { - if ( - gun.research_cost !== undefined && - typeof gun.research_cost.research_cost_type!.value === "number" - ) { - gunXps.set(gun.id, gun.research_cost.research_cost_type!.value); - } - }); - }); - - tank.engines.forEach((engine) => { - if ( - engine.research_cost !== undefined && - typeof engine.research_cost.research_cost_type!.value === "number" - ) { - engineXps.set( - engine.id, - engine.research_cost.research_cost_type!.value, - ); - } - }); - - tank.tracks.forEach((track) => { - if ( - track.research_cost !== undefined && - typeof track.research_cost.research_cost_type!.value === "number" - ) { - trackXps.set( - track.id, - track.research_cost.research_cost_type!.value, - ); - } - }); - - let moduleXp = 0; - turretXps.forEach((value) => { - moduleXp += value; - }); - gunXps.forEach((value) => { - moduleXp += value; - }); - engineXps.forEach((value) => { - moduleXp += value; - }); - trackXps.forEach((value) => { - moduleXp += value; - }); - - return { - research: tank.research_cost!.research_cost_type!.value as number, - upgrades: moduleXp, - purchase: tank.price!.value, - equipment: equipmentPriceMatrix[tank.tier].reduce( - (a, b) => a + 3 * b, - 0, - ), - }; - }), - ); - - const image = ( - - - -
-
-
- -
- - {strings.bot.commands.research.body.total} - -
- -
-
- XP icon - - Experience:{" "} - {costs - .reduce((a, b) => a + (b.research ?? 0) + b.upgrades, 0) - .toLocaleString(interaction.locale)} - -
- -
- Silver icon - - Purchase:{" "} - {costs - .reduce((a, b) => a + b.purchase, 0) - .toLocaleString(interaction.locale)} - -
- -
- Silver icon - - Equipment:{" "} - {costs - .reduce((a, b) => a + b.equipment, 0) - .toLocaleString(interaction.locale)} - -
-
-
- - {await Promise.all( - line.map(async (id, index) => { - const tank = awaitedTankDefinitions.tanks[id]; - - return ( -
-
- - {TIER_ROMAN_NUMERALS[tank.tier]} - -
- -
- Tank icon - -
- {unwrap(tank.name!)} - - - {unwrap(tank.name!)} - -
-
- -
-
- XP icon - - {literals( - strings.bot.commands.research.body.research, - { - cost: costs[index].research?.toLocaleString( - interaction.locale, - ), - }, - )} - -
- - {costs[index].upgrades > 0 && ( -
- Free XP icon - - {literals( - strings.bot.commands.research.body.upgrades, - { cost: costs[index].upgrades.toLocaleString() }, - )} - -
- )} - -
- Silver icon - - {literals( - strings.bot.commands.research.body.purchase, - { - cost: costs[index].purchase.toLocaleString( - interaction.locale, - ), - }, - )} - -
- -
- Silver icon - - {literals( - strings.bot.commands.research.body.equipment, - { - cost: costs[index].equipment.toLocaleString( - interaction.locale, - ), - }, - )} - -
-
-
- ); - }), - )} -
-
- ); - - return startingTankRaw - ? image - : [strings.bot.commands.research.body.estimation, image]; - }, - - autocomplete(interaction) { - autocompleteTanks(interaction, true, ["target-tank", "starting-tank"]); - }, - }); -}); diff --git a/packages/bot/src/commands/searchClans.ts b/packages/bot/src/commands/searchClans.ts deleted file mode 100644 index 45c0c6aee..000000000 --- a/packages/bot/src/commands/searchClans.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { type ClanList, fetchBlitz, type Region } from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { Locale } from "discord.js"; -import markdownEscape from "markdown-escape"; -import { addClanChoices } from "../core/discord/addClanChoices"; -import { addRegionChoices } from "../core/discord/addRegionChoices"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { embedInfo } from "../core/discord/embedInfo"; -import { localizationObject } from "../core/discord/localizationObject"; -import { translator } from "../core/localization/translator"; -import type { CommandRegistry } from "../events/interactionCreate"; - -const DEFAULT_LIMIT = 25; - -export const searchClansCommand = new Promise((resolve) => { - resolve({ - command: createLocalizedCommand("search-clans") - .addStringOption(addRegionChoices) - .addStringOption((option) => - addClanChoices(option).setAutocomplete(false) - ) - .addIntegerOption((option) => { - const { strings } = translator(Locale.EnglishUS); - - return option - .setName(strings.bot.commands.search_clans.options.limit.name) - .setNameLocalizations( - localizationObject( - (strings) => strings.bot.commands.search_clans.options.limit.name, - undefined, - true - ) - ) - .setDescription( - literals( - strings.bot.commands.search_clans.options.limit.description, - { limit: DEFAULT_LIMIT } - ) - ) - .setDescriptionLocalizations( - localizationObject( - (strings) => - strings.bot.commands.search_clans.options.limit.description, - { limit: DEFAULT_LIMIT } - ) - ) - .setMinValue(1) - .setMaxValue(100); - }), - - async handler(interaction) { - const { strings } = translator(interaction.locale); - const region = interaction.options.getString("region") as Region; - const clan = interaction.options.getString("clan")!; - const limit = interaction.options.getInteger("limit") ?? DEFAULT_LIMIT; - const clanList = await fetchBlitz(region, "clans/list", { - search: clan, - limit, - }); - - return embedInfo( - literals(strings.bot.commands.search_clans.body.title, { - search: markdownEscape(clan), - region: strings.common.regions.normal[region], - }), - clanList.length === 0 - ? strings.bot.commands.search_clans.body.no_results - : `\`\`\`\n${clanList - .map((clan) => `${clan.name} [${clan.tag}]`) - .join("\n")}\n\`\`\`` - ); - }, - }); -}); diff --git a/packages/bot/src/commands/searchPlayers.ts b/packages/bot/src/commands/searchPlayers.ts deleted file mode 100644 index 54bfa4dc1..000000000 --- a/packages/bot/src/commands/searchPlayers.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { - type AccountList, - fetchBlitz, - type Region, - usernamePattern, -} from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { Locale } from "discord.js"; -import markdownEscape from "markdown-escape"; -import { addRegionChoices } from "../core/discord/addRegionChoices"; -import { addUsernameChoices } from "../core/discord/addUsernameChoices"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { embedInfo } from "../core/discord/embedInfo"; -import { localizationObject } from "../core/discord/localizationObject"; -import { translator } from "../core/localization/translator"; -import type { CommandRegistry } from "../events/interactionCreate"; - -const DEFAULT_LIMIT = 25; - -export const searchPlayersCommand = new Promise((resolve) => { - const { strings } = translator(Locale.EnglishUS); - - resolve({ - command: createLocalizedCommand("search-players") - .addStringOption(addRegionChoices) - .addStringOption((option) => - addUsernameChoices(option).setAutocomplete(false).setRequired(true) - ) - .addIntegerOption((option) => - option - .setName(strings.bot.commands.search_players.options.limit.name) - .setNameLocalizations( - localizationObject( - (strings) => - strings.bot.commands.search_players.options.limit.name, - undefined, - true - ) - ) - .setDescription( - literals( - strings.bot.commands.search_players.options.limit.description, - { limit: DEFAULT_LIMIT } - ) - ) - .setDescriptionLocalizations( - localizationObject( - (strings) => - strings.bot.commands.search_players.options.limit.description, - { limit: DEFAULT_LIMIT } - ) - ) - .setMinValue(1) - .setMaxValue(100) - ), - - async handler(interaction) { - const { strings } = translator(interaction.locale); - const region = interaction.options.getString("region") as Region; - const name = interaction.options.getString("username")!; - const limit = interaction.options.getInteger("limit") ?? 25; - const trimmedSearch = name.trim(); - const players = usernamePattern.test(trimmedSearch) - ? await fetchBlitz(region, "account/list", { - search: trimmedSearch, - limit, - }) - : []; - - return embedInfo( - literals(strings.bot.commands.search_players.body.title, { - search: markdownEscape(trimmedSearch), - region: strings.common.regions.normal[region], - }), - `\`\`\`${ - players.length === 0 - ? strings.bot.commands.search_players.body.no_results - : players.map((player) => player.nickname).join("\n") - }\`\`\`` - ); - }, - }); -}); diff --git a/packages/bot/src/commands/stats.tsx b/packages/bot/src/commands/stats.tsx deleted file mode 100644 index 38337cd09..000000000 --- a/packages/bot/src/commands/stats.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import { emblemURL, getAccountInfo, getClanAccountInfo } from "@blitzkit/core"; -import { Locale } from "discord.js"; -import { AllStatsOverview } from "../components/AllStatsOverview"; -import { CommandWrapper } from "../components/CommandWrapper"; -import { NoData } from "../components/NoData"; -import { TitleBar } from "../components/TitleBar"; -import { filtersToDescription } from "../core/blitzkit/filtersToDescription"; -import { filterStats, type StatFilters } from "../core/blitzstars/filterStats"; -import { getStatsInPeriod } from "../core/blitzstars/getStatsInPeriod"; -import { addPeriodicFilterOptions } from "../core/discord/addPeriodicFilterOptions"; -import { addUsernameChoices } from "../core/discord/addUsernameChoices"; -import { autocompleteTanks } from "../core/discord/autocompleteTanks"; -import { autocompleteUsername } from "../core/discord/autocompleteUsername"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { getFiltersFromButton } from "../core/discord/getFiltersFromButton"; -import { resolvePeriodFromButton } from "../core/discord/resolvePeriodFromButton"; -import { type ResolvedPeriod } from "../core/discord/resolvePeriodFromCommand"; -import { resolvePlayerFromButton } from "../core/discord/resolvePlayerFromButton"; -import { type ResolvedPlayer } from "../core/discord/resolvePlayerFromCommand"; -import { translator } from "../core/localization/translator"; -import type { CommandRegistry } from "../events/interactionCreate"; - -async function render( - { region, id }: ResolvedPlayer, - { start, end, name }: ResolvedPeriod, - filters: StatFilters, - locale: Locale, -) { - const { nickname } = await getAccountInfo(region, id); - const clan = (await getClanAccountInfo(region, id, ["clan"]))?.clan; - const clanImage = clan ? emblemURL(clan.emblem_set_id) : undefined; - const diffedTankStats = await getStatsInPeriod( - region, - id, - start, - end, - locale, - ); - const { stats, supplementary } = await filterStats(diffedTankStats, filters); - const filterDescriptions = await filtersToDescription(filters, locale); - - return ( - - - - {!stats.battles && } - {stats.battles > 0 && ( - - )} - - ); -} - -export const statsCommand = new Promise(async (resolve) => { - resolve({ - command: await addPeriodicFilterOptions( - createLocalizedCommand("stats"), - (option) => option.addStringOption(addUsernameChoices), - ), - - async handler(interaction) { - const { strings } = translator(interaction.locale); - return strings.bot.commands.dead; - - // const player = await resolvePlayerFromCommand(interaction); - // const period = resolvePeriodFromCommand(player.region, interaction); - // const filters = await getFiltersFromCommand(interaction); - // const path = commandToURL(interaction, { - // ...player, - // ...getCustomPeriodParams(interaction), - // ...filters, - // }); - - // return Promise.all([ - // render(player, period, filters, interaction.locale), - // buttonRefresh(interaction, path), - // getBlitzStarsLinkButton(player.region, player.id), - // ]); - }, - - autocomplete: (interaction) => { - autocompleteUsername(interaction); - autocompleteTanks(interaction); - }, - - async button(interaction) { - const player = await resolvePlayerFromButton(interaction); - const period = resolvePeriodFromButton(player.region, interaction); - const filters = getFiltersFromButton(interaction); - - return await render(player, period, filters, interaction.locale); - }, - }); -}); diff --git a/packages/bot/src/commands/today.ts b/packages/bot/src/commands/today.ts deleted file mode 100644 index 65256cad9..000000000 --- a/packages/bot/src/commands/today.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { gameDefinitions } from "../core/blitzkit/nonBlockingPromises"; -import { addFilterOptions } from "../core/discord/addFilterOptions"; -import { addUsernameChoices } from "../core/discord/addUsernameChoices"; -import { autocompleteTanks } from "../core/discord/autocompleteTanks"; -import { autocompleteUsername } from "../core/discord/autocompleteUsername"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { getFiltersFromButton } from "../core/discord/getFiltersFromButton"; -import { resolvePeriodFromButton } from "../core/discord/resolvePeriodFromButton"; -import { resolvePlayerFromButton } from "../core/discord/resolvePlayerFromButton"; -import { translator } from "../core/localization/translator"; -import { type CommandRegistry } from "../events/interactionCreate"; -import { renderBreakdown } from "./breakdown/render"; - -export const todayCommand = new Promise(async (resolve) => { - const awaitedGameDefinitions = await gameDefinitions; - - resolve({ - command: addFilterOptions( - createLocalizedCommand("today"), - awaitedGameDefinitions.nations, - ).addStringOption(addUsernameChoices), - - async handler(interaction) { - const { strings } = translator(interaction.locale); - return strings.bot.commands.dead; - - // const player = await resolvePlayerFromCommand(interaction); - // const period = resolvePeriodFromCommand( - // player.region, - // interaction, - // 'today', - // ); - // const filters = await getFiltersFromCommand(interaction); - // const path = commandToURL(interaction, { - // ...player, - // ...getCustomPeriodParams(interaction, true), - // ...filters, - // }); - - // return [ - // ...(await renderBreakdown(player, period, filters, interaction.locale)), - // buttonRefresh(interaction, path), - // await getBlitzStarsLinkButton(player.region, player.id), - // ]; - }, - - autocomplete: (interaction) => { - autocompleteUsername(interaction); - autocompleteTanks(interaction); - }, - - async button(interaction) { - const player = await resolvePlayerFromButton(interaction); - const period = resolvePeriodFromButton(player.region, interaction); - const filters = getFiltersFromButton(interaction); - - return await renderBreakdown(player, period, filters, interaction.locale); - }, - }); -}); diff --git a/packages/bot/src/components/AllStatsOverview/components/HeroStat/components/Glow.tsx b/packages/bot/src/components/AllStatsOverview/components/HeroStat/components/Glow.tsx deleted file mode 100644 index 1a3a00277..000000000 --- a/packages/bot/src/components/AllStatsOverview/components/HeroStat/components/Glow.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { type ComponentProps } from "react"; - -export interface GlowProps extends ComponentProps<"div"> { - color: string; - direction?: "default" | "reverse"; - rotation?: number; - startOpacity?: number; - endOpacity?: number; -} - -const width = 16; - -export function Glow({ - color, - direction = "default", - rotation = 0, - style, - startOpacity = 0, - endOpacity = 1, - ...props -}: GlowProps) { - const color1 = `${color}${Math.round(endOpacity * 255) - .toString(16) - .padStart(2, "0")}`; - const color2 = `${color}${Math.round(startOpacity * 255) - .toString(16) - .padStart(2, "0")}`; - const background = `linear-gradient(${ - direction === "default" ? 0 : 180 - }deg, ${color1} 0%, ${color2} 100%)`; - - return ( -
-
-
-
-
-
-
- ); -} diff --git a/packages/bot/src/components/AllStatsOverview/components/HeroStat/index.tsx b/packages/bot/src/components/AllStatsOverview/components/HeroStat/index.tsx deleted file mode 100644 index be3b5a0f2..000000000 --- a/packages/bot/src/components/AllStatsOverview/components/HeroStat/index.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { theme } from '../../../../stitches.config'; -import { Glow } from './components/Glow'; - -export interface WN8DisplayProps { - subtitle: string; - color: string; - value: string | number; -} - -export function HeroStat({ color, subtitle, value }: WN8DisplayProps) { - return ( -
- - -
-
- - {value} - - - {subtitle} - -
-
- - -
- ); -} diff --git a/packages/bot/src/components/AllStatsOverview/components/WN8SurroundingStat.tsx b/packages/bot/src/components/AllStatsOverview/components/WN8SurroundingStat.tsx deleted file mode 100644 index 62db93c41..000000000 --- a/packages/bot/src/components/AllStatsOverview/components/WN8SurroundingStat.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { theme } from '../../../stitches.config'; - -export enum WN8SurroundingStatAlign { - Left, - Right, -} - -export interface WN8SurroundingStatProps { - label: string; - value: string | number; - align?: WN8SurroundingStatAlign; - padded?: boolean; -} - -export function WN8SurroundingStat({ - label, - value, - align = WN8SurroundingStatAlign.Left, - padded = false, -}: WN8SurroundingStatProps) { - return ( -
- - {value} - - - {label} - -
- ); -} diff --git a/packages/bot/src/components/AllStatsOverview/index.tsx b/packages/bot/src/components/AllStatsOverview/index.tsx deleted file mode 100644 index 62073cb00..000000000 --- a/packages/bot/src/components/AllStatsOverview/index.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import { - getWN8Percentile, - Percentile, - type SupplementaryStats, -} from '@blitzkit/core'; -import { Locale } from 'discord.js'; -import { translator } from '../../core/localization/translator'; -import { PERCENTILE_COLORS } from '../PercentileIndicator/constants'; -import { HeroStat } from './components/HeroStat'; -import { - WN8SurroundingStat, - WN8SurroundingStatAlign, -} from './components/WN8SurroundingStat'; - -export interface AllStatsOverviewProps { - stats: { - battles: number; - wins: number; - damage_dealt: number; - damage_received: number; - survived_battles: number; - hits: number; - shots: number; - frags: number; - }; - supplementaryStats: SupplementaryStats; - locale: Locale; -} - -export function AllStatsOverview({ - stats, - locale, - supplementaryStats, -}: AllStatsOverviewProps) { - const { strings } = translator(locale); - const percentile = - typeof supplementaryStats.WN8 === 'number' - ? getWN8Percentile(supplementaryStats.WN8) - : Percentile.VeryBad; - const color = PERCENTILE_COLORS[percentile]; - - const heroStat = - typeof supplementaryStats.WN8 === 'number' - ? supplementaryStats.WN8.toFixed(0) - : '--'; - - return ( -
-
- - - - -
- - - -
- - - - -
-
- ); -} diff --git a/packages/bot/src/components/Breakdown/components/Root.tsx b/packages/bot/src/components/Breakdown/components/Root.tsx deleted file mode 100644 index cbbe20573..000000000 --- a/packages/bot/src/components/Breakdown/components/Root.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { type ReactNode } from "react"; - -interface RootProps { - children: ReactNode; -} - -export function Root({ children }: RootProps) { - return ( -
- {children} -
- ); -} diff --git a/packages/bot/src/components/Breakdown/components/Row.tsx b/packages/bot/src/components/Breakdown/components/Row.tsx deleted file mode 100644 index a5af946d6..000000000 --- a/packages/bot/src/components/Breakdown/components/Row.tsx +++ /dev/null @@ -1,126 +0,0 @@ -import { - Percentile, - TankClass, - TankType, - TREE_TYPE_ICONS, -} from "@blitzkit/core"; -import { createColors } from "bepaint"; -import { - type AccentColor, - type GrayColor, - PALETTES, -} from "../../../core/radix/radixColors"; -import { RowStat } from "./RowStat"; - -export interface RowStatItem { - title: string; - current?: number | string; - career?: number | string; - delta?: number; - percentile?: Percentile; -} - -interface RowProps { - displayType?: "tank" | "summary"; - naked?: boolean; - title: string; - minimized?: boolean; - type?: TankType; - class?: TankClass; - color?: AccentColor | GrayColor; - stats: (RowStatItem | undefined)[]; -} - -export function Row({ - color = "slate", - stats, - title, - minimized, - class: tankClass, - type, - naked = false, - displayType, -}: RowProps) { - const palette = PALETTES[`${color}Dark`]; - const theme = createColors(palette); - - return ( -
-
- {displayType === "tank" && tankClass !== undefined && ( - {TankClass[tankClass]} - )} - - - {title} - -
- -
- {stats.map( - (row, index) => - row && ( - - ), - )} -
-
- ); -} diff --git a/packages/bot/src/components/Breakdown/components/RowStat.tsx b/packages/bot/src/components/Breakdown/components/RowStat.tsx deleted file mode 100644 index 430f8b6a6..000000000 --- a/packages/bot/src/components/Breakdown/components/RowStat.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { Percentile } from "@blitzkit/core"; -import { createColors } from "bepaint"; -import { - type AccentColor, - type GrayColor, - PALETTES, -} from "../../../core/radix/radixColors"; -import { PercentileIndicator } from "../../PercentileIndicator"; - -interface RowStatProps { - name: string; - value?: string | number; - delta?: number; - percentile?: Percentile; - minimized?: boolean; - color?: AccentColor | GrayColor; -} - -export function RowStat({ - name, - value, - delta, - percentile, - minimized = false, - color = "slate", -}: RowStatProps) { - const theme = createColors(PALETTES[`${color}Dark`]); - - return ( -
-
- {percentile === undefined && delta !== undefined && delta !== 0 && ( - {delta 0 ? "Increase" : "Decrease"} - src={ - (delta ?? 0) > 0 - ? "https://i.imgur.com/qbjiXa1.png" - : "https://i.imgur.com/3uyNhun.png" - } - style={{ width: 12, height: 12 }} - /> - )} - {percentile !== undefined && ( - - )} - - - {value} - -
- - {!minimized && ( - - {name} - - )} -
- ); -} diff --git a/packages/bot/src/components/Breakdown/index.ts b/packages/bot/src/components/Breakdown/index.ts deleted file mode 100644 index bf85c5f20..000000000 --- a/packages/bot/src/components/Breakdown/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './components/Root'; -export * from './components/Row'; -export * from './components/RowStat'; diff --git a/packages/bot/src/components/CommandWrapper.tsx b/packages/bot/src/components/CommandWrapper.tsx deleted file mode 100644 index c4dd6b066..000000000 --- a/packages/bot/src/components/CommandWrapper.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { type ReactNode } from "react"; -import { theme } from "../stitches.config"; - -interface CommandWrapperProps { - children: ReactNode; - fat?: boolean; -} - -export function CommandWrapper({ children, fat }: CommandWrapperProps) { - return ( -
- {children} -
- ); -} diff --git a/packages/bot/src/components/DeltaCaret.tsx b/packages/bot/src/components/DeltaCaret.tsx deleted file mode 100644 index 15c1ae8aa..000000000 --- a/packages/bot/src/components/DeltaCaret.tsx +++ /dev/null @@ -1,21 +0,0 @@ -interface DeltaCaretProps { - delta: number; -} - -export function DeltaCaret({ delta }: DeltaCaretProps) { - return ( - {delta 0 ? 'Increase' : 'Decrease'} - style={{ - width: 16, - height: 16, - objectFit: 'contain', - }} - src={ - delta > 0 - ? 'https://i.imgur.com/gjI4a5f.png' - : 'https://i.imgur.com/lLGZygN.png' - } - /> - ); -} diff --git a/packages/bot/src/components/Flag.tsx b/packages/bot/src/components/Flag.tsx deleted file mode 100644 index 110b71f95..000000000 --- a/packages/bot/src/components/Flag.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { asset } from '@blitzkit/core'; - -interface FlagProps { - nation: string; -} - -export function Flag({ nation }: FlagProps) { - return ( -
- {nation} -
- ); -} diff --git a/packages/bot/src/components/GenericAllStats/components/Card.tsx b/packages/bot/src/components/GenericAllStats/components/Card.tsx deleted file mode 100644 index 2f0922c29..000000000 --- a/packages/bot/src/components/GenericAllStats/components/Card.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { Percentile } from '@blitzkit/core'; -import { theme } from '../../../stitches.config'; -import { PercentileIndicator } from '../../PercentileIndicator'; - -interface CardItem { - label: string; - value: string | number | undefined; - percentile?: Percentile; -} - -interface CardProps { - items: CardItem[]; - title: string; -} - -export function Card({ items, title }: CardProps) { - return ( -
- - {title} - - -
- {items.map((item) => ( -
- - {item.label} - - -
- -
- {item.percentile !== undefined && ( - - )} - - {item.value === undefined || Number.isNaN(item.value) - ? '--' - : item.value} - -
-
- ))} -
-
- ); -} diff --git a/packages/bot/src/components/GenericAllStats/components/Root.tsx b/packages/bot/src/components/GenericAllStats/components/Root.tsx deleted file mode 100644 index 5c362f44b..000000000 --- a/packages/bot/src/components/GenericAllStats/components/Root.tsx +++ /dev/null @@ -1,17 +0,0 @@ -interface RootProps { - children: React.ReactNode; -} - -export function Root({ children }: RootProps) { - return ( -
- {children} -
- ); -} diff --git a/packages/bot/src/components/GenericAllStats/components/Row.tsx b/packages/bot/src/components/GenericAllStats/components/Row.tsx deleted file mode 100644 index bd5cfa17e..000000000 --- a/packages/bot/src/components/GenericAllStats/components/Row.tsx +++ /dev/null @@ -1,7 +0,0 @@ -interface RowProps { - children: React.ReactNode; -} - -export function Row({ children }: RowProps) { - return
{children}
; -} diff --git a/packages/bot/src/components/GenericAllStats/index.tsx b/packages/bot/src/components/GenericAllStats/index.tsx deleted file mode 100644 index 36b687132..000000000 --- a/packages/bot/src/components/GenericAllStats/index.tsx +++ /dev/null @@ -1,149 +0,0 @@ -import { - type BlitzStats, - getWN8Percentile, - isNumber, - type SupplementaryStats, -} from '@blitzkit/core'; -import { Locale } from 'discord.js'; -import { translator } from '../../core/localization/translator'; -import { Card } from './components/Card'; -import { Root } from './components/Root'; -import { Row } from './components/Row'; - -interface GenericAllStatsProps { - stats: BlitzStats; - supplementaryStats?: SupplementaryStats; - locale: Locale; -} - -export function GenericAllStats({ - stats, - supplementaryStats, - locale, -}: GenericAllStatsProps) { - const { strings } = translator(locale); - const damageRatio = stats.damage_dealt / stats.damage_received; - const killsToDeathRatio = - stats.frags / (stats.battles - stats.survived_battles); - - return ( - - - - - - - - - - - ); -} diff --git a/packages/bot/src/components/GenericStats/components/GenericStatsColumn.tsx b/packages/bot/src/components/GenericStats/components/GenericStatsColumn.tsx deleted file mode 100644 index 5346cde64..000000000 --- a/packages/bot/src/components/GenericStats/components/GenericStatsColumn.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { type GenericStat } from ".."; -import { GenericStatsRow } from "./GenericStatsRow"; - -interface GenericStatsColumnProps { - stats: GenericStat[]; -} - -export function GenericStatsColumn({ stats }: GenericStatsColumnProps) { - return ( -
- {stats.map((stat) => ( - - ))} -
- ); -} diff --git a/packages/bot/src/components/GenericStats/components/GenericStatsRow.tsx b/packages/bot/src/components/GenericStats/components/GenericStatsRow.tsx deleted file mode 100644 index 1db1c6bda..000000000 --- a/packages/bot/src/components/GenericStats/components/GenericStatsRow.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { type GenericStat } from ".."; -import { theme } from "../../../stitches.config"; -import { PercentileIndicator } from "../../PercentileIndicator"; - -interface GenericStatsRowProps { - stat: GenericStat; -} - -export function GenericStatsRow({ stat }: GenericStatsRowProps) { - return ( -
- - {stat[0]} - - - - {stat[2] !== undefined && } - {stat[1]} - -
- ); -} diff --git a/packages/bot/src/components/GenericStats/index.tsx b/packages/bot/src/components/GenericStats/index.tsx deleted file mode 100644 index ae23b18e9..000000000 --- a/packages/bot/src/components/GenericStats/index.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { Percentile } from '@blitzkit/core'; -import { GenericStatsColumn } from './components/GenericStatsColumn'; - -export type GenericStat = - | [string, string | number | undefined] - | [string, string | number | undefined, Percentile | undefined]; - -export interface GenericStatsProps { - stats: GenericStat[]; - columnCount?: number; -} - -export function GenericStats({ stats, columnCount = 2 }: GenericStatsProps) { - const filteredStats = stats.filter(([, value]) => value !== undefined); - const itemsPerRow = Math.ceil(filteredStats.length / columnCount); - const columns: GenericStat[][] = []; - - filteredStats.forEach((stat, index) => { - const column = Math.floor(index / itemsPerRow); - if (!columns[column]) columns[column] = []; - columns[column].push(stat); - }); - - return ( -
- {columns.map((stats) => ( - stat[0]).join('-')} - stats={stats} - /> - ))} -
- ); -} diff --git a/packages/bot/src/components/Graph/components/Line/constants.ts b/packages/bot/src/components/Graph/components/Line/constants.ts deleted file mode 100644 index 74170a3a8..000000000 --- a/packages/bot/src/components/Graph/components/Line/constants.ts +++ /dev/null @@ -1,11 +0,0 @@ -export enum LineColor { - Red, - Green, - Blue, - White, -} -export enum LineStyle { - Solid, - Dashes, - DashesAndDots, -} diff --git a/packages/bot/src/components/Graph/components/Line/index.tsx b/packages/bot/src/components/Graph/components/Line/index.tsx deleted file mode 100644 index ad0beda82..000000000 --- a/packages/bot/src/components/Graph/components/Line/index.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { blue, green, red, slateDark } from '@radix-ui/colors'; -import { GRAPH_HEIGHT, GRAPH_WIDTH } from '../Root/constants'; -import { LineColor, LineStyle } from './constants'; - -const LINE_COLORS = { - [LineColor.Red]: red.red9, - [LineColor.Green]: green.green9, - [LineColor.Blue]: blue.blue9, - [LineColor.White]: slateDark.slate11, -}; - -const LINE_STYLES = { - [LineStyle.Solid]: undefined, - [LineStyle.Dashes]: '8, 8', - [LineStyle.DashesAndDots]: '1, 4, 8, 4', -}; - -export type PlotItem = [number, number]; -type Plot = PlotItem[]; - -interface LineProps { - plot: Plot; - minY?: number; - maxY?: number; - color?: LineColor; - style?: LineStyle; - lineWidth?: number; -} - -export function Line({ - plot, - minY: providedMinY, - maxY: providedMaxY, - color = LineColor.Red, - style = LineStyle.Solid, - lineWidth = 2, -}: LineProps) { - const xs = plot.map(([x]) => x); - const minX = Math.min(...xs); - const maxX = Math.max(...xs); - const ys = plot.map(([, y]) => y); - const minY = providedMinY ?? Math.min(...ys); - const maxY = providedMaxY ?? Math.max(...ys); - const rangeX = maxX - minX; - const rangeY = maxY - minY; - const points = plot - .map( - ([x, y]) => - `${(x - minX) * (GRAPH_WIDTH / rangeX)},${ - GRAPH_HEIGHT - (y - minY) * (GRAPH_HEIGHT / rangeY) - }`, - ) - .join(' '); - - return ( - - ); -} diff --git a/packages/bot/src/components/Graph/components/Margin/constants.ts b/packages/bot/src/components/Graph/components/Margin/constants.ts deleted file mode 100644 index fab153d6c..000000000 --- a/packages/bot/src/components/Graph/components/Margin/constants.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum MarginOrientation { - Vertical, - Horizontal, -} diff --git a/packages/bot/src/components/Graph/components/Margin/index.tsx b/packages/bot/src/components/Graph/components/Margin/index.tsx deleted file mode 100644 index eb15c732c..000000000 --- a/packages/bot/src/components/Graph/components/Margin/index.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { times } from 'lodash-es'; -import { theme } from '../../../../stitches.config'; -import { type MarginInputProps } from '../Root'; -import { MarginOrientation } from './constants'; - -interface MarginProps extends MarginInputProps { - verticalSeparations: number; - orientation?: MarginOrientation; -} - -export function Margin({ - verticalSeparations, - max, - min, - suffix, - precision = 0, - orientation = MarginOrientation.Vertical, -}: MarginProps) { - const isVertical = orientation === MarginOrientation.Vertical; - - return ( -
- {times(verticalSeparations, (index) => ( - - {`${( - (max - min) * - ((verticalSeparations - (index + 1)) / - (verticalSeparations - 1)) + - min - ).toFixed(precision)}${suffix ?? ''}`} - - ))} -
- ); -} diff --git a/packages/bot/src/components/Graph/components/Root/constants.ts b/packages/bot/src/components/Graph/components/Root/constants.ts deleted file mode 100644 index 0ff4e7d6b..000000000 --- a/packages/bot/src/components/Graph/components/Root/constants.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const GRAPH_WIDTH = 384; -export const GRAPH_HEIGHT = 320; diff --git a/packages/bot/src/components/Graph/components/Root/index.tsx b/packages/bot/src/components/Graph/components/Root/index.tsx deleted file mode 100644 index 86ac910f7..000000000 --- a/packages/bot/src/components/Graph/components/Root/index.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import { times } from "lodash-es"; -import { type ReactNode } from "react"; -import { theme } from "../../../../stitches.config"; -import { Margin } from "../Margin"; -import { MarginOrientation } from "../Margin/constants"; -import { GRAPH_HEIGHT, GRAPH_WIDTH } from "./constants"; - -export interface MarginInputProps { - min: number; - max: number; - suffix?: string; - precision?: number; -} - -interface RootProps { - children: ReactNode; - verticalMargin?: MarginInputProps; - horizontalMargin?: MarginInputProps; - separations?: number; - xMinLabel: string; - xMaxLabel: string; - xTitle?: string; -} - -export function Root({ - children, - verticalMargin, - horizontalMargin, - separations = 5, - xMinLabel, - xMaxLabel, - xTitle, -}: RootProps) { - return ( -
-
- - {xMinLabel} - - - {xMaxLabel} - -
- -
- {verticalMargin && ( - - )} - - - {times(separations - 2, (index) => { - const y = GRAPH_HEIGHT * ((index + 1) / (separations - 1)); - - return ( - - ); - })} - - {children} - -
- - {horizontalMargin && ( - - )} - - {xTitle !== undefined && ( -
- - {xTitle} - -
- )} -
- ); -} diff --git a/packages/bot/src/components/Graph/index.ts b/packages/bot/src/components/Graph/index.ts deleted file mode 100644 index 5aaadb77e..000000000 --- a/packages/bot/src/components/Graph/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './components/Line'; -export * from './components/Margin'; -export * from './components/Root'; diff --git a/packages/bot/src/components/Leaderboard/components/Gap.tsx b/packages/bot/src/components/Leaderboard/components/Gap.tsx deleted file mode 100644 index f1fa3f16c..000000000 --- a/packages/bot/src/components/Leaderboard/components/Gap.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { theme } from '../../../stitches.config'; - -interface GapProps { - message: string; -} - -export function Gap({ message: number }: GapProps) { - return ( -
- - {number.toLocaleString()} - -
- ); -} diff --git a/packages/bot/src/components/Leaderboard/components/Item.tsx b/packages/bot/src/components/Leaderboard/components/Item.tsx deleted file mode 100644 index 249221e41..000000000 --- a/packages/bot/src/components/Leaderboard/components/Item.tsx +++ /dev/null @@ -1,171 +0,0 @@ -import { type ComponentProps } from "react"; -import { theme } from "../../../stitches.config"; - -interface ItemProps extends ComponentProps<"div"> { - position: number; - deltaPosition?: number; - score?: number; - deltaScore?: number; - nickname: string; - clan?: string; - highlight?: boolean; -} - -export function Item({ - position, - deltaPosition, - score, - deltaScore, - nickname, - clan, - highlight, - ...props -}: ItemProps) { - return ( -
- - {position.toLocaleString()}. - - -
- - {nickname} - - {clan && ( - - [{clan}] - - )} -
- - {deltaPosition !== undefined && deltaPosition !== 0 && ( -
- {deltaPosition 0 ? "Increase" : "Decrease"} - style={{ - width: 8, - height: 8, - }} - src={ - deltaPosition > 0 - ? "https://i.imgur.com/qbjiXa1.png" - : "https://i.imgur.com/3uyNhun.png" - } - /> - - {Math.abs(deltaPosition).toLocaleString()} - -
- )} - - {/* TODO: lol remove this spacer div for a better solution */} -
- -
- {deltaScore !== 0 && deltaScore !== undefined && ( - - {Math.abs(deltaScore).toLocaleString()} - - )} - {deltaScore !== 0 && deltaScore !== undefined && ( - {deltaScore 0 ? "Increase" : "Decrease"} - style={{ - width: 8, - height: 8, - }} - src={ - deltaScore > 0 - ? "https://i.imgur.com/qbjiXa1.png" - : "https://i.imgur.com/3uyNhun.png" - } - /> - )} - - {score !== undefined && ( - - {score.toLocaleString()} - - )} -
-
- ); -} diff --git a/packages/bot/src/components/Leaderboard/components/Root.tsx b/packages/bot/src/components/Leaderboard/components/Root.tsx deleted file mode 100644 index d812cf3c7..000000000 --- a/packages/bot/src/components/Leaderboard/components/Root.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { type ReactNode } from 'react'; - -interface RootProps { - children: ReactNode; -} - -export function Root({ children }: RootProps) { - return ( -
- {children} -
- ); -} diff --git a/packages/bot/src/components/Leaderboard/index.ts b/packages/bot/src/components/Leaderboard/index.ts deleted file mode 100644 index 4334194b1..000000000 --- a/packages/bot/src/components/Leaderboard/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './components/Gap'; -export * from './components/Item'; -export * from './components/Root'; diff --git a/packages/bot/src/components/NoData/constants.ts b/packages/bot/src/components/NoData/constants.ts deleted file mode 100644 index a01763279..000000000 --- a/packages/bot/src/components/NoData/constants.ts +++ /dev/null @@ -1,5 +0,0 @@ -export enum NoDataType { - BattlesInPeriod, - PlayersInPeriod, - TanksFound, -} diff --git a/packages/bot/src/components/NoData/index.tsx b/packages/bot/src/components/NoData/index.tsx deleted file mode 100644 index 7fa7227aa..000000000 --- a/packages/bot/src/components/NoData/index.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { Locale } from 'discord.js'; -import { translator } from '../../core/localization/translator'; -import { theme } from '../../stitches.config'; - -interface NoDataProps { - type: - | 'battles_in_period' - | 'players_in_period' - | 'tanks_found' - | 'battles_in_season'; - locale: Locale; -} - -export function NoData({ type, locale }: NoDataProps) { - const { strings } = translator(locale); - - return ( -
- - {strings.bot.common.no_data[type]} - -
- ); -} diff --git a/packages/bot/src/components/PercentileIndicator/constants.ts b/packages/bot/src/components/PercentileIndicator/constants.ts deleted file mode 100644 index b5cefb554..000000000 --- a/packages/bot/src/components/PercentileIndicator/constants.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Percentile } from '@blitzkit/core'; -import { - amber, - blue, - green, - lime, - orange, - pink, - purple, - red, - slate, - teal, -} from '@radix-ui/colors'; - -export const PERCENTILE_COLORS = { - [Percentile.VeryBad]: slate.slate9, - [Percentile.Bad]: red.red9, - [Percentile.BelowAverage]: orange.orange9, - [Percentile.Average]: amber.amber10, - [Percentile.AboveAverage]: lime.lime8, - [Percentile.Good]: green.green9, - [Percentile.VeryGood]: teal.teal9, - [Percentile.Great]: blue.blue9, - [Percentile.Unicum]: pink.pink9, - [Percentile.SuperUnicum]: purple.purple9, -}; diff --git a/packages/bot/src/components/PercentileIndicator/index.tsx b/packages/bot/src/components/PercentileIndicator/index.tsx deleted file mode 100644 index f7c0f5b68..000000000 --- a/packages/bot/src/components/PercentileIndicator/index.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { Percentile } from '@blitzkit/core'; -import { PERCENTILE_COLORS } from './constants'; - -interface PercentileIndicatorProps { - percentile: Percentile; -} - -export function PercentileIndicator({ percentile }: PercentileIndicatorProps) { - return ( -
- ); -} diff --git a/packages/bot/src/components/Tanks/components/Column.tsx b/packages/bot/src/components/Tanks/components/Column.tsx deleted file mode 100644 index 6e3549c89..000000000 --- a/packages/bot/src/components/Tanks/components/Column.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { type ReactNode } from 'react'; - -interface ColumnProps { - children: ReactNode; -} - -export function Column({ children }: ColumnProps) { - return ( -
- {children} -
- ); -} diff --git a/packages/bot/src/components/Tanks/components/Item/index.tsx b/packages/bot/src/components/Tanks/components/Item/index.tsx deleted file mode 100644 index f33342c3a..000000000 --- a/packages/bot/src/components/Tanks/components/Item/index.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { TankClass, TankType, TREE_TYPE_ICONS } from "@blitzkit/core"; -import { theme } from "../../../../stitches.config"; - -export interface ItemProps { - tankClass?: TankClass; - name: string; - tankType: TankType; -} - -const TREE_TYPE_COLOR = { - [TankType.TANK_TYPE_RESEARCHABLE]: "", - [TankType.TANK_TYPE_PREMIUM]: "_amber", - [TankType.TANK_TYPE_COLLECTOR]: "_blue", -} as const; - -export function Item({ - tankClass: tankType, - name, - tankType: treeType, -}: ItemProps) { - return ( -
-
- {tankType && ( - {TankType[tankType]} - )} - - {name} - -
- - {/* {image && ( - // TODO: remove hardcoded dimensions when satori fixes non-width unloadable images - {name} - )} */} -
- ); -} diff --git a/packages/bot/src/components/Tanks/components/Root.tsx b/packages/bot/src/components/Tanks/components/Root.tsx deleted file mode 100644 index a5d2e3107..000000000 --- a/packages/bot/src/components/Tanks/components/Root.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { type ReactNode } from "react"; - -interface RootProps { - children: ReactNode; -} - -export function Root({ children }: RootProps) { - return ( -
- {children} -
- ); -} diff --git a/packages/bot/src/components/Tanks/components/Row.tsx b/packages/bot/src/components/Tanks/components/Row.tsx deleted file mode 100644 index f5520127d..000000000 --- a/packages/bot/src/components/Tanks/components/Row.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { type ReactNode } from "react"; - -interface RowProps { - children: ReactNode; -} - -export function Row({ children }: RowProps) { - return ( -
- {children} -
- ); -} diff --git a/packages/bot/src/components/Tanks/components/Title.tsx b/packages/bot/src/components/Tanks/components/Title.tsx deleted file mode 100644 index 10e7d6281..000000000 --- a/packages/bot/src/components/Tanks/components/Title.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { theme } from '../../../stitches.config'; - -interface TitleProps { - children: string; -} - -export function Title({ children }: TitleProps) { - return ( - - {children} - - ); -} diff --git a/packages/bot/src/components/Tanks/index.ts b/packages/bot/src/components/Tanks/index.ts deleted file mode 100644 index 7042919a3..000000000 --- a/packages/bot/src/components/Tanks/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './components/Column'; -export * from './components/Item'; -export * from './components/Root'; -export * from './components/Row'; -export * from './components/Title'; diff --git a/packages/bot/src/components/TierWeights.tsx b/packages/bot/src/components/TierWeights.tsx deleted file mode 100644 index 415c6b6bc..000000000 --- a/packages/bot/src/components/TierWeights.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import type { TierWeightsRecord } from "@blitzkit/core"; -import { theme } from "../stitches.config"; - -interface TierWeightsProps { - weights: TierWeightsRecord; -} - -export function TierWeights({ weights }: TierWeightsProps) { - return ( -
- {Object.entries(weights).map(([tier, weight]) => ( -
- {tier} -
- ))} -
- ); -} diff --git a/packages/bot/src/components/TitleBar.tsx b/packages/bot/src/components/TitleBar.tsx deleted file mode 100644 index e64d0db7b..000000000 --- a/packages/bot/src/components/TitleBar.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { theme } from '../stitches.config'; - -interface TitleBarProps { - title: string; - image?: string; - description?: string; -} - -export function TitleBar({ title, image, description }: TitleBarProps) { - return ( -
-
- {image && ( - {title} - )} - - - {title} - -
- - - {description} - -
- ); -} diff --git a/packages/bot/src/core/blitz/normalizeLeagueIcon.ts b/packages/bot/src/core/blitz/normalizeLeagueIcon.ts deleted file mode 100644 index 4c450ed19..000000000 --- a/packages/bot/src/core/blitz/normalizeLeagueIcon.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function normalizeLeagueIcon(url: string) { - return url.startsWith('http') ? url : `https:${url}`; -} diff --git a/packages/bot/src/core/blitz/resolveTankId.ts b/packages/bot/src/core/blitz/resolveTankId.ts deleted file mode 100644 index 4415a8362..000000000 --- a/packages/bot/src/core/blitz/resolveTankId.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { SEARCH_KEYS } from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { Locale } from "discord.js"; -import { go } from "fuzzysort"; -import markdownEscape from "markdown-escape"; -import { tankDefinitions, tankNames } from "../blitzkit/nonBlockingPromises"; -import { UserError } from "../blitzkit/userError"; -import { tankNamesTechTreeOnly } from "../discord/autocompleteTanks"; -import { translator } from "../localization/translator"; - -export async function resolveTankId( - tank: string | number, - locale: Locale, - techTreeOnly = false -) { - const { strings } = translator(locale); - const number = typeof tank === "string" ? Number(tank) : tank; - - if (Number.isNaN(number)) { - const searchResult = go( - `${tank}`, - await (techTreeOnly ? tankNamesTechTreeOnly : tankNames), - { keys: SEARCH_KEYS, limit: 1 } - ); - - if (searchResult.length === 0) { - throw new UserError( - literals(strings.bot.common.errors.tank_name_not_found, { - search: markdownEscape(`${tank}`), - }) - ); - } else { - return searchResult[0].obj.id; - } - } else { - const awaitedTankDefinitions = await tankDefinitions; - - if (awaitedTankDefinitions.tanks[number]) { - return number; - } else { - throw new UserError( - literals(strings.bot.common.errors.tank_id_not_found, { - search: number, - }) - ); - } - } -} diff --git a/packages/bot/src/core/blitz/searchClansAcrossRegionsBotWrapper.ts b/packages/bot/src/core/blitz/searchClansAcrossRegionsBotWrapper.ts deleted file mode 100644 index c06e03b30..000000000 --- a/packages/bot/src/core/blitz/searchClansAcrossRegionsBotWrapper.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { searchClansAcrossRegions } from '@blitzkit/core'; -import { Locale } from 'discord.js'; -import { UserError } from '../blitzkit/userError'; -import { translator } from '../localization/translator'; - -export async function searchClansAcrossRegionsBotWrapper( - search: string, - locale: Locale, - limit?: number, -) { - const { strings } = translator(locale); - const trimmed = search.trim(); - - if (trimmed.length < 3) { - throw new UserError(strings.bot.common.errors.query_too_small); - } - - if (trimmed.length > 100) { - throw new UserError(strings.bot.common.errors.query_too_long); - } - - return await searchClansAcrossRegions(trimmed, limit); -} diff --git a/packages/bot/src/core/blitz/searchPlayersAcrossRegionsBotWrapper.ts b/packages/bot/src/core/blitz/searchPlayersAcrossRegionsBotWrapper.ts deleted file mode 100644 index 89e473a8d..000000000 --- a/packages/bot/src/core/blitz/searchPlayersAcrossRegionsBotWrapper.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { searchPlayersAcrossRegions } from '@blitzkit/core'; -import { Locale } from 'discord.js'; -import { UserError } from '../blitzkit/userError'; -import { translator } from '../localization/translator'; - -export async function searchPlayersAcrossRegionsBotWrapper( - search: string, - locale: Locale, - limit?: number, -) { - const { strings } = translator(locale); - const trimmed = search.trim(); - - if (trimmed.length < 3) { - throw new UserError(strings.bot.common.errors.query_too_small); - } - - if (trimmed.length > 100) { - throw new UserError(strings.bot.common.errors.query_too_long); - } - - return await searchPlayersAcrossRegions(trimmed, limit); -} diff --git a/packages/bot/src/core/blitzkit/buildTechTreeLine.ts b/packages/bot/src/core/blitzkit/buildTechTreeLine.ts deleted file mode 100644 index e8e1ed463..000000000 --- a/packages/bot/src/core/blitzkit/buildTechTreeLine.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { fetchTankDefinitions } from '@blitzkit/core'; -import { resolveAncestry } from './resolveAncestry'; - -export async function buildTechTreeLine(start: number, end: number) { - const tankDefinitions = await fetchTankDefinitions(); - const endTank = tankDefinitions.tanks[end]; - if (!endTank.ancestors) throw new Error('End tank has no ancestors'); - const line = [end]; - - if (start === end) return line; - for (const ancestorId of endTank.ancestors) { - if ((await resolveAncestry(ancestorId)).includes(start)) { - const ancestorTree = await buildTechTreeLine(start, ancestorId); - line.push(...ancestorTree); - break; - } - } - - return line; -} diff --git a/packages/bot/src/core/blitzkit/filtersToDescription.ts b/packages/bot/src/core/blitzkit/filtersToDescription.ts deleted file mode 100644 index a8c8377cd..000000000 --- a/packages/bot/src/core/blitzkit/filtersToDescription.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { fetchTankDefinitions, TIER_ROMAN_NUMERALS } from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { Locale } from "discord.js"; -import { type StatFilters } from "../blitzstars/filterStats"; -import { translator } from "../localization/translator"; - -export async function filtersToDescription( - { nation, tier, type, class: tankClass, tank }: StatFilters, - locale: Locale, -) { - const { strings, bkLocale } = translator(locale); - const awaitedTankDefinitions = await fetchTankDefinitions(); - const info: string[] = []; - - // TODO: somehow localize tank names? - if (tank) return awaitedTankDefinitions.tanks[tank].name!.locales[bkLocale]; - if (nation) - info.push((strings.common.nations as Record)[nation]); - if (tier) { - info.push( - literals(strings.bot.common.filters.tier, { - tier: TIER_ROMAN_NUMERALS[tier], - }), - ); - } - if (tankClass) info.push(strings.common.tank_class_short[tankClass]); - if (type) info.push(strings.common.tree_type[type]); - - return info.length === 0 ? strings.bot.common.filters.none : info.join(", "); -} diff --git a/packages/bot/src/core/blitzkit/getBlitzFromDiscord.ts b/packages/bot/src/core/blitzkit/getBlitzFromDiscord.ts deleted file mode 100644 index 16088c64b..000000000 --- a/packages/bot/src/core/blitzkit/getBlitzFromDiscord.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { idToRegion } from '@blitzkit/core'; -import { flagUserActivity } from '../db/flagUserActivity'; -import { usersDatabase } from '../db/users'; - -export async function getBlitzFromDiscord(discordId: bigint) { - const unique = await usersDatabase.discordUser.findUnique({ - where: { discord_id: discordId }, - }); - - if (unique === null) return null; - - const id = Number(unique.blitz_id); - - await flagUserActivity(id); - return { id, region: idToRegion(id) }; -} diff --git a/packages/bot/src/core/blitzkit/getPeriodNow.ts b/packages/bot/src/core/blitzkit/getPeriodNow.ts deleted file mode 100644 index 030f2aa94..000000000 --- a/packages/bot/src/core/blitzkit/getPeriodNow.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function getPeriodNow() { - return Date.now() / 1000; -} diff --git a/packages/bot/src/core/blitzkit/getPeriodStart.ts b/packages/bot/src/core/blitzkit/getPeriodStart.ts deleted file mode 100644 index aef8b4328..000000000 --- a/packages/bot/src/core/blitzkit/getPeriodStart.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { getTimeDaysAgo, type Region } from '@blitzkit/core'; -import type { PeriodSize } from '../discord/addPeriodSubCommands'; - -export function getPeriodStart(region: Region, period: PeriodSize) { - if (period === 'career') return 0; - if (period === 'today') return getTimeDaysAgo(region, 1); - return getTimeDaysAgo(region, parseInt(period)); -} diff --git a/packages/bot/src/core/blitzkit/iconPng.ts b/packages/bot/src/core/blitzkit/iconPng.ts deleted file mode 100644 index c575b07b7..000000000 --- a/packages/bot/src/core/blitzkit/iconPng.ts +++ /dev/null @@ -1,16 +0,0 @@ -import sharp from "sharp"; - -const cache: Record = {}; - -export async function iconPng(url: string) { - if (!url.endsWith(".webp")) return url; - - if (!cache[url]) { - const imageResponse = await fetch(url); - const imageBufferWEBP = await imageResponse.arrayBuffer(); - const imageBufferPNG = await sharp(imageBufferWEBP).png().toBuffer(); - cache[url] = imageBufferPNG; - } - - return `data:image/png;base64,${cache[url].toString("base64")}`; -} diff --git a/packages/bot/src/core/blitzkit/jsxToPng.ts b/packages/bot/src/core/blitzkit/jsxToPng.ts deleted file mode 100644 index bc8ae24d0..000000000 --- a/packages/bot/src/core/blitzkit/jsxToPng.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { jsxToSvg } from "./jsxToSvg"; -import { svgToPng } from "./svgToPng"; - -export async function jsxToPng(jsx: React.JSX.Element) { - const svg = await jsxToSvg(jsx); - const png = svgToPng(svg); - - return png; -} diff --git a/packages/bot/src/core/blitzkit/jsxToPngThreaded.ts b/packages/bot/src/core/blitzkit/jsxToPngThreaded.ts deleted file mode 100644 index 16ee14401..000000000 --- a/packages/bot/src/core/blitzkit/jsxToPngThreaded.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { jsxToSvg } from "./jsxToSvg"; -import { RenderConfiguration } from "./renderConfiguration"; -import { svgToPngThreaded } from "./svgToPngThreaded"; - -export async function jsxToPngThreaded( - jsx: React.JSX.Element, - renderConfiguration: RenderConfiguration -) { - const svg = await jsxToSvg(jsx, renderConfiguration); - const png = await svgToPngThreaded(svg); - return png; -} diff --git a/packages/bot/src/core/blitzkit/jsxToSvg/Roboto-Black.ttf b/packages/bot/src/core/blitzkit/jsxToSvg/Roboto-Black.ttf deleted file mode 100644 index 96ddf0003d64c4c3c2d5d3ff119de17c548c0410..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 129228 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 S;t$MGZ{&B8(NcpQ=YIi$MEq<3 diff --git a/packages/bot/src/core/blitzkit/jsxToSvg/Roboto-Bold.ttf b/packages/bot/src/core/blitzkit/jsxToSvg/Roboto-Bold.ttf deleted file mode 100644 index 7a876e28e8e43ac9ab91fe361228c5e4c4e5253e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 128676 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 TOP&AVyW^ewt}wmSK*#xSw!BZl diff --git a/packages/bot/src/core/blitzkit/jsxToSvg/index.ts b/packages/bot/src/core/blitzkit/jsxToSvg/index.ts deleted file mode 100644 index 40c0e7bb0..000000000 --- a/packages/bot/src/core/blitzkit/jsxToSvg/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { readFile } from "fs/promises"; -import satori from "satori"; -import { RenderConfiguration } from "../renderConfiguration"; -import robotoBlack from "./Roboto-Black.ttf"; -import robotoBold from "./Roboto-Bold.ttf"; -import roboto from "./Roboto.ttf"; - -const fontsPromise = [ - { file: roboto, weight: 400 as const }, - { file: robotoBold, weight: 700 as const }, - { file: robotoBlack, weight: 900 as const }, -].map(async ({ file, weight }) => { - const data = await readFile(`${__dirname}/${file}`); - return { name: "Roboto", weight, data }; -}); - -export async function jsxToSvg( - jsx: React.JSX.Element, - renderConfiguration = new RenderConfiguration() -) { - const fonts = await Promise.all(fontsPromise); - return await satori(jsx, { width: renderConfiguration.width, fonts }); -} diff --git a/packages/bot/src/core/blitzkit/nonBlockingPromises.ts b/packages/bot/src/core/blitzkit/nonBlockingPromises.ts deleted file mode 100644 index 647a955d0..000000000 --- a/packages/bot/src/core/blitzkit/nonBlockingPromises.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { - fetchAverageDefinitions, - fetchDiscoveredIdsDefinitions, - fetchGameDefinitions, - fetchMapDefinitions, - fetchTankDefinitions, - fetchTankNames, -} from "@blitzkit/core"; - -export const averageDefinitions = fetchAverageDefinitions(); -export const tankDefinitions = fetchTankDefinitions(); -export const gameDefinitions = fetchGameDefinitions(); -export const tankNames = fetchTankNames(); -export const mapDefinitions = fetchMapDefinitions(); -export const discoveredIdsDefinitions = fetchDiscoveredIdsDefinitions(); diff --git a/packages/bot/src/core/blitzkit/processes.ts b/packages/bot/src/core/blitzkit/processes.ts deleted file mode 100644 index a639e25cc..000000000 --- a/packages/bot/src/core/blitzkit/processes.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { usersDatabase } from "../db/users"; -import { client } from "../discord/client"; - -const EXIT_EVENTS = ["SIGINT", "SIGTERM", "SIGUSR1", "SIGUSR2"]; - -let cleaningUp = false; - -async function cleanup(signal: string) { - if (cleaningUp) return; - cleaningUp = true; - - console.log(`Cleaning up... (${signal})`); - - try { - await Promise.all([usersDatabase.$disconnect(), client.destroy()]); - } catch (err) { - console.error("Cleanup error:", err); - } - - console.log("Gracefully exiting..."); - - process.exit(0); -} - -export function registerProcesses() { - process.on("uncaughtException", (err) => { - console.error("uncaughtException:", err); - }); - - process.on("unhandledRejection", (err) => { - console.error("unhandledRejection:", err); - }); - - client.on("error", console.error); - - EXIT_EVENTS.forEach((event) => process.on(event, () => cleanup(event))); -} diff --git a/packages/bot/src/core/blitzkit/renderConfiguration.ts b/packages/bot/src/core/blitzkit/renderConfiguration.ts deleted file mode 100644 index 508e20591..000000000 --- a/packages/bot/src/core/blitzkit/renderConfiguration.ts +++ /dev/null @@ -1,11 +0,0 @@ -export interface RenderConfiguration { - width: number; -} - -export class RenderConfiguration implements RenderConfiguration { - width = 480; - - constructor(configuration?: Partial) { - Object.assign(this, configuration); - } -} diff --git a/packages/bot/src/core/blitzkit/resolveAncestry.ts b/packages/bot/src/core/blitzkit/resolveAncestry.ts deleted file mode 100644 index e5ac8b5db..000000000 --- a/packages/bot/src/core/blitzkit/resolveAncestry.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { fetchTankDefinitions } from '@blitzkit/core'; - -export async function resolveAncestry(id: number) { - const tankDefinitions = await fetchTankDefinitions(); - const tank = tankDefinitions.tanks[id]; - const ancestors: number[] = []; - - if (tank.ancestors) { - for (const ancestor of tank.ancestors) { - if (!ancestors.includes(ancestor)) { - ancestors.push(ancestor); - - for (const ancestorLayer2 of await resolveAncestry(ancestor)) { - if (!ancestors.includes(ancestorLayer2)) { - ancestors.push(ancestorLayer2); - } - } - } - } - } - - return ancestors.sort( - (a, b) => tankDefinitions.tanks[b].tier - tankDefinitions.tanks[a].tier, - ); -} diff --git a/packages/bot/src/core/blitzkit/svgToPng.ts b/packages/bot/src/core/blitzkit/svgToPng.ts deleted file mode 100644 index 06ace7446..000000000 --- a/packages/bot/src/core/blitzkit/svgToPng.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Resvg } from '@resvg/resvg-js'; - -export function svgToPng(svg: string) { - return new Resvg(svg).render().asPng(); -} diff --git a/packages/bot/src/core/blitzkit/svgToPngThreaded.ts b/packages/bot/src/core/blitzkit/svgToPngThreaded.ts deleted file mode 100644 index 344fb4627..000000000 --- a/packages/bot/src/core/blitzkit/svgToPngThreaded.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Worker } from 'worker_threads'; - -const RENDER_WORKERS = 1; - -const workers = [...Array(RENDER_WORKERS)].map(() => { - const worker = new Worker('dist/bot/workers/render.js'); - console.log(`🟡 Launching render worker ${worker.threadId}`); - return worker; -}); -const queue: { - svg: string; - resolve: (png: Buffer) => void; -}[] = []; - -function manageQueue() { - if (queue.length > 0 && workers.length > 0) { - const { svg, resolve } = queue.shift()!; - const worker = workers.shift()!; - - function cleanup() { - workers.push(worker); - worker.off('message', handleMessage); - worker.off('error', handleError); - manageQueue(); - } - function handleMessage(png: Uint8Array) { - cleanup(); - resolve(Buffer.from(png)); - } - function handleError(error: Error) { - cleanup(); - throw error; - } - - worker.postMessage(svg); - worker.on('message', handleMessage); - worker.on('error', handleError); - } -} - -export function svgToPngThreaded(svg: string) { - return new Promise((resolve) => { - queue.push({ svg, resolve }); - manageQueue(); - }); -} diff --git a/packages/bot/src/core/blitzkit/tankIconPng.ts b/packages/bot/src/core/blitzkit/tankIconPng.ts deleted file mode 100644 index a15b09c8b..000000000 --- a/packages/bot/src/core/blitzkit/tankIconPng.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { tankIcon } from '@blitzkit/core'; -import { iconPng } from './iconPng'; - -export function tankIconPng(id: number) { - return iconPng(tankIcon(id)); -} diff --git a/packages/bot/src/core/blitzkit/userError.ts b/packages/bot/src/core/blitzkit/userError.ts deleted file mode 100644 index 44a647f15..000000000 --- a/packages/bot/src/core/blitzkit/userError.ts +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Discriminator for user caused errors to not report in logs - */ -export class UserError { - constructor(public message: string) {} -} diff --git a/packages/bot/src/core/blitzstars/filterStats.ts b/packages/bot/src/core/blitzstars/filterStats.ts deleted file mode 100644 index 2936170a9..000000000 --- a/packages/bot/src/core/blitzstars/filterStats.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { TankClass, TankType } from "@blitzkit/core"; -import type { SupplementaryStats } from "@blitzkit/core/src/blitz/getAccountInfo"; -import { calculateWN8 } from "@blitzkit/core/src/statistics/calculateWN8"; -import type { BlitzStats } from "@blitzkit/core/src/statistics/compositeStats"; -import { sumAllStats } from "@blitzkit/core/src/statistics/sumAllStats"; -import { - averageDefinitions, - tankDefinitions, -} from "../blitzkit/nonBlockingPromises"; - -export interface StatFilters { - nation?: string; - tier?: number; - class?: TankClass; - type?: TankType; - tank?: number; -} - -export async function filterStats( - { diff, order }: DiffedTankStats, - filters: StatFilters, -) { - // tank, if provided, takes priority over all other filters - if (filters.tank) filters = { tank: filters.tank }; - - const awaitedTankDefinitions = await tankDefinitions; - const awaitedTankAverages = await averageDefinitions; - const filteredOrder = order.filter((id) => { - const entry = awaitedTankDefinitions.tanks[id]; - - return ( - entry && - (filters.nation === undefined || entry.nation === filters.nation) && - (filters.tier === undefined || entry.tier === filters.tier) && - (filters.class === undefined || entry.class === filters.class) && - (filters.type === undefined || - (filters.type === TankType.TANK_TYPE_COLLECTOR && - entry.type === TankType.TANK_TYPE_COLLECTOR) || - (filters.type === TankType.TANK_TYPE_PREMIUM && - entry.type === TankType.TANK_TYPE_PREMIUM) || - (filters.type === TankType.TANK_TYPE_RESEARCHABLE && - entry.type === TankType.TANK_TYPE_RESEARCHABLE)) && - (filters.tank === undefined || entry.id === filters.tank) - ); - }); - const stats = sumAllStats(filteredOrder.map((id) => diff[id])); - const battlesOfTanksWithAverages = filteredOrder.reduce( - (accumulator, id) => - awaitedTankAverages.averages[id] - ? accumulator + diff[id].battles - : accumulator, - 0, - ); - const battlesOfTanksWithTankDefinition = filteredOrder.reduce( - (accumulator, id) => - awaitedTankDefinitions.tanks[id] - ? accumulator + diff[id].battles - : accumulator, - 0, - ); - const supplementary = { - WN8: - filteredOrder.reduce( - (accumulator, id) => - awaitedTankAverages.averages[id] - ? accumulator + - calculateWN8(awaitedTankAverages.averages[id].mu, diff[id]) * - diff[id].battles - : accumulator, - 0, - ) / battlesOfTanksWithAverages, - tier: - filteredOrder.reduce( - (accumulator, id) => - awaitedTankDefinitions.tanks[id] - ? accumulator + - awaitedTankDefinitions.tanks[id]!.tier * diff[id].battles - : accumulator, - 0, - ) / battlesOfTanksWithTankDefinition, - } satisfies SupplementaryStats; - - return { stats, supplementary, filteredOrder }; -} - -export interface DiffedTankStats { - diff: Record; - order: number[]; -} diff --git a/packages/bot/src/core/blitzstars/getBlitzStarsLinkButton.ts b/packages/bot/src/core/blitzstars/getBlitzStarsLinkButton.ts deleted file mode 100644 index 9a17855e7..000000000 --- a/packages/bot/src/core/blitzstars/getBlitzStarsLinkButton.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { getAccountInfo, type Region } from "@blitzkit/core"; -import { buttonLink } from "../discord/buttonLink"; - -export async function getBlitzStarsLinkButton( - region: Region, - id: number, - tankId?: number -) { - const { nickname } = await getAccountInfo(region, id); - - return buttonLink( - `https://www.blitzstars.com/player/${region}/${nickname}${ - tankId ? `/tank/${tankId!}` : "" - }`, - "BlitzStars" - ); -} diff --git a/packages/bot/src/core/blitzstars/getPlayerHistories.ts b/packages/bot/src/core/blitzstars/getPlayerHistories.ts deleted file mode 100644 index 0434b8116..000000000 --- a/packages/bot/src/core/blitzstars/getPlayerHistories.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { type Region, emptyAllStats, getAccountInfo } from '@blitzkit/core'; -import { - type GetHistoriesOptions, - type Histories, - type History, - getHistoriesDefaultOptions, -} from '../../types/histories'; -import type { PlayerStats } from './getPlayerStats'; - -export async function getPlayerHistories( - server: Region, - id: number, - options?: Partial, -) { - const mergedOptions = { ...getHistoriesDefaultOptions, ...options }; - const response = await fetch( - `https://www.blitzstars.com/api/playerstats/${id}`, - ); - const playerHistories = ((await response.json()) as PlayerStats).map( - (history) => - ({ - all: history.statistics.all, - last_battle_time: history.last_battle_time, - }) satisfies History, - ); - let lastNonCompliantIndex = -1; - let hasCompliedOnce = false; - const filtered = playerHistories.filter(({ last_battle_time }, index) => { - const complies = - last_battle_time >= mergedOptions.start && - last_battle_time <= mergedOptions.end; - - if (complies && !hasCompliedOnce) hasCompliedOnce = true; - if (!hasCompliedOnce) lastNonCompliantIndex = index; - - return complies; - }); - - const latestNodes: Histories = []; - - if (mergedOptions.includeLatestHistories) { - const { last_battle_time, statistics } = await getAccountInfo(server, id); - - latestNodes.push({ all: statistics.all, last_battle_time }); - } - - const previousNodes: Histories = []; - - if (mergedOptions.includePreviousHistories) { - if (lastNonCompliantIndex !== -1) { - previousNodes.push( - playerHistories[lastNonCompliantIndex] ?? - ({ - all: emptyAllStats, - last_battle_time: -Infinity, - } satisfies History), - ); - } - } - - return [...latestNodes, ...filtered, ...previousNodes] satisfies Histories; -} diff --git a/packages/bot/src/core/blitzstars/getPlayerStats.ts b/packages/bot/src/core/blitzstars/getPlayerStats.ts deleted file mode 100644 index 1555bc147..000000000 --- a/packages/bot/src/core/blitzstars/getPlayerStats.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { BlitzStats, Region } from "@blitzkit/core"; - -interface IndividualPlayerStats { - clan: { clan_id: number; name: string; tag: string }; - statistics: { - all: BlitzStats; - wn8: number; - wn7: number; - average_tier: number; - }; - last_battle_time: number; - nickname: string; - account_id: number; - achievements: { - account_id: number; - [key: string]: number; - }; - region: Region; -} - -export type PlayerStats = IndividualPlayerStats[]; - -export async function getPlayerStats< - Ids extends number | number[], - ReturnType = Ids extends number ? PlayerStats : PlayerStats[], ->(region: Region, ids: Ids) { - const stats = await Promise.all( - ((typeof ids === "number" ? [ids] : ids) as number[]).map((id) => - fetch(`https://www.blitzstars.com/api/playerstats/${id}`).then( - (response) => response.json() as Promise - ) - ) - ); - - if (typeof ids === "number") return stats[0] as ReturnType; - return stats as ReturnType; -} diff --git a/packages/bot/src/core/blitzstars/getStatsInPeriod.ts b/packages/bot/src/core/blitzstars/getStatsInPeriod.ts deleted file mode 100644 index 861f5c8a5..000000000 --- a/packages/bot/src/core/blitzstars/getStatsInPeriod.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { type BlitzStats, emptyAllStats, type Region } from "@blitzkit/core"; -import { Locale } from "discord.js"; -import type { DiffedTankStats } from "./filterStats"; -import { - getTankHistories, - type TankHistories, - type TankHistoryRaw, -} from "./getTankHistories"; - -const emptyTankHistoryNode: TankHistoryRaw = { - account_id: 0, - battle_life_time: 0, - last_battle_time: 0, - mark_of_mastery: 0, - tank_id: 0, - all: emptyAllStats, -}; - -// BIG TODO: merge this neatly with diffNormalizedTankStats -export async function getStatsInPeriod( - server: Region, - id: number, - start: number, - end: number, - locale: Locale -) { - const history = await getTankHistories(server, id, locale, { - includeLatestHistories: true, - }); - - // sort them even though most won't be used - const tankSortedHistory: Record = {}; - const tanks: number[] = []; - history.forEach((node) => { - if (!tankSortedHistory[node.tank_id]) { - tankSortedHistory[node.tank_id] = [node]; - tanks.push(node.tank_id); - } else { - tankSortedHistory[node.tank_id].push(node); - } - }); - - // find the ones played within the range - const inRangeTanks = tanks.filter((tankId) => - tankSortedHistory[tankId].some( - (node) => node.last_battle_time > start && node.last_battle_time < end - ) - ); - const playedTanks: number[] = []; - - // fetch history node right before "start" and right before "end" - const diff: Record = {}; - inRangeTanks.forEach((tankId) => { - const sortedHistory = tankSortedHistory[tankId]; - const previousIndex = - sortedHistory.findIndex((node) => node.last_battle_time > start) - 1; - const latestIndex = - sortedHistory.findIndex((node) => node.last_battle_time > end) - 1; - const previous = sortedHistory[previousIndex] ?? emptyTankHistoryNode; - const latest = - sortedHistory[latestIndex] ?? sortedHistory[sortedHistory.length - 1]; - - function d(get: (allStats: BlitzStats) => number) { - return get(latest.all) - get(previous.all); - } - - // check if there was a change in battles as games in rating do update last_battle_time - if (d((a) => a.battles) > 0) { - diff[tankId] = { - battles: d((a) => a.battles), - capture_points: d((a) => a.capture_points), - damage_dealt: d((a) => a.damage_dealt), - damage_received: d((a) => a.damage_received), - dropped_capture_points: d((a) => a.dropped_capture_points), - frags: d((a) => a.frags), - frags8p: d((a) => a.frags8p), - hits: d((a) => a.hits), - losses: d((a) => a.losses), - max_frags: latest.all.max_frags, - max_xp: latest.all.max_xp, - shots: d((a) => a.shots), - spotted: d((a) => a.spotted), - survived_battles: d((a) => a.survived_battles), - win_and_survived: d((a) => a.win_and_survived), - wins: d((a) => a.wins), - xp: d((a) => a.xp), - }; - - playedTanks.push(tankId); - } - }); - - const order = playedTanks.sort((a, b) => { - const aHistories = tankSortedHistory[a]; - const bHistories = tankSortedHistory[b]; - const aLatest = aHistories[aHistories.length - 1]; - const bLatest = bHistories[bHistories.length - 1]; - - return bLatest.last_battle_time - aLatest.last_battle_time; - }); - - return { diff, order } satisfies DiffedTankStats; -} diff --git a/packages/bot/src/core/blitzstars/getTankHistories.ts b/packages/bot/src/core/blitzstars/getTankHistories.ts deleted file mode 100644 index a8cbb9f95..000000000 --- a/packages/bot/src/core/blitzstars/getTankHistories.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { type Region, emptyAllStats, getTankStats } from "@blitzkit/core"; -import { Locale } from "discord.js"; -import { - type GetHistoriesOptions, - type Histories, - type History, - getHistoriesDefaultOptions, -} from "../../types/histories"; -import { UserError } from "../blitzkit/userError"; -import { translator } from "../localization/translator"; - -export interface TankHistory extends History { - tank_id: number; -} - -export type TankHistories = TankHistory[]; - -export interface TankHistoryRaw extends TankHistory { - mark_of_mastery: number; - battle_life_time: number; - account_id: number; -} - -export type TankHistoriesRaw = TankHistoryRaw[]; - -export interface GetTankHistoriesOptions extends GetHistoriesOptions { - tankId?: number; -} - -const getTankHistoriesDefaultOptions: GetTankHistoriesOptions = { - ...getHistoriesDefaultOptions, - tankId: undefined, -}; - -export async function getTankHistories( - server: Region, - id: number, - locale: Locale, - options?: Partial -) { - const { strings } = translator(locale); - const mergedOptions = { ...getTankHistoriesDefaultOptions, ...options }; - const tankHistoriesResponse = await fetch( - `https://www.blitzstars.com/api/tankhistories/for/${id}/` - ); - const tankHistories = ( - (await tankHistoriesResponse.json()) as TankHistoriesRaw - ).map( - (history) => - ({ - all: history.all, - last_battle_time: history.last_battle_time, - tank_id: history.tank_id, - }) satisfies TankHistory - ); - const compliantTanks: number[] = []; - const lastBattles: Record = {}; - const lastNonCompliantIndex: Record = {}; - const hasCompliedOnce: Record = {}; - const filtered = tankHistories.filter( - ({ last_battle_time, tank_id, all: { battles } }, index) => { - const complies = - last_battle_time >= mergedOptions.start && - last_battle_time <= mergedOptions.end && - (mergedOptions.tankId === undefined || - tank_id === mergedOptions.tankId); - - if (complies && !hasCompliedOnce[tank_id]) { - hasCompliedOnce[tank_id] = true; - compliantTanks.push(tank_id); - } - if (!hasCompliedOnce[tank_id]) lastNonCompliantIndex[tank_id] = index; - lastBattles[tank_id] = battles; - - return complies; - } - ); - - const latestNodes: TankHistories = []; - - if (mergedOptions.includeLatestHistories) { - const tankStats = await getTankStats(server, id); - - if (tankStats === null) { - throw new UserError(strings.bot.common.errors.no_tank_stats); - } - - tankStats.forEach((tankHistory) => { - if ( - tankHistory.all.battles > (lastBattles[tankHistory.tank_id] ?? 0) && - (mergedOptions.tankId === undefined || - tankHistory.tank_id === mergedOptions.tankId) - ) { - if (!hasCompliedOnce[tankHistory.tank_id]) { - // was played today for the first time... I think??? - hasCompliedOnce[tankHistory.tank_id] = true; - compliantTanks.push(tankHistory.tank_id); - } - latestNodes.push(tankHistory); - } - }); - } - - const previousNodes: TankHistories = []; - - if (mergedOptions.includePreviousHistories) { - compliantTanks.forEach((tankId) => { - if (tankId in lastNonCompliantIndex) { - previousNodes.push( - tankHistories[lastNonCompliantIndex[tankId]] ?? - ({ - all: emptyAllStats, - last_battle_time: -Infinity, - tank_id: tankId, - } satisfies TankHistory) - ); - } - }); - } - - return [...previousNodes, ...filtered, ...latestNodes] satisfies Histories; -} diff --git a/packages/bot/src/core/db/flagUserActivity.ts b/packages/bot/src/core/db/flagUserActivity.ts deleted file mode 100644 index 4fa958410..000000000 --- a/packages/bot/src/core/db/flagUserActivity.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { usersDatabase } from './users'; - -export async function flagUserActivity(blitzId: number) { - await usersDatabase.user.upsert({ - where: { blitz_id: blitzId }, - update: {}, - create: { blitz_id: blitzId }, - }); -} diff --git a/packages/bot/src/core/db/linkBlitzAndDiscord.ts b/packages/bot/src/core/db/linkBlitzAndDiscord.ts deleted file mode 100644 index 229aaeb1c..000000000 --- a/packages/bot/src/core/db/linkBlitzAndDiscord.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { flagUserActivity } from './flagUserActivity'; -import { usersDatabase } from './users'; - -export async function linkBlitzAndDiscord(discordId: bigint, blitzId: number) { - await usersDatabase.discordUser.upsert({ - where: { discord_id: discordId }, - update: { blitz_id: blitzId }, - create: { discord_id: discordId, blitz_id: blitzId }, - }); - await flagUserActivity(blitzId); -} diff --git a/packages/bot/src/core/db/users.ts b/packages/bot/src/core/db/users.ts deleted file mode 100644 index bfa600844..000000000 --- a/packages/bot/src/core/db/users.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { PrismaClient } from '../../prisma'; - -export const usersDatabase = new PrismaClient(); diff --git a/packages/bot/src/core/discord/addClanChoices.ts b/packages/bot/src/core/discord/addClanChoices.ts deleted file mode 100644 index 328ee5c34..000000000 --- a/packages/bot/src/core/discord/addClanChoices.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Locale, SlashCommandStringOption } from 'discord.js'; -import { translator } from '../localization/translator'; -import { localizationObject } from './localizationObject'; - -export function addClanChoices(option: SlashCommandStringOption) { - const { strings } = translator(Locale.EnglishUS); - - return option - .setName('clan') - .setNameLocalizations( - localizationObject( - (strings) => strings.bot.common.options.clan.name, - undefined, - true, - ), - ) - .setDescription(strings.bot.common.options.clan.description) - .setDescriptionLocalizations( - localizationObject( - (strings) => strings.bot.common.options.clan.description, - ), - ) - .setAutocomplete(true) - .setMinLength(2) - .setRequired(true); -} diff --git a/packages/bot/src/core/discord/addFilterOptions.ts b/packages/bot/src/core/discord/addFilterOptions.ts deleted file mode 100644 index 7e1efd606..000000000 --- a/packages/bot/src/core/discord/addFilterOptions.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { - TANK_CLASSES, - TANK_TYPE_COMMAND_NAMES, - TANK_TYPES, -} from '@blitzkit/core'; -import { - type APIApplicationCommandOptionChoice, - Locale, - SlashCommandBuilder, - SlashCommandSubcommandBuilder, -} from 'discord.js'; -import { translator } from '../localization/translator'; -import { addTankChoices } from './addTankChoices'; -import { addTierChoices } from './addTierChoices'; -import { localizationObject } from './localizationObject'; - -export function addFilterOptions< - Option extends SlashCommandBuilder | SlashCommandSubcommandBuilder, ->(option: Option, nations: string[]) { - const { strings } = translator(Locale.EnglishUS); - - return ( - option - .addStringOption((option) => - option - .setName(strings.bot.common.options.filters.nation.name) - .setNameLocalizations( - localizationObject( - (strings) => strings.bot.common.options.filters.nation.name, - undefined, - true, - ), - ) - .setDescription(strings.bot.common.options.filters.nation.description) - .setDescriptionLocalizations( - localizationObject( - (strings) => - strings.bot.common.options.filters.nation.description, - ), - ) - .addChoices( - ...nations.map( - (nation) => - ({ - value: nation, - name: (strings.common.nations as Record)[ - nation - ], - name_localizations: localizationObject( - (strings) => - (strings.common.nations as Record)[ - nation - ], - ), - }) satisfies APIApplicationCommandOptionChoice, - ), - ) - .setRequired(false), - ) - .addStringOption((option) => addTierChoices(option).setRequired(false)) - .addStringOption((option) => - option - .setName(strings.bot.common.options.filters.class.name) - .setNameLocalizations( - localizationObject( - (strings) => strings.bot.common.options.filters.class.name, - undefined, - true, - ), - ) - .setDescription(strings.bot.common.options.filters.class.description) - .setDescriptionLocalizations( - localizationObject( - (strings) => strings.bot.common.options.filters.class.description, - ), - ) - .addChoices( - ...TANK_CLASSES.map( - (tankType) => - ({ - value: `${tankType}`, - name: strings.common.tank_class_short[tankType], - name_localizations: localizationObject( - (strings) => strings.common.tank_class_short[tankType], - ), - }) satisfies APIApplicationCommandOptionChoice, - ), - ) - .setRequired(false), - ) - .addStringOption((option) => - option - .setName(strings.bot.common.options.filters.type.name) - .setNameLocalizations( - localizationObject( - (strings) => strings.bot.common.options.filters.type.name, - undefined, - true, - ), - ) - .setDescription(strings.bot.common.options.filters.type.description) - .setDescriptionLocalizations( - localizationObject( - (strings) => strings.bot.common.options.filters.type.description, - ), - ) - .addChoices( - ...TANK_TYPES.map( - (treeType) => - ({ - value: TANK_TYPE_COMMAND_NAMES[treeType], - name: strings.common.tree_type[treeType], - name_localizations: localizationObject( - (strings) => strings.common.tree_type[treeType], - ), - }) satisfies APIApplicationCommandOptionChoice, - ), - ) - .setRequired(false), - ) - // TODO: somehow localize this? - .addStringOption((option) => - addTankChoices(option).setRequired(false), - ) as Option - ); -} diff --git a/packages/bot/src/core/discord/addPeriodSubCommands.ts b/packages/bot/src/core/discord/addPeriodSubCommands.ts deleted file mode 100644 index 94cf335d4..000000000 --- a/packages/bot/src/core/discord/addPeriodSubCommands.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { - Locale, - SlashCommandBuilder, - SlashCommandSubcommandBuilder, - SlashCommandSubcommandGroupBuilder, -} from 'discord.js'; -import { translator } from '../localization/translator'; -import { localizationObject } from './localizationObject'; - -export type PeriodType = 'today' | '30' | '60' | '90' | 'career' | 'custom'; -export type PeriodSize = 'today' | `${number}` | 'career'; - -export function addPeriodSubCommands( - option: SlashCommandBuilder | SlashCommandSubcommandGroupBuilder, - extra: ( - option: SlashCommandSubcommandBuilder, - ) => SlashCommandSubcommandBuilder = (option) => option, -) { - const { strings } = translator(Locale.EnglishUS); - const subcommandNameLocalizations = { - today: localizationObject( - (strings) => strings.bot.common.subcommands.today.name, - undefined, - true, - ), - 30: localizationObject( - (strings) => strings.bot.common.subcommands[30].name, - undefined, - true, - ), - 60: localizationObject( - (strings) => strings.bot.common.subcommands[60].name, - undefined, - true, - ), - 90: localizationObject( - (strings) => strings.bot.common.subcommands[90].name, - undefined, - true, - ), - career: localizationObject( - (strings) => strings.bot.common.subcommands.career.name, - undefined, - true, - ), - custom: localizationObject( - (strings) => strings.bot.common.subcommands.custom.name, - undefined, - true, - ), - }; - const subcommandDescriptionLocalizations = { - today: localizationObject( - (strings) => strings.bot.common.subcommands.today.description, - ), - 30: localizationObject( - (strings) => strings.bot.common.subcommands[30].description, - ), - 60: localizationObject( - (strings) => strings.bot.common.subcommands[60].description, - ), - 90: localizationObject( - (strings) => strings.bot.common.subcommands[90].description, - ), - career: localizationObject( - (strings) => strings.bot.common.subcommands.career.description, - ), - custom: localizationObject( - (strings) => strings.bot.common.subcommands.custom.description, - ), - }; - - option - .addSubcommand((option) => - extra( - option - .setName(strings.bot.common.subcommands.today.name) - .setNameLocalizations(subcommandNameLocalizations.today) - .setDescription(strings.bot.common.subcommands.today.description) - .setDescriptionLocalizations( - subcommandDescriptionLocalizations.today, - ), - ), - ) - .addSubcommand((option) => - extra( - option - .setName(strings.bot.common.subcommands[30].name) - .setNameLocalizations(subcommandNameLocalizations[30]) - .setDescription(strings.bot.common.subcommands[30].description) - .setDescriptionLocalizations(subcommandDescriptionLocalizations[30]), - ), - ) - .addSubcommand((option) => - extra( - option - .setName(strings.bot.common.subcommands[60].name) - .setNameLocalizations(subcommandNameLocalizations[60]) - .setDescription(strings.bot.common.subcommands[60].description) - .setDescriptionLocalizations(subcommandDescriptionLocalizations[60]), - ), - ) - .addSubcommand((option) => - extra( - option - .setName(strings.bot.common.subcommands[90].name) - .setNameLocalizations(subcommandNameLocalizations[90]) - .setDescription(strings.bot.common.subcommands[90].description) - .setDescriptionLocalizations(subcommandDescriptionLocalizations[90]), - ), - ) - .addSubcommand((option) => - extra( - option - .setName(strings.bot.common.subcommands.career.name) - .setNameLocalizations(subcommandNameLocalizations.career) - .setDescription(strings.bot.common.subcommands.career.description) - .setDescriptionLocalizations( - subcommandDescriptionLocalizations.career, - ), - ), - ) - .addSubcommand((option) => - extra( - option - .setName(strings.bot.common.subcommands.custom.name) - .setNameLocalizations(subcommandNameLocalizations.custom) - .setDescription(strings.bot.common.subcommands.custom.description) - .setDescriptionLocalizations( - subcommandDescriptionLocalizations.custom, - ) - .addIntegerOption((option) => - option - .setName(strings.bot.common.subcommands.custom.options.start.name) - .setDescription( - strings.bot.common.subcommands.custom.options.start.description, - ) - .setRequired(true), - ) - .addIntegerOption((option) => - option - .setName(strings.bot.common.subcommands.custom.options.end.name) - .setDescription( - strings.bot.common.subcommands.custom.options.end.description, - ) - .setRequired(true), - ), - ), - ); -} diff --git a/packages/bot/src/core/discord/addPeriodicFilterOptions.ts b/packages/bot/src/core/discord/addPeriodicFilterOptions.ts deleted file mode 100644 index 8e9d2f04b..000000000 --- a/packages/bot/src/core/discord/addPeriodicFilterOptions.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { - SlashCommandBuilder, - SlashCommandSubcommandBuilder, - SlashCommandSubcommandGroupBuilder, -} from 'discord.js'; -import { gameDefinitions } from '../blitzkit/nonBlockingPromises'; -import { addFilterOptions } from './addFilterOptions'; -import { addPeriodSubCommands } from './addPeriodSubCommands'; - -export async function addPeriodicFilterOptions< - OptionType extends SlashCommandBuilder | SlashCommandSubcommandGroupBuilder, ->( - option: OptionType, - extra: ( - option: SlashCommandSubcommandBuilder, - ) => SlashCommandSubcommandBuilder = (option) => option, -): Promise { - const awaitedGameDefinitions = await gameDefinitions; - - addPeriodSubCommands(option, (option) => - addFilterOptions(extra(option), awaitedGameDefinitions.nations), - ); - - return option; -} diff --git a/packages/bot/src/core/discord/addRegionChoices.ts b/packages/bot/src/core/discord/addRegionChoices.ts deleted file mode 100644 index 38adf10c9..000000000 --- a/packages/bot/src/core/discord/addRegionChoices.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { REGIONS } from "@blitzkit/core"; -import { - type APIApplicationCommandOptionChoice, - Locale, - SlashCommandStringOption, -} from "discord.js"; -import { translator } from "../localization/translator"; -import { localizationObject } from "./localizationObject"; - -export function addRegionChoices(option: SlashCommandStringOption) { - const { strings } = translator(Locale.EnglishUS); - - return option - .setName(strings.bot.common.options.region.name) - .setNameLocalizations( - localizationObject( - (strings) => strings.bot.common.options.region.name, - undefined, - true - ) - ) - .setDescription(strings.bot.common.options.region.description) - .setDescriptionLocalizations( - localizationObject( - (strings) => strings.bot.common.options.region.description - ) - ) - .addChoices( - ...REGIONS.map( - (region) => - ({ - value: region, - name: strings.common.regions.normal[region], - name_localizations: localizationObject( - (strings) => strings.common.regions.normal[region] - ), - }) satisfies APIApplicationCommandOptionChoice - ) - ) - .setRequired(true); -} diff --git a/packages/bot/src/core/discord/addTankChoices.ts b/packages/bot/src/core/discord/addTankChoices.ts deleted file mode 100644 index 3b38e220b..000000000 --- a/packages/bot/src/core/discord/addTankChoices.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Locale, SlashCommandStringOption } from "discord.js"; -import { translator } from "../localization/translator"; -import { localizationObject } from "./localizationObject"; - -export function addTankChoices(option: SlashCommandStringOption) { - const { strings } = translator(Locale.EnglishUS); - - return option - .setName(strings.bot.common.options.tank.name!) - .setNameLocalizations( - localizationObject( - (strings) => strings.bot.common.options.tank.name, - undefined, - true, - ), - ) - .setDescription(strings.bot.common.options.tank.description) - .setDescriptionLocalizations( - localizationObject( - (strings) => strings.bot.common.options.tank.description, - ), - ) - .setAutocomplete(true) - .setRequired(true); -} diff --git a/packages/bot/src/core/discord/addTierChoices.ts b/packages/bot/src/core/discord/addTierChoices.ts deleted file mode 100644 index 40ac51671..000000000 --- a/packages/bot/src/core/discord/addTierChoices.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { numberToRomanNumeral } from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { - APIApplicationCommandOptionChoice, - Locale, - SlashCommandStringOption, -} from "discord.js"; -import { range } from "lodash-es"; -import { translator } from "../localization/translator"; -import { localizationObject } from "./localizationObject"; - -export function addTierChoices(option: SlashCommandStringOption) { - const { strings } = translator(Locale.EnglishUS); - - return option - .setName(strings.bot.common.options.tier.name) - .setNameLocalizations( - localizationObject( - (strings) => strings.bot.common.options.tier.name, - undefined, - true - ) - ) - .setDescription(strings.bot.common.options.tier.description) - .setDescriptionLocalizations( - localizationObject( - (strings) => strings.bot.common.options.tier.description - ) - ) - .setChoices( - ...range(10, 0).map( - (tier) => - ({ - name: literals(strings.bot.common.options.tier.choices.tier, { - tier_1: tier, - tier_2: numberToRomanNumeral(tier), - }), - value: `${tier}`, - name_localizations: localizationObject( - (strings) => strings.bot.common.options.tier.choices.tier, - { tier_1: tier, tier_2: numberToRomanNumeral(tier) } - ), - }) satisfies APIApplicationCommandOptionChoice - ) - ) - .setRequired(true); -} diff --git a/packages/bot/src/core/discord/addUsernameChoices.ts b/packages/bot/src/core/discord/addUsernameChoices.ts deleted file mode 100644 index 35d851fdd..000000000 --- a/packages/bot/src/core/discord/addUsernameChoices.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Locale, SlashCommandStringOption } from 'discord.js'; -import { translator } from '../localization/translator'; -import { localizationObject } from './localizationObject'; - -export function addUsernameChoices(option: SlashCommandStringOption) { - const { strings } = translator(Locale.EnglishUS); - - return option - .setName(strings.bot.common.options.username.name) - .setNameLocalizations( - localizationObject( - (strings) => strings.bot.common.options.username.name, - undefined, - true, - ), - ) - .setDescription(strings.bot.common.options.username.description) - .setDescriptionLocalizations( - localizationObject( - (strings) => strings.bot.common.options.username.description, - ), - ) - .setAutocomplete(true); -} diff --git a/packages/bot/src/core/discord/autocompleteClan/constants.ts b/packages/bot/src/core/discord/autocompleteClan/constants.ts deleted file mode 100644 index 26b7f01f6..000000000 --- a/packages/bot/src/core/discord/autocompleteClan/constants.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const DISCORD_CHOICES_MAX_NAME_SIZE = 25; -export const OVERFLOW_SUFFIX = '…'; diff --git a/packages/bot/src/core/discord/autocompleteClan/index.ts b/packages/bot/src/core/discord/autocompleteClan/index.ts deleted file mode 100644 index 5ed5d8d3f..000000000 --- a/packages/bot/src/core/discord/autocompleteClan/index.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { searchClansAcrossRegions } from '@blitzkit/core'; -import { - ApplicationCommandOptionChoiceData, - AutocompleteInteraction, - CacheType, -} from 'discord.js'; -import { translator } from '../../localization/translator'; -import { DISCORD_CHOICES_MAX_NAME_SIZE, OVERFLOW_SUFFIX } from './constants'; - -export async function autocompleteClan( - interaction: AutocompleteInteraction, -) { - const { strings } = translator(interaction.locale); - const focusedOption = interaction.options.getFocused(true); - if (focusedOption.name !== 'clan') return; - if (focusedOption.value.length < 2) return interaction.respond([]); - const clans = await searchClansAcrossRegions(focusedOption.value); - - await interaction.respond( - clans - ? clans.map((clan) => { - let name = `${clan.name} (${strings.common.regions.short[clan.region]})`; - - if (name.length > DISCORD_CHOICES_MAX_NAME_SIZE) { - const overSize = name.length - DISCORD_CHOICES_MAX_NAME_SIZE; - - name = `${clan.name.slice( - 0, - clan.name.length - overSize - OVERFLOW_SUFFIX.length, - )}${OVERFLOW_SUFFIX} (${strings.common.regions.short[clan.region]})`; - } - - return { - name, - value: `${clan.region}/${clan.clan_id}`, - } satisfies ApplicationCommandOptionChoiceData; - }) - : [], - ); -} diff --git a/packages/bot/src/core/discord/autocompleteTanks.ts b/packages/bot/src/core/discord/autocompleteTanks.ts deleted file mode 100644 index e43c975fb..000000000 --- a/packages/bot/src/core/discord/autocompleteTanks.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { fetchTankNames, SEARCH_KEYS, TankType } from "@blitzkit/core"; -import { - type ApplicationCommandOptionChoiceData, - AutocompleteInteraction, - type CacheType, -} from "discord.js"; -import { go } from "fuzzysort"; -import { tankNames } from "../blitzkit/nonBlockingPromises"; -import { translator } from "../localization/translator"; -import { - DISCORD_CHOICES_MAX_NAME_SIZE, - OVERFLOW_SUFFIX, -} from "./autocompleteClan/constants"; - -export const tankNamesTechTreeOnly = fetchTankNames().then((names) => - names.filter((tank) => tank.treeType === TankType.TANK_TYPE_RESEARCHABLE), -); - -export async function autocompleteTanks( - interaction: AutocompleteInteraction, - techTreeOnly = false, - fields = ["tank"], -) { - const { unwrap } = translator(interaction.locale); - const focusedOption = interaction.options.getFocused(true); - if (!fields.includes(focusedOption.name)) return; - - await interaction.respond( - focusedOption.value - ? await Promise.all( - go( - focusedOption.value, - await (techTreeOnly ? tankNamesTechTreeOnly : tankNames), - { keys: SEARCH_KEYS, limit: 10 }, - ).map(async (item) => { - let name = unwrap(item.obj.searchableName); - - if (name.length > DISCORD_CHOICES_MAX_NAME_SIZE) { - const overSize = name.length - DISCORD_CHOICES_MAX_NAME_SIZE; - - name = `${unwrap(item.obj.searchableName).slice( - 0, - unwrap(item.obj.searchableName).length - - overSize - - OVERFLOW_SUFFIX.length, - )}${OVERFLOW_SUFFIX}`; - } - - return { - name, - value: `${item.obj.id}`, - } satisfies ApplicationCommandOptionChoiceData; - }), - ) - : [], - ); -} diff --git a/packages/bot/src/core/discord/autocompleteUsername.ts b/packages/bot/src/core/discord/autocompleteUsername.ts deleted file mode 100644 index 522721c84..000000000 --- a/packages/bot/src/core/discord/autocompleteUsername.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { searchPlayersAcrossRegions } from '@blitzkit/core'; -import { - ApplicationCommandOptionChoiceData, - AutocompleteInteraction, - CacheType, -} from 'discord.js'; -import { translator } from '../localization/translator'; -import { - DISCORD_CHOICES_MAX_NAME_SIZE, - OVERFLOW_SUFFIX, -} from './autocompleteClan/constants'; - -export async function autocompleteUsername( - interaction: AutocompleteInteraction, -) { - const { strings } = translator(interaction.locale); - const focusedOption = interaction.options.getFocused(true); - if (focusedOption.name !== 'username') return; - const players = await searchPlayersAcrossRegions(focusedOption.value); - - try { - await interaction.respond( - players - ? players.map((player) => { - let name = `${player.nickname} (${ - strings.common.regions.short[player.region] - })`; - - if (name.length > DISCORD_CHOICES_MAX_NAME_SIZE) { - const overSize = name.length - DISCORD_CHOICES_MAX_NAME_SIZE; - - name = `${player.nickname.slice( - 0, - player.nickname.length - overSize - OVERFLOW_SUFFIX.length, - )}${OVERFLOW_SUFFIX} (${ - strings.common.regions.short[player.region] - })`; - } - - return { - name, - value: `${player.account_id}`, - } satisfies ApplicationCommandOptionChoiceData; - }) - : [], - ); - } catch (error) {} -} diff --git a/packages/bot/src/core/discord/buttonLink.ts b/packages/bot/src/core/discord/buttonLink.ts deleted file mode 100644 index 65751dd57..000000000 --- a/packages/bot/src/core/discord/buttonLink.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { - ButtonBuilder, - ButtonStyle, - type ComponentEmojiResolvable, -} from "discord.js"; - -export function buttonLink( - url: string, - label: string, - emoji?: ComponentEmojiResolvable -) { - const button = new ButtonBuilder() - .setStyle(ButtonStyle.Link) - .setURL(url) - .setLabel(label); - - if (emoji) button.setEmoji(emoji); - - return button; -} diff --git a/packages/bot/src/core/discord/buttonPrimary.ts b/packages/bot/src/core/discord/buttonPrimary.ts deleted file mode 100644 index 2fa4733a0..000000000 --- a/packages/bot/src/core/discord/buttonPrimary.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { - ButtonBuilder, - ButtonStyle, - type ComponentEmojiResolvable, -} from "discord.js"; - -export function buttonPrimary( - id: string, - label: string, - emoji?: ComponentEmojiResolvable -) { - const button = new ButtonBuilder() - .setCustomId(id) - .setStyle(ButtonStyle.Primary) - .setLabel(label); - - if (emoji) button.setEmoji(emoji); - - return button; -} diff --git a/packages/bot/src/core/discord/buttonRefresh.ts b/packages/bot/src/core/discord/buttonRefresh.ts deleted file mode 100644 index fdf21e961..000000000 --- a/packages/bot/src/core/discord/buttonRefresh.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { ChatInputCommandInteraction } from 'discord.js'; -import { translator } from '../localization/translator'; -import { buttonPrimary } from './buttonPrimary'; - -export function buttonRefresh( - interaction: ChatInputCommandInteraction, - path: string, -) { - const { strings } = translator(interaction.locale); - - if ( - interaction.appPermissions?.has('ReadMessageHistory') && - interaction.appPermissions?.has('ViewChannel') - ) { - return buttonPrimary(path, strings.bot.common.buttons.refresh); - } - - return null; -} diff --git a/packages/bot/src/core/discord/chunkLines.ts b/packages/bot/src/core/discord/chunkLines.ts deleted file mode 100644 index dfcdef6d0..000000000 --- a/packages/bot/src/core/discord/chunkLines.ts +++ /dev/null @@ -1,17 +0,0 @@ -export function chunkLines(lines: string[], joiner = '\n', maxSize = 2000) { - const chunks = ['']; - - lines.map((line) => { - if (chunks.at(-1)!.length + line.length > maxSize) { - chunks.push(''); - } - - if (chunks.at(-1)!.length === 0) { - chunks[chunks.length - 1] += line; - } else { - chunks[chunks.length - 1] += joiner + line; - } - }); - - return chunks; -} diff --git a/packages/bot/src/core/discord/client.ts b/packages/bot/src/core/discord/client.ts deleted file mode 100644 index a83fb3822..000000000 --- a/packages/bot/src/core/discord/client.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Client, GatewayIntentBits } from "discord.js"; -import { interactionCreate } from "../../events/interactionCreate"; -import { ready } from "../../events/ready"; - -export const client = new Client({ - intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers], -}) - .on("clientReady", ready) - .on("interactionCreate", interactionCreate); diff --git a/packages/bot/src/core/discord/colors.ts b/packages/bot/src/core/discord/colors.ts deleted file mode 100644 index bb2c7f51d..000000000 --- a/packages/bot/src/core/discord/colors.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const ACCENT_COLOR = '#ecedee'; -export const POSITIVE_COLOR = '#82ff29'; -export const NEGATIVE_COLOR = '#ff4747'; -export const WARNING_COLOR = '#ffab00'; diff --git a/packages/bot/src/core/discord/commandToURL.ts b/packages/bot/src/core/discord/commandToURL.ts deleted file mode 100644 index 91455adeb..000000000 --- a/packages/bot/src/core/discord/commandToURL.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { type CacheType, ChatInputCommandInteraction } from "discord.js"; - -export interface Parameters { - [key: string]: string | number | boolean | undefined | null; -} - -// TODO: automatically infer command options??? -// but how would that work with the region and id options? - -export function commandToURL( - interaction: ChatInputCommandInteraction, - parameters?: Parameters -) { - const pathname = [ - interaction.commandName, - interaction.options.getSubcommandGroup(false), - interaction.options.getSubcommand(false), - ] - .filter(Boolean) - .join("/"); - const params = (parameters ? Object.entries(parameters) : []) - .filter(([, value]) => value !== undefined && value !== null) - .map(([key, value]) => `${key}=${value}`) - .join("&"); - - return `${pathname}${params.length > 0 ? `?${params}` : ""}`; -} diff --git a/packages/bot/src/core/discord/createLocalizedCommand.ts b/packages/bot/src/core/discord/createLocalizedCommand.ts deleted file mode 100644 index acb00ca49..000000000 --- a/packages/bot/src/core/discord/createLocalizedCommand.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { type BlitzKitStrings } from "@blitzkit/i18n"; -import { - Locale, - SlashCommandBuilder, - SlashCommandSubcommandBuilder, - SlashCommandSubcommandGroupBuilder, -} from "discord.js"; -import { translator } from "../localization/translator"; -import { localizationObject } from "./localizationObject"; - -interface CreateLocalizedCommandSubcommand { - subcommand: string; - modify?: (option: SlashCommandSubcommandBuilder) => void; -} - -interface CreateLocalizedCommandSubcommandGroup { - group: string; - modify?: (option: SlashCommandSubcommandGroupBuilder) => void; -} - -export function createLocalizedCommand( - command: string, - sub?: - | CreateLocalizedCommandSubcommand[] - | CreateLocalizedCommandSubcommandGroup[], -) { - const commandPathItem = command.replaceAll( - "-", - "_", - ) as keyof BlitzKitStrings["bot"]["commands"]; - const { strings } = translator(Locale.EnglishUS); - const slashCommand = new SlashCommandBuilder() - .setName(command) - .setNameLocalizations( - localizationObject( - (strings) => strings.bot.commands[commandPathItem].name, - undefined, - true, - ), - ) - .setDescription(strings.bot.commands[commandPathItem].description) - .setDescriptionLocalizations( - localizationObject( - (strings) => strings.bot.commands[commandPathItem].description, - ), - ); - - if (sub) { - sub.map((subItem) => { - if ("subcommand" in subItem) { - const subcommandNameLocalizations = localizationObject( - (strings) => - ( - strings.bot.commands[commandPathItem] as unknown as { - subcommands: Record; - } - ).subcommands[subItem.subcommand].name, - undefined, - true, - ); - const subcommandDescriptionLocalizations = localizationObject( - (strings) => - ( - strings.bot.commands[commandPathItem] as unknown as { - subcommands: Record; - } - ).subcommands[subItem.subcommand].description, - ); - - slashCommand.addSubcommand((option) => { - if (subItem.modify) subItem.modify(option); - - return option - .setName(subItem.subcommand) - .setNameLocalizations(subcommandNameLocalizations) - .setDescription( - ( - strings.bot.commands[commandPathItem] as unknown as { - subcommands: Record; - } - ).subcommands[subItem.subcommand].description, - ) - .setDescriptionLocalizations(subcommandDescriptionLocalizations); - }); - } else { - const subcommandNameLocalizations = localizationObject( - (strings) => - ( - strings.bot.commands[commandPathItem] as unknown as { - groups: Record; - } - ).groups[subItem.group].name, - undefined, - true, - ); - const subcommandDescriptionLocalizations = localizationObject( - (strings) => - ( - strings.bot.commands[commandPathItem] as unknown as { - groups: Record; - } - ).groups[subItem.group].description, - ); - - slashCommand.addSubcommandGroup((option) => { - if (subItem.modify) subItem.modify(option); - - return option - .setName(subItem.group) - .setNameLocalizations(subcommandNameLocalizations) - .setDescription( - ( - strings.bot.commands[commandPathItem] as unknown as { - groups: Record; - } - ).groups[subItem.group].description, - ) - .setDescriptionLocalizations(subcommandDescriptionLocalizations); - }); - } - }); - } - - return slashCommand; -} diff --git a/packages/bot/src/core/discord/embedInfo.ts b/packages/bot/src/core/discord/embedInfo.ts deleted file mode 100644 index 5359e3212..000000000 --- a/packages/bot/src/core/discord/embedInfo.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { EmbedBuilder } from 'discord.js'; -import { ACCENT_COLOR } from './colors'; - -export function embedInfo(title: string, description: string) { - const embed = new EmbedBuilder() - .setColor(ACCENT_COLOR) - .setDescription(description); - - if (title) embed.setTitle(title); - - return embed; -} diff --git a/packages/bot/src/core/discord/embedNegative.ts b/packages/bot/src/core/discord/embedNegative.ts deleted file mode 100644 index 1d520500a..000000000 --- a/packages/bot/src/core/discord/embedNegative.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { EmbedBuilder } from 'discord.js'; -import { NEGATIVE_COLOR } from './colors'; - -export function embedNegative(title: string, description: string) { - return new EmbedBuilder() - .setColor(NEGATIVE_COLOR) - .setTitle(title) - .setDescription(description); -} diff --git a/packages/bot/src/core/discord/embedPositive.ts b/packages/bot/src/core/discord/embedPositive.ts deleted file mode 100644 index 5f157a851..000000000 --- a/packages/bot/src/core/discord/embedPositive.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { EmbedBuilder } from 'discord.js'; -import { POSITIVE_COLOR } from './colors'; - -export function embedPositive(title: string, description: string) { - return new EmbedBuilder() - .setColor(POSITIVE_COLOR) - .setTitle(title) - .setDescription(description); -} diff --git a/packages/bot/src/core/discord/embedWarning.ts b/packages/bot/src/core/discord/embedWarning.ts deleted file mode 100644 index 0cb912d9e..000000000 --- a/packages/bot/src/core/discord/embedWarning.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { EmbedBuilder } from 'discord.js'; -import { WARNING_COLOR } from './colors'; - -export function embedWarning(title: string, description: string) { - return new EmbedBuilder() - .setColor(WARNING_COLOR) - .setTitle(title) - .setDescription(description); -} diff --git a/packages/bot/src/core/discord/getCustomPeriodParams.ts b/packages/bot/src/core/discord/getCustomPeriodParams.ts deleted file mode 100644 index eaa19e7fa..000000000 --- a/packages/bot/src/core/discord/getCustomPeriodParams.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { ChatInputCommandInteraction } from "discord.js"; -import type { PeriodType } from "./addPeriodSubCommands"; - -export function getCustomPeriodParams( - interaction: ChatInputCommandInteraction, - forcedNonCustomPeriod = false -) { - if ( - !forcedNonCustomPeriod && - (interaction.options.getSubcommand() as PeriodType) === "custom" - ) { - return { - start: interaction.options.getInteger("start", true), - end: interaction.options.getInteger("end", true), - }; - } - - return undefined; -} diff --git a/packages/bot/src/core/discord/getFiltersFromButton.ts b/packages/bot/src/core/discord/getFiltersFromButton.ts deleted file mode 100644 index 37689b7f0..000000000 --- a/packages/bot/src/core/discord/getFiltersFromButton.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { TankClass, TankType } from "@blitzkit/core"; -import { ButtonInteraction } from "discord.js"; -import type { StatFilters } from "../blitzstars/filterStats"; - -export function getFiltersFromButton(interaction: ButtonInteraction) { - const url = new URL(`https://example.com/${interaction.customId}`); - - return { - nation: url.searchParams.get("nation") ?? undefined, - tank: parseInt(url.searchParams.get("tank") ?? "0") || undefined, - class: - (Number(url.searchParams.get("class")) as TankClass | undefined) ?? - undefined, - type: - (Number(url.searchParams.get("type")) as TankType | undefined) ?? - undefined, - tier: parseInt(url.searchParams.get("tier") ?? "0") || undefined, - } satisfies StatFilters; -} diff --git a/packages/bot/src/core/discord/getFiltersFromCommand.ts b/packages/bot/src/core/discord/getFiltersFromCommand.ts deleted file mode 100644 index d4cac40cf..000000000 --- a/packages/bot/src/core/discord/getFiltersFromCommand.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { ChatInputCommandInteraction } from 'discord.js'; -import { resolveTankId } from '../blitz/resolveTankId'; -import type { StatFilters } from '../blitzstars/filterStats'; - -export async function getFiltersFromCommand( - interaction: ChatInputCommandInteraction, -) { - const nationRaw = interaction.options.getString('nation'); - const tierRaw = interaction.options.getString('tier'); - const classRaw = interaction.options.getString('class'); - const typeRaw = interaction.options.getString('type'); - const tankRaw = interaction.options.getString('tank'); - - return { - nation: nationRaw ?? undefined, - tier: tierRaw === null ? undefined : Number(tierRaw), - class: classRaw === null ? undefined : Number(classRaw), - type: typeRaw === null ? undefined : Number(typeRaw), - tank: - tankRaw === null - ? undefined - : await resolveTankId(tankRaw, interaction.locale), - } satisfies StatFilters; -} diff --git a/packages/bot/src/core/discord/getPeriodOptionName.ts b/packages/bot/src/core/discord/getPeriodOptionName.ts deleted file mode 100644 index 1eaea0772..000000000 --- a/packages/bot/src/core/discord/getPeriodOptionName.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { literals } from "@blitzkit/i18n"; -import { Locale } from "discord.js"; -import { translator } from "../localization/translator"; -import type { PeriodSize } from "./addPeriodSubCommands"; - -export function getPeriodOptionName(period: PeriodSize, locale: Locale) { - const { strings } = translator(locale); - if (period === "career") return strings.bot.common.periods.career; - if (period === "today") return strings.bot.common.periods.today; - return literals(strings.bot.common.periods.days, { days: period }); -} diff --git a/packages/bot/src/core/discord/localizationObject.ts b/packages/bot/src/core/discord/localizationObject.ts deleted file mode 100644 index 637a6d604..000000000 --- a/packages/bot/src/core/discord/localizationObject.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { type BlitzKitStrings, literals as literalsFunc } from "@blitzkit/i18n"; -import { Locale } from "discord.js"; -import { SUPPORTED_LOCALES_DISCORD } from "../localization/strings/constants"; -import { translator } from "../localization/translator"; - -const validNameRegex = - /^[\p{Ll}\p{Lm}\p{Lo}\p{N}\p{sc=Devanagari}\p{sc=Thai}_-]+$/u; - -export function localizationObject( - path: (strings: BlitzKitStrings) => string, - literals?: Record, - strict = false -) { - return SUPPORTED_LOCALES_DISCORD.reduce>>( - (localizations, locale) => { - const { strings } = translator(locale); - const translation = literalsFunc(path(strings), literals); - const isValid = - (!strict || validNameRegex.test(translation)) && translation.length > 0; - - if (!isValid) { - // console.warn( - // `Invalid localization for ${locale} (${path}): ${translation}; skipping...` - // ); - return localizations; - } - - return { - ...localizations, - [locale]: translation, - }; - }, - {} - ); -} diff --git a/packages/bot/src/core/discord/markdownTable.ts b/packages/bot/src/core/discord/markdownTable.ts deleted file mode 100644 index 6ad0b6f64..000000000 --- a/packages/bot/src/core/discord/markdownTable.ts +++ /dev/null @@ -1,9 +0,0 @@ -export type TableInputEntry = [string, string | number] | []; -export type TableInput = TableInputEntry[]; - -export function markdownTable(input: TableInput) { - return input - .filter((array) => array[1] !== undefined) - .map((array) => (array.length === 0 ? '' : `**${array[0]}**: ${array[1]}`)) - .join('\n'); -} diff --git a/packages/bot/src/core/discord/normalizeInteractionReturnable.tsx b/packages/bot/src/core/discord/normalizeInteractionReturnable.tsx deleted file mode 100644 index 7e67b9767..000000000 --- a/packages/bot/src/core/discord/normalizeInteractionReturnable.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { - ActionRowBuilder, - AttachmentBuilder, - ButtonBuilder, - EmbedBuilder, - type InteractionEditReplyOptions, - type InteractionReplyOptions, - type MessageEditOptions, -} from "discord.js"; -import type { InteractionReturnable } from "../../events/interactionCreate"; -import type { Writeable } from "../../types/writable"; -import { jsxToPngThreaded } from "../blitzkit/jsxToPngThreaded"; -import { RenderConfiguration } from "../blitzkit/renderConfiguration"; - -export async function normalizeInteractionReturnable( - returnable: InteractionReturnable -) { - const images: [number, Buffer][] = []; - const reply: InteractionEditReplyOptions & - InteractionReplyOptions & - MessageEditOptions = {}; - const awaitedReturnable = await returnable; - const normalizedReturnable = Array.isArray(awaitedReturnable) - ? awaitedReturnable - : [awaitedReturnable]; - let renderConfiguration = new RenderConfiguration(); - - await Promise.all( - normalizedReturnable.map(async (item, index) => { - if (typeof item === "string") { - reply.content = item; - } else if (item instanceof EmbedBuilder) { - if (!reply.embeds) reply.embeds = []; - (reply.embeds as Writeable).push(item); - } else if (item instanceof ButtonBuilder) { - if (!reply.components) - reply.components = [new ActionRowBuilder()]; - (reply.components[0] as ActionRowBuilder).addComponents( - item - ); - } else if (item instanceof AttachmentBuilder) { - if (!reply.files) reply.files = []; - (reply.files as Writeable).push(item); - } else if (item instanceof RenderConfiguration) { - renderConfiguration = item; - } else if (item === null) { - return; - } else { - const image = await jsxToPngThreaded(item, renderConfiguration); - images.push([index, image]); - } - }) - ); - - if (images.length > 0) { - images.sort(([a], [b]) => b - a); - if (!reply.files) reply.files = []; - images.forEach(([, image]) => - (reply.files as Writeable)?.push(image) - ); - } - - return reply; -} diff --git a/packages/bot/src/core/discord/psa.ts b/packages/bot/src/core/discord/psa.ts deleted file mode 100644 index 42dfe93eb..000000000 --- a/packages/bot/src/core/discord/psa.ts +++ /dev/null @@ -1,36 +0,0 @@ -export type PSAData = { - secret?: boolean; - links?: { - title: string; - url: string; - }[]; - commands?: string[]; -} & ( - | { - type?: 'embed'; - title: string; - description: string; - image?: string; - } - | { - type: 'image'; - image: string; - title?: string; - } -); - -export interface PSA { - data: PSAData | null; -} - -export const psa: PSA = { - data: null, -}; - -fetch( - 'https://gist.githubusercontent.com/tresabhi/ed4b136f08856e615212e573aabe1968/raw', -) - .then((response) => response.json()) - .then((data) => { - psa.data = data as PSAData; - }); diff --git a/packages/bot/src/core/discord/resolveClanFromCommand.ts b/packages/bot/src/core/discord/resolveClanFromCommand.ts deleted file mode 100644 index 2a78793b6..000000000 --- a/packages/bot/src/core/discord/resolveClanFromCommand.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { Region } from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { ChatInputCommandInteraction } from "discord.js"; -import markdownEscape from "markdown-escape"; -import { searchClansAcrossRegionsBotWrapper } from "../blitz/searchClansAcrossRegionsBotWrapper"; -import { UserError } from "../blitzkit/userError"; -import { translator } from "../localization/translator"; - -export const serverAndIdPattern = /(com|eu|asia)\/[0-9]+/; - -export async function resolveClanFromCommand( - interaction: ChatInputCommandInteraction -) { - const { strings } = translator(interaction.locale); - const clan = interaction.options.getString("clan", true); - - if (serverAndIdPattern.test(clan)) { - const [server, accountId] = clan.split("/"); - return { region: server as Region, id: Number(accountId) }; - } else { - const accounts = await searchClansAcrossRegionsBotWrapper( - clan, - interaction.locale - ); - - if (accounts[0]) { - return { region: accounts[0].region, id: accounts[0].clan_id }; - } else { - throw new UserError( - literals(strings.bot.common.errors.clan_not_found, { - search: markdownEscape(clan), - }) - ); - } - } -} diff --git a/packages/bot/src/core/discord/resolvePeriodFromButton.ts b/packages/bot/src/core/discord/resolvePeriodFromButton.ts deleted file mode 100644 index cb9388bdf..000000000 --- a/packages/bot/src/core/discord/resolvePeriodFromButton.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { getTimeDaysAgo, type Region } from "@blitzkit/core"; -import { ButtonInteraction } from "discord.js"; -import { getPeriodNow } from "../blitzkit/getPeriodNow"; -import { getPeriodStart } from "../blitzkit/getPeriodStart"; -import type { PeriodType } from "./addPeriodSubCommands"; -import { getPeriodOptionName } from "./getPeriodOptionName"; -import type { ResolvedPeriod } from "./resolvePeriodFromCommand"; - -export function resolvePeriodFromButton( - region: Region, - interaction: ButtonInteraction -) { - let name: string; - let start: number; - let end: number; - const url = new URL(`https://example.com/${interaction.customId}`); - const path = url.pathname.split("/"); - const periodSubcommand = path.at(-1) as PeriodType; - - if (periodSubcommand === "custom") { - const startRaw = parseInt(url!.searchParams.get("start")!); - const endRaw = parseInt(url!.searchParams.get("end")!); - const startDaysAgoMin = Math.min(startRaw, endRaw); - const endDaysAgoMax = Math.max(startRaw, endRaw); - - name = `${startDaysAgoMin} - ${endDaysAgoMax} days`; - start = getTimeDaysAgo(region, endDaysAgoMax); - end = getTimeDaysAgo(region, startDaysAgoMin); - } else { - name = getPeriodOptionName(periodSubcommand, interaction.locale); - start = getPeriodStart(region, periodSubcommand); - end = getPeriodNow(); - } - - return { - name, - start, - end, - } satisfies ResolvedPeriod; -} diff --git a/packages/bot/src/core/discord/resolvePeriodFromCommand.ts b/packages/bot/src/core/discord/resolvePeriodFromCommand.ts deleted file mode 100644 index 55ad2af4e..000000000 --- a/packages/bot/src/core/discord/resolvePeriodFromCommand.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { getTimeDaysAgo, type Region } from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { type CacheType, ChatInputCommandInteraction } from "discord.js"; -import { getPeriodNow } from "../blitzkit/getPeriodNow"; -import { getPeriodStart } from "../blitzkit/getPeriodStart"; -import { translator } from "../localization/translator"; -import type { PeriodType } from "./addPeriodSubCommands"; -import { getPeriodOptionName } from "./getPeriodOptionName"; - -export interface ResolvedPeriod { - name: string; - start: number; - end: number; -} - -export function resolvePeriodFromCommand( - region: Region, - interaction: ChatInputCommandInteraction, - forcedPeriod?: PeriodType -) { - const { strings } = translator(interaction.locale); - let name: string; - let start: number; - let end: number; - const periodSubcommand = forcedPeriod - ? forcedPeriod - : (interaction.options.getSubcommand() as PeriodType); - - if (periodSubcommand === "custom") { - const startOption = interaction.options.getInteger("start", true); - const endOption = interaction.options.getInteger("end", true); - const startDaysAgoMin = Math.min(startOption, endOption); - const endDaysAgoMax = Math.max(startOption, endOption); - - name = literals(strings.bot.common.periods.custom, { - day_1: startDaysAgoMin, - day_2: endDaysAgoMax, - }); - start = getTimeDaysAgo(region, endDaysAgoMax); - end = getTimeDaysAgo(region, startDaysAgoMin); - } else { - name = getPeriodOptionName(periodSubcommand, interaction.locale); - start = getPeriodStart(region, periodSubcommand); - end = getPeriodNow(); - } - - return { - name, - start, - end, - } satisfies ResolvedPeriod; -} diff --git a/packages/bot/src/core/discord/resolvePlayerFromButton.ts b/packages/bot/src/core/discord/resolvePlayerFromButton.ts deleted file mode 100644 index 06b3e2895..000000000 --- a/packages/bot/src/core/discord/resolvePlayerFromButton.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { Region } from "@blitzkit/core"; -import { ButtonInteraction } from "discord.js"; -import type { ResolvedPlayer } from "./resolvePlayerFromCommand"; - -export async function resolvePlayerFromButton(interaction: ButtonInteraction) { - const url = new URL(`https://exmaple.com/${interaction.customId}`); - - return { - id: parseInt(url.searchParams.get("id")!), - region: url.searchParams.get("region") as Region, - } satisfies ResolvedPlayer; -} diff --git a/packages/bot/src/core/discord/resolvePlayerFromCommand/index.ts b/packages/bot/src/core/discord/resolvePlayerFromCommand/index.ts deleted file mode 100644 index c76e6c70e..000000000 --- a/packages/bot/src/core/discord/resolvePlayerFromCommand/index.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { idToRegion, type Region } from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { type CacheType, ChatInputCommandInteraction } from "discord.js"; -import markdownEscape from "markdown-escape"; -import { searchPlayersAcrossRegionsBotWrapper } from "../../blitz/searchPlayersAcrossRegionsBotWrapper"; -import { getBlitzFromDiscord } from "../../blitzkit/getBlitzFromDiscord"; -import { UserError } from "../../blitzkit/userError"; -import { translator } from "../../localization/translator"; - -export interface ResolvedPlayer { - region: Region; - id: number; -} - -export async function resolvePlayerFromCommand( - interaction: ChatInputCommandInteraction -) { - const { strings } = translator(interaction.locale); - const commandUsername = interaction.options.getString("username"); - - if (commandUsername !== null) { - const id = parseInt(commandUsername); - - if (!isNaN(id)) { - const region = idToRegion(id); - return { region, id } satisfies ResolvedPlayer; - } else { - const accounts = await searchPlayersAcrossRegionsBotWrapper( - commandUsername, - interaction.locale - ); - - if (accounts[0]) { - return { - region: accounts[0].region, - id: accounts[0].account_id, - } satisfies ResolvedPlayer; - } else { - throw new UserError( - literals(strings.bot.common.errors.player_not_found, { - search: markdownEscape(commandUsername), - }) - ); - } - } - } else { - const account = await getBlitzFromDiscord(BigInt(interaction.user.id)); - - if (account) return account; - - throw new UserError(strings.bot.common.errors.player_not_linked); - } -} diff --git a/packages/bot/src/core/localization/strings/constants.ts b/packages/bot/src/core/localization/strings/constants.ts deleted file mode 100644 index 4cb69a89c..000000000 --- a/packages/bot/src/core/localization/strings/constants.ts +++ /dev/null @@ -1,25 +0,0 @@ -import locales from "@blitzkit/i18n/locales.json"; -import { Locale } from "discord.js"; - -const SUPPORTED_LOCALES_DISCORD_MAP: Record = { - en: Locale.EnglishUS, - es: Locale.SpanishES, - pt: Locale.PortugueseBR, - ru: Locale.Russian, - uk: Locale.Ukrainian, - zh: Locale.ChineseCN, - fr: Locale.French, - ja: Locale.Japanese, - pl: Locale.Polish, -}; - -export const SUPPORTED_LOCALES_DISCORD_MAP_INVERSE = Object.fromEntries( - Object.entries(SUPPORTED_LOCALES_DISCORD_MAP).map(([k, v]) => [v, k]) -) as Record; - -export const SUPPORTED_LOCALES_DISCORD = Object.values( - SUPPORTED_LOCALES_DISCORD_MAP -); - -export const DEFAULT_LOCALE_DISCORD = - SUPPORTED_LOCALES_DISCORD_MAP[locales.default]; diff --git a/packages/bot/src/core/localization/translator.ts b/packages/bot/src/core/localization/translator.ts deleted file mode 100644 index 37168ac30..000000000 --- a/packages/bot/src/core/localization/translator.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { STRINGS, unwrapper } from '@blitzkit/i18n'; -import { Locale } from 'discord.js'; -import { - DEFAULT_LOCALE_DISCORD, - SUPPORTED_LOCALES_DISCORD, - SUPPORTED_LOCALES_DISCORD_MAP_INVERSE, -} from './strings/constants'; - -export function translator(localeRaw: Locale) { - const locale = - localeRaw !== DEFAULT_LOCALE_DISCORD && - SUPPORTED_LOCALES_DISCORD.includes(localeRaw) - ? localeRaw - : DEFAULT_LOCALE_DISCORD; - const bkLocale = SUPPORTED_LOCALES_DISCORD_MAP_INVERSE[locale]; - const strings = STRINGS[bkLocale]; - const unwrap = unwrapper(bkLocale); - - return { locale, strings, unwrap, bkLocale }; -} diff --git a/packages/bot/src/core/radix/radixColors.ts b/packages/bot/src/core/radix/radixColors.ts deleted file mode 100644 index dfe17a359..000000000 --- a/packages/bot/src/core/radix/radixColors.ts +++ /dev/null @@ -1,55 +0,0 @@ -import * as colors from "@radix-ui/colors"; -import { Theme } from "@radix-ui/themes"; -import type { ComponentProps } from "react"; - -export type AccentColor = Exclude< - ComponentProps["accentColor"], - "gray" | undefined ->; -export type AccentColorDark = `${AccentColor}Dark`; -export type GrayColor = Exclude< - ComponentProps["grayColor"], - "auto" | undefined ->; -export type GrayColorDark = `${GrayColor}Dark`; - -export const ACCENT_COLORS: AccentColor[] = [ - "tomato", - "red", - "ruby", - "crimson", - "pink", - "plum", - "purple", - "violet", - "iris", - "indigo", - "blue", - "cyan", - "teal", - "jade", - "green", - "grass", - "brown", - "orange", - "sky", - "mint", - "lime", - "yellow", - "amber", - "gold", - "bronze", -]; -export const GRAY_COLORS: GrayColor[] = [ - "gray", - "mauve", - "slate", - "sage", - "olive", - "sand", -]; - -export const PALETTES: Record< - AccentColor | AccentColorDark | GrayColor | GrayColorDark, - { [key: `${string}${number}`]: string } -> = colors; diff --git a/packages/bot/src/core/streams/wotbreplay.ts b/packages/bot/src/core/streams/wotbreplay.ts deleted file mode 100644 index 47d18d9d3..000000000 --- a/packages/bot/src/core/streams/wotbreplay.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { ReadStream } from "@blitzkit/core"; -import AdmZip from "adm-zip"; -import { readFile } from "fs/promises"; -import { Parser } from "pickleparser"; -import protobuf from "protobufjs"; - -interface WotbReplayMetaJson { - version: string; - title: string; - dbid: string; - playerName: string; - battleStartTime: string; - playerVehicleName: string; - mapName: string; - arenaUniqueId: string; - battleDuration: number; - vehicleCompDescriptor: number; - camouflageId: number; - mapId: number; - arenaBonusType: number; -} - -export class WotbReplayReadStream extends ReadStream { - async wotbReplay() { - const pickleParser = new Parser(); - const zip = new AdmZip(Buffer.from(this.buffer)); - const metaJsonBuffer = zip.getEntry("meta.json")?.getData(); - const battleResultsDat = zip.getEntry("battle_results.dat")?.getData(); - - if (!metaJsonBuffer || !battleResultsDat) - throw new SyntaxError("meta.json not found"); - - const metaJson = JSON.parse( - metaJsonBuffer.toString() - ) as WotbReplayMetaJson; - const unpickledBattleResults = pickleParser.parse(battleResultsDat); - const [, battleResultsString] = unpickledBattleResults as [number, string]; - const root = await protobuf.load( - `${__dirname}/../../protos/BattleResultsClient.proto` - ); - const BattleResults = root.lookupType( - "BattleResultsGenerated.BattleResultsClient" - ); - - const reader = protobuf.Reader.create(Buffer.from(battleResultsString)); - - // const decodedBattleResults = BattleResults.decodeDelimited( - // Buffer.from(battleResultsString), - // ); - // console.log(decodedBattleResults.toJSON()); - } -} - -const replay = await readFile("temp/test3.wotbreplay"); -const stream = new WotbReplayReadStream(replay.buffer as ArrayBuffer); - -stream.wotbReplay(); diff --git a/packages/bot/src/events/interactionCreate/handlers/autocomplete.ts b/packages/bot/src/events/interactionCreate/handlers/autocomplete.ts deleted file mode 100644 index 59ec8f072..000000000 --- a/packages/bot/src/events/interactionCreate/handlers/autocomplete.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { AutocompleteInteraction, CacheType } from "discord.js"; -import { commands } from ".."; - -export async function handleAutocomplete( - interaction: AutocompleteInteraction, -) { - const command = commands[interaction.commandName]; - - if (!command) { - console.error(`No command matching ${interaction.commandName} was found.`); - return; - } - - if (command.autocomplete) command.autocomplete(interaction); -} diff --git a/packages/bot/src/events/interactionCreate/handlers/button.tsx b/packages/bot/src/events/interactionCreate/handlers/button.tsx deleted file mode 100644 index 8ac4b7269..000000000 --- a/packages/bot/src/events/interactionCreate/handlers/button.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { ButtonInteraction, CacheType } from "discord.js"; -import { commands } from ".."; -import { normalizeInteractionReturnable } from "../../../core/discord/normalizeInteractionReturnable"; - -export async function handleButton(interaction: ButtonInteraction) { - await interaction.deferUpdate(); - - const url = new URL(`https://example.com/${interaction.customId}`); - const commandName = url.pathname.split("/")[1]; - const button = commands[commandName]?.button; - - if (!button) { - throw new Error(`Button handler not found for "${commandName}"`); - } - - if (interaction.message.interaction?.user.id !== interaction.user.id) { - interaction.followUp({ - ephemeral: true, - content: "You are not the author of this command.", - }); - return; - } - - await interaction.message.edit( - await normalizeInteractionReturnable(button(interaction)), - ); -} diff --git a/packages/bot/src/events/interactionCreate/handlers/chatInputCommand.ts b/packages/bot/src/events/interactionCreate/handlers/chatInputCommand.ts deleted file mode 100644 index d385a036a..000000000 --- a/packages/bot/src/events/interactionCreate/handlers/chatInputCommand.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { - ActionRowBuilder, - AttachmentBuilder, - ButtonBuilder, - ButtonStyle, - type CacheType, - ChatInputCommandInteraction, - type InteractionReplyOptions, -} from "discord.js"; -import { type InteractionRawReturnable, commands } from ".."; -import { UserError } from "../../../core/blitzkit/userError"; -import { buttonLink } from "../../../core/discord/buttonLink"; -import { embedWarning } from "../../../core/discord/embedWarning"; -import { normalizeInteractionReturnable } from "../../../core/discord/normalizeInteractionReturnable"; -import { psa } from "../../../core/discord/psa"; -import { translator } from "../../../core/localization/translator"; -import type { Writeable } from "../../../types/writable"; - -export async function handleChatInputCommand( - interaction: ChatInputCommandInteraction, -) { - await interaction.deferReply(); - - const registry = commands[interaction.commandName]; - - try { - const returnable = await registry.handler(interaction); - - if (registry.handlesInteraction) return; - - const chunks: InteractionRawReturnable[][] = [[]]; - - (Array.isArray(returnable) ? returnable : [returnable]).forEach( - (object) => { - if (typeof object === "string") { - chunks.push([object]); - } else chunks.at(-1)?.push(object!); - }, - ); - - let index = 0; - for (const chunk of chunks.filter((chunk) => chunk.length > 0)) { - const reply = await normalizeInteractionReturnable(chunk); - - if ( - psa.data && - !psa.data.secret && - (psa.data.commands === undefined || - psa.data.commands.includes(interaction.commandName)) - ) { - if (psa.data.type === undefined || psa.data.type === "embed") { - if (!reply.embeds) reply.embeds = []; - - const embed = embedWarning(psa.data.title, psa.data.description); - - if (psa.data.image) embed.setImage(psa.data.image); - (reply.embeds as Writeable).push(embed); - } else if (psa.data.type === "image") { - if (!reply.files) reply.files = []; - (reply.files as Writeable).push( - new AttachmentBuilder(psa.data.image), - ); - reply.content = reply.content ?? psa.data.title; - } - - if (psa.data.links) { - if (!reply.components?.[0]) { - reply.components = [new ActionRowBuilder()]; - } - - ( - reply.components[0] as ActionRowBuilder - ).addComponents( - psa.data.links.map(({ title, url }) => buttonLink(url, title)), - ); - } - } - - if (index === 0) { - await interaction.editReply(reply); - } else { - await interaction.followUp(reply); - } - - if ( - psa.data && - psa.data.secret && - (psa.data.commands === undefined || - psa.data.commands.includes(interaction.commandName)) - ) { - const followUp: InteractionReplyOptions = { - ephemeral: true, - }; - - if (psa.data.type === undefined || psa.data.type === "embed") { - if (!followUp.embeds) followUp.embeds = []; - - const embed = embedWarning(psa.data.title, psa.data.description); - - if (psa.data.image) embed.setImage(psa.data.image); - (followUp.embeds as Writeable).push(embed); - } else if (psa.data.type === "image") { - if (!followUp.files) followUp.files = []; - (followUp.files as Writeable).push( - new AttachmentBuilder(psa.data.image), - ); - followUp.content = followUp.content ?? psa.data.title; - } - - if (psa.data.links) { - if (!followUp.components?.[0]) { - followUp.components = [new ActionRowBuilder()]; - } - - ( - followUp.components[0] as ActionRowBuilder - ).addComponents( - psa.data.links.map(({ title, url }) => buttonLink(url, title)), - ); - } - - interaction.followUp(followUp); - } - - index++; - } - } catch (error) { - const { strings } = translator(interaction.locale); - - const components = [ - new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setLabel(strings.bot.common.errors.get_help) - .setURL("https://discord.gg/nDt7AjGJQH") - .setStyle(ButtonStyle.Link), - ), - ]; - if (error instanceof UserError) { - interaction.editReply({ - content: error.message, - components, - }); - } else { - console.error(interaction.commandName, error); - interaction.editReply({ - content: strings.bot.common.errors.uncaught_error, - components, - }); - } - } -} diff --git a/packages/bot/src/events/interactionCreate/index.ts b/packages/bot/src/events/interactionCreate/index.ts deleted file mode 100644 index 9eef8b024..000000000 --- a/packages/bot/src/events/interactionCreate/index.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { assertSecret } from "@blitzkit/core"; -import { - AttachmentBuilder, - AutocompleteInteraction, - ButtonBuilder, - ButtonInteraction, - type CacheType, - ChatInputCommandInteraction, - EmbedBuilder, - type Interaction, - REST, - Routes, - SlashCommandBuilder, - type SlashCommandOptionsOnlyBuilder, - type SlashCommandSubcommandsOnlyBuilder, -} from "discord.js"; -import { aboutCommand } from "../../commands/about"; -import { breakdownCommand } from "../../commands/breakdown"; -import { debugCommand } from "../../commands/debug"; -import { evolutionCommand } from "../../commands/evolution"; -import { fullStatsCommand } from "../../commands/fullStats"; -import { inactiveCommand } from "../../commands/inactive"; -import { verifyCommand } from "../../commands/link"; -import { ownedTanksCommand } from "../../commands/ownedTanks"; -import { permissionsCommand } from "../../commands/permissions"; -import { pingCommand } from "../../commands/ping"; -import { playerInfoCommand } from "../../commands/playerInfo"; -import { ratingCommand } from "../../commands/rating"; -import { ratingLeaderboardCommand } from "../../commands/ratingLeaderboard"; -import { replayCommand } from "../../commands/replay"; -import { researchCommand } from "../../commands/research"; -import { searchClansCommand } from "../../commands/searchClans"; -import { searchPlayersCommand } from "../../commands/searchPlayers"; -import { statsCommand } from "../../commands/stats"; -import { todayCommand } from "../../commands/today"; -import { RenderConfiguration } from "../../core/blitzkit/renderConfiguration"; -import { handleAutocomplete } from "./handlers/autocomplete"; -import { handleButton } from "./handlers/button"; -import { handleChatInputCommand } from "./handlers/chatInputCommand"; - -export type InteractionRawReturnable = - | string - | EmbedBuilder - | ButtonBuilder - | AttachmentBuilder - | RenderConfiguration - | React.JSX.Element - | null; -export type InteractionIterableReturnable = - | InteractionRawReturnable - | InteractionRawReturnable[]; -export type InteractionReturnable = - | InteractionIterableReturnable - | Promise; - -interface CommandRegistryBase { - command: - | SlashCommandBuilder - | SlashCommandSubcommandsOnlyBuilder - | SlashCommandOptionsOnlyBuilder - | Omit; - - autocomplete?: (interaction: AutocompleteInteraction) => void; - button?: (interaction: ButtonInteraction) => InteractionReturnable; -} - -interface CommandRegistryImplicit extends CommandRegistryBase { - handlesInteraction: true; - handler: (interaction: ChatInputCommandInteraction) => void; -} - -interface CommandRegistryExplicit extends CommandRegistryBase { - handlesInteraction?: false; - handler: ( - interaction: ChatInputCommandInteraction, - ) => InteractionReturnable; -} - -export type CommandRegistry = CommandRegistryImplicit | CommandRegistryExplicit; - -export const commandsArray: Promise[] = [ - // auctionCommand, - permissionsCommand, - debugCommand, - aboutCommand, - inactiveCommand, - ownedTanksCommand, - playerInfoCommand, - searchClansCommand, - searchPlayersCommand, - fullStatsCommand, - breakdownCommand, - verifyCommand, - pingCommand, - evolutionCommand, - statsCommand, - ratingLeaderboardCommand, - todayCommand, - researchCommand, - ratingCommand, - replayCommand, -]; - -export const commands: Record = {}; -const commandsFulfilled: CommandRegistry[] = []; - -let index = 0; -for (const registry of await Promise.allSettled(commandsArray)) { - if (registry.status === "rejected") { - console.warn(`Command ${index} rejected`, registry.reason); - continue; - } - - commandsFulfilled.push(registry.value); - commands[registry.value.command.name] = registry.value; - index++; -} - -const rest = new REST().setToken(assertSecret(import.meta.env.DISCORD_TOKEN)); -const body = commandsFulfilled.map((registry) => registry.command.toJSON()); -const applicationCommandsRoute = Routes.applicationCommands( - assertSecret(import.meta.env.DISCORD_CLIENT_ID), -); - -await rest.put(applicationCommandsRoute, { body }); - -export async function interactionCreate(interaction: Interaction) { - if (interaction.isAutocomplete()) { - handleAutocomplete(interaction); - } else if (interaction.isChatInputCommand()) { - handleChatInputCommand(interaction); - } else if (interaction.isButton()) { - handleButton(interaction); - } -} diff --git a/packages/bot/src/events/ready.ts b/packages/bot/src/events/ready.ts deleted file mode 100644 index 2612b1908..000000000 --- a/packages/bot/src/events/ready.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Client } from 'discord.js'; - -export function ready(client: Client) { - console.log(`🟢 Launched bot ${client.shard?.ids[0] ?? 'default'}`); -} diff --git a/packages/bot/src/main.ts b/packages/bot/src/main.ts deleted file mode 100644 index 8bac09c3a..000000000 --- a/packages/bot/src/main.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { assertSecret } from "@blitzkit/core"; -import { ActivityType, ShardingManager } from "discord.js"; -import { - discoveredIdsDefinitions, - tankDefinitions, -} from "./core/blitzkit/nonBlockingPromises"; - -let manager: ShardingManager; -let iteration = 0; -const interval = setInterval(async () => { - console.log(`Attempting launch on iteration ${++iteration}`); - - manager?.respawnAll(); - manager = new ShardingManager("dist/bot/workers/bot.js", { - token: assertSecret(import.meta.env.DISCORD_TOKEN), - }); - - const shards = await manager - .on("shardCreate", (shard) => { - clearInterval(interval); - console.log(`🟡 Launching shard ${shard.id}`); - shard.on("ready", () => console.log(`🟢 Launched shard ${shard.id}`)); - }) - .spawn({ timeout: 60000 }); - - try { - let servers = 0; - let channels = 0; - let users = 0; - - for (const [, shard] of shards) { - const guilds = (await shard.fetchClientValue("guilds.cache")) as any; - - servers += guilds.length; - - for (const guild of guilds) { - channels += guild.channels.length; - users += guild.memberCount; - } - } - - const awaitedTankDefinitions = await tankDefinitions; - const tanks = Object.values(awaitedTankDefinitions.tanks).length; - const awaitedDiscoveredIdsDefinitions = await discoveredIdsDefinitions; - const usernames = awaitedDiscoveredIdsDefinitions.count; - - manager.broadcastEval( - async (client, { servers, users, channels, tanks, usernames }) => { - const bios = [ - `Living in ${servers.toLocaleString()} servers`, - `Serving ${users.toLocaleString()} users`, - `Observing ${channels.toLocaleString()} channels`, - `Tinkering with ${tanks.toLocaleString()} tanks`, - `Memorizing ${usernames.toLocaleString()} usernames`, - ]; - - client.user?.setPresence({ - activities: [ - { - type: ActivityType.Custom, - name: bios[Math.floor(Math.random() * bios.length)], - }, - ], - }); - - for (const guild of client.guilds.cache.values()) { - try { - const botMember = await guild.members.fetch(client.user.id); - await botMember.setNickname("BlitzKit"); - } catch {} - } - }, - { context: { servers, channels, users, tanks, usernames } }, - ); - } catch (error) { - console.warn( - "An error occurred when setting up presence, ignoring to keep the process alive", - error, - ); - } -}, 15000); diff --git a/packages/bot/src/stitches.config.ts b/packages/bot/src/stitches.config.ts deleted file mode 100644 index f50f1a5fa..000000000 --- a/packages/bot/src/stitches.config.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { - amberDark, - amberDarkA, - blueDark, - brownDark, - crimsonDark, - greenDark, - mauveDark, - orangeDark, - purpleDark, - redDark, - rubyDark, - sandDark, - slate, - slateDark, - slateDarkA, - tealDark, -} from '@radix-ui/colors'; -import { createBorderStyles, createColors, createSpaces } from 'bepaint'; - -// this isn't really a stitches config, but the object is compliant -export const theme = { - spaces: createSpaces(), - - colors: { - ...createColors(mauveDark), - ...createColors(slateDarkA, 'alpha'), - ...createColors(slate, 'light'), - - ...createColors(amberDark, 'amber'), - ...createColors(amberDarkA, 'amberAlpha'), - ...createColors(orangeDark, 'orange'), - ...createColors(purpleDark, 'purple'), - ...createColors(blueDark, 'blue'), - ...createColors(tealDark, 'teal'), - ...createColors(greenDark, 'green'), - ...createColors(redDark, 'red'), - ...createColors(brownDark, 'brown'), - ...createColors(mauveDark, 'mauve'), - ...createColors(sandDark, 'sand'), - ...createColors(crimsonDark, 'crimson'), - ...createColors(rubyDark, 'ruby'), - }, - - borderStyles: { - subtle: `1px solid ${slateDark.slate2}`, - ...createBorderStyles(slateDark), - ...createBorderStyles(blueDark, undefined, 'blue'), - }, - - durations: { - regular: '0.2s', - }, -}; diff --git a/packages/bot/src/types.d.ts b/packages/bot/src/types.d.ts deleted file mode 100644 index 29102695c..000000000 --- a/packages/bot/src/types.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -declare module '*.ttf' { - const path: string; - export default path; -} diff --git a/packages/bot/src/types/histories.ts b/packages/bot/src/types/histories.ts deleted file mode 100644 index 297552339..000000000 --- a/packages/bot/src/types/histories.ts +++ /dev/null @@ -1,24 +0,0 @@ -// a cleaner simplification of Blitz Stars' histories because it's a mess there - -import { type BlitzStats } from '@blitzkit/core'; - -export interface History { - all: BlitzStats; - last_battle_time: number; -} - -export type Histories = History[]; - -export interface GetHistoriesOptions { - start: number; - end: number; - includeLatestHistories: boolean; - includePreviousHistories: boolean; -} - -export const getHistoriesDefaultOptions: GetHistoriesOptions = { - start: -Infinity, - end: Infinity, - includeLatestHistories: false, - includePreviousHistories: false, -}; diff --git a/packages/bot/src/types/possiblyPromise.ts b/packages/bot/src/types/possiblyPromise.ts deleted file mode 100644 index a4658c873..000000000 --- a/packages/bot/src/types/possiblyPromise.ts +++ /dev/null @@ -1 +0,0 @@ -export type PossiblyPromise = Promise | Type; diff --git a/packages/bot/src/types/ttf.d.ts b/packages/bot/src/types/ttf.d.ts deleted file mode 100644 index 29102695c..000000000 --- a/packages/bot/src/types/ttf.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -declare module '*.ttf' { - const path: string; - export default path; -} diff --git a/packages/bot/src/types/writable.ts b/packages/bot/src/types/writable.ts deleted file mode 100644 index 2f45b1129..000000000 --- a/packages/bot/src/types/writable.ts +++ /dev/null @@ -1 +0,0 @@ -export type Writeable = { -readonly [P in keyof T]: T[P] }; diff --git a/packages/bot/src/workers/bot.ts b/packages/bot/src/workers/bot.ts deleted file mode 100644 index b3af429ca..000000000 --- a/packages/bot/src/workers/bot.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { assertSecret } from "@blitzkit/core"; -import { registerProcesses } from "../core/blitzkit/processes"; -import { client } from "../core/discord/client"; - -console.log(`🟡 Launching bot ${client.shard?.ids[0]}`); - -registerProcesses(); - -console.log(`post register processes on shard ${client.shard?.ids[0]}`); - -client.login(assertSecret(import.meta.env.DISCORD_TOKEN)); - -console.log(`post client login on shard ${client.shard?.ids[0]}`); diff --git a/packages/bot/src/workers/render.ts b/packages/bot/src/workers/render.ts deleted file mode 100644 index 1838e44ad..000000000 --- a/packages/bot/src/workers/render.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { parentPort, threadId } from 'worker_threads'; -import { svgToPng } from '../core/blitzkit/svgToPng'; - -console.log(`🟢 Launched render worker ${threadId}`); - -parentPort?.on('message', async (svg: string) => { - const png = svgToPng(svg); - parentPort?.postMessage(png); -}); - -console.log(`post render thread parent port on thread ${threadId}`); diff --git a/packages/bot/tsconfig.json b/packages/bot/tsconfig.json deleted file mode 100644 index d24722b0d..000000000 --- a/packages/bot/tsconfig.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "include": ["src/**/*", "../core/src/blitzkit/supportedLocales.ts", "../i18n/src/unwrapper.ts"] -} diff --git a/packages/i18n/strings/en.json b/packages/i18n/strings/en.json index 8e5746268..418c90783 100644 --- a/packages/i18n/strings/en.json +++ b/packages/i18n/strings/en.json @@ -204,536 +204,6 @@ } }, - "bot": { - "common": { - "errors": { - "clan_not_found": "# Could not find clan\nCouldn't find \"{search}\". Try selecting a clan from the search result.", - "player_not_found": "# Could not find user\nI couldn't find user \"{search}\". Try picking an user from the search result, typing in a valid username, or using the `/link` command.", - "player_not_linked": "# Use `/link` to get started\nI don't know who you are but you can tell me today!", - "tank_name_not_found": "# Tank not found\nCould not find tank by the name \"{search}\".", - "tank_id_not_found": "# Tank not found\nCould not find tank by the ID \"{search}\". Try typing the name of the tank instead of a number.", - "no_tank_stats": "# No tank stats available\nThis player doesn't have any stats available. This may not be the player you are looking for.", - "get_help": "Help Discord server", - "uncaught_error": "# BlitzKit ran into an error!\nWe're so sorry about this. Feel free to [join the official Discord server](https://discord.gg/nDt7AjGJQH) to get help.", - "preview": "# This command is in preview\n\nYou can only access this command in [the official Discord server](https://discord.gg/nDt7AjGJQH). The command will be generally available soon.", - "query_too_small": "# Your search is too short\nYour search must be at least 3 characters long.", - "query_too_long": "# Your search is too long\nYour search must be at most 100 characters long." - }, - - "wn8": { - "name": "WN8", - - "types": { - "very_bad": "Very bad", - "bad": "Bad", - "below_average": "Below average", - "average": "Average", - "above_average": "Above average", - "good": "Good", - "very_good": "Very good", - "great": "Great", - "unicum": "Unicum", - "super_unicum": "Super unicum" - } - }, - - "subcommands": { - "today": { - "name": "today", - "description": "Today" - }, - "30": { - "name": "30", - "description": "30 days" - }, - "60": { - "name": "60", - "description": "60 days" - }, - "90": { - "name": "90", - "description": "90 days" - }, - "career": { - "name": "career", - "description": "Career" - }, - "custom": { - "name": "custom", - "description": "Custom range", - - "options": { - "start": { - "name": "start", - "description": "Start in number of days ago from today" - }, - "end": { - "name": "end", - "description": "End in number of days ago from today" - } - } - } - }, - - "options": { - "clan": { - "name": "clan", - "description": "The clan name or tag" - }, - - "tank": { - "name": "tank", - "description": "The tank to get stats for" - }, - - "username": { - "name": "username", - "description": "The Blitz username of the player" - }, - - "filters": { - "nation": { - "name": "nation", - "description": "Nation" - }, - "class": { - "name": "class", - "description": "Class" - }, - "type": { - "name": "type", - "description": "Type" - } - }, - - "region": { - "name": "region", - "description": "Region" - }, - - "tier": { - "name": "tier", - "description": "Tier", - - "choices": { - "tier": "Tier {tier_1} - {tier_2}" - } - } - }, - - "periods": { - "career": "Career", - "today": "Today", - "days": "{days} days", - "custom": "{day_1} - {day_2} days" - }, - - "filters": { - "none": "No filters", - "tier": "Tier {tier}" - }, - - "no_data": { - "battles_in_period": "No battles played in this period", - "battles_in_season": "No battles played in this season", - "players_in_period": "No players in this period", - "tanks_found": "No tanks found" - }, - - "buttons": { - "refresh": "Refresh" - }, - - "hero_stats": { - "battles": "Battles", - "winrate": "Winrate", - "damage": "Damage", - "damage_ratio": "Damage ratio", - "tier": "Tier", - "survival": "Survival", - "accuracy": "Accuracy", - "kills": "Kills" - } - }, - - "commands": { - "dead": "# Stat Commands Are Dead\n\nThe BlitzKit Discord bot used to use BlitzStars to fetch periodic statistics. BlitzStars has enabled aggressive Cloudflare protections; hence, disabling BlitzKit from using BlitzStar's API. This has impacted the following commands:\n\n- `/stats`\n- `/today`\n- `/breakdown`\n- `/evolution`\n- `/full-stats`\n\nPlease use [Aftermath]() as an alternative which is completely independent of BlitzStars. BlitzKit-hosted stat commands will return in the near future.", - - "replay": { - "name": "replay", - "description": "Display replay results", - - "options": { - "file": { - "name": "file", - "description": "Replay file" - } - } - }, - - "research": { - "name": "research", - "description": "Lists the expenses in researching a line", - - "options": { - "target_tank": { - "name": "target-tank", - "description": "The final tank to research in the line" - }, - "starting_tank": { - "name": "starting-tank", - "description": "The starting tank in the line (inferred automatically if not provided)" - } - }, - - "body": { - "subtitle": "Research", - "total": "Total", - "research": "Research: {cost}", - "upgrades": "Upgrades: {cost}", - "purchase": "Purchase: {cost}", - "equipment": "Equipment: {cost}", - "estimation": "# This is an estimation!\n\nThis list may include already researched tanks because of the inaccurate information Wargaming provides. Try providing a `starting-tank` from the line when using the command if you can." - }, - - "errors": { - "non_tech_tree": "# {tank} is not on the tech tree\n\nYou cannot research a premium/collector tank.", - "tier_1": "# {tank} is a tier I\n\nTier I tanks are always unlocked for free for all players.", - "already_researched": "# {tank} is already researched\n\nTry providing a `starting-tank` when using the command to view the totals for a line you have already researched.", - "start_end_equal": "# {tank} was provided twice\n\nMake sure `starting-tank` and `target-tank` are different.", - "tanks_not_on_line": "# {tank_1} and {tank_2} are not on the same line\n\nMake sure `starting-tank` and `target-tank` are on the same tech-tree line.", - "unordered": "# {tank_1} higher tiered to {tank_2}\n\nMake sure to provide {tank_2} as the `starting-tank` instead as it is the lower tiered tank." - } - }, - - "about": { - "name": "about", - "description": "All the info you need about the bot", - "subcommands": { - "commands": { - "name": "commands", - "description": "List of all commands and their descriptions", - "body": "# Commands\n\nBlitzKit offers the following commands:\n\n" - }, - "embeds": { - "name": "embeds", - "description": "How embeds work" - }, - "introduction": { - "name": "introduction", - "description": "About the bot" - }, - "invite": { - "name": "invite", - "description": "How to invite the bot" - }, - "timezones": { - "name": "timezones", - "description": "How BlitzKit infers and uses your timezone" - }, - "credits": { - "name": "credits", - "description": "People who have helped BlitzKit" - } - } - }, - - "breakdown": { - "name": "breakdown", - "description": "A period's breakdown by tanks played", - - "body": { - "total": "Total", - "battles": "Battles", - "wn8": "WN8", - "winrate": "Winrate", - "damage": "Damage", - "unknown_tank": "Unknown tank {id}" - } - }, - - "debug": { - "name": "debug", - "description": "Debug information about the bot", - - "body": { - "title": "# Debug\n\n", - "version": "Version", - "shard": { - "label": "Shard", - "default": "default" - }, - "uptime": "Uptime" - } - }, - - "evolution": { - "name": "evolution", - "description": "Evolution of statistics", - - "groups": { - "player": { - "name": "player", - "description": "Player statistics" - }, - - "tank": { - "name": "tank", - "description": "Tank statistics" - } - } - }, - - "inactive": { - "name": "inactive", - "description": "Lists all inactive players in a clan above a threshold", - - "options": { - "threshold": { - "name": "threshold", - "description": "The number of days inactive (default: {days} days)" - } - }, - - "body": { - "subtitle": "Inactive for {days}+ days", - "listing": "{days} days" - } - }, - - "full_stats": { - "name": "full-stats", - "description": "Full in-game statistics", - - "body": { - "cards": { - "games": { - "title": "Games", - "winrate": "Winrate", - "battles": "Battles", - "wins_losses": "Wins / losses" - }, - "efficiency": { - "title": "Efficiency", - "wn8": "WN8", - "survival": "Survival", - "accuracy": "Accuracy" - }, - "lethality": { - "title": "Lethality", - "average_damage": "Average damage", - "damage_per_tier": "Damage per tier", - "damage_ratio": "Damage ratio", - "average_kills": "Average kills" - }, - "miscellaneous": { - "title": "Miscellaneous", - "average_tier": "Average tier", - "average_spots": "Average spots", - "average_xp": "Average XP", - "kills_to_death_ratio": "Kills to death ratio" - } - } - } - }, - - "link": { - "name": "link", - "description": "Links your Blitz and Discord account", - - "body": { - "accounts_linked": "Accounts linked", - "no_role_permissions": { - "title": "{name} failed to verify", - "description": "I don't have the permission to manage roles." - } - }, - - "embed": { - "title": "Incorrect account?", - "description": "Use the command again and select a username from the search results." - } - }, - - "search_clans": { - "name": "search-clans", - "description": "Search clans", - - "options": { - "limit": { - "name": "limit", - "description": "The max number of clan results (default: {limit})" - } - }, - - "body": { - "title": "Clan search for {search} in region {region}", - "no_results": "No clans found" - } - }, - - "search_players": { - "name": "search-players", - "description": "Search players", - - "options": { - "limit": { - "name": "limit", - "description": "The max number of player results (default: {limit})" - } - }, - - "body": { - "title": "Player search for {search} in region {region}", - "no_results": "No players found" - } - }, - - "rating_leaderboard": { - "name": "rating-leaderboard", - "description": "Rating battles statistics", - - "subcommands": { - "neighbors": { - "name": "neighbors", - "description": "Neighboring players on the leaderboard" - }, - - "league": { - "name": "league", - "description": "Top players of a league" - } - }, - - "options": { - "limit": { - "name": "limit", - "description": "How many neighbors to display (default: {count})" - }, - - "season": { - "name": "season", - "description": "Season number (default: current)", - - "choices": { - "current": "{season} (current)" - } - }, - - "league": { - "name": "league", - "description": "The league to display" - } - }, - - "body": { - "top_players": "Top {count} players", - "no_participation": "# {player} didn't play rating in season {season}\n\nThis player did not participate in this season or did not get past calibration.", - "no_ongoing_season": "# No ongoing season\n\nThere isn't a currently running rating season.", - "deleted_player": "Deleted {id}", - "neighbors": "Rating neighbors", - "more": "+ {count} more" - } - }, - - "today": { - "name": "today", - "description": "Today's played tanks and statistics" - }, - - "stats": { - "name": "stats", - "description": "Quick statistics for a period" - }, - - "owned_tanks": { - "name": "owned-tanks", - "description": "Shows all own tanks in a tier", - - "body": { - "title": "# {name}'s owned tier {tier} tanks\n\n" - } - }, - - "permissions": { - "name": "permissions", - "description": "Checks wether the bot has the required permissions to function", - - "body": { - "title": "# Permissions\n\n{body}", - "view_channels": "View channels: needed to find the message the refresh button was used on", - "read_message_history": "Read message history: needed to find the message to refresh", - "attach_files": "Attach files: needed to add images to image-based commands", - "external_emojis": "External emojis: needed to add national flags and tank class emojis" - } - }, - - "ping": { - "name": "ping", - "description": "Check wether the bot is alive.", - - "body": { - "pong": "Pong 🏓", - "pong_with_time": "Pong 🏓 - {time}ms" - }, - - "subcommands": { - "blitzkit": { - "name": "blitzkit", - "description": "Ping BlitzKit" - }, - "wotb": { - "name": "wotb", - "description": "Ping World of Tanks Blitz" - } - } - }, - - "player_info": { - "name": "player-info", - "description": "Basic information about a player", - - "body": { - "title": "{name}'s information", - "nickname": "Nickname", - "battles": "Battles", - "winrate": "Winrate", - "account_id": "Account ID", - "created": "Created", - "last_battle": "Last battle" - } - }, - - "rating": { - "name": "rating", - "description": "Rating player's statistics", - - "subcommands": { - "today": { - "name": "today", - "description": "Today's rating statistics" - }, - "season": { - "name": "season", - "description": "This season's rating statistics" - } - }, - - "body": { - "subtitle": { - "today": "Today's rating statistics", - "season": "This season's rating statistics" - } - }, - - "errors": { - "no_participation": "# {name} hasn't played rating\n\nThis player hasn't participated in the rating gamemode this season.", - "unrecorded_stats": "# Statistics currently unavailable\n\nWargaming may be currently be facing server downtime. If there is an on-going season, statistics should be available withing 24 hours." - } - } - } - }, - "website": { "redirect": { "title": "301: moved permanently", diff --git a/packages/scripts/package.json b/packages/scripts/package.json index 8e788dc2d..41678e3ad 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -41,7 +41,6 @@ "setup:patches": "bun src/applyPatches.ts", "build:reviews": "bun src/reviews.ts", "build:ids": "bun src/discoverIds.ts", - "build:bot": "bun src/buildBot.ts", "build:ratingleaderboard": "bun src/buildRatingLeaderboard.ts", "build:proxyclient": "bun src/buildProxyClient.ts", "build:catalogitemaccesssor": "bun src/buildCatalogItemAccessor.ts", diff --git a/packages/scripts/src/buildBot.ts b/packages/scripts/src/buildBot.ts deleted file mode 100644 index 379bfc72a..000000000 --- a/packages/scripts/src/buildBot.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { build } from "esbuild"; -import { cp, readdir, rm } from "fs/promises"; - -const projectDir = "../.."; -const srcRoot = `${projectDir}/packages/bot`; -const distRoot = `${projectDir}/dist/bot`; -const modulesRoot = "../../node_modules"; -const workers = await readdir(`${srcRoot}/src/workers`); - -await rm(distRoot, { recursive: true, force: true }); - -build({ - entryPoints: [ - `${srcRoot}/src/main.ts`, - ...workers.map((worker) => `${srcRoot}/src/workers/${worker}`), - ], - bundle: true, - outdir: "../../dist/bot", - platform: "node", - format: "esm", - loader: { - ".ttf": "file", - ".node": "empty", - ".json": "json", - }, - external: ["sharp"], - minifyIdentifiers: false, // causes goofy ahh issues - minifySyntax: true, - minifyWhitespace: true, - sourcemap: true, - jsxImportSource: "react", - jsx: "automatic", -}); - -for (const module of await readdir(`${modulesRoot}/@resvg`)) { - readdir(`${modulesRoot}/@resvg/${module}`).then((files) => - files - .filter((file) => file.endsWith(".node")) - .map((file) => - cp( - `${modulesRoot}/@resvg/${module}/${file}`, - `${distRoot}/workers/${file}`, - ), - ), - ); -} - -await cp(`${projectDir}/prisma`, `${distRoot}/prisma`, { recursive: true }); - -for (const file of await readdir(`${modulesRoot}/prisma`).then((files) => - files.filter((file) => file.endsWith(".node")), -)) { - cp(`${modulesRoot}/prisma/${file}`, `${distRoot}/prisma/${file}`); -} diff --git a/prisma/schema.prisma b/prisma/schema.prisma deleted file mode 100644 index dcc620ae3..000000000 --- a/prisma/schema.prisma +++ /dev/null @@ -1,24 +0,0 @@ -generator client { - provider = "prisma-client-js" - binaryTargets = ["native", "debian-openssl-1.1.x", "debian-openssl-3.0.x"] - output = "../packages/bot/src/prisma" -} - -datasource db { - provider = "cockroachdb" - url = env("DATABASE_URL") -} - -model User { - blitz_id BigInt @id @map("blitz") // map: "blitz" was too vague - last_used DateTime? @default(now()) - - @@map("Users") // map: "Users" was mistakenly plural -} - -model DiscordUser { - discord_id BigInt @id @map("discord") // map: "discord" was too vague - blitz_id BigInt @map("blitz") // map: "blitz" was too vague - - @@map("DiscordUsers") // map: "DiscordUsers" was mistakenly plural -} From c2a5b742a7a93ec0f5e1b13bfaa2bda3d779f7c0 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Fri, 31 Jul 2026 20:21:27 -0500 Subject: [PATCH 004/147] clean up root --- package.json | 31 +--------------------- packages/website/src/pages/api/index.astro | 7 ----- packages/website/src/pages/debug.astro | 1 - 3 files changed, 1 insertion(+), 38 deletions(-) delete mode 100644 packages/website/src/pages/api/index.astro delete mode 100644 packages/website/src/pages/debug.astro diff --git a/package.json b/package.json index 8064764f0..85067f6ec 100644 --- a/package.json +++ b/package.json @@ -4,36 +4,7 @@ "license": "BlitzKit License", "private": true, "scripts": { - "build:assets": "cd packages/scripts && bun run build:assets", - "build:averages": "bun --filter=@blitzkit/scripts run build:averages", - "build:website": "cd packages/website && bun run build", - "build:prisma": "prisma generate", - "build:proxyclient": "cd packages/scripts && bun run build:proxyclient", - "build:catalogitemaccesssor": "cd packages/scripts && bun run build:catalogitemaccesssor", - "build:ratingleaderboard": "cd packages/scripts && bun run build:ratingleaderboard", - "build:app": "cd packages/app && bun run build", - "dev:docs": "vitepress dev docs", - "dev:website": "cd packages/website && bun run dev", - "dev:app": "cd packages/app && bun run dev", - "dev": "bun run dev:website", - "dev:api": "bun --filter=@blitzkit/api run dev", - "setup:depotdownloader": "bun --filter=@blitzkit/scripts run setup:depotdownloader", - "setup:patches": "mkdir -p packages/closed/node_modules && ln -sf ../../../node_modules/lz4js packages/closed/node_modules/lz4js && cd packages/scripts && bun run setup:patches", - "start:website": "bun --filter=@blitzkit/website run start", - "host:docs": "vitepress preview docs", - "lint": "tsc", - "lint:website": "cd packages/website && bun run lint", - "format": "prettier --write \"**/*.@(json|js|jsx|css|md|ts|tsx|html|xml|yml|gltf)\"", - "update": "yarn upgrade --latest", - "tunnel": "cloudflared tunnel --url http://localhost:3000", - "discoverids": "scripts/discoverIds.ts", - "build:protos": "cd packages/scripts && bun run build:protos", - "preview:website": "cd packages/website && bun run preview", - "build:reviews": "cd packages/scripts && bun build:reviews", - "build:ids": "cd packages/scripts && bun build:ids", - "prunestrings": "cd packages/scripts && bun prunestrings", - "setup": "bun install && bun run build:prisma", - "patchnotes": "cd packages/scripts && bun run patchnotes" + "dev": "cd packages/website && bun run dev" }, "type": "module", "workspaces": [ diff --git a/packages/website/src/pages/api/index.astro b/packages/website/src/pages/api/index.astro deleted file mode 100644 index b6ceca726..000000000 --- a/packages/website/src/pages/api/index.astro +++ /dev/null @@ -1,7 +0,0 @@ ---- -import locales from "@blitzkit/i18n/locales.json"; -import { tools } from "../../constants/tools"; -import ToolLayout from "../../layouts/ToolLayout.astro"; ---- - - diff --git a/packages/website/src/pages/debug.astro b/packages/website/src/pages/debug.astro deleted file mode 100644 index d287cd3ed..000000000 --- a/packages/website/src/pages/debug.astro +++ /dev/null @@ -1 +0,0 @@ -debug From ad15f83abb477044d7be5990cdd407a53bea0b02 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Fri, 31 Jul 2026 20:22:12 -0500 Subject: [PATCH 005/147] move to chunks --- .../{ => [...chunk-pages]}/[...locale]/auth/[provider]/_index.tsx | 0 .../[...locale]/auth/[provider]/index.astro | 0 .../pages/{ => [...chunk-pages]}/[...locale]/compare/_index.tsx | 0 .../pages/{ => [...chunk-pages]}/[...locale]/compare/index.astro | 0 .../[...locale]/docs/[directory]/[document]/_index.tsx | 0 .../[...locale]/docs/[directory]/[document]/index.astro | 0 .../{ => [...chunk-pages]}/[...locale]/docs/patches/index.astro | 0 .../{ => [...chunk-pages]}/[...locale]/embed/[embed]/_index.tsx | 0 .../[...locale]/embed/[embed]/host/_index.tsx | 0 .../[...locale]/embed/[embed]/host/index.astro | 0 .../{ => [...chunk-pages]}/[...locale]/embed/[embed]/index.astro | 0 .../src/pages/{ => [...chunk-pages]}/[...locale]/embed/_index.tsx | 0 .../pages/{ => [...chunk-pages]}/[...locale]/embed/index.astro | 0 .../pages/{ => [...chunk-pages]}/[...locale]/gallery/_index.tsx | 0 .../pages/{ => [...chunk-pages]}/[...locale]/gallery/index.astro | 0 .../src/pages/{ => [...chunk-pages]}/[...locale]/guess/_index.tsx | 0 .../pages/{ => [...chunk-pages]}/[...locale]/guess/index.astro | 0 .../src/pages/{ => [...chunk-pages]}/[...locale]/index.astro | 0 .../src/pages/{ => [...chunk-pages]}/[...locale]/mixer/_index.tsx | 0 .../pages/{ => [...chunk-pages]}/[...locale]/mixer/index.astro | 0 .../{ => [...chunk-pages]}/[...locale]/performance/_index.tsx | 0 .../{ => [...chunk-pages]}/[...locale]/performance/index.astro | 0 .../pages/{ => [...chunk-pages]}/[...locale]/playlist/_index.tsx | 0 .../pages/{ => [...chunk-pages]}/[...locale]/playlist/index.astro | 0 .../pages/{ => [...chunk-pages]}/[...locale]/session/_index.tsx | 0 .../pages/{ => [...chunk-pages]}/[...locale]/session/index.astro | 0 .../pages/{ => [...chunk-pages]}/[...locale]/settings/_index.tsx | 0 .../pages/{ => [...chunk-pages]}/[...locale]/settings/index.astro | 0 .../{ => [...chunk-pages]}/[...locale]/tanks/[slug]/_index.tsx | 0 .../{ => [...chunk-pages]}/[...locale]/tanks/[slug]/index.astro | 0 .../src/pages/{ => [...chunk-pages]}/[...locale]/tanks/_index.tsx | 0 .../pages/{ => [...chunk-pages]}/[...locale]/tanks/index.astro | 0 .../pages/{ => [...chunk-pages]}/[...locale]/tier-list/_index.tsx | 0 .../{ => [...chunk-pages]}/[...locale]/tier-list/index.astro | 0 .../{ => [...chunk-pages]}/[...locale]/tools/compare/index.astro | 0 .../[...locale]/tools/embed/[embed]/index.astro | 0 .../{ => [...chunk-pages]}/[...locale]/tools/embed/index.astro | 0 .../{ => [...chunk-pages]}/[...locale]/tools/gallery/index.astro | 0 .../[...locale]/tools/performance/index.astro | 0 .../{ => [...chunk-pages]}/[...locale]/tools/session/index.astro | 0 .../[...locale]/tools/tankopedia/[id]/index.astro | 0 .../[...locale]/tools/tankopedia/index.astro | 0 .../[...locale]/tools/tier-list/index.astro | 0 43 files changed, 0 insertions(+), 0 deletions(-) rename packages/website/src/pages/{ => [...chunk-pages]}/[...locale]/auth/[provider]/_index.tsx (100%) rename packages/website/src/pages/{ => [...chunk-pages]}/[...locale]/auth/[provider]/index.astro (100%) rename packages/website/src/pages/{ => [...chunk-pages]}/[...locale]/compare/_index.tsx (100%) rename packages/website/src/pages/{ => [...chunk-pages]}/[...locale]/compare/index.astro (100%) rename packages/website/src/pages/{ => [...chunk-pages]}/[...locale]/docs/[directory]/[document]/_index.tsx (100%) rename packages/website/src/pages/{ => [...chunk-pages]}/[...locale]/docs/[directory]/[document]/index.astro (100%) rename packages/website/src/pages/{ => [...chunk-pages]}/[...locale]/docs/patches/index.astro (100%) rename packages/website/src/pages/{ => [...chunk-pages]}/[...locale]/embed/[embed]/_index.tsx (100%) rename packages/website/src/pages/{ => [...chunk-pages]}/[...locale]/embed/[embed]/host/_index.tsx (100%) rename packages/website/src/pages/{ => [...chunk-pages]}/[...locale]/embed/[embed]/host/index.astro (100%) rename packages/website/src/pages/{ => [...chunk-pages]}/[...locale]/embed/[embed]/index.astro (100%) rename packages/website/src/pages/{ => [...chunk-pages]}/[...locale]/embed/_index.tsx (100%) rename packages/website/src/pages/{ => [...chunk-pages]}/[...locale]/embed/index.astro (100%) rename packages/website/src/pages/{ => [...chunk-pages]}/[...locale]/gallery/_index.tsx (100%) rename packages/website/src/pages/{ => [...chunk-pages]}/[...locale]/gallery/index.astro (100%) rename packages/website/src/pages/{ => [...chunk-pages]}/[...locale]/guess/_index.tsx (100%) rename packages/website/src/pages/{ => [...chunk-pages]}/[...locale]/guess/index.astro (100%) rename packages/website/src/pages/{ => [...chunk-pages]}/[...locale]/index.astro (100%) rename packages/website/src/pages/{ => [...chunk-pages]}/[...locale]/mixer/_index.tsx (100%) rename packages/website/src/pages/{ => [...chunk-pages]}/[...locale]/mixer/index.astro (100%) rename packages/website/src/pages/{ => [...chunk-pages]}/[...locale]/performance/_index.tsx (100%) rename packages/website/src/pages/{ => [...chunk-pages]}/[...locale]/performance/index.astro (100%) rename packages/website/src/pages/{ => [...chunk-pages]}/[...locale]/playlist/_index.tsx (100%) rename packages/website/src/pages/{ => [...chunk-pages]}/[...locale]/playlist/index.astro (100%) rename packages/website/src/pages/{ => [...chunk-pages]}/[...locale]/session/_index.tsx (100%) rename packages/website/src/pages/{ => [...chunk-pages]}/[...locale]/session/index.astro (100%) rename packages/website/src/pages/{ => [...chunk-pages]}/[...locale]/settings/_index.tsx (100%) rename packages/website/src/pages/{ => [...chunk-pages]}/[...locale]/settings/index.astro (100%) rename packages/website/src/pages/{ => [...chunk-pages]}/[...locale]/tanks/[slug]/_index.tsx (100%) rename packages/website/src/pages/{ => [...chunk-pages]}/[...locale]/tanks/[slug]/index.astro (100%) rename packages/website/src/pages/{ => [...chunk-pages]}/[...locale]/tanks/_index.tsx (100%) rename packages/website/src/pages/{ => [...chunk-pages]}/[...locale]/tanks/index.astro (100%) rename packages/website/src/pages/{ => [...chunk-pages]}/[...locale]/tier-list/_index.tsx (100%) rename packages/website/src/pages/{ => [...chunk-pages]}/[...locale]/tier-list/index.astro (100%) rename packages/website/src/pages/{ => [...chunk-pages]}/[...locale]/tools/compare/index.astro (100%) rename packages/website/src/pages/{ => [...chunk-pages]}/[...locale]/tools/embed/[embed]/index.astro (100%) rename packages/website/src/pages/{ => [...chunk-pages]}/[...locale]/tools/embed/index.astro (100%) rename packages/website/src/pages/{ => [...chunk-pages]}/[...locale]/tools/gallery/index.astro (100%) rename packages/website/src/pages/{ => [...chunk-pages]}/[...locale]/tools/performance/index.astro (100%) rename packages/website/src/pages/{ => [...chunk-pages]}/[...locale]/tools/session/index.astro (100%) rename packages/website/src/pages/{ => [...chunk-pages]}/[...locale]/tools/tankopedia/[id]/index.astro (100%) rename packages/website/src/pages/{ => [...chunk-pages]}/[...locale]/tools/tankopedia/index.astro (100%) rename packages/website/src/pages/{ => [...chunk-pages]}/[...locale]/tools/tier-list/index.astro (100%) diff --git a/packages/website/src/pages/[...locale]/auth/[provider]/_index.tsx b/packages/website/src/pages/[...chunk-pages]/[...locale]/auth/[provider]/_index.tsx similarity index 100% rename from packages/website/src/pages/[...locale]/auth/[provider]/_index.tsx rename to packages/website/src/pages/[...chunk-pages]/[...locale]/auth/[provider]/_index.tsx diff --git a/packages/website/src/pages/[...locale]/auth/[provider]/index.astro b/packages/website/src/pages/[...chunk-pages]/[...locale]/auth/[provider]/index.astro similarity index 100% rename from packages/website/src/pages/[...locale]/auth/[provider]/index.astro rename to packages/website/src/pages/[...chunk-pages]/[...locale]/auth/[provider]/index.astro diff --git a/packages/website/src/pages/[...locale]/compare/_index.tsx b/packages/website/src/pages/[...chunk-pages]/[...locale]/compare/_index.tsx similarity index 100% rename from packages/website/src/pages/[...locale]/compare/_index.tsx rename to packages/website/src/pages/[...chunk-pages]/[...locale]/compare/_index.tsx diff --git a/packages/website/src/pages/[...locale]/compare/index.astro b/packages/website/src/pages/[...chunk-pages]/[...locale]/compare/index.astro similarity index 100% rename from packages/website/src/pages/[...locale]/compare/index.astro rename to packages/website/src/pages/[...chunk-pages]/[...locale]/compare/index.astro diff --git a/packages/website/src/pages/[...locale]/docs/[directory]/[document]/_index.tsx b/packages/website/src/pages/[...chunk-pages]/[...locale]/docs/[directory]/[document]/_index.tsx similarity index 100% rename from packages/website/src/pages/[...locale]/docs/[directory]/[document]/_index.tsx rename to packages/website/src/pages/[...chunk-pages]/[...locale]/docs/[directory]/[document]/_index.tsx diff --git a/packages/website/src/pages/[...locale]/docs/[directory]/[document]/index.astro b/packages/website/src/pages/[...chunk-pages]/[...locale]/docs/[directory]/[document]/index.astro similarity index 100% rename from packages/website/src/pages/[...locale]/docs/[directory]/[document]/index.astro rename to packages/website/src/pages/[...chunk-pages]/[...locale]/docs/[directory]/[document]/index.astro diff --git a/packages/website/src/pages/[...locale]/docs/patches/index.astro b/packages/website/src/pages/[...chunk-pages]/[...locale]/docs/patches/index.astro similarity index 100% rename from packages/website/src/pages/[...locale]/docs/patches/index.astro rename to packages/website/src/pages/[...chunk-pages]/[...locale]/docs/patches/index.astro diff --git a/packages/website/src/pages/[...locale]/embed/[embed]/_index.tsx b/packages/website/src/pages/[...chunk-pages]/[...locale]/embed/[embed]/_index.tsx similarity index 100% rename from packages/website/src/pages/[...locale]/embed/[embed]/_index.tsx rename to packages/website/src/pages/[...chunk-pages]/[...locale]/embed/[embed]/_index.tsx diff --git a/packages/website/src/pages/[...locale]/embed/[embed]/host/_index.tsx b/packages/website/src/pages/[...chunk-pages]/[...locale]/embed/[embed]/host/_index.tsx similarity index 100% rename from packages/website/src/pages/[...locale]/embed/[embed]/host/_index.tsx rename to packages/website/src/pages/[...chunk-pages]/[...locale]/embed/[embed]/host/_index.tsx diff --git a/packages/website/src/pages/[...locale]/embed/[embed]/host/index.astro b/packages/website/src/pages/[...chunk-pages]/[...locale]/embed/[embed]/host/index.astro similarity index 100% rename from packages/website/src/pages/[...locale]/embed/[embed]/host/index.astro rename to packages/website/src/pages/[...chunk-pages]/[...locale]/embed/[embed]/host/index.astro diff --git a/packages/website/src/pages/[...locale]/embed/[embed]/index.astro b/packages/website/src/pages/[...chunk-pages]/[...locale]/embed/[embed]/index.astro similarity index 100% rename from packages/website/src/pages/[...locale]/embed/[embed]/index.astro rename to packages/website/src/pages/[...chunk-pages]/[...locale]/embed/[embed]/index.astro diff --git a/packages/website/src/pages/[...locale]/embed/_index.tsx b/packages/website/src/pages/[...chunk-pages]/[...locale]/embed/_index.tsx similarity index 100% rename from packages/website/src/pages/[...locale]/embed/_index.tsx rename to packages/website/src/pages/[...chunk-pages]/[...locale]/embed/_index.tsx diff --git a/packages/website/src/pages/[...locale]/embed/index.astro b/packages/website/src/pages/[...chunk-pages]/[...locale]/embed/index.astro similarity index 100% rename from packages/website/src/pages/[...locale]/embed/index.astro rename to packages/website/src/pages/[...chunk-pages]/[...locale]/embed/index.astro diff --git a/packages/website/src/pages/[...locale]/gallery/_index.tsx b/packages/website/src/pages/[...chunk-pages]/[...locale]/gallery/_index.tsx similarity index 100% rename from packages/website/src/pages/[...locale]/gallery/_index.tsx rename to packages/website/src/pages/[...chunk-pages]/[...locale]/gallery/_index.tsx diff --git a/packages/website/src/pages/[...locale]/gallery/index.astro b/packages/website/src/pages/[...chunk-pages]/[...locale]/gallery/index.astro similarity index 100% rename from packages/website/src/pages/[...locale]/gallery/index.astro rename to packages/website/src/pages/[...chunk-pages]/[...locale]/gallery/index.astro diff --git a/packages/website/src/pages/[...locale]/guess/_index.tsx b/packages/website/src/pages/[...chunk-pages]/[...locale]/guess/_index.tsx similarity index 100% rename from packages/website/src/pages/[...locale]/guess/_index.tsx rename to packages/website/src/pages/[...chunk-pages]/[...locale]/guess/_index.tsx diff --git a/packages/website/src/pages/[...locale]/guess/index.astro b/packages/website/src/pages/[...chunk-pages]/[...locale]/guess/index.astro similarity index 100% rename from packages/website/src/pages/[...locale]/guess/index.astro rename to packages/website/src/pages/[...chunk-pages]/[...locale]/guess/index.astro diff --git a/packages/website/src/pages/[...locale]/index.astro b/packages/website/src/pages/[...chunk-pages]/[...locale]/index.astro similarity index 100% rename from packages/website/src/pages/[...locale]/index.astro rename to packages/website/src/pages/[...chunk-pages]/[...locale]/index.astro diff --git a/packages/website/src/pages/[...locale]/mixer/_index.tsx b/packages/website/src/pages/[...chunk-pages]/[...locale]/mixer/_index.tsx similarity index 100% rename from packages/website/src/pages/[...locale]/mixer/_index.tsx rename to packages/website/src/pages/[...chunk-pages]/[...locale]/mixer/_index.tsx diff --git a/packages/website/src/pages/[...locale]/mixer/index.astro b/packages/website/src/pages/[...chunk-pages]/[...locale]/mixer/index.astro similarity index 100% rename from packages/website/src/pages/[...locale]/mixer/index.astro rename to packages/website/src/pages/[...chunk-pages]/[...locale]/mixer/index.astro diff --git a/packages/website/src/pages/[...locale]/performance/_index.tsx b/packages/website/src/pages/[...chunk-pages]/[...locale]/performance/_index.tsx similarity index 100% rename from packages/website/src/pages/[...locale]/performance/_index.tsx rename to packages/website/src/pages/[...chunk-pages]/[...locale]/performance/_index.tsx diff --git a/packages/website/src/pages/[...locale]/performance/index.astro b/packages/website/src/pages/[...chunk-pages]/[...locale]/performance/index.astro similarity index 100% rename from packages/website/src/pages/[...locale]/performance/index.astro rename to packages/website/src/pages/[...chunk-pages]/[...locale]/performance/index.astro diff --git a/packages/website/src/pages/[...locale]/playlist/_index.tsx b/packages/website/src/pages/[...chunk-pages]/[...locale]/playlist/_index.tsx similarity index 100% rename from packages/website/src/pages/[...locale]/playlist/_index.tsx rename to packages/website/src/pages/[...chunk-pages]/[...locale]/playlist/_index.tsx diff --git a/packages/website/src/pages/[...locale]/playlist/index.astro b/packages/website/src/pages/[...chunk-pages]/[...locale]/playlist/index.astro similarity index 100% rename from packages/website/src/pages/[...locale]/playlist/index.astro rename to packages/website/src/pages/[...chunk-pages]/[...locale]/playlist/index.astro diff --git a/packages/website/src/pages/[...locale]/session/_index.tsx b/packages/website/src/pages/[...chunk-pages]/[...locale]/session/_index.tsx similarity index 100% rename from packages/website/src/pages/[...locale]/session/_index.tsx rename to packages/website/src/pages/[...chunk-pages]/[...locale]/session/_index.tsx diff --git a/packages/website/src/pages/[...locale]/session/index.astro b/packages/website/src/pages/[...chunk-pages]/[...locale]/session/index.astro similarity index 100% rename from packages/website/src/pages/[...locale]/session/index.astro rename to packages/website/src/pages/[...chunk-pages]/[...locale]/session/index.astro diff --git a/packages/website/src/pages/[...locale]/settings/_index.tsx b/packages/website/src/pages/[...chunk-pages]/[...locale]/settings/_index.tsx similarity index 100% rename from packages/website/src/pages/[...locale]/settings/_index.tsx rename to packages/website/src/pages/[...chunk-pages]/[...locale]/settings/_index.tsx diff --git a/packages/website/src/pages/[...locale]/settings/index.astro b/packages/website/src/pages/[...chunk-pages]/[...locale]/settings/index.astro similarity index 100% rename from packages/website/src/pages/[...locale]/settings/index.astro rename to packages/website/src/pages/[...chunk-pages]/[...locale]/settings/index.astro diff --git a/packages/website/src/pages/[...locale]/tanks/[slug]/_index.tsx b/packages/website/src/pages/[...chunk-pages]/[...locale]/tanks/[slug]/_index.tsx similarity index 100% rename from packages/website/src/pages/[...locale]/tanks/[slug]/_index.tsx rename to packages/website/src/pages/[...chunk-pages]/[...locale]/tanks/[slug]/_index.tsx diff --git a/packages/website/src/pages/[...locale]/tanks/[slug]/index.astro b/packages/website/src/pages/[...chunk-pages]/[...locale]/tanks/[slug]/index.astro similarity index 100% rename from packages/website/src/pages/[...locale]/tanks/[slug]/index.astro rename to packages/website/src/pages/[...chunk-pages]/[...locale]/tanks/[slug]/index.astro diff --git a/packages/website/src/pages/[...locale]/tanks/_index.tsx b/packages/website/src/pages/[...chunk-pages]/[...locale]/tanks/_index.tsx similarity index 100% rename from packages/website/src/pages/[...locale]/tanks/_index.tsx rename to packages/website/src/pages/[...chunk-pages]/[...locale]/tanks/_index.tsx diff --git a/packages/website/src/pages/[...locale]/tanks/index.astro b/packages/website/src/pages/[...chunk-pages]/[...locale]/tanks/index.astro similarity index 100% rename from packages/website/src/pages/[...locale]/tanks/index.astro rename to packages/website/src/pages/[...chunk-pages]/[...locale]/tanks/index.astro diff --git a/packages/website/src/pages/[...locale]/tier-list/_index.tsx b/packages/website/src/pages/[...chunk-pages]/[...locale]/tier-list/_index.tsx similarity index 100% rename from packages/website/src/pages/[...locale]/tier-list/_index.tsx rename to packages/website/src/pages/[...chunk-pages]/[...locale]/tier-list/_index.tsx diff --git a/packages/website/src/pages/[...locale]/tier-list/index.astro b/packages/website/src/pages/[...chunk-pages]/[...locale]/tier-list/index.astro similarity index 100% rename from packages/website/src/pages/[...locale]/tier-list/index.astro rename to packages/website/src/pages/[...chunk-pages]/[...locale]/tier-list/index.astro diff --git a/packages/website/src/pages/[...locale]/tools/compare/index.astro b/packages/website/src/pages/[...chunk-pages]/[...locale]/tools/compare/index.astro similarity index 100% rename from packages/website/src/pages/[...locale]/tools/compare/index.astro rename to packages/website/src/pages/[...chunk-pages]/[...locale]/tools/compare/index.astro diff --git a/packages/website/src/pages/[...locale]/tools/embed/[embed]/index.astro b/packages/website/src/pages/[...chunk-pages]/[...locale]/tools/embed/[embed]/index.astro similarity index 100% rename from packages/website/src/pages/[...locale]/tools/embed/[embed]/index.astro rename to packages/website/src/pages/[...chunk-pages]/[...locale]/tools/embed/[embed]/index.astro diff --git a/packages/website/src/pages/[...locale]/tools/embed/index.astro b/packages/website/src/pages/[...chunk-pages]/[...locale]/tools/embed/index.astro similarity index 100% rename from packages/website/src/pages/[...locale]/tools/embed/index.astro rename to packages/website/src/pages/[...chunk-pages]/[...locale]/tools/embed/index.astro diff --git a/packages/website/src/pages/[...locale]/tools/gallery/index.astro b/packages/website/src/pages/[...chunk-pages]/[...locale]/tools/gallery/index.astro similarity index 100% rename from packages/website/src/pages/[...locale]/tools/gallery/index.astro rename to packages/website/src/pages/[...chunk-pages]/[...locale]/tools/gallery/index.astro diff --git a/packages/website/src/pages/[...locale]/tools/performance/index.astro b/packages/website/src/pages/[...chunk-pages]/[...locale]/tools/performance/index.astro similarity index 100% rename from packages/website/src/pages/[...locale]/tools/performance/index.astro rename to packages/website/src/pages/[...chunk-pages]/[...locale]/tools/performance/index.astro diff --git a/packages/website/src/pages/[...locale]/tools/session/index.astro b/packages/website/src/pages/[...chunk-pages]/[...locale]/tools/session/index.astro similarity index 100% rename from packages/website/src/pages/[...locale]/tools/session/index.astro rename to packages/website/src/pages/[...chunk-pages]/[...locale]/tools/session/index.astro diff --git a/packages/website/src/pages/[...locale]/tools/tankopedia/[id]/index.astro b/packages/website/src/pages/[...chunk-pages]/[...locale]/tools/tankopedia/[id]/index.astro similarity index 100% rename from packages/website/src/pages/[...locale]/tools/tankopedia/[id]/index.astro rename to packages/website/src/pages/[...chunk-pages]/[...locale]/tools/tankopedia/[id]/index.astro diff --git a/packages/website/src/pages/[...locale]/tools/tankopedia/index.astro b/packages/website/src/pages/[...chunk-pages]/[...locale]/tools/tankopedia/index.astro similarity index 100% rename from packages/website/src/pages/[...locale]/tools/tankopedia/index.astro rename to packages/website/src/pages/[...chunk-pages]/[...locale]/tools/tankopedia/index.astro diff --git a/packages/website/src/pages/[...locale]/tools/tier-list/index.astro b/packages/website/src/pages/[...chunk-pages]/[...locale]/tools/tier-list/index.astro similarity index 100% rename from packages/website/src/pages/[...locale]/tools/tier-list/index.astro rename to packages/website/src/pages/[...chunk-pages]/[...locale]/tools/tier-list/index.astro From 251d40b0f4da6542fff99e062dede6ca9cf9120d Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Fri, 31 Jul 2026 20:23:39 -0500 Subject: [PATCH 006/147] fix typo --- .../[...locale]/auth/[provider]/_index.tsx | 0 .../[...locale]/auth/[provider]/index.astro | 0 .../[...locale]/compare/_index.tsx | 0 .../[...locale]/compare/index.astro | 0 .../docs/[directory]/[document]/_index.tsx | 0 .../docs/[directory]/[document]/index.astro | 0 .../[...locale]/docs/patches/index.astro | 0 .../[...locale]/embed/[embed]/_index.tsx | 0 .../[...locale]/embed/[embed]/host/_index.tsx | 0 .../embed/[embed]/host/index.astro | 0 .../[...locale]/embed/[embed]/index.astro | 0 .../[...locale]/embed/_index.tsx | 0 .../[...locale]/embed/index.astro | 0 .../[...locale]/gallery/_index.tsx | 0 .../[...locale]/gallery/index.astro | 0 .../[...locale]/guess/_index.tsx | 0 .../[...locale]/guess/index.astro | 0 .../[...locale]/index.astro | 30 +++++-------------- .../[...locale]/mixer/_index.tsx | 0 .../[...locale]/mixer/index.astro | 0 .../[...locale]/performance/_index.tsx | 0 .../[...locale]/performance/index.astro | 0 .../[...locale]/playlist/_index.tsx | 0 .../[...locale]/playlist/index.astro | 0 .../[...locale]/session/_index.tsx | 0 .../[...locale]/session/index.astro | 0 .../[...locale]/settings/_index.tsx | 0 .../[...locale]/settings/index.astro | 0 .../[...locale]/tanks/[slug]/_index.tsx | 0 .../[...locale]/tanks/[slug]/index.astro | 0 .../[...locale]/tanks/_index.tsx | 0 .../[...locale]/tanks/index.astro | 0 .../[...locale]/tier-list/_index.tsx | 0 .../[...locale]/tier-list/index.astro | 0 .../[...locale]/tools/compare/index.astro | 0 .../tools/embed/[embed]/index.astro | 0 .../[...locale]/tools/embed/index.astro | 0 .../[...locale]/tools/gallery/index.astro | 0 .../[...locale]/tools/performance/index.astro | 0 .../[...locale]/tools/session/index.astro | 0 .../tools/tankopedia/[id]/index.astro | 0 .../[...locale]/tools/tankopedia/index.astro | 0 .../[...locale]/tools/tier-list/index.astro | 0 43 files changed, 7 insertions(+), 23 deletions(-) rename packages/website/src/pages/{[...chunk-pages] => [...chunk_pages]}/[...locale]/auth/[provider]/_index.tsx (100%) rename packages/website/src/pages/{[...chunk-pages] => [...chunk_pages]}/[...locale]/auth/[provider]/index.astro (100%) rename packages/website/src/pages/{[...chunk-pages] => [...chunk_pages]}/[...locale]/compare/_index.tsx (100%) rename packages/website/src/pages/{[...chunk-pages] => [...chunk_pages]}/[...locale]/compare/index.astro (100%) rename packages/website/src/pages/{[...chunk-pages] => [...chunk_pages]}/[...locale]/docs/[directory]/[document]/_index.tsx (100%) rename packages/website/src/pages/{[...chunk-pages] => [...chunk_pages]}/[...locale]/docs/[directory]/[document]/index.astro (100%) rename packages/website/src/pages/{[...chunk-pages] => [...chunk_pages]}/[...locale]/docs/patches/index.astro (100%) rename packages/website/src/pages/{[...chunk-pages] => [...chunk_pages]}/[...locale]/embed/[embed]/_index.tsx (100%) rename packages/website/src/pages/{[...chunk-pages] => [...chunk_pages]}/[...locale]/embed/[embed]/host/_index.tsx (100%) rename packages/website/src/pages/{[...chunk-pages] => [...chunk_pages]}/[...locale]/embed/[embed]/host/index.astro (100%) rename packages/website/src/pages/{[...chunk-pages] => [...chunk_pages]}/[...locale]/embed/[embed]/index.astro (100%) rename packages/website/src/pages/{[...chunk-pages] => [...chunk_pages]}/[...locale]/embed/_index.tsx (100%) rename packages/website/src/pages/{[...chunk-pages] => [...chunk_pages]}/[...locale]/embed/index.astro (100%) rename packages/website/src/pages/{[...chunk-pages] => [...chunk_pages]}/[...locale]/gallery/_index.tsx (100%) rename packages/website/src/pages/{[...chunk-pages] => [...chunk_pages]}/[...locale]/gallery/index.astro (100%) rename packages/website/src/pages/{[...chunk-pages] => [...chunk_pages]}/[...locale]/guess/_index.tsx (100%) rename packages/website/src/pages/{[...chunk-pages] => [...chunk_pages]}/[...locale]/guess/index.astro (100%) rename packages/website/src/pages/{[...chunk-pages] => [...chunk_pages]}/[...locale]/index.astro (77%) rename packages/website/src/pages/{[...chunk-pages] => [...chunk_pages]}/[...locale]/mixer/_index.tsx (100%) rename packages/website/src/pages/{[...chunk-pages] => [...chunk_pages]}/[...locale]/mixer/index.astro (100%) rename packages/website/src/pages/{[...chunk-pages] => [...chunk_pages]}/[...locale]/performance/_index.tsx (100%) rename packages/website/src/pages/{[...chunk-pages] => [...chunk_pages]}/[...locale]/performance/index.astro (100%) rename packages/website/src/pages/{[...chunk-pages] => [...chunk_pages]}/[...locale]/playlist/_index.tsx (100%) rename packages/website/src/pages/{[...chunk-pages] => [...chunk_pages]}/[...locale]/playlist/index.astro (100%) rename packages/website/src/pages/{[...chunk-pages] => [...chunk_pages]}/[...locale]/session/_index.tsx (100%) rename packages/website/src/pages/{[...chunk-pages] => [...chunk_pages]}/[...locale]/session/index.astro (100%) rename packages/website/src/pages/{[...chunk-pages] => [...chunk_pages]}/[...locale]/settings/_index.tsx (100%) rename packages/website/src/pages/{[...chunk-pages] => [...chunk_pages]}/[...locale]/settings/index.astro (100%) rename packages/website/src/pages/{[...chunk-pages] => [...chunk_pages]}/[...locale]/tanks/[slug]/_index.tsx (100%) rename packages/website/src/pages/{[...chunk-pages] => [...chunk_pages]}/[...locale]/tanks/[slug]/index.astro (100%) rename packages/website/src/pages/{[...chunk-pages] => [...chunk_pages]}/[...locale]/tanks/_index.tsx (100%) rename packages/website/src/pages/{[...chunk-pages] => [...chunk_pages]}/[...locale]/tanks/index.astro (100%) rename packages/website/src/pages/{[...chunk-pages] => [...chunk_pages]}/[...locale]/tier-list/_index.tsx (100%) rename packages/website/src/pages/{[...chunk-pages] => [...chunk_pages]}/[...locale]/tier-list/index.astro (100%) rename packages/website/src/pages/{[...chunk-pages] => [...chunk_pages]}/[...locale]/tools/compare/index.astro (100%) rename packages/website/src/pages/{[...chunk-pages] => [...chunk_pages]}/[...locale]/tools/embed/[embed]/index.astro (100%) rename packages/website/src/pages/{[...chunk-pages] => [...chunk_pages]}/[...locale]/tools/embed/index.astro (100%) rename packages/website/src/pages/{[...chunk-pages] => [...chunk_pages]}/[...locale]/tools/gallery/index.astro (100%) rename packages/website/src/pages/{[...chunk-pages] => [...chunk_pages]}/[...locale]/tools/performance/index.astro (100%) rename packages/website/src/pages/{[...chunk-pages] => [...chunk_pages]}/[...locale]/tools/session/index.astro (100%) rename packages/website/src/pages/{[...chunk-pages] => [...chunk_pages]}/[...locale]/tools/tankopedia/[id]/index.astro (100%) rename packages/website/src/pages/{[...chunk-pages] => [...chunk_pages]}/[...locale]/tools/tankopedia/index.astro (100%) rename packages/website/src/pages/{[...chunk-pages] => [...chunk_pages]}/[...locale]/tools/tier-list/index.astro (100%) diff --git a/packages/website/src/pages/[...chunk-pages]/[...locale]/auth/[provider]/_index.tsx b/packages/website/src/pages/[...chunk_pages]/[...locale]/auth/[provider]/_index.tsx similarity index 100% rename from packages/website/src/pages/[...chunk-pages]/[...locale]/auth/[provider]/_index.tsx rename to packages/website/src/pages/[...chunk_pages]/[...locale]/auth/[provider]/_index.tsx diff --git a/packages/website/src/pages/[...chunk-pages]/[...locale]/auth/[provider]/index.astro b/packages/website/src/pages/[...chunk_pages]/[...locale]/auth/[provider]/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk-pages]/[...locale]/auth/[provider]/index.astro rename to packages/website/src/pages/[...chunk_pages]/[...locale]/auth/[provider]/index.astro diff --git a/packages/website/src/pages/[...chunk-pages]/[...locale]/compare/_index.tsx b/packages/website/src/pages/[...chunk_pages]/[...locale]/compare/_index.tsx similarity index 100% rename from packages/website/src/pages/[...chunk-pages]/[...locale]/compare/_index.tsx rename to packages/website/src/pages/[...chunk_pages]/[...locale]/compare/_index.tsx diff --git a/packages/website/src/pages/[...chunk-pages]/[...locale]/compare/index.astro b/packages/website/src/pages/[...chunk_pages]/[...locale]/compare/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk-pages]/[...locale]/compare/index.astro rename to packages/website/src/pages/[...chunk_pages]/[...locale]/compare/index.astro diff --git a/packages/website/src/pages/[...chunk-pages]/[...locale]/docs/[directory]/[document]/_index.tsx b/packages/website/src/pages/[...chunk_pages]/[...locale]/docs/[directory]/[document]/_index.tsx similarity index 100% rename from packages/website/src/pages/[...chunk-pages]/[...locale]/docs/[directory]/[document]/_index.tsx rename to packages/website/src/pages/[...chunk_pages]/[...locale]/docs/[directory]/[document]/_index.tsx diff --git a/packages/website/src/pages/[...chunk-pages]/[...locale]/docs/[directory]/[document]/index.astro b/packages/website/src/pages/[...chunk_pages]/[...locale]/docs/[directory]/[document]/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk-pages]/[...locale]/docs/[directory]/[document]/index.astro rename to packages/website/src/pages/[...chunk_pages]/[...locale]/docs/[directory]/[document]/index.astro diff --git a/packages/website/src/pages/[...chunk-pages]/[...locale]/docs/patches/index.astro b/packages/website/src/pages/[...chunk_pages]/[...locale]/docs/patches/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk-pages]/[...locale]/docs/patches/index.astro rename to packages/website/src/pages/[...chunk_pages]/[...locale]/docs/patches/index.astro diff --git a/packages/website/src/pages/[...chunk-pages]/[...locale]/embed/[embed]/_index.tsx b/packages/website/src/pages/[...chunk_pages]/[...locale]/embed/[embed]/_index.tsx similarity index 100% rename from packages/website/src/pages/[...chunk-pages]/[...locale]/embed/[embed]/_index.tsx rename to packages/website/src/pages/[...chunk_pages]/[...locale]/embed/[embed]/_index.tsx diff --git a/packages/website/src/pages/[...chunk-pages]/[...locale]/embed/[embed]/host/_index.tsx b/packages/website/src/pages/[...chunk_pages]/[...locale]/embed/[embed]/host/_index.tsx similarity index 100% rename from packages/website/src/pages/[...chunk-pages]/[...locale]/embed/[embed]/host/_index.tsx rename to packages/website/src/pages/[...chunk_pages]/[...locale]/embed/[embed]/host/_index.tsx diff --git a/packages/website/src/pages/[...chunk-pages]/[...locale]/embed/[embed]/host/index.astro b/packages/website/src/pages/[...chunk_pages]/[...locale]/embed/[embed]/host/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk-pages]/[...locale]/embed/[embed]/host/index.astro rename to packages/website/src/pages/[...chunk_pages]/[...locale]/embed/[embed]/host/index.astro diff --git a/packages/website/src/pages/[...chunk-pages]/[...locale]/embed/[embed]/index.astro b/packages/website/src/pages/[...chunk_pages]/[...locale]/embed/[embed]/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk-pages]/[...locale]/embed/[embed]/index.astro rename to packages/website/src/pages/[...chunk_pages]/[...locale]/embed/[embed]/index.astro diff --git a/packages/website/src/pages/[...chunk-pages]/[...locale]/embed/_index.tsx b/packages/website/src/pages/[...chunk_pages]/[...locale]/embed/_index.tsx similarity index 100% rename from packages/website/src/pages/[...chunk-pages]/[...locale]/embed/_index.tsx rename to packages/website/src/pages/[...chunk_pages]/[...locale]/embed/_index.tsx diff --git a/packages/website/src/pages/[...chunk-pages]/[...locale]/embed/index.astro b/packages/website/src/pages/[...chunk_pages]/[...locale]/embed/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk-pages]/[...locale]/embed/index.astro rename to packages/website/src/pages/[...chunk_pages]/[...locale]/embed/index.astro diff --git a/packages/website/src/pages/[...chunk-pages]/[...locale]/gallery/_index.tsx b/packages/website/src/pages/[...chunk_pages]/[...locale]/gallery/_index.tsx similarity index 100% rename from packages/website/src/pages/[...chunk-pages]/[...locale]/gallery/_index.tsx rename to packages/website/src/pages/[...chunk_pages]/[...locale]/gallery/_index.tsx diff --git a/packages/website/src/pages/[...chunk-pages]/[...locale]/gallery/index.astro b/packages/website/src/pages/[...chunk_pages]/[...locale]/gallery/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk-pages]/[...locale]/gallery/index.astro rename to packages/website/src/pages/[...chunk_pages]/[...locale]/gallery/index.astro diff --git a/packages/website/src/pages/[...chunk-pages]/[...locale]/guess/_index.tsx b/packages/website/src/pages/[...chunk_pages]/[...locale]/guess/_index.tsx similarity index 100% rename from packages/website/src/pages/[...chunk-pages]/[...locale]/guess/_index.tsx rename to packages/website/src/pages/[...chunk_pages]/[...locale]/guess/_index.tsx diff --git a/packages/website/src/pages/[...chunk-pages]/[...locale]/guess/index.astro b/packages/website/src/pages/[...chunk_pages]/[...locale]/guess/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk-pages]/[...locale]/guess/index.astro rename to packages/website/src/pages/[...chunk_pages]/[...locale]/guess/index.astro diff --git a/packages/website/src/pages/[...chunk-pages]/[...locale]/index.astro b/packages/website/src/pages/[...chunk_pages]/[...locale]/index.astro similarity index 77% rename from packages/website/src/pages/[...chunk-pages]/[...locale]/index.astro rename to packages/website/src/pages/[...chunk_pages]/[...locale]/index.astro index 4495f1d92..113b6e785 100644 --- a/packages/website/src/pages/[...chunk-pages]/[...locale]/index.astro +++ b/packages/website/src/pages/[...chunk_pages]/[...locale]/index.astro @@ -5,13 +5,13 @@ import locales from "@blitzkit/i18n/locales.json"; import { literals } from "@blitzkit/i18n/src/literals"; import { Box, Flex, Link } from "@radix-ui/themes"; import type { GetStaticPaths } from "astro"; -import { AesonPlug } from "../../components/AesonPlug"; -import HomeHero from "../../components/HomeHero.astro"; -import { Plugs } from "../../components/Plugs"; -import ToolCard from "../../components/ToolCard.astro"; -import { tools, type Tool } from "../../constants/tools"; -import { getStrings } from "../../core/i18n/getStrings"; -import BaseLayout from "../../layouts/BaseLayout.astro"; +import { AesonPlug } from "../../../components/AesonPlug"; +import HomeHero from "../../../components/HomeHero.astro"; +import { Plugs } from "../../../components/Plugs"; +import ToolCard from "../../../components/ToolCard.astro"; +import { tools, type Tool } from "../../../constants/tools"; +import { getStrings } from "../../../core/i18n/getStrings"; +import BaseLayout from "../../../layouts/BaseLayout.astro"; const MAX_TOOLS_PER_ROW = 2; @@ -64,22 +64,6 @@ export const getStaticPaths = (() => { - - { locale !== locales.default && ( diff --git a/packages/website/src/pages/[...chunk-pages]/[...locale]/mixer/_index.tsx b/packages/website/src/pages/[...chunk_pages]/[...locale]/mixer/_index.tsx similarity index 100% rename from packages/website/src/pages/[...chunk-pages]/[...locale]/mixer/_index.tsx rename to packages/website/src/pages/[...chunk_pages]/[...locale]/mixer/_index.tsx diff --git a/packages/website/src/pages/[...chunk-pages]/[...locale]/mixer/index.astro b/packages/website/src/pages/[...chunk_pages]/[...locale]/mixer/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk-pages]/[...locale]/mixer/index.astro rename to packages/website/src/pages/[...chunk_pages]/[...locale]/mixer/index.astro diff --git a/packages/website/src/pages/[...chunk-pages]/[...locale]/performance/_index.tsx b/packages/website/src/pages/[...chunk_pages]/[...locale]/performance/_index.tsx similarity index 100% rename from packages/website/src/pages/[...chunk-pages]/[...locale]/performance/_index.tsx rename to packages/website/src/pages/[...chunk_pages]/[...locale]/performance/_index.tsx diff --git a/packages/website/src/pages/[...chunk-pages]/[...locale]/performance/index.astro b/packages/website/src/pages/[...chunk_pages]/[...locale]/performance/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk-pages]/[...locale]/performance/index.astro rename to packages/website/src/pages/[...chunk_pages]/[...locale]/performance/index.astro diff --git a/packages/website/src/pages/[...chunk-pages]/[...locale]/playlist/_index.tsx b/packages/website/src/pages/[...chunk_pages]/[...locale]/playlist/_index.tsx similarity index 100% rename from packages/website/src/pages/[...chunk-pages]/[...locale]/playlist/_index.tsx rename to packages/website/src/pages/[...chunk_pages]/[...locale]/playlist/_index.tsx diff --git a/packages/website/src/pages/[...chunk-pages]/[...locale]/playlist/index.astro b/packages/website/src/pages/[...chunk_pages]/[...locale]/playlist/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk-pages]/[...locale]/playlist/index.astro rename to packages/website/src/pages/[...chunk_pages]/[...locale]/playlist/index.astro diff --git a/packages/website/src/pages/[...chunk-pages]/[...locale]/session/_index.tsx b/packages/website/src/pages/[...chunk_pages]/[...locale]/session/_index.tsx similarity index 100% rename from packages/website/src/pages/[...chunk-pages]/[...locale]/session/_index.tsx rename to packages/website/src/pages/[...chunk_pages]/[...locale]/session/_index.tsx diff --git a/packages/website/src/pages/[...chunk-pages]/[...locale]/session/index.astro b/packages/website/src/pages/[...chunk_pages]/[...locale]/session/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk-pages]/[...locale]/session/index.astro rename to packages/website/src/pages/[...chunk_pages]/[...locale]/session/index.astro diff --git a/packages/website/src/pages/[...chunk-pages]/[...locale]/settings/_index.tsx b/packages/website/src/pages/[...chunk_pages]/[...locale]/settings/_index.tsx similarity index 100% rename from packages/website/src/pages/[...chunk-pages]/[...locale]/settings/_index.tsx rename to packages/website/src/pages/[...chunk_pages]/[...locale]/settings/_index.tsx diff --git a/packages/website/src/pages/[...chunk-pages]/[...locale]/settings/index.astro b/packages/website/src/pages/[...chunk_pages]/[...locale]/settings/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk-pages]/[...locale]/settings/index.astro rename to packages/website/src/pages/[...chunk_pages]/[...locale]/settings/index.astro diff --git a/packages/website/src/pages/[...chunk-pages]/[...locale]/tanks/[slug]/_index.tsx b/packages/website/src/pages/[...chunk_pages]/[...locale]/tanks/[slug]/_index.tsx similarity index 100% rename from packages/website/src/pages/[...chunk-pages]/[...locale]/tanks/[slug]/_index.tsx rename to packages/website/src/pages/[...chunk_pages]/[...locale]/tanks/[slug]/_index.tsx diff --git a/packages/website/src/pages/[...chunk-pages]/[...locale]/tanks/[slug]/index.astro b/packages/website/src/pages/[...chunk_pages]/[...locale]/tanks/[slug]/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk-pages]/[...locale]/tanks/[slug]/index.astro rename to packages/website/src/pages/[...chunk_pages]/[...locale]/tanks/[slug]/index.astro diff --git a/packages/website/src/pages/[...chunk-pages]/[...locale]/tanks/_index.tsx b/packages/website/src/pages/[...chunk_pages]/[...locale]/tanks/_index.tsx similarity index 100% rename from packages/website/src/pages/[...chunk-pages]/[...locale]/tanks/_index.tsx rename to packages/website/src/pages/[...chunk_pages]/[...locale]/tanks/_index.tsx diff --git a/packages/website/src/pages/[...chunk-pages]/[...locale]/tanks/index.astro b/packages/website/src/pages/[...chunk_pages]/[...locale]/tanks/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk-pages]/[...locale]/tanks/index.astro rename to packages/website/src/pages/[...chunk_pages]/[...locale]/tanks/index.astro diff --git a/packages/website/src/pages/[...chunk-pages]/[...locale]/tier-list/_index.tsx b/packages/website/src/pages/[...chunk_pages]/[...locale]/tier-list/_index.tsx similarity index 100% rename from packages/website/src/pages/[...chunk-pages]/[...locale]/tier-list/_index.tsx rename to packages/website/src/pages/[...chunk_pages]/[...locale]/tier-list/_index.tsx diff --git a/packages/website/src/pages/[...chunk-pages]/[...locale]/tier-list/index.astro b/packages/website/src/pages/[...chunk_pages]/[...locale]/tier-list/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk-pages]/[...locale]/tier-list/index.astro rename to packages/website/src/pages/[...chunk_pages]/[...locale]/tier-list/index.astro diff --git a/packages/website/src/pages/[...chunk-pages]/[...locale]/tools/compare/index.astro b/packages/website/src/pages/[...chunk_pages]/[...locale]/tools/compare/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk-pages]/[...locale]/tools/compare/index.astro rename to packages/website/src/pages/[...chunk_pages]/[...locale]/tools/compare/index.astro diff --git a/packages/website/src/pages/[...chunk-pages]/[...locale]/tools/embed/[embed]/index.astro b/packages/website/src/pages/[...chunk_pages]/[...locale]/tools/embed/[embed]/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk-pages]/[...locale]/tools/embed/[embed]/index.astro rename to packages/website/src/pages/[...chunk_pages]/[...locale]/tools/embed/[embed]/index.astro diff --git a/packages/website/src/pages/[...chunk-pages]/[...locale]/tools/embed/index.astro b/packages/website/src/pages/[...chunk_pages]/[...locale]/tools/embed/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk-pages]/[...locale]/tools/embed/index.astro rename to packages/website/src/pages/[...chunk_pages]/[...locale]/tools/embed/index.astro diff --git a/packages/website/src/pages/[...chunk-pages]/[...locale]/tools/gallery/index.astro b/packages/website/src/pages/[...chunk_pages]/[...locale]/tools/gallery/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk-pages]/[...locale]/tools/gallery/index.astro rename to packages/website/src/pages/[...chunk_pages]/[...locale]/tools/gallery/index.astro diff --git a/packages/website/src/pages/[...chunk-pages]/[...locale]/tools/performance/index.astro b/packages/website/src/pages/[...chunk_pages]/[...locale]/tools/performance/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk-pages]/[...locale]/tools/performance/index.astro rename to packages/website/src/pages/[...chunk_pages]/[...locale]/tools/performance/index.astro diff --git a/packages/website/src/pages/[...chunk-pages]/[...locale]/tools/session/index.astro b/packages/website/src/pages/[...chunk_pages]/[...locale]/tools/session/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk-pages]/[...locale]/tools/session/index.astro rename to packages/website/src/pages/[...chunk_pages]/[...locale]/tools/session/index.astro diff --git a/packages/website/src/pages/[...chunk-pages]/[...locale]/tools/tankopedia/[id]/index.astro b/packages/website/src/pages/[...chunk_pages]/[...locale]/tools/tankopedia/[id]/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk-pages]/[...locale]/tools/tankopedia/[id]/index.astro rename to packages/website/src/pages/[...chunk_pages]/[...locale]/tools/tankopedia/[id]/index.astro diff --git a/packages/website/src/pages/[...chunk-pages]/[...locale]/tools/tankopedia/index.astro b/packages/website/src/pages/[...chunk_pages]/[...locale]/tools/tankopedia/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk-pages]/[...locale]/tools/tankopedia/index.astro rename to packages/website/src/pages/[...chunk_pages]/[...locale]/tools/tankopedia/index.astro diff --git a/packages/website/src/pages/[...chunk-pages]/[...locale]/tools/tier-list/index.astro b/packages/website/src/pages/[...chunk_pages]/[...locale]/tools/tier-list/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk-pages]/[...locale]/tools/tier-list/index.astro rename to packages/website/src/pages/[...chunk_pages]/[...locale]/tools/tier-list/index.astro From 4cff06110a8c2e8e3ac124005b12381a64eb49a8 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Fri, 31 Jul 2026 20:27:39 -0500 Subject: [PATCH 007/147] remove app --- README.md | 15 +- packages/app/package.json | 18 - packages/app/src-tauri/.gitignore | 7 - packages/app/src-tauri/Cargo.lock | 5286 ----------------- packages/app/src-tauri/Cargo.toml | 25 - packages/app/src-tauri/build.rs | 3 - .../app/src-tauri/capabilities/default.json | 10 - packages/app/src-tauri/src/lib.rs | 14 - packages/app/src-tauri/src/main.rs | 27 - packages/app/src-tauri/tauri.conf.json | 38 - packages/app/tsconfig.json | 19 - packages/i18n/strings/en.json | 9 - rust-project.json | 10 - 13 files changed, 7 insertions(+), 5474 deletions(-) delete mode 100644 packages/app/package.json delete mode 100644 packages/app/src-tauri/.gitignore delete mode 100644 packages/app/src-tauri/Cargo.lock delete mode 100644 packages/app/src-tauri/Cargo.toml delete mode 100644 packages/app/src-tauri/build.rs delete mode 100644 packages/app/src-tauri/capabilities/default.json delete mode 100644 packages/app/src-tauri/src/lib.rs delete mode 100644 packages/app/src-tauri/src/main.rs delete mode 100644 packages/app/src-tauri/tauri.conf.json delete mode 100644 packages/app/tsconfig.json delete mode 100644 rust-project.json diff --git a/README.md b/README.md index 4a7de2108..a3c6446fd 100644 --- a/README.md +++ b/README.md @@ -8,14 +8,13 @@ If you intend to contribute to the code, note that you may need to check out sen ## Packages -| Package | Title | Source | Memo | -| ------------------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------ | -| [packages/app](packages/app) | BlitzKit | [Play Store](https://play.google.com/store/apps/details?id=com.tresabhi.blitzkit), [App Store](https://apps.apple.com/app/4321), [Microsoft Store](https://apps.microsoft.com/detail/zyxw) | The native app of BlitzKit for all platforms. Unavailable from stores at this time. | -| [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/varuna](packages/varuna) | Varuna | [NPM](https://www.npmjs.com/package/varuna) | React and Astro-oriented state management library, awaiting separation from BlitzKit. Unavailable from NPM at this time. | -| [packages/website](packages/website) | BlitzKit | https://blitzkit.app | The BlitzKit website provides a wide range of tools, APIs, and interconnects all other services. | +| 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/varuna](packages/varuna) | Varuna | [NPM](https://www.npmjs.com/package/varuna) | React and Astro-oriented state management library, awaiting separation from BlitzKit. Unavailable from NPM at this time. | +| [packages/website](packages/website) | BlitzKit | https://blitzkit.app | The BlitzKit website provides a wide range of tools, APIs, and interconnects all other services. | ## Submodules diff --git a/packages/app/package.json b/packages/app/package.json deleted file mode 100644 index 0001efb50..000000000 --- a/packages/app/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "@blitzkit/app", - "private": true, - "type": "module", - "scripts": { - "dev": "vite", - "build": "tsc && vite build", - "preview": "vite preview", - "tauri": "tauri" - }, - "dependencies": { - "@tauri-apps/api": "^2", - "@tauri-apps/plugin-opener": "^2", - "@tauri-apps/cli": "^2", - "vite": "^6.0.3", - "typescript": "~5.6.2" - } -} diff --git a/packages/app/src-tauri/.gitignore b/packages/app/src-tauri/.gitignore deleted file mode 100644 index b21bd681d..000000000 --- a/packages/app/src-tauri/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ - -# Generated by Tauri -# will have schema files for capabilities auto-completion -/gen/schemas diff --git a/packages/app/src-tauri/Cargo.lock b/packages/app/src-tauri/Cargo.lock deleted file mode 100644 index a2c357c41..000000000 --- a/packages/app/src-tauri/Cargo.lock +++ /dev/null @@ -1,5286 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "addr2line" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - -[[package]] -name = "alloc-no-stdlib" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" - -[[package]] -name = "alloc-stdlib" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" -dependencies = [ - "alloc-no-stdlib", -] - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "anyhow" -version = "1.0.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" - -[[package]] -name = "async-broadcast" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" -dependencies = [ - "event-listener", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-channel" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" -dependencies = [ - "concurrent-queue", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-executor" -version = "1.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497c00e0fd83a72a79a39fcbd8e3e2f055d6f6c7e025f3b3d91f4f8e76527fb8" -dependencies = [ - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "pin-project-lite", - "slab", -] - -[[package]] -name = "async-io" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" -dependencies = [ - "autocfg", - "cfg-if", - "concurrent-queue", - "futures-io", - "futures-lite", - "parking", - "polling", - "rustix", - "slab", - "windows-sys 0.61.1", -] - -[[package]] -name = "async-lock" -version = "3.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" -dependencies = [ - "event-listener", - "event-listener-strategy", - "pin-project-lite", -] - -[[package]] -name = "async-process" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" -dependencies = [ - "async-channel", - "async-io", - "async-lock", - "async-signal", - "async-task", - "blocking", - "cfg-if", - "event-listener", - "futures-lite", - "rustix", -] - -[[package]] -name = "async-recursion" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "async-signal" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c" -dependencies = [ - "async-io", - "async-lock", - "atomic-waker", - "cfg-if", - "futures-core", - "futures-io", - "rustix", - "signal-hook-registry", - "slab", - "windows-sys 0.61.1", -] - -[[package]] -name = "async-task" -version = "4.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" - -[[package]] -name = "async-trait" -version = "0.1.89" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "atk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" -dependencies = [ - "atk-sys", - "glib", - "libc", -] - -[[package]] -name = "atk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "autocfg" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - -[[package]] -name = "backtrace" -version = "0.3.76" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-link 0.2.0", -] - -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" -dependencies = [ - "serde", -] - -[[package]] -name = "blitzkit" -version = "0.1.0" -dependencies = [ - "serde", - "serde_json", - "tauri", - "tauri-build", - "tauri-plugin-opener", -] - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "block2" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" -dependencies = [ - "objc2 0.5.2", -] - -[[package]] -name = "block2" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "340d2f0bdb2a43c1d3cd40513185b2bd7def0aa1052f956455114bc98f82dcf2" -dependencies = [ - "objc2 0.6.2", -] - -[[package]] -name = "blocking" -version = "1.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" -dependencies = [ - "async-channel", - "async-task", - "futures-io", - "futures-lite", - "piper", -] - -[[package]] -name = "brotli" -version = "8.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", -] - -[[package]] -name = "brotli-decompressor" -version = "5.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", -] - -[[package]] -name = "bumpalo" -version = "3.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" - -[[package]] -name = "bytemuck" -version = "1.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" -dependencies = [ - "serde", -] - -[[package]] -name = "cairo-rs" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" -dependencies = [ - "bitflags 2.9.4", - "cairo-sys-rs", - "glib", - "libc", - "once_cell", - "thiserror 1.0.69", -] - -[[package]] -name = "cairo-sys-rs" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" -dependencies = [ - "glib-sys", - "libc", - "system-deps", -] - -[[package]] -name = "camino" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1de8bc0aa9e9385ceb3bf0c152e3a9b9544f6c4a912c8ae504e80c1f0368603" -dependencies = [ - "serde_core", -] - -[[package]] -name = "cargo-platform" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo_metadata" -version = "0.19.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" -dependencies = [ - "camino", - "cargo-platform", - "semver", - "serde", - "serde_json", - "thiserror 2.0.17", -] - -[[package]] -name = "cargo_toml" -version = "0.22.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" -dependencies = [ - "serde", - "toml 0.9.7", -] - -[[package]] -name = "cc" -version = "1.2.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1354349954c6fc9cb0deab020f27f783cf0b604e8bb754dc4658ecf0d29c35f" -dependencies = [ - "find-msvc-tools", - "shlex", -] - -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - -[[package]] -name = "cfb" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" -dependencies = [ - "byteorder", - "fnv", - "uuid", -] - -[[package]] -name = "cfg-expr" -version = "0.15.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" -dependencies = [ - "smallvec", - "target-lexicon", -] - -[[package]] -name = "cfg-if" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" - -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - -[[package]] -name = "chrono" -version = "0.4.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" -dependencies = [ - "iana-time-zone", - "num-traits", - "serde", - "windows-link 0.2.0", -] - -[[package]] -name = "combine" -version = "4.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" -dependencies = [ - "bytes", - "memchr", -] - -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - -[[package]] -name = "cookie" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" -dependencies = [ - "time", - "version_check", -] - -[[package]] -name = "core-foundation" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "core-graphics" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" -dependencies = [ - "bitflags 2.9.4", - "core-foundation", - "core-graphics-types", - "foreign-types", - "libc", -] - -[[package]] -name = "core-graphics-types" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" -dependencies = [ - "bitflags 2.9.4", - "core-foundation", - "libc", -] - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "crc32fast" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "cssparser" -version = "0.29.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93d03419cb5950ccfd3daf3ff1c7a36ace64609a1a8746d493df1ca0afde0fa" -dependencies = [ - "cssparser-macros", - "dtoa-short", - "itoa", - "matches", - "phf 0.10.1", - "proc-macro2", - "quote", - "smallvec", - "syn 1.0.109", -] - -[[package]] -name = "cssparser-macros" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" -dependencies = [ - "quote", - "syn 2.0.106", -] - -[[package]] -name = "ctor" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" -dependencies = [ - "quote", - "syn 2.0.106", -] - -[[package]] -name = "darling" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.106", -] - -[[package]] -name = "darling_macro" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" -dependencies = [ - "darling_core", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "deranged" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" -dependencies = [ - "powerfmt", - "serde_core", -] - -[[package]] -name = "derive_more" -version = "0.99.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version", - "syn 2.0.106", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", -] - -[[package]] -name = "dirs" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys 0.61.1", -] - -[[package]] -name = "dispatch" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" - -[[package]] -name = "dispatch2" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" -dependencies = [ - "bitflags 2.9.4", - "objc2 0.6.2", -] - -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "dlopen2" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b54f373ccf864bf587a89e880fb7610f8d73f3045f13580948ccbcaff26febff" -dependencies = [ - "dlopen2_derive", - "libc", - "once_cell", - "winapi", -] - -[[package]] -name = "dlopen2_derive" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "788160fb30de9cdd857af31c6a2675904b16ece8fc2737b2c7127ba368c9d0f4" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "dpi" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" -dependencies = [ - "serde", -] - -[[package]] -name = "dtoa" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04" - -[[package]] -name = "dtoa-short" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" -dependencies = [ - "dtoa", -] - -[[package]] -name = "dunce" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" - -[[package]] -name = "dyn-clone" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" - -[[package]] -name = "embed-resource" -version = "3.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55a075fc573c64510038d7ee9abc7990635863992f83ebc52c8b433b8411a02e" -dependencies = [ - "cc", - "memchr", - "rustc_version", - "toml 0.9.7", - "vswhom", - "winreg", -] - -[[package]] -name = "embed_plist" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" - -[[package]] -name = "endi" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" - -[[package]] -name = "enumflags2" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" -dependencies = [ - "enumflags2_derive", - "serde", -] - -[[package]] -name = "enumflags2_derive" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "erased-serde" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "259d404d09818dec19332e31d94558aeb442fea04c817006456c24b5460bbd4b" -dependencies = [ - "serde", - "serde_core", - "typeid", -] - -[[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys 0.61.1", -] - -[[package]] -name = "event-listener" -version = "5.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" -dependencies = [ - "event-listener", - "pin-project-lite", -] - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "fdeflate" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" -dependencies = [ - "simd-adler32", -] - -[[package]] -name = "field-offset" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" -dependencies = [ - "memoffset", - "rustc_version", -] - -[[package]] -name = "find-msvc-tools" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959" - -[[package]] -name = "flate2" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foreign-types" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" -dependencies = [ - "foreign-types-macros", - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-macros" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "foreign-types-shared" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" - -[[package]] -name = "form_urlencoded" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "futf" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" -dependencies = [ - "mac", - "new_debug_unreachable", -] - -[[package]] -name = "futures-channel" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", -] - -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "futures-executor" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - -[[package]] -name = "futures-lite" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", -] - -[[package]] -name = "futures-macro" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - -[[package]] -name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-util" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" -dependencies = [ - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - -[[package]] -name = "gdk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" -dependencies = [ - "cairo-rs", - "gdk-pixbuf", - "gdk-sys", - "gio", - "glib", - "libc", - "pango", -] - -[[package]] -name = "gdk-pixbuf" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" -dependencies = [ - "gdk-pixbuf-sys", - "gio", - "glib", - "libc", - "once_cell", -] - -[[package]] -name = "gdk-pixbuf-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" -dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "gdk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" -dependencies = [ - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "pkg-config", - "system-deps", -] - -[[package]] -name = "gdkwayland-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" -dependencies = [ - "gdk-sys", - "glib-sys", - "gobject-sys", - "libc", - "pkg-config", - "system-deps", -] - -[[package]] -name = "gdkx11" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe" -dependencies = [ - "gdk", - "gdkx11-sys", - "gio", - "glib", - "libc", - "x11", -] - -[[package]] -name = "gdkx11-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" -dependencies = [ - "gdk-sys", - "glib-sys", - "libc", - "system-deps", - "x11", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.11.1+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasi 0.14.7+wasi-0.2.4", -] - -[[package]] -name = "gimli" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" - -[[package]] -name = "gio" -version = "0.18.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "gio-sys", - "glib", - "libc", - "once_cell", - "pin-project-lite", - "smallvec", - "thiserror 1.0.69", -] - -[[package]] -name = "gio-sys" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", - "winapi", -] - -[[package]] -name = "glib" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" -dependencies = [ - "bitflags 2.9.4", - "futures-channel", - "futures-core", - "futures-executor", - "futures-task", - "futures-util", - "gio-sys", - "glib-macros", - "glib-sys", - "gobject-sys", - "libc", - "memchr", - "once_cell", - "smallvec", - "thiserror 1.0.69", -] - -[[package]] -name = "glib-macros" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" -dependencies = [ - "heck 0.4.1", - "proc-macro-crate 2.0.2", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "glib-sys" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" -dependencies = [ - "libc", - "system-deps", -] - -[[package]] -name = "glob" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" - -[[package]] -name = "gobject-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" -dependencies = [ - "glib-sys", - "libc", - "system-deps", -] - -[[package]] -name = "gtk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" -dependencies = [ - "atk", - "cairo-rs", - "field-offset", - "futures-channel", - "gdk", - "gdk-pixbuf", - "gio", - "glib", - "gtk-sys", - "gtk3-macros", - "libc", - "pango", - "pkg-config", -] - -[[package]] -name = "gtk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" -dependencies = [ - "atk-sys", - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gdk-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "system-deps", -] - -[[package]] -name = "gtk3-macros" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" -dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "hashbrown" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hermit-abi" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "html5ever" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c" -dependencies = [ - "log", - "mac", - "markup5ever", - "match_token", -] - -[[package]] -name = "http" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - -[[package]] -name = "hyper" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" -dependencies = [ - "atomic-waker", - "bytes", - "futures-channel", - "futures-core", - "http", - "http-body", - "httparse", - "itoa", - "pin-project-lite", - "pin-utils", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-util" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" -dependencies = [ - "base64 0.22.1", - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "http", - "http-body", - "hyper", - "ipnet", - "libc", - "percent-encoding", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core 0.62.1", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "ico" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc50b891e4acf8fe0e71ef88ec43ad82ee07b3810ad09de10f1d01f072ed4b98" -dependencies = [ - "byteorder", - "png", -] - -[[package]] -name = "icu_collections" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" -dependencies = [ - "displaydoc", - "potential_utf", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" - -[[package]] -name = "icu_properties" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "potential_utf", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" - -[[package]] -name = "icu_provider" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" -dependencies = [ - "displaydoc", - "icu_locale_core", - "stable_deref_trait", - "tinystr", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "idna" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", -] - -[[package]] -name = "indexmap" -version = "2.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" -dependencies = [ - "equivalent", - "hashbrown 0.16.0", - "serde", - "serde_core", -] - -[[package]] -name = "infer" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" -dependencies = [ - "cfb", -] - -[[package]] -name = "io-uring" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" -dependencies = [ - "bitflags 2.9.4", - "cfg-if", - "libc", -] - -[[package]] -name = "ipnet" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" - -[[package]] -name = "iri-string" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" -dependencies = [ - "memchr", - "serde", -] - -[[package]] -name = "is-docker" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" -dependencies = [ - "once_cell", -] - -[[package]] -name = "is-wsl" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" -dependencies = [ - "is-docker", - "once_cell", -] - -[[package]] -name = "itoa" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" - -[[package]] -name = "javascriptcore-rs" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" -dependencies = [ - "bitflags 1.3.2", - "glib", - "javascriptcore-rs-sys", -] - -[[package]] -name = "javascriptcore-rs-sys" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "jni" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" -dependencies = [ - "cesu8", - "cfg-if", - "combine", - "jni-sys", - "log", - "thiserror 1.0.69", - "walkdir", - "windows-sys 0.45.0", -] - -[[package]] -name = "jni-sys" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" - -[[package]] -name = "js-sys" -version = "0.3.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "json-patch" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" -dependencies = [ - "jsonptr", - "serde", - "serde_json", - "thiserror 1.0.69", -] - -[[package]] -name = "jsonptr" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" -dependencies = [ - "serde", - "serde_json", -] - -[[package]] -name = "keyboard-types" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" -dependencies = [ - "bitflags 2.9.4", - "serde", - "unicode-segmentation", -] - -[[package]] -name = "kuchikiki" -version = "0.8.8-speedreader" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2" -dependencies = [ - "cssparser", - "html5ever", - "indexmap 2.11.4", - "selectors", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "libappindicator" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" -dependencies = [ - "glib", - "gtk", - "gtk-sys", - "libappindicator-sys", - "log", -] - -[[package]] -name = "libappindicator-sys" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" -dependencies = [ - "gtk-sys", - "libloading", - "once_cell", -] - -[[package]] -name = "libc" -version = "0.2.176" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" - -[[package]] -name = "libloading" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" -dependencies = [ - "cfg-if", - "winapi", -] - -[[package]] -name = "libredox" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" -dependencies = [ - "bitflags 2.9.4", - "libc", -] - -[[package]] -name = "linux-raw-sys" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" - -[[package]] -name = "litemap" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" - -[[package]] -name = "lock_api" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" - -[[package]] -name = "mac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" - -[[package]] -name = "markup5ever" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18" -dependencies = [ - "log", - "phf 0.11.3", - "phf_codegen 0.11.3", - "string_cache", - "string_cache_codegen", - "tendril", -] - -[[package]] -name = "match_token" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "matches" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" - -[[package]] -name = "memchr" -version = "2.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" - -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", - "simd-adler32", -] - -[[package]] -name = "mio" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" -dependencies = [ - "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.59.0", -] - -[[package]] -name = "muda" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01c1738382f66ed56b3b9c8119e794a2e23148ac8ea214eda86622d4cb9d415a" -dependencies = [ - "crossbeam-channel", - "dpi", - "gtk", - "keyboard-types", - "objc2 0.6.2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation 0.3.1", - "once_cell", - "png", - "serde", - "thiserror 2.0.17", - "windows-sys 0.60.2", -] - -[[package]] -name = "ndk" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" -dependencies = [ - "bitflags 2.9.4", - "jni-sys", - "log", - "ndk-sys", - "num_enum", - "raw-window-handle", - "thiserror 1.0.69", -] - -[[package]] -name = "ndk-context" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" - -[[package]] -name = "ndk-sys" -version = "0.6.0+11769913" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" -dependencies = [ - "jni-sys", -] - -[[package]] -name = "new_debug_unreachable" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" - -[[package]] -name = "nix" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" -dependencies = [ - "bitflags 2.9.4", - "cfg-if", - "cfg_aliases", - "libc", - "memoffset", -] - -[[package]] -name = "nodrop" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" - -[[package]] -name = "num-conv" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_enum" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" -dependencies = [ - "num_enum_derive", - "rustversion", -] - -[[package]] -name = "num_enum_derive" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" -dependencies = [ - "proc-macro-crate 3.4.0", - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "objc-sys" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" - -[[package]] -name = "objc2" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" -dependencies = [ - "objc-sys", - "objc2-encode", -] - -[[package]] -name = "objc2" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561f357ba7f3a2a61563a186a163d0a3a5247e1089524a3981d49adb775078bc" -dependencies = [ - "objc2-encode", - "objc2-exception-helper", -] - -[[package]] -name = "objc2-app-kit" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" -dependencies = [ - "bitflags 2.9.4", - "block2 0.6.1", - "libc", - "objc2 0.6.2", - "objc2-cloud-kit", - "objc2-core-data", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-core-image", - "objc2-foundation 0.3.1", - "objc2-quartz-core 0.3.1", -] - -[[package]] -name = "objc2-cloud-kit" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17614fdcd9b411e6ff1117dfb1d0150f908ba83a7df81b1f118005fe0a8ea15d" -dependencies = [ - "bitflags 2.9.4", - "objc2 0.6.2", - "objc2-foundation 0.3.1", -] - -[[package]] -name = "objc2-core-data" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291fbbf7d29287518e8686417cf7239c74700fd4b607623140a7d4a3c834329d" -dependencies = [ - "bitflags 2.9.4", - "objc2 0.6.2", - "objc2-foundation 0.3.1", -] - -[[package]] -name = "objc2-core-foundation" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" -dependencies = [ - "bitflags 2.9.4", - "dispatch2", - "objc2 0.6.2", -] - -[[package]] -name = "objc2-core-graphics" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4" -dependencies = [ - "bitflags 2.9.4", - "dispatch2", - "objc2 0.6.2", - "objc2-core-foundation", - "objc2-io-surface", -] - -[[package]] -name = "objc2-core-image" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79b3dc0cc4386b6ccf21c157591b34a7f44c8e75b064f85502901ab2188c007e" -dependencies = [ - "objc2 0.6.2", - "objc2-foundation 0.3.1", -] - -[[package]] -name = "objc2-encode" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" - -[[package]] -name = "objc2-exception-helper" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" -dependencies = [ - "cc", -] - -[[package]] -name = "objc2-foundation" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" -dependencies = [ - "bitflags 2.9.4", - "block2 0.5.1", - "libc", - "objc2 0.5.2", -] - -[[package]] -name = "objc2-foundation" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" -dependencies = [ - "bitflags 2.9.4", - "block2 0.6.1", - "libc", - "objc2 0.6.2", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-io-surface" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c" -dependencies = [ - "bitflags 2.9.4", - "objc2 0.6.2", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-javascript-core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9052cb1bb50a4c161d934befcf879526fb87ae9a68858f241e693ca46225cf5a" -dependencies = [ - "objc2 0.6.2", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-metal" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" -dependencies = [ - "bitflags 2.9.4", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-quartz-core" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" -dependencies = [ - "bitflags 2.9.4", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation 0.2.2", - "objc2-metal", -] - -[[package]] -name = "objc2-quartz-core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ffb6a0cd5f182dc964334388560b12a57f7b74b3e2dec5e2722aa2dfb2ccd5" -dependencies = [ - "bitflags 2.9.4", - "objc2 0.6.2", - "objc2-foundation 0.3.1", -] - -[[package]] -name = "objc2-security" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1f8e0ef3ab66b08c42644dcb34dba6ec0a574bbd8adbb8bdbdc7a2779731a44" -dependencies = [ - "bitflags 2.9.4", - "objc2 0.6.2", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-ui-kit" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25b1312ad7bc8a0e92adae17aa10f90aae1fb618832f9b993b022b591027daed" -dependencies = [ - "bitflags 2.9.4", - "objc2 0.6.2", - "objc2-core-foundation", - "objc2-foundation 0.3.1", -] - -[[package]] -name = "objc2-web-kit" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91672909de8b1ce1c2252e95bbee8c1649c9ad9d14b9248b3d7b4c47903c47ad" -dependencies = [ - "bitflags 2.9.4", - "block2 0.6.1", - "objc2 0.6.2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation 0.3.1", - "objc2-javascript-core", - "objc2-security", -] - -[[package]] -name = "object" -version = "0.37.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" - -[[package]] -name = "open" -version = "5.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2483562e62ea94312f3576a7aca397306df7990b8d89033e18766744377ef95" -dependencies = [ - "dunce", - "is-wsl", - "libc", - "pathdiff", -] - -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - -[[package]] -name = "ordered-stream" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" -dependencies = [ - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "pango" -version = "0.18.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" -dependencies = [ - "gio", - "glib", - "libc", - "once_cell", - "pango-sys", -] - -[[package]] -name = "pango-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" - -[[package]] -name = "parking_lot" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets 0.52.6", -] - -[[package]] -name = "pathdiff" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" - -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - -[[package]] -name = "phf" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" -dependencies = [ - "phf_shared 0.8.0", -] - -[[package]] -name = "phf" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" -dependencies = [ - "phf_macros 0.10.0", - "phf_shared 0.10.0", - "proc-macro-hack", -] - -[[package]] -name = "phf" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" -dependencies = [ - "phf_macros 0.11.3", - "phf_shared 0.11.3", -] - -[[package]] -name = "phf_codegen" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" -dependencies = [ - "phf_generator 0.8.0", - "phf_shared 0.8.0", -] - -[[package]] -name = "phf_codegen" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", -] - -[[package]] -name = "phf_generator" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" -dependencies = [ - "phf_shared 0.8.0", - "rand 0.7.3", -] - -[[package]] -name = "phf_generator" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" -dependencies = [ - "phf_shared 0.10.0", - "rand 0.8.5", -] - -[[package]] -name = "phf_generator" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" -dependencies = [ - "phf_shared 0.11.3", - "rand 0.8.5", -] - -[[package]] -name = "phf_macros" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" -dependencies = [ - "phf_generator 0.10.0", - "phf_shared 0.10.0", - "proc-macro-hack", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "phf_macros" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "phf_shared" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" -dependencies = [ - "siphasher 0.3.11", -] - -[[package]] -name = "phf_shared" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" -dependencies = [ - "siphasher 0.3.11", -] - -[[package]] -name = "phf_shared" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" -dependencies = [ - "siphasher 1.0.1", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "piper" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" -dependencies = [ - "atomic-waker", - "fastrand", - "futures-io", -] - -[[package]] -name = "pkg-config" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" - -[[package]] -name = "plist" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" -dependencies = [ - "base64 0.22.1", - "indexmap 2.11.4", - "quick-xml", - "serde", - "time", -] - -[[package]] -name = "png" -version = "0.17.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" -dependencies = [ - "bitflags 1.3.2", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide", -] - -[[package]] -name = "polling" -version = "3.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" -dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi", - "pin-project-lite", - "rustix", - "windows-sys 0.61.1", -] - -[[package]] -name = "potential_utf" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" -dependencies = [ - "zerovec", -] - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "precomputed-hash" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" - -[[package]] -name = "proc-macro-crate" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" -dependencies = [ - "once_cell", - "toml_edit 0.19.15", -] - -[[package]] -name = "proc-macro-crate" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" -dependencies = [ - "toml_datetime 0.6.3", - "toml_edit 0.20.2", -] - -[[package]] -name = "proc-macro-crate" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" -dependencies = [ - "toml_edit 0.23.6", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro-hack" -version = "0.5.20+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" - -[[package]] -name = "proc-macro2" -version = "1.0.101" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quick-xml" -version = "0.38.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42a232e7487fc2ef313d96dde7948e7a3c05101870d8985e4fd8d26aedd27b89" -dependencies = [ - "memchr", -] - -[[package]] -name = "quote" -version = "1.0.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", - "rand_pcg", -] - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.16", -] - -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "rand_pcg" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "raw-window-handle" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" - -[[package]] -name = "redox_syscall" -version = "0.5.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" -dependencies = [ - "bitflags 2.9.4", -] - -[[package]] -name = "redox_users" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" -dependencies = [ - "getrandom 0.2.16", - "libredox", - "thiserror 2.0.17", -] - -[[package]] -name = "ref-cast" -version = "1.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" -dependencies = [ - "ref-cast-impl", -] - -[[package]] -name = "ref-cast-impl" -version = "1.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "regex" -version = "1.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" - -[[package]] -name = "reqwest" -version = "0.12.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" -dependencies = [ - "base64 0.22.1", - "bytes", - "futures-core", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-util", - "js-sys", - "log", - "percent-encoding", - "pin-project-lite", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tokio-util", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams", - "web-sys", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - -[[package]] -name = "rustix" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" -dependencies = [ - "bitflags 2.9.4", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.61.1", -] - -[[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "schemars" -version = "0.8.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" -dependencies = [ - "dyn-clone", - "indexmap 1.9.3", - "schemars_derive", - "serde", - "serde_json", - "url", - "uuid", -] - -[[package]] -name = "schemars" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" -dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", -] - -[[package]] -name = "schemars" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" -dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", -] - -[[package]] -name = "schemars_derive" -version = "0.8.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn 2.0.106", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "selectors" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c37578180969d00692904465fb7f6b3d50b9a2b952b87c23d0e2e5cb5013416" -dependencies = [ - "bitflags 1.3.2", - "cssparser", - "derive_more", - "fxhash", - "log", - "phf 0.8.0", - "phf_codegen 0.8.0", - "precomputed-hash", - "servo_arc", - "smallvec", -] - -[[package]] -name = "semver" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" -dependencies = [ - "serde", - "serde_core", -] - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde-untagged" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" -dependencies = [ - "erased-serde", - "serde", - "serde_core", - "typeid", -] - -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "serde_derive_internals" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "serde_json" -version = "1.0.145" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", - "serde_core", -] - -[[package]] -name = "serde_repr" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "serde_spanned" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_spanned" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5417783452c2be558477e104686f7de5dae53dba813c28435e0e70f82d9b04ee" -dependencies = [ - "serde_core", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_with" -version = "3.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c522100790450cf78eeac1507263d0a350d4d5b30df0c8e1fe051a10c22b376e" -dependencies = [ - "base64 0.22.1", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.11.4", - "schemars 0.9.0", - "schemars 1.0.4", - "serde", - "serde_derive", - "serde_json", - "serde_with_macros", - "time", -] - -[[package]] -name = "serde_with_macros" -version = "3.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327ada00f7d64abaac1e55a6911e90cf665aa051b9a561c7006c157f4633135e" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "serialize-to-javascript" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5" -dependencies = [ - "serde", - "serde_json", - "serialize-to-javascript-impl", -] - -[[package]] -name = "serialize-to-javascript-impl" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "servo_arc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52aa42f8fdf0fed91e5ce7f23d8138441002fa31dca008acf47e6fd4721f741" -dependencies = [ - "nodrop", - "stable_deref_trait", -] - -[[package]] -name = "sha2" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "signal-hook-registry" -version = "1.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" -dependencies = [ - "libc", -] - -[[package]] -name = "simd-adler32" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" - -[[package]] -name = "siphasher" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" - -[[package]] -name = "siphasher" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" - -[[package]] -name = "slab" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" - -[[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - -[[package]] -name = "socket2" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" -dependencies = [ - "libc", - "windows-sys 0.59.0", -] - -[[package]] -name = "softbuffer" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18051cdd562e792cad055119e0cdb2cfc137e44e3987532e0f9659a77931bb08" -dependencies = [ - "bytemuck", - "cfg_aliases", - "core-graphics", - "foreign-types", - "js-sys", - "log", - "objc2 0.5.2", - "objc2-foundation 0.2.2", - "objc2-quartz-core 0.2.2", - "raw-window-handle", - "redox_syscall", - "wasm-bindgen", - "web-sys", - "windows-sys 0.59.0", -] - -[[package]] -name = "soup3" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" -dependencies = [ - "futures-channel", - "gio", - "glib", - "libc", - "soup3-sys", -] - -[[package]] -name = "soup3-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" -dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "string_cache" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" -dependencies = [ - "new_debug_unreachable", - "parking_lot", - "phf_shared 0.11.3", - "precomputed-hash", - "serde", -] - -[[package]] -name = "string_cache_codegen" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", - "proc-macro2", - "quote", -] - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "swift-rs" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7" -dependencies = [ - "base64 0.21.7", - "serde", - "serde_json", -] - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "system-deps" -version = "6.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" -dependencies = [ - "cfg-expr", - "heck 0.5.0", - "pkg-config", - "toml 0.8.2", - "version-compare", -] - -[[package]] -name = "tao" -version = "0.34.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "959469667dbcea91e5485fc48ba7dd6023face91bb0f1a14681a70f99847c3f7" -dependencies = [ - "bitflags 2.9.4", - "block2 0.6.1", - "core-foundation", - "core-graphics", - "crossbeam-channel", - "dispatch", - "dlopen2", - "dpi", - "gdkwayland-sys", - "gdkx11-sys", - "gtk", - "jni", - "lazy_static", - "libc", - "log", - "ndk", - "ndk-context", - "ndk-sys", - "objc2 0.6.2", - "objc2-app-kit", - "objc2-foundation 0.3.1", - "once_cell", - "parking_lot", - "raw-window-handle", - "scopeguard", - "tao-macros", - "unicode-segmentation", - "url", - "windows", - "windows-core 0.61.2", - "windows-version", - "x11-dl", -] - -[[package]] -name = "tao-macros" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "target-lexicon" -version = "0.12.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" - -[[package]] -name = "tauri" -version = "2.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d1d3b3dc4c101ac989fd7db77e045cc6d91a25349cd410455cb5c57d510c1c" -dependencies = [ - "anyhow", - "bytes", - "cookie", - "dirs", - "dunce", - "embed_plist", - "getrandom 0.3.3", - "glob", - "gtk", - "heck 0.5.0", - "http", - "jni", - "libc", - "log", - "mime", - "muda", - "objc2 0.6.2", - "objc2-app-kit", - "objc2-foundation 0.3.1", - "objc2-ui-kit", - "objc2-web-kit", - "percent-encoding", - "plist", - "raw-window-handle", - "reqwest", - "serde", - "serde_json", - "serde_repr", - "serialize-to-javascript", - "swift-rs", - "tauri-build", - "tauri-macros", - "tauri-runtime", - "tauri-runtime-wry", - "tauri-utils", - "thiserror 2.0.17", - "tokio", - "tray-icon", - "url", - "urlpattern", - "webkit2gtk", - "webview2-com", - "window-vibrancy", - "windows", -] - -[[package]] -name = "tauri-build" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c432ccc9ff661803dab74c6cd78de11026a578a9307610bbc39d3c55be7943f" -dependencies = [ - "anyhow", - "cargo_toml", - "dirs", - "glob", - "heck 0.5.0", - "json-patch", - "schemars 0.8.22", - "semver", - "serde", - "serde_json", - "tauri-utils", - "tauri-winres", - "toml 0.9.7", - "walkdir", -] - -[[package]] -name = "tauri-codegen" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ab3a62cf2e6253936a8b267c2e95839674e7439f104fa96ad0025e149d54d8a" -dependencies = [ - "base64 0.22.1", - "brotli", - "ico", - "json-patch", - "plist", - "png", - "proc-macro2", - "quote", - "semver", - "serde", - "serde_json", - "sha2", - "syn 2.0.106", - "tauri-utils", - "thiserror 2.0.17", - "time", - "url", - "uuid", - "walkdir", -] - -[[package]] -name = "tauri-macros" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4368ea8094e7045217edb690f493b55b30caf9f3e61f79b4c24b6db91f07995e" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.106", - "tauri-codegen", - "tauri-utils", -] - -[[package]] -name = "tauri-plugin" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9946a3cede302eac0c6eb6c6070ac47b1768e326092d32efbb91f21ed58d978f" -dependencies = [ - "anyhow", - "glob", - "plist", - "schemars 0.8.22", - "serde", - "serde_json", - "tauri-utils", - "toml 0.9.7", - "walkdir", -] - -[[package]] -name = "tauri-plugin-opener" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786156aa8e89e03d271fbd3fe642207da8e65f3c961baa9e2930f332bf80a1f5" -dependencies = [ - "dunce", - "glob", - "objc2-app-kit", - "objc2-foundation 0.3.1", - "open", - "schemars 0.8.22", - "serde", - "serde_json", - "tauri", - "tauri-plugin", - "thiserror 2.0.17", - "url", - "windows", - "zbus", -] - -[[package]] -name = "tauri-runtime" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4cfc9ad45b487d3fded5a4731a567872a4812e9552e3964161b08edabf93846" -dependencies = [ - "cookie", - "dpi", - "gtk", - "http", - "jni", - "objc2 0.6.2", - "objc2-ui-kit", - "objc2-web-kit", - "raw-window-handle", - "serde", - "serde_json", - "tauri-utils", - "thiserror 2.0.17", - "url", - "webkit2gtk", - "webview2-com", - "windows", -] - -[[package]] -name = "tauri-runtime-wry" -version = "2.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1fe9d48bd122ff002064e88cfcd7027090d789c4302714e68fcccba0f4b7807" -dependencies = [ - "gtk", - "http", - "jni", - "log", - "objc2 0.6.2", - "objc2-app-kit", - "objc2-foundation 0.3.1", - "once_cell", - "percent-encoding", - "raw-window-handle", - "softbuffer", - "tao", - "tauri-runtime", - "tauri-utils", - "url", - "webkit2gtk", - "webview2-com", - "windows", - "wry", -] - -[[package]] -name = "tauri-utils" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41a3852fdf9a4f8fbeaa63dc3e9a85284dd6ef7200751f0bd66ceee30c93f212" -dependencies = [ - "anyhow", - "brotli", - "cargo_metadata", - "ctor", - "dunce", - "glob", - "html5ever", - "http", - "infer", - "json-patch", - "kuchikiki", - "log", - "memchr", - "phf 0.11.3", - "proc-macro2", - "quote", - "regex", - "schemars 0.8.22", - "semver", - "serde", - "serde-untagged", - "serde_json", - "serde_with", - "swift-rs", - "thiserror 2.0.17", - "toml 0.9.7", - "url", - "urlpattern", - "uuid", - "walkdir", -] - -[[package]] -name = "tauri-winres" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd21509dd1fa9bd355dc29894a6ff10635880732396aa38c0066c1e6c1ab8074" -dependencies = [ - "embed-resource", - "toml 0.9.7", -] - -[[package]] -name = "tempfile" -version = "3.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" -dependencies = [ - "fastrand", - "getrandom 0.3.3", - "once_cell", - "rustix", - "windows-sys 0.61.1", -] - -[[package]] -name = "tendril" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" -dependencies = [ - "futf", - "mac", - "utf-8", -] - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" -dependencies = [ - "thiserror-impl 2.0.17", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "time" -version = "0.3.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" -dependencies = [ - "deranged", - "itoa", - "num-conv", - "powerfmt", - "serde_core", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" - -[[package]] -name = "time-macros" -version = "0.2.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "tinystr" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tokio" -version = "1.47.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" -dependencies = [ - "backtrace", - "bytes", - "io-uring", - "libc", - "mio", - "pin-project-lite", - "slab", - "socket2", - "windows-sys 0.59.0", -] - -[[package]] -name = "tokio-util" -version = "0.7.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "toml" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" -dependencies = [ - "serde", - "serde_spanned 0.6.9", - "toml_datetime 0.6.3", - "toml_edit 0.20.2", -] - -[[package]] -name = "toml" -version = "0.9.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00e5e5d9bf2475ac9d4f0d9edab68cc573dc2fd644b0dba36b0c30a92dd9eaa0" -dependencies = [ - "indexmap 2.11.4", - "serde_core", - "serde_spanned 1.0.2", - "toml_datetime 0.7.2", - "toml_parser", - "toml_writer", - "winnow 0.7.13", -] - -[[package]] -name = "toml_datetime" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_datetime" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1" -dependencies = [ - "serde_core", -] - -[[package]] -name = "toml_edit" -version = "0.19.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" -dependencies = [ - "indexmap 2.11.4", - "toml_datetime 0.6.3", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" -dependencies = [ - "indexmap 2.11.4", - "serde", - "serde_spanned 0.6.9", - "toml_datetime 0.6.3", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.23.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3effe7c0e86fdff4f69cdd2ccc1b96f933e24811c5441d44904e8683e27184b" -dependencies = [ - "indexmap 2.11.4", - "toml_datetime 0.7.2", - "toml_parser", - "winnow 0.7.13", -] - -[[package]] -name = "toml_parser" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627" -dependencies = [ - "winnow 0.7.13", -] - -[[package]] -name = "toml_writer" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d163a63c116ce562a22cda521fcc4d79152e7aba014456fb5eb442f6d6a10109" - -[[package]] -name = "tower" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-http" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" -dependencies = [ - "bitflags 2.9.4", - "bytes", - "futures-util", - "http", - "http-body", - "iri-string", - "pin-project-lite", - "tower", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" -dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "tracing-core" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" -dependencies = [ - "once_cell", -] - -[[package]] -name = "tray-icon" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0d92153331e7d02ec09137538996a7786fe679c629c279e82a6be762b7e6fe2" -dependencies = [ - "crossbeam-channel", - "dirs", - "libappindicator", - "muda", - "objc2 0.6.2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-foundation 0.3.1", - "once_cell", - "png", - "serde", - "thiserror 2.0.17", - "windows-sys 0.59.0", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "typeid" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" - -[[package]] -name = "typenum" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" - -[[package]] -name = "uds_windows" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" -dependencies = [ - "memoffset", - "tempfile", - "winapi", -] - -[[package]] -name = "unic-char-property" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" -dependencies = [ - "unic-char-range", -] - -[[package]] -name = "unic-char-range" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" - -[[package]] -name = "unic-common" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" - -[[package]] -name = "unic-ucd-ident" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" -dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", -] - -[[package]] -name = "unic-ucd-version" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" -dependencies = [ - "unic-common", -] - -[[package]] -name = "unicode-ident" -version = "1.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" - -[[package]] -name = "unicode-segmentation" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" - -[[package]] -name = "url" -version = "2.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", -] - -[[package]] -name = "urlpattern" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" -dependencies = [ - "regex", - "serde", - "unic-ucd-ident", - "url", -] - -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "uuid" -version = "1.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" -dependencies = [ - "getrandom 0.3.3", - "js-sys", - "serde", - "wasm-bindgen", -] - -[[package]] -name = "version-compare" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "vswhom" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" -dependencies = [ - "libc", - "vswhom-sys", -] - -[[package]] -name = "vswhom-sys" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wasi" -version = "0.14.7+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" -dependencies = [ - "wasip2", -] - -[[package]] -name = "wasip2" -version = "1.0.1+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn 2.0.106", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" -dependencies = [ - "cfg-if", - "js-sys", - "once_cell", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "wasm-streams" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "web-sys" -version = "0.3.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webkit2gtk" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76b1bc1e54c581da1e9f179d0b38512ba358fb1af2d634a1affe42e37172361a" -dependencies = [ - "bitflags 1.3.2", - "cairo-rs", - "gdk", - "gdk-sys", - "gio", - "gio-sys", - "glib", - "glib-sys", - "gobject-sys", - "gtk", - "gtk-sys", - "javascriptcore-rs", - "libc", - "once_cell", - "soup3", - "webkit2gtk-sys", -] - -[[package]] -name = "webkit2gtk-sys" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62daa38afc514d1f8f12b8693d30d5993ff77ced33ce30cd04deebc267a6d57c" -dependencies = [ - "bitflags 1.3.2", - "cairo-sys-rs", - "gdk-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "gtk-sys", - "javascriptcore-rs-sys", - "libc", - "pkg-config", - "soup3-sys", - "system-deps", -] - -[[package]] -name = "webview2-com" -version = "0.38.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4ba622a989277ef3886dd5afb3e280e3dd6d974b766118950a08f8f678ad6a4" -dependencies = [ - "webview2-com-macros", - "webview2-com-sys", - "windows", - "windows-core 0.61.2", - "windows-implement", - "windows-interface", -] - -[[package]] -name = "webview2-com-macros" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d228f15bba3b9d56dde8bddbee66fa24545bd17b48d5128ccf4a8742b18e431" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "webview2-com-sys" -version = "0.38.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36695906a1b53a3bf5c4289621efedac12b73eeb0b89e7e1a89b517302d5d75c" -dependencies = [ - "thiserror 2.0.17", - "windows", - "windows-core 0.61.2", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" -dependencies = [ - "windows-sys 0.61.1", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "window-vibrancy" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" -dependencies = [ - "objc2 0.6.2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation 0.3.1", - "raw-window-handle", - "windows-sys 0.59.0", - "windows-version", -] - -[[package]] -name = "windows" -version = "0.61.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" -dependencies = [ - "windows-collections", - "windows-core 0.61.2", - "windows-future", - "windows-link 0.1.3", - "windows-numerics", -] - -[[package]] -name = "windows-collections" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" -dependencies = [ - "windows-core 0.61.2", -] - -[[package]] -name = "windows-core" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link 0.1.3", - "windows-result 0.3.4", - "windows-strings 0.4.2", -] - -[[package]] -name = "windows-core" -version = "0.62.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6844ee5416b285084d3d3fffd743b925a6c9385455f64f6d4fa3031c4c2749a9" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link 0.2.0", - "windows-result 0.4.0", - "windows-strings 0.5.0", -] - -[[package]] -name = "windows-future" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" -dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", - "windows-threading", -] - -[[package]] -name = "windows-implement" -version = "0.60.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edb307e42a74fb6de9bf3a02d9712678b22399c87e6fa869d6dfcd8c1b7754e0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "windows-interface" -version = "0.59.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0abd1ddbc6964ac14db11c7213d6532ef34bd9aa042c2e5935f59d7908b46a5" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - -[[package]] -name = "windows-link" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" - -[[package]] -name = "windows-numerics" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" -dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", -] - -[[package]] -name = "windows-result" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows-result" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f" -dependencies = [ - "windows-link 0.2.0", -] - -[[package]] -name = "windows-strings" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows-strings" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda" -dependencies = [ - "windows-link 0.2.0", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.4", -] - -[[package]] -name = "windows-sys" -version = "0.61.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f" -dependencies = [ - "windows-link 0.2.0", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d42b7b7f66d2a06854650af09cfdf8713e427a439c97ad65a6375318033ac4b" -dependencies = [ - "windows-link 0.2.0", - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", -] - -[[package]] -name = "windows-threading" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows-version" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "700dad7c058606087f6fdc1f88da5841e06da40334413c6cd4367b25ef26d24e" -dependencies = [ - "windows-link 0.2.0", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" - -[[package]] -name = "winnow" -version = "0.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" -dependencies = [ - "memchr", -] - -[[package]] -name = "winnow" -version = "0.7.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" -dependencies = [ - "memchr", -] - -[[package]] -name = "winreg" -version = "0.55.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" -dependencies = [ - "cfg-if", - "windows-sys 0.59.0", -] - -[[package]] -name = "wit-bindgen" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" - -[[package]] -name = "writeable" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" - -[[package]] -name = "wry" -version = "0.53.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f0e9642a0d061f6236c54ccae64c2722a7879ad4ec7dff59bd376d446d8e90" -dependencies = [ - "base64 0.22.1", - "block2 0.6.1", - "cookie", - "crossbeam-channel", - "dirs", - "dpi", - "dunce", - "gdkx11", - "gtk", - "html5ever", - "http", - "javascriptcore-rs", - "jni", - "kuchikiki", - "libc", - "ndk", - "objc2 0.6.2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation 0.3.1", - "objc2-ui-kit", - "objc2-web-kit", - "once_cell", - "percent-encoding", - "raw-window-handle", - "sha2", - "soup3", - "tao-macros", - "thiserror 2.0.17", - "url", - "webkit2gtk", - "webkit2gtk-sys", - "webview2-com", - "windows", - "windows-core 0.61.2", - "windows-version", - "x11-dl", -] - -[[package]] -name = "x11" -version = "2.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" -dependencies = [ - "libc", - "pkg-config", -] - -[[package]] -name = "x11-dl" -version = "2.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" -dependencies = [ - "libc", - "once_cell", - "pkg-config", -] - -[[package]] -name = "yoke" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" -dependencies = [ - "serde", - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", - "synstructure", -] - -[[package]] -name = "zbus" -version = "5.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d07e46d035fb8e375b2ce63ba4e4ff90a7f73cf2ffb0138b29e1158d2eaadf7" -dependencies = [ - "async-broadcast", - "async-executor", - "async-io", - "async-lock", - "async-process", - "async-recursion", - "async-task", - "async-trait", - "blocking", - "enumflags2", - "event-listener", - "futures-core", - "futures-lite", - "hex", - "nix", - "ordered-stream", - "serde", - "serde_repr", - "tracing", - "uds_windows", - "windows-sys 0.60.2", - "winnow 0.7.13", - "zbus_macros", - "zbus_names", - "zvariant", -] - -[[package]] -name = "zbus_macros" -version = "5.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57e797a9c847ed3ccc5b6254e8bcce056494b375b511b3d6edcec0aeb4defaca" -dependencies = [ - "proc-macro-crate 3.4.0", - "proc-macro2", - "quote", - "syn 2.0.106", - "zbus_names", - "zvariant", - "zvariant_utils", -] - -[[package]] -name = "zbus_names" -version = "4.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97" -dependencies = [ - "serde", - "static_assertions", - "winnow 0.7.13", - "zvariant", -] - -[[package]] -name = "zerocopy" -version = "0.8.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "zerofrom" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", - "synstructure", -] - -[[package]] -name = "zerotrie" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "zvariant" -version = "5.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "999dd3be73c52b1fccd109a4a81e4fcd20fab1d3599c8121b38d04e1419498db" -dependencies = [ - "endi", - "enumflags2", - "serde", - "winnow 0.7.13", - "zvariant_derive", - "zvariant_utils", -] - -[[package]] -name = "zvariant_derive" -version = "5.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6643fd0b26a46d226bd90d3f07c1b5321fe9bb7f04673cb37ac6d6883885b68e" -dependencies = [ - "proc-macro-crate 3.4.0", - "proc-macro2", - "quote", - "syn 2.0.106", - "zvariant_utils", -] - -[[package]] -name = "zvariant_utils" -version = "3.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6949d142f89f6916deca2232cf26a8afacf2b9fdc35ce766105e104478be599" -dependencies = [ - "proc-macro2", - "quote", - "serde", - "syn 2.0.106", - "winnow 0.7.13", -] diff --git a/packages/app/src-tauri/Cargo.toml b/packages/app/src-tauri/Cargo.toml deleted file mode 100644 index 155fe8b1a..000000000 --- a/packages/app/src-tauri/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "blitzkit" -version = "0.1.0" -description = "A Tauri App" -authors = ["you"] -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[lib] -# The `_lib` suffix may seem redundant but it is necessary -# to make the lib name unique and wouldn't conflict with the bin name. -# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519 -name = "blitzkit_lib" -crate-type = ["staticlib", "cdylib", "rlib"] - -[build-dependencies] -tauri-build = { version = "2", features = [] } - -[dependencies] -tauri = { version = "2", features = [] } -tauri-plugin-opener = "2" -serde = { version = "1", features = ["derive"] } -serde_json = "1" - diff --git a/packages/app/src-tauri/build.rs b/packages/app/src-tauri/build.rs deleted file mode 100644 index d860e1e6a..000000000 --- a/packages/app/src-tauri/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - tauri_build::build() -} diff --git a/packages/app/src-tauri/capabilities/default.json b/packages/app/src-tauri/capabilities/default.json deleted file mode 100644 index 4cdbf49a7..000000000 --- a/packages/app/src-tauri/capabilities/default.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "$schema": "../gen/schemas/desktop-schema.json", - "identifier": "default", - "description": "Capability for the main window", - "windows": ["main"], - "permissions": [ - "core:default", - "opener:default" - ] -} diff --git a/packages/app/src-tauri/src/lib.rs b/packages/app/src-tauri/src/lib.rs deleted file mode 100644 index 4a277ef35..000000000 --- a/packages/app/src-tauri/src/lib.rs +++ /dev/null @@ -1,14 +0,0 @@ -// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/ -#[tauri::command] -fn greet(name: &str) -> String { - format!("Hello, {}! You've been greeted from Rust!", name) -} - -#[cfg_attr(mobile, tauri::mobile_entry_point)] -pub fn run() { - tauri::Builder::default() - .plugin(tauri_plugin_opener::init()) - .invoke_handler(tauri::generate_handler![greet]) - .run(tauri::generate_context!()) - .expect("error while running tauri application"); -} diff --git a/packages/app/src-tauri/src/main.rs b/packages/app/src-tauri/src/main.rs deleted file mode 100644 index 986d1d794..000000000 --- a/packages/app/src-tauri/src/main.rs +++ /dev/null @@ -1,27 +0,0 @@ -#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] - -use std::process::{Command, Stdio}; -use tauri::Manager; - -fn main() { - tauri::Builder::default() - .setup(|app| { - let app_handle = app.handle(); - - let entry = app_handle - .path_resolver() - .resolve_resource("../../dist/website/server/entry.mjs") - .expect("failed to resolve server.ts"); - - Command::new("bun") - .arg("run") - .arg(entry) - .stderr(Stdio::null()) - .spawn() - .expect("failed to start Bun server"); - - Ok(()) - }) - .run(tauri::generate_context!()) - .expect("error while running Tauri app"); -} diff --git a/packages/app/src-tauri/tauri.conf.json b/packages/app/src-tauri/tauri.conf.json deleted file mode 100644 index 36d837238..000000000 --- a/packages/app/src-tauri/tauri.conf.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "$schema": "https://schema.tauri.app/config/2", - "productName": "blitzkit", - "version": "0.1.0", - "identifier": "com.tresabhi.blitzkit", - "build": { - "beforeDevCommand": "cd ../website && bun run dev", - "_beforeBuildCommand": "cd ../website && bun run build", - "frontendDist": "../../../dist/website/client", - "devUrl": "http://localhost:4321" - }, - "app": { - "withGlobalTauri": true, - "windows": [ - { - "url": "http://localhost:4321", - "title": "BlitzKit", - "fullscreen": false, - "resizable": true, - "maximized": true - } - ], - "security": { - "csp": null - } - }, - "bundle": { - "active": true, - "targets": "all", - "icon": [ - "icons/32x32.png", - "icons/128x128.png", - "icons/128x128@2x.png", - "icons/icon.icns", - "icons/icon.ico" - ] - } -} diff --git a/packages/app/tsconfig.json b/packages/app/tsconfig.json deleted file mode 100644 index b533f27f1..000000000 --- a/packages/app/tsconfig.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "extends": ["../../tsconfig.json"], - "compilerOptions": { - "target": "ES2020", - "useDefineForClassFields": true, - "module": "ESNext", - "lib": ["ES2020", "DOM", "DOM.Iterable"], - "skipLibCheck": true, - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true - } -} diff --git a/packages/i18n/strings/en.json b/packages/i18n/strings/en.json index 418c90783..070868b14 100644 --- a/packages/i18n/strings/en.json +++ b/packages/i18n/strings/en.json @@ -308,15 +308,6 @@ }, "home": { - "pwa": { - "title": "Install the BlitzKit App", - "new": "NEW", - "checking": "Checking compatibility...", - "instructions": "Instructions for Apple devices.", - "install": "Install", - "dismiss": "Dismiss" - }, - "discord": { "join_short": "Join cool people!", "join_long": "Join super duper cool people!", diff --git a/rust-project.json b/rust-project.json deleted file mode 100644 index 17546412b..000000000 --- a/rust-project.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "crates": [ - { - "root_module": "packages/app/src-tauri/src/main.rs", - "edition": "2021", - "deps": [], - "cfg": [] - } - ] -} From 7ee49c4026f03e0e90cadb49972f72c7e7c41fc7 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Fri, 31 Jul 2026 20:28:18 -0500 Subject: [PATCH 008/147] remove prisma --- bun.lock | 279 +++---------------------------------- package.json | 2 - packages/core/package.json | 1 - 3 files changed, 16 insertions(+), 266 deletions(-) diff --git a/bun.lock b/bun.lock index fe8a7edec..3bc4bf582 100644 --- a/bun.lock +++ b/bun.lock @@ -5,51 +5,11 @@ "": { "name": "blitzkit", "dependencies": { - "@prisma/client": "^6.18.0", "lz4js": "^0.2.0", - "prisma": "^6.18.0", "sharp": "^0.34.5", "typescript": "^5.9.3", }, }, - "packages/app": { - "name": "@blitzkit/app", - "dependencies": { - "@tauri-apps/api": "^2", - "@tauri-apps/cli": "^2", - "@tauri-apps/plugin-opener": "^2", - "typescript": "~5.6.2", - "vite": "^6.0.3", - }, - }, - "packages/bot": { - "name": "@blitzkit/bot", - "dependencies": { - "@blitzkit/core": "workspace:*", - "@blitzkit/i18n": "workspace:*", - "@radix-ui/colors": "^3.0.0", - "@radix-ui/themes": "^3.3.0", - "@resvg/resvg-js": "^2.6.2", - "@types/adm-zip": "^0.5.7", - "@types/bun": "^1.3.9", - "@types/lodash-es": "^4.17.12", - "@types/markdown-escape": "^1.1.3", - "@types/react": "^19.2.14", - "adm-zip": "^0.5.16", - "bepaint": "^0.1.0-beta.3", - "discord.js": "^14.25.1", - "dotenv": "^17.3.1", - "fuzzysort": "^3.1.0", - "lodash-es": "^4.17.23", - "markdown-escape": "^2.0.0", - "pickleparser": "^0.2.1", - "prisma": "^6.19.2", - "protobufjs": "^7.5.4", - "react": "^19.2.4", - "satori": "^0.18.4", - "sharp": "^0.34.5", - }, - }, "packages/closed": { "name": "@blitzkit/closed", "dependencies": { @@ -68,7 +28,6 @@ "@bufbuild/protobuf": "^2.9.0", "@octokit/plugin-throttling": "^11.0.1", "@octokit/rest": "^22.0.0", - "@prisma/client": "^6.16.2", "@types/adm-zip": "^0.5.7", "@types/bun": "^1.3.1", "@types/lodash-es": "^4.17.12", @@ -394,10 +353,6 @@ "@bbob/plugin-helper": ["@bbob/plugin-helper@2.9.0", "", {}, "sha512-idpUcNQ2co6T1oU/7/DG/ZRfipSSkTn9Ozw9f5vaXH7nzV3qhqZnhFVlHTzGGnRlzKlBwWOBzOdWi4Zeqg1c5A=="], - "@blitzkit/app": ["@blitzkit/app@workspace:packages/app"], - - "@blitzkit/bot": ["@blitzkit/bot@workspace:packages/bot"], - "@blitzkit/closed": ["@blitzkit/closed@workspace:packages/closed"], "@blitzkit/core": ["@blitzkit/core@workspace:packages/core"], @@ -414,18 +369,6 @@ "@dimforge/rapier3d-compat": ["@dimforge/rapier3d-compat@0.12.0", "", {}, "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow=="], - "@discordjs/builders": ["@discordjs/builders@1.13.1", "", { "dependencies": { "@discordjs/formatters": "^0.6.2", "@discordjs/util": "^1.2.0", "@sapphire/shapeshift": "^4.0.0", "discord-api-types": "^0.38.33", "fast-deep-equal": "^3.1.3", "ts-mixer": "^6.0.4", "tslib": "^2.6.3" } }, "sha512-cOU0UDHc3lp/5nKByDxkmRiNZBpdp0kx55aarbiAfakfKJHlxv/yFW1zmIqCAmwH5CRlrH9iMFKJMpvW4DPB+w=="], - - "@discordjs/collection": ["@discordjs/collection@1.5.3", "", {}, "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ=="], - - "@discordjs/formatters": ["@discordjs/formatters@0.6.2", "", { "dependencies": { "discord-api-types": "^0.38.33" } }, "sha512-y4UPwWhH6vChKRkGdMB4odasUbHOUwy7KL+OVwF86PvT6QVOwElx+TiI1/6kcmcEe+g5YRXJFiXSXUdabqZOvQ=="], - - "@discordjs/rest": ["@discordjs/rest@2.6.0", "", { "dependencies": { "@discordjs/collection": "^2.1.1", "@discordjs/util": "^1.1.1", "@sapphire/async-queue": "^1.5.3", "@sapphire/snowflake": "^3.5.3", "@vladfrangu/async_event_emitter": "^2.4.6", "discord-api-types": "^0.38.16", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", "undici": "6.21.3" } }, "sha512-RDYrhmpB7mTvmCKcpj+pc5k7POKszS4E2O9TYc+U+Y4iaCP+r910QdO43qmpOja8LRr1RJ0b3U+CqVsnPqzf4w=="], - - "@discordjs/util": ["@discordjs/util@1.2.0", "", { "dependencies": { "discord-api-types": "^0.38.33" } }, "sha512-3LKP7F2+atl9vJFhaBjn4nOaSWahZ/yWjOvA4e5pnXkt2qyXRCHLxoBQy81GFtLGCq7K9lPm9R517M1U+/90Qg=="], - - "@discordjs/ws": ["@discordjs/ws@1.2.3", "", { "dependencies": { "@discordjs/collection": "^2.1.0", "@discordjs/rest": "^2.5.1", "@discordjs/util": "^1.1.0", "@sapphire/async-queue": "^1.5.2", "@types/ws": "^8.5.10", "@vladfrangu/async_event_emitter": "^2.2.4", "discord-api-types": "^0.38.1", "tslib": "^2.6.2", "ws": "^8.17.0" } }, "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw=="], - "@doctormckay/stdlib": ["@doctormckay/stdlib@2.10.0", "", { "dependencies": { "psl": "^1.9.0" } }, "sha512-bwy+gPn6oa2KTpfxJKX3leZoV/wHDVtO0/gq3usPvqPswG//dcf3jVB8LcbRRsKO3BXCt5DqctOQ+Xb07ivxnw=="], "@doctormckay/steam-crypto": ["@doctormckay/steam-crypto@1.2.0", "", {}, "sha512-lsxgLw640gEdZBOXpVIcYWcYD+V+QbtEsMPzRvjmjz2XXKc7QeEMyHL07yOFRmay+cUwO4ObKTJO0dSInEuq5g=="], @@ -672,20 +615,6 @@ "@oslojs/encoding": ["@oslojs/encoding@1.1.0", "", {}, "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ=="], - "@prisma/client": ["@prisma/client@6.18.0", "", { "peerDependencies": { "prisma": "*", "typescript": ">=5.1.0" }, "optionalPeers": ["prisma", "typescript"] }, "sha512-jnL2I9gDnPnw4A+4h5SuNn8Gc+1mL1Z79U/3I9eE2gbxJG1oSA+62ByPW4xkeDgwE0fqMzzpAZ7IHxYnLZ4iQA=="], - - "@prisma/config": ["@prisma/config@6.18.0", "", { "dependencies": { "c12": "3.1.0", "deepmerge-ts": "7.1.5", "effect": "3.18.4", "empathic": "2.0.0" } }, "sha512-rgFzspCpwsE+q3OF/xkp0fI2SJ3PfNe9LLMmuSVbAZ4nN66WfBiKqJKo/hLz3ysxiPQZf8h1SMf2ilqPMeWATQ=="], - - "@prisma/debug": ["@prisma/debug@6.18.0", "", {}, "sha512-PMVPMmxPj0ps1VY75DIrT430MoOyQx9hmm174k6cmLZpcI95rAPXOQ+pp8ANQkJtNyLVDxnxVJ0QLbrm/ViBcg=="], - - "@prisma/engines": ["@prisma/engines@6.18.0", "", { "dependencies": { "@prisma/debug": "6.18.0", "@prisma/engines-version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", "@prisma/fetch-engine": "6.18.0", "@prisma/get-platform": "6.18.0" } }, "sha512-i5RzjGF/ex6AFgqEe2o1IW8iIxJGYVQJVRau13kHPYEL1Ck8Zvwuzamqed/1iIljs5C7L+Opiz5TzSsUebkriA=="], - - "@prisma/engines-version": ["@prisma/engines-version@6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", "", {}, "sha512-T7Af4QsJQnSgWN1zBbX+Cha5t4qjHRxoeoWpK4JugJzG/ipmmDMY5S+O0N1ET6sCBNVkf6lz+Y+ZNO9+wFU8pQ=="], - - "@prisma/fetch-engine": ["@prisma/fetch-engine@6.18.0", "", { "dependencies": { "@prisma/debug": "6.18.0", "@prisma/engines-version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", "@prisma/get-platform": "6.18.0" } }, "sha512-TdaBvTtBwP3IoqVYoGIYpD4mWlk0pJpjTJjir/xLeNWlwog7Sl3bD2J0jJ8+5+q/6RBg+acb9drsv5W6lqae7A=="], - - "@prisma/get-platform": ["@prisma/get-platform@6.18.0", "", { "dependencies": { "@prisma/debug": "6.18.0" } }, "sha512-uXNJCJGhxTCXo2B25Ta91Rk1/Nmlqg9p7G9GKh8TPhxvAyXCvMNQoogj4JLEUy+3ku8g59cpyQIKFhqY2xO2bg=="], - "@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="], "@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="], @@ -838,32 +767,6 @@ "@react-three/fiber": ["@react-three/fiber@9.4.0", "", { "dependencies": { "@babel/runtime": "^7.17.8", "@types/react-reconciler": "^0.32.0", "@types/webxr": "*", "base64-js": "^1.5.1", "buffer": "^6.0.3", "its-fine": "^2.0.0", "react-reconciler": "^0.31.0", "react-use-measure": "^2.1.7", "scheduler": "^0.25.0", "suspend-react": "^0.1.3", "use-sync-external-store": "^1.4.0", "zustand": "^5.0.3" }, "peerDependencies": { "expo": ">=43.0", "expo-asset": ">=8.4", "expo-file-system": ">=11.0", "expo-gl": ">=11.0", "react": "^19.0.0", "react-dom": "^19.0.0", "react-native": ">=0.78", "three": ">=0.156" }, "optionalPeers": ["expo", "expo-asset", "expo-file-system", "expo-gl", "react-dom", "react-native"] }, "sha512-k4iu1R6e5D54918V4sqmISUkI5OgTw3v7/sDRKEC632Wd5g2WBtUS5gyG63X0GJO/HZUj1tsjSXfyzwrUHZl1g=="], - "@resvg/resvg-js": ["@resvg/resvg-js@2.6.2", "", { "optionalDependencies": { "@resvg/resvg-js-android-arm-eabi": "2.6.2", "@resvg/resvg-js-android-arm64": "2.6.2", "@resvg/resvg-js-darwin-arm64": "2.6.2", "@resvg/resvg-js-darwin-x64": "2.6.2", "@resvg/resvg-js-linux-arm-gnueabihf": "2.6.2", "@resvg/resvg-js-linux-arm64-gnu": "2.6.2", "@resvg/resvg-js-linux-arm64-musl": "2.6.2", "@resvg/resvg-js-linux-x64-gnu": "2.6.2", "@resvg/resvg-js-linux-x64-musl": "2.6.2", "@resvg/resvg-js-win32-arm64-msvc": "2.6.2", "@resvg/resvg-js-win32-ia32-msvc": "2.6.2", "@resvg/resvg-js-win32-x64-msvc": "2.6.2" } }, "sha512-xBaJish5OeGmniDj9cW5PRa/PtmuVU3ziqrbr5xJj901ZDN4TosrVaNZpEiLZAxdfnhAe7uQ7QFWfjPe9d9K2Q=="], - - "@resvg/resvg-js-android-arm-eabi": ["@resvg/resvg-js-android-arm-eabi@2.6.2", "", { "os": "android", "cpu": "arm" }, "sha512-FrJibrAk6v29eabIPgcTUMPXiEz8ssrAk7TXxsiZzww9UTQ1Z5KAbFJs+Z0Ez+VZTYgnE5IQJqBcoSiMebtPHA=="], - - "@resvg/resvg-js-android-arm64": ["@resvg/resvg-js-android-arm64@2.6.2", "", { "os": "android", "cpu": "arm64" }, "sha512-VcOKezEhm2VqzXpcIJoITuvUS/fcjIw5NA/w3tjzWyzmvoCdd+QXIqy3FBGulWdClvp4g+IfUemigrkLThSjAQ=="], - - "@resvg/resvg-js-darwin-arm64": ["@resvg/resvg-js-darwin-arm64@2.6.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-nmok2LnAd6nLUKI16aEB9ydMC6Lidiiq2m1nEBDR1LaaP7FGs4AJ90qDraxX+CWlVuRlvNjyYJTNv8qFjtL9+A=="], - - "@resvg/resvg-js-darwin-x64": ["@resvg/resvg-js-darwin-x64@2.6.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-GInyZLjgWDfsVT6+SHxQVRwNzV0AuA1uqGsOAW+0th56J7Nh6bHHKXHBWzUrihxMetcFDmQMAX1tZ1fZDYSRsw=="], - - "@resvg/resvg-js-linux-arm-gnueabihf": ["@resvg/resvg-js-linux-arm-gnueabihf@2.6.2", "", { "os": "linux", "cpu": "arm" }, "sha512-YIV3u/R9zJbpqTTNwTZM5/ocWetDKGsro0SWp70eGEM9eV2MerWyBRZnQIgzU3YBnSBQ1RcxRZvY/UxwESfZIw=="], - - "@resvg/resvg-js-linux-arm64-gnu": ["@resvg/resvg-js-linux-arm64-gnu@2.6.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-zc2BlJSim7YR4FZDQ8OUoJg5holYzdiYMeobb9pJuGDidGL9KZUv7SbiD4E8oZogtYY42UZEap7dqkkYuA91pg=="], - - "@resvg/resvg-js-linux-arm64-musl": ["@resvg/resvg-js-linux-arm64-musl@2.6.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-3h3dLPWNgSsD4lQBJPb4f+kvdOSJHa5PjTYVsWHxLUzH4IFTJUAnmuWpw4KqyQ3NA5QCyhw4TWgxk3jRkQxEKg=="], - - "@resvg/resvg-js-linux-x64-gnu": ["@resvg/resvg-js-linux-x64-gnu@2.6.2", "", { "os": "linux", "cpu": "x64" }, "sha512-IVUe+ckIerA7xMZ50duAZzwf1U7khQe2E0QpUxu5MBJNao5RqC0zwV/Zm965vw6D3gGFUl7j4m+oJjubBVoftw=="], - - "@resvg/resvg-js-linux-x64-musl": ["@resvg/resvg-js-linux-x64-musl@2.6.2", "", { "os": "linux", "cpu": "x64" }, "sha512-UOf83vqTzoYQO9SZ0fPl2ZIFtNIz/Rr/y+7X8XRX1ZnBYsQ/tTb+cj9TE+KHOdmlTFBxhYzVkP2lRByCzqi4jQ=="], - - "@resvg/resvg-js-win32-arm64-msvc": ["@resvg/resvg-js-win32-arm64-msvc@2.6.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-7C/RSgCa+7vqZ7qAbItfiaAWhyRSoD4l4BQAbVDqRRsRgY+S+hgS3in0Rxr7IorKUpGE69X48q6/nOAuTJQxeQ=="], - - "@resvg/resvg-js-win32-ia32-msvc": ["@resvg/resvg-js-win32-ia32-msvc@2.6.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-har4aPAlvjnLcil40AC77YDIk6loMawuJwFINEM7n0pZviwMkMvjb2W5ZirsNOZY4aDbo5tLx0wNMREp5Brk+w=="], - - "@resvg/resvg-js-win32-x64-msvc": ["@resvg/resvg-js-win32-x64-msvc@2.6.2", "", { "os": "win32", "cpu": "x64" }, "sha512-ZXtYhtUr5SSaBrUDq7DiyjOFJqBVL/dOBN7N/qmi/pO0IgiWW/f/ue3nbvu9joWE5aAKDoIzy/CxsY0suwGosQ=="], - "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="], "@rollup/plugin-babel": ["@rollup/plugin-babel@5.3.1", "", { "dependencies": { "@babel/helper-module-imports": "^7.10.4", "@rollup/pluginutils": "^3.1.0" }, "peerDependencies": { "@babel/core": "^7.0.0", "@types/babel__core": "^7.1.9", "rollup": "^1.20.0||^2.0.0" }, "optionalPeers": ["@types/babel__core"] }, "sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q=="], @@ -920,12 +823,6 @@ "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.52.3", "", { "os": "win32", "cpu": "x64" }, "sha512-zGIbEVVXVtauFgl3MRwGWEN36P5ZGenHRMgNw88X5wEhEBpq0XrMEZwOn07+ICrwM17XO5xfMZqh0OldCH5VTA=="], - "@sapphire/async-queue": ["@sapphire/async-queue@1.5.5", "", {}, "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg=="], - - "@sapphire/shapeshift": ["@sapphire/shapeshift@4.0.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "lodash": "^4.17.21" } }, "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg=="], - - "@sapphire/snowflake": ["@sapphire/snowflake@3.5.3", "", {}, "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ=="], - "@shikijs/core": ["@shikijs/core@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-3P8rGsg2Eh2qIHekwuQjzWhKI4jV97PhvYjYUzGqjvJfqdQPz+nMlfWahU24GZAyW1FxFI1sYjyhfh5CoLmIUA=="], "@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.3" } }, "sha512-Ty7xv32XCp8u0eQt8rItpMs6rU9Ki6LJ1dQOW3V/56PKDcpvfHPnYFbsx5FFUP2Yim34m/UkazidamMNVR4vKg=="], @@ -940,42 +837,10 @@ "@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="], - "@shuding/opentype.js": ["@shuding/opentype.js@1.4.0-beta.0", "", { "dependencies": { "fflate": "^0.7.3", "string.prototype.codepointat": "^0.2.1" }, "bin": { "ot": "bin/ot" } }, "sha512-3NgmNyH3l/Hv6EvsWJbsvpcpUba6R8IREQ83nH83cyakCw7uM1arZKNfHwv1Wz6jgqrF/j4x5ELvR6PnK9nTcA=="], - - "@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="], - "@surma/rollup-plugin-off-main-thread": ["@surma/rollup-plugin-off-main-thread@2.2.3", "", { "dependencies": { "ejs": "^3.1.6", "json5": "^2.2.0", "magic-string": "^0.25.0", "string.prototype.matchall": "^4.0.6" } }, "sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ=="], "@swc/helpers": ["@swc/helpers@0.5.17", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A=="], - "@tauri-apps/api": ["@tauri-apps/api@2.8.0", "", {}, "sha512-ga7zdhbS2GXOMTIZRT0mYjKJtR9fivsXzsyq5U3vjDL0s6DTMwYRm0UHNjzTY5dh4+LSC68Sm/7WEiimbQNYlw=="], - - "@tauri-apps/cli": ["@tauri-apps/cli@2.8.4", "", { "optionalDependencies": { "@tauri-apps/cli-darwin-arm64": "2.8.4", "@tauri-apps/cli-darwin-x64": "2.8.4", "@tauri-apps/cli-linux-arm-gnueabihf": "2.8.4", "@tauri-apps/cli-linux-arm64-gnu": "2.8.4", "@tauri-apps/cli-linux-arm64-musl": "2.8.4", "@tauri-apps/cli-linux-riscv64-gnu": "2.8.4", "@tauri-apps/cli-linux-x64-gnu": "2.8.4", "@tauri-apps/cli-linux-x64-musl": "2.8.4", "@tauri-apps/cli-win32-arm64-msvc": "2.8.4", "@tauri-apps/cli-win32-ia32-msvc": "2.8.4", "@tauri-apps/cli-win32-x64-msvc": "2.8.4" }, "bin": { "tauri": "tauri.js" } }, "sha512-ejUZBzuQRcjFV+v/gdj/DcbyX/6T4unZQjMSBZwLzP/CymEjKcc2+Fc8xTORThebHDUvqoXMdsCZt8r+hyN15g=="], - - "@tauri-apps/cli-darwin-arm64": ["@tauri-apps/cli-darwin-arm64@2.8.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-BKu8HRkYV01SMTa7r4fLx+wjgtRK8Vep7lmBdHDioP6b8XH3q2KgsAyPWfEZaZIkZ2LY4SqqGARaE9oilNe0oA=="], - - "@tauri-apps/cli-darwin-x64": ["@tauri-apps/cli-darwin-x64@2.8.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-imb9PfSd/7G6VAO7v1bQ2A3ZH4NOCbhGJFLchxzepGcXf9NKkfun157JH9mko29K6sqAwuJ88qtzbKCbWJTH9g=="], - - "@tauri-apps/cli-linux-arm-gnueabihf": ["@tauri-apps/cli-linux-arm-gnueabihf@2.8.4", "", { "os": "linux", "cpu": "arm" }, "sha512-Ml215UnDdl7/fpOrF1CNovym/KjtUbCuPgrcZ4IhqUCnhZdXuphud/JT3E8X97Y03TZ40Sjz8raXYI2ET0exzw=="], - - "@tauri-apps/cli-linux-arm64-gnu": ["@tauri-apps/cli-linux-arm64-gnu@2.8.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-pbcgBpMyI90C83CxE5REZ9ODyIlmmAPkkJXtV398X3SgZEIYy5TACYqlyyv2z5yKgD8F8WH4/2fek7+jH+ZXAw=="], - - "@tauri-apps/cli-linux-arm64-musl": ["@tauri-apps/cli-linux-arm64-musl@2.8.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-zumFeaU1Ws5Ay872FTyIm7z8kfzEHu8NcIn8M6TxbJs0a7GRV21KBdpW1zNj2qy7HynnpQCqjAYXTUUmm9JAOw=="], - - "@tauri-apps/cli-linux-riscv64-gnu": ["@tauri-apps/cli-linux-riscv64-gnu@2.8.4", "", { "os": "linux", "cpu": "none" }, "sha512-qiqbB3Zz6IyO201f+1ojxLj65WYj8mixL5cOMo63nlg8CIzsP23cPYUrx1YaDPsCLszKZo7tVs14pc7BWf+/aQ=="], - - "@tauri-apps/cli-linux-x64-gnu": ["@tauri-apps/cli-linux-x64-gnu@2.8.4", "", { "os": "linux", "cpu": "x64" }, "sha512-TaqaDd9Oy6k45Hotx3pOf+pkbsxLaApv4rGd9mLuRM1k6YS/aw81YrsMryYPThrxrScEIUcmNIHaHsLiU4GMkw=="], - - "@tauri-apps/cli-linux-x64-musl": ["@tauri-apps/cli-linux-x64-musl@2.8.4", "", { "os": "linux", "cpu": "x64" }, "sha512-ot9STAwyezN8w+bBHZ+bqSQIJ0qPZFlz/AyscpGqB/JnJQVDFQcRDmUPFEaAtt2UUHSWzN3GoTJ5ypqLBp2WQA=="], - - "@tauri-apps/cli-win32-arm64-msvc": ["@tauri-apps/cli-win32-arm64-msvc@2.8.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-+2aJ/g90dhLiOLFSD1PbElXX3SoMdpO7HFPAZB+xot3CWlAZD1tReUFy7xe0L5GAR16ZmrxpIDM9v9gn5xRy/w=="], - - "@tauri-apps/cli-win32-ia32-msvc": ["@tauri-apps/cli-win32-ia32-msvc@2.8.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-yj7WDxkL1t9Uzr2gufQ1Hl7hrHuFKTNEOyascbc109EoiAqCp0tgZ2IykQqOZmZOHU884UAWI1pVMqBhS/BfhA=="], - - "@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.8.4", "", { "os": "win32", "cpu": "x64" }, "sha512-XuvGB4ehBdd7QhMZ9qbj/8icGEatDuBNxyYHbLKsTYh90ggUlPa/AtaqcC1Fo69lGkTmq9BOKrs1aWSi7xDonA=="], - - "@tauri-apps/plugin-opener": ["@tauri-apps/plugin-opener@2.5.0", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-B0LShOYae4CZjN8leiNDbnfjSrTwoZakqKaWpfoH6nXiJwt6Rgj6RnVIffG3DoJiKsffRhMkjmBV9VeilSb4TA=="], - "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], "@tootallnate/quickjs-emscripten": ["@tootallnate/quickjs-emscripten@0.23.0", "", {}, "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA=="], @@ -1018,8 +883,6 @@ "@types/lz4js": ["@types/lz4js@0.2.1", "", {}, "sha512-aAnbA4uKPNqZqu0XK1QAwKP0Wskb4Oa7ZFqxW5CMIyGgqYQKFgBxTfK3m3KODXoOLv5t14VregzgrEak13uGQA=="], - "@types/markdown-escape": ["@types/markdown-escape@1.1.3", "", {}, "sha512-JIc1+s3y5ujKnt/+N+wq6s/QdL2qZ11fP79MijrVXsAAnzSxCbT2j/3prHRouJdZ2yFLN3vkP0HytfnoCczjOw=="], - "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], @@ -1058,8 +921,6 @@ "@types/webxr": ["@types/webxr@0.5.23", "", {}, "sha512-GPe4AsfOSpqWd3xA/0gwoKod13ChcfV67trvxaW2krUbgb9gxQjnCx8zGshzMl8LSHZlNH5gQ8LNScsDuc7nGQ=="], - "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], - "@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="], "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], @@ -1072,8 +933,6 @@ "@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="], - "@vladfrangu/async_event_emitter": ["@vladfrangu/async_event_emitter@2.4.6", "", {}, "sha512-RaI5qZo6D2CVS6sTHFKg1v5Ohq/+Bo2LZ5gzUEwZ/WkHhwtGTCB/sVLw8ijOkAUxasZ+WshN/Rzj4ywsABJ5ZA=="], - "@volar/kit": ["@volar/kit@2.4.23", "", { "dependencies": { "@volar/language-service": "2.4.23", "@volar/typescript": "2.4.23", "typesafe-path": "^0.2.2", "vscode-languageserver-textdocument": "^1.0.11", "vscode-uri": "^3.0.8" }, "peerDependencies": { "typescript": "*" } }, "sha512-YuUIzo9zwC2IkN7FStIcVl1YS9w5vkSFEZfPvnu0IbIMaR9WHhc9ZxvlT+91vrcSoRY469H2jwbrGqpG7m1KaQ=="], "@volar/language-core": ["@volar/language-core@2.4.23", "", { "dependencies": { "@volar/source-map": "2.4.23" } }, "sha512-hEEd5ET/oSmBC6pi1j6NaNYRWoAiDhINbT8rmwtINugR39loROSlufGdYMF9TaKGfz+ViGs1Idi3mAhnuPcoGQ=="], @@ -1176,8 +1035,6 @@ "before-after-hook": ["before-after-hook@4.0.0", "", {}, "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ=="], - "bepaint": ["bepaint@0.1.0-beta.3", "", { "peerDependencies": { "@radix-ui/colors": "^0.1.8" } }, "sha512-rybuEo674bEal5QUm/qO9jok3TptyRH2bdHDheFO25fw+HCBic5Oiy+ZWnOFnROCGZQy2Zcw7po4KrKSxz02Pg=="], - "bidi-js": ["bidi-js@1.0.3", "", { "dependencies": { "require-from-string": "^2.0.2" } }, "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw=="], "bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="], @@ -1220,8 +1077,6 @@ "bytebuffer": ["bytebuffer@5.0.1", "", { "dependencies": { "long": "~3" } }, "sha512-IuzSdmADppkZ6DlpycMkm8l9zeEq16fWtLvunEwFiYciR/BHo4E8/xs5piFquG+Za8OWmMqHF8zuRviz2LHvRQ=="], - "c12": ["c12@3.1.0", "", { "dependencies": { "chokidar": "^4.0.3", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^16.6.1", "exsolve": "^1.0.7", "giget": "^2.0.0", "jiti": "^2.4.2", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^1.0.0", "pkg-types": "^2.2.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "^0.3.5" }, "optionalPeers": ["magicast"] }, "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw=="], - "call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="], "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], @@ -1232,8 +1087,6 @@ "camelcase": ["camelcase@8.0.0", "", {}, "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA=="], - "camelize": ["camelize@1.0.1", "", {}, "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ=="], - "camera-controls": ["camera-controls@3.1.0", "", { "peerDependencies": { "three": ">=0.126.1" } }, "sha512-w5oULNpijgTRH0ARFJJ0R5ct1nUM3R3WP7/b8A6j9uTGpRfnsypc/RBMPQV8JQDPayUe37p/TZZY1PcUr4czOQ=="], "caniuse-lite": ["caniuse-lite@1.0.30001745", "", {}, "sha512-ywt6i8FzvdgrrrGbr1jZVObnVv6adj+0if2/omv9cmR2oiZs30zL4DIyaptKcbOrBdOIc74QTMoJvSE2QHh5UQ=="], @@ -1260,8 +1113,6 @@ "ci-info": ["ci-info@4.3.0", "", {}, "sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ=="], - "citty": ["citty@0.1.6", "", { "dependencies": { "consola": "^3.2.3" } }, "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ=="], - "classnames": ["classnames@2.5.1", "", {}, "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="], "cli-boxes": ["cli-boxes@3.0.0", "", {}, "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g=="], @@ -1292,10 +1143,6 @@ "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], - "confbox": ["confbox@0.2.2", "", {}, "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ=="], - - "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], - "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], "cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="], @@ -1318,18 +1165,8 @@ "crypto-random-string": ["crypto-random-string@2.0.0", "", {}, "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA=="], - "css-background-parser": ["css-background-parser@0.1.0", "", {}, "sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA=="], - - "css-box-shadow": ["css-box-shadow@1.0.0-3", "", {}, "sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg=="], - - "css-color-keywords": ["css-color-keywords@1.0.0", "", {}, "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg=="], - - "css-gradient-parser": ["css-gradient-parser@0.0.17", "", {}, "sha512-w2Xy9UMMwlKtou0vlRnXvWglPAceXCTtcmVSo8ZBUvqCV5aXEFP/PC6d+I464810I9FT++UACwTD5511bmGPUg=="], - "css-select": ["css-select@5.2.2", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="], - "css-to-react-native": ["css-to-react-native@3.2.0", "", { "dependencies": { "camelize": "^1.0.0", "css-color-keywords": "^1.0.0", "postcss-value-parser": "^4.0.2" } }, "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ=="], - "css-tree": ["css-tree@3.1.0", "", { "dependencies": { "mdn-data": "2.12.2", "source-map-js": "^1.0.1" } }, "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w=="], "css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="], @@ -1368,8 +1205,6 @@ "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], - "deepmerge-ts": ["deepmerge-ts@7.1.5", "", {}, "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw=="], - "define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="], "define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="], @@ -1402,10 +1237,6 @@ "diff": ["diff@5.2.0", "", {}, "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A=="], - "discord-api-types": ["discord-api-types@0.38.39", "", {}, "sha512-XRdDQvZvID1XvcFftjSmd4dcmMi/RL/jSy5sduBDAvCGFcNFHThdIQXCEBDZFe52lCNEzuIL0QJoKYAmRmxLUA=="], - - "discord.js": ["discord.js@14.25.1", "", { "dependencies": { "@discordjs/builders": "^1.13.0", "@discordjs/collection": "1.5.3", "@discordjs/formatters": "^0.6.2", "@discordjs/rest": "^2.6.0", "@discordjs/util": "^1.2.0", "@discordjs/ws": "^1.2.3", "@sapphire/snowflake": "3.5.3", "discord-api-types": "^0.38.33", "fast-deep-equal": "3.1.3", "lodash.snakecase": "4.1.1", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", "undici": "6.21.3" } }, "sha512-2l0gsPOLPs5t6GFZfQZKnL1OJNYFcuC/ETWsW4VtKVD/tg4ICa9x+jb9bkPffkMdRpRpuUaO/fKkHCBeiCKh8g=="], - "dlv": ["dlv@1.1.3", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="], "dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="], @@ -1430,8 +1261,6 @@ "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], - "effect": ["effect@3.18.4", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA=="], - "ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="], "electron-to-chromium": ["electron-to-chromium@1.5.225", "", {}, "sha512-Oiv6+nGcMg0xuSUeYumk+eE0pDk0PuQ6Gnz16pErrPtlitaFZxq95hUtvGRg90kcJ/AdsM+AW5VkVUl3fGk+SQ=="], @@ -1440,10 +1269,6 @@ "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - "emoji-regex-xs": ["emoji-regex-xs@2.0.1", "", {}, "sha512-1QFuh8l7LqUcKe24LsPUNzjrzJQ7pgRwp1QMcZ5MX6mFplk2zQ08NVCM84++1cveaUUYtcCYHmeFEuNg16sU4g=="], - - "empathic": ["empathic@2.0.0", "", {}, "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA=="], - "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], "encoding-sniffer": ["encoding-sniffer@0.2.1", "", { "dependencies": { "iconv-lite": "^0.6.3", "whatwg-encoding": "^3.1.1" } }, "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw=="], @@ -1500,17 +1325,13 @@ "exif-parser": ["exif-parser@0.1.12", "", {}, "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw=="], - "exsolve": ["exsolve@1.0.7", "", {}, "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw=="], - "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], "extract-zip": ["extract-zip@2.0.1", "", { "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", "yauzl": "^2.10.0" }, "optionalDependencies": { "@types/yauzl": "^2.9.1" }, "bin": { "extract-zip": "cli.js" } }, "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg=="], - "fast-check": ["fast-check@3.23.2", "", { "dependencies": { "pure-rand": "^6.1.0" } }, "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A=="], - "fast-content-type-parse": ["fast-content-type-parse@3.0.0", "", {}, "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg=="], - "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + "fast-deep-equal": ["fast-deep-equal@2.0.1", "", {}, "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w=="], "fast-fifo": ["fast-fifo@1.3.2", "", {}, "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="], @@ -1596,8 +1417,6 @@ "gifwrap": ["gifwrap@0.10.1", "", { "dependencies": { "image-q": "^4.0.0", "omggif": "^1.0.10" } }, "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw=="], - "giget": ["giget@2.0.0", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.6.0", "pathe": "^2.0.3" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA=="], - "github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="], "glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], @@ -1656,8 +1475,6 @@ "hastscript": ["hastscript@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w=="], - "hex-rgb": ["hex-rgb@4.3.0", "", {}, "sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw=="], - "hls.js": ["hls.js@1.6.13", "", {}, "sha512-hNEzjZNHf5bFrUNvdS4/1RjIanuJ6szpWNfTaX5I6WfGynWXGT7K/YQLYtemSvFExzeMdgdE4SsyVLJbd5PcZA=="], "html-escaper": ["html-escaper@3.0.3", "", {}, "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ=="], @@ -1828,8 +1645,6 @@ "lie": ["lie@3.3.0", "", { "dependencies": { "immediate": "~3.0.5" } }, "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ=="], - "linebreak": ["linebreak@1.1.0", "", { "dependencies": { "base64-js": "0.0.8", "unicode-trie": "^2.0.0" } }, "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ=="], - "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="], @@ -1838,8 +1653,6 @@ "lodash.debounce": ["lodash.debounce@4.0.8", "", {}, "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="], - "lodash.snakecase": ["lodash.snakecase@4.1.1", "", {}, "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw=="], - "lodash.sortby": ["lodash.sortby@4.7.0", "", {}, "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA=="], "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], @@ -1854,16 +1667,12 @@ "maath": ["maath@0.10.8", "", { "peerDependencies": { "@types/three": ">=0.134.0", "three": ">=0.134.0" } }, "sha512-tRvbDF0Pgqz+9XUa4jjfgAQ8/aPKmQdWXilFu2tMy4GWj4NOsx99HlULO4IeREfbO3a0sA145DZYyvXPkybm0g=="], - "magic-bytes.js": ["magic-bytes.js@1.12.1", "", {}, "sha512-ThQLOhN86ZkJ7qemtVRGYM+gRgR8GEXNli9H/PMvpnZsE44Xfh3wx9kGJaldg314v85m+bFW6WBMaVHJc/c3zA=="], - "magic-string": ["magic-string@0.30.19", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw=="], "magicast": ["magicast@0.3.5", "", { "dependencies": { "@babel/parser": "^7.25.4", "@babel/types": "^7.25.4", "source-map-js": "^1.2.0" } }, "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ=="], "make-dir": ["make-dir@1.3.0", "", { "dependencies": { "pify": "^3.0.0" } }, "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ=="], - "markdown-escape": ["markdown-escape@2.0.0", "", {}, "sha512-Trz4v0+XWlwy68LJIyw3bLbsJiC8XAbRCKF9DbEtZjyndKOGVx6n+wNB0VfoRmY2LKboQLeniap3xrb6LGSJ8A=="], - "markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="], "markdown-to-jsx": ["markdown-to-jsx@7.7.13", "", { "peerDependencies": { "react": ">= 0.14.0" } }, "sha512-DiueEq2bttFcSxUs85GJcQVrOr0+VVsPfj9AEUPqmExJ3f8P/iQNvZHltV4tm1XVhu1kl0vWBZWT3l99izRMaA=="], @@ -2010,8 +1819,6 @@ "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="], - "nypm": ["nypm@0.6.2", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.2", "pathe": "^2.0.3", "pkg-types": "^2.3.0", "tinyexec": "^1.0.1" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g=="], - "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], @@ -2058,8 +1865,6 @@ "parse-bmfont-xml": ["parse-bmfont-xml@1.1.6", "", { "dependencies": { "xml-parse-from-string": "^1.0.0", "xml2js": "^0.5.0" } }, "sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA=="], - "parse-css-color": ["parse-css-color@0.2.1", "", { "dependencies": { "color-name": "^1.1.4", "hex-rgb": "^4.1.0" } }, "sha512-bwS/GGIFV3b6KS4uwpzCFj4w297Yl3uqnSgIPsoQkx7GMLROXfMnWvxfNkL0oh8HVhZA4hvJoEoEIqonfJ3BWg=="], - "parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="], "parse-latin": ["parse-latin@7.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "@types/unist": "^3.0.0", "nlcst-to-string": "^4.0.0", "unist-util-modify-children": "^4.0.0", "unist-util-visit-children": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ=="], @@ -2078,14 +1883,10 @@ "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], - "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], - "peek-readable": ["peek-readable@4.1.0", "", {}, "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg=="], "pend": ["pend@1.2.0", "", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="], - "perfect-debounce": ["perfect-debounce@1.0.0", "", {}, "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="], - "permessage-deflate": ["permessage-deflate@0.1.7", "", { "dependencies": { "safe-buffer": "*" } }, "sha512-EUNi/RIsyJ1P1u9QHFwMOUWMYetqlE22ZgGbad7YP856WF4BFF0B7DuNy6vEGsgNNud6c/SkdWzkne71hH8MjA=="], "pickleparser": ["pickleparser@0.2.1", "", { "bin": { "pickletojson": "bin/pickletojson.js", "pickleparser": "bin/pickletojson.js" } }, "sha512-kMzY3uFYcR6OjOqr7nV2nkaXaBsUEOafu3zgPxeD6s/2ueMfVQH8lrymcDWBPGx0OkVxGMikxQit6jgByXjwBg=="], @@ -2102,24 +1903,18 @@ "pixelmatch": ["pixelmatch@5.3.0", "", { "dependencies": { "pngjs": "^6.0.0" }, "bin": { "pixelmatch": "bin/pixelmatch" } }, "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q=="], - "pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="], - "pngjs": ["pngjs@7.0.0", "", {}, "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow=="], "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="], "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], - "postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="], - "potpack": ["potpack@1.0.2", "", {}, "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ=="], "prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="], "pretty-bytes": ["pretty-bytes@6.1.1", "", {}, "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ=="], - "prisma": ["prisma@6.18.0", "", { "dependencies": { "@prisma/config": "6.18.0", "@prisma/engines": "6.18.0" }, "peerDependencies": { "typescript": ">=5.1.0" }, "optionalPeers": ["typescript"], "bin": { "prisma": "build/index.js" } }, "sha512-bXWy3vTk8mnRmT+SLyZBQoC2vtV9Z8u7OHvEu+aULYxwiop/CPiFZ+F56KsNRNf35jw+8wcu8pmLsjxpBxAO9g=="], - "prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="], "process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="], @@ -2152,8 +1947,6 @@ "puppeteer-core": ["puppeteer-core@24.22.3", "", { "dependencies": { "@puppeteer/browsers": "2.10.10", "chromium-bidi": "9.1.0", "debug": "^4.4.3", "devtools-protocol": "0.0.1495869", "typed-query-selector": "^2.12.0", "webdriver-bidi-protocol": "0.2.11", "ws": "^8.18.3" } }, "sha512-M/Jhg4PWRANSbL/C9im//Yb55wsWBS5wdp+h59iwM+EPicVQQCNs56iC5aEAO7avfDPRfxs4MM16wHjOYHNJEw=="], - "pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="], - "pwa-install-handler": ["pwa-install-handler@2.6.3", "", {}, "sha512-F6/Q4emNJyh/kHLYF0fn5NTKQszvpEJh2oWQLtQED8q46+c23dfppSB3nRF1O99/5cxmc3FRVpLSZ7NE5l0DoA=="], "qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="], @@ -2170,8 +1963,6 @@ "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="], - "rc9": ["rc9@2.1.2", "", { "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" } }, "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg=="], - "react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="], "react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="], @@ -2270,8 +2061,6 @@ "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], - "satori": ["satori@0.18.4", "", { "dependencies": { "@shuding/opentype.js": "1.4.0-beta.0", "css-background-parser": "^0.1.0", "css-box-shadow": "1.0.0-3", "css-gradient-parser": "^0.0.17", "css-to-react-native": "^3.0.0", "emoji-regex-xs": "^2.0.1", "escape-html": "^1.0.3", "linebreak": "^1.1.0", "parse-css-color": "^0.2.1", "postcss-value-parser": "^4.2.0", "yoga-layout": "^3.2.1" } }, "sha512-HanEzgXHlX3fzpGgxPoR3qI7FDpc/B+uE/KplzA6BkZGlWMaH98B/1Amq+OBF1pYPlGNzAXPYNHlrEVBvRBnHQ=="], - "sax": ["sax@1.4.1", "", {}, "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg=="], "scheduler": ["scheduler@0.25.0", "", {}, "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA=="], @@ -2326,7 +2115,7 @@ "socks-proxy-agent": ["socks-proxy-agent@7.0.0", "", { "dependencies": { "agent-base": "^6.0.2", "debug": "^4.3.3", "socks": "^2.6.2" } }, "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww=="], - "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + "source-map": ["source-map@0.8.0-beta.0", "", { "dependencies": { "whatwg-url": "^7.0.0" } }, "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA=="], "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], @@ -2358,8 +2147,6 @@ "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - "string.prototype.codepointat": ["string.prototype.codepointat@0.2.1", "", {}, "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg=="], - "string.prototype.matchall": ["string.prototype.matchall@4.0.12", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-abstract": "^1.23.6", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "regexp.prototype.flags": "^1.5.3", "set-function-name": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA=="], "string.prototype.trim": ["string.prototype.trim@1.2.10", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "define-data-property": "^1.1.4", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-object-atoms": "^1.0.0", "has-property-descriptors": "^1.0.2" } }, "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA=="], @@ -2442,8 +2229,6 @@ "ts-deepmerge": ["ts-deepmerge@7.0.3", "", {}, "sha512-Du/ZW2RfwV/D4cmA5rXafYjBQVuvu4qGiEEla4EmEHVHgRdx68Gftx7i66jn2bzHPwSVZY36Ae6OuDn9el4ZKA=="], - "ts-mixer": ["ts-mixer@6.0.4", "", {}, "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA=="], - "ts-poet": ["ts-poet@6.12.0", "", { "dependencies": { "dprint-node": "^1.0.8" } }, "sha512-xo+iRNMWqyvXpFTaOAvLPA5QAWO6TZrSUs5s4Odaya3epqofBu/fMLHEWl8jPmjhA0s9sgj9sNvF1BmaQlmQkA=="], "ts-proto": ["ts-proto@2.7.7", "", { "dependencies": { "@bufbuild/protobuf": "^2.0.0", "case-anything": "^2.1.13", "ts-poet": "^6.12.0", "ts-proto-descriptors": "2.0.0" }, "bin": { "protoc-gen-ts_proto": "protoc-gen-ts_proto" } }, "sha512-/OfN9/Yriji2bbpOysZ/Jzc96isOKz+eBTJEcKaIZ0PR6x1TNgVm4Lz0zfbo+J0jwFO7fJjJyssefBPQ0o1V9A=="], @@ -2484,7 +2269,7 @@ "uncrypto": ["uncrypto@0.1.3", "", {}, "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="], - "undici": ["undici@6.21.3", "", {}, "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw=="], + "undici": ["undici@7.16.0", "", {}, "sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g=="], "undici-types": ["undici-types@7.12.0", "", {}, "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ=="], @@ -2564,7 +2349,7 @@ "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], - "vite": ["vite@6.3.6", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "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-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA=="], + "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=="], "vite-plugin-pwa": ["vite-plugin-pwa@1.0.3", "", { "dependencies": { "debug": "^4.3.6", "pretty-bytes": "^6.1.1", "tinyglobby": "^0.2.10", "workbox-build": "^7.3.0", "workbox-window": "^7.3.0" }, "peerDependencies": { "@vite-pwa/assets-generator": "^1.0.0", "vite": "^3.1.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["@vite-pwa/assets-generator"] }, "sha512-/OpqIpUldALGxcsEnv/ekQiQ5xHkQ53wcoN5ewX4jiIDNGs3W+eNcI1WYZeyOLmzoEjg09D7aX0O89YGjen1aw=="], @@ -2708,8 +2493,6 @@ "yoctocolors": ["yoctocolors@2.1.2", "", {}, "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug=="], - "yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="], - "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], "zod-to-json-schema": ["zod-to-json-schema@3.24.6", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg=="], @@ -2724,6 +2507,8 @@ "@astrojs/markdown-remark/@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.7.4", "", {}, "sha512-lDA9MqE8WGi7T/t2BMi+EAXhs4Vcvr94Gqx3q15cFEz8oFZMO4/SFBqYr/UcmNlvW+35alowkVj+w9VhLvs5Cw=="], + "@astrojs/react/vite": ["vite@6.3.6", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "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-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA=="], + "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], @@ -2736,24 +2521,6 @@ "@babel/preset-env/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - "@blitzkit/app/typescript": ["typescript@5.6.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw=="], - - "@blitzkit/bot/prisma": ["prisma@6.19.2", "", { "dependencies": { "@prisma/config": "6.19.2", "@prisma/engines": "6.19.2" }, "peerDependencies": { "typescript": ">=5.1.0" }, "optionalPeers": ["typescript"], "bin": { "prisma": "build/index.js" } }, "sha512-XTKeKxtQElcq3U9/jHyxSPgiRgeYDKxWTPOf6NkXA0dNj5j40MfEsZkMbyNpwDWCUv7YBFUl7I2VK/6ALbmhEg=="], - - "@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=="], - - "@discordjs/rest/@discordjs/util": ["@discordjs/util@1.1.1", "", {}, "sha512-eddz6UnOBEB1oITPinyrB2Pttej49M9FZQY8NxgEvc3tq6ZICZ19m70RsmzRdDHk80O9NoYN/25AqJl8vPVf/g=="], - - "@discordjs/rest/discord-api-types": ["discord-api-types@0.38.26", "", {}, "sha512-xpmPviHjIJ6dFu1eNwNDIGQ3N6qmPUUYFVAx/YZ64h7ZgPkTcKjnciD8bZe8Vbeji7yS5uYljyciunpq0J5NSw=="], - - "@discordjs/ws/@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="], - - "@discordjs/ws/@discordjs/util": ["@discordjs/util@1.1.1", "", {}, "sha512-eddz6UnOBEB1oITPinyrB2Pttej49M9FZQY8NxgEvc3tq6ZICZ19m70RsmzRdDHk80O9NoYN/25AqJl8vPVf/g=="], - - "@discordjs/ws/discord-api-types": ["discord-api-types@0.38.26", "", {}, "sha512-xpmPviHjIJ6dFu1eNwNDIGQ3N6qmPUUYFVAx/YZ64h7ZgPkTcKjnciD8bZe8Vbeji7yS5uYljyciunpq0J5NSw=="], - "@jimp/core/file-type": ["file-type@16.5.4", "", { "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/types@14.1.0", "", { "dependencies": { "@octokit/openapi-types": "^25.1.0" } }, "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g=="], @@ -2780,10 +2547,10 @@ "@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], - "@shuding/opentype.js/fflate": ["fflate@0.7.4", "", {}, "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw=="], - "@surma/rollup-plugin-off-main-thread/magic-string": ["magic-string@0.25.9", "", { "dependencies": { "sourcemap-codec": "^1.4.8" } }, "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ=="], + "ajv/fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], "astro/@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.7.4", "", {}, "sha512-lDA9MqE8WGi7T/t2BMi+EAXhs4Vcvr94Gqx3q15cFEz8oFZMO4/SFBqYr/UcmNlvW+35alowkVj+w9VhLvs5Cw=="], @@ -2792,6 +2559,8 @@ "astro/sharp": ["sharp@0.34.4", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.0", "semver": "^7.7.2" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.4", "@img/sharp-darwin-x64": "0.34.4", "@img/sharp-libvips-darwin-arm64": "1.2.3", "@img/sharp-libvips-darwin-x64": "1.2.3", "@img/sharp-libvips-linux-arm": "1.2.3", "@img/sharp-libvips-linux-arm64": "1.2.3", "@img/sharp-libvips-linux-ppc64": "1.2.3", "@img/sharp-libvips-linux-s390x": "1.2.3", "@img/sharp-libvips-linux-x64": "1.2.3", "@img/sharp-libvips-linuxmusl-arm64": "1.2.3", "@img/sharp-libvips-linuxmusl-x64": "1.2.3", "@img/sharp-linux-arm": "0.34.4", "@img/sharp-linux-arm64": "0.34.4", "@img/sharp-linux-ppc64": "0.34.4", "@img/sharp-linux-s390x": "0.34.4", "@img/sharp-linux-x64": "0.34.4", "@img/sharp-linuxmusl-arm64": "0.34.4", "@img/sharp-linuxmusl-x64": "0.34.4", "@img/sharp-wasm32": "0.34.4", "@img/sharp-win32-arm64": "0.34.4", "@img/sharp-win32-ia32": "0.34.4", "@img/sharp-win32-x64": "0.34.4" } }, "sha512-FUH39xp3SBPnxWvd5iib1X8XY7J0K0X7d93sie9CJg2PO8/7gmg89Nve6OjItK53/MlAushNNxteBYfM6DEuoA=="], + "astro/vite": ["vite@6.3.6", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "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-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA=="], + "babel-plugin-polyfill-corejs2/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], "binarykvparser/long": ["long@3.2.0", "", { "bundled": true }, "sha512-ZYvPPOMqUwPoDsbJaR10iQJYnMuZhRTvHYl62ErLIEX7RgFlziSBUUvrt3OVfc47QlHHpzPZYP17g3Fv7oeJkg=="], @@ -2800,10 +2569,6 @@ "bytebuffer/long": ["long@3.2.0", "", {}, "sha512-ZYvPPOMqUwPoDsbJaR10iQJYnMuZhRTvHYl62ErLIEX7RgFlziSBUUvrt3OVfc47QlHHpzPZYP17g3Fv7oeJkg=="], - "c12/dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="], - - "cheerio/undici": ["undici@7.16.0", "", {}, "sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g=="], - "cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], "decompress-tarbz2/file-type": ["file-type@6.2.0", "", {}, "sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg=="], @@ -2812,6 +2577,8 @@ "dprint-node/detect-libc": ["detect-libc@1.0.3", "", { "bin": { "detect-libc": "./bin/detect-libc.js" } }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="], + "escodegen/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + "extract-zip/get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="], "favilib/sharp": ["sharp@0.33.5", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.6.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-x64": "0.33.5", "@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-linux-arm": "0.33.5", "@img/sharp-linux-arm64": "0.33.5", "@img/sharp-linux-s390x": "0.33.5", "@img/sharp-linux-x64": "0.33.5", "@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-wasm32": "0.33.5", "@img/sharp-win32-ia32": "0.33.5", "@img/sharp-win32-x64": "0.33.5" } }, "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw=="], @@ -2820,6 +2587,8 @@ "filelist/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="], + "fontkit/fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + "get-uri/data-uri-to-buffer": ["data-uri-to-buffer@6.0.2", "", {}, "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw=="], "hast-util-to-parse5/property-information": ["property-information@6.5.0", "", {}, "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig=="], @@ -2836,8 +2605,6 @@ "its-fine/@types/react-reconciler": ["@types/react-reconciler@0.28.9", "", { "peerDependencies": { "@types/react": "*" } }, "sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg=="], - "linebreak/base64-js": ["base64-js@0.0.8", "", {}, "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw=="], - "make-dir/pify": ["pify@3.0.0", "", {}, "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg=="], "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], @@ -2864,8 +2631,6 @@ "react-dom/scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], - "react-promise-suspense/fast-deep-equal": ["fast-deep-equal@2.0.1", "", {}, "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w=="], - "readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], "readable-web-to-node-stream/readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="], @@ -2876,6 +2641,8 @@ "simple-swizzle/is-arrayish": ["is-arrayish@0.3.4", "", {}, "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA=="], + "source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + "stats-gl/three": ["three@0.170.0", "", {}, "sha512-FQK+LEpYc0fBD+J8g6oSEyyNzjp+Q7Ks1C568WWaoMRLW+TkNNWmenWeGgJjV105Gd+p/2ql1ZcjYvNiPZBhuQ=="], "steam-appticket/@doctormckay/stdlib": ["@doctormckay/stdlib@1.16.1", "", {}, "sha512-XhuUOzElz6fnNdt70IYNKqhPAEpGaL4JHOhAvklRh0hAhVPW+/wLxaWT3DWUbaG5Dta5YvIp7+cZK3GhIpAuug=="], @@ -2922,8 +2689,6 @@ "workbox-build/rollup": ["rollup@2.79.2", "", { "optionalDependencies": { "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ=="], - "workbox-build/source-map": ["source-map@0.8.0-beta.0", "", { "dependencies": { "whatwg-url": "^7.0.0" } }, "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA=="], - "wrap-ansi/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], "wrap-ansi/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], @@ -2936,10 +2701,6 @@ "yaml-language-server/yaml": ["yaml@2.2.2", "", {}, "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA=="], - "@blitzkit/bot/prisma/@prisma/config": ["@prisma/config@6.19.2", "", { "dependencies": { "c12": "3.1.0", "deepmerge-ts": "7.1.5", "effect": "3.18.4", "empathic": "2.0.0" } }, "sha512-kadBGDl+aUswv/zZMk9Mx0C8UZs1kjao8H9/JpI4Wh4SHZaM7zkTwiKn/iFLfRg+XtOAo/Z/c6pAYhijKl0nzQ=="], - - "@blitzkit/bot/prisma/@prisma/engines": ["@prisma/engines@6.19.2", "", { "dependencies": { "@prisma/debug": "6.19.2", "@prisma/engines-version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7", "@prisma/fetch-engine": "6.19.2", "@prisma/get-platform": "6.19.2" } }, "sha512-TTkJ8r+uk/uqczX40wb+ODG0E0icVsMgwCTyTHXehaEfb0uo80M9g1aW1tEJrxmFHeOZFXdI2sTA1j1AgcHi4A=="], - "@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=="], @@ -3122,14 +2883,6 @@ "yaml-language-server/vscode-languageserver/vscode-languageserver-protocol": ["vscode-languageserver-protocol@3.16.0", "", { "dependencies": { "vscode-jsonrpc": "6.0.0", "vscode-languageserver-types": "3.16.0" } }, "sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A=="], - "@blitzkit/bot/prisma/@prisma/engines/@prisma/debug": ["@prisma/debug@6.19.2", "", {}, "sha512-lFnEZsLdFLmEVCVNdskLDCL8Uup41GDfU0LUfquw+ercJC8ODTuL0WNKgOKmYxCJVvFwf0OuZBzW99DuWmoH2A=="], - - "@blitzkit/bot/prisma/@prisma/engines/@prisma/engines-version": ["@prisma/engines-version@7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7", "", {}, "sha512-03bgb1VD5gvuumNf+7fVGBzfpJPjmqV423l/WxsWk2cNQ42JD0/SsFBPhN6z8iAvdHs07/7ei77SKu7aZfq8bA=="], - - "@blitzkit/bot/prisma/@prisma/engines/@prisma/fetch-engine": ["@prisma/fetch-engine@6.19.2", "", { "dependencies": { "@prisma/debug": "6.19.2", "@prisma/engines-version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7", "@prisma/get-platform": "6.19.2" } }, "sha512-h4Ff4Pho+SR1S8XerMCC12X//oY2bG3Iug/fUnudfcXEUnIeRiBdXHFdGlGOgQ3HqKgosTEhkZMvGM9tWtYC+Q=="], - - "@blitzkit/bot/prisma/@prisma/engines/@prisma/get-platform": ["@prisma/get-platform@6.19.2", "", { "dependencies": { "@prisma/debug": "6.19.2" } }, "sha512-PGLr06JUSTqIvztJtAzIxOwtWKtJm5WwOG6xpsgD37Rc84FpfUBGLKz65YpJBGtkRQGXTYEFie7pYALocC3MtA=="], - "astro/sharp/@img/sharp-wasm32/@emnapi/runtime": ["@emnapi/runtime@1.5.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ=="], "boxen/string-width/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], diff --git a/package.json b/package.json index 85067f6ec..9dd548e13 100644 --- a/package.json +++ b/package.json @@ -11,9 +11,7 @@ "packages/*" ], "dependencies": { - "@prisma/client": "^6.18.0", "lz4js": "^0.2.0", - "prisma": "^6.18.0", "sharp": "^0.34.5", "typescript": "^5.9.3" } diff --git a/packages/core/package.json b/packages/core/package.json index 7f5046a56..540a568ee 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -8,7 +8,6 @@ "@bufbuild/protobuf": "^2.9.0", "@octokit/plugin-throttling": "^11.0.1", "@octokit/rest": "^22.0.0", - "@prisma/client": "^6.16.2", "@types/adm-zip": "^0.5.7", "@types/bun": "^1.3.1", "@types/lodash-es": "^4.17.12", From 901cb0fcc8a90e59d0ff9d30e50d9bb81dd5416a Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Fri, 31 Jul 2026 20:30:19 -0500 Subject: [PATCH 009/147] remove assets --- .github/workflows/assets.yml | 83 ------------------------------------ 1 file changed, 83 deletions(-) delete mode 100644 .github/workflows/assets.yml diff --git a/.github/workflows/assets.yml b/.github/workflows/assets.yml deleted file mode 100644 index e9280a7a9..000000000 --- a/.github/workflows/assets.yml +++ /dev/null @@ -1,83 +0,0 @@ -name: Assets - -on: - push: - branches: production - - workflow_dispatch: - inputs: - asset_repo: - type: choice - required: false - description: What asset repo to write to - options: - - blitzkit/assets - - blitzkit/assets-preview - - blitzkit/assets-dev - default: blitzkit/assets - - asset_source: - type: string - required: false - description: "Archive URL or Steam to download (example: steam:APP_ID/DEPOT_ID or zip:URL_TO_ZIP)" - default: "steam:444200/444202" - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.inputs.asset_repo }} - cancel-in-progress: true - -env: - DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }} - DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }} - DATABASE_URL: ${{ secrets.DATABASE_URL }} - VIDEOS_SHEET: ${{ secrets.VIDEOS_SHEET }} - GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }} - WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }} - WOTB_DLC_CDN: ${{ secrets.WOTB_DLC_CDN }} - PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }} - PUBLIC_ASSET_REPO: ${{ github.event.inputs.asset_repo != '' && github.event.inputs.asset_repo || secrets.PUBLIC_ASSET_REPO }} - PUBLIC_ASSET_BRANCH: ${{ secrets.PUBLIC_ASSET_BRANCH }} - WOTB_GLOSSARY_DOMAIN: ${{ secrets.WOTB_GLOSSARY_DOMAIN }} - WOTB_GLOSSARY_PATH: ${{ secrets.WOTB_GLOSSARY_PATH }} - PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }} - ALLOW_ROBOTS: true - PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }} - PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }} - PUBLIC_PATREON_CLIENT_ID: ${{ secrets.PUBLIC_PATREON_CLIENT_ID }} - PUBLIC_PATREON_REDIRECT_URI: ${{ secrets.PUBLIC_PATREON_REDIRECT_URI }} - PATREON_CLIENT_SECRET: ${{ secrets.PATREON_CLIENT_SECRET }} - PUBLIC_NITROPAY_ID: ${{ secrets.PUBLIC_NITROPAY_ID }} - PUBLIC_DEBUG_MISSING_I18N: false - PUBLIC_DISCORD_SERVER_ID: ${{ secrets.PUBLIC_DISCORD_SERVER_ID }} - PUBLIC_ASSET_FAST_USER: blitzkit - PUBLIC_ASSET_FAST_REPO: assets - PUBLIC_ASSET_BASE: ${{ secrets.PUBLIC_ASSET_BASE }} - GH_TOKEN: ${{ secrets.GH_TOKEN }} - STEAM_USERNAME: ${{ secrets.STEAM_USERNAME }} - STEAM_PASSWORD: ${{ secrets.STEAM_PASSWORD }} - -jobs: - build_assets: - name: Build assets - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v6 - with: - submodules: recursive - token: ${{ secrets.GH_TOKEN }} - - uses: arduino/setup-protoc@v3 - with: - repo-token: ${{ secrets.GH_TOKEN }} - - uses: oven-sh/setup-bun@v2 - - - run: bun run setup - - - name: AppArmor hack - run: echo 0 | sudo tee /proc/sys/kernel/apparmor_restrict_unprivileged_userns - - - name: Install patches - run: bun run setup:patches --source=${{ github.event.inputs.asset_source || 'steam:444200/444202' }} - - - name: Build assets - run: bun run build:assets --all-targets --source=${{ github.event.inputs.asset_source || 'steam:444200/444202' }} From d81d885767672ead8e3207b462f34265b789d900 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Fri, 31 Jul 2026 20:30:35 -0500 Subject: [PATCH 010/147] reintroduce assets temporarily --- .github/workflows/assets.yml | 83 ++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 .github/workflows/assets.yml diff --git a/.github/workflows/assets.yml b/.github/workflows/assets.yml new file mode 100644 index 000000000..e9280a7a9 --- /dev/null +++ b/.github/workflows/assets.yml @@ -0,0 +1,83 @@ +name: Assets + +on: + push: + branches: production + + workflow_dispatch: + inputs: + asset_repo: + type: choice + required: false + description: What asset repo to write to + options: + - blitzkit/assets + - blitzkit/assets-preview + - blitzkit/assets-dev + default: blitzkit/assets + + asset_source: + type: string + required: false + description: "Archive URL or Steam to download (example: steam:APP_ID/DEPOT_ID or zip:URL_TO_ZIP)" + default: "steam:444200/444202" + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.inputs.asset_repo }} + cancel-in-progress: true + +env: + DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }} + DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }} + DATABASE_URL: ${{ secrets.DATABASE_URL }} + VIDEOS_SHEET: ${{ secrets.VIDEOS_SHEET }} + GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }} + WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }} + WOTB_DLC_CDN: ${{ secrets.WOTB_DLC_CDN }} + PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }} + PUBLIC_ASSET_REPO: ${{ github.event.inputs.asset_repo != '' && github.event.inputs.asset_repo || secrets.PUBLIC_ASSET_REPO }} + PUBLIC_ASSET_BRANCH: ${{ secrets.PUBLIC_ASSET_BRANCH }} + WOTB_GLOSSARY_DOMAIN: ${{ secrets.WOTB_GLOSSARY_DOMAIN }} + WOTB_GLOSSARY_PATH: ${{ secrets.WOTB_GLOSSARY_PATH }} + PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }} + ALLOW_ROBOTS: true + PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }} + PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }} + PUBLIC_PATREON_CLIENT_ID: ${{ secrets.PUBLIC_PATREON_CLIENT_ID }} + PUBLIC_PATREON_REDIRECT_URI: ${{ secrets.PUBLIC_PATREON_REDIRECT_URI }} + PATREON_CLIENT_SECRET: ${{ secrets.PATREON_CLIENT_SECRET }} + PUBLIC_NITROPAY_ID: ${{ secrets.PUBLIC_NITROPAY_ID }} + PUBLIC_DEBUG_MISSING_I18N: false + PUBLIC_DISCORD_SERVER_ID: ${{ secrets.PUBLIC_DISCORD_SERVER_ID }} + PUBLIC_ASSET_FAST_USER: blitzkit + PUBLIC_ASSET_FAST_REPO: assets + PUBLIC_ASSET_BASE: ${{ secrets.PUBLIC_ASSET_BASE }} + GH_TOKEN: ${{ secrets.GH_TOKEN }} + STEAM_USERNAME: ${{ secrets.STEAM_USERNAME }} + STEAM_PASSWORD: ${{ secrets.STEAM_PASSWORD }} + +jobs: + build_assets: + name: Build assets + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + with: + submodules: recursive + token: ${{ secrets.GH_TOKEN }} + - uses: arduino/setup-protoc@v3 + with: + repo-token: ${{ secrets.GH_TOKEN }} + - uses: oven-sh/setup-bun@v2 + + - run: bun run setup + + - name: AppArmor hack + run: echo 0 | sudo tee /proc/sys/kernel/apparmor_restrict_unprivileged_userns + + - name: Install patches + run: bun run setup:patches --source=${{ github.event.inputs.asset_source || 'steam:444200/444202' }} + + - name: Build assets + run: bun run build:assets --all-targets --source=${{ github.event.inputs.asset_source || 'steam:444200/444202' }} From 39745dd4753ec8c0708b5c3820155bc075223ab4 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Fri, 31 Jul 2026 20:48:33 -0500 Subject: [PATCH 011/147] infrastructure for inline vfs and clients --- packages/core/src/blitzkit/api/base.ts | 5 ++ packages/core/src/blitzkit/api/client.ts | 9 +++ packages/core/src/blitzkit/api/index.ts | 3 + packages/core/src/blitzkit/api/server.ts | 69 +++++++++++++++++++ packages/core/src/blitzkit/gameDefinitions.ts | 7 -- packages/core/src/blitzkit/index.ts | 3 +- .../src/blitzkit}/vfs/abstract.ts | 0 packages/core/src/blitzkit/vfs/index.ts | 5 ++ .../core => core/src/blitzkit}/vfs/local.ts | 0 .../core => core/src/blitzkit}/vfs/mixed.ts | 0 .../core => core/src/blitzkit}/vfs/steam.ts | 0 .../src/core => core/src/blitzkit}/vfs/zip.ts | 0 .../core/src/types/availableNationsYaml.ts | 3 + packages/core/src/types/index.ts | 6 +- .../src/types/squadBattleTypeStylesYaml.ts | 9 +++ .../scripts/src/buildAssets/definitions.ts | 52 +------------- .../src/buildAssets/gameModeBanners.ts | 6 +- packages/website/src/components/Footer.astro | 5 +- packages/website/src/core/blitzkit/api.ts | 15 ++++ 19 files changed, 133 insertions(+), 64 deletions(-) create mode 100644 packages/core/src/blitzkit/api/base.ts create mode 100644 packages/core/src/blitzkit/api/client.ts create mode 100644 packages/core/src/blitzkit/api/index.ts create mode 100644 packages/core/src/blitzkit/api/server.ts delete mode 100644 packages/core/src/blitzkit/gameDefinitions.ts rename packages/{scripts/src/core => core/src/blitzkit}/vfs/abstract.ts (100%) create mode 100644 packages/core/src/blitzkit/vfs/index.ts rename packages/{scripts/src/core => core/src/blitzkit}/vfs/local.ts (100%) rename packages/{scripts/src/core => core/src/blitzkit}/vfs/mixed.ts (100%) rename packages/{scripts/src/core => core/src/blitzkit}/vfs/steam.ts (100%) rename packages/{scripts/src/core => core/src/blitzkit}/vfs/zip.ts (100%) create mode 100644 packages/core/src/types/availableNationsYaml.ts create mode 100644 packages/core/src/types/squadBattleTypeStylesYaml.ts create mode 100644 packages/website/src/core/blitzkit/api.ts diff --git a/packages/core/src/blitzkit/api/base.ts b/packages/core/src/blitzkit/api/base.ts new file mode 100644 index 000000000..4de3155a9 --- /dev/null +++ b/packages/core/src/blitzkit/api/base.ts @@ -0,0 +1,5 @@ +import { GameDefinitions } from "../../protos"; + +export abstract class BlitzKitAPI { + abstract game(): Promise; +} diff --git a/packages/core/src/blitzkit/api/client.ts b/packages/core/src/blitzkit/api/client.ts new file mode 100644 index 000000000..82f751ce1 --- /dev/null +++ b/packages/core/src/blitzkit/api/client.ts @@ -0,0 +1,9 @@ +import { fetchPB } from "../../protobuf"; +import { GameDefinitions } from "../../protos"; +import { BlitzKitAPI } from "./base"; + +export class ClientBlitzKitAPI extends BlitzKitAPI { + game() { + return fetchPB("/api/definitions/game.pb", GameDefinitions); + } +} diff --git a/packages/core/src/blitzkit/api/index.ts b/packages/core/src/blitzkit/api/index.ts new file mode 100644 index 000000000..2087a06c6 --- /dev/null +++ b/packages/core/src/blitzkit/api/index.ts @@ -0,0 +1,3 @@ +export * from "./base"; +export * from "./client"; +export * from "./server"; diff --git a/packages/core/src/blitzkit/api/server.ts b/packages/core/src/blitzkit/api/server.ts new file mode 100644 index 000000000..3cfc74a66 --- /dev/null +++ b/packages/core/src/blitzkit/api/server.ts @@ -0,0 +1,69 @@ +import { + AbstractVFS, + AvailableNationsYaml, + SquadBattleTypeStylesYaml, +} from "@blitzkit/core"; +import { GameDefinitions } from "../../protos"; +import { BlitzKitAPI } from "./base"; + +export class ServerBlitzKitAPI extends BlitzKitAPI { + vfs: AbstractVFS; + + constructor(vfs: AbstractVFS) { + super(); + + this.vfs = vfs; + } + + async game() { + const versionFile = await this.vfs.text("Data/version.txt"); + const version = versionFile.split(" ")[0]; + + const availableNations = await this.vfs.yaml( + "Data/available_nations.yaml", + ); + const nations = availableNations.available_nations; + + const game: GameDefinitions = { + version, + nations, + gameModes: {}, + roles: {}, + }; + + const squadBattleTypeStyles = + await this.vfs.yaml( + `Data/UI/Screens3/Lobby/Hangar/Squad/SquadBattleType.yaml`, + ); + + for (const match of squadBattleTypeStyles.Prototypes[0].components.UIDataLocalBindingsComponent.data[1][2].matchAll( + /"(\d+)" -> "(battleType\/([a-zA-Z]+))"/g, + )) { + const id = Number(match[1]); + const name = getString(match[2]); + + gameModeNativeNames[match[3]] = id; + game.gameModes[id] = { + name, + }; + } + + Object.entries(combatRoles).forEach(([, value]) => { + gameDefinitions.roles[value.id] = { provisions: [], consumables: [] }; + + value.default_abilities.forEach((ability) => { + if (ability in consumableNativeNames) { + gameDefinitions.roles[value.id].consumables.push( + consumableNativeNames[ability], + ); + } else if (ability in provisionNativeNames) { + gameDefinitions.roles[value.id].provisions.push( + provisionNativeNames[ability], + ); + } else throw new Error(`Unknown ability ${ability}`); + }); + }); + + return game; + } +} diff --git a/packages/core/src/blitzkit/gameDefinitions.ts b/packages/core/src/blitzkit/gameDefinitions.ts deleted file mode 100644 index d7573adb0..000000000 --- a/packages/core/src/blitzkit/gameDefinitions.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { fetchPB } from '../protobuf'; -import { GameDefinitions } from '../protos'; -import { asset } from './asset'; - -export function fetchGameDefinitions() { - return fetchPB(asset('definitions/game.pb'), GameDefinitions); -} diff --git a/packages/core/src/blitzkit/index.ts b/packages/core/src/blitzkit/index.ts index fdae4b28d..935f5e654 100644 --- a/packages/core/src/blitzkit/index.ts +++ b/packages/core/src/blitzkit/index.ts @@ -7,15 +7,16 @@ export * from "./averageDefinitions"; export * from "./coefficient"; export * from "./consumableDefinitions"; // export * from './context'; +export * from "./api"; export * from "./createDefaultSkills"; export * from "./degressiveStat"; export * from "./discoveredIdDefinitions"; export * from "./emblemURL"; export * from "./equipmentDefinitions"; export * from "./eventManager"; -export * from "./gameDefinitions"; export * from "./getArchivedLatestSeasonNumber"; export * from "./sluggify"; +export * from "./vfs"; // export * from './getArchivedRatingInfo'; export * from "./getArchivedRatingLeaderboard"; // export * from './getArchivedRatingMidnightLeaderboard'; diff --git a/packages/scripts/src/core/vfs/abstract.ts b/packages/core/src/blitzkit/vfs/abstract.ts similarity index 100% rename from packages/scripts/src/core/vfs/abstract.ts rename to packages/core/src/blitzkit/vfs/abstract.ts diff --git a/packages/core/src/blitzkit/vfs/index.ts b/packages/core/src/blitzkit/vfs/index.ts new file mode 100644 index 000000000..939b46d1c --- /dev/null +++ b/packages/core/src/blitzkit/vfs/index.ts @@ -0,0 +1,5 @@ +export * from "./abstract"; +export * from "./local"; +export * from "./mixed"; +export * from "./steam"; +export * from "./zip"; diff --git a/packages/scripts/src/core/vfs/local.ts b/packages/core/src/blitzkit/vfs/local.ts similarity index 100% rename from packages/scripts/src/core/vfs/local.ts rename to packages/core/src/blitzkit/vfs/local.ts diff --git a/packages/scripts/src/core/vfs/mixed.ts b/packages/core/src/blitzkit/vfs/mixed.ts similarity index 100% rename from packages/scripts/src/core/vfs/mixed.ts rename to packages/core/src/blitzkit/vfs/mixed.ts diff --git a/packages/scripts/src/core/vfs/steam.ts b/packages/core/src/blitzkit/vfs/steam.ts similarity index 100% rename from packages/scripts/src/core/vfs/steam.ts rename to packages/core/src/blitzkit/vfs/steam.ts diff --git a/packages/scripts/src/core/vfs/zip.ts b/packages/core/src/blitzkit/vfs/zip.ts similarity index 100% rename from packages/scripts/src/core/vfs/zip.ts rename to packages/core/src/blitzkit/vfs/zip.ts diff --git a/packages/core/src/types/availableNationsYaml.ts b/packages/core/src/types/availableNationsYaml.ts new file mode 100644 index 000000000..735642b69 --- /dev/null +++ b/packages/core/src/types/availableNationsYaml.ts @@ -0,0 +1,3 @@ +export interface AvailableNationsYaml { + available_nations: string[]; +} diff --git a/packages/core/src/types/index.ts b/packages/core/src/types/index.ts index c47180e45..4b42a8d8b 100644 --- a/packages/core/src/types/index.ts +++ b/packages/core/src/types/index.ts @@ -1,2 +1,4 @@ -export * from './glossary'; -export * from './tanksStats'; +export * from "./availableNationsYaml"; +export * from "./glossary"; +export * from "./squadBattleTypeStylesYaml"; +export * from "./tanksStats"; diff --git a/packages/core/src/types/squadBattleTypeStylesYaml.ts b/packages/core/src/types/squadBattleTypeStylesYaml.ts new file mode 100644 index 000000000..f8d3d30ca --- /dev/null +++ b/packages/core/src/types/squadBattleTypeStylesYaml.ts @@ -0,0 +1,9 @@ +export interface SquadBattleTypeStylesYaml { + Prototypes: { + components: { + UIDataLocalBindingsComponent: { + data: [string, string, string][]; + }; + }; + }[]; +} diff --git a/packages/scripts/src/buildAssets/definitions.ts b/packages/scripts/src/buildAssets/definitions.ts index b2b10160d..71afd37b1 100644 --- a/packages/scripts/src/buildAssets/definitions.ts +++ b/packages/scripts/src/buildAssets/definitions.ts @@ -11,7 +11,6 @@ import { Equalizer, EquipmentDefinitions, EquipmentSlot, - GameDefinitions, GunDefinition, I18nString, MapDefinitions, @@ -491,20 +490,6 @@ export interface MapAlly { points: Array; } -interface AvailableNationsYaml { - available_nations: string[]; -} - -export interface SquadBattleTypeStyles { - Prototypes: { - components: { - UIDataLocalBindingsComponent: { - data: [string, string, string][]; - }; - }; - }[]; -} - type CombatRolesYaml = Record< string, { @@ -548,14 +533,7 @@ export async function definitions() { console.log("Building definitions..."); const uploader = new AssetUploader("definitions"); - const gameDefinitions: GameDefinitions = { - version: (await vfs.text("Data/version.txt")).split(" ")[0], - nations: ( - await vfs.yaml("Data/available_nations.yaml") - ).available_nations, - gameModes: {}, - roles: {}, - }; + const tankDefinitions: TankDefinitions = { tanks: {} }; const camouflageDefinitions: CamouflageDefinitions = { camouflages: {} }; const modelDefinitions: ModelDefinitions = { models: {} }; @@ -673,10 +651,10 @@ export async function definitions() { `Data/camouflages.yaml`, ); const camouflagesXmlEntries = Object.entries(camouflagesXml.root.camouflages); - const squadBattleTypeStyles = await vfs.yaml( + const squadBattleTypeStyles = await vfs.yaml( `Data/UI/Screens3/Lobby/Hangar/Squad/SquadBattleType.yaml`, ); - const gameTypeSelectorStyles = await vfs.yaml( + const gameTypeSelectorStyles = await vfs.yaml( `Data/UI/Screens/Lobby/Hangar/GameTypeSelector.yaml`, ); const gameModeNativeNames: Record = {}; @@ -708,12 +686,8 @@ export async function definitions() { /"(\d+)" -> "(battleType\/([a-zA-Z]+))"/g, )) { const id = Number(match[1]); - const name = getString(match[2]); gameModeNativeNames[match[3]] = id; - gameDefinitions.gameModes[id] = { - name, - }; } for (const match of gameTypeSelectorStyles.Prototypes[0].components.UIDataLocalBindingsComponent.data[1][2].matchAll( @@ -1767,22 +1741,6 @@ export async function definitions() { } }); - Object.entries(combatRoles).forEach(([, value]) => { - gameDefinitions.roles[value.id] = { provisions: [], consumables: [] }; - - value.default_abilities.forEach((ability) => { - if (ability in consumableNativeNames) { - gameDefinitions.roles[value.id].consumables.push( - consumableNativeNames[ability], - ); - } else if (ability in provisionNativeNames) { - gameDefinitions.roles[value.id].provisions.push( - provisionNativeNames[ability], - ); - } else throw new Error(`Unknown ability ${ability}`); - }); - }); - Object.entries(avatar.root.skillsByClasses).forEach(([tankClass, skills]) => { skillDefinitions.classes[ blitzTankClassToBlitzkit[tankClass as BlitzTankClass] @@ -1798,10 +1756,6 @@ export async function definitions() { }; }); - await uploader.add({ - content: GameDefinitions.encode(gameDefinitions).finish(), - path: "definitions/game.pb", - }); await uploader.add({ content: TankDefinitions.encode(tankDefinitions).finish(), path: "definitions/tanks.pb", diff --git a/packages/scripts/src/buildAssets/gameModeBanners.ts b/packages/scripts/src/buildAssets/gameModeBanners.ts index df8a6a73d..1e53a68a3 100644 --- a/packages/scripts/src/buildAssets/gameModeBanners.ts +++ b/packages/scripts/src/buildAssets/gameModeBanners.ts @@ -1,16 +1,16 @@ import sharp from "sharp"; import { AssetUploader } from "../core/github/assetUploader"; import { vfs } from "./constants"; -import { SquadBattleTypeStyles } from "./definitions"; +import { SquadBattleTypeStylesYaml } from "./definitions"; export async function gameModeBanners() { console.log("Building game mode banners..."); using uploader = new AssetUploader("game mode banners"); - const gameTypeSelectorStyles = await vfs.yaml( + const gameTypeSelectorStyles = await vfs.yaml( `Data/UI/Screens/Lobby/Hangar/GameTypeSelector.yaml`, ); - const squadBattleTypeStyles = await vfs.yaml( + const squadBattleTypeStyles = await vfs.yaml( `Data/UI/Screens3/Lobby/Hangar/Squad/SquadBattleType.yaml`, ); const bannerMatches: { name: string; path: string }[] = []; diff --git a/packages/website/src/components/Footer.astro b/packages/website/src/components/Footer.astro index 196f62f34..7db12917e 100644 --- a/packages/website/src/components/Footer.astro +++ b/packages/website/src/components/Footer.astro @@ -1,7 +1,8 @@ --- -import { assertSecret, fetchGameDefinitions } from "@blitzkit/core"; +import { assertSecret } from "@blitzkit/core"; import { Box, Code, Flex, Heading, Link, Text } from "@radix-ui/themes"; import packageJson from "../../../../package.json"; +import { api } from "../core/blitzkit/api"; import { getStrings } from "../core/i18n/getStrings"; import { LinkI18n } from "./LinkI18n"; import { LocaleSwitcherThemeWrapper } from "./LocaleSwitcher"; @@ -10,7 +11,7 @@ export interface Props { locale: string; } -const gameDefinitions = await fetchGameDefinitions(); +const gameDefinitions = await api.game(); const PUBLIC_ASSET_BRANCH = assertSecret(import.meta.env.PUBLIC_BRANCH); const { locale } = Astro.props; const strings = getStrings(locale); diff --git a/packages/website/src/core/blitzkit/api.ts b/packages/website/src/core/blitzkit/api.ts new file mode 100644 index 000000000..904849c84 --- /dev/null +++ b/packages/website/src/core/blitzkit/api.ts @@ -0,0 +1,15 @@ +import { + ClientBlitzKitAPI, + ServerBlitzKitAPI, + type BlitzKitAPI, +} from "@blitzkit/core"; + +let _api: BlitzKitAPI; + +if (import.meta.env.SSR) { + _api = new ServerBlitzKitAPI(); +} else { + _api = new ClientBlitzKitAPI(); +} + +export const api = _api; From 17c54be1c824b67e9f9535b7606663a9977f850f Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sat, 1 Aug 2026 12:56:12 -0500 Subject: [PATCH 012/147] some definitions headers --- packages/core/src/blitzkit/api/base.ts | 4 +- packages/core/src/blitzkit/api/server.ts | 143 ++++++++++++------ packages/core/src/types/blitzStrings.ts | 3 + packages/core/src/types/combatRolesYaml.ts | 7 + packages/core/src/types/consumablesCommon.ts | 24 +++ .../src/types/consumablesVehicleFilter.ts | 4 + packages/core/src/types/index.ts | 7 + .../core/src/types/optionalDeviceSlots.ts | 13 ++ packages/core/src/types/optionalDevices.ts | 10 ++ packages/core/src/types/provisionsCommon.ts | 22 +++ packages/scripts/src/buildAssets/constants.ts | 14 +- .../scripts/src/buildAssets/definitions.ts | 120 +-------------- packages/website/src/core/blitzkit/api.ts | 2 +- 13 files changed, 206 insertions(+), 167 deletions(-) create mode 100644 packages/core/src/types/blitzStrings.ts create mode 100644 packages/core/src/types/combatRolesYaml.ts create mode 100644 packages/core/src/types/consumablesCommon.ts create mode 100644 packages/core/src/types/consumablesVehicleFilter.ts create mode 100644 packages/core/src/types/optionalDeviceSlots.ts create mode 100644 packages/core/src/types/optionalDevices.ts create mode 100644 packages/core/src/types/provisionsCommon.ts diff --git a/packages/core/src/blitzkit/api/base.ts b/packages/core/src/blitzkit/api/base.ts index 4de3155a9..5b7460efb 100644 --- a/packages/core/src/blitzkit/api/base.ts +++ b/packages/core/src/blitzkit/api/base.ts @@ -1,5 +1,3 @@ -import { GameDefinitions } from "../../protos"; - export abstract class BlitzKitAPI { - abstract game(): Promise; + // abstract game(): Promise; } diff --git a/packages/core/src/blitzkit/api/server.ts b/packages/core/src/blitzkit/api/server.ts index 3cfc74a66..b155390e7 100644 --- a/packages/core/src/blitzkit/api/server.ts +++ b/packages/core/src/blitzkit/api/server.ts @@ -1,69 +1,124 @@ import { AbstractVFS, - AvailableNationsYaml, - SquadBattleTypeStylesYaml, + BlitzStrings, + CamouflageDefinitions, + ConsumableDefinitions, + ConsumablesCommon, + EquipmentDefinitions, + I18nString, + MapDefinitions, + ModelDefinitions, + OptionalDevices, + OptionalDeviceSlots, + ProvisionDefinitions, + ProvisionsCommon, + SkillDefinitions, + TankDefinitions, } from "@blitzkit/core"; -import { GameDefinitions } from "../../protos"; +import { SUPPORTED_LOCALE_BLITZ_MAP } from "@blitzkit/i18n"; +import locales from "@blitzkit/i18n/locales.json"; +import { parse as parseYaml } from "yaml"; import { BlitzKitAPI } from "./base"; export class ServerBlitzKitAPI extends BlitzKitAPI { - vfs: AbstractVFS; + private vfs: AbstractVFS; + + private stringsI18n: Record> = {}; + + private nationsDir?: string[]; + consumablesCommon: ConsumablesCommon = {}; + provisionsCommon: ProvisionsCommon = {}; + + private optionalDevices?: { root: OptionalDevices }; + private optionalDeviceSlots?: { root: OptionalDeviceSlots }; constructor(vfs: AbstractVFS) { super(); - this.vfs = vfs; } - async game() { - const versionFile = await this.vfs.text("Data/version.txt"); - const version = versionFile.split(" ")[0]; + async init() { + await Promise.all( + locales.supported.map(async ({ locale }) => { + const blitzLocale = SUPPORTED_LOCALE_BLITZ_MAP[locale]; + const cache = await fetch( + `https://stufficons.wgcdn.co/localizations/${blitzLocale}.yaml`, + ) + .then((response) => response.text()) + .then((string) => parseYaml(string) as BlitzStrings); + const preInstalled = await this.vfs.yaml( + `Data/Strings/${blitzLocale}.yaml`, + ); - const availableNations = await this.vfs.yaml( - "Data/available_nations.yaml", + this.stringsI18n[locale] = { + ...cache, + ...preInstalled, + }; + }), ); - const nations = availableNations.available_nations; - const game: GameDefinitions = { - version, - nations, - gameModes: {}, - roles: {}, + this.nationsDir = await this.vfs + .dir("Data/XML/item_defs/vehicles") + .then((files) => files.filter((nation) => nation !== "common")); + + this.optionalDevices = await this.vfs.xml<{ root: OptionalDevices }>( + "Data/XML/item_defs/vehicles/common/optional_devices.xml", + ); + this.optionalDeviceSlots = await this.vfs.xml<{ + root: OptionalDeviceSlots; + }>(`Data/XML/item_defs/vehicles/common/optional_device_slots.xml`); + } + + private getString(name: string) { + const collection: Record = { + [locales.default]: this.stringsI18n[locales.default][name], }; - const squadBattleTypeStyles = - await this.vfs.yaml( - `Data/UI/Screens3/Lobby/Hangar/Squad/SquadBattleType.yaml`, - ); + for (const { locale } of locales.supported) { + const localizedString = this.stringsI18n[locale][name]; - for (const match of squadBattleTypeStyles.Prototypes[0].components.UIDataLocalBindingsComponent.data[1][2].matchAll( - /"(\d+)" -> "(battleType\/([a-zA-Z]+))"/g, - )) { - const id = Number(match[1]); - const name = getString(match[2]); + if ( + localizedString === undefined || + localizedString === collection[locales.default] + ) { + continue; + } - gameModeNativeNames[match[3]] = id; - game.gameModes[id] = { - name, - }; + collection[locale] = localizedString; } - Object.entries(combatRoles).forEach(([, value]) => { - gameDefinitions.roles[value.id] = { provisions: [], consumables: [] }; + return { locales: collection } satisfies I18nString; + } - value.default_abilities.forEach((ability) => { - if (ability in consumableNativeNames) { - gameDefinitions.roles[value.id].consumables.push( - consumableNativeNames[ability], - ); - } else if (ability in provisionNativeNames) { - gameDefinitions.roles[value.id].provisions.push( - provisionNativeNames[ability], - ); - } else throw new Error(`Unknown ability ${ability}`); - }); - }); + async tankDefinitions() { + const tankDefinitions = TankDefinitions.create(); + } - return game; + async camouflageDefinitions() { + const camouflageDefinitions = CamouflageDefinitions.create(); + } + + async modelDefinitions() { + const modelDefinitions = ModelDefinitions.create(); + } + + async mapDefinitions() { + const mapDefinitions = MapDefinitions.create(); + } + + async equipmentDefinitions() { + const equipmentDefinitions = EquipmentDefinitions.create(); + } + + async consumableDefinitions() { + const consumableDefinitions = ConsumableDefinitions.create(); + } + + async provisionDefinitions() { + const provisionDefinitions = ProvisionDefinitions.create(); + } + + async skillDefinitions() { + const skillDefinitions = SkillDefinitions.create(); } } diff --git a/packages/core/src/types/blitzStrings.ts b/packages/core/src/types/blitzStrings.ts new file mode 100644 index 000000000..c57857a90 --- /dev/null +++ b/packages/core/src/types/blitzStrings.ts @@ -0,0 +1,3 @@ +export interface BlitzStrings { + [key: string]: string; +} diff --git a/packages/core/src/types/combatRolesYaml.ts b/packages/core/src/types/combatRolesYaml.ts new file mode 100644 index 000000000..a0a42a393 --- /dev/null +++ b/packages/core/src/types/combatRolesYaml.ts @@ -0,0 +1,7 @@ +export type CombatRolesYaml = Record< + string, + { + id: number; + default_abilities: string[]; + } +>; diff --git a/packages/core/src/types/consumablesCommon.ts b/packages/core/src/types/consumablesCommon.ts new file mode 100644 index 000000000..6b4068edf --- /dev/null +++ b/packages/core/src/types/consumablesCommon.ts @@ -0,0 +1,24 @@ +import { ConsumablesVehicleFilter } from "./consumablesVehicleFilter"; + +export interface ConsumablesCommon { + [key: string]: { + id: number; + userString: string; + description: string; + icon: string; + category: string; + tags: string; + vehicleFilter?: { + include: { vehicle: ConsumablesVehicleFilter; nations?: string }; + exclude?: { vehicle: ConsumablesVehicleFilter; nations?: string }; + }; + script: { + "#text": string; + automatic?: boolean; + cooldown: number; + duration?: number; + shotEffect?: string; + bonusValues?: { [key: string]: number }; + } & Record; + }; +} diff --git a/packages/core/src/types/consumablesVehicleFilter.ts b/packages/core/src/types/consumablesVehicleFilter.ts new file mode 100644 index 000000000..637664919 --- /dev/null +++ b/packages/core/src/types/consumablesVehicleFilter.ts @@ -0,0 +1,4 @@ +export type ConsumablesVehicleFilter = + | { minLevel: number; maxLevel: number } + | { name: string } + | { extendedTags: string }; diff --git a/packages/core/src/types/index.ts b/packages/core/src/types/index.ts index 4b42a8d8b..a0f1104fc 100644 --- a/packages/core/src/types/index.ts +++ b/packages/core/src/types/index.ts @@ -1,4 +1,11 @@ export * from "./availableNationsYaml"; +export * from "./blitzStrings"; +export * from "./combatRolesYaml"; +export * from "./consumablesCommon"; +export * from "./consumablesVehicleFilter"; export * from "./glossary"; +export * from "./optionalDevices"; +export * from "./optionalDeviceSlots"; +export * from "./provisionsCommon"; export * from "./squadBattleTypeStylesYaml"; export * from "./tanksStats"; diff --git a/packages/core/src/types/optionalDeviceSlots.ts b/packages/core/src/types/optionalDeviceSlots.ts new file mode 100644 index 000000000..df1dd9427 --- /dev/null +++ b/packages/core/src/types/optionalDeviceSlots.ts @@ -0,0 +1,13 @@ +export interface OptionalDeviceSlots { + presets: { + [key: string]: { + level0: OptionalDeviceSlotRow; + level1: OptionalDeviceSlotRow; + level2: OptionalDeviceSlotRow; + }; + }; +} + +interface OptionalDeviceSlotRow { + [key: string]: { device0: string; device1: string }; +} diff --git a/packages/core/src/types/optionalDevices.ts b/packages/core/src/types/optionalDevices.ts new file mode 100644 index 000000000..26e14bc27 --- /dev/null +++ b/packages/core/src/types/optionalDevices.ts @@ -0,0 +1,10 @@ +export interface OptionalDevices { + [key: string]: { + id: number; + userString: string; + description: string; + icon: string; + script: unknown; + display_params: unknown; + }; +} diff --git a/packages/core/src/types/provisionsCommon.ts b/packages/core/src/types/provisionsCommon.ts new file mode 100644 index 000000000..9bd27ad3e --- /dev/null +++ b/packages/core/src/types/provisionsCommon.ts @@ -0,0 +1,22 @@ +import { ConsumablesVehicleFilter } from "./consumablesVehicleFilter"; + +export interface ProvisionsCommon { + [key: string]: { + id: number; + userString: string; + description: string; + icon: string; + category: string; + tags: string; + vehicleFilter?: { + include: { vehicle: ConsumablesVehicleFilter; nations?: string }; + exclude?: { vehicle: ConsumablesVehicleFilter; nations?: string }; + }; + script: { + "#text": string; + automatic?: boolean; + shotEffect?: string; + bonusValues?: { [key: string]: number }; + } & Record; + }; +} diff --git a/packages/scripts/src/buildAssets/constants.ts b/packages/scripts/src/buildAssets/constants.ts index d295c7ad6..1cc136550 100644 --- a/packages/scripts/src/buildAssets/constants.ts +++ b/packages/scripts/src/buildAssets/constants.ts @@ -1,9 +1,11 @@ -import { assertSecret } from "@blitzkit/core"; -import { AbstractVFS } from "../core/vfs/abstract"; -import { LocalVFS } from "../core/vfs/local"; -import { MixedVFS } from "../core/vfs/mixed"; -import { SteamVFS } from "../core/vfs/steam"; -import { ZipVFS } from "../core/vfs/zip"; +import { + AbstractVFS, + assertSecret, + LocalVFS, + MixedVFS, + SteamVFS, + ZipVFS, +} from "@blitzkit/core"; export const PATCHES_ROOT = "../../temp/patches"; diff --git a/packages/scripts/src/buildAssets/definitions.ts b/packages/scripts/src/buildAssets/definitions.ts index 71afd37b1..8d7f4ac99 100644 --- a/packages/scripts/src/buildAssets/definitions.ts +++ b/packages/scripts/src/buildAssets/definitions.ts @@ -12,7 +12,6 @@ import { EquipmentDefinitions, EquipmentSlot, GunDefinition, - I18nString, MapDefinitions, ModelDefinitions, ModuleType, @@ -22,6 +21,7 @@ import { ShellType, SkillDefinitions, sluggify, + SquadBattleTypeStylesYaml, TankClass, TankDefinitions, TankPrice, @@ -31,13 +31,9 @@ import { Unlock, Vector3, } from "@blitzkit/core"; -import { SUPPORTED_LOCALE_BLITZ_MAP } from "@blitzkit/i18n"; -import locales from "@blitzkit/i18n/locales.json"; import { readFile } from "fs/promises"; import { parse as parsePath } from "path"; import type { Vector3Tuple } from "three"; -import { parse as parseYaml } from "yaml"; -import { AssetUploader } from "../core/github/assetUploader"; import { vfs } from "./constants"; import type { Avatar } from "./skillIcons"; import type { TankParameters } from "./tankIcons"; @@ -350,31 +346,6 @@ interface GunDefinitionsList { }; }; } - -export interface OptionalDevices { - [key: string]: { - id: number; - userString: string; - description: string; - icon: string; - script: unknown; - display_params: unknown; - }; -} - -interface OptionalDeviceSlots { - presets: { - [key: string]: { - level0: OptionalDeviceSlotRow; - level1: OptionalDeviceSlotRow; - level2: OptionalDeviceSlotRow; - }; - }; -} - -interface OptionalDeviceSlotRow { - [key: string]: { device0: string; device1: string }; -} export interface ConsumablesCommon { [key: string]: { id: number; @@ -530,84 +501,7 @@ function assignArmor( } export async function definitions() { - console.log("Building definitions..."); - - const uploader = new AssetUploader("definitions"); - - const tankDefinitions: TankDefinitions = { tanks: {} }; - const camouflageDefinitions: CamouflageDefinitions = { camouflages: {} }; - const modelDefinitions: ModelDefinitions = { models: {} }; - const mapDefinitions: MapDefinitions = { maps: {} }; - const equipmentDefinitions: EquipmentDefinitions = { - presets: {}, - equipments: {}, - }; - const consumableDefinitions: ConsumableDefinitions = { consumables: {} }; - const provisionDefinitions: ProvisionDefinitions = { provisions: {} }; - const skillDefinitions: SkillDefinitions = { - classes: { - [TankClass.TANK_CLASS_LIGHT]: { skills: [] }, - [TankClass.TANK_CLASS_MEDIUM]: { skills: [] }, - [TankClass.TANK_CLASS_HEAVY]: { skills: [] }, - [TankClass.TANK_CLASS_TANK_DESTROYER]: { skills: [] }, - }, - }; - const nations = await vfs - .dir("Data/XML/item_defs/vehicles") - .then((files) => files.filter((nation) => nation !== "common")); const tankStringIdMap: Record = {}; - const optionalDevices = await vfs.xml<{ root: OptionalDevices }>( - "Data/XML/item_defs/vehicles/common/optional_devices.xml", - ); - const stringsI18n: Record> = {}; - - await Promise.all( - locales.supported.map(async ({ locale }) => { - const blitzLocale = SUPPORTED_LOCALE_BLITZ_MAP[locale]; - const cache = await fetch( - `https://stufficons.wgcdn.co/localizations/${blitzLocale}.yaml`, - ) - .then((response) => response.text()) - .then((string) => parseYaml(string) as BlitzStrings); - const preInstalled = await vfs.yaml( - `Data/Strings/${blitzLocale}.yaml`, - ); - - stringsI18n[locale] = { - ...cache, - ...preInstalled, - }; - - console.log(`Loaded strings for ${locale}`); - }), - ); - - function getString(name: string) { - const collection: Record = { - [locales.default]: stringsI18n[locales.default][name], - }; - - for (const { locale } of locales.supported) { - const localizedString = stringsI18n[locale][name]; - - if ( - localizedString === undefined || - localizedString === collection[locales.default] - ) { - continue; - } - - collection[locale] = localizedString; - } - - return { locales: collection } satisfies I18nString; - } - - const optionalDeviceSlots = await vfs.xml<{ - root: OptionalDeviceSlots; - }>(`Data/XML/item_defs/vehicles/common/optional_device_slots.xml`); - const consumables: ConsumablesCommon = {}; - const provisions: ProvisionsCommon = {}; for (const match of ( await vfs.text(`Data/XML/item_defs/vehicles/common/consumables/list.xml`) @@ -615,7 +509,7 @@ export async function definitions() { if (match[1] === "prototypes") continue; Object.assign( - consumables, + consumablesCommon, ( await vfs.xml<{ root: ConsumablesCommon }>( `Data/XML/item_defs/vehicles/common/consumables/${match[1]}.xml`, @@ -630,7 +524,7 @@ export async function definitions() { if (match[1] === "prototypes") continue; Object.assign( - provisions, + provisionsCommon, ( await vfs.xml<{ root: ConsumablesCommon }>( `Data/XML/item_defs/vehicles/common/provisions/${match[1]}.xml`, @@ -723,7 +617,7 @@ export async function definitions() { const slugRequesters = new Map(); const idToNation: Record = {}; - for (const nation of nations) { + for (const nation of nationsDir) { const tankList = await vfs.xml<{ root: VehicleDefinitionList }>( `Data/XML/item_defs/vehicles/${nation}/list.xml`, ); @@ -803,7 +697,7 @@ export async function definitions() { } }); - for (const nation of nations) { + for (const nation of nationsDir) { const tankList = await vfs.xml<{ root: VehicleDefinitionList }>( `Data/XML/item_defs/vehicles/${nation}/list.xml`, ); @@ -1538,7 +1432,7 @@ export async function definitions() { }, ); - Object.entries(consumables).forEach(([key, consumable]) => { + Object.entries(consumablesCommon).forEach(([key, consumable]) => { consumableNativeNames[key] = consumable.id; const entry: Consumable = { @@ -1638,7 +1532,7 @@ export async function definitions() { } }); - Object.entries(provisions).forEach(([key, provision]) => { + Object.entries(provisionsCommon).forEach(([key, provision]) => { provisionNativeNames[key] = provision.id; const entry: Provision = { diff --git a/packages/website/src/core/blitzkit/api.ts b/packages/website/src/core/blitzkit/api.ts index 904849c84..ac76d15c0 100644 --- a/packages/website/src/core/blitzkit/api.ts +++ b/packages/website/src/core/blitzkit/api.ts @@ -7,7 +7,7 @@ import { let _api: BlitzKitAPI; if (import.meta.env.SSR) { - _api = new ServerBlitzKitAPI(); + _api = await new ServerBlitzKitAPI(vfs).init(); } else { _api = new ClientBlitzKitAPI(); } From fd8448b0a67ff5c511cf56f1a1b86b6898a6548b Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sat, 1 Aug 2026 13:07:04 -0500 Subject: [PATCH 013/147] (2) some definitions headers --- packages/core/src/blitzkit/api/server.ts | 102 +++++++++++-- packages/core/src/types/camouflagesXml.ts | 33 +++++ packages/core/src/types/camouflagesYaml.ts | 7 + packages/core/src/types/index.ts | 4 + packages/core/src/types/mapsYaml.ts | 24 +++ packages/core/src/types/tankmenAvatar.ts | 20 +++ .../scripts/src/buildAssets/definitions.ts | 140 ++---------------- .../scripts/src/buildAssets/skillIcons.ts | 22 +-- 8 files changed, 195 insertions(+), 157 deletions(-) create mode 100644 packages/core/src/types/camouflagesXml.ts create mode 100644 packages/core/src/types/camouflagesYaml.ts create mode 100644 packages/core/src/types/mapsYaml.ts create mode 100644 packages/core/src/types/tankmenAvatar.ts diff --git a/packages/core/src/blitzkit/api/server.ts b/packages/core/src/blitzkit/api/server.ts index b155390e7..377d7e8ba 100644 --- a/packages/core/src/blitzkit/api/server.ts +++ b/packages/core/src/blitzkit/api/server.ts @@ -2,18 +2,24 @@ import { AbstractVFS, BlitzStrings, CamouflageDefinitions, + CamouflagesXml, + CamouflagesYaml, + CombatRolesYaml, ConsumableDefinitions, ConsumablesCommon, EquipmentDefinitions, I18nString, MapDefinitions, + MapsYaml, ModelDefinitions, OptionalDevices, OptionalDeviceSlots, ProvisionDefinitions, ProvisionsCommon, SkillDefinitions, + SquadBattleTypeStylesYaml, TankDefinitions, + TankmenAvatar, } from "@blitzkit/core"; import { SUPPORTED_LOCALE_BLITZ_MAP } from "@blitzkit/i18n"; import locales from "@blitzkit/i18n/locales.json"; @@ -29,8 +35,16 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { consumablesCommon: ConsumablesCommon = {}; provisionsCommon: ProvisionsCommon = {}; + // TODO: suffix with "yaml"s and "xml"s private optionalDevices?: { root: OptionalDevices }; private optionalDeviceSlots?: { root: OptionalDeviceSlots }; + private tankmenAvatar?: { root: TankmenAvatar }; + private mapsYaml?: MapsYaml; + private camouflagesXml?: { root: CamouflagesXml }; + private camouflagesYaml?: CamouflagesYaml; + private squadBattleTypeStyles?: SquadBattleTypeStylesYaml; + private gameTypeSelectorStyles?: SquadBattleTypeStylesYaml; + private combatRoles?: CombatRolesYaml; constructor(vfs: AbstractVFS) { super(); @@ -38,6 +52,37 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { } async init() { + this.nationsDir = await this.vfs + .dir("Data/XML/item_defs/vehicles") + .then((files) => files.filter((nation) => nation !== "common")); + + this.optionalDevices = await this.vfs.xml<{ root: OptionalDevices }>( + "Data/XML/item_defs/vehicles/common/optional_devices.xml", + ); + this.optionalDeviceSlots = await this.vfs.xml<{ + root: OptionalDeviceSlots; + }>("Data/XML/item_defs/vehicles/common/optional_device_slots.xml"); + this.tankmenAvatar = await this.vfs.xml<{ root: TankmenAvatar }>( + "Data/XML/item_defs/tankmen/avatar.xml", + ); + this.mapsYaml = await this.vfs.yaml("Data/maps.yaml"); + this.camouflagesXml = await this.vfs.xml<{ root: CamouflagesXml }>( + "Data/XML/item_defs/vehicles/common/camouflages.xml", + ); + this.camouflagesYaml = await this.vfs.yaml( + "Data/camouflages.yaml", + ); + this.squadBattleTypeStyles = await this.vfs.yaml( + `Data/UI/Screens3/Lobby/Hangar/Squad/SquadBattleType.yaml`, + ); + this.gameTypeSelectorStyles = + await this.vfs.yaml( + `Data/UI/Screens/Lobby/Hangar/GameTypeSelector.yaml`, + ); + this.combatRoles = await this.vfs.yaml( + `Data/XML/item_defs/vehicles/common/combat_roles.yaml`, + ); + await Promise.all( locales.supported.map(async ({ locale }) => { const blitzLocale = SUPPORTED_LOCALE_BLITZ_MAP[locale]; @@ -57,16 +102,39 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { }), ); - this.nationsDir = await this.vfs - .dir("Data/XML/item_defs/vehicles") - .then((files) => files.filter((nation) => nation !== "common")); + for (const match of ( + await this.vfs.text( + `Data/XML/item_defs/vehicles/common/consumables/list.xml`, + ) + ).matchAll(//g)) { + if (match[1] === "prototypes") continue; - this.optionalDevices = await this.vfs.xml<{ root: OptionalDevices }>( - "Data/XML/item_defs/vehicles/common/optional_devices.xml", - ); - this.optionalDeviceSlots = await this.vfs.xml<{ - root: OptionalDeviceSlots; - }>(`Data/XML/item_defs/vehicles/common/optional_device_slots.xml`); + Object.assign( + this.consumablesCommon, + ( + await this.vfs.xml<{ root: ConsumablesCommon }>( + `Data/XML/item_defs/vehicles/common/consumables/${match[1]}.xml`, + ) + ).root, + ); + } + + for (const match of ( + await this.vfs.text( + `Data/XML/item_defs/vehicles/common/provisions/list.xml`, + ) + ).matchAll(//g)) { + if (match[1] === "prototypes") continue; + + Object.assign( + this.provisionsCommon, + ( + await this.vfs.xml<{ root: ConsumablesCommon }>( + `Data/XML/item_defs/vehicles/common/provisions/${match[1]}.xml`, + ) + ).root, + ); + } } private getString(name: string) { @@ -92,33 +160,49 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { async tankDefinitions() { const tankDefinitions = TankDefinitions.create(); + + return tankDefinitions; } async camouflageDefinitions() { const camouflageDefinitions = CamouflageDefinitions.create(); + + return camouflageDefinitions; } async modelDefinitions() { const modelDefinitions = ModelDefinitions.create(); + + return modelDefinitions; } async mapDefinitions() { const mapDefinitions = MapDefinitions.create(); + + return mapDefinitions; } async equipmentDefinitions() { const equipmentDefinitions = EquipmentDefinitions.create(); + + return equipmentDefinitions; } async consumableDefinitions() { const consumableDefinitions = ConsumableDefinitions.create(); + + return consumableDefinitions; } async provisionDefinitions() { const provisionDefinitions = ProvisionDefinitions.create(); + + return provisionDefinitions; } async skillDefinitions() { const skillDefinitions = SkillDefinitions.create(); + + return skillDefinitions; } } diff --git a/packages/core/src/types/camouflagesXml.ts b/packages/core/src/types/camouflagesXml.ts new file mode 100644 index 000000000..9002904cf --- /dev/null +++ b/packages/core/src/types/camouflagesXml.ts @@ -0,0 +1,33 @@ +export interface CamouflagesXml { + camouflages: Record< + string, + { + id: number; + userString: string; + description: string; + category: string; + group: string; + kind: string; + notInShop: boolean; + unlockCostCategory?: string; + vehicleFilter: { + include?: CamouflagesInclude | CamouflagesInclude[]; + }; + script: string[] | string; + icon?: string; + unlockPremium?: string; + unlockQuest?: string; + unlockClanLevel?: number; + unlockShopBundle?: string; + unlockTankRank?: number; + } + >; +} + +type CamouflagesInclude = + | { + nations?: string; + } + | { + vehicle?: { name?: string; minLevel?: number; maxLevel?: number }; + }; diff --git a/packages/core/src/types/camouflagesYaml.ts b/packages/core/src/types/camouflagesYaml.ts new file mode 100644 index 000000000..7671432fe --- /dev/null +++ b/packages/core/src/types/camouflagesYaml.ts @@ -0,0 +1,7 @@ +export type CamouflagesYaml = Record< + string, + { + userString?: string; + shortUserString?: string; + } +>; diff --git a/packages/core/src/types/index.ts b/packages/core/src/types/index.ts index a0f1104fc..72dfb7218 100644 --- a/packages/core/src/types/index.ts +++ b/packages/core/src/types/index.ts @@ -1,11 +1,15 @@ export * from "./availableNationsYaml"; export * from "./blitzStrings"; +export * from "./camouflagesXml"; +export * from "./camouflagesYaml"; export * from "./combatRolesYaml"; export * from "./consumablesCommon"; export * from "./consumablesVehicleFilter"; export * from "./glossary"; +export * from "./mapsYaml"; export * from "./optionalDevices"; export * from "./optionalDeviceSlots"; export * from "./provisionsCommon"; export * from "./squadBattleTypeStylesYaml"; +export * from "./tankmenAvatar"; export * from "./tanksStats"; diff --git a/packages/core/src/types/mapsYaml.ts b/packages/core/src/types/mapsYaml.ts new file mode 100644 index 000000000..68aadc9b3 --- /dev/null +++ b/packages/core/src/types/mapsYaml.ts @@ -0,0 +1,24 @@ +export interface MapsYaml { + maps: { + [key: string]: { + id: number; + tags?: string; + localName: string; + avaliableInTrainingRoom: boolean; // lol wg typo + spriteFrame: number; + supremacyPointsThreshold?: number; + availableModes: number[]; + shadowMapsAvailable?: boolean; + assaultRespawnPoints: { + allies: MapAlly[]; + enemies: MapAlly[]; + }; + levels?: number[]; + }; + }; +} + +export interface MapAlly { + respawnNumber: number; + points: Array; +} diff --git a/packages/core/src/types/tankmenAvatar.ts b/packages/core/src/types/tankmenAvatar.ts new file mode 100644 index 000000000..b57550cbc --- /dev/null +++ b/packages/core/src/types/tankmenAvatar.ts @@ -0,0 +1,20 @@ +import { TankClass } from "@blitzkit/core"; + +export interface TankmenAvatar { + roles: unknown; + skillsByClasses: Record; + skills: { + [name: string]: { + userString: string; + effectDescription: string; + tipDescription: string; + icon: SkillIcon | SkillIcon[]; + type: "continuous" | "trigger"; + }; + }; +} + +interface SkillIcon { + name: string; + state: number; +} diff --git a/packages/scripts/src/buildAssets/definitions.ts b/packages/scripts/src/buildAssets/definitions.ts index 8d7f4ac99..b4a79511d 100644 --- a/packages/scripts/src/buildAssets/definitions.ts +++ b/packages/scripts/src/buildAssets/definitions.ts @@ -21,7 +21,6 @@ import { ShellType, SkillDefinitions, sluggify, - SquadBattleTypeStylesYaml, TankClass, TankDefinitions, TankPrice, @@ -35,7 +34,6 @@ import { readFile } from "fs/promises"; import { parse as parsePath } from "path"; import type { Vector3Tuple } from "three"; import { vfs } from "./constants"; -import type { Avatar } from "./skillIcons"; import type { TankParameters } from "./tankIcons"; const nationSlugDiscriminators = { @@ -389,78 +387,11 @@ export interface ProvisionsCommon { }; } -type CamouflagesInclude = - | { - nations?: string; - } - | { - vehicle?: { name?: string; minLevel?: number; maxLevel?: number }; - }; - -interface CamouflagesXML { - camouflages: Record< - string, - { - id: number; - userString: string; - description: string; - category: string; - group: string; - kind: string; - notInShop: boolean; - unlockCostCategory?: string; - vehicleFilter: { - include?: CamouflagesInclude | CamouflagesInclude[]; - }; - script: string[] | string; - icon?: string; - unlockPremium?: string; - unlockQuest?: string; - unlockClanLevel?: number; - unlockShopBundle?: string; - unlockTankRank?: number; - } - >; -} - -type CamouflagesYaml = Record< - string, - { - userString?: string; - shortUserString?: string; - } ->; - type ConsumablesVehicleFilter = | { minLevel: number; maxLevel: number } | { name: string } | { extendedTags: string }; -export interface Maps { - maps: { - [key: string]: { - id: number; - tags?: string; - localName: string; - avaliableInTrainingRoom: boolean; // lol wg typo - spriteFrame: number; - supremacyPointsThreshold?: number; - availableModes: number[]; - shadowMapsAvailable?: boolean; - assaultRespawnPoints: { - allies: MapAlly[]; - enemies: MapAlly[]; - }; - levels?: number[]; - }; - }; -} - -export interface MapAlly { - respawnNumber: number; - points: Array; -} - type CombatRolesYaml = Record< string, { @@ -502,59 +433,12 @@ function assignArmor( export async function definitions() { const tankStringIdMap: Record = {}; - - for (const match of ( - await vfs.text(`Data/XML/item_defs/vehicles/common/consumables/list.xml`) - ).matchAll(//g)) { - if (match[1] === "prototypes") continue; - - Object.assign( - consumablesCommon, - ( - await vfs.xml<{ root: ConsumablesCommon }>( - `Data/XML/item_defs/vehicles/common/consumables/${match[1]}.xml`, - ) - ).root, - ); - } - - for (const match of ( - await vfs.text(`Data/XML/item_defs/vehicles/common/provisions/list.xml`) - ).matchAll(//g)) { - if (match[1] === "prototypes") continue; - - Object.assign( - provisionsCommon, - ( - await vfs.xml<{ root: ConsumablesCommon }>( - `Data/XML/item_defs/vehicles/common/provisions/${match[1]}.xml`, - ) - ).root, - ); - } - - const avatar = await vfs.xml<{ root: Avatar }>( - `Data/XML/item_defs/tankmen/avatar.xml`, - ); - const maps = await vfs.yaml(`Data/maps.yaml`); const tankXps = new Map(); - const camouflagesXml = await vfs.xml<{ root: CamouflagesXML }>( - `Data/XML/item_defs/vehicles/common/camouflages.xml`, - ); - const camouflagesYaml = await vfs.yaml( - `Data/camouflages.yaml`, - ); + const camouflagesXmlEntries = Object.entries(camouflagesXml.root.camouflages); - const squadBattleTypeStyles = await vfs.yaml( - `Data/UI/Screens3/Lobby/Hangar/Squad/SquadBattleType.yaml`, - ); - const gameTypeSelectorStyles = await vfs.yaml( - `Data/UI/Screens/Lobby/Hangar/GameTypeSelector.yaml`, - ); + const gameModeNativeNames: Record = {}; - const combatRoles = await vfs.yaml( - `Data/XML/item_defs/vehicles/common/combat_roles.yaml`, - ); + const consumableNativeNames: Record = {}; const provisionNativeNames: Record = {}; let equalizerDefinitions: string[][]; @@ -1635,15 +1519,17 @@ export async function definitions() { } }); - Object.entries(avatar.root.skillsByClasses).forEach(([tankClass, skills]) => { - skillDefinitions.classes[ - blitzTankClassToBlitzkit[tankClass as BlitzTankClass] - ] = { - skills: skills.split(" "), - }; - }); + Object.entries(tankmenAvatar.root.skillsByClasses).forEach( + ([tankClass, skills]) => { + skillDefinitions.classes[ + blitzTankClassToBlitzkit[tankClass as BlitzTankClass] + ] = { + skills: skills.split(" "), + }; + }, + ); - Object.entries(maps.maps).forEach(([key, map]) => { + Object.entries(mapsYaml.maps).forEach(([key, map]) => { mapDefinitions.maps[map.id] = { id: map.id, name: getString(`#maps:${key}:${map.localName}`), diff --git a/packages/scripts/src/buildAssets/skillIcons.ts b/packages/scripts/src/buildAssets/skillIcons.ts index 4b3264ae1..02c27a2a3 100644 --- a/packages/scripts/src/buildAssets/skillIcons.ts +++ b/packages/scripts/src/buildAssets/skillIcons.ts @@ -1,31 +1,11 @@ -import { TankClass } from "@blitzkit/core"; import sharp from "sharp"; import { AssetUploader } from "../core/github/assetUploader"; import { vfs } from "./constants"; -interface SkillIcon { - name: string; - state: number; -} - -export interface Avatar { - roles: unknown; - skillsByClasses: Record; - skills: { - [name: string]: { - userString: string; - effectDescription: string; - tipDescription: string; - icon: SkillIcon | SkillIcon[]; - type: "continuous" | "trigger"; - }; - }; -} - export async function skillIcons() { console.log("Building skill icons..."); - const avatar = await vfs.xml<{ root: Avatar }>( + const avatar = await vfs.xml<{ root: TankmenAvatar }>( `Data/XML/item_defs/tankmen/avatar.xml`, ); const uploader = new AssetUploader("skill icons"); From 244968bbbfcb559a7a8028a8870420946fd65ea0 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sat, 1 Aug 2026 13:26:45 -0500 Subject: [PATCH 014/147] (3) some definitions headers --- packages/core/src/blitzkit/api/server.ts | 159 ++++++++++++++- packages/core/src/types/index.ts | 1 + .../core/src/types/vehicleDefinitionList.ts | 16 ++ .../scripts/src/buildAssets/definitions.ts | 188 +----------------- 4 files changed, 175 insertions(+), 189 deletions(-) create mode 100644 packages/core/src/types/vehicleDefinitionList.ts diff --git a/packages/core/src/blitzkit/api/server.ts b/packages/core/src/blitzkit/api/server.ts index 377d7e8ba..d0ea56e14 100644 --- a/packages/core/src/blitzkit/api/server.ts +++ b/packages/core/src/blitzkit/api/server.ts @@ -16,10 +16,14 @@ import { OptionalDeviceSlots, ProvisionDefinitions, ProvisionsCommon, + ResearchCost, SkillDefinitions, + sluggify, SquadBattleTypeStylesYaml, TankDefinitions, TankmenAvatar, + toUniqueId, + VehicleDefinitionList, } from "@blitzkit/core"; import { SUPPORTED_LOCALE_BLITZ_MAP } from "@blitzkit/i18n"; import locales from "@blitzkit/i18n/locales.json"; @@ -29,11 +33,23 @@ import { BlitzKitAPI } from "./base"; export class ServerBlitzKitAPI extends BlitzKitAPI { private vfs: AbstractVFS; - private stringsI18n: Record> = {}; + private nationSlugDiscriminators = { + china: "cn", + european: "eu", + france: "fr", + germany: "de", + japan: "jp", + other: "ot", + uk: "gb", + usa: "us", + ussr: "ru", + }; + private stringsI18n: Record> = {}; private nationsDir?: string[]; - consumablesCommon: ConsumablesCommon = {}; - provisionsCommon: ProvisionsCommon = {}; + private tankStringIdMap: Record = {}; + private consumablesCommon: ConsumablesCommon = {}; + private provisionsCommon: ProvisionsCommon = {}; // TODO: suffix with "yaml"s and "xml"s private optionalDevices?: { root: OptionalDevices }; @@ -45,6 +61,7 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { private squadBattleTypeStyles?: SquadBattleTypeStylesYaml; private gameTypeSelectorStyles?: SquadBattleTypeStylesYaml; private combatRoles?: CombatRolesYaml; + private tierEqualizer?: string[][]; constructor(vfs: AbstractVFS) { super(); @@ -82,6 +99,10 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { this.combatRoles = await this.vfs.yaml( `Data/XML/item_defs/vehicles/common/combat_roles.yaml`, ); + this.tierEqualizer = await this.vfs.csv( + `Data/XML/item_defs/vehicles/common/tier_equializer.csv`, + { delimiter: ";" }, + ); await Promise.all( locales.supported.map(async ({ locale }) => { @@ -161,12 +182,144 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { async tankDefinitions() { const tankDefinitions = TankDefinitions.create(); + const gameModeNativeNames: Record = {}; + const squadBattleTypeGameModeNativeNameMatches = + this.squadBattleTypeStyles!.Prototypes[0].components.UIDataLocalBindingsComponent.data[1][2].matchAll( + /"(\d+)" -> "(battleType\/([a-zA-Z]+))"/g, + ); + const gameTypeGameModeNativeNameMatches = + this.gameTypeSelectorStyles!.Prototypes[0].components.UIDataLocalBindingsComponent.data[1][2].matchAll( + /eGameMode\.([a-zA-Z]+) -> "~res:\/Gfx\/UI\/Hangar\/GameTypes\/battle-type_([^"]+)"/g, + ); + + for (const match of squadBattleTypeGameModeNativeNameMatches) { + const id = Number(match[1]); + gameModeNativeNames[match[3]] = id; + } + + for (const match of gameTypeGameModeNativeNameMatches) { + Object.entries(gameModeNativeNames).forEach(([key, value]) => { + if (key.toLowerCase() === match[2].toLowerCase()) { + gameModeNativeNames[match[1]] = value; + } + }); + } + + const botPattern = /^.+((tutorial_bot(\d+)?)|(TU))$/; + const slugRequesters = new Map(); + const idToNation: Record = {}; + + for (const nation of this.nationsDir!) { + const tankList = await this.vfs.xml<{ root: VehicleDefinitionList }>( + `Data/XML/item_defs/vehicles/${nation}/list.xml`, + ); + + for (const tankKey in tankList.root) { + if (botPattern.test(tankKey)) continue; + + const tank = tankList.root[tankKey]; + const tankId = toUniqueId(nation, tank.id); + + const name = ( + (tank.shortUserString + ? this.getString(tank.shortUserString) + : undefined) ?? this.getString(tank.userString) + ).locales.en; + + let slug = sluggify(name); + + idToNation[tankId] = nation; + + if (slugRequesters.has(slug)) { + slugRequesters.get(slug)!.push({ id: tankId, key: tankKey }); + } else { + slugRequesters.set(slug, [{ id: tankId, key: tankKey }]); + } + } + } + + const slugs = new Map(); + + slugRequesters.forEach((requesters, slug) => { + if (requesters.length === 1) { + slugs.set(requesters[0].id, slug); + return; + } + + console.warn( + `Multiple tanks share slug ${slug}: ${requesters + .map(({ key }) => key) + .join(", ")}`, + ); + + if (requesters.length !== 2) { + throw new Error("Unresolvable number of duplicates :("); + } + + const nonCanonical = requesters.find(({ key }) => key.endsWith("TUR")); + + if (nonCanonical === undefined) { + console.log("Using nations to discriminate"); + // both are non-tutorial tanks, will have to discriminate using nation + + requesters.forEach(({ id, key }) => { + const nation = idToNation[id]; + const discriminator = + this.nationSlugDiscriminators[ + nation as keyof typeof this.nationSlugDiscriminators + ]; + + console.log(`Solution: ${key} -> ${slug}-${discriminator}`); + slugs.set(id, `${slug}-${discriminator}`); + }); + } else { + console.log("Using tutorial bot suffix to discriminate"); + + const canonical = requesters.find(({ key }) => !key.endsWith("TUR")); + + if (canonical === undefined) { + throw new Error( + "Two tutorial bots share the same slug? The world is truly broken.", + ); + } + + console.log(`Solution: ${canonical.key} -> ${slug}`); + slugs.set(canonical.id, slug); + console.log(`Solution: ${nonCanonical.key} -> ${slug}-tur`); + slugs.set(nonCanonical.id, `${slug}-tur`); + } + }); + + const tankXps = new Map(); + return tankDefinitions; } async camouflageDefinitions() { const camouflageDefinitions = CamouflageDefinitions.create(); + for (const camoKey in this.camouflagesXml!.root.camouflages) { + const camo = this.camouflagesXml!.root.camouflages[camoKey]; + + const yamlEntry = this.camouflagesYaml![camoKey]; + const fullName = yamlEntry.userString + ? this.getString(yamlEntry.userString) + : undefined; + const shortName = yamlEntry.shortUserString + ? this.getString(yamlEntry.shortUserString) + : undefined; + const resolvedTankName = shortName ?? fullName; + const resolvedTankNameFull = + resolvedTankName === fullName ? undefined : fullName; + + camouflageDefinitions.camouflages[camo.id] = { + id: camo.id, + name: this.getString(camo.userString), + tank_name: resolvedTankName, + tank_name_full: resolvedTankNameFull, + }; + } + return camouflageDefinitions; } diff --git a/packages/core/src/types/index.ts b/packages/core/src/types/index.ts index 72dfb7218..83bea7b00 100644 --- a/packages/core/src/types/index.ts +++ b/packages/core/src/types/index.ts @@ -13,3 +13,4 @@ export * from "./provisionsCommon"; export * from "./squadBattleTypeStylesYaml"; export * from "./tankmenAvatar"; export * from "./tanksStats"; +export * from "./vehicleDefinitionList"; diff --git a/packages/core/src/types/vehicleDefinitionList.ts b/packages/core/src/types/vehicleDefinitionList.ts new file mode 100644 index 000000000..9997ed50a --- /dev/null +++ b/packages/core/src/types/vehicleDefinitionList.ts @@ -0,0 +1,16 @@ +export interface VehicleDefinitionList { + [key: string]: { + id: number; + userString: string; + shortUserString?: string; + description: string; + price: number | { gold: ""; "#text": number }; + sellPrice?: { gold: ""; "#text": number }; + enrichmentPermanentCost: number; + notInShop?: boolean; + tags: string; + level: number; + combatRole?: Record; + configurationModes: string; + }; +} diff --git a/packages/scripts/src/buildAssets/definitions.ts b/packages/scripts/src/buildAssets/definitions.ts index b4a79511d..dfbc375ea 100644 --- a/packages/scripts/src/buildAssets/definitions.ts +++ b/packages/scripts/src/buildAssets/definitions.ts @@ -20,7 +20,6 @@ import { ResearchCost, ShellType, SkillDefinitions, - sluggify, TankClass, TankDefinitions, TankPrice, @@ -30,24 +29,11 @@ import { Unlock, Vector3, } from "@blitzkit/core"; -import { readFile } from "fs/promises"; import { parse as parsePath } from "path"; import type { Vector3Tuple } from "three"; import { vfs } from "./constants"; import type { TankParameters } from "./tankIcons"; -const nationSlugDiscriminators = { - china: "cn", - european: "eu", - france: "fr", - germany: "de", - japan: "jp", - other: "ot", - uk: "gb", - usa: "us", - ussr: "ru", -}; - function parseResearchCost(raw: number | string) { if (typeof raw === "number") { return { @@ -293,22 +279,6 @@ interface VehicleDefinitions { }; }; } -export interface VehicleDefinitionList { - [key: string]: { - id: number; - userString: string; - shortUserString?: string; - description: string; - price: number | { gold: ""; "#text": number }; - sellPrice?: { gold: ""; "#text": number }; - enrichmentPermanentCost: number; - notInShop?: boolean; - tags: string; - level: number; - combatRole?: Record; - configurationModes: string; - }; -} interface TurretDefinitionsList { nextAvailableId: number; ids: Record; @@ -406,7 +376,6 @@ const blitzShellKindToBlitzkit: Record = { HIGH_EXPLOSIVE: ShellType.SHELL_TYPE_HE, HOLLOW_CHARGE: ShellType.SHELL_TYPE_HEAT, }; -export const botPattern = /^.+((tutorial_bot(\d+)?)|(TU))$/; const blitzModuleTypeToBlitzkit: Record = { chassis: ModuleType.MODULE_TYPE_TRACKS, engine: ModuleType.MODULE_TYPE_ENGINE, @@ -432,155 +401,6 @@ function assignArmor( } export async function definitions() { - const tankStringIdMap: Record = {}; - const tankXps = new Map(); - - const camouflagesXmlEntries = Object.entries(camouflagesXml.root.camouflages); - - const gameModeNativeNames: Record = {}; - - const consumableNativeNames: Record = {}; - const provisionNativeNames: Record = {}; - let equalizerDefinitions: string[][]; - - if ( - await vfs.resolve("Data/XML/item_defs/vehicles/common/tier_equializer.csv") - ) { - equalizerDefinitions = await vfs.csv( - `Data/XML/item_defs/vehicles/common/tier_equializer.csv`, - { delimiter: ";" }, - ); - } else { - const stubEqualizerDefinitions = await readFile("stub/equalizer.csv").then( - (buffer) => buffer.toString(), - ); - - equalizerDefinitions = await vfs._csv(stubEqualizerDefinitions, { - delimiter: ";", - }); - } - - for (const match of squadBattleTypeStyles.Prototypes[0].components.UIDataLocalBindingsComponent.data[1][2].matchAll( - /"(\d+)" -> "(battleType\/([a-zA-Z]+))"/g, - )) { - const id = Number(match[1]); - - gameModeNativeNames[match[3]] = id; - } - - for (const match of gameTypeSelectorStyles.Prototypes[0].components.UIDataLocalBindingsComponent.data[1][2].matchAll( - /eGameMode\.([a-zA-Z]+) -> "~res:\/Gfx\/UI\/Hangar\/GameTypes\/battle-type_([^"]+)"/g, - )) { - Object.entries(gameModeNativeNames).forEach(([key, value]) => { - if (key.toLowerCase() === match[2].toLowerCase()) { - gameModeNativeNames[match[1]] = value; - } - }); - } - - camouflagesXmlEntries.forEach(([camoKey, camo]) => { - const yamlEntry = camouflagesYaml[camoKey]; - const fullName = yamlEntry.userString - ? getString(yamlEntry.userString) - : undefined; - const shortName = yamlEntry.shortUserString - ? getString(yamlEntry.shortUserString) - : undefined; - const resolvedTankName = shortName ?? fullName; - const resolvedTankNameFull = - resolvedTankName === fullName ? undefined : fullName; - - camouflageDefinitions.camouflages[camo.id] = { - id: camo.id, - name: getString(camo.userString), - tank_name: resolvedTankName, - tank_name_full: resolvedTankNameFull, - }; - }); - - const slugRequesters = new Map(); - const idToNation: Record = {}; - - for (const nation of nationsDir) { - const tankList = await vfs.xml<{ root: VehicleDefinitionList }>( - `Data/XML/item_defs/vehicles/${nation}/list.xml`, - ); - - for (const tankKey in tankList.root) { - if (botPattern.test(tankKey)) continue; - - const tank = tankList.root[tankKey]; - const tankId = toUniqueId(nation, tank.id); - - const name = ( - (tank.shortUserString ? getString(tank.shortUserString) : undefined) ?? - getString(tank.userString) - ).locales.en; - - let slug = sluggify(name); - - idToNation[tankId] = nation; - - if (slugRequesters.has(slug)) { - slugRequesters.get(slug)!.push({ id: tankId, key: tankKey }); - } else { - slugRequesters.set(slug, [{ id: tankId, key: tankKey }]); - } - } - } - - const slugs = new Map(); - - slugRequesters.forEach((requesters, slug) => { - if (requesters.length === 1) { - slugs.set(requesters[0].id, slug); - return; - } - - console.warn( - `Multiple tanks share slug ${slug}: ${requesters - .map(({ key }) => key) - .join(", ")}`, - ); - - if (requesters.length !== 2) { - throw new Error("Unresolvable number of duplicates :("); - } - - const nonCanonical = requesters.find(({ key }) => key.endsWith("TUR")); - - if (nonCanonical === undefined) { - console.log("Using nations to discriminate"); - // both are non-tutorial tanks, will have to discriminate using nation - - requesters.forEach(({ id, key }) => { - const nation = idToNation[id]; - const discriminator = - nationSlugDiscriminators[ - nation as keyof typeof nationSlugDiscriminators - ]; - - console.log(`Solution: ${key} -> ${slug}-${discriminator}`); - slugs.set(id, `${slug}-${discriminator}`); - }); - } else { - console.log("Using tutorial bot suffix to discriminate"); - - const canonical = requesters.find(({ key }) => !key.endsWith("TUR")); - - if (canonical === undefined) { - throw new Error( - "Two tutorial bots share the same slug? The world is truly broken.", - ); - } - - console.log(`Solution: ${canonical.key} -> ${slug}`); - slugs.set(canonical.id, slug); - console.log(`Solution: ${nonCanonical.key} -> ${slug}-tur`); - slugs.set(nonCanonical.id, `${slug}-tur`); - } - }); - for (const nation of nationsDir) { const tankList = await vfs.xml<{ root: VehicleDefinitionList }>( `Data/XML/item_defs/vehicles/${nation}/list.xml`, @@ -742,7 +562,7 @@ export async function definitions() { } }); - const camouflages = camouflagesXmlEntries + const camouflages = Object.entries(camouflagesXml.root.camouflages) .filter(([, camo]) => { if (!camo.vehicleFilter.include) return false; if (camo.unlockCostCategory !== "legendary-skins-gold") return false; @@ -761,7 +581,7 @@ export async function definitions() { }) .map(([, camo]) => camo.id); - let equalizerEntry = equalizerDefinitions.find( + let equalizerEntry = tierEqualizer.find( (line) => line[0] === `${nation}:${tankKey}`, ); @@ -1317,8 +1137,6 @@ export async function definitions() { ); Object.entries(consumablesCommon).forEach(([key, consumable]) => { - consumableNativeNames[key] = consumable.id; - const entry: Consumable = { id: consumable.id, game_mode_exclusive: "gameModeFilter" in consumable, @@ -1417,8 +1235,6 @@ export async function definitions() { }); Object.entries(provisionsCommon).forEach(([key, provision]) => { - provisionNativeNames[key] = provision.id; - const entry: Provision = { id: provision.id, exclude: [], From b0611d7d72915fd31536840bfbe24cbbfab79f42 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sat, 1 Aug 2026 13:44:44 -0500 Subject: [PATCH 015/147] (1) some implementation --- packages/core/src/blitzkit/api/server.ts | 1074 +++++++++++++++++ packages/core/src/types/blitzModuleType.ts | 10 + .../core/src/types/chassisDefinitionsList.ts | 4 + .../core/src/types/engineDefinitionsList.ts | 13 + packages/core/src/types/gunDefinitionsList.ts | 31 + packages/core/src/types/index.ts | 9 + .../core/src/types/shellDefinitionsList.ts | 22 + packages/core/src/types/tankParameters.ts | 33 + .../core/src/types/turretDefinitionsList.ts | 4 + .../core/src/types/vehicleDefinitionArmor.ts | 4 + packages/core/src/types/vehicleDefinitions.ts | 152 +++ .../scripts/src/buildAssets/definitions.ts | 976 +-------------- packages/scripts/src/buildAssets/tankIcons.ts | 33 - 13 files changed, 1359 insertions(+), 1006 deletions(-) create mode 100644 packages/core/src/types/blitzModuleType.ts create mode 100644 packages/core/src/types/chassisDefinitionsList.ts create mode 100644 packages/core/src/types/engineDefinitionsList.ts create mode 100644 packages/core/src/types/gunDefinitionsList.ts create mode 100644 packages/core/src/types/shellDefinitionsList.ts create mode 100644 packages/core/src/types/tankParameters.ts create mode 100644 packages/core/src/types/turretDefinitionsList.ts create mode 100644 packages/core/src/types/vehicleDefinitionArmor.ts create mode 100644 packages/core/src/types/vehicleDefinitions.ts diff --git a/packages/core/src/blitzkit/api/server.ts b/packages/core/src/blitzkit/api/server.ts index d0ea56e14..5162f345e 100644 --- a/packages/core/src/blitzkit/api/server.ts +++ b/packages/core/src/blitzkit/api/server.ts @@ -1,29 +1,47 @@ import { AbstractVFS, + Armor, + BlitzCrewType, + BlitzModuleType, BlitzStrings, CamouflageDefinitions, CamouflagesXml, CamouflagesYaml, + ChassisDefinitionsList, CombatRolesYaml, ConsumableDefinitions, ConsumablesCommon, + Crew, + CrewType, + EngineDefinitionsList, EquipmentDefinitions, + GunDefinitionsList, I18nString, MapDefinitions, MapsYaml, ModelDefinitions, + ModuleType, OptionalDevices, OptionalDeviceSlots, ProvisionDefinitions, ProvisionsCommon, ResearchCost, + ShellDefinitionsList, SkillDefinitions, sluggify, SquadBattleTypeStylesYaml, TankDefinitions, TankmenAvatar, + TankParameters, + TankPrice, + TankPriceType, toUniqueId, + TurretDefinitionsList, + Unlock, + UnlocksListing, + VehicleDefinitionArmor, VehicleDefinitionList, + VehicleDefinitions, } from "@blitzkit/core"; import { SUPPORTED_LOCALE_BLITZ_MAP } from "@blitzkit/i18n"; import locales from "@blitzkit/i18n/locales.json"; @@ -44,6 +62,28 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { usa: "us", ussr: "ru", }; + private blitzModuleTypeToBlitzkit: Record = + { + chassis: ModuleType.MODULE_TYPE_TRACKS, + engine: ModuleType.MODULE_TYPE_ENGINE, + gun: ModuleType.MODULE_TYPE_GUN, + turret: ModuleType.MODULE_TYPE_TURRET, + vehicle: ModuleType.MODULE_TYPE_VEHICLE, + }; + private blitzkitCrewTypeToBlitz: Record = { + [CrewType.CREW_TYPE_COMMANDER]: "commander", + [CrewType.CREW_TYPE_DRIVER]: "driver", + [CrewType.CREW_TYPE_GUNNER]: "gunner", + [CrewType.CREW_TYPE_LOADER]: "loader", + [CrewType.CREW_TYPE_RADIOMAN]: "radioman", + }; + private blitzCrewTypeToBlitzkit: Record = { + commander: CrewType.CREW_TYPE_COMMANDER, + driver: CrewType.CREW_TYPE_DRIVER, + gunner: CrewType.CREW_TYPE_GUNNER, + loader: CrewType.CREW_TYPE_LOADER, + radioman: CrewType.CREW_TYPE_RADIOMAN, + }; private stringsI18n: Record> = {}; private nationsDir?: string[]; @@ -179,6 +219,22 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { return { locales: collection } satisfies I18nString; } + private assignArmor( + raw: VehicleDefinitionArmor[string], + id: number, + armor: Armor, + ) { + if (typeof raw === "number") { + armor.thickness[id] = raw; + } else if (Array.isArray(raw)) { + armor.thickness[id] = raw.at(-1)!; + } else { + if (!armor.spaced) armor.spaced = []; + armor.thickness[id] = raw["#text"]; + armor.spaced.push(id); + } + } + async tankDefinitions() { const tankDefinitions = TankDefinitions.create(); @@ -292,6 +348,598 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { const tankXps = new Map(); + for (const nation of this.nationsDir!) { + const tankList = await this.vfs.xml<{ root: VehicleDefinitionList }>( + `Data/XML/item_defs/vehicles/${nation}/list.xml`, + ); + const turretList = await this.vfs.xml<{ + root: TurretDefinitionsList; + }>(`Data/XML/item_defs/vehicles/${nation}/components/turrets.xml`); + const gunList = await this.vfs.xml<{ + root: GunDefinitionsList; + }>(`Data/XML/item_defs/vehicles/${nation}/components/guns.xml`); + const shellList = await this.vfs.xml<{ + root: ShellDefinitionsList; + }>(`Data/XML/item_defs/vehicles/${nation}/components/shells.xml`); + const enginesList = await this.vfs.xml<{ + root: EngineDefinitionsList; + }>(`Data/XML/item_defs/vehicles/${nation}/components/engines.xml`); + const chassisList = await this.vfs.xml<{ + root: ChassisDefinitionsList; + }>(`Data/XML/item_defs/vehicles/${nation}/components/chassis.xml`); + + function resolveUnlocks( + blitzModuleTypeToBlitzkit: Record, + unlocks?: BlitzModuleType, + ) { + if (!unlocks) return []; + + return Object.entries(unlocks) + .map(([type, list]) => + (Array.isArray(list) ? list : [list]).map((item) => { + const typeTyped = type as keyof BlitzModuleType; + let rawId: number; + + if (typeTyped === "chassis") { + rawId = chassisList.root.ids[item["#text"]]; + } else if (typeTyped === "engine") { + rawId = enginesList.root.ids[item["#text"]]; + } else if (typeTyped === "gun") { + rawId = gunList.root.ids[item["#text"]]; + } else if (typeTyped === "turret") { + rawId = turretList.root.ids[item["#text"]]; + } else if (typeTyped === "vehicle") { + rawId = tankList.root[item["#text"]].id; + } + + return { + id: toUniqueId(nation, rawId!), + type: blitzModuleTypeToBlitzkit[typeTyped], + cost: { + type: + typeof item.cost === "number" + ? "xp" + : item.cost.split(":")[0], + value: + typeof item.cost === "number" + ? item.cost + : Number(item.cost.split(":")[1]), + }, + } satisfies Unlock; + }), + ) + .flat(); + } + + for (const tankKey in tankList.root) { + if (botPattern.test(tankKey)) continue; + + const gunXps = new Map(); + const turretXps = new Map(); + const engineXps = new Map(); + const trackXps = new Map(); + const tank = tankList.root[tankKey]; + let tankPrice: TankPrice; + const tankDefinition = await this.vfs.xml<{ root: VehicleDefinitions }>( + `Data/XML/item_defs/vehicles/${nation}/${tankKey}.xml`, + ); + const tankParameters = await this.vfs.yaml( + `Data/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`, + ); + const tankId = toUniqueId(nation, tank.id); + + const tankTags = tank.tags.split(" "); + const hullArmor: Armor = { spaced: [], thickness: {} }; + const equipment = tankDefinition.root.optDevicePreset; + this.tankStringIdMap[`${nation}:${tankKey}`] = tankId; + const slug = slugs.get(tankId); + + if (slug === undefined) { + throw new Error( + `Could not find slug for ${nation}/${tankKey} (${tankId})`, + ); + } + + if (tank.sellPrice) { + tankPrice = { + type: TankPriceType.TANK_PRICE_TYPE_GOLD, + value: tank.sellPrice["#text"] * 2, + }; + } else if (typeof tank.price === "number") { + tankPrice = { + type: TankPriceType.TANK_PRICE_TYPE_CREDITS, + value: tank.price, + }; + } else { + tankPrice = { + type: TankPriceType.TANK_PRICE_TYPE_CREDITS, + value: tank.price["#text"] * 400, + }; + } + + Object.keys(tankDefinition.root.hull.armor) + .filter((name) => name.startsWith("armor_")) + .forEach((name) => { + const armorIdString = name.match(/armor_(\d+)/)?.[1]; + + if (armorIdString === undefined) { + throw new SyntaxError(`Invalid armor id: ${name}`); + } + + const armorId = parseInt(armorIdString); + const armorRaw = tankDefinition.root.hull.armor[name]; + + this.assignArmor(armorRaw, armorId, hullArmor); + }); + const crew: Crew[] = []; + const fixedCamouflage = tankTags.includes("eventCamouflage_user"); + const totalUnlocks: UnlocksListing[] = []; + + for (const crewKey in tankDefinition.root.crew) { + const value = tankDefinition.root.crew[crewKey as BlitzCrewType]; + + { + let entry: Crew; + const index = crew.findIndex( + ({ type }) => this.blitzkitCrewTypeToBlitz[type] === crewKey, + ); + if (index === -1) { + if (crewKey === "#text") return; + entry = { + type: this.blitzCrewTypeToBlitzkit[crewKey as BlitzCrewType], + count: 0, + substitute: [], + }; + crew.push(entry); + } else { + entry = crew[index]; + } + + if (typeof value === "string") { + entry.count++; + + if (value !== "") { + entry.substitute = value.split(/\n| /).map((member) => { + return this.blitzCrewTypeToBlitzkit[ + member.trim() as BlitzCrewType + ]; + }); + } + } else { + if (entry.count === undefined) { + entry.count = value.length; + } else { + entry.count += value.length; + } + } + } + } + + const camouflages = Object.entries(camouflagesXml.root.camouflages) + .filter(([, camo]) => { + if (!camo.vehicleFilter.include) return false; + if (camo.unlockCostCategory !== "legendary-skins-gold") + return false; + + const includeArray = Array.isArray(camo.vehicleFilter.include) + ? camo.vehicleFilter.include + : [camo.vehicleFilter.include]; + + return includeArray.some((filter) => { + if ("vehicle" in filter && filter.vehicle?.name) { + return filter.vehicle.name === `${nation}:${tankKey}`; + } + + return false; + }); + }) + .map(([, camo]) => camo.id); + + let equalizerEntry = tierEqualizer.find( + (line) => line[0] === `${nation}:${tankKey}`, + ); + + let equalizer: Equalizer | undefined; + + if (equalizerEntry) { + const [health, penetration, module_health, damage, armor] = + equalizerEntry?.slice(1).map(Number); + equalizer = { health, penetration, module_health, damage, armor }; + } + + tankDefinitions.tanks[tankId] = { + ancestors: [], + successors: [], + id: tankId, + dev_name: tankKey, + roles: {}, + camouflages: camouflages, + fixed_camouflage: fixedCamouflage, + crew, + weight: tankDefinition.root.hull.weight, + health: tankDefinition.root.hull.maxHealth, + speed_forwards: tankDefinition.root.speedLimits.forward, + speed_backwards: tankDefinition.root.speedLimits.backward, + equipment_preset: + typeof equipment === "string" ? equipment : equipment.at(-1)!, + max_consumables: tankDefinition.root.consumableSlots, + max_provisions: tankDefinition.root.provisionSlots, + name: + (tank.shortUserString + ? getString(tank.shortUserString) + : undefined) ?? getString(tank.userString), + slug, + nation, + type: tankTags.includes("collectible") + ? TankType.TANK_TYPE_COLLECTOR + : (typeof tank.price === "number" ? false : "gold" in tank.price) + ? TankType.TANK_TYPE_PREMIUM + : TankType.TANK_TYPE_RESEARCHABLE, + tier: tank.level, + class: blitzTankClassToBlitzkit[tankTags[0] as BlitzTankClass], + testing: tankTags.includes("testTank"), + deprecated: tankTags.includes("deprecated"), + price: tankPrice, + camouflage_still: tankDefinition.root.invisibility.still, + camouflage_moving: tankDefinition.root.invisibility.moving, + camouflage_on_fire: tankDefinition.root.invisibility.firePenalty, + turrets: [], + engines: [], + tracks: [], + equalizer, + }; + + if (tank.combatRole) { + Object.entries(tank.combatRole).forEach(([gameMode, role]) => { + const id = Object.entries(gameModeNativeNames).find( + ([key]) => key.toLowerCase() === gameMode.toLowerCase(), + )?.[1]; + + if (id === undefined) { + throw new Error( + `Unknown game mode in tank ${tankKey}: ${gameMode}`, + ); + } + + tankDefinitions.tanks[tankId].roles[id] = combatRoles[role].id; + }); + } + + Object.keys(tankDefinition.root.chassis).forEach((key) => { + const track = tankDefinition.root.chassis[key]; + const trackId = toUniqueId(nation, chassisList.root.ids[key]); + const terrainResistances = track.terrainResistance + .split(" ") + .map(Number); + + totalUnlocks.push(track.unlocks); + tankDefinitions.tanks[tankId].tracks.push({ + id: trackId, + weight: track.weight, + name: getString(track.userString), + traverse_speed: track.rotationSpeed, + dispersion_move: track.shotDispersionFactors.vehicleMovement, + dispersion_traverse: track.shotDispersionFactors.vehicleRotation, + resistance_hard: terrainResistances[0], + resistance_medium: terrainResistances[1], + resistance_soft: terrainResistances[2], + tier: track.level, + unlocks: resolveUnlocks(track.unlocks), + }); + }); + + Object.keys(tankDefinition.root.engines).forEach((engineKey) => { + const engine = tankDefinition.root.engines[engineKey]; + const engineListEntry = enginesList.root.shared[engineKey]; + const engineId = toUniqueId(nation, enginesList.root.ids[engineKey]); + + totalUnlocks.push(engine.unlocks); + tankDefinitions.tanks[tankId].engines.push({ + id: engineId, + name: getString(engineListEntry.userString), + fire_chance: engineListEntry.fireStartingChance, + tier: engineListEntry.level, + weight: engineListEntry.weight, + power: engineListEntry.power, + unlocks: resolveUnlocks(engine.unlocks), + }); + }); + + Object.keys(tankDefinition.root.turrets0).forEach( + (turretKey, turretIndex) => { + const turret = tankDefinition.root.turrets0[turretKey]; + const turretId = toUniqueId(nation, turretList.root.ids[turretKey]); + + const turretArmor: Armor = { thickness: {}, spaced: [] }; + + totalUnlocks.push(turret.unlocks); + Object.keys(turret.armor) + .filter((name) => name.startsWith("armor_")) + .forEach((name) => { + const armorIdString = name.match(/armor_(\d+)/)?.[1]; + + if (armorIdString === undefined) { + throw new SyntaxError(`Invalid armor id: ${name}`); + } + + const armorId = parseInt(armorIdString); + const armorRaw = turret.armor[name]; + + assignArmor(armorRaw, armorId, turretArmor); + }); + + turret.userString; + + tankDefinitions.tanks[tankId].turrets.push({ + id: turretId, + traverse_speed: turret.rotationSpeed, + name: getString(turret.userString), + tier: turret.level, + guns: [], + health: turret.maxHealth, + view_range: turret.circularVisionRadius, + weight: turret.weight, + unlocks: resolveUnlocks(turret.unlocks), + }); + + Object.keys(turret.guns).forEach((gunKey, gunIndex) => { + const gun = turret.guns[gunKey]; + const gunId = toUniqueId(nation, gunList.root.ids[gunKey]); + const gunListEntry = gunList.root.shared[gunKey]; + const pitchLimitsRaw = + gun.pitchLimits ?? gunListEntry.pitchLimits; + const gunPitch = ( + typeof pitchLimitsRaw === "string" + ? pitchLimitsRaw + : pitchLimitsRaw.at(-1)! + ) + .split(" ") + .map(Number) as [number, number]; + const gunModel = Number( + parsePath(gun.models.undamaged).name.split("_")[1], + ); + const gunName = getString(gunListEntry.userString); + const gunType = + "clip" in gun + ? gun.pumpGunMode + ? "autoReloader" + : "autoLoader" + : "regular"; + // const gunReload = gun.reloadTime; + // const shellReloads = + const gunClipCount = gunType === "regular" ? 1 : gun.clip!.count; + const gunArmor: Armor = { thickness: {}, spaced: [] }; + const shotDispersionFactors = + gun.shotDispersionFactors ?? gunListEntry.shotDispersionFactors; + const maskName = `gun_${gunModel.toString().padStart(2, "0")}`; + const maskEnabled = + tankParameters.maskSlice?.[maskName]?.enabled ?? false; + let assault_ranges: AssaultRanges | undefined; + + if (gun.extras?.trayShell) { + const types = gun.extras.trayShell.kinds + .split(" ") + .map((string) => { + const trimmed = string.trim(); + + if (trimmed in blitzShellKindToBlitzkit) { + return blitzShellKindToBlitzkit[trimmed as ShellKind]; + } + + throw new SyntaxError(`Invalid shell kind: ${trimmed}`); + }); + const sectorNames = Object.keys(gun.extras.trayShell.sectors); + + if (sectorNames.length !== 1 || sectorNames[0] !== "sector") { + throw new SyntaxError("Invalid tray shell sector"); + } + + const sector = gun.extras.trayShell.sectors.sector; + + assault_ranges = { + types, + ranges: sector.map((value) => ({ + factor: value.factor, + distance: value.distance, + })), + }; + } + + if (maskEnabled) { + const maskRaw = tankParameters.maskSlice![maskName]!; + mask = maskRaw.planePosition[1]; + } else { + mask = undefined; + } + + totalUnlocks.push(gun.unlocks); + Object.keys(gun.armor) + .filter((name) => name.startsWith("armor_")) + .forEach((name) => { + const armorIdString = name.match(/armor_(\d+)/)?.[1]; + if (armorIdString === undefined) { + throw new SyntaxError(`Invalid armor id: ${name}`); + } + const armorId = parseInt(armorIdString); + const armorRaw = gun.armor[name]; + + assignArmor(armorRaw, armorId, gunArmor); + }); + + tankDefinitions.tanks[tankId].turrets[turretIndex].guns.push({ + id: gunId, + weight: gunListEntry.weight, + rotation_speed: gunListEntry.rotationSpeed, + name: gunName, + tier: gunListEntry.level, + shells: [], + camouflage_loss: + typeof gun.invisibilityFactorAtShot === "number" + ? gun.invisibilityFactorAtShot + : gun.invisibilityFactorAtShot.at(-1)!, + aim_time: gun.aimingTime ?? gunListEntry.aimingTime, + dispersion_base: + gun.shotDispersionRadius ?? gunListEntry.shotDispersionRadius, + dispersion_damaged: shotDispersionFactors.whileGunDamaged, + dispersion_shot: shotDispersionFactors.afterShot, + dispersion_traverse: shotDispersionFactors.turretRotation, + unlocks: resolveUnlocks(gun.unlocks), + shell_capacity: gun.maxAmmo ?? gunListEntry.maxAmmo, + assault_ranges, + burst: + gun.burst && gun.burst.count > 1 + ? { + count: gun.burst.count, + interval: 60 / gun.burst.rate, + } + : undefined, + gun_type: + gunType === "regular" + ? { + $case: "regular", + value: { + reload: gun.reloadTime, + }, + } + : gunType === "autoReloader" + ? { + $case: "auto_reloader", + value: { + intra_clip: 60 / gun.clip!.rate, + shell_count: gunClipCount, + shell_reloads: gun + .pumpGunReloadTimes!.split(" ") + .map(Number), + }, + } + : { + $case: "auto_loader", + value: { + intra_clip: 60 / gun.clip!.rate, + clip_reload: gun.reloadTime, + shell_count: gunClipCount, + }, + }, + } satisfies GunDefinition); + + Object.keys(gunListEntry.shots).forEach((shellKey) => { + const gunShellEntry = gunListEntry.shots[shellKey]; + const shell = shellList.root[shellKey]; + const shellId = toUniqueId(nation, shell.id); + const shellName = getString(shell.userString); + const penetrationRaw = gunShellEntry.piercingPower + .split(" ") + .filter((penetrationString) => penetrationString !== "") + .map(Number); + + tankDefinitions.tanks[tankId].turrets[turretIndex].guns[ + gunIndex + ].shells.push({ + id: shellId, + name: shellName, + velocity: gunShellEntry.speed, + armor_damage: shell.damage.armor, + module_damage: shell.damage.devices, + caliber: shell.caliber, + normalization: shell.normalizationAngle, + ricochet: shell.ricochetAngle, + type: blitzShellKindToBlitzkit[shell.kind], + explosion_radius: + shell.kind === "HIGH_EXPLOSIVE" + ? (shell.explosionRadius ?? 0) + : undefined, + icon: shell.icon, + penetration: { + near: penetrationRaw[0], + far: penetrationRaw[1], + }, + range: gunShellEntry.maxDistance, + }); + }); + }); + }, + ); + + totalUnlocks.forEach((unlocks) => { + if (unlocks === undefined) return; + + Object.entries(unlocks).forEach(([key, value]) => { + (Array.isArray(value) ? value : [value]).forEach((vehicle) => { + switch (key as keyof BlitzModuleType) { + case "vehicle": { + const tankListEntry = tankList.root[vehicle["#text"]]; + const currentTank = tankDefinitions.tanks[tankId]; + const successorId = toUniqueId(nation, tankListEntry.id); + + tankXps.set(successorId, parseResearchCost(vehicle.cost)); + + if (currentTank.successors === undefined) { + currentTank.successors = []; + } + if (!currentTank.successors!.includes(successorId)) { + currentTank.successors!.push(successorId); + } + break; + } + + case "gun": { + gunXps.set( + toUniqueId(nation, gunList.root.ids[vehicle["#text"]]), + parseResearchCost(vehicle.cost), + ); + break; + } + + case "turret": { + turretXps.set( + toUniqueId(nation, turretList.root.ids[vehicle["#text"]]), + parseResearchCost(vehicle.cost), + ); + break; + } + + case "engine": { + engineXps.set( + toUniqueId(nation, enginesList.root.ids[vehicle["#text"]]), + parseResearchCost(vehicle.cost), + ); + break; + } + + case "chassis": { + trackXps.set( + toUniqueId(nation, chassisList.root.ids[vehicle["#text"]]), + parseResearchCost(vehicle.cost), + ); + break; + } + } + }); + }); + }); + + Object.values(tankDefinitions.tanks[tankId].turrets).forEach( + (turret) => { + turret.research_cost = turretXps.get(turret.id); + + Object.values(turret.guns).forEach((gunRaw) => { + gunRaw.research_cost = gunXps.get(gunRaw.id); + }); + }, + ); + + Object.values(tankDefinitions.tanks[tankId].engines).forEach( + (engine) => { + engine.research_cost = engineXps.get(engine.id); + }, + ); + + Object.values(tankDefinitions.tanks[tankId].tracks).forEach((track) => { + track.research_cost = trackXps.get(track.id); + }); + } + } + return tankDefinitions; } @@ -326,6 +974,432 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { async modelDefinitions() { const modelDefinitions = ModelDefinitions.create(); + for (const nation of nationsDir) { + const tankList = await vfs.xml<{ root: VehicleDefinitionList }>( + `Data/XML/item_defs/vehicles/${nation}/list.xml`, + ); + const turretList = await vfs.xml<{ + root: TurretDefinitionsList; + }>(`Data/XML/item_defs/vehicles/${nation}/components/turrets.xml`); + const gunList = await vfs.xml<{ + root: GunDefinitionsList; + }>(`Data/XML/item_defs/vehicles/${nation}/components/guns.xml`); + const shellList = await vfs.xml<{ + root: ShellDefinitionsList; + }>(`Data/XML/item_defs/vehicles/${nation}/components/shells.xml`); + const enginesList = await vfs.xml<{ + root: EngineDefinitionsList; + }>(`Data/XML/item_defs/vehicles/${nation}/components/engines.xml`); + const chassisList = await vfs.xml<{ + root: ChassisDefinitionsList; + }>(`Data/XML/item_defs/vehicles/${nation}/components/chassis.xml`); + + for (const tankKey in tankList.root) { + if (botPattern.test(tankKey)) continue; + + const gunXps = new Map(); + const turretXps = new Map(); + const engineXps = new Map(); + const trackXps = new Map(); + const tank = tankList.root[tankKey]; + const tankDefinition = await vfs.xml<{ root: VehicleDefinitions }>( + `Data/XML/item_defs/vehicles/${nation}/${tankKey}.xml`, + ); + const tankParameters = await vfs.yaml( + `Data/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`, + ); + const turretOrigin = tankDefinition.root.hull.turretPositions.turret + .split(" ") + .map(Number) as Vector3Tuple; + const tankId = toUniqueId(nation, tank.id); + + const tankTags = tank.tags.split(" "); + const hullArmor: Armor = { spaced: [], thickness: {} }; + tankStringIdMap[`${nation}:${tankKey}`] = tankId; + const slug = slugs.get(tankId); + + if (slug === undefined) { + throw new Error( + `Could not find slug for ${nation}/${tankKey} (${tankId})`, + ); + } + + if (tank.sellPrice) { + tankPrice = { + type: TankPriceType.TANK_PRICE_TYPE_GOLD, + value: tank.sellPrice["#text"] * 2, + }; + } else if (typeof tank.price === "number") { + tankPrice = { + type: TankPriceType.TANK_PRICE_TYPE_CREDITS, + value: tank.price, + }; + } else { + tankPrice = { + type: TankPriceType.TANK_PRICE_TYPE_CREDITS, + value: tank.price["#text"] * 400, + }; + } + + Object.keys(tankDefinition.root.hull.armor) + .filter((name) => name.startsWith("armor_")) + .forEach((name) => { + const armorIdString = name.match(/armor_(\d+)/)?.[1]; + + if (armorIdString === undefined) { + throw new SyntaxError(`Invalid armor id: ${name}`); + } + + const armorId = parseInt(armorIdString); + const armorRaw = tankDefinition.root.hull.armor[name]; + + assignArmor(armorRaw, armorId, hullArmor); + }); + const crew: Crew[] = []; + const totalUnlocks: UnlocksListing[] = []; + + Object.entries(tankDefinition.root.crew).forEach(([crewKey, value]) => { + let entry: Crew; + const index = crew.findIndex( + ({ type }) => blitzkitCrewTypeToBlitz[type] === crewKey, + ); + if (index === -1) { + if (crewKey === "#text") return; + entry = { + type: blitzCrewTypeToBlitzkit[crewKey as BlitzCrewType], + count: 0, + substitute: [], + }; + crew.push(entry); + } else { + entry = crew[index]; + } + + if (typeof value === "string") { + entry.count++; + + if (value !== "") { + entry.substitute = value.split(/\n| /).map((member) => { + return blitzCrewTypeToBlitzkit[member.trim() as BlitzCrewType]; + }); + } + } else { + if (entry.count === undefined) { + entry.count = value.length; + } else { + entry.count += value.length; + } + } + }); + + let equalizerEntry = tierEqualizer.find( + (line) => line[0] === `${nation}:${tankKey}`, + ); + + if (equalizerEntry) { + const [health, penetration, module_health, damage, armor] = + equalizerEntry?.slice(1).map(Number); + equalizer = { health, penetration, module_health, damage, armor }; + } + + if (tank.combatRole) { + Object.entries(tank.combatRole).forEach(([gameMode, role]) => { + const id = Object.entries(gameModeNativeNames).find( + ([key]) => key.toLowerCase() === gameMode.toLowerCase(), + )?.[1]; + + if (id === undefined) { + throw new Error( + `Unknown game mode in tank ${tankKey}: ${gameMode}`, + ); + } + }); + } + + modelDefinitions.models[tankId] = { + armor: hullArmor, + turret_origin: vector3TupleToBlitzkit(turretOrigin), + initial_turret_rotation: tankDefinition.root.hull + .turretInitialRotation + ? { + yaw: tankDefinition.root.hull.turretInitialRotation.yaw, + pitch: tankDefinition.root.hull.turretInitialRotation.pitch, + roll: tankDefinition.root.hull.turretInitialRotation.roll, + } + : undefined, + bounding_box: { + min: vector3TupleToBlitzkit(tankParameters.collision.hull.bbox.min), + max: vector3TupleToBlitzkit(tankParameters.collision.hull.bbox.max), + }, + turrets: {}, + tracks: {}, + }; + + Object.keys(tankDefinition.root.chassis).forEach((key) => { + const track = tankDefinition.root.chassis[key]; + const trackId = toUniqueId(nation, chassisList.root.ids[key]); + const trackArmorRaw = track.armor.leftTrack; + const hullOrigin = track.hullPosition + .split(" ") + .map(Number) as Vector3Tuple; + + totalUnlocks.push(track.unlocks); + + modelDefinitions.models[tankId].tracks[trackId] = { + thickness: + typeof trackArmorRaw === "number" + ? trackArmorRaw + : trackArmorRaw["#text"], + origin: vector3TupleToBlitzkit(hullOrigin), + }; + }); + + Object.keys(tankDefinition.root.engines).forEach((engineKey) => { + const engine = tankDefinition.root.engines[engineKey]; + + totalUnlocks.push(engine.unlocks); + }); + + Object.keys(tankDefinition.root.turrets0).forEach((turretKey) => { + const turret = tankDefinition.root.turrets0[turretKey]; + const turretModel = Number( + parsePath(turret.models.undamaged).name.split("_")[1], + ); + const turretId = toUniqueId(nation, turretList.root.ids[turretKey]); + const turretYaw = ( + typeof turret.yawLimits === "string" + ? turret.yawLimits + : turret.yawLimits.at(-1)! + ) + .split(" ") + .map(Number) as [number, number]; + const gunOrigin = ( + typeof turret.gunPosition === "string" + ? turret.gunPosition + : turret.gunPosition[0] + ) + .split(" ") + .map(Number) as Vector3Tuple; + const turretArmor: Armor = { thickness: {}, spaced: [] }; + + totalUnlocks.push(turret.unlocks); + Object.keys(turret.armor) + .filter((name) => name.startsWith("armor_")) + .forEach((name) => { + const armorIdString = name.match(/armor_(\d+)/)?.[1]; + + if (armorIdString === undefined) { + throw new SyntaxError(`Invalid armor id: ${name}`); + } + + const armorId = parseInt(armorIdString); + const armorRaw = turret.armor[name]; + + assignArmor(armorRaw, armorId, turretArmor); + }); + + turret.userString; + + modelDefinitions.models[tankId].turrets[turretId] = { + armor: turretArmor, + gun_origin: vector3TupleToBlitzkit(gunOrigin), + model_id: turretModel, + yaw: + -turretYaw[0] + turretYaw[1] === 360 + ? undefined + : { min: turretYaw[0], max: turretYaw[1] }, + guns: {}, + bounding_box: { + min: vector3TupleToBlitzkit( + tankParameters.collision[ + parsePath(turret.hitTester.collisionModel).name.toLowerCase() + ].bbox.min, + ), + max: vector3TupleToBlitzkit( + tankParameters.collision[ + parsePath(turret.hitTester.collisionModel).name.toLowerCase() + ].bbox.max, + ), + }, + }; + + Object.keys(turret.guns).forEach((gunKey, gunIndex) => { + const gun = turret.guns[gunKey]; + const gunId = toUniqueId(nation, gunList.root.ids[gunKey]); + const gunListEntry = gunList.root.shared[gunKey]; + const pitchLimitsRaw = gun.pitchLimits ?? gunListEntry.pitchLimits; + const gunPitch = ( + typeof pitchLimitsRaw === "string" + ? pitchLimitsRaw + : pitchLimitsRaw.at(-1)! + ) + .split(" ") + .map(Number) as [number, number]; + const gunModel = Number( + parsePath(gun.models.undamaged).name.split("_")[1], + ); + // const gunReload = gun.reloadTime; + // const shellReloads = + const front = gun.extraPitchLimits?.front + ? gun.extraPitchLimits.front.split(" ").map(Number) + : undefined; + const back = gun.extraPitchLimits?.back + ? gun.extraPitchLimits.back.split(" ").map(Number) + : undefined; + const transition = gun.extraPitchLimits?.transition + ? typeof gun.extraPitchLimits.transition === "number" + ? gun.extraPitchLimits.transition + : gun.extraPitchLimits.transition.at(-1)! + : undefined; + const gunArmor: Armor = { thickness: {}, spaced: [] }; + const maskName = `gun_${gunModel.toString().padStart(2, "0")}`; + const maskEnabled = + tankParameters.maskSlice?.[maskName]?.enabled ?? false; + let mask: number | undefined; + + if (gun.extras?.trayShell) { + const types = gun.extras.trayShell.kinds + .split(" ") + .map((string) => { + const trimmed = string.trim(); + + if (trimmed in blitzShellKindToBlitzkit) { + return blitzShellKindToBlitzkit[trimmed as ShellKind]; + } + + throw new SyntaxError(`Invalid shell kind: ${trimmed}`); + }); + const sectorNames = Object.keys(gun.extras.trayShell.sectors); + + if (sectorNames.length !== 1 || sectorNames[0] !== "sector") { + throw new SyntaxError("Invalid tray shell sector"); + } + + const sector = gun.extras.trayShell.sectors.sector; + + assault_ranges = { + types, + ranges: sector.map((value) => ({ + factor: value.factor, + distance: value.distance, + })), + }; + } + + if (maskEnabled) { + const maskRaw = tankParameters.maskSlice![maskName]!; + mask = maskRaw.planePosition[1]; + } else { + mask = undefined; + } + + totalUnlocks.push(gun.unlocks); + Object.keys(gun.armor) + .filter((name) => name.startsWith("armor_")) + .forEach((name) => { + const armorIdString = name.match(/armor_(\d+)/)?.[1]; + if (armorIdString === undefined) { + throw new SyntaxError(`Invalid armor id: ${name}`); + } + const armorId = parseInt(armorIdString); + const armorRaw = gun.armor[name]; + + assignArmor(armorRaw, armorId, gunArmor); + }); + + modelDefinitions.models[tankId].turrets[turretId].guns[gunId] = { + armor: gunArmor, + model_id: gunModel, + mask, + thickness: + gun.armor.gun === undefined + ? 0 + : typeof gun.armor.gun === "number" + ? gun.armor.gun + : gun.armor.gun["#text"], + pitch: { + min: gunPitch[0], + max: gunPitch[1], + + front: front + ? { + min: front[0], + max: front[1], + range: front[2], + } + : undefined, + back: back + ? { + min: back[0], + max: back[1], + range: back[2], + } + : undefined, + transition, + }, + }; + }); + }); + + totalUnlocks.forEach((unlocks) => { + if (unlocks === undefined) return; + + Object.entries(unlocks).forEach(([key, value]) => { + (Array.isArray(value) ? value : [value]).forEach((vehicle) => { + switch (key as keyof BlitzModuleType) { + case "vehicle": { + const tankListEntry = tankList.root[vehicle["#text"]]; + const successorId = toUniqueId(nation, tankListEntry.id); + + tankXps.set(successorId, parseResearchCost(vehicle.cost)); + + if (currentTank.successors === undefined) { + currentTank.successors = []; + } + if (!currentTank.successors!.includes(successorId)) { + currentTank.successors!.push(successorId); + } + break; + } + + case "gun": { + gunXps.set( + toUniqueId(nation, gunList.root.ids[vehicle["#text"]]), + parseResearchCost(vehicle.cost), + ); + break; + } + + case "turret": { + turretXps.set( + toUniqueId(nation, turretList.root.ids[vehicle["#text"]]), + parseResearchCost(vehicle.cost), + ); + break; + } + + case "engine": { + engineXps.set( + toUniqueId(nation, enginesList.root.ids[vehicle["#text"]]), + parseResearchCost(vehicle.cost), + ); + break; + } + + case "chassis": { + trackXps.set( + toUniqueId(nation, chassisList.root.ids[vehicle["#text"]]), + parseResearchCost(vehicle.cost), + ); + break; + } + } + }); + }); + }); + } + } + return modelDefinitions; } diff --git a/packages/core/src/types/blitzModuleType.ts b/packages/core/src/types/blitzModuleType.ts new file mode 100644 index 000000000..3a8ac4774 --- /dev/null +++ b/packages/core/src/types/blitzModuleType.ts @@ -0,0 +1,10 @@ +export type BlitzModuleType = { + [key in "vehicle" | "engine" | "chassis" | "turret" | "gun"]: + | UnlocksInner + | UnlocksInner[]; +}; + +interface UnlocksInner { + cost: number | string; + "#text": number; +} diff --git a/packages/core/src/types/chassisDefinitionsList.ts b/packages/core/src/types/chassisDefinitionsList.ts new file mode 100644 index 000000000..b41ba53ea --- /dev/null +++ b/packages/core/src/types/chassisDefinitionsList.ts @@ -0,0 +1,4 @@ +export interface ChassisDefinitionsList { + nextAvailableId: number; + ids: Record; +} diff --git a/packages/core/src/types/engineDefinitionsList.ts b/packages/core/src/types/engineDefinitionsList.ts new file mode 100644 index 000000000..8707ca1b1 --- /dev/null +++ b/packages/core/src/types/engineDefinitionsList.ts @@ -0,0 +1,13 @@ +export interface EngineDefinitionsList { + nextAvailableId: number; + ids: Record; + shared: { + [key: string]: { + userString: string; + level: number; + fireStartingChance: number; + weight: number; + power: number; + }; + }; +} diff --git a/packages/core/src/types/gunDefinitionsList.ts b/packages/core/src/types/gunDefinitionsList.ts new file mode 100644 index 000000000..005238d10 --- /dev/null +++ b/packages/core/src/types/gunDefinitionsList.ts @@ -0,0 +1,31 @@ +export interface GunDefinitionsList { + nextAvailableId: number; + ids: Record; + shared: { + [key: string]: { + rotationSpeed: number; + weight: number; + shotDispersionFactors: { + turretRotation: number; + afterShot: number; + whileGunDamaged: number; + }; + maxAmmo: number; + aimingTime: number; + shotDispersionRadius: number; + userString: string; + tags: string; + level: number; + pitchLimits: string; + burst?: { count: number; rate: number }; + clip?: { count: number; rate: number }; + shots: { + [key: string]: { + speed: number; + piercingPower: string; + maxDistance: number; + }; + }; + }; + }; +} diff --git a/packages/core/src/types/index.ts b/packages/core/src/types/index.ts index 83bea7b00..22bdfb454 100644 --- a/packages/core/src/types/index.ts +++ b/packages/core/src/types/index.ts @@ -1,16 +1,25 @@ export * from "./availableNationsYaml"; +export * from "./blitzModuleType"; export * from "./blitzStrings"; export * from "./camouflagesXml"; export * from "./camouflagesYaml"; +export * from "./chassisDefinitionsList"; export * from "./combatRolesYaml"; export * from "./consumablesCommon"; export * from "./consumablesVehicleFilter"; +export * from "./engineDefinitionsList"; export * from "./glossary"; +export * from "./gunDefinitionsList"; export * from "./mapsYaml"; export * from "./optionalDevices"; export * from "./optionalDeviceSlots"; export * from "./provisionsCommon"; +export * from "./shellDefinitionsList"; export * from "./squadBattleTypeStylesYaml"; export * from "./tankmenAvatar"; +export * from "./tankParameters"; export * from "./tanksStats"; +export * from "./turretDefinitionsList"; +export * from "./vehicleDefinitionArmor"; export * from "./vehicleDefinitionList"; +export * from "./vehicleDefinitions"; diff --git a/packages/core/src/types/shellDefinitionsList.ts b/packages/core/src/types/shellDefinitionsList.ts new file mode 100644 index 000000000..be8681432 --- /dev/null +++ b/packages/core/src/types/shellDefinitionsList.ts @@ -0,0 +1,22 @@ +export type ShellDefinitionsList = Record< + string, + { + id: number; + userString: string; + icon: string; + kind: ShellKind; + caliber: number; + damage: { armor: number; devices: number }; + normalizationAngle?: number; + ricochetAngle?: number; + explosionRadius?: number; + } +> & { + icons: Record; +}; + +type ShellKind = + | "ARMOR_PIERCING" + | "ARMOR_PIERCING_CR" + | "HIGH_EXPLOSIVE" + | "HOLLOW_CHARGE"; diff --git a/packages/core/src/types/tankParameters.ts b/packages/core/src/types/tankParameters.ts new file mode 100644 index 000000000..3a7ac082e --- /dev/null +++ b/packages/core/src/types/tankParameters.ts @@ -0,0 +1,33 @@ +import { Vector3Tuple } from "three"; + +export interface TankParameters { + resourcesPath: { + smallIconPath: string; + bigIconPath: string; + blitzModelPath: string; + }; + collision: { + [key: string]: { + points: Vector3Tuple; + bbox: { + min: Vector3Tuple; + max: Vector3Tuple; + }; + averageThickness?: Record; + }; + }; + maskSlice?: { + [key: string]: + | { + enabled: boolean; + planePosition: Vector3Tuple; + planeNormal: Vector3Tuple; + planeAxis: Vector3Tuple; + frustumConeBaseRadius: number; + frustumConeAngleDegrees: number; + planeSliceMode: number; + frustumConeSliceMode: number; + } + | undefined; + }; +} diff --git a/packages/core/src/types/turretDefinitionsList.ts b/packages/core/src/types/turretDefinitionsList.ts new file mode 100644 index 000000000..3e2ead959 --- /dev/null +++ b/packages/core/src/types/turretDefinitionsList.ts @@ -0,0 +1,4 @@ +export interface TurretDefinitionsList { + nextAvailableId: number; + ids: Record; +} diff --git a/packages/core/src/types/vehicleDefinitionArmor.ts b/packages/core/src/types/vehicleDefinitionArmor.ts new file mode 100644 index 000000000..9f92b26ec --- /dev/null +++ b/packages/core/src/types/vehicleDefinitionArmor.ts @@ -0,0 +1,4 @@ +export type VehicleDefinitionArmor = Record< + string, + number | { vehicleDamageFactor: 0; "#text": number } | number[] +>; diff --git a/packages/core/src/types/vehicleDefinitions.ts b/packages/core/src/types/vehicleDefinitions.ts new file mode 100644 index 000000000..e0ff86532 --- /dev/null +++ b/packages/core/src/types/vehicleDefinitions.ts @@ -0,0 +1,152 @@ +import { BlitzModuleType } from "@blitzkit/core"; + +export interface VehicleDefinitions { + invisibility: { + moving: number; + still: number; + firePenalty: number; + }; + crew: Record; + speedLimits: { + forward: number; + backward: number; + }; + consumableSlots: number; + provisionSlots: number; + optDevicePreset: string | string[]; + hull: { + armor: VehicleDefinitionArmor; + turretPositions: { turret: string }; + turretInitialRotation?: { yaw: 0; pitch: 6.5; roll: 0 }; + maxHealth: number; + weight: number; + }; + chassis: { + [key: string]: { + unlocks: UnlocksListing; + weight: number; + terrainResistance: string; + rotationSpeed: number; + userString: string; + shotDispersionFactors: { + vehicleMovement: number; + vehicleRotation: number; + }; + level: number; + hullPosition: string; + armor: { + leftTrack: + | number + | { + chanceToHitByProjectile: number; + chanceToHitByExplosion: number; + "#text": number; + }; + rightTrack: + | number + | { + chanceToHitByProjectile: number; + chanceToHitByExplosion: number; + "#text": number; + }; + }; + }; + }; + engines: { + [key: string]: { + unlocks: UnlocksListing; + }; + }; + turrets0: { + [key: string]: { + unlocks: UnlocksListing; + rotationSpeed: number; + weight: number; + circularVisionRadius: number; + maxHealth: number; + armor: VehicleDefinitionArmor; + userString: string; + level: number; + yawLimits: string | string[]; + gunPosition: string | string[]; + models: { undamaged: string }; + hitTester: { + collisionModel: string; + }; + guns: { + [key: string]: { + unlocks: UnlocksListing; + armor: VehicleDefinitionArmor & { + gun?: + | number + | { + chanceToHitByProjectile: number; + chanceToHitByExplosion: number; + "#text": number; + }; + }; + invisibilityFactorAtShot: number | number[]; + reloadTime: number; + aimingTime?: number; + shotDispersionRadius: number; + shotDispersionFactors?: { + turretRotation: number; + afterShot: number; + whileGunDamaged: number; + }; + maxAmmo: number; + extraPitchLimits?: { + front?: string; + back?: string; + transition?: number | number[]; + }; + pitchLimits?: string | string[]; + pumpGunMode?: boolean; + pumpGunReloadTimes?: string; + clip?: { count: number; rate: number }; + burst?: { count: number; rate: number }; + models: { undamaged: string }; + extras?: { + trayShell?: { + kinds: string; + sectors: Record< + string, + { id: number; distance: number; factor: number }[] + >; + }; + }; + }; + }; + }; + }; + extras?: { + armorsStatesController?: { + state: [ + { + id: 0; + armors: string; + type: "default"; + enabled: true; + factorsModifiers: ""; + default: ""; + }, + { + id: 1; + armors: string; + type: "default"; + enabled: false; + factorsModifiers: ""; + }, + ]; + }; + }; +} + +export type BlitzCrewType = + | "commander" + | "radioman" + | "gunner" + | "driver" + | "loader"; + +export type UnlocksListing = BlitzModuleType | undefined; diff --git a/packages/scripts/src/buildAssets/definitions.ts b/packages/scripts/src/buildAssets/definitions.ts index dfbc375ea..4b6ad7329 100644 --- a/packages/scripts/src/buildAssets/definitions.ts +++ b/packages/scripts/src/buildAssets/definitions.ts @@ -1,20 +1,14 @@ import { - Armor, - AssaultRanges, type BlitzTankClass, CamouflageDefinitions, Consumable, ConsumableDefinitions, ConsumableTankCategoryFilterCategory, - Crew, CrewType, - Equalizer, EquipmentDefinitions, EquipmentSlot, - GunDefinition, MapDefinitions, ModelDefinitions, - ModuleType, Provision, ProvisionDefinitions, ResearchCost, @@ -22,17 +16,9 @@ import { SkillDefinitions, TankClass, TankDefinitions, - TankPrice, - TankPriceType, - TankType, - toUniqueId, - Unlock, Vector3, } from "@blitzkit/core"; -import { parse as parsePath } from "path"; import type { Vector3Tuple } from "three"; -import { vfs } from "./constants"; -import type { TankParameters } from "./tankIcons"; function parseResearchCost(raw: number | string) { if (typeof raw === "number") { @@ -71,249 +57,9 @@ const blitzTankClassToBlitzkit: Record = { export interface BlitzStrings { [key: string]: string; } -type ShellKind = - | "ARMOR_PIERCING" - | "ARMOR_PIERCING_CR" - | "HIGH_EXPLOSIVE" - | "HOLLOW_CHARGE"; -type ShellDefinitionsList = Record< - string, - { - id: number; - userString: string; - icon: string; - kind: ShellKind; - caliber: number; - damage: { armor: number; devices: number }; - normalizationAngle?: number; - ricochetAngle?: number; - explosionRadius?: number; - } -> & { - icons: Record; -}; -interface EngineDefinitionsList { - nextAvailableId: number; - ids: Record; - shared: { - [key: string]: { - userString: string; - level: number; - fireStartingChance: number; - weight: number; - power: number; - }; - }; -} -export interface ChassisDefinitionsList { - nextAvailableId: number; - ids: Record; -} -type VehicleDefinitionArmor = Record< - string, - number | { vehicleDamageFactor: 0; "#text": number } | number[] ->; -interface UnlocksInner { - cost: number | string; - "#text": number; -} -type BlitzModuleType = { - [key in "vehicle" | "engine" | "chassis" | "turret" | "gun"]: - | UnlocksInner - | UnlocksInner[]; -}; -type UnlocksListing = BlitzModuleType | undefined; -type BlitzCrewType = "commander" | "radioman" | "gunner" | "driver" | "loader"; -const blitzCrewTypeToBlitzkit: Record = { - commander: CrewType.CREW_TYPE_COMMANDER, - driver: CrewType.CREW_TYPE_DRIVER, - gunner: CrewType.CREW_TYPE_GUNNER, - loader: CrewType.CREW_TYPE_LOADER, - radioman: CrewType.CREW_TYPE_RADIOMAN, -}; -const blitzkitCrewTypeToBlitz: Record = { - [CrewType.CREW_TYPE_COMMANDER]: "commander", - [CrewType.CREW_TYPE_DRIVER]: "driver", - [CrewType.CREW_TYPE_GUNNER]: "gunner", - [CrewType.CREW_TYPE_LOADER]: "loader", - [CrewType.CREW_TYPE_RADIOMAN]: "radioman", -}; -interface VehicleDefinitions { - invisibility: { - moving: number; - still: number; - firePenalty: number; - }; - crew: Record; - speedLimits: { - forward: number; - backward: number; - }; - consumableSlots: number; - provisionSlots: number; - optDevicePreset: string | string[]; - hull: { - armor: VehicleDefinitionArmor; - turretPositions: { turret: string }; - turretInitialRotation?: { yaw: 0; pitch: 6.5; roll: 0 }; - maxHealth: number; - weight: number; - }; - chassis: { - [key: string]: { - unlocks: UnlocksListing; - weight: number; - terrainResistance: string; - rotationSpeed: number; - userString: string; - shotDispersionFactors: { - vehicleMovement: number; - vehicleRotation: number; - }; - level: number; - hullPosition: string; - armor: { - leftTrack: - | number - | { - chanceToHitByProjectile: number; - chanceToHitByExplosion: number; - "#text": number; - }; - rightTrack: - | number - | { - chanceToHitByProjectile: number; - chanceToHitByExplosion: number; - "#text": number; - }; - }; - }; - }; - engines: { - [key: string]: { - unlocks: UnlocksListing; - }; - }; - turrets0: { - [key: string]: { - unlocks: UnlocksListing; - rotationSpeed: number; - weight: number; - circularVisionRadius: number; - maxHealth: number; - armor: VehicleDefinitionArmor; - userString: string; - level: number; - yawLimits: string | string[]; - gunPosition: string | string[]; - models: { undamaged: string }; - hitTester: { - collisionModel: string; - }; - guns: { - [key: string]: { - unlocks: UnlocksListing; - armor: VehicleDefinitionArmor & { - gun?: - | number - | { - chanceToHitByProjectile: number; - chanceToHitByExplosion: number; - "#text": number; - }; - }; - invisibilityFactorAtShot: number | number[]; - reloadTime: number; - aimingTime?: number; - shotDispersionRadius: number; - shotDispersionFactors?: { - turretRotation: number; - afterShot: number; - whileGunDamaged: number; - }; - maxAmmo: number; - extraPitchLimits?: { - front?: string; - back?: string; - transition?: number | number[]; - }; - pitchLimits?: string | string[]; - pumpGunMode?: boolean; - pumpGunReloadTimes?: string; - clip?: { count: number; rate: number }; - burst?: { count: number; rate: number }; - models: { undamaged: string }; - extras?: { - trayShell?: { - kinds: string; - sectors: Record< - string, - { id: number; distance: number; factor: number }[] - >; - }; - }; - }; - }; - }; - }; - extras?: { - armorsStatesController?: { - state: [ - { - id: 0; - armors: string; - type: "default"; - enabled: true; - factorsModifiers: ""; - default: ""; - }, - { - id: 1; - armors: string; - type: "default"; - enabled: false; - factorsModifiers: ""; - }, - ]; - }; - }; -} -interface TurretDefinitionsList { - nextAvailableId: number; - ids: Record; -} -interface GunDefinitionsList { - nextAvailableId: number; - ids: Record; - shared: { - [key: string]: { - rotationSpeed: number; - weight: number; - shotDispersionFactors: { - turretRotation: number; - afterShot: number; - whileGunDamaged: number; - }; - maxAmmo: number; - aimingTime: number; - shotDispersionRadius: number; - userString: string; - tags: string; - level: number; - pitchLimits: string; - burst?: { count: number; rate: number }; - clip?: { count: number; rate: number }; - shots: { - [key: string]: { - speed: number; - piercingPower: string; - maxDistance: number; - }; - }; - }; - }; -} + + + export interface ConsumablesCommon { [key: string]: { id: number; @@ -376,724 +122,8 @@ const blitzShellKindToBlitzkit: Record = { HIGH_EXPLOSIVE: ShellType.SHELL_TYPE_HE, HOLLOW_CHARGE: ShellType.SHELL_TYPE_HEAT, }; -const blitzModuleTypeToBlitzkit: Record = { - chassis: ModuleType.MODULE_TYPE_TRACKS, - engine: ModuleType.MODULE_TYPE_ENGINE, - gun: ModuleType.MODULE_TYPE_GUN, - turret: ModuleType.MODULE_TYPE_TURRET, - vehicle: ModuleType.MODULE_TYPE_VEHICLE, -}; - -function assignArmor( - raw: VehicleDefinitionArmor[string], - id: number, - armor: Armor, -) { - if (typeof raw === "number") { - armor.thickness[id] = raw; - } else if (Array.isArray(raw)) { - armor.thickness[id] = raw.at(-1)!; - } else { - if (!armor.spaced) armor.spaced = []; - armor.thickness[id] = raw["#text"]; - armor.spaced.push(id); - } -} export async function definitions() { - for (const nation of nationsDir) { - const tankList = await vfs.xml<{ root: VehicleDefinitionList }>( - `Data/XML/item_defs/vehicles/${nation}/list.xml`, - ); - const turretList = await vfs.xml<{ - root: TurretDefinitionsList; - }>(`Data/XML/item_defs/vehicles/${nation}/components/turrets.xml`); - const gunList = await vfs.xml<{ - root: GunDefinitionsList; - }>(`Data/XML/item_defs/vehicles/${nation}/components/guns.xml`); - const shellList = await vfs.xml<{ - root: ShellDefinitionsList; - }>(`Data/XML/item_defs/vehicles/${nation}/components/shells.xml`); - const enginesList = await vfs.xml<{ - root: EngineDefinitionsList; - }>(`Data/XML/item_defs/vehicles/${nation}/components/engines.xml`); - const chassisList = await vfs.xml<{ - root: ChassisDefinitionsList; - }>(`Data/XML/item_defs/vehicles/${nation}/components/chassis.xml`); - - function resolveUnlocks(unlocks?: BlitzModuleType) { - if (!unlocks) return []; - - return Object.entries(unlocks) - .map(([type, list]) => - (Array.isArray(list) ? list : [list]).map((item) => { - const typeTyped = type as keyof BlitzModuleType; - let rawId: number; - - if (typeTyped === "chassis") { - rawId = chassisList.root.ids[item["#text"]]; - } else if (typeTyped === "engine") { - rawId = enginesList.root.ids[item["#text"]]; - } else if (typeTyped === "gun") { - rawId = gunList.root.ids[item["#text"]]; - } else if (typeTyped === "turret") { - rawId = turretList.root.ids[item["#text"]]; - } else if (typeTyped === "vehicle") { - rawId = tankList.root[item["#text"]].id; - } - - return { - id: toUniqueId(nation, rawId!), - type: blitzModuleTypeToBlitzkit[typeTyped], - cost: { - type: - typeof item.cost === "number" - ? "xp" - : item.cost.split(":")[0], - value: - typeof item.cost === "number" - ? item.cost - : Number(item.cost.split(":")[1]), - }, - } satisfies Unlock; - }), - ) - .flat(); - } - - for (const tankKey in tankList.root) { - if (botPattern.test(tankKey)) continue; - - const gunXps = new Map(); - const turretXps = new Map(); - const engineXps = new Map(); - const trackXps = new Map(); - const tank = tankList.root[tankKey]; - let tankPrice: TankPrice; - const tankDefinition = await vfs.xml<{ root: VehicleDefinitions }>( - `Data/XML/item_defs/vehicles/${nation}/${tankKey}.xml`, - ); - const tankParameters = await vfs.yaml( - `Data/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`, - ); - const turretOrigin = tankDefinition.root.hull.turretPositions.turret - .split(" ") - .map(Number) as Vector3Tuple; - const tankId = toUniqueId(nation, tank.id); - - const tankTags = tank.tags.split(" "); - const hullArmor: Armor = { spaced: [], thickness: {} }; - const equipment = tankDefinition.root.optDevicePreset; - tankStringIdMap[`${nation}:${tankKey}`] = tankId; - const slug = slugs.get(tankId); - - if (slug === undefined) { - throw new Error( - `Could not find slug for ${nation}/${tankKey} (${tankId})`, - ); - } - - if (tank.sellPrice) { - tankPrice = { - type: TankPriceType.TANK_PRICE_TYPE_GOLD, - value: tank.sellPrice["#text"] * 2, - }; - } else if (typeof tank.price === "number") { - tankPrice = { - type: TankPriceType.TANK_PRICE_TYPE_CREDITS, - value: tank.price, - }; - } else { - tankPrice = { - type: TankPriceType.TANK_PRICE_TYPE_CREDITS, - value: tank.price["#text"] * 400, - }; - } - - Object.keys(tankDefinition.root.hull.armor) - .filter((name) => name.startsWith("armor_")) - .forEach((name) => { - const armorIdString = name.match(/armor_(\d+)/)?.[1]; - - if (armorIdString === undefined) { - throw new SyntaxError(`Invalid armor id: ${name}`); - } - - const armorId = parseInt(armorIdString); - const armorRaw = tankDefinition.root.hull.armor[name]; - - assignArmor(armorRaw, armorId, hullArmor); - }); - const crew: Crew[] = []; - const fixedCamouflage = tankTags.includes("eventCamouflage_user"); - const totalUnlocks: UnlocksListing[] = []; - - Object.entries(tankDefinition.root.crew).forEach(([crewKey, value]) => { - let entry: Crew; - const index = crew.findIndex( - ({ type }) => blitzkitCrewTypeToBlitz[type] === crewKey, - ); - if (index === -1) { - if (crewKey === "#text") return; - entry = { - type: blitzCrewTypeToBlitzkit[crewKey as BlitzCrewType], - count: 0, - substitute: [], - }; - crew.push(entry); - } else { - entry = crew[index]; - } - - if (typeof value === "string") { - entry.count++; - - if (value !== "") { - entry.substitute = value.split(/\n| /).map((member) => { - return blitzCrewTypeToBlitzkit[member.trim() as BlitzCrewType]; - }); - } - } else { - if (entry.count === undefined) { - entry.count = value.length; - } else { - entry.count += value.length; - } - } - }); - - const camouflages = Object.entries(camouflagesXml.root.camouflages) - .filter(([, camo]) => { - if (!camo.vehicleFilter.include) return false; - if (camo.unlockCostCategory !== "legendary-skins-gold") return false; - - const includeArray = Array.isArray(camo.vehicleFilter.include) - ? camo.vehicleFilter.include - : [camo.vehicleFilter.include]; - - return includeArray.some((filter) => { - if ("vehicle" in filter && filter.vehicle?.name) { - return filter.vehicle.name === `${nation}:${tankKey}`; - } - - return false; - }); - }) - .map(([, camo]) => camo.id); - - let equalizerEntry = tierEqualizer.find( - (line) => line[0] === `${nation}:${tankKey}`, - ); - - let equalizer: Equalizer | undefined; - - if (equalizerEntry) { - const [health, penetration, module_health, damage, armor] = - equalizerEntry?.slice(1).map(Number); - equalizer = { health, penetration, module_health, damage, armor }; - } - - tankDefinitions.tanks[tankId] = { - ancestors: [], - successors: [], - id: tankId, - dev_name: tankKey, - roles: {}, - camouflages: camouflages, - fixed_camouflage: fixedCamouflage, - crew, - weight: tankDefinition.root.hull.weight, - health: tankDefinition.root.hull.maxHealth, - speed_forwards: tankDefinition.root.speedLimits.forward, - speed_backwards: tankDefinition.root.speedLimits.backward, - equipment_preset: - typeof equipment === "string" ? equipment : equipment.at(-1)!, - max_consumables: tankDefinition.root.consumableSlots, - max_provisions: tankDefinition.root.provisionSlots, - name: - (tank.shortUserString - ? getString(tank.shortUserString) - : undefined) ?? getString(tank.userString), - slug, - nation, - type: tankTags.includes("collectible") - ? TankType.TANK_TYPE_COLLECTOR - : (typeof tank.price === "number" ? false : "gold" in tank.price) - ? TankType.TANK_TYPE_PREMIUM - : TankType.TANK_TYPE_RESEARCHABLE, - tier: tank.level, - class: blitzTankClassToBlitzkit[tankTags[0] as BlitzTankClass], - testing: tankTags.includes("testTank"), - deprecated: tankTags.includes("deprecated"), - price: tankPrice, - camouflage_still: tankDefinition.root.invisibility.still, - camouflage_moving: tankDefinition.root.invisibility.moving, - camouflage_on_fire: tankDefinition.root.invisibility.firePenalty, - turrets: [], - engines: [], - tracks: [], - equalizer, - }; - - if (tank.combatRole) { - Object.entries(tank.combatRole).forEach(([gameMode, role]) => { - const id = Object.entries(gameModeNativeNames).find( - ([key]) => key.toLowerCase() === gameMode.toLowerCase(), - )?.[1]; - - if (id === undefined) { - throw new Error( - `Unknown game mode in tank ${tankKey}: ${gameMode}`, - ); - } - - tankDefinitions.tanks[tankId].roles[id] = combatRoles[role].id; - }); - } - - modelDefinitions.models[tankId] = { - armor: hullArmor, - turret_origin: vector3TupleToBlitzkit(turretOrigin), - initial_turret_rotation: tankDefinition.root.hull.turretInitialRotation - ? { - yaw: tankDefinition.root.hull.turretInitialRotation.yaw, - pitch: tankDefinition.root.hull.turretInitialRotation.pitch, - roll: tankDefinition.root.hull.turretInitialRotation.roll, - } - : undefined, - bounding_box: { - min: vector3TupleToBlitzkit(tankParameters.collision.hull.bbox.min), - max: vector3TupleToBlitzkit(tankParameters.collision.hull.bbox.max), - }, - turrets: {}, - tracks: {}, - }; - - Object.keys(tankDefinition.root.chassis).forEach((key) => { - const track = tankDefinition.root.chassis[key]; - const trackId = toUniqueId(nation, chassisList.root.ids[key]); - const terrainResistances = track.terrainResistance - .split(" ") - .map(Number); - const trackArmorRaw = track.armor.leftTrack; - const hullOrigin = track.hullPosition - .split(" ") - .map(Number) as Vector3Tuple; - - totalUnlocks.push(track.unlocks); - tankDefinitions.tanks[tankId].tracks.push({ - id: trackId, - weight: track.weight, - name: getString(track.userString), - traverse_speed: track.rotationSpeed, - dispersion_move: track.shotDispersionFactors.vehicleMovement, - dispersion_traverse: track.shotDispersionFactors.vehicleRotation, - resistance_hard: terrainResistances[0], - resistance_medium: terrainResistances[1], - resistance_soft: terrainResistances[2], - tier: track.level, - unlocks: resolveUnlocks(track.unlocks), - }); - - modelDefinitions.models[tankId].tracks[trackId] = { - thickness: - typeof trackArmorRaw === "number" - ? trackArmorRaw - : trackArmorRaw["#text"], - origin: vector3TupleToBlitzkit(hullOrigin), - }; - }); - - Object.keys(tankDefinition.root.engines).forEach((engineKey) => { - const engine = tankDefinition.root.engines[engineKey]; - const engineListEntry = enginesList.root.shared[engineKey]; - const engineId = toUniqueId(nation, enginesList.root.ids[engineKey]); - - totalUnlocks.push(engine.unlocks); - tankDefinitions.tanks[tankId].engines.push({ - id: engineId, - name: getString(engineListEntry.userString), - fire_chance: engineListEntry.fireStartingChance, - tier: engineListEntry.level, - weight: engineListEntry.weight, - power: engineListEntry.power, - unlocks: resolveUnlocks(engine.unlocks), - }); - }); - - Object.keys(tankDefinition.root.turrets0).forEach( - (turretKey, turretIndex) => { - const turret = tankDefinition.root.turrets0[turretKey]; - const turretModel = Number( - parsePath(turret.models.undamaged).name.split("_")[1], - ); - const turretId = toUniqueId(nation, turretList.root.ids[turretKey]); - const turretYaw = ( - typeof turret.yawLimits === "string" - ? turret.yawLimits - : turret.yawLimits.at(-1)! - ) - .split(" ") - .map(Number) as [number, number]; - const gunOrigin = ( - typeof turret.gunPosition === "string" - ? turret.gunPosition - : turret.gunPosition[0] - ) - .split(" ") - .map(Number) as Vector3Tuple; - const turretArmor: Armor = { thickness: {}, spaced: [] }; - - totalUnlocks.push(turret.unlocks); - Object.keys(turret.armor) - .filter((name) => name.startsWith("armor_")) - .forEach((name) => { - const armorIdString = name.match(/armor_(\d+)/)?.[1]; - - if (armorIdString === undefined) { - throw new SyntaxError(`Invalid armor id: ${name}`); - } - - const armorId = parseInt(armorIdString); - const armorRaw = turret.armor[name]; - - assignArmor(armorRaw, armorId, turretArmor); - }); - - turret.userString; - - tankDefinitions.tanks[tankId].turrets.push({ - id: turretId, - traverse_speed: turret.rotationSpeed, - name: getString(turret.userString), - tier: turret.level, - guns: [], - health: turret.maxHealth, - view_range: turret.circularVisionRadius, - weight: turret.weight, - unlocks: resolveUnlocks(turret.unlocks), - }); - - modelDefinitions.models[tankId].turrets[turretId] = { - armor: turretArmor, - gun_origin: vector3TupleToBlitzkit(gunOrigin), - model_id: turretModel, - yaw: - -turretYaw[0] + turretYaw[1] === 360 - ? undefined - : { min: turretYaw[0], max: turretYaw[1] }, - guns: {}, - bounding_box: { - min: vector3TupleToBlitzkit( - tankParameters.collision[ - parsePath(turret.hitTester.collisionModel).name.toLowerCase() - ].bbox.min, - ), - max: vector3TupleToBlitzkit( - tankParameters.collision[ - parsePath(turret.hitTester.collisionModel).name.toLowerCase() - ].bbox.max, - ), - }, - }; - - Object.keys(turret.guns).forEach((gunKey, gunIndex) => { - const gun = turret.guns[gunKey]; - const gunId = toUniqueId(nation, gunList.root.ids[gunKey]); - const gunListEntry = gunList.root.shared[gunKey]; - const pitchLimitsRaw = gun.pitchLimits ?? gunListEntry.pitchLimits; - const gunPitch = ( - typeof pitchLimitsRaw === "string" - ? pitchLimitsRaw - : pitchLimitsRaw.at(-1)! - ) - .split(" ") - .map(Number) as [number, number]; - const gunModel = Number( - parsePath(gun.models.undamaged).name.split("_")[1], - ); - const gunName = getString(gunListEntry.userString); - const gunType = - "clip" in gun - ? gun.pumpGunMode - ? "autoReloader" - : "autoLoader" - : "regular"; - // const gunReload = gun.reloadTime; - // const shellReloads = - const gunClipCount = gunType === "regular" ? 1 : gun.clip!.count; - const front = gun.extraPitchLimits?.front - ? gun.extraPitchLimits.front.split(" ").map(Number) - : undefined; - const back = gun.extraPitchLimits?.back - ? gun.extraPitchLimits.back.split(" ").map(Number) - : undefined; - const transition = gun.extraPitchLimits?.transition - ? typeof gun.extraPitchLimits.transition === "number" - ? gun.extraPitchLimits.transition - : gun.extraPitchLimits.transition.at(-1)! - : undefined; - const gunArmor: Armor = { thickness: {}, spaced: [] }; - const shotDispersionFactors = - gun.shotDispersionFactors ?? gunListEntry.shotDispersionFactors; - const maskName = `gun_${gunModel.toString().padStart(2, "0")}`; - const maskEnabled = - tankParameters.maskSlice?.[maskName]?.enabled ?? false; - let mask: number | undefined; - let assault_ranges: AssaultRanges | undefined; - - if (gun.extras?.trayShell) { - const types = gun.extras.trayShell.kinds - .split(" ") - .map((string) => { - const trimmed = string.trim(); - - if (trimmed in blitzShellKindToBlitzkit) { - return blitzShellKindToBlitzkit[trimmed as ShellKind]; - } - - throw new SyntaxError(`Invalid shell kind: ${trimmed}`); - }); - const sectorNames = Object.keys(gun.extras.trayShell.sectors); - - if (sectorNames.length !== 1 || sectorNames[0] !== "sector") { - throw new SyntaxError("Invalid tray shell sector"); - } - - const sector = gun.extras.trayShell.sectors.sector; - - assault_ranges = { - types, - ranges: sector.map((value) => ({ - factor: value.factor, - distance: value.distance, - })), - }; - } - - if (maskEnabled) { - const maskRaw = tankParameters.maskSlice![maskName]!; - mask = maskRaw.planePosition[1]; - } else { - mask = undefined; - } - - totalUnlocks.push(gun.unlocks); - Object.keys(gun.armor) - .filter((name) => name.startsWith("armor_")) - .forEach((name) => { - const armorIdString = name.match(/armor_(\d+)/)?.[1]; - if (armorIdString === undefined) { - throw new SyntaxError(`Invalid armor id: ${name}`); - } - const armorId = parseInt(armorIdString); - const armorRaw = gun.armor[name]; - - assignArmor(armorRaw, armorId, gunArmor); - }); - - tankDefinitions.tanks[tankId].turrets[turretIndex].guns.push({ - id: gunId, - weight: gunListEntry.weight, - rotation_speed: gunListEntry.rotationSpeed, - name: gunName, - tier: gunListEntry.level, - shells: [], - camouflage_loss: - typeof gun.invisibilityFactorAtShot === "number" - ? gun.invisibilityFactorAtShot - : gun.invisibilityFactorAtShot.at(-1)!, - aim_time: gun.aimingTime ?? gunListEntry.aimingTime, - dispersion_base: - gun.shotDispersionRadius ?? gunListEntry.shotDispersionRadius, - dispersion_damaged: shotDispersionFactors.whileGunDamaged, - dispersion_shot: shotDispersionFactors.afterShot, - dispersion_traverse: shotDispersionFactors.turretRotation, - unlocks: resolveUnlocks(gun.unlocks), - shell_capacity: gun.maxAmmo ?? gunListEntry.maxAmmo, - assault_ranges, - burst: - gun.burst && gun.burst.count > 1 - ? { - count: gun.burst.count, - interval: 60 / gun.burst.rate, - } - : undefined, - gun_type: - gunType === "regular" - ? { - $case: "regular", - value: { - reload: gun.reloadTime, - }, - } - : gunType === "autoReloader" - ? { - $case: "auto_reloader", - value: { - intra_clip: 60 / gun.clip!.rate, - shell_count: gunClipCount, - shell_reloads: gun - .pumpGunReloadTimes!.split(" ") - .map(Number), - }, - } - : { - $case: "auto_loader", - value: { - intra_clip: 60 / gun.clip!.rate, - clip_reload: gun.reloadTime, - shell_count: gunClipCount, - }, - }, - } satisfies GunDefinition); - - modelDefinitions.models[tankId].turrets[turretId].guns[gunId] = { - armor: gunArmor, - model_id: gunModel, - mask, - thickness: - gun.armor.gun === undefined - ? 0 - : typeof gun.armor.gun === "number" - ? gun.armor.gun - : gun.armor.gun["#text"], - pitch: { - min: gunPitch[0], - max: gunPitch[1], - - front: front - ? { - min: front[0], - max: front[1], - range: front[2], - } - : undefined, - back: back - ? { - min: back[0], - max: back[1], - range: back[2], - } - : undefined, - transition, - }, - }; - - Object.keys(gunListEntry.shots).forEach((shellKey) => { - const gunShellEntry = gunListEntry.shots[shellKey]; - const shell = shellList.root[shellKey]; - const shellId = toUniqueId(nation, shell.id); - const shellName = getString(shell.userString); - const penetrationRaw = gunShellEntry.piercingPower - .split(" ") - .filter((penetrationString) => penetrationString !== "") - .map(Number); - - tankDefinitions.tanks[tankId].turrets[turretIndex].guns[ - gunIndex - ].shells.push({ - id: shellId, - name: shellName, - velocity: gunShellEntry.speed, - armor_damage: shell.damage.armor, - module_damage: shell.damage.devices, - caliber: shell.caliber, - normalization: shell.normalizationAngle, - ricochet: shell.ricochetAngle, - type: blitzShellKindToBlitzkit[shell.kind], - explosion_radius: - shell.kind === "HIGH_EXPLOSIVE" - ? (shell.explosionRadius ?? 0) - : undefined, - icon: shell.icon, - penetration: { - near: penetrationRaw[0], - far: penetrationRaw[1], - }, - range: gunShellEntry.maxDistance, - }); - }); - }); - }, - ); - - totalUnlocks.forEach((unlocks) => { - if (unlocks === undefined) return; - - Object.entries(unlocks).forEach(([key, value]) => { - (Array.isArray(value) ? value : [value]).forEach((vehicle) => { - switch (key as keyof BlitzModuleType) { - case "vehicle": { - const tankListEntry = tankList.root[vehicle["#text"]]; - const currentTank = tankDefinitions.tanks[tankId]; - const successorId = toUniqueId(nation, tankListEntry.id); - - tankXps.set(successorId, parseResearchCost(vehicle.cost)); - - if (currentTank.successors === undefined) { - currentTank.successors = []; - } - if (!currentTank.successors!.includes(successorId)) { - currentTank.successors!.push(successorId); - } - break; - } - - case "gun": { - gunXps.set( - toUniqueId(nation, gunList.root.ids[vehicle["#text"]]), - parseResearchCost(vehicle.cost), - ); - break; - } - - case "turret": { - turretXps.set( - toUniqueId(nation, turretList.root.ids[vehicle["#text"]]), - parseResearchCost(vehicle.cost), - ); - break; - } - - case "engine": { - engineXps.set( - toUniqueId(nation, enginesList.root.ids[vehicle["#text"]]), - parseResearchCost(vehicle.cost), - ); - break; - } - - case "chassis": { - trackXps.set( - toUniqueId(nation, chassisList.root.ids[vehicle["#text"]]), - parseResearchCost(vehicle.cost), - ); - break; - } - } - }); - }); - }); - - Object.values(tankDefinitions.tanks[tankId].turrets).forEach((turret) => { - turret.research_cost = turretXps.get(turret.id); - - Object.values(turret.guns).forEach((gunRaw) => { - gunRaw.research_cost = gunXps.get(gunRaw.id); - }); - }); - - Object.values(tankDefinitions.tanks[tankId].engines).forEach((engine) => { - engine.research_cost = engineXps.get(engine.id); - }); - - Object.values(tankDefinitions.tanks[tankId].tracks).forEach((track) => { - track.research_cost = trackXps.get(track.id); - }); - } - } - Object.values(tankDefinitions.tanks).forEach((tank) => { tank.research_cost = tankXps.get(tank.id); }); diff --git a/packages/scripts/src/buildAssets/tankIcons.ts b/packages/scripts/src/buildAssets/tankIcons.ts index fb74388d9..3e8995f8d 100644 --- a/packages/scripts/src/buildAssets/tankIcons.ts +++ b/packages/scripts/src/buildAssets/tankIcons.ts @@ -1,43 +1,10 @@ import { NATION_IDS } from "@blitzkit/core"; import sharp from "sharp"; -import type { Vector3Tuple } from "three"; import { parsePackedSpriteRect } from "../core/blitz/parsePackedSpriteRect"; import { AssetUploader } from "../core/github/assetUploader"; import { vfs } from "./constants"; import type { VehicleDefinitionList } from "./definitions"; -export interface TankParameters { - resourcesPath: { - smallIconPath: string; - bigIconPath: string; - blitzModelPath: string; - }; - collision: { - [key: string]: { - points: Vector3Tuple; - bbox: { - min: Vector3Tuple; - max: Vector3Tuple; - }; - averageThickness?: Record; - }; - }; - maskSlice?: { - [key: string]: - | { - enabled: boolean; - planePosition: Vector3Tuple; - planeNormal: Vector3Tuple; - planeAxis: Vector3Tuple; - frustumConeBaseRadius: number; - frustumConeAngleDegrees: number; - planeSliceMode: number; - frustumConeSliceMode: number; - } - | undefined; - }; -} - async function extractPackedTankIcon(packedBuffer: Buffer, context: string) { const texture = sharp(packedBuffer); const spriteRect = parsePackedSpriteRect(packedBuffer); From f7180f0e1aa25001def2f30286d72fabf3e4d2ee Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sat, 1 Aug 2026 13:53:00 -0500 Subject: [PATCH 016/147] (2) some implementation --- packages/core/src/blitzkit/api/server.ts | 445 ++++++++++-------- .../core/src/types/shellDefinitionsList.ts | 2 +- .../scripts/src/buildAssets/definitions.ts | 38 +- 3 files changed, 258 insertions(+), 227 deletions(-) diff --git a/packages/core/src/blitzkit/api/server.ts b/packages/core/src/blitzkit/api/server.ts index 5162f345e..4958f53fb 100644 --- a/packages/core/src/blitzkit/api/server.ts +++ b/packages/core/src/blitzkit/api/server.ts @@ -1,9 +1,11 @@ import { AbstractVFS, Armor, + AssaultRanges, BlitzCrewType, BlitzModuleType, BlitzStrings, + BlitzTankClass, CamouflageDefinitions, CamouflagesXml, CamouflagesYaml, @@ -14,7 +16,9 @@ import { Crew, CrewType, EngineDefinitionsList, + Equalizer, EquipmentDefinitions, + GunDefinition, GunDefinitionsList, I18nString, MapDefinitions, @@ -27,14 +31,18 @@ import { ProvisionsCommon, ResearchCost, ShellDefinitionsList, + ShellKind, + ShellType, SkillDefinitions, sluggify, SquadBattleTypeStylesYaml, + TankClass, TankDefinitions, TankmenAvatar, TankParameters, TankPrice, TankPriceType, + TankType, toUniqueId, TurretDefinitionsList, Unlock, @@ -45,6 +53,7 @@ import { } from "@blitzkit/core"; import { SUPPORTED_LOCALE_BLITZ_MAP } from "@blitzkit/i18n"; import locales from "@blitzkit/i18n/locales.json"; +import { parse as parsePath } from "path"; import { parse as parseYaml } from "yaml"; import { BlitzKitAPI } from "./base"; @@ -84,6 +93,18 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { loader: CrewType.CREW_TYPE_LOADER, radioman: CrewType.CREW_TYPE_RADIOMAN, }; + private blitzTankClassToBlitzkit: Record = { + lightTank: TankClass.TANK_CLASS_LIGHT, + "AT-SPG": TankClass.TANK_CLASS_TANK_DESTROYER, + heavyTank: TankClass.TANK_CLASS_HEAVY, + mediumTank: TankClass.TANK_CLASS_MEDIUM, + }; + private blitzShellKindToBlitzkit: Record = { + ARMOR_PIERCING: ShellType.SHELL_TYPE_AP, + ARMOR_PIERCING_CR: ShellType.SHELL_TYPE_APCR, + HIGH_EXPLOSIVE: ShellType.SHELL_TYPE_HE, + HOLLOW_CHARGE: ShellType.SHELL_TYPE_HEAT, + }; private stringsI18n: Record> = {}; private nationsDir?: string[]; @@ -235,6 +256,24 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { } } + private parseResearchCost(raw: number | string) { + if (typeof raw === "number") { + return { + research_cost_type: { $case: "xp", value: raw }, + } satisfies ResearchCost; + } else { + return { + research_cost_type: { + $case: "seasonal_tokens", + value: { + season: Number(/prx_season_(\d+):\d+/.exec(raw)![1]), + tokens: Number(/prx_season_\d+:(\d+)/.exec(raw)![1]), + }, + }, + } satisfies ResearchCost; + } + } + async tankDefinitions() { const tankDefinitions = TankDefinitions.create(); @@ -515,7 +554,9 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { } } - const camouflages = Object.entries(camouflagesXml.root.camouflages) + const camouflages = Object.entries( + this.camouflagesXml!.root.camouflages, + ) .filter(([, camo]) => { if (!camo.vehicleFilter.include) return false; if (camo.unlockCostCategory !== "legendary-skins-gold") @@ -535,7 +576,7 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { }) .map(([, camo]) => camo.id); - let equalizerEntry = tierEqualizer.find( + let equalizerEntry = this.tierEqualizer!.find( (line) => line[0] === `${nation}:${tankKey}`, ); @@ -566,8 +607,8 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { max_provisions: tankDefinition.root.provisionSlots, name: (tank.shortUserString - ? getString(tank.shortUserString) - : undefined) ?? getString(tank.userString), + ? this.getString(tank.shortUserString) + : undefined) ?? this.getString(tank.userString), slug, nation, type: tankTags.includes("collectible") @@ -576,7 +617,7 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { ? TankType.TANK_TYPE_PREMIUM : TankType.TANK_TYPE_RESEARCHABLE, tier: tank.level, - class: blitzTankClassToBlitzkit[tankTags[0] as BlitzTankClass], + class: this.blitzTankClassToBlitzkit[tankTags[0] as BlitzTankClass], testing: tankTags.includes("testTank"), deprecated: tankTags.includes("deprecated"), price: tankPrice, @@ -601,7 +642,8 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { ); } - tankDefinitions.tanks[tankId].roles[id] = combatRoles[role].id; + tankDefinitions.tanks[tankId].roles[id] = + this.combatRoles![role].id; }); } @@ -616,7 +658,7 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { tankDefinitions.tanks[tankId].tracks.push({ id: trackId, weight: track.weight, - name: getString(track.userString), + name: this.getString(track.userString), traverse_speed: track.rotationSpeed, dispersion_move: track.shotDispersionFactors.vehicleMovement, dispersion_traverse: track.shotDispersionFactors.vehicleRotation, @@ -624,7 +666,10 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { resistance_medium: terrainResistances[1], resistance_soft: terrainResistances[2], tier: track.level, - unlocks: resolveUnlocks(track.unlocks), + unlocks: resolveUnlocks( + this.blitzModuleTypeToBlitzkit, + track.unlocks, + ), }); }); @@ -636,17 +681,21 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { totalUnlocks.push(engine.unlocks); tankDefinitions.tanks[tankId].engines.push({ id: engineId, - name: getString(engineListEntry.userString), + name: this.getString(engineListEntry.userString), fire_chance: engineListEntry.fireStartingChance, tier: engineListEntry.level, weight: engineListEntry.weight, power: engineListEntry.power, - unlocks: resolveUnlocks(engine.unlocks), + unlocks: resolveUnlocks( + this.blitzModuleTypeToBlitzkit, + engine.unlocks, + ), }); }); - Object.keys(tankDefinition.root.turrets0).forEach( - (turretKey, turretIndex) => { + let turretIndex = 0; + for (const turretKey in tankDefinition.root.turrets0) { + { const turret = tankDefinition.root.turrets0[turretKey]; const turretId = toUniqueId(nation, turretList.root.ids[turretKey]); @@ -665,7 +714,7 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { const armorId = parseInt(armorIdString); const armorRaw = turret.armor[name]; - assignArmor(armorRaw, armorId, turretArmor); + this.assignArmor(armorRaw, armorId, turretArmor); }); turret.userString; @@ -673,205 +722,223 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { tankDefinitions.tanks[tankId].turrets.push({ id: turretId, traverse_speed: turret.rotationSpeed, - name: getString(turret.userString), + name: this.getString(turret.userString), tier: turret.level, guns: [], health: turret.maxHealth, view_range: turret.circularVisionRadius, weight: turret.weight, - unlocks: resolveUnlocks(turret.unlocks), + unlocks: resolveUnlocks( + this.blitzModuleTypeToBlitzkit, + turret.unlocks, + ), }); - Object.keys(turret.guns).forEach((gunKey, gunIndex) => { - const gun = turret.guns[gunKey]; - const gunId = toUniqueId(nation, gunList.root.ids[gunKey]); - const gunListEntry = gunList.root.shared[gunKey]; - const pitchLimitsRaw = - gun.pitchLimits ?? gunListEntry.pitchLimits; - const gunPitch = ( - typeof pitchLimitsRaw === "string" - ? pitchLimitsRaw - : pitchLimitsRaw.at(-1)! - ) - .split(" ") - .map(Number) as [number, number]; - const gunModel = Number( - parsePath(gun.models.undamaged).name.split("_")[1], - ); - const gunName = getString(gunListEntry.userString); - const gunType = - "clip" in gun - ? gun.pumpGunMode - ? "autoReloader" - : "autoLoader" - : "regular"; - // const gunReload = gun.reloadTime; - // const shellReloads = - const gunClipCount = gunType === "regular" ? 1 : gun.clip!.count; - const gunArmor: Armor = { thickness: {}, spaced: [] }; - const shotDispersionFactors = - gun.shotDispersionFactors ?? gunListEntry.shotDispersionFactors; - const maskName = `gun_${gunModel.toString().padStart(2, "0")}`; - const maskEnabled = - tankParameters.maskSlice?.[maskName]?.enabled ?? false; - let assault_ranges: AssaultRanges | undefined; - - if (gun.extras?.trayShell) { - const types = gun.extras.trayShell.kinds + let gunIndex = 0; + for (const gunKey in turret.guns) { + { + const gun = turret.guns[gunKey]; + const gunId = toUniqueId(nation, gunList.root.ids[gunKey]); + const gunListEntry = gunList.root.shared[gunKey]; + const pitchLimitsRaw = + gun.pitchLimits ?? gunListEntry.pitchLimits; + const gunPitch = ( + typeof pitchLimitsRaw === "string" + ? pitchLimitsRaw + : pitchLimitsRaw.at(-1)! + ) .split(" ") - .map((string) => { - const trimmed = string.trim(); + .map(Number) as [number, number]; + const gunModel = Number( + parsePath(gun.models.undamaged).name.split("_")[1], + ); + const gunName = this.getString(gunListEntry.userString); + const gunType = + "clip" in gun + ? gun.pumpGunMode + ? "autoReloader" + : "autoLoader" + : "regular"; + // const gunReload = gun.reloadTime; + // const shellReloads = + const gunClipCount = + gunType === "regular" ? 1 : gun.clip!.count; + const gunArmor: Armor = { thickness: {}, spaced: [] }; + const shotDispersionFactors = + gun.shotDispersionFactors ?? + gunListEntry.shotDispersionFactors; + const maskName = `gun_${gunModel.toString().padStart(2, "0")}`; + const maskEnabled = + tankParameters.maskSlice?.[maskName]?.enabled ?? false; + let assault_ranges: AssaultRanges | undefined; - if (trimmed in blitzShellKindToBlitzkit) { - return blitzShellKindToBlitzkit[trimmed as ShellKind]; - } + if (gun.extras?.trayShell) { + const types = gun.extras.trayShell.kinds + .split(" ") + .map((string) => { + const trimmed = string.trim(); - throw new SyntaxError(`Invalid shell kind: ${trimmed}`); - }); - const sectorNames = Object.keys(gun.extras.trayShell.sectors); + if (trimmed in this.blitzShellKindToBlitzkit) { + return this.blitzShellKindToBlitzkit[ + trimmed as ShellKind + ]; + } - if (sectorNames.length !== 1 || sectorNames[0] !== "sector") { - throw new SyntaxError("Invalid tray shell sector"); + throw new SyntaxError(`Invalid shell kind: ${trimmed}`); + }); + const sectorNames = Object.keys(gun.extras.trayShell.sectors); + + if (sectorNames.length !== 1 || sectorNames[0] !== "sector") { + throw new SyntaxError("Invalid tray shell sector"); + } + + const sector = gun.extras.trayShell.sectors.sector; + + assault_ranges = { + types, + ranges: sector.map((value) => ({ + factor: value.factor, + distance: value.distance, + })), + }; } - const sector = gun.extras.trayShell.sectors.sector; + totalUnlocks.push(gun.unlocks); + Object.keys(gun.armor) + .filter((name) => name.startsWith("armor_")) + .forEach((name) => { + const armorIdString = name.match(/armor_(\d+)/)?.[1]; + if (armorIdString === undefined) { + throw new SyntaxError(`Invalid armor id: ${name}`); + } + const armorId = parseInt(armorIdString); + const armorRaw = gun.armor[name]; - assault_ranges = { - types, - ranges: sector.map((value) => ({ - factor: value.factor, - distance: value.distance, - })), - }; - } + this.assignArmor(armorRaw, armorId, gunArmor); + }); - if (maskEnabled) { - const maskRaw = tankParameters.maskSlice![maskName]!; - mask = maskRaw.planePosition[1]; - } else { - mask = undefined; - } - - totalUnlocks.push(gun.unlocks); - Object.keys(gun.armor) - .filter((name) => name.startsWith("armor_")) - .forEach((name) => { - const armorIdString = name.match(/armor_(\d+)/)?.[1]; - if (armorIdString === undefined) { - throw new SyntaxError(`Invalid armor id: ${name}`); - } - const armorId = parseInt(armorIdString); - const armorRaw = gun.armor[name]; - - assignArmor(armorRaw, armorId, gunArmor); - }); - - tankDefinitions.tanks[tankId].turrets[turretIndex].guns.push({ - id: gunId, - weight: gunListEntry.weight, - rotation_speed: gunListEntry.rotationSpeed, - name: gunName, - tier: gunListEntry.level, - shells: [], - camouflage_loss: - typeof gun.invisibilityFactorAtShot === "number" - ? gun.invisibilityFactorAtShot - : gun.invisibilityFactorAtShot.at(-1)!, - aim_time: gun.aimingTime ?? gunListEntry.aimingTime, - dispersion_base: - gun.shotDispersionRadius ?? gunListEntry.shotDispersionRadius, - dispersion_damaged: shotDispersionFactors.whileGunDamaged, - dispersion_shot: shotDispersionFactors.afterShot, - dispersion_traverse: shotDispersionFactors.turretRotation, - unlocks: resolveUnlocks(gun.unlocks), - shell_capacity: gun.maxAmmo ?? gunListEntry.maxAmmo, - assault_ranges, - burst: - gun.burst && gun.burst.count > 1 - ? { - count: gun.burst.count, - interval: 60 / gun.burst.rate, - } - : undefined, - gun_type: - gunType === "regular" - ? { - $case: "regular", - value: { - reload: gun.reloadTime, - }, - } - : gunType === "autoReloader" + tankDefinitions.tanks[tankId].turrets[turretIndex].guns.push({ + id: gunId, + weight: gunListEntry.weight, + rotation_speed: gunListEntry.rotationSpeed, + name: gunName, + tier: gunListEntry.level, + shells: [], + camouflage_loss: + typeof gun.invisibilityFactorAtShot === "number" + ? gun.invisibilityFactorAtShot + : gun.invisibilityFactorAtShot.at(-1)!, + aim_time: gun.aimingTime ?? gunListEntry.aimingTime, + dispersion_base: + gun.shotDispersionRadius ?? + gunListEntry.shotDispersionRadius, + dispersion_damaged: shotDispersionFactors.whileGunDamaged, + dispersion_shot: shotDispersionFactors.afterShot, + dispersion_traverse: shotDispersionFactors.turretRotation, + unlocks: resolveUnlocks( + this.blitzModuleTypeToBlitzkit, + gun.unlocks, + ), + shell_capacity: gun.maxAmmo ?? gunListEntry.maxAmmo, + assault_ranges, + burst: + gun.burst && gun.burst.count > 1 ? { - $case: "auto_reloader", + count: gun.burst.count, + interval: 60 / gun.burst.rate, + } + : undefined, + gun_type: + gunType === "regular" + ? { + $case: "regular", value: { - intra_clip: 60 / gun.clip!.rate, - shell_count: gunClipCount, - shell_reloads: gun - .pumpGunReloadTimes!.split(" ") - .map(Number), + reload: gun.reloadTime, }, } - : { - $case: "auto_loader", - value: { - intra_clip: 60 / gun.clip!.rate, - clip_reload: gun.reloadTime, - shell_count: gunClipCount, + : gunType === "autoReloader" + ? { + $case: "auto_reloader", + value: { + intra_clip: 60 / gun.clip!.rate, + shell_count: gunClipCount, + shell_reloads: gun + .pumpGunReloadTimes!.split(" ") + .map(Number), + }, + } + : { + $case: "auto_loader", + value: { + intra_clip: 60 / gun.clip!.rate, + clip_reload: gun.reloadTime, + shell_count: gunClipCount, + }, }, - }, - } satisfies GunDefinition); + } satisfies GunDefinition); - Object.keys(gunListEntry.shots).forEach((shellKey) => { - const gunShellEntry = gunListEntry.shots[shellKey]; - const shell = shellList.root[shellKey]; - const shellId = toUniqueId(nation, shell.id); - const shellName = getString(shell.userString); - const penetrationRaw = gunShellEntry.piercingPower - .split(" ") - .filter((penetrationString) => penetrationString !== "") - .map(Number); + for (const shellKey in gunListEntry.shots) { + { + const gunShellEntry = gunListEntry.shots[shellKey]; + const shell = shellList.root[shellKey]; + const shellId = toUniqueId(nation, shell.id); + const shellName = this.getString(shell.userString); + const penetrationRaw = gunShellEntry.piercingPower + .split(" ") + .filter((penetrationString) => penetrationString !== "") + .map(Number); - tankDefinitions.tanks[tankId].turrets[turretIndex].guns[ - gunIndex - ].shells.push({ - id: shellId, - name: shellName, - velocity: gunShellEntry.speed, - armor_damage: shell.damage.armor, - module_damage: shell.damage.devices, - caliber: shell.caliber, - normalization: shell.normalizationAngle, - ricochet: shell.ricochetAngle, - type: blitzShellKindToBlitzkit[shell.kind], - explosion_radius: - shell.kind === "HIGH_EXPLOSIVE" - ? (shell.explosionRadius ?? 0) - : undefined, - icon: shell.icon, - penetration: { - near: penetrationRaw[0], - far: penetrationRaw[1], - }, - range: gunShellEntry.maxDistance, - }); - }); - }); - }, - ); + tankDefinitions.tanks[tankId].turrets[turretIndex].guns[ + gunIndex + ].shells.push({ + id: shellId, + name: shellName, + velocity: gunShellEntry.speed, + armor_damage: shell.damage.armor, + module_damage: shell.damage.devices, + caliber: shell.caliber, + normalization: shell.normalizationAngle, + ricochet: shell.ricochetAngle, + type: this.blitzShellKindToBlitzkit[shell.kind], + explosion_radius: + shell.kind === "HIGH_EXPLOSIVE" + ? (shell.explosionRadius ?? 0) + : undefined, + icon: shell.icon, + penetration: { + near: penetrationRaw[0], + far: penetrationRaw[1], + }, + range: gunShellEntry.maxDistance, + }); + } + } + } - totalUnlocks.forEach((unlocks) => { + gunIndex++; + } + } + + turretIndex++; + } + + for (const unlocks of totalUnlocks) { if (unlocks === undefined) return; - Object.entries(unlocks).forEach(([key, value]) => { - (Array.isArray(value) ? value : [value]).forEach((vehicle) => { + for (const key in unlocks) { + const value = unlocks[key as keyof BlitzModuleType]; + + for (const vehicle of Array.isArray(value) ? value : [value]) { switch (key as keyof BlitzModuleType) { case "vehicle": { const tankListEntry = tankList.root[vehicle["#text"]]; const currentTank = tankDefinitions.tanks[tankId]; const successorId = toUniqueId(nation, tankListEntry.id); - tankXps.set(successorId, parseResearchCost(vehicle.cost)); + tankXps.set( + successorId, + this.parseResearchCost(vehicle.cost), + ); if (currentTank.successors === undefined) { currentTank.successors = []; @@ -885,7 +952,7 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { case "gun": { gunXps.set( toUniqueId(nation, gunList.root.ids[vehicle["#text"]]), - parseResearchCost(vehicle.cost), + this.parseResearchCost(vehicle.cost), ); break; } @@ -893,7 +960,7 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { case "turret": { turretXps.set( toUniqueId(nation, turretList.root.ids[vehicle["#text"]]), - parseResearchCost(vehicle.cost), + this.parseResearchCost(vehicle.cost), ); break; } @@ -901,7 +968,7 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { case "engine": { engineXps.set( toUniqueId(nation, enginesList.root.ids[vehicle["#text"]]), - parseResearchCost(vehicle.cost), + this.parseResearchCost(vehicle.cost), ); break; } @@ -909,14 +976,14 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { case "chassis": { trackXps.set( toUniqueId(nation, chassisList.root.ids[vehicle["#text"]]), - parseResearchCost(vehicle.cost), + this.parseResearchCost(vehicle.cost), ); break; } } - }); - }); - }); + } + } + } Object.values(tankDefinitions.tanks[tankId].turrets).forEach( (turret) => { @@ -974,7 +1041,7 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { async modelDefinitions() { const modelDefinitions = ModelDefinitions.create(); - for (const nation of nationsDir) { + for (const nation of this.nationsDir!) { const tankList = await vfs.xml<{ root: VehicleDefinitionList }>( `Data/XML/item_defs/vehicles/${nation}/list.xml`, ); diff --git a/packages/core/src/types/shellDefinitionsList.ts b/packages/core/src/types/shellDefinitionsList.ts index be8681432..d9e5f3e6e 100644 --- a/packages/core/src/types/shellDefinitionsList.ts +++ b/packages/core/src/types/shellDefinitionsList.ts @@ -15,7 +15,7 @@ export type ShellDefinitionsList = Record< icons: Record; }; -type ShellKind = +export type ShellKind = | "ARMOR_PIERCING" | "ARMOR_PIERCING_CR" | "HIGH_EXPLOSIVE" diff --git a/packages/scripts/src/buildAssets/definitions.ts b/packages/scripts/src/buildAssets/definitions.ts index 4b6ad7329..1cac67f8e 100644 --- a/packages/scripts/src/buildAssets/definitions.ts +++ b/packages/scripts/src/buildAssets/definitions.ts @@ -4,40 +4,18 @@ import { Consumable, ConsumableDefinitions, ConsumableTankCategoryFilterCategory, - CrewType, EquipmentDefinitions, EquipmentSlot, MapDefinitions, ModelDefinitions, Provision, ProvisionDefinitions, - ResearchCost, - ShellType, SkillDefinitions, - TankClass, TankDefinitions, Vector3, } from "@blitzkit/core"; import type { Vector3Tuple } from "three"; -function parseResearchCost(raw: number | string) { - if (typeof raw === "number") { - return { - research_cost_type: { $case: "xp", value: raw }, - } satisfies ResearchCost; - } else { - return { - research_cost_type: { - $case: "seasonal_tokens", - value: { - season: Number(/prx_season_(\d+):\d+/.exec(raw)![1]), - tokens: Number(/prx_season_\d+:(\d+)/.exec(raw)![1]), - }, - }, - } satisfies ResearchCost; - } -} - type BlitzTankFilterDefinitionCategory = "clip"; const blitzTankFilterDefinitionCategoryToBlitzkit: Record< BlitzTankFilterDefinitionCategory, @@ -48,18 +26,11 @@ const blitzTankFilterDefinitionCategoryToBlitzkit: Record< function vector3TupleToBlitzkit(tuple: Vector3Tuple) { return { x: tuple[0], y: tuple[1], z: tuple[2] } satisfies Vector3; } -const blitzTankClassToBlitzkit: Record = { - lightTank: TankClass.TANK_CLASS_LIGHT, - "AT-SPG": TankClass.TANK_CLASS_TANK_DESTROYER, - heavyTank: TankClass.TANK_CLASS_HEAVY, - mediumTank: TankClass.TANK_CLASS_MEDIUM, -}; + export interface BlitzStrings { [key: string]: string; } - - export interface ConsumablesCommon { [key: string]: { id: number; @@ -116,13 +87,6 @@ type CombatRolesYaml = Record< } >; -const blitzShellKindToBlitzkit: Record = { - ARMOR_PIERCING: ShellType.SHELL_TYPE_AP, - ARMOR_PIERCING_CR: ShellType.SHELL_TYPE_APCR, - HIGH_EXPLOSIVE: ShellType.SHELL_TYPE_HE, - HOLLOW_CHARGE: ShellType.SHELL_TYPE_HEAT, -}; - export async function definitions() { Object.values(tankDefinitions.tanks).forEach((tank) => { tank.research_cost = tankXps.get(tank.id); From bd7c952478e3ed26a001fbf2c487870d512b6889 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sat, 1 Aug 2026 13:59:56 -0500 Subject: [PATCH 017/147] (3) some implementation --- packages/core/src/blitzkit/api/server.ts | 350 +++++------------- .../scripts/src/buildAssets/definitions.ts | 3 - 2 files changed, 86 insertions(+), 267 deletions(-) diff --git a/packages/core/src/blitzkit/api/server.ts b/packages/core/src/blitzkit/api/server.ts index 4958f53fb..879e7cc9e 100644 --- a/packages/core/src/blitzkit/api/server.ts +++ b/packages/core/src/blitzkit/api/server.ts @@ -47,6 +47,7 @@ import { TurretDefinitionsList, Unlock, UnlocksListing, + Vector3, VehicleDefinitionArmor, VehicleDefinitionList, VehicleDefinitions, @@ -54,6 +55,7 @@ import { import { SUPPORTED_LOCALE_BLITZ_MAP } from "@blitzkit/i18n"; import locales from "@blitzkit/i18n/locales.json"; import { parse as parsePath } from "path"; +import { Vector3Tuple } from "three"; import { parse as parseYaml } from "yaml"; import { BlitzKitAPI } from "./base"; @@ -105,6 +107,7 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { HIGH_EXPLOSIVE: ShellType.SHELL_TYPE_HE, HOLLOW_CHARGE: ShellType.SHELL_TYPE_HEAT, }; + private botPattern = /^.+((tutorial_bot(\d+)?)|(TU))$/; private stringsI18n: Record> = {}; private nationsDir?: string[]; @@ -240,22 +243,6 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { return { locales: collection } satisfies I18nString; } - private assignArmor( - raw: VehicleDefinitionArmor[string], - id: number, - armor: Armor, - ) { - if (typeof raw === "number") { - armor.thickness[id] = raw; - } else if (Array.isArray(raw)) { - armor.thickness[id] = raw.at(-1)!; - } else { - if (!armor.spaced) armor.spaced = []; - armor.thickness[id] = raw["#text"]; - armor.spaced.push(id); - } - } - private parseResearchCost(raw: number | string) { if (typeof raw === "number") { return { @@ -300,7 +287,6 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { }); } - const botPattern = /^.+((tutorial_bot(\d+)?)|(TU))$/; const slugRequesters = new Map(); const idToNation: Record = {}; @@ -310,7 +296,7 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { ); for (const tankKey in tankList.root) { - if (botPattern.test(tankKey)) continue; + if (this.botPattern.test(tankKey)) continue; const tank = tankList.root[tankKey]; const tankId = toUniqueId(nation, tank.id); @@ -451,7 +437,7 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { } for (const tankKey in tankList.root) { - if (botPattern.test(tankKey)) continue; + if (this.botPattern.test(tankKey)) continue; const gunXps = new Map(); const turretXps = new Map(); @@ -468,9 +454,10 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { const tankId = toUniqueId(nation, tank.id); const tankTags = tank.tags.split(" "); - const hullArmor: Armor = { spaced: [], thickness: {} }; const equipment = tankDefinition.root.optDevicePreset; + this.tankStringIdMap[`${nation}:${tankKey}`] = tankId; + const slug = slugs.get(tankId); if (slug === undefined) { @@ -496,20 +483,6 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { }; } - Object.keys(tankDefinition.root.hull.armor) - .filter((name) => name.startsWith("armor_")) - .forEach((name) => { - const armorIdString = name.match(/armor_(\d+)/)?.[1]; - - if (armorIdString === undefined) { - throw new SyntaxError(`Invalid armor id: ${name}`); - } - - const armorId = parseInt(armorIdString); - const armorRaw = tankDefinition.root.hull.armor[name]; - - this.assignArmor(armorRaw, armorId, hullArmor); - }); const crew: Crew[] = []; const fixedCamouflage = tankTags.includes("eventCamouflage_user"); const totalUnlocks: UnlocksListing[] = []; @@ -699,25 +672,7 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { const turret = tankDefinition.root.turrets0[turretKey]; const turretId = toUniqueId(nation, turretList.root.ids[turretKey]); - const turretArmor: Armor = { thickness: {}, spaced: [] }; - totalUnlocks.push(turret.unlocks); - Object.keys(turret.armor) - .filter((name) => name.startsWith("armor_")) - .forEach((name) => { - const armorIdString = name.match(/armor_(\d+)/)?.[1]; - - if (armorIdString === undefined) { - throw new SyntaxError(`Invalid armor id: ${name}`); - } - - const armorId = parseInt(armorIdString); - const armorRaw = turret.armor[name]; - - this.assignArmor(armorRaw, armorId, turretArmor); - }); - - turret.userString; tankDefinitions.tanks[tankId].turrets.push({ id: turretId, @@ -804,18 +759,6 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { } totalUnlocks.push(gun.unlocks); - Object.keys(gun.armor) - .filter((name) => name.startsWith("armor_")) - .forEach((name) => { - const armorIdString = name.match(/armor_(\d+)/)?.[1]; - if (armorIdString === undefined) { - throw new SyntaxError(`Invalid armor id: ${name}`); - } - const armorId = parseInt(armorIdString); - const armorRaw = gun.armor[name]; - - this.assignArmor(armorRaw, armorId, gunArmor); - }); tankDefinitions.tanks[tankId].turrets[turretIndex].guns.push({ id: gunId, @@ -1038,41 +981,58 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { return camouflageDefinitions; } + private vector3TupleToBlitzkit(tuple: Vector3Tuple) { + return { x: tuple[0], y: tuple[1], z: tuple[2] } satisfies Vector3; + } + + private assignArmor( + raw: VehicleDefinitionArmor[string], + id: number, + armor: Armor, + ) { + if (typeof raw === "number") { + armor.thickness[id] = raw; + } else if (Array.isArray(raw)) { + armor.thickness[id] = raw.at(-1)!; + } else { + if (!armor.spaced) armor.spaced = []; + armor.thickness[id] = raw["#text"]; + armor.spaced.push(id); + } + } + async modelDefinitions() { const modelDefinitions = ModelDefinitions.create(); for (const nation of this.nationsDir!) { - const tankList = await vfs.xml<{ root: VehicleDefinitionList }>( + const tankList = await this.vfs.xml<{ root: VehicleDefinitionList }>( `Data/XML/item_defs/vehicles/${nation}/list.xml`, ); - const turretList = await vfs.xml<{ + const turretList = await this.vfs.xml<{ root: TurretDefinitionsList; }>(`Data/XML/item_defs/vehicles/${nation}/components/turrets.xml`); - const gunList = await vfs.xml<{ + const gunList = await this.vfs.xml<{ root: GunDefinitionsList; }>(`Data/XML/item_defs/vehicles/${nation}/components/guns.xml`); - const shellList = await vfs.xml<{ - root: ShellDefinitionsList; - }>(`Data/XML/item_defs/vehicles/${nation}/components/shells.xml`); - const enginesList = await vfs.xml<{ + const enginesList = await this.vfs.xml<{ root: EngineDefinitionsList; }>(`Data/XML/item_defs/vehicles/${nation}/components/engines.xml`); - const chassisList = await vfs.xml<{ + const chassisList = await this.vfs.xml<{ root: ChassisDefinitionsList; }>(`Data/XML/item_defs/vehicles/${nation}/components/chassis.xml`); for (const tankKey in tankList.root) { - if (botPattern.test(tankKey)) continue; + if (this.botPattern.test(tankKey)) continue; const gunXps = new Map(); const turretXps = new Map(); const engineXps = new Map(); const trackXps = new Map(); const tank = tankList.root[tankKey]; - const tankDefinition = await vfs.xml<{ root: VehicleDefinitions }>( + const tankDefinition = await this.vfs.xml<{ root: VehicleDefinitions }>( `Data/XML/item_defs/vehicles/${nation}/${tankKey}.xml`, ); - const tankParameters = await vfs.yaml( + const tankParameters = await this.vfs.yaml( `Data/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`, ); const turretOrigin = tankDefinition.root.hull.turretPositions.turret @@ -1080,33 +1040,8 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { .map(Number) as Vector3Tuple; const tankId = toUniqueId(nation, tank.id); - const tankTags = tank.tags.split(" "); const hullArmor: Armor = { spaced: [], thickness: {} }; - tankStringIdMap[`${nation}:${tankKey}`] = tankId; - const slug = slugs.get(tankId); - - if (slug === undefined) { - throw new Error( - `Could not find slug for ${nation}/${tankKey} (${tankId})`, - ); - } - - if (tank.sellPrice) { - tankPrice = { - type: TankPriceType.TANK_PRICE_TYPE_GOLD, - value: tank.sellPrice["#text"] * 2, - }; - } else if (typeof tank.price === "number") { - tankPrice = { - type: TankPriceType.TANK_PRICE_TYPE_CREDITS, - value: tank.price, - }; - } else { - tankPrice = { - type: TankPriceType.TANK_PRICE_TYPE_CREDITS, - value: tank.price["#text"] * 400, - }; - } + this.tankStringIdMap[`${nation}:${tankKey}`] = tankId; Object.keys(tankDefinition.root.hull.armor) .filter((name) => name.startsWith("armor_")) @@ -1120,72 +1055,53 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { const armorId = parseInt(armorIdString); const armorRaw = tankDefinition.root.hull.armor[name]; - assignArmor(armorRaw, armorId, hullArmor); + this.assignArmor(armorRaw, armorId, hullArmor); }); const crew: Crew[] = []; - const totalUnlocks: UnlocksListing[] = []; - Object.entries(tankDefinition.root.crew).forEach(([crewKey, value]) => { - let entry: Crew; - const index = crew.findIndex( - ({ type }) => blitzkitCrewTypeToBlitz[type] === crewKey, - ); - if (index === -1) { - if (crewKey === "#text") return; - entry = { - type: blitzCrewTypeToBlitzkit[crewKey as BlitzCrewType], - count: 0, - substitute: [], - }; - crew.push(entry); - } else { - entry = crew[index]; - } + for (const crewKey in tankDefinition.root.crew) { + const value = tankDefinition.root.crew[crewKey as BlitzCrewType]; - if (typeof value === "string") { - entry.count++; - - if (value !== "") { - entry.substitute = value.split(/\n| /).map((member) => { - return blitzCrewTypeToBlitzkit[member.trim() as BlitzCrewType]; - }); - } - } else { - if (entry.count === undefined) { - entry.count = value.length; + { + let entry: Crew; + const index = crew.findIndex( + ({ type }) => this.blitzkitCrewTypeToBlitz[type] === crewKey, + ); + if (index === -1) { + if (crewKey === "#text") return; + entry = { + type: this.blitzCrewTypeToBlitzkit[crewKey as BlitzCrewType], + count: 0, + substitute: [], + }; + crew.push(entry); } else { - entry.count += value.length; + entry = crew[index]; + } + + if (typeof value === "string") { + entry.count++; + + if (value !== "") { + entry.substitute = value.split(/\n| /).map((member) => { + return this.blitzCrewTypeToBlitzkit[ + member.trim() as BlitzCrewType + ]; + }); + } + } else { + if (entry.count === undefined) { + entry.count = value.length; + } else { + entry.count += value.length; + } } } - }); - - let equalizerEntry = tierEqualizer.find( - (line) => line[0] === `${nation}:${tankKey}`, - ); - - if (equalizerEntry) { - const [health, penetration, module_health, damage, armor] = - equalizerEntry?.slice(1).map(Number); - equalizer = { health, penetration, module_health, damage, armor }; - } - - if (tank.combatRole) { - Object.entries(tank.combatRole).forEach(([gameMode, role]) => { - const id = Object.entries(gameModeNativeNames).find( - ([key]) => key.toLowerCase() === gameMode.toLowerCase(), - )?.[1]; - - if (id === undefined) { - throw new Error( - `Unknown game mode in tank ${tankKey}: ${gameMode}`, - ); - } - }); } modelDefinitions.models[tankId] = { armor: hullArmor, - turret_origin: vector3TupleToBlitzkit(turretOrigin), + turret_origin: this.vector3TupleToBlitzkit(turretOrigin), initial_turret_rotation: tankDefinition.root.hull .turretInitialRotation ? { @@ -1195,14 +1111,18 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { } : undefined, bounding_box: { - min: vector3TupleToBlitzkit(tankParameters.collision.hull.bbox.min), - max: vector3TupleToBlitzkit(tankParameters.collision.hull.bbox.max), + min: this.vector3TupleToBlitzkit( + tankParameters.collision.hull.bbox.min, + ), + max: this.vector3TupleToBlitzkit( + tankParameters.collision.hull.bbox.max, + ), }, turrets: {}, tracks: {}, }; - Object.keys(tankDefinition.root.chassis).forEach((key) => { + for (const key in tankDefinition.root.chassis) { const track = tankDefinition.root.chassis[key]; const trackId = toUniqueId(nation, chassisList.root.ids[key]); const trackArmorRaw = track.armor.leftTrack; @@ -1210,22 +1130,14 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { .split(" ") .map(Number) as Vector3Tuple; - totalUnlocks.push(track.unlocks); - modelDefinitions.models[tankId].tracks[trackId] = { thickness: typeof trackArmorRaw === "number" ? trackArmorRaw : trackArmorRaw["#text"], - origin: vector3TupleToBlitzkit(hullOrigin), + origin: this.vector3TupleToBlitzkit(hullOrigin), }; - }); - - Object.keys(tankDefinition.root.engines).forEach((engineKey) => { - const engine = tankDefinition.root.engines[engineKey]; - - totalUnlocks.push(engine.unlocks); - }); + } Object.keys(tankDefinition.root.turrets0).forEach((turretKey) => { const turret = tankDefinition.root.turrets0[turretKey]; @@ -1249,7 +1161,6 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { .map(Number) as Vector3Tuple; const turretArmor: Armor = { thickness: {}, spaced: [] }; - totalUnlocks.push(turret.unlocks); Object.keys(turret.armor) .filter((name) => name.startsWith("armor_")) .forEach((name) => { @@ -1262,14 +1173,14 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { const armorId = parseInt(armorIdString); const armorRaw = turret.armor[name]; - assignArmor(armorRaw, armorId, turretArmor); + this.assignArmor(armorRaw, armorId, turretArmor); }); turret.userString; modelDefinitions.models[tankId].turrets[turretId] = { armor: turretArmor, - gun_origin: vector3TupleToBlitzkit(gunOrigin), + gun_origin: this.vector3TupleToBlitzkit(gunOrigin), model_id: turretModel, yaw: -turretYaw[0] + turretYaw[1] === 360 @@ -1277,12 +1188,12 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { : { min: turretYaw[0], max: turretYaw[1] }, guns: {}, bounding_box: { - min: vector3TupleToBlitzkit( + min: this.vector3TupleToBlitzkit( tankParameters.collision[ parsePath(turret.hitTester.collisionModel).name.toLowerCase() ].bbox.min, ), - max: vector3TupleToBlitzkit( + max: this.vector3TupleToBlitzkit( tankParameters.collision[ parsePath(turret.hitTester.collisionModel).name.toLowerCase() ].bbox.max, @@ -1305,8 +1216,6 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { const gunModel = Number( parsePath(gun.models.undamaged).name.split("_")[1], ); - // const gunReload = gun.reloadTime; - // const shellReloads = const front = gun.extraPitchLimits?.front ? gun.extraPitchLimits.front.split(" ").map(Number) : undefined; @@ -1324,35 +1233,6 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { tankParameters.maskSlice?.[maskName]?.enabled ?? false; let mask: number | undefined; - if (gun.extras?.trayShell) { - const types = gun.extras.trayShell.kinds - .split(" ") - .map((string) => { - const trimmed = string.trim(); - - if (trimmed in blitzShellKindToBlitzkit) { - return blitzShellKindToBlitzkit[trimmed as ShellKind]; - } - - throw new SyntaxError(`Invalid shell kind: ${trimmed}`); - }); - const sectorNames = Object.keys(gun.extras.trayShell.sectors); - - if (sectorNames.length !== 1 || sectorNames[0] !== "sector") { - throw new SyntaxError("Invalid tray shell sector"); - } - - const sector = gun.extras.trayShell.sectors.sector; - - assault_ranges = { - types, - ranges: sector.map((value) => ({ - factor: value.factor, - distance: value.distance, - })), - }; - } - if (maskEnabled) { const maskRaw = tankParameters.maskSlice![maskName]!; mask = maskRaw.planePosition[1]; @@ -1360,7 +1240,6 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { mask = undefined; } - totalUnlocks.push(gun.unlocks); Object.keys(gun.armor) .filter((name) => name.startsWith("armor_")) .forEach((name) => { @@ -1371,7 +1250,7 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { const armorId = parseInt(armorIdString); const armorRaw = gun.armor[name]; - assignArmor(armorRaw, armorId, gunArmor); + this.assignArmor(armorRaw, armorId, gunArmor); }); modelDefinitions.models[tankId].turrets[turretId].guns[gunId] = { @@ -1407,63 +1286,6 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { }; }); }); - - totalUnlocks.forEach((unlocks) => { - if (unlocks === undefined) return; - - Object.entries(unlocks).forEach(([key, value]) => { - (Array.isArray(value) ? value : [value]).forEach((vehicle) => { - switch (key as keyof BlitzModuleType) { - case "vehicle": { - const tankListEntry = tankList.root[vehicle["#text"]]; - const successorId = toUniqueId(nation, tankListEntry.id); - - tankXps.set(successorId, parseResearchCost(vehicle.cost)); - - if (currentTank.successors === undefined) { - currentTank.successors = []; - } - if (!currentTank.successors!.includes(successorId)) { - currentTank.successors!.push(successorId); - } - break; - } - - case "gun": { - gunXps.set( - toUniqueId(nation, gunList.root.ids[vehicle["#text"]]), - parseResearchCost(vehicle.cost), - ); - break; - } - - case "turret": { - turretXps.set( - toUniqueId(nation, turretList.root.ids[vehicle["#text"]]), - parseResearchCost(vehicle.cost), - ); - break; - } - - case "engine": { - engineXps.set( - toUniqueId(nation, enginesList.root.ids[vehicle["#text"]]), - parseResearchCost(vehicle.cost), - ); - break; - } - - case "chassis": { - trackXps.set( - toUniqueId(nation, chassisList.root.ids[vehicle["#text"]]), - parseResearchCost(vehicle.cost), - ); - break; - } - } - }); - }); - }); } } diff --git a/packages/scripts/src/buildAssets/definitions.ts b/packages/scripts/src/buildAssets/definitions.ts index 1cac67f8e..ee6f9b047 100644 --- a/packages/scripts/src/buildAssets/definitions.ts +++ b/packages/scripts/src/buildAssets/definitions.ts @@ -23,9 +23,6 @@ const blitzTankFilterDefinitionCategoryToBlitzkit: Record< > = { clip: ConsumableTankCategoryFilterCategory.CONSUMABLE_TANK_CATEGORY_FILTER_CATEGORY_CLIP, }; -function vector3TupleToBlitzkit(tuple: Vector3Tuple) { - return { x: tuple[0], y: tuple[1], z: tuple[2] } satisfies Vector3; -} export interface BlitzStrings { [key: string]: string; From 6ad64c9bd98d7285d1e87d4dd4c46ebe360d5316 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sat, 1 Aug 2026 14:01:34 -0500 Subject: [PATCH 018/147] (4) some implementation --- packages/core/src/blitzkit/api/server.ts | 45 +++++++++++++++++++ .../scripts/src/buildAssets/definitions.ts | 45 ------------------- 2 files changed, 45 insertions(+), 45 deletions(-) diff --git a/packages/core/src/blitzkit/api/server.ts b/packages/core/src/blitzkit/api/server.ts index 879e7cc9e..074fe1fb4 100644 --- a/packages/core/src/blitzkit/api/server.ts +++ b/packages/core/src/blitzkit/api/server.ts @@ -18,6 +18,7 @@ import { EngineDefinitionsList, Equalizer, EquipmentDefinitions, + EquipmentSlot, GunDefinition, GunDefinitionsList, I18nString, @@ -950,6 +951,20 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { } } + Object.values(tankDefinitions.tanks).forEach((tank) => { + tank.research_cost = tankXps.get(tank.id); + }); + + Object.values(tankDefinitions.tanks).forEach((tank) => { + tank.successors?.forEach((predecessorId) => { + if ( + !tankDefinitions.tanks[predecessorId].ancestors?.includes(tank.id) + ) { + tankDefinitions.tanks[predecessorId].ancestors?.push(tank.id); + } + }); + }); + return tankDefinitions; } @@ -1301,6 +1316,36 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { async equipmentDefinitions() { const equipmentDefinitions = EquipmentDefinitions.create(); + Object.entries(this.optionalDevices!.root).forEach( + ([optionalDeviceKey, optionalDeviceEntry]) => { + if (optionalDeviceKey === "nextAvailableId") return; + + equipmentDefinitions.equipments[optionalDeviceEntry.id] = { + name: this.getString(optionalDeviceEntry.userString), + description: this.getString(optionalDeviceEntry.description), + }; + }, + ); + + Object.entries(this.optionalDeviceSlots!.root.presets).forEach( + ([optionalDeviceSlotKey, optionalDeviceSlotEntry]) => { + if (optionalDeviceSlotKey === "emptyPreset") return; + + equipmentDefinitions.presets[optionalDeviceSlotKey] = { + slots: Object.values(optionalDeviceSlotEntry) + .map((level) => { + return Object.values(level).map((options) => { + return { + left: this.optionalDevices!.root[options.device0].id, + right: this.optionalDevices!.root[options.device1].id, + } satisfies EquipmentSlot; + }); + }) + .flat(), + }; + }, + ); + return equipmentDefinitions; } diff --git a/packages/scripts/src/buildAssets/definitions.ts b/packages/scripts/src/buildAssets/definitions.ts index ee6f9b047..cf18760f4 100644 --- a/packages/scripts/src/buildAssets/definitions.ts +++ b/packages/scripts/src/buildAssets/definitions.ts @@ -5,16 +5,13 @@ import { ConsumableDefinitions, ConsumableTankCategoryFilterCategory, EquipmentDefinitions, - EquipmentSlot, MapDefinitions, ModelDefinitions, Provision, ProvisionDefinitions, SkillDefinitions, TankDefinitions, - Vector3, } from "@blitzkit/core"; -import type { Vector3Tuple } from "three"; type BlitzTankFilterDefinitionCategory = "clip"; const blitzTankFilterDefinitionCategoryToBlitzkit: Record< @@ -85,48 +82,6 @@ type CombatRolesYaml = Record< >; export async function definitions() { - Object.values(tankDefinitions.tanks).forEach((tank) => { - tank.research_cost = tankXps.get(tank.id); - }); - - Object.values(tankDefinitions.tanks).forEach((tank) => { - tank.successors?.forEach((predecessorId) => { - if (!tankDefinitions.tanks[predecessorId].ancestors?.includes(tank.id)) { - tankDefinitions.tanks[predecessorId].ancestors?.push(tank.id); - } - }); - }); - - Object.entries(optionalDevices.root).forEach( - ([optionalDeviceKey, optionalDeviceEntry]) => { - if (optionalDeviceKey === "nextAvailableId") return; - - equipmentDefinitions.equipments[optionalDeviceEntry.id] = { - name: getString(optionalDeviceEntry.userString), - description: getString(optionalDeviceEntry.description), - }; - }, - ); - - Object.entries(optionalDeviceSlots.root.presets).forEach( - ([optionalDeviceSlotKey, optionalDeviceSlotEntry]) => { - if (optionalDeviceSlotKey === "emptyPreset") return; - - equipmentDefinitions.presets[optionalDeviceSlotKey] = { - slots: Object.values(optionalDeviceSlotEntry) - .map((level) => { - return Object.values(level).map((options) => { - return { - left: optionalDevices.root[options.device0].id, - right: optionalDevices.root[options.device1].id, - } satisfies EquipmentSlot; - }); - }) - .flat(), - }; - }, - ); - Object.entries(consumablesCommon).forEach(([key, consumable]) => { const entry: Consumable = { id: consumable.id, From ed31f4087642b35baf24feaa891c203f563f846b Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sat, 1 Aug 2026 14:13:18 -0500 Subject: [PATCH 019/147] (5) some implementation --- packages/core/src/blitzkit/api/server.ts | 682 +++++++++++------- .../blitzTankFilterDefinitionCategory.ts | 1 + packages/core/src/types/index.ts | 1 + .../buildAssets/consumableProvisionIcons.ts | 2 +- .../scripts/src/buildAssets/definitions.ts | 335 --------- 5 files changed, 442 insertions(+), 579 deletions(-) create mode 100644 packages/core/src/types/blitzTankFilterDefinitionCategory.ts delete mode 100644 packages/scripts/src/buildAssets/definitions.ts diff --git a/packages/core/src/blitzkit/api/server.ts b/packages/core/src/blitzkit/api/server.ts index 074fe1fb4..ed2d7ad7a 100644 --- a/packages/core/src/blitzkit/api/server.ts +++ b/packages/core/src/blitzkit/api/server.ts @@ -6,13 +6,16 @@ import { BlitzModuleType, BlitzStrings, BlitzTankClass, + BlitzTankFilterDefinitionCategory, CamouflageDefinitions, CamouflagesXml, CamouflagesYaml, ChassisDefinitionsList, CombatRolesYaml, + Consumable, ConsumableDefinitions, ConsumablesCommon, + ConsumableTankCategoryFilterCategory, Crew, CrewType, EngineDefinitionsList, @@ -28,6 +31,7 @@ import { ModuleType, OptionalDevices, OptionalDeviceSlots, + Provision, ProvisionDefinitions, ProvisionsCommon, ResearchCost, @@ -108,6 +112,13 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { HIGH_EXPLOSIVE: ShellType.SHELL_TYPE_HE, HOLLOW_CHARGE: ShellType.SHELL_TYPE_HEAT, }; + private blitzTankFilterDefinitionCategoryToBlitzkit: Record< + BlitzTankFilterDefinitionCategory, + ConsumableTankCategoryFilterCategory + > = { + clip: ConsumableTankCategoryFilterCategory.CONSUMABLE_TANK_CATEGORY_FILTER_CATEGORY_CLIP, + }; + private botPattern = /^.+((tutorial_bot(\d+)?)|(TU))$/; private stringsI18n: Record> = {}; @@ -165,6 +176,7 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { `Data/XML/item_defs/vehicles/common/combat_roles.yaml`, ); this.tierEqualizer = await this.vfs.csv( + // spell:disable-next-line `Data/XML/item_defs/vehicles/common/tier_equializer.csv`, { delimiter: ";" }, ); @@ -490,40 +502,37 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { for (const crewKey in tankDefinition.root.crew) { const value = tankDefinition.root.crew[crewKey as BlitzCrewType]; + let entry: Crew; + const index = crew.findIndex( + ({ type }) => this.blitzkitCrewTypeToBlitz[type] === crewKey, + ); + if (index === -1) { + if (crewKey === "#text") return; + entry = { + type: this.blitzCrewTypeToBlitzkit[crewKey as BlitzCrewType], + count: 0, + substitute: [], + }; + crew.push(entry); + } else { + entry = crew[index]; + } - { - let entry: Crew; - const index = crew.findIndex( - ({ type }) => this.blitzkitCrewTypeToBlitz[type] === crewKey, - ); - if (index === -1) { - if (crewKey === "#text") return; - entry = { - type: this.blitzCrewTypeToBlitzkit[crewKey as BlitzCrewType], - count: 0, - substitute: [], - }; - crew.push(entry); - } else { - entry = crew[index]; + if (typeof value === "string") { + entry.count++; + + if (value !== "") { + entry.substitute = value.split(/\n| /).map((member) => { + return this.blitzCrewTypeToBlitzkit[ + member.trim() as BlitzCrewType + ]; + }); } - - if (typeof value === "string") { - entry.count++; - - if (value !== "") { - entry.substitute = value.split(/\n| /).map((member) => { - return this.blitzCrewTypeToBlitzkit[ - member.trim() as BlitzCrewType - ]; - }); - } + } else { + if (entry.count === undefined) { + entry.count = value.length; } else { - if (entry.count === undefined) { - entry.count = value.length; - } else { - entry.count += value.length; - } + entry.count += value.length; } } } @@ -669,198 +678,169 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { let turretIndex = 0; for (const turretKey in tankDefinition.root.turrets0) { - { - const turret = tankDefinition.root.turrets0[turretKey]; - const turretId = toUniqueId(nation, turretList.root.ids[turretKey]); + const turret = tankDefinition.root.turrets0[turretKey]; + const turretId = toUniqueId(nation, turretList.root.ids[turretKey]); - totalUnlocks.push(turret.unlocks); + totalUnlocks.push(turret.unlocks); - tankDefinitions.tanks[tankId].turrets.push({ - id: turretId, - traverse_speed: turret.rotationSpeed, - name: this.getString(turret.userString), - tier: turret.level, - guns: [], - health: turret.maxHealth, - view_range: turret.circularVisionRadius, - weight: turret.weight, - unlocks: resolveUnlocks( - this.blitzModuleTypeToBlitzkit, - turret.unlocks, - ), - }); + tankDefinitions.tanks[tankId].turrets.push({ + id: turretId, + traverse_speed: turret.rotationSpeed, + name: this.getString(turret.userString), + tier: turret.level, + guns: [], + health: turret.maxHealth, + view_range: turret.circularVisionRadius, + weight: turret.weight, + unlocks: resolveUnlocks( + this.blitzModuleTypeToBlitzkit, + turret.unlocks, + ), + }); - let gunIndex = 0; - for (const gunKey in turret.guns) { - { - const gun = turret.guns[gunKey]; - const gunId = toUniqueId(nation, gunList.root.ids[gunKey]); - const gunListEntry = gunList.root.shared[gunKey]; - const pitchLimitsRaw = - gun.pitchLimits ?? gunListEntry.pitchLimits; - const gunPitch = ( - typeof pitchLimitsRaw === "string" - ? pitchLimitsRaw - : pitchLimitsRaw.at(-1)! - ) - .split(" ") - .map(Number) as [number, number]; - const gunModel = Number( - parsePath(gun.models.undamaged).name.split("_")[1], - ); - const gunName = this.getString(gunListEntry.userString); - const gunType = - "clip" in gun - ? gun.pumpGunMode - ? "autoReloader" - : "autoLoader" - : "regular"; - // const gunReload = gun.reloadTime; - // const shellReloads = - const gunClipCount = - gunType === "regular" ? 1 : gun.clip!.count; - const gunArmor: Armor = { thickness: {}, spaced: [] }; - const shotDispersionFactors = - gun.shotDispersionFactors ?? - gunListEntry.shotDispersionFactors; - const maskName = `gun_${gunModel.toString().padStart(2, "0")}`; - const maskEnabled = - tankParameters.maskSlice?.[maskName]?.enabled ?? false; - let assault_ranges: AssaultRanges | undefined; + let gunIndex = 0; + for (const gunKey in turret.guns) { + const gun = turret.guns[gunKey]; + const gunId = toUniqueId(nation, gunList.root.ids[gunKey]); + const gunListEntry = gunList.root.shared[gunKey]; + const gunName = this.getString(gunListEntry.userString); + const gunType = + "clip" in gun + ? gun.pumpGunMode + ? "autoReloader" + : "autoLoader" + : "regular"; + const gunClipCount = gunType === "regular" ? 1 : gun.clip!.count; + const shotDispersionFactors = + gun.shotDispersionFactors ?? gunListEntry.shotDispersionFactors; + let assault_ranges: AssaultRanges | undefined; - if (gun.extras?.trayShell) { - const types = gun.extras.trayShell.kinds - .split(" ") - .map((string) => { - const trimmed = string.trim(); + if (gun.extras?.trayShell) { + const types = gun.extras.trayShell.kinds + .split(" ") + .map((string) => { + const trimmed = string.trim(); - if (trimmed in this.blitzShellKindToBlitzkit) { - return this.blitzShellKindToBlitzkit[ - trimmed as ShellKind - ]; - } - - throw new SyntaxError(`Invalid shell kind: ${trimmed}`); - }); - const sectorNames = Object.keys(gun.extras.trayShell.sectors); - - if (sectorNames.length !== 1 || sectorNames[0] !== "sector") { - throw new SyntaxError("Invalid tray shell sector"); + if (trimmed in this.blitzShellKindToBlitzkit) { + return this.blitzShellKindToBlitzkit[trimmed as ShellKind]; } - const sector = gun.extras.trayShell.sectors.sector; + throw new SyntaxError(`Invalid shell kind: ${trimmed}`); + }); + const sectorNames = Object.keys(gun.extras.trayShell.sectors); - assault_ranges = { - types, - ranges: sector.map((value) => ({ - factor: value.factor, - distance: value.distance, - })), - }; - } - - totalUnlocks.push(gun.unlocks); - - tankDefinitions.tanks[tankId].turrets[turretIndex].guns.push({ - id: gunId, - weight: gunListEntry.weight, - rotation_speed: gunListEntry.rotationSpeed, - name: gunName, - tier: gunListEntry.level, - shells: [], - camouflage_loss: - typeof gun.invisibilityFactorAtShot === "number" - ? gun.invisibilityFactorAtShot - : gun.invisibilityFactorAtShot.at(-1)!, - aim_time: gun.aimingTime ?? gunListEntry.aimingTime, - dispersion_base: - gun.shotDispersionRadius ?? - gunListEntry.shotDispersionRadius, - dispersion_damaged: shotDispersionFactors.whileGunDamaged, - dispersion_shot: shotDispersionFactors.afterShot, - dispersion_traverse: shotDispersionFactors.turretRotation, - unlocks: resolveUnlocks( - this.blitzModuleTypeToBlitzkit, - gun.unlocks, - ), - shell_capacity: gun.maxAmmo ?? gunListEntry.maxAmmo, - assault_ranges, - burst: - gun.burst && gun.burst.count > 1 - ? { - count: gun.burst.count, - interval: 60 / gun.burst.rate, - } - : undefined, - gun_type: - gunType === "regular" - ? { - $case: "regular", - value: { - reload: gun.reloadTime, - }, - } - : gunType === "autoReloader" - ? { - $case: "auto_reloader", - value: { - intra_clip: 60 / gun.clip!.rate, - shell_count: gunClipCount, - shell_reloads: gun - .pumpGunReloadTimes!.split(" ") - .map(Number), - }, - } - : { - $case: "auto_loader", - value: { - intra_clip: 60 / gun.clip!.rate, - clip_reload: gun.reloadTime, - shell_count: gunClipCount, - }, - }, - } satisfies GunDefinition); - - for (const shellKey in gunListEntry.shots) { - { - const gunShellEntry = gunListEntry.shots[shellKey]; - const shell = shellList.root[shellKey]; - const shellId = toUniqueId(nation, shell.id); - const shellName = this.getString(shell.userString); - const penetrationRaw = gunShellEntry.piercingPower - .split(" ") - .filter((penetrationString) => penetrationString !== "") - .map(Number); - - tankDefinitions.tanks[tankId].turrets[turretIndex].guns[ - gunIndex - ].shells.push({ - id: shellId, - name: shellName, - velocity: gunShellEntry.speed, - armor_damage: shell.damage.armor, - module_damage: shell.damage.devices, - caliber: shell.caliber, - normalization: shell.normalizationAngle, - ricochet: shell.ricochetAngle, - type: this.blitzShellKindToBlitzkit[shell.kind], - explosion_radius: - shell.kind === "HIGH_EXPLOSIVE" - ? (shell.explosionRadius ?? 0) - : undefined, - icon: shell.icon, - penetration: { - near: penetrationRaw[0], - far: penetrationRaw[1], - }, - range: gunShellEntry.maxDistance, - }); - } - } + if (sectorNames.length !== 1 || sectorNames[0] !== "sector") { + throw new SyntaxError("Invalid tray shell sector"); } - gunIndex++; + const sector = gun.extras.trayShell.sectors.sector; + + assault_ranges = { + types, + ranges: sector.map((value) => ({ + factor: value.factor, + distance: value.distance, + })), + }; } + + totalUnlocks.push(gun.unlocks); + + tankDefinitions.tanks[tankId].turrets[turretIndex].guns.push({ + id: gunId, + weight: gunListEntry.weight, + rotation_speed: gunListEntry.rotationSpeed, + name: gunName, + tier: gunListEntry.level, + shells: [], + camouflage_loss: + typeof gun.invisibilityFactorAtShot === "number" + ? gun.invisibilityFactorAtShot + : gun.invisibilityFactorAtShot.at(-1)!, + aim_time: gun.aimingTime ?? gunListEntry.aimingTime, + dispersion_base: + gun.shotDispersionRadius ?? gunListEntry.shotDispersionRadius, + dispersion_damaged: shotDispersionFactors.whileGunDamaged, + dispersion_shot: shotDispersionFactors.afterShot, + dispersion_traverse: shotDispersionFactors.turretRotation, + unlocks: resolveUnlocks( + this.blitzModuleTypeToBlitzkit, + gun.unlocks, + ), + shell_capacity: gun.maxAmmo ?? gunListEntry.maxAmmo, + assault_ranges, + burst: + gun.burst && gun.burst.count > 1 + ? { + count: gun.burst.count, + interval: 60 / gun.burst.rate, + } + : undefined, + gun_type: + gunType === "regular" + ? { + $case: "regular", + value: { + reload: gun.reloadTime, + }, + } + : gunType === "autoReloader" + ? { + $case: "auto_reloader", + value: { + intra_clip: 60 / gun.clip!.rate, + shell_count: gunClipCount, + shell_reloads: gun + .pumpGunReloadTimes!.split(" ") + .map(Number), + }, + } + : { + $case: "auto_loader", + value: { + intra_clip: 60 / gun.clip!.rate, + clip_reload: gun.reloadTime, + shell_count: gunClipCount, + }, + }, + } satisfies GunDefinition); + + for (const shellKey in gunListEntry.shots) { + const gunShellEntry = gunListEntry.shots[shellKey]; + const shell = shellList.root[shellKey]; + const shellId = toUniqueId(nation, shell.id); + const shellName = this.getString(shell.userString); + const penetrationRaw = gunShellEntry.piercingPower + .split(" ") + .filter((penetrationString) => penetrationString !== "") + .map(Number); + + tankDefinitions.tanks[tankId].turrets[turretIndex].guns[ + gunIndex + ].shells.push({ + id: shellId, + name: shellName, + velocity: gunShellEntry.speed, + armor_damage: shell.damage.armor, + module_damage: shell.damage.devices, + caliber: shell.caliber, + normalization: shell.normalizationAngle, + ricochet: shell.ricochetAngle, + type: this.blitzShellKindToBlitzkit[shell.kind], + explosion_radius: + shell.kind === "HIGH_EXPLOSIVE" + ? (shell.explosionRadius ?? 0) + : undefined, + icon: shell.icon, + penetration: { + near: penetrationRaw[0], + far: penetrationRaw[1], + }, + range: gunShellEntry.maxDistance, + }); + } + + gunIndex++; } turretIndex++; @@ -1076,40 +1056,37 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { for (const crewKey in tankDefinition.root.crew) { const value = tankDefinition.root.crew[crewKey as BlitzCrewType]; + let entry: Crew; + const index = crew.findIndex( + ({ type }) => this.blitzkitCrewTypeToBlitz[type] === crewKey, + ); + if (index === -1) { + if (crewKey === "#text") return; + entry = { + type: this.blitzCrewTypeToBlitzkit[crewKey as BlitzCrewType], + count: 0, + substitute: [], + }; + crew.push(entry); + } else { + entry = crew[index]; + } - { - let entry: Crew; - const index = crew.findIndex( - ({ type }) => this.blitzkitCrewTypeToBlitz[type] === crewKey, - ); - if (index === -1) { - if (crewKey === "#text") return; - entry = { - type: this.blitzCrewTypeToBlitzkit[crewKey as BlitzCrewType], - count: 0, - substitute: [], - }; - crew.push(entry); - } else { - entry = crew[index]; + if (typeof value === "string") { + entry.count++; + + if (value !== "") { + entry.substitute = value.split(/\n| /).map((member) => { + return this.blitzCrewTypeToBlitzkit[ + member.trim() as BlitzCrewType + ]; + }); } - - if (typeof value === "string") { - entry.count++; - - if (value !== "") { - entry.substitute = value.split(/\n| /).map((member) => { - return this.blitzCrewTypeToBlitzkit[ - member.trim() as BlitzCrewType - ]; - }); - } + } else { + if (entry.count === undefined) { + entry.count = value.length; } else { - if (entry.count === undefined) { - entry.count = value.length; - } else { - entry.count += value.length; - } + entry.count += value.length; } } } @@ -1310,6 +1287,15 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { async mapDefinitions() { const mapDefinitions = MapDefinitions.create(); + for (const key in this.mapsYaml!.maps) { + const map = this.mapsYaml!.maps[key]; + + mapDefinitions.maps[map.id] = { + id: map.id, + name: this.getString(`#maps:${key}:${map.localName}`), + }; + } + return mapDefinitions; } @@ -1352,18 +1338,228 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { async consumableDefinitions() { const consumableDefinitions = ConsumableDefinitions.create(); + Object.entries(this.consumablesCommon).forEach(([key, consumable]) => { + const entry: Consumable = { + id: consumable.id, + game_mode_exclusive: "gameModeFilter" in consumable, + cooldown: consumable.script.cooldown, + duration: consumable.script.duration, + name: this.getString(consumable.userString), + exclude: [], + include: [], + }; + consumableDefinitions.consumables[consumable.id] = entry; + + const includeRaw = consumable.vehicleFilter?.include.vehicle; + const excludeRaw = consumable.vehicleFilter?.exclude?.vehicle; + + if (includeRaw) { + entry.include = []; + + if ("minLevel" in includeRaw) { + entry.include.push({ + filter_type: { + $case: "tiers", + value: { + min: includeRaw.minLevel, + max: includeRaw.maxLevel, + }, + }, + }); + } else if ("name" in includeRaw) { + entry.include.push({ + filter_type: { + $case: "ids", + value: { + ids: includeRaw.name.split(/ +/).map((key) => { + return this.tankStringIdMap[key]; + }), + }, + }, + }); + } else throw new SyntaxError("Unhandled include type"); + + if (consumable.vehicleFilter?.include.nations) { + entry.include.push({ + filter_type: { + $case: "nations", + value: { + nations: consumable.vehicleFilter.include.nations.split(" "), + }, + }, + }); + } + } + + if (excludeRaw) { + entry.exclude = []; + + if ("name" in excludeRaw) { + entry.exclude!.push({ + filter_type: { + $case: "ids", + value: { + ids: excludeRaw.name.split(/ +/).map((key) => { + return this.tankStringIdMap[key]; + }), + }, + }, + }); + } else if ("extendedTags" in excludeRaw) { + entry.exclude!.push({ + filter_type: { + $case: "categories", + value: { + categories: excludeRaw.extendedTags + .split(" ") + .map( + (item) => + this.blitzTankFilterDefinitionCategoryToBlitzkit[ + item as BlitzTankFilterDefinitionCategory + ], + ), + }, + }, + }); + } else throw new SyntaxError("Unhandled exclude type"); + + if (consumable.vehicleFilter?.exclude?.nations) { + entry.exclude!.push({ + filter_type: { + $case: "nations", + value: { + nations: consumable.vehicleFilter.exclude.nations.split(" "), + }, + }, + }); + } + } + }); + return consumableDefinitions; } async provisionDefinitions() { const provisionDefinitions = ProvisionDefinitions.create(); + Object.entries(this.provisionsCommon).forEach(([key, provision]) => { + const entry: Provision = { + id: provision.id, + exclude: [], + include: [], + game_mode_exclusive: "gameModeFilter" in provision, + name: this.getString(provision.userString), + }; + provisionDefinitions.provisions[provision.id] = entry; + + const includeRaw = provision.vehicleFilter?.include.vehicle; + const excludeRaw = provision.vehicleFilter?.exclude?.vehicle; + + if (includeRaw) { + entry.include = []; + + if ("minLevel" in includeRaw) { + entry.include.push({ + filter_type: { + $case: "tiers", + value: { + min: includeRaw.minLevel, + max: includeRaw.maxLevel, + }, + }, + }); + } else if ("name" in includeRaw) { + entry.include.push({ + filter_type: { + $case: "ids", + value: { + ids: includeRaw.name.split(/ +/).map((key) => { + return this.tankStringIdMap[key]; + }), + }, + }, + }); + } else throw new SyntaxError("Unhandled include type"); + + if (provision.vehicleFilter?.include.nations) { + entry.include.push({ + filter_type: { + $case: "nations", + value: { + nations: provision.vehicleFilter.include.nations.split(" "), + }, + }, + }); + } + } + + if (excludeRaw) { + entry.exclude = []; + + if ("name" in excludeRaw) { + entry.exclude!.push({ + filter_type: { + $case: "ids", + value: { + ids: excludeRaw.name + .split(/ +/) + .map((key) => this.tankStringIdMap[key]), + }, + }, + }); + } else if ("extendedTags" in excludeRaw) { + entry.exclude!.push({ + filter_type: { + $case: "categories", + value: { + categories: excludeRaw.extendedTags + .split(" ") + .map( + (item) => + this.blitzTankFilterDefinitionCategoryToBlitzkit[ + item as BlitzTankFilterDefinitionCategory + ], + ), + }, + }, + }); + } else throw new SyntaxError("Unhandled exclude type"); + + if (provision.vehicleFilter?.exclude?.nations) { + entry.exclude!.push({ + filter_type: { + $case: "nations", + value: { + nations: provision.vehicleFilter.exclude.nations.split(" "), + }, + }, + }); + } + } + + if (provision.script.bonusValues?.crewLevelIncrease !== undefined) { + provisionDefinitions.provisions[provision.id].crew = + provision.script.bonusValues?.crewLevelIncrease; + } + }); + return provisionDefinitions; } async skillDefinitions() { const skillDefinitions = SkillDefinitions.create(); + for (const tankClass in this.tankmenAvatar!.root.skillsByClasses) { + const skills = + this.tankmenAvatar!.root.skillsByClasses[ + Number(tankClass) as TankClass + ]; + + skillDefinitions.classes[ + this.blitzTankClassToBlitzkit[tankClass as BlitzTankClass] + ] = { skills: skills.split(" ") }; + } + return skillDefinitions; } } diff --git a/packages/core/src/types/blitzTankFilterDefinitionCategory.ts b/packages/core/src/types/blitzTankFilterDefinitionCategory.ts new file mode 100644 index 000000000..99620b0ec --- /dev/null +++ b/packages/core/src/types/blitzTankFilterDefinitionCategory.ts @@ -0,0 +1 @@ +export type BlitzTankFilterDefinitionCategory = "clip"; diff --git a/packages/core/src/types/index.ts b/packages/core/src/types/index.ts index 22bdfb454..a4038c044 100644 --- a/packages/core/src/types/index.ts +++ b/packages/core/src/types/index.ts @@ -1,6 +1,7 @@ export * from "./availableNationsYaml"; export * from "./blitzModuleType"; export * from "./blitzStrings"; +export * from "./blitzTankFilterDefinitionCategory"; export * from "./camouflagesXml"; export * from "./camouflagesYaml"; export * from "./chassisDefinitionsList"; diff --git a/packages/scripts/src/buildAssets/consumableProvisionIcons.ts b/packages/scripts/src/buildAssets/consumableProvisionIcons.ts index a9031bb24..b30fabdc9 100644 --- a/packages/scripts/src/buildAssets/consumableProvisionIcons.ts +++ b/packages/scripts/src/buildAssets/consumableProvisionIcons.ts @@ -1,6 +1,6 @@ import sharp from "sharp"; -import { AssetUploader } from "../core/github/assetUploader"; import { parsePackedSpriteRect } from "../core/blitz/parsePackedSpriteRect"; +import { AssetUploader } from "../core/github/assetUploader"; import { vfs } from "./constants"; import { ConsumablesCommon, ProvisionsCommon } from "./definitions"; diff --git a/packages/scripts/src/buildAssets/definitions.ts b/packages/scripts/src/buildAssets/definitions.ts deleted file mode 100644 index cf18760f4..000000000 --- a/packages/scripts/src/buildAssets/definitions.ts +++ /dev/null @@ -1,335 +0,0 @@ -import { - type BlitzTankClass, - CamouflageDefinitions, - Consumable, - ConsumableDefinitions, - ConsumableTankCategoryFilterCategory, - EquipmentDefinitions, - MapDefinitions, - ModelDefinitions, - Provision, - ProvisionDefinitions, - SkillDefinitions, - TankDefinitions, -} from "@blitzkit/core"; - -type BlitzTankFilterDefinitionCategory = "clip"; -const blitzTankFilterDefinitionCategoryToBlitzkit: Record< - BlitzTankFilterDefinitionCategory, - ConsumableTankCategoryFilterCategory -> = { - clip: ConsumableTankCategoryFilterCategory.CONSUMABLE_TANK_CATEGORY_FILTER_CATEGORY_CLIP, -}; - -export interface BlitzStrings { - [key: string]: string; -} - -export interface ConsumablesCommon { - [key: string]: { - id: number; - userString: string; - description: string; - icon: string; - category: string; - tags: string; - vehicleFilter?: { - include: { vehicle: ConsumablesVehicleFilter; nations?: string }; - exclude?: { vehicle: ConsumablesVehicleFilter; nations?: string }; - }; - script: { - "#text": string; - automatic?: boolean; - cooldown: number; - duration?: number; - shotEffect?: string; - bonusValues?: { [key: string]: number }; - } & Record; - }; -} -export interface ProvisionsCommon { - [key: string]: { - id: number; - userString: string; - description: string; - icon: string; - category: string; - tags: string; - vehicleFilter?: { - include: { vehicle: ConsumablesVehicleFilter; nations?: string }; - exclude?: { vehicle: ConsumablesVehicleFilter; nations?: string }; - }; - script: { - "#text": string; - automatic?: boolean; - shotEffect?: string; - bonusValues?: { [key: string]: number }; - } & Record; - }; -} - -type ConsumablesVehicleFilter = - | { minLevel: number; maxLevel: number } - | { name: string } - | { extendedTags: string }; - -type CombatRolesYaml = Record< - string, - { - id: number; - default_abilities: string[]; - } ->; - -export async function definitions() { - Object.entries(consumablesCommon).forEach(([key, consumable]) => { - const entry: Consumable = { - id: consumable.id, - game_mode_exclusive: "gameModeFilter" in consumable, - cooldown: consumable.script.cooldown, - duration: consumable.script.duration, - name: getString(consumable.userString), - exclude: [], - include: [], - }; - consumableDefinitions.consumables[consumable.id] = entry; - - const includeRaw = consumable.vehicleFilter?.include.vehicle; - const excludeRaw = consumable.vehicleFilter?.exclude?.vehicle; - - if (includeRaw) { - entry.include = []; - - if ("minLevel" in includeRaw) { - entry.include.push({ - filter_type: { - $case: "tiers", - value: { - min: includeRaw.minLevel, - max: includeRaw.maxLevel, - }, - }, - }); - } else if ("name" in includeRaw) { - entry.include.push({ - filter_type: { - $case: "ids", - value: { - ids: includeRaw.name.split(/ +/).map((key) => { - return tankStringIdMap[key]; - }), - }, - }, - }); - } else throw new SyntaxError("Unhandled include type"); - - if (consumable.vehicleFilter?.include.nations) { - entry.include.push({ - filter_type: { - $case: "nations", - value: { - nations: consumable.vehicleFilter.include.nations.split(" "), - }, - }, - }); - } - } - - if (excludeRaw) { - entry.exclude = []; - - if ("name" in excludeRaw) { - entry.exclude!.push({ - filter_type: { - $case: "ids", - value: { - ids: excludeRaw.name.split(/ +/).map((key) => { - return tankStringIdMap[key]; - }), - }, - }, - }); - } else if ("extendedTags" in excludeRaw) { - entry.exclude!.push({ - filter_type: { - $case: "categories", - value: { - categories: excludeRaw.extendedTags - .split(" ") - .map( - (item) => - blitzTankFilterDefinitionCategoryToBlitzkit[ - item as BlitzTankFilterDefinitionCategory - ], - ), - }, - }, - }); - } else throw new SyntaxError("Unhandled exclude type"); - - if (consumable.vehicleFilter?.exclude?.nations) { - entry.exclude!.push({ - filter_type: { - $case: "nations", - value: { - nations: consumable.vehicleFilter.exclude.nations.split(" "), - }, - }, - }); - } - } - }); - - Object.entries(provisionsCommon).forEach(([key, provision]) => { - const entry: Provision = { - id: provision.id, - exclude: [], - include: [], - game_mode_exclusive: "gameModeFilter" in provision, - name: getString(provision.userString), - }; - provisionDefinitions.provisions[provision.id] = entry; - - const includeRaw = provision.vehicleFilter?.include.vehicle; - const excludeRaw = provision.vehicleFilter?.exclude?.vehicle; - - if (includeRaw) { - entry.include = []; - - if ("minLevel" in includeRaw) { - entry.include.push({ - filter_type: { - $case: "tiers", - value: { - min: includeRaw.minLevel, - max: includeRaw.maxLevel, - }, - }, - }); - } else if ("name" in includeRaw) { - entry.include.push({ - filter_type: { - $case: "ids", - value: { - ids: includeRaw.name.split(/ +/).map((key) => { - return tankStringIdMap[key]; - }), - }, - }, - }); - } else throw new SyntaxError("Unhandled include type"); - - if (provision.vehicleFilter?.include.nations) { - entry.include.push({ - filter_type: { - $case: "nations", - value: { - nations: provision.vehicleFilter.include.nations.split(" "), - }, - }, - }); - } - } - - if (excludeRaw) { - entry.exclude = []; - - if ("name" in excludeRaw) { - entry.exclude!.push({ - filter_type: { - $case: "ids", - value: { - ids: excludeRaw.name - .split(/ +/) - .map((key) => tankStringIdMap[key]), - }, - }, - }); - } else if ("extendedTags" in excludeRaw) { - entry.exclude!.push({ - filter_type: { - $case: "categories", - value: { - categories: excludeRaw.extendedTags - .split(" ") - .map( - (item) => - blitzTankFilterDefinitionCategoryToBlitzkit[ - item as BlitzTankFilterDefinitionCategory - ], - ), - }, - }, - }); - } else throw new SyntaxError("Unhandled exclude type"); - - if (provision.vehicleFilter?.exclude?.nations) { - entry.exclude!.push({ - filter_type: { - $case: "nations", - value: { - nations: provision.vehicleFilter.exclude.nations.split(" "), - }, - }, - }); - } - } - - if (provision.script.bonusValues?.crewLevelIncrease !== undefined) { - provisionDefinitions.provisions[provision.id].crew = - provision.script.bonusValues?.crewLevelIncrease; - } - }); - - Object.entries(tankmenAvatar.root.skillsByClasses).forEach( - ([tankClass, skills]) => { - skillDefinitions.classes[ - blitzTankClassToBlitzkit[tankClass as BlitzTankClass] - ] = { - skills: skills.split(" "), - }; - }, - ); - - Object.entries(mapsYaml.maps).forEach(([key, map]) => { - mapDefinitions.maps[map.id] = { - id: map.id, - name: getString(`#maps:${key}:${map.localName}`), - }; - }); - - await uploader.add({ - content: TankDefinitions.encode(tankDefinitions).finish(), - path: "definitions/tanks.pb", - }); - await uploader.add({ - content: ModelDefinitions.encode(modelDefinitions).finish(), - path: "definitions/models.pb", - }); - await uploader.add({ - content: EquipmentDefinitions.encode(equipmentDefinitions).finish(), - path: "definitions/equipment.pb", - }); - await uploader.add({ - content: ConsumableDefinitions.encode(consumableDefinitions).finish(), - path: "definitions/consumables.pb", - }); - await uploader.add({ - content: ProvisionDefinitions.encode(provisionDefinitions).finish(), - path: "definitions/provisions.pb", - }); - await uploader.add({ - content: SkillDefinitions.encode(skillDefinitions).finish(), - path: "definitions/skills.pb", - }); - await uploader.add({ - content: MapDefinitions.encode(mapDefinitions).finish(), - path: "definitions/maps.pb", - }); - await uploader.add({ - content: CamouflageDefinitions.encode(camouflageDefinitions).finish(), - path: "definitions/camouflages.pb", - }); - - await uploader.flush(); -} From 30522bfda049d654b088fd5ef995d17cadb7f33c Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sat, 1 Aug 2026 14:21:11 -0500 Subject: [PATCH 020/147] caching --- packages/core/src/blitzkit/api/server.ts | 70 +++++++++++++++++++++++ packages/website/src/core/blitzkit/api.ts | 5 ++ 2 files changed, 75 insertions(+) diff --git a/packages/core/src/blitzkit/api/server.ts b/packages/core/src/blitzkit/api/server.ts index ed2d7ad7a..bc0e3bdfd 100644 --- a/packages/core/src/blitzkit/api/server.ts +++ b/packages/core/src/blitzkit/api/server.ts @@ -145,6 +145,12 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { } async init() { + console.log("Initializing server; this will take a while..."); + + console.log("Initializing virtual file system..."); + await this.vfs.init(); + + console.log("Fetching game data..."); this.nationsDir = await this.vfs .dir("Data/XML/item_defs/vehicles") .then((files) => files.filter((nation) => nation !== "common")); @@ -181,6 +187,8 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { { delimiter: ";" }, ); + console.log("Fetching game localizations..."); + let fetchedLocalizations = 0; await Promise.all( locales.supported.map(async ({ locale }) => { const blitzLocale = SUPPORTED_LOCALE_BLITZ_MAP[locale]; @@ -197,9 +205,15 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { ...cache, ...preInstalled, }; + + console.log( + `Fetched localizations for ${locale} (${++fetchedLocalizations}/${locales.supported.length})`, + ); }), ); + console.log("Fetching consumable and provision data..."); + for (const match of ( await this.vfs.text( `Data/XML/item_defs/vehicles/common/consumables/list.xml`, @@ -233,6 +247,10 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { ).root, ); } + + console.log("Initialization complete, enjoy :)"); + + return this; } private getString(name: string) { @@ -274,6 +292,7 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { } } + @Cache() async tankDefinitions() { const tankDefinitions = TankDefinitions.create(); @@ -948,6 +967,7 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { return tankDefinitions; } + @Cache() async camouflageDefinitions() { const camouflageDefinitions = CamouflageDefinitions.create(); @@ -996,6 +1016,7 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { } } + @Cache() async modelDefinitions() { const modelDefinitions = ModelDefinitions.create(); @@ -1284,6 +1305,7 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { return modelDefinitions; } + @Cache() async mapDefinitions() { const mapDefinitions = MapDefinitions.create(); @@ -1299,6 +1321,7 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { return mapDefinitions; } + @Cache() async equipmentDefinitions() { const equipmentDefinitions = EquipmentDefinitions.create(); @@ -1335,6 +1358,7 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { return equipmentDefinitions; } + @Cache() async consumableDefinitions() { const consumableDefinitions = ConsumableDefinitions.create(); @@ -1439,6 +1463,7 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { return consumableDefinitions; } + @Cache() async provisionDefinitions() { const provisionDefinitions = ProvisionDefinitions.create(); @@ -1546,6 +1571,7 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { return provisionDefinitions; } + @Cache() async skillDefinitions() { const skillDefinitions = SkillDefinitions.create(); @@ -1563,3 +1589,47 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { return skillDefinitions; } } + +export function Cache(disableInDev = false) { + const cache = new WeakMap>>(); + + return function ( + target: (this: This, ...args: Arguments) => Promise, + // TODO: evaluate if we need context to make the error trace easier + // context: ClassMethodDecoratorContext< + // This, + // (this: This, ...args: Arguments) => Promise + // >, + ) { + return async function replacementMethod(this: This, ...args: Arguments) { + const key = args.join("-"); + let thisCache: Map>; + + if (cache.has(this as object)) { + thisCache = cache.get(this as object) as Map>; + } else { + thisCache = new Map(); + cache.set(this as object, thisCache); + } + + const disabled = disableInDev && import.meta.env.DEV; + + if (thisCache.has(key) && !disabled) { + return thisCache.get(key)!; + } + + const promise = (async () => { + try { + return await target.apply(this, args); + } catch (error) { + thisCache!.delete(key); + throw error; + } + })(); + + thisCache.set(key, promise); + + return promise; + }; + }; +} diff --git a/packages/website/src/core/blitzkit/api.ts b/packages/website/src/core/blitzkit/api.ts index ac76d15c0..d96a3b9e3 100644 --- a/packages/website/src/core/blitzkit/api.ts +++ b/packages/website/src/core/blitzkit/api.ts @@ -1,5 +1,6 @@ import { ClientBlitzKitAPI, + LocalVFS, ServerBlitzKitAPI, type BlitzKitAPI, } from "@blitzkit/core"; @@ -7,6 +8,10 @@ import { let _api: BlitzKitAPI; if (import.meta.env.SSR) { + const vfs = new LocalVFS( + "/run/media/tresabhi/Windows/Program Files/WindowsApps/7458BE2C.WorldofTanksBlitz_11.19.272.0_x64__x4tje2y229k00", + ); + _api = await new ServerBlitzKitAPI(vfs).init(); } else { _api = new ClientBlitzKitAPI(); From 2dc23ba09d14a42e06b2b67974177beab479daee Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sat, 1 Aug 2026 14:32:25 -0500 Subject: [PATCH 021/147] some returns --- packages/core/src/blitzkit/api/base.ts | 22 +++++++++++++++++++- packages/core/src/blitzkit/api/server.ts | 7 ++++--- packages/website/astro.config.ts | 16 ++++++++------ packages/website/src/components/Footer.astro | 2 +- 4 files changed, 36 insertions(+), 11 deletions(-) diff --git a/packages/core/src/blitzkit/api/base.ts b/packages/core/src/blitzkit/api/base.ts index 5b7460efb..49173042d 100644 --- a/packages/core/src/blitzkit/api/base.ts +++ b/packages/core/src/blitzkit/api/base.ts @@ -1,3 +1,23 @@ +import { + CamouflageDefinitions, + ConsumableDefinitions, + EquipmentDefinitions, + GameDefinitions, + MapDefinitions, + ModelDefinitions, + ProvisionDefinitions, + SkillDefinitions, + TankDefinitions, +} from "@blitzkit/core"; + export abstract class BlitzKitAPI { - // abstract game(): Promise; + abstract skillDefinitions(): Promise; + abstract provisionDefinitions(): Promise; + abstract consumableDefinitions(): Promise; + abstract equipmentDefinitions(): Promise; + abstract mapDefinitions(): Promise; + abstract modelDefinitions(): Promise; + abstract camouflageDefinitions(): Promise; + abstract tankDefinitions(): Promise; + abstract gameDefinitions(): Promise; } diff --git a/packages/core/src/blitzkit/api/server.ts b/packages/core/src/blitzkit/api/server.ts index bc0e3bdfd..11b7aa2ed 100644 --- a/packages/core/src/blitzkit/api/server.ts +++ b/packages/core/src/blitzkit/api/server.ts @@ -526,7 +526,7 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { ({ type }) => this.blitzkitCrewTypeToBlitz[type] === crewKey, ); if (index === -1) { - if (crewKey === "#text") return; + if (crewKey === "#text") continue; entry = { type: this.blitzCrewTypeToBlitzkit[crewKey as BlitzCrewType], count: 0, @@ -866,7 +866,7 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { } for (const unlocks of totalUnlocks) { - if (unlocks === undefined) return; + if (unlocks === undefined) continue; for (const key in unlocks) { const value = unlocks[key as keyof BlitzModuleType]; @@ -1082,7 +1082,8 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { ({ type }) => this.blitzkitCrewTypeToBlitz[type] === crewKey, ); if (index === -1) { - if (crewKey === "#text") return; + if (crewKey === "#text") continue; + entry = { type: this.blitzCrewTypeToBlitzkit[crewKey as BlitzCrewType], count: 0, diff --git a/packages/website/astro.config.ts b/packages/website/astro.config.ts index 07c0b288d..89243fbbf 100644 --- a/packages/website/astro.config.ts +++ b/packages/website/astro.config.ts @@ -12,7 +12,7 @@ import { tools } from "./src/constants/tools"; const POSSIBLE_TARGETS = ["app", "web"]; -const target = process.env.PUBLIC_TARGET ; +const target = process.env.PUBLIC_TARGET; if (!target || !POSSIBLE_TARGETS.includes(target)) { throw new Error("Invalid target"); @@ -21,7 +21,11 @@ if (!target || !POSSIBLE_TARGETS.includes(target)) { export default defineConfig({ devToolbar: { enabled: false }, - vite: { server: { allowedHosts: [] } }, + vite: { + server: { allowedHosts: [] }, + esbuild: { target: "es2022" }, + }, + output: target === "web" ? "static" : "server", site: "https://blitzkit.app", outDir: "../../dist/website", @@ -50,15 +54,15 @@ export default defineConfig({ return { name: strings.website.tools[stringId].name, name_localized: wrapper( - (strings) => strings.website.tools[stringId].name + (strings) => strings.website.tools[stringId].name, ), description: strings.website.tools[stringId].description, description_localized: wrapper( - (strings) => strings.website.tools[stringId].description + (strings) => strings.website.tools[stringId].description, ), short_name: strings.website.tools[stringId].name, short_name_localized: wrapper( - (strings) => strings.website.tools[stringId].name + (strings) => strings.website.tools[stringId].name, ), url: `/${tool.id}`, icon: `public/assets/images/tools/${tool.id}.webp`, @@ -68,7 +72,7 @@ export default defineConfig({ manifest: { description: strings.website.home.seo_description, description_localized: wrapper( - (strings) => strings.website.home.seo_description + (strings) => strings.website.home.seo_description, ), dir: "ltr", diff --git a/packages/website/src/components/Footer.astro b/packages/website/src/components/Footer.astro index 7db12917e..ec9e7c750 100644 --- a/packages/website/src/components/Footer.astro +++ b/packages/website/src/components/Footer.astro @@ -11,7 +11,7 @@ export interface Props { locale: string; } -const gameDefinitions = await api.game(); +const gameDefinitions = await api.gameDefinitions(); const PUBLIC_ASSET_BRANCH = assertSecret(import.meta.env.PUBLIC_BRANCH); const { locale } = Astro.props; const strings = getStrings(locale); From 32a7db238b5808580df3eba7910b78db95bbcf0f Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sat, 1 Aug 2026 14:44:23 -0500 Subject: [PATCH 022/147] update (1) --- packages/core/src/blitzkit/api/server.ts | 59 +++++++++++++++++++ packages/core/src/blitzkit/asset.ts | 5 -- .../src/blitzkit/averageDefinitions/index.ts | 16 ++--- packages/core/src/blitzkit/index.ts | 2 +- packages/core/src/blitzkit/staticAsset.ts | 5 ++ .../src/components/TankSearch/index.tsx | 10 ++-- .../src/core/awaitables/gameDefinitions.ts | 3 - .../website/src/core/awaitables/tankNames.ts | 2 +- .../[...locale]/tanks/_index.tsx | 8 +-- .../[...locale]/tanks/index.astro | 4 +- 10 files changed, 81 insertions(+), 33 deletions(-) delete mode 100644 packages/core/src/blitzkit/asset.ts create mode 100644 packages/core/src/blitzkit/staticAsset.ts delete mode 100644 packages/website/src/core/awaitables/gameDefinitions.ts diff --git a/packages/core/src/blitzkit/api/server.ts b/packages/core/src/blitzkit/api/server.ts index 11b7aa2ed..ec274ebdf 100644 --- a/packages/core/src/blitzkit/api/server.ts +++ b/packages/core/src/blitzkit/api/server.ts @@ -2,6 +2,7 @@ import { AbstractVFS, Armor, AssaultRanges, + AvailableNationsYaml, BlitzCrewType, BlitzModuleType, BlitzStrings, @@ -22,6 +23,7 @@ import { Equalizer, EquipmentDefinitions, EquipmentSlot, + GameDefinitions, GunDefinition, GunDefinitionsList, I18nString, @@ -188,6 +190,11 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { ); console.log("Fetching game localizations..."); + + if (import.meta.env.DEV) { + locales.supported = [locales.supported[0]]; + } + let fetchedLocalizations = 0; await Promise.all( locales.supported.map(async ({ locale }) => { @@ -1589,6 +1596,58 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { return skillDefinitions; } + + @Cache() + async gameDefinitions() { + const gameDefinitions = GameDefinitions.create(); + + const consumableNativeNames: Record = {}; + const provisionNativeNames: Record = {}; + + Object.entries(this.consumablesCommon).forEach(([key, consumable]) => { + consumableNativeNames[key] = consumable.id; + }); + + Object.entries(this.provisionsCommon).forEach(([key, provision]) => { + provisionNativeNames[key] = provision.id; + }); + + const version = (await this.vfs.text("Data/version.txt")).split(" ")[0]; + + gameDefinitions.version = version; + gameDefinitions.nations = ( + await this.vfs.yaml("Data/available_nations.yaml") + ).available_nations; + + for (const match of this.squadBattleTypeStyles!.Prototypes[0].components.UIDataLocalBindingsComponent.data[1][2].matchAll( + /"(\d+)" -> "(battleType\/([a-zA-Z]+))"/g, + )) { + const id = Number(match[1]); + const name = this.getString(match[2]); + + gameDefinitions.gameModes[id] = { + name, + }; + } + + Object.entries(this.combatRoles!).forEach(([, value]) => { + gameDefinitions.roles[value.id] = { provisions: [], consumables: [] }; + + value.default_abilities.forEach((ability) => { + if (ability in consumableNativeNames) { + gameDefinitions.roles[value.id].consumables.push( + consumableNativeNames[ability], + ); + } else if (ability in provisionNativeNames) { + gameDefinitions.roles[value.id].provisions.push( + provisionNativeNames[ability], + ); + } else throw new Error(`Unknown ability ${ability}`); + }); + }); + + return gameDefinitions; + } } export function Cache(disableInDev = false) { diff --git a/packages/core/src/blitzkit/asset.ts b/packages/core/src/blitzkit/asset.ts deleted file mode 100644 index adfd8d5db..000000000 --- a/packages/core/src/blitzkit/asset.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { assertSecret } from "./assertSecret"; - -export function asset(path: string) { - return `${assertSecret(import.meta.env.PUBLIC_ASSET_BASE)}/${path}`; -} diff --git a/packages/core/src/blitzkit/averageDefinitions/index.ts b/packages/core/src/blitzkit/averageDefinitions/index.ts index 61ddb2c57..188e289d8 100644 --- a/packages/core/src/blitzkit/averageDefinitions/index.ts +++ b/packages/core/src/blitzkit/averageDefinitions/index.ts @@ -1,6 +1,6 @@ -import { fetchPB } from '../../protobuf'; -import { AverageDefinitions } from '../../protos'; -import { asset } from '../asset'; +import { staticAsset } from "@blitzkit/core"; +import { fetchPB } from "../../protobuf"; +import { AverageDefinitions } from "../../protos"; export interface AverageDefinitionsManifest { version: 1; @@ -11,13 +11,7 @@ export interface AverageDefinitionsManifest { } export async function fetchAverageDefinitions() { - const manifestResponse = await fetch(asset('averages/manifest.json')); - const manifestJson = - (await manifestResponse.json()) as AverageDefinitionsManifest; - return await fetchPB( - asset(`averages/${manifestJson.latest}.pb`), - AverageDefinitions, - ); + return await fetchPB(staticAsset("averages.pb"), AverageDefinitions); } -export * from './constants'; +export * from "./constants"; diff --git a/packages/core/src/blitzkit/index.ts b/packages/core/src/blitzkit/index.ts index 935f5e654..13dadad9c 100644 --- a/packages/core/src/blitzkit/index.ts +++ b/packages/core/src/blitzkit/index.ts @@ -1,5 +1,4 @@ export * from "./assertSecret"; -export * from "./asset"; export * from "./availableProvisions"; export * from "./averageDefinitions"; // export * from './camouflageDefinitions'; @@ -16,6 +15,7 @@ export * from "./equipmentDefinitions"; export * from "./eventManager"; export * from "./getArchivedLatestSeasonNumber"; export * from "./sluggify"; +export * from "./staticAsset"; export * from "./vfs"; // export * from './getArchivedRatingInfo'; export * from "./getArchivedRatingLeaderboard"; diff --git a/packages/core/src/blitzkit/staticAsset.ts b/packages/core/src/blitzkit/staticAsset.ts new file mode 100644 index 000000000..3843f3338 --- /dev/null +++ b/packages/core/src/blitzkit/staticAsset.ts @@ -0,0 +1,5 @@ +import { assertSecret } from "./assertSecret"; + +export function staticAsset(path: string) { + return `${assertSecret(import.meta.env.PUBLIC_STATIC_ASSET_BASE)}/${path}`; +} diff --git a/packages/website/src/components/TankSearch/index.tsx b/packages/website/src/components/TankSearch/index.tsx index 6a6513145..35321dc2c 100644 --- a/packages/website/src/components/TankSearch/index.tsx +++ b/packages/website/src/components/TankSearch/index.tsx @@ -12,10 +12,8 @@ import fuzzysort from "fuzzysort"; import { times, uniq } from "lodash-es"; import { memo, useEffect, useMemo, useState } from "react"; import usePromise from "react-promise-suspense"; -import { awaitableGameDefinitions } from "../../core/awaitables/gameDefinitions"; -import { awaitableModelDefinitions } from "../../core/awaitables/modelDefinitions"; -import { awaitableTankDefinitions } from "../../core/awaitables/tankDefinitions"; import { awaitableTankNames } from "../../core/awaitables/tankNames"; +import { api } from "../../core/blitzkit/api"; import { filterTanks } from "../../core/blitzkit/filterTanks"; import { resolveReload } from "../../core/blitzkit/resolveReload"; import { useLocale } from "../../hooks/useLocale"; @@ -47,9 +45,9 @@ const DEFAULT_LOADED_CARDS = 64; const [gameDefinitions, modelDefinitions, tankDefinitions, tankNames] = await Promise.all([ - awaitableGameDefinitions, - awaitableModelDefinitions, - awaitableTankDefinitions, + api.gameDefinitions(), + api.modelDefinitions(), + api.tankDefinitions(), awaitableTankNames, ]); diff --git a/packages/website/src/core/awaitables/gameDefinitions.ts b/packages/website/src/core/awaitables/gameDefinitions.ts deleted file mode 100644 index 84db30fce..000000000 --- a/packages/website/src/core/awaitables/gameDefinitions.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { fetchGameDefinitions } from '@blitzkit/core'; - -export const awaitableGameDefinitions = fetchGameDefinitions(); diff --git a/packages/website/src/core/awaitables/tankNames.ts b/packages/website/src/core/awaitables/tankNames.ts index d043c9705..383392e72 100644 --- a/packages/website/src/core/awaitables/tankNames.ts +++ b/packages/website/src/core/awaitables/tankNames.ts @@ -1,3 +1,3 @@ -import { fetchTankNames } from '@blitzkit/core'; +import { fetchTankNames } from "@blitzkit/core"; export const awaitableTankNames = fetchTankNames(); diff --git a/packages/website/src/pages/[...chunk_pages]/[...locale]/tanks/_index.tsx b/packages/website/src/pages/[...chunk_pages]/[...locale]/tanks/_index.tsx index f5b22a310..f99dbf649 100644 --- a/packages/website/src/pages/[...chunk_pages]/[...locale]/tanks/_index.tsx +++ b/packages/website/src/pages/[...chunk_pages]/[...locale]/tanks/_index.tsx @@ -1,10 +1,10 @@ -import { PageWrapper } from "../../../components/PageWrapper"; -import { TankSearch } from "../../../components/TankSearch"; +import { PageWrapper } from "../../../../components/PageWrapper"; +import { TankSearch } from "../../../../components/TankSearch"; import { LocaleProvider, type LocaleAcceptorProps, -} from "../../../hooks/useLocale"; -import type { MaybeSkeletonComponentProps } from "../../../types/maybeSkeletonComponentProps"; +} from "../../../../hooks/useLocale"; +import type { MaybeSkeletonComponentProps } from "../../../../types/maybeSkeletonComponentProps"; export function Page({ locale, diff --git a/packages/website/src/pages/[...chunk_pages]/[...locale]/tanks/index.astro b/packages/website/src/pages/[...chunk_pages]/[...locale]/tanks/index.astro index b4dcbd295..e2d900c65 100644 --- a/packages/website/src/pages/[...chunk_pages]/[...locale]/tanks/index.astro +++ b/packages/website/src/pages/[...chunk_pages]/[...locale]/tanks/index.astro @@ -1,6 +1,6 @@ --- -import { tools } from "../../../constants/tools"; -import ToolLayout from "../../../layouts/ToolLayout.astro"; +import { tools } from "../../../../constants/tools"; +import ToolLayout from "../../../../layouts/ToolLayout.astro"; import { Page } from "./_index"; export { getStaticPaths } from "../index.astro"; From d41d45abaef161550cbe2b6ac35da15568570049 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sat, 1 Aug 2026 14:48:18 -0500 Subject: [PATCH 023/147] update (2) --- .../src/blitzkit/consumableDefinitions.ts | 7 ---- .../src/blitzkit/discoveredIdDefinitions.ts | 4 +-- .../core/src/blitzkit/equipmentDefinitions.ts | 7 ---- packages/core/src/blitzkit/gallery.ts | 7 ---- .../blitzkit/getArchivedRatingLeaderboard.ts | 23 ------------- packages/core/src/blitzkit/index.ts | 34 +++++-------------- packages/core/src/blitzkit/mapDefinitions.ts | 7 ---- .../core/src/blitzkit/modelDefinitions.ts | 7 ---- packages/core/src/blitzkit/popularTanks.ts | 8 ++--- .../core/src/blitzkit/provisionDefinitions.ts | 7 ---- packages/core/src/blitzkit/reviews.ts | 8 ++--- .../core/src/blitzkit/skillDefinitions.ts | 7 ---- .../src/blitzkit/tankDefinitions/index.ts | 9 ----- packages/core/src/blitzkit/tankIcon.ts | 4 +-- 14 files changed, 19 insertions(+), 120 deletions(-) delete mode 100644 packages/core/src/blitzkit/consumableDefinitions.ts delete mode 100644 packages/core/src/blitzkit/equipmentDefinitions.ts delete mode 100644 packages/core/src/blitzkit/gallery.ts delete mode 100644 packages/core/src/blitzkit/getArchivedRatingLeaderboard.ts delete mode 100644 packages/core/src/blitzkit/mapDefinitions.ts delete mode 100644 packages/core/src/blitzkit/modelDefinitions.ts delete mode 100644 packages/core/src/blitzkit/provisionDefinitions.ts delete mode 100644 packages/core/src/blitzkit/skillDefinitions.ts delete mode 100644 packages/core/src/blitzkit/tankDefinitions/index.ts diff --git a/packages/core/src/blitzkit/consumableDefinitions.ts b/packages/core/src/blitzkit/consumableDefinitions.ts deleted file mode 100644 index 077e2e3b3..000000000 --- a/packages/core/src/blitzkit/consumableDefinitions.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { fetchPB } from '../protobuf'; -import { ConsumableDefinitions } from '../protos'; -import { asset } from './asset'; - -export function fetchConsumableDefinitions() { - return fetchPB(asset('definitions/consumables.pb'), ConsumableDefinitions); -} diff --git a/packages/core/src/blitzkit/discoveredIdDefinitions.ts b/packages/core/src/blitzkit/discoveredIdDefinitions.ts index c6830554c..2354afc9f 100644 --- a/packages/core/src/blitzkit/discoveredIdDefinitions.ts +++ b/packages/core/src/blitzkit/discoveredIdDefinitions.ts @@ -1,4 +1,4 @@ -import { asset } from './asset'; +import { staticAsset } from "@blitzkit/core"; export interface DiscoveredIdsDefinitions { time: number; @@ -7,6 +7,6 @@ export interface DiscoveredIdsDefinitions { } export async function fetchDiscoveredIdsDefinitions() { - const response = await fetch(asset('ids/manifest.json')); + const response = await fetch(staticAsset("ids/manifest.json")); return response.json() as Promise; } diff --git a/packages/core/src/blitzkit/equipmentDefinitions.ts b/packages/core/src/blitzkit/equipmentDefinitions.ts deleted file mode 100644 index a42f5bced..000000000 --- a/packages/core/src/blitzkit/equipmentDefinitions.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { fetchPB } from '../protobuf'; -import { EquipmentDefinitions } from '../protos'; -import { asset } from './asset'; - -export function fetchEquipmentDefinitions() { - return fetchPB(asset('definitions/equipment.pb'), EquipmentDefinitions); -} diff --git a/packages/core/src/blitzkit/gallery.ts b/packages/core/src/blitzkit/gallery.ts deleted file mode 100644 index 18ddc672b..000000000 --- a/packages/core/src/blitzkit/gallery.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { fetchPB } from '../protobuf'; -import { Gallery } from '../protos'; -import { asset } from './asset'; - -export function fetchGallery() { - return fetchPB(asset('definitions/gallery.pb'), Gallery); -} diff --git a/packages/core/src/blitzkit/getArchivedRatingLeaderboard.ts b/packages/core/src/blitzkit/getArchivedRatingLeaderboard.ts deleted file mode 100644 index 7943199bb..000000000 --- a/packages/core/src/blitzkit/getArchivedRatingLeaderboard.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { Region } from "../blitz/regions"; -import { RatingLeaderboard } from "../protos"; -import { asset } from "./asset"; - -const cache: Record> = { - asia: {}, - eu: {}, - com: {}, -}; - -export async function getArchivedRatingLeaderboard( - region: Region, - season: number -) { - if (!cache[region][season]) { - const info = await fetch(asset(`regions/${region}/rating/${season}.pb`)) - .then((response) => response.arrayBuffer()) - .then((buffer) => RatingLeaderboard.decode(new Uint8Array(buffer))); - cache[region][season] = info; - } - - return cache[region][season]; -} diff --git a/packages/core/src/blitzkit/index.ts b/packages/core/src/blitzkit/index.ts index 13dadad9c..184e360b5 100644 --- a/packages/core/src/blitzkit/index.ts +++ b/packages/core/src/blitzkit/index.ts @@ -1,51 +1,33 @@ +export * from "./api"; export * from "./assertSecret"; export * from "./availableProvisions"; export * from "./averageDefinitions"; -// export * from './camouflageDefinitions'; -// export * from './checkConsumableProvisionInclusivity'; export * from "./coefficient"; -export * from "./consumableDefinitions"; -// export * from './context'; -export * from "./api"; +export * from "./createDefaultProvisions"; export * from "./createDefaultSkills"; export * from "./degressiveStat"; export * from "./discoveredIdDefinitions"; export * from "./emblemURL"; -export * from "./equipmentDefinitions"; export * from "./eventManager"; -export * from "./getArchivedLatestSeasonNumber"; -export * from "./sluggify"; -export * from "./staticAsset"; -export * from "./vfs"; -// export * from './getArchivedRatingInfo'; -export * from "./getArchivedRatingLeaderboard"; -// export * from './getArchivedRatingMidnightLeaderboard'; export * from "./getTimeDaysAgo"; export * from "./isExplosive"; -export * from "./mapDefinitions"; -export * from "./modelDefinitions"; +export * from "./metaSortTank"; export * from "./normalizeBoundingBox"; -// export * from './parseBkni'; -// export * from './patientFetch'; export * from "./patientFetchJSON"; +export * from "./popularTanks"; export * from "./progressiveStat"; -export * from "./provisionDefinitions"; export * from "./resolveDpm"; export * from "./resolvePenetrationCoefficient"; export * from "./reviews"; -export * from "./skillDefinitions"; +export * from "./romanize"; +export * from "./sluggify"; +export * from "./staticAsset"; export * from "./sum"; -export * from "./tankDefinitions"; export * from "./tankIcon"; export * from "./tankNames"; export * from "./unionBoundingBox"; export * from "./uniqueGuns"; export * from "./unwrapBlitzkitResponse"; +export * from "./vfs"; export * from "./votes"; -// export * from './withCORSProxy'; -export * from "./createDefaultProvisions"; -export * from "./gallery"; -export * from "./metaSortTank"; -export * from "./popularTanks"; -export * from "./romanize"; export * from "./youtubers"; diff --git a/packages/core/src/blitzkit/mapDefinitions.ts b/packages/core/src/blitzkit/mapDefinitions.ts deleted file mode 100644 index 50bf8525a..000000000 --- a/packages/core/src/blitzkit/mapDefinitions.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { fetchPB } from '../protobuf'; -import { MapDefinitions } from '../protos'; -import { asset } from './asset'; - -export function fetchMapDefinitions() { - return fetchPB(asset('definitions/maps.pb'), MapDefinitions); -} diff --git a/packages/core/src/blitzkit/modelDefinitions.ts b/packages/core/src/blitzkit/modelDefinitions.ts deleted file mode 100644 index 59ca764e5..000000000 --- a/packages/core/src/blitzkit/modelDefinitions.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { fetchPB } from '../protobuf'; -import { ModelDefinitions } from '../protos'; -import { asset } from './asset'; - -export function fetchModelDefinitions() { - return fetchPB(asset('definitions/models.pb'), ModelDefinitions); -} diff --git a/packages/core/src/blitzkit/popularTanks.ts b/packages/core/src/blitzkit/popularTanks.ts index 92d6e372b..7f7e4a5b1 100644 --- a/packages/core/src/blitzkit/popularTanks.ts +++ b/packages/core/src/blitzkit/popularTanks.ts @@ -1,7 +1,7 @@ -import { fetchPB } from '../protobuf'; -import { PopularTanks } from '../protos'; -import { asset } from './asset'; +import { staticAsset } from "@blitzkit/core"; +import { fetchPB } from "../protobuf"; +import { PopularTanks } from "../protos"; export function fetchPopularTanks() { - return fetchPB(asset('definitions/popular-tanks.pb'), PopularTanks); + return fetchPB(staticAsset("definitions/popular-tanks.pb"), PopularTanks); } diff --git a/packages/core/src/blitzkit/provisionDefinitions.ts b/packages/core/src/blitzkit/provisionDefinitions.ts deleted file mode 100644 index 6c85d2d5b..000000000 --- a/packages/core/src/blitzkit/provisionDefinitions.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { fetchPB } from '../protobuf'; -import { ProvisionDefinitions } from '../protos'; -import { asset } from './asset'; - -export function fetchProvisionDefinitions() { - return fetchPB(asset('definitions/provisions.pb'), ProvisionDefinitions); -} diff --git a/packages/core/src/blitzkit/reviews.ts b/packages/core/src/blitzkit/reviews.ts index 40922fca4..29a2377d5 100644 --- a/packages/core/src/blitzkit/reviews.ts +++ b/packages/core/src/blitzkit/reviews.ts @@ -1,7 +1,7 @@ -import { fetchPB } from '../protobuf'; -import { Reviews } from '../protos'; -import { asset } from './asset'; +import { staticAsset } from "@blitzkit/core"; +import { fetchPB } from "../protobuf"; +import { Reviews } from "../protos"; export function fetchReviews() { - return fetchPB(asset('definitions/reviews.pb'), Reviews); + return fetchPB(staticAsset("definitions/reviews.pb"), Reviews); } diff --git a/packages/core/src/blitzkit/skillDefinitions.ts b/packages/core/src/blitzkit/skillDefinitions.ts deleted file mode 100644 index a2bebd8f0..000000000 --- a/packages/core/src/blitzkit/skillDefinitions.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { fetchPB } from '../protobuf'; -import { SkillDefinitions } from '../protos'; -import { asset } from './asset'; - -export function fetchSkillDefinitions() { - return fetchPB(asset('definitions/skills.pb'), SkillDefinitions); -} diff --git a/packages/core/src/blitzkit/tankDefinitions/index.ts b/packages/core/src/blitzkit/tankDefinitions/index.ts deleted file mode 100644 index 18bdfcd2a..000000000 --- a/packages/core/src/blitzkit/tankDefinitions/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { fetchPB } from '../../protobuf'; -import { TankDefinitions } from '../../protos'; -import { asset } from '../asset'; - -export function fetchTankDefinitions() { - return fetchPB(asset('definitions/tanks.pb'), TankDefinitions); -} - -export * from './constants'; diff --git a/packages/core/src/blitzkit/tankIcon.ts b/packages/core/src/blitzkit/tankIcon.ts index 546ef5f8f..6fef51e05 100644 --- a/packages/core/src/blitzkit/tankIcon.ts +++ b/packages/core/src/blitzkit/tankIcon.ts @@ -1,7 +1,5 @@ -import { asset } from "./asset"; - export const BLITZKIT_TANK_ICON_SIZE = { width: 40 * 16, height: 30 * 16 }; export function tankIcon(id: number, size: "big" | "small" = "big") { - return asset(`icons/tanks/${size}/${id}.webp`); + return `/api/tanks/${id}/icons/${size}.webp`; } From ed92440b94d10fc83cb3664f358cf55176168928 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sat, 1 Aug 2026 14:54:40 -0500 Subject: [PATCH 024/147] update (3) --- .../src/blitzkit/camouflageDefinitions.ts | 7 --- packages/core/src/blitzkit/index.ts | 1 + .../src/blitzkit/tankDefinitions/index.ts | 1 + packages/core/src/blitzkit/tankNames.ts | 9 ++-- .../website/src/components/TankCard/index.tsx | 10 ++-- .../components/TankSearch/components/Card.tsx | 4 +- .../TankSearch/components/FilterControl.tsx | 54 ++++++++----------- .../core/awaitables/consumableDefinitions.ts | 3 -- .../src/core/awaitables/modelDefinitions.ts | 3 -- .../core/awaitables/provisionDefinitions.ts | 3 -- .../src/core/awaitables/tankDefinitions.ts | 3 -- .../website/src/core/awaitables/tankNames.ts | 3 +- .../website/src/core/blitzkit/filterTank.ts | 48 ++++++++--------- .../[...locale]/performance/index.astro | 6 +-- 14 files changed, 61 insertions(+), 94 deletions(-) delete mode 100644 packages/core/src/blitzkit/camouflageDefinitions.ts create mode 100644 packages/core/src/blitzkit/tankDefinitions/index.ts delete mode 100644 packages/website/src/core/awaitables/consumableDefinitions.ts delete mode 100644 packages/website/src/core/awaitables/modelDefinitions.ts delete mode 100644 packages/website/src/core/awaitables/provisionDefinitions.ts delete mode 100644 packages/website/src/core/awaitables/tankDefinitions.ts diff --git a/packages/core/src/blitzkit/camouflageDefinitions.ts b/packages/core/src/blitzkit/camouflageDefinitions.ts deleted file mode 100644 index 20315860c..000000000 --- a/packages/core/src/blitzkit/camouflageDefinitions.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { fetchPB } from '../protobuf'; -import { CamouflageDefinitions } from '../protos'; -import { asset } from './asset'; - -export function fetchCamouflageDefinitions() { - return fetchPB(asset('definitions/camouflages.pb'), CamouflageDefinitions); -} diff --git a/packages/core/src/blitzkit/index.ts b/packages/core/src/blitzkit/index.ts index 184e360b5..88ba099ef 100644 --- a/packages/core/src/blitzkit/index.ts +++ b/packages/core/src/blitzkit/index.ts @@ -23,6 +23,7 @@ export * from "./romanize"; export * from "./sluggify"; export * from "./staticAsset"; export * from "./sum"; +export * from "./tankDefinitions"; export * from "./tankIcon"; export * from "./tankNames"; export * from "./unionBoundingBox"; diff --git a/packages/core/src/blitzkit/tankDefinitions/index.ts b/packages/core/src/blitzkit/tankDefinitions/index.ts new file mode 100644 index 000000000..b04bfcf75 --- /dev/null +++ b/packages/core/src/blitzkit/tankDefinitions/index.ts @@ -0,0 +1 @@ +export * from "./constants"; diff --git a/packages/core/src/blitzkit/tankNames.ts b/packages/core/src/blitzkit/tankNames.ts index d778ed33e..bb19c9597 100644 --- a/packages/core/src/blitzkit/tankNames.ts +++ b/packages/core/src/blitzkit/tankNames.ts @@ -1,13 +1,12 @@ +import { BlitzKitAPI } from "@blitzkit/core"; import locales from "@blitzkit/i18n/locales.json"; import { deburr } from "lodash-es"; import { I18nString } from "../protos"; -import { fetchCamouflageDefinitions } from "./camouflageDefinitions"; -import { fetchTankDefinitions } from "./tankDefinitions"; -export async function fetchTankNames() { +export async function fetchTankNames(api: BlitzKitAPI) { const [tankDefinitions, camouflageDefinitions] = await Promise.all([ - fetchTankDefinitions(), - fetchCamouflageDefinitions(), + api.tankDefinitions(), + api.camouflageDefinitions(), ]); const tankDefinitionsArray = Object.values(tankDefinitions.tanks); diff --git a/packages/website/src/components/TankCard/index.tsx b/packages/website/src/components/TankCard/index.tsx index 64eece530..6d3cfa96d 100644 --- a/packages/website/src/components/TankCard/index.tsx +++ b/packages/website/src/components/TankCard/index.tsx @@ -1,12 +1,8 @@ -import { - asset, - fetchTankDefinitions, - TankType, - type TankDefinition, -} from "@blitzkit/core"; +import { asset, TankType, type TankDefinition } from "@blitzkit/core"; import { Flex, Text, type TextProps } from "@radix-ui/themes"; import { uniq } from "lodash-es"; import { forwardRef, type ReactNode } from "react"; +import { api } from "../../core/blitzkit/api"; import { useLocale } from "../../hooks/useLocale"; import { TankopediaPersistent } from "../../stores/tankopediaPersistent"; import { classIcons } from "../ClassIcon"; @@ -21,7 +17,7 @@ type TankCardProps = TextProps & { noLink?: boolean; }; -const tankDefinitions = await fetchTankDefinitions(); +const tankDefinitions = await api.tankDefinitions(); export const TankCard = forwardRef( ( diff --git a/packages/website/src/components/TankSearch/components/Card.tsx b/packages/website/src/components/TankSearch/components/Card.tsx index 8eb75f918..97182487a 100644 --- a/packages/website/src/components/TankSearch/components/Card.tsx +++ b/packages/website/src/components/TankSearch/components/Card.tsx @@ -5,7 +5,7 @@ import { type TankDefinition, } from "@blitzkit/core"; import { useMemo } from "react"; -import { awaitableModelDefinitions } from "../../../core/awaitables/modelDefinitions"; +import { api } from "../../../core/blitzkit/api"; import { resolveReload } from "../../../core/blitzkit/resolveReload"; import { TankSort } from "../../../stores/tankopediaSort"; import { TankCard } from "../../TankCard"; @@ -15,7 +15,7 @@ interface TankSearchCardProps { onSelect?: (tank: TankDefinition) => void; } -const modelDefinitions = await awaitableModelDefinitions; +const modelDefinitions = await api.modelDefinitions(); export function TankSearchCard({ tank, onSelect }: TankSearchCardProps) { const by = TankSort.use((state) => state.by); diff --git a/packages/website/src/components/TankSearch/components/FilterControl.tsx b/packages/website/src/components/TankSearch/components/FilterControl.tsx index 645e8f73c..1451bda88 100644 --- a/packages/website/src/components/TankSearch/components/FilterControl.tsx +++ b/packages/website/src/components/TankSearch/components/FilterControl.tsx @@ -1,5 +1,4 @@ import { - asset, GunDefinition, ShellType, TANK_CLASSES, @@ -27,10 +26,7 @@ import { } from "@radix-ui/themes"; import { times } from "lodash-es"; import { Fragment, type ComponentProps, type ReactNode } from "react"; -import { awaitableConsumableDefinitions } from "../../../core/awaitables/consumableDefinitions"; -import { awaitableGameDefinitions } from "../../../core/awaitables/gameDefinitions"; -import { awaitableProvisionDefinitions } from "../../../core/awaitables/provisionDefinitions"; -import { awaitableTankDefinitions } from "../../../core/awaitables/tankDefinitions"; +import { api } from "../../../core/blitzkit/api"; import { useLocale } from "../../../hooks/useLocale"; import { App } from "../../../stores/app"; import { TankFilters, type CaseType } from "../../../stores/tankFilters"; @@ -43,10 +39,10 @@ import { ResearchedIcon } from "../../ResearchedIcon"; import { ScienceIcon } from "../../ScienceIcon"; import { ScienceOffIcon } from "../../ScienceOffIcon"; -const gameDefinitions = await awaitableGameDefinitions; -const consumableDefinitions = await awaitableConsumableDefinitions; -const provisionDefinitions = await awaitableProvisionDefinitions; -const tankDefinitions = await awaitableTankDefinitions; +const gameDefinitions = await api.gameDefinitions(); +const consumableDefinitions = await api.consumableDefinitions(); +const provisionDefinitions = await api.provisionDefinitions(); +const tankDefinitions = await api.tankDefinitions(); const gameModeRoleSets: Record> = {}; @@ -313,7 +309,7 @@ function NationsFilter() { width: "1.25em", height: "1.25em", }} - src={asset(`flags/circle/${nation}.webp`)} + src={`/api/flags/circle/${nation}.webp`} /> ))} @@ -350,9 +346,7 @@ function NationsFilter() { right="0" mr="-2" style={{ - backgroundImage: `url(${asset( - `flags/scratched/${nation}.webp`, - )})`, + backgroundImage: `url(${`/api/flags/scratched/${nation}.webp`})`, backgroundSize: "100%", backgroundRepeat: "no-repeat", backgroundPosition: "5rem center", @@ -685,11 +679,9 @@ function IndividualShellFilter({ {shells[index] !== null && ( )} @@ -720,11 +712,9 @@ function IndividualShellFilter({ color={selected ? undefined : "gray"} > @@ -893,7 +883,7 @@ function ConsumablesFilter() { height: "1.25em", objectFit: "contain", }} - src={asset(`icons/consumables/${consumable}.webp`)} + src={`/api/icons/consumables/${consumable}.webp`} /> ))} @@ -938,7 +928,7 @@ function ConsumablesFilter() { height: "1.25em", objectFit: "contain", }} - src={asset(`icons/consumables/${consumable}.webp`)} + src={`/api/icons/consumables/${consumable}.webp`} /> {unwrap(consumableDefinition.name!)} @@ -987,7 +977,7 @@ function ProvisionsFilter() { height: "1.25em", objectFit: "contain", }} - src={asset(`icons/provisions/${provision}.webp`)} + src={`/api/icons/provisions/${provision}.webp`} /> ))} @@ -1032,7 +1022,7 @@ function ProvisionsFilter() { height: "1.25em", objectFit: "contain", }} - src={asset(`icons/provisions/${provision}.webp`)} + src={`/api/icons/provisions/${provision}.webp`} /> {unwrap(provisionDefinition.name!)} @@ -1075,8 +1065,8 @@ function GameModeAbilitiesFilter() { : rawPowers; const icons = [ - ...abilities.map((ability) => `icons/consumables/${ability}.webp`), - ...powers.map((power) => `icons/provisions/${power}.webp`), + ...abilities.map((ability) => `api/icons/consumables/${ability}.webp`), + ...powers.map((power) => `api/icons/provisions/${power}.webp`), ]; return ( @@ -1094,7 +1084,7 @@ function GameModeAbilitiesFilter() { height: "1.25em", objectFit: "contain", }} - src={asset(icon)} + src={icon} /> ))} @@ -1161,7 +1151,7 @@ function GameModeAbilitiesFilter() { height: "1.25em", objectFit: "contain", }} - src={asset(`icons/consumables/${consumable}.webp`)} + src={`/api/icons/consumables/${consumable}.webp`} /> {unwrap(abilityDefinition.name!)} @@ -1210,7 +1200,7 @@ function GameModeAbilitiesFilter() { height: "1.25em", objectFit: "contain", }} - src={asset(`icons/provisions/${provision}.webp`)} + src={`/api/icons/provisions/${provision}.webp`} /> {unwrap(powerDefinition.name!)} diff --git a/packages/website/src/core/awaitables/consumableDefinitions.ts b/packages/website/src/core/awaitables/consumableDefinitions.ts deleted file mode 100644 index be0b8efb0..000000000 --- a/packages/website/src/core/awaitables/consumableDefinitions.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { fetchConsumableDefinitions } from '@blitzkit/core'; - -export const awaitableConsumableDefinitions = fetchConsumableDefinitions(); diff --git a/packages/website/src/core/awaitables/modelDefinitions.ts b/packages/website/src/core/awaitables/modelDefinitions.ts deleted file mode 100644 index 6f1cc2fd0..000000000 --- a/packages/website/src/core/awaitables/modelDefinitions.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { fetchModelDefinitions } from '@blitzkit/core'; - -export const awaitableModelDefinitions = fetchModelDefinitions(); diff --git a/packages/website/src/core/awaitables/provisionDefinitions.ts b/packages/website/src/core/awaitables/provisionDefinitions.ts deleted file mode 100644 index 1f21a1459..000000000 --- a/packages/website/src/core/awaitables/provisionDefinitions.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { fetchProvisionDefinitions } from '@blitzkit/core'; - -export const awaitableProvisionDefinitions = fetchProvisionDefinitions(); diff --git a/packages/website/src/core/awaitables/tankDefinitions.ts b/packages/website/src/core/awaitables/tankDefinitions.ts deleted file mode 100644 index fe76877f3..000000000 --- a/packages/website/src/core/awaitables/tankDefinitions.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { fetchTankDefinitions } from '@blitzkit/core'; - -export const awaitableTankDefinitions = fetchTankDefinitions(); diff --git a/packages/website/src/core/awaitables/tankNames.ts b/packages/website/src/core/awaitables/tankNames.ts index 383392e72..846ff8cbd 100644 --- a/packages/website/src/core/awaitables/tankNames.ts +++ b/packages/website/src/core/awaitables/tankNames.ts @@ -1,3 +1,4 @@ import { fetchTankNames } from "@blitzkit/core"; +import { api } from "../blitzkit/api"; -export const awaitableTankNames = fetchTankNames(); +export const awaitableTankNames = fetchTankNames(api); diff --git a/packages/website/src/core/blitzkit/filterTank.ts b/packages/website/src/core/blitzkit/filterTank.ts index 02bea5262..91c808b56 100644 --- a/packages/website/src/core/blitzkit/filterTank.ts +++ b/packages/website/src/core/blitzkit/filterTank.ts @@ -1,21 +1,19 @@ import type { TankDefinition } from "@blitzkit/core"; import { checkConsumableProvisionInclusivity } from "@blitzkit/core/src/blitzkit/checkConsumableProvisionInclusivity"; import { times } from "lodash-es"; -import { awaitableConsumableDefinitions } from "../../core/awaitables/consumableDefinitions"; import type { TankFilters } from "../../stores/tankFilters"; -import { awaitableGameDefinitions } from "../awaitables/gameDefinitions"; -import { awaitableProvisionDefinitions } from "../awaitables/provisionDefinitions"; +import { api } from "./api"; const SHELLS = times(3, (index) => index); -const consumableDefinitions = await awaitableConsumableDefinitions; -const provisionDefinitions = await awaitableProvisionDefinitions; -const gameDefinitions = await awaitableGameDefinitions; +const consumableDefinitions = await api.consumableDefinitions(); +const provisionDefinitions = await api.provisionDefinitions(); +const gameDefinitions = await api.gameDefinitions(); export async function filterTank( filters: TankFilters, tank: TankDefinition, - owned: number[] = [] + owned: number[] = [], ) { return ( ((filters.showOwned && owned.includes(tank.id)) || @@ -29,15 +27,15 @@ export async function filterTank( (filters.gunType.length === 0 || (filters.gunType.includes("regular") && tank.turrets.some((turret) => - turret.guns.some((gun) => gun.gun_type!.$case === "regular") + turret.guns.some((gun) => gun.gun_type!.$case === "regular"), )) || (filters.gunType.includes("auto_loader") && tank.turrets.some((turret) => - turret.guns.some((gun) => gun.gun_type!.$case === "auto_loader") + turret.guns.some((gun) => gun.gun_type!.$case === "auto_loader"), )) || (filters.gunType.includes("auto_reloader") && tank.turrets.some((turret) => - turret.guns.some((gun) => gun.gun_type!.$case === "auto_reloader") + turret.guns.some((gun) => gun.gun_type!.$case === "auto_reloader"), ))) && tank.turrets.some((turret) => turret.guns.some((gun) => @@ -45,9 +43,9 @@ export async function filterTank( (index) => filters.shells[index] === null || gun.shells[index] === undefined || - gun.shells[index].type === filters.shells[index] - ) - ) + gun.shells[index].type === filters.shells[index], + ), + ), ) && (filters.consumables.length === 0 || filters.consumables.every((consumable) => @@ -56,10 +54,10 @@ export async function filterTank( checkConsumableProvisionInclusivity( consumableDefinitions.consumables[consumable], tank, - gun - ) - ) - ) + gun, + ), + ), + ), )) && (filters.provisions.length === 0 || filters.provisions.every((provision) => @@ -68,22 +66,22 @@ export async function filterTank( checkConsumableProvisionInclusivity( provisionDefinitions.provisions[provision], tank, - gun - ) - ) - ) + gun, + ), + ), + ), )) && (filters.abilities.length === 0 || Object.values(tank.roles).some((id) => gameDefinitions.roles[id as unknown as number].consumables.some( - (consumable) => filters.abilities.includes(consumable) - ) + (consumable) => filters.abilities.includes(consumable), + ), )) && (filters.powers.length === 0 || Object.values(tank.roles).some((id) => gameDefinitions.roles[id as unknown as number].provisions.some( - (consumable) => filters.powers.includes(consumable) - ) + (consumable) => filters.powers.includes(consumable), + ), )) ); } diff --git a/packages/website/src/pages/[...chunk_pages]/[...locale]/performance/index.astro b/packages/website/src/pages/[...chunk_pages]/[...locale]/performance/index.astro index fc34d8b9b..c34e55a48 100644 --- a/packages/website/src/pages/[...chunk_pages]/[...locale]/performance/index.astro +++ b/packages/website/src/pages/[...chunk_pages]/[...locale]/performance/index.astro @@ -1,7 +1,7 @@ --- -import ToolOpenGraph from "../../../components/ToolOpenGraph.astro"; -import { tools } from "../../../constants/tools"; -import BaseLayout from "../../../layouts/BaseLayout.astro"; +import ToolOpenGraph from "../../../../components/ToolOpenGraph.astro"; +import { tools } from "../../../../constants/tools"; +import BaseLayout from "../../../../layouts/BaseLayout.astro"; import type { getStaticPaths } from "../index.astro"; import { Page } from "./_index"; From f85d097c16f574c26a0b990f51e405e52f4cf36c Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sat, 1 Aug 2026 14:55:57 -0500 Subject: [PATCH 025/147] rename --- .../TankSearch/components/RecentlyViewed.tsx | 10 +++++----- .../[...locale]/auth/[provider]/_index.tsx | 0 .../[...locale]/auth/[provider]/index.astro | 0 .../[...locale]/compare/_index.tsx | 0 .../[...locale]/compare/index.astro | 0 .../[...locale]/docs/[directory]/[document]/_index.tsx | 0 .../docs/[directory]/[document]/index.astro | 0 .../[...locale]/docs/patches/index.astro | 0 .../[...locale]/embed/[embed]/_index.tsx | 0 .../[...locale]/embed/[embed]/host/_index.tsx | 0 .../[...locale]/embed/[embed]/host/index.astro | 0 .../[...locale]/embed/[embed]/index.astro | 0 .../[...locale]/embed/_index.tsx | 0 .../[...locale]/embed/index.astro | 0 .../[...locale]/gallery/_index.tsx | 0 .../[...locale]/gallery/index.astro | 0 .../[...locale]/guess/_index.tsx | 0 .../[...locale]/guess/index.astro | 0 .../[...locale]/index.astro | 0 .../[...locale]/mixer/_index.tsx | 0 .../[...locale]/mixer/index.astro | 0 .../[...locale]/performance/_index.tsx | 0 .../[...locale]/performance/index.astro | 0 .../[...locale]/playlist/_index.tsx | 0 .../[...locale]/playlist/index.astro | 0 .../[...locale]/session/_index.tsx | 0 .../[...locale]/session/index.astro | 0 .../[...locale]/settings/_index.tsx | 0 .../[...locale]/settings/index.astro | 0 .../[...locale]/tanks/[slug]/_index.tsx | 0 .../[...locale]/tanks/[slug]/index.astro | 0 .../[...locale]/tanks/_index.tsx | 0 .../[...locale]/tanks/index.astro | 0 .../[...locale]/tier-list/_index.tsx | 0 .../[...locale]/tier-list/index.astro | 0 .../[...locale]/tools/compare/index.astro | 0 .../[...locale]/tools/embed/[embed]/index.astro | 0 .../[...locale]/tools/embed/index.astro | 0 .../[...locale]/tools/gallery/index.astro | 0 .../[...locale]/tools/performance/index.astro | 0 .../[...locale]/tools/session/index.astro | 0 .../[...locale]/tools/tankopedia/[id]/index.astro | 0 .../[...locale]/tools/tankopedia/index.astro | 0 .../[...locale]/tools/tier-list/index.astro | 0 44 files changed, 5 insertions(+), 5 deletions(-) rename packages/website/src/pages/{[...chunk_pages] => [...chunk_default]}/[...locale]/auth/[provider]/_index.tsx (100%) rename packages/website/src/pages/{[...chunk_pages] => [...chunk_default]}/[...locale]/auth/[provider]/index.astro (100%) rename packages/website/src/pages/{[...chunk_pages] => [...chunk_default]}/[...locale]/compare/_index.tsx (100%) rename packages/website/src/pages/{[...chunk_pages] => [...chunk_default]}/[...locale]/compare/index.astro (100%) rename packages/website/src/pages/{[...chunk_pages] => [...chunk_default]}/[...locale]/docs/[directory]/[document]/_index.tsx (100%) rename packages/website/src/pages/{[...chunk_pages] => [...chunk_default]}/[...locale]/docs/[directory]/[document]/index.astro (100%) rename packages/website/src/pages/{[...chunk_pages] => [...chunk_default]}/[...locale]/docs/patches/index.astro (100%) rename packages/website/src/pages/{[...chunk_pages] => [...chunk_default]}/[...locale]/embed/[embed]/_index.tsx (100%) rename packages/website/src/pages/{[...chunk_pages] => [...chunk_default]}/[...locale]/embed/[embed]/host/_index.tsx (100%) rename packages/website/src/pages/{[...chunk_pages] => [...chunk_default]}/[...locale]/embed/[embed]/host/index.astro (100%) rename packages/website/src/pages/{[...chunk_pages] => [...chunk_default]}/[...locale]/embed/[embed]/index.astro (100%) rename packages/website/src/pages/{[...chunk_pages] => [...chunk_default]}/[...locale]/embed/_index.tsx (100%) rename packages/website/src/pages/{[...chunk_pages] => [...chunk_default]}/[...locale]/embed/index.astro (100%) rename packages/website/src/pages/{[...chunk_pages] => [...chunk_default]}/[...locale]/gallery/_index.tsx (100%) rename packages/website/src/pages/{[...chunk_pages] => [...chunk_default]}/[...locale]/gallery/index.astro (100%) rename packages/website/src/pages/{[...chunk_pages] => [...chunk_default]}/[...locale]/guess/_index.tsx (100%) rename packages/website/src/pages/{[...chunk_pages] => [...chunk_default]}/[...locale]/guess/index.astro (100%) rename packages/website/src/pages/{[...chunk_pages] => [...chunk_default]}/[...locale]/index.astro (100%) rename packages/website/src/pages/{[...chunk_pages] => [...chunk_default]}/[...locale]/mixer/_index.tsx (100%) rename packages/website/src/pages/{[...chunk_pages] => [...chunk_default]}/[...locale]/mixer/index.astro (100%) rename packages/website/src/pages/{[...chunk_pages] => [...chunk_default]}/[...locale]/performance/_index.tsx (100%) rename packages/website/src/pages/{[...chunk_pages] => [...chunk_default]}/[...locale]/performance/index.astro (100%) rename packages/website/src/pages/{[...chunk_pages] => [...chunk_default]}/[...locale]/playlist/_index.tsx (100%) rename packages/website/src/pages/{[...chunk_pages] => [...chunk_default]}/[...locale]/playlist/index.astro (100%) rename packages/website/src/pages/{[...chunk_pages] => [...chunk_default]}/[...locale]/session/_index.tsx (100%) rename packages/website/src/pages/{[...chunk_pages] => [...chunk_default]}/[...locale]/session/index.astro (100%) rename packages/website/src/pages/{[...chunk_pages] => [...chunk_default]}/[...locale]/settings/_index.tsx (100%) rename packages/website/src/pages/{[...chunk_pages] => [...chunk_default]}/[...locale]/settings/index.astro (100%) rename packages/website/src/pages/{[...chunk_pages] => [...chunk_default]}/[...locale]/tanks/[slug]/_index.tsx (100%) rename packages/website/src/pages/{[...chunk_pages] => [...chunk_default]}/[...locale]/tanks/[slug]/index.astro (100%) rename packages/website/src/pages/{[...chunk_pages] => [...chunk_default]}/[...locale]/tanks/_index.tsx (100%) rename packages/website/src/pages/{[...chunk_pages] => [...chunk_default]}/[...locale]/tanks/index.astro (100%) rename packages/website/src/pages/{[...chunk_pages] => [...chunk_default]}/[...locale]/tier-list/_index.tsx (100%) rename packages/website/src/pages/{[...chunk_pages] => [...chunk_default]}/[...locale]/tier-list/index.astro (100%) rename packages/website/src/pages/{[...chunk_pages] => [...chunk_default]}/[...locale]/tools/compare/index.astro (100%) rename packages/website/src/pages/{[...chunk_pages] => [...chunk_default]}/[...locale]/tools/embed/[embed]/index.astro (100%) rename packages/website/src/pages/{[...chunk_pages] => [...chunk_default]}/[...locale]/tools/embed/index.astro (100%) rename packages/website/src/pages/{[...chunk_pages] => [...chunk_default]}/[...locale]/tools/gallery/index.astro (100%) rename packages/website/src/pages/{[...chunk_pages] => [...chunk_default]}/[...locale]/tools/performance/index.astro (100%) rename packages/website/src/pages/{[...chunk_pages] => [...chunk_default]}/[...locale]/tools/session/index.astro (100%) rename packages/website/src/pages/{[...chunk_pages] => [...chunk_default]}/[...locale]/tools/tankopedia/[id]/index.astro (100%) rename packages/website/src/pages/{[...chunk_pages] => [...chunk_default]}/[...locale]/tools/tankopedia/index.astro (100%) rename packages/website/src/pages/{[...chunk_pages] => [...chunk_default]}/[...locale]/tools/tier-list/index.astro (100%) diff --git a/packages/website/src/components/TankSearch/components/RecentlyViewed.tsx b/packages/website/src/components/TankSearch/components/RecentlyViewed.tsx index da71d5966..beb07de67 100644 --- a/packages/website/src/components/TankSearch/components/RecentlyViewed.tsx +++ b/packages/website/src/components/TankSearch/components/RecentlyViewed.tsx @@ -1,7 +1,7 @@ import { Flex, Separator, Text } from "@radix-ui/themes"; import { isEqual } from "lodash-es"; import { useMemo } from "react"; -import { awaitableTankDefinitions } from "../../../core/awaitables/tankDefinitions"; +import { api } from "../../../core/blitzkit/api"; import { useLocale } from "../../../hooks/useLocale"; import { TankFilters } from "../../../stores/tankFilters"; import { TankopediaPersistent } from "../../../stores/tankopediaPersistent"; @@ -9,23 +9,23 @@ import { TankSort } from "../../../stores/tankopediaSort"; import { TankCard } from "../../TankCard"; import { TankCardWrapper } from "./TankCardWrapper"; -const tankDefinitions = await awaitableTankDefinitions; +const tankDefinitions = await api.tankDefinitions(); export function RecentlyViewed() { const filters = TankFilters.use(); // non-reactive because it is a little weird that it updates instantly even before the page loads const recentlyViewed = TankopediaPersistent.state.recentlyViewed.filter( - (id) => id in tankDefinitions.tanks + (id) => id in tankDefinitions.tanks, ); const hasFilters = useMemo( () => Object.entries(filters).some(([key, value]) => { return !isEqual( value, - TankFilters.initial[key as keyof typeof TankFilters.initial] + TankFilters.initial[key as keyof typeof TankFilters.initial], ); }), - [filters] + [filters], ); const { strings } = useLocale(); const by = TankSort.use((state) => state.by); diff --git a/packages/website/src/pages/[...chunk_pages]/[...locale]/auth/[provider]/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/auth/[provider]/_index.tsx similarity index 100% rename from packages/website/src/pages/[...chunk_pages]/[...locale]/auth/[provider]/_index.tsx rename to packages/website/src/pages/[...chunk_default]/[...locale]/auth/[provider]/_index.tsx diff --git a/packages/website/src/pages/[...chunk_pages]/[...locale]/auth/[provider]/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/auth/[provider]/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk_pages]/[...locale]/auth/[provider]/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/auth/[provider]/index.astro diff --git a/packages/website/src/pages/[...chunk_pages]/[...locale]/compare/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/compare/_index.tsx similarity index 100% rename from packages/website/src/pages/[...chunk_pages]/[...locale]/compare/_index.tsx rename to packages/website/src/pages/[...chunk_default]/[...locale]/compare/_index.tsx diff --git a/packages/website/src/pages/[...chunk_pages]/[...locale]/compare/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/compare/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk_pages]/[...locale]/compare/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/compare/index.astro diff --git a/packages/website/src/pages/[...chunk_pages]/[...locale]/docs/[directory]/[document]/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/docs/[directory]/[document]/_index.tsx similarity index 100% rename from packages/website/src/pages/[...chunk_pages]/[...locale]/docs/[directory]/[document]/_index.tsx rename to packages/website/src/pages/[...chunk_default]/[...locale]/docs/[directory]/[document]/_index.tsx diff --git a/packages/website/src/pages/[...chunk_pages]/[...locale]/docs/[directory]/[document]/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/docs/[directory]/[document]/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk_pages]/[...locale]/docs/[directory]/[document]/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/docs/[directory]/[document]/index.astro diff --git a/packages/website/src/pages/[...chunk_pages]/[...locale]/docs/patches/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/docs/patches/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk_pages]/[...locale]/docs/patches/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/docs/patches/index.astro diff --git a/packages/website/src/pages/[...chunk_pages]/[...locale]/embed/[embed]/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/embed/[embed]/_index.tsx similarity index 100% rename from packages/website/src/pages/[...chunk_pages]/[...locale]/embed/[embed]/_index.tsx rename to packages/website/src/pages/[...chunk_default]/[...locale]/embed/[embed]/_index.tsx diff --git a/packages/website/src/pages/[...chunk_pages]/[...locale]/embed/[embed]/host/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/embed/[embed]/host/_index.tsx similarity index 100% rename from packages/website/src/pages/[...chunk_pages]/[...locale]/embed/[embed]/host/_index.tsx rename to packages/website/src/pages/[...chunk_default]/[...locale]/embed/[embed]/host/_index.tsx diff --git a/packages/website/src/pages/[...chunk_pages]/[...locale]/embed/[embed]/host/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/embed/[embed]/host/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk_pages]/[...locale]/embed/[embed]/host/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/embed/[embed]/host/index.astro diff --git a/packages/website/src/pages/[...chunk_pages]/[...locale]/embed/[embed]/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/embed/[embed]/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk_pages]/[...locale]/embed/[embed]/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/embed/[embed]/index.astro diff --git a/packages/website/src/pages/[...chunk_pages]/[...locale]/embed/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/embed/_index.tsx similarity index 100% rename from packages/website/src/pages/[...chunk_pages]/[...locale]/embed/_index.tsx rename to packages/website/src/pages/[...chunk_default]/[...locale]/embed/_index.tsx diff --git a/packages/website/src/pages/[...chunk_pages]/[...locale]/embed/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/embed/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk_pages]/[...locale]/embed/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/embed/index.astro diff --git a/packages/website/src/pages/[...chunk_pages]/[...locale]/gallery/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/gallery/_index.tsx similarity index 100% rename from packages/website/src/pages/[...chunk_pages]/[...locale]/gallery/_index.tsx rename to packages/website/src/pages/[...chunk_default]/[...locale]/gallery/_index.tsx diff --git a/packages/website/src/pages/[...chunk_pages]/[...locale]/gallery/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/gallery/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk_pages]/[...locale]/gallery/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/gallery/index.astro diff --git a/packages/website/src/pages/[...chunk_pages]/[...locale]/guess/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/guess/_index.tsx similarity index 100% rename from packages/website/src/pages/[...chunk_pages]/[...locale]/guess/_index.tsx rename to packages/website/src/pages/[...chunk_default]/[...locale]/guess/_index.tsx diff --git a/packages/website/src/pages/[...chunk_pages]/[...locale]/guess/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/guess/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk_pages]/[...locale]/guess/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/guess/index.astro diff --git a/packages/website/src/pages/[...chunk_pages]/[...locale]/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk_pages]/[...locale]/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/index.astro diff --git a/packages/website/src/pages/[...chunk_pages]/[...locale]/mixer/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/mixer/_index.tsx similarity index 100% rename from packages/website/src/pages/[...chunk_pages]/[...locale]/mixer/_index.tsx rename to packages/website/src/pages/[...chunk_default]/[...locale]/mixer/_index.tsx diff --git a/packages/website/src/pages/[...chunk_pages]/[...locale]/mixer/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/mixer/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk_pages]/[...locale]/mixer/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/mixer/index.astro diff --git a/packages/website/src/pages/[...chunk_pages]/[...locale]/performance/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/performance/_index.tsx similarity index 100% rename from packages/website/src/pages/[...chunk_pages]/[...locale]/performance/_index.tsx rename to packages/website/src/pages/[...chunk_default]/[...locale]/performance/_index.tsx diff --git a/packages/website/src/pages/[...chunk_pages]/[...locale]/performance/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/performance/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk_pages]/[...locale]/performance/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/performance/index.astro diff --git a/packages/website/src/pages/[...chunk_pages]/[...locale]/playlist/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/playlist/_index.tsx similarity index 100% rename from packages/website/src/pages/[...chunk_pages]/[...locale]/playlist/_index.tsx rename to packages/website/src/pages/[...chunk_default]/[...locale]/playlist/_index.tsx diff --git a/packages/website/src/pages/[...chunk_pages]/[...locale]/playlist/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/playlist/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk_pages]/[...locale]/playlist/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/playlist/index.astro diff --git a/packages/website/src/pages/[...chunk_pages]/[...locale]/session/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/session/_index.tsx similarity index 100% rename from packages/website/src/pages/[...chunk_pages]/[...locale]/session/_index.tsx rename to packages/website/src/pages/[...chunk_default]/[...locale]/session/_index.tsx diff --git a/packages/website/src/pages/[...chunk_pages]/[...locale]/session/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/session/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk_pages]/[...locale]/session/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/session/index.astro diff --git a/packages/website/src/pages/[...chunk_pages]/[...locale]/settings/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/settings/_index.tsx similarity index 100% rename from packages/website/src/pages/[...chunk_pages]/[...locale]/settings/_index.tsx rename to packages/website/src/pages/[...chunk_default]/[...locale]/settings/_index.tsx diff --git a/packages/website/src/pages/[...chunk_pages]/[...locale]/settings/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/settings/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk_pages]/[...locale]/settings/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/settings/index.astro diff --git a/packages/website/src/pages/[...chunk_pages]/[...locale]/tanks/[slug]/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/tanks/[slug]/_index.tsx similarity index 100% rename from packages/website/src/pages/[...chunk_pages]/[...locale]/tanks/[slug]/_index.tsx rename to packages/website/src/pages/[...chunk_default]/[...locale]/tanks/[slug]/_index.tsx diff --git a/packages/website/src/pages/[...chunk_pages]/[...locale]/tanks/[slug]/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/tanks/[slug]/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk_pages]/[...locale]/tanks/[slug]/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/tanks/[slug]/index.astro diff --git a/packages/website/src/pages/[...chunk_pages]/[...locale]/tanks/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/tanks/_index.tsx similarity index 100% rename from packages/website/src/pages/[...chunk_pages]/[...locale]/tanks/_index.tsx rename to packages/website/src/pages/[...chunk_default]/[...locale]/tanks/_index.tsx diff --git a/packages/website/src/pages/[...chunk_pages]/[...locale]/tanks/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/tanks/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk_pages]/[...locale]/tanks/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/tanks/index.astro diff --git a/packages/website/src/pages/[...chunk_pages]/[...locale]/tier-list/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/tier-list/_index.tsx similarity index 100% rename from packages/website/src/pages/[...chunk_pages]/[...locale]/tier-list/_index.tsx rename to packages/website/src/pages/[...chunk_default]/[...locale]/tier-list/_index.tsx diff --git a/packages/website/src/pages/[...chunk_pages]/[...locale]/tier-list/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/tier-list/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk_pages]/[...locale]/tier-list/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/tier-list/index.astro diff --git a/packages/website/src/pages/[...chunk_pages]/[...locale]/tools/compare/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/tools/compare/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk_pages]/[...locale]/tools/compare/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/tools/compare/index.astro diff --git a/packages/website/src/pages/[...chunk_pages]/[...locale]/tools/embed/[embed]/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/tools/embed/[embed]/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk_pages]/[...locale]/tools/embed/[embed]/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/tools/embed/[embed]/index.astro diff --git a/packages/website/src/pages/[...chunk_pages]/[...locale]/tools/embed/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/tools/embed/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk_pages]/[...locale]/tools/embed/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/tools/embed/index.astro diff --git a/packages/website/src/pages/[...chunk_pages]/[...locale]/tools/gallery/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/tools/gallery/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk_pages]/[...locale]/tools/gallery/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/tools/gallery/index.astro diff --git a/packages/website/src/pages/[...chunk_pages]/[...locale]/tools/performance/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/tools/performance/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk_pages]/[...locale]/tools/performance/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/tools/performance/index.astro diff --git a/packages/website/src/pages/[...chunk_pages]/[...locale]/tools/session/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/tools/session/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk_pages]/[...locale]/tools/session/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/tools/session/index.astro diff --git a/packages/website/src/pages/[...chunk_pages]/[...locale]/tools/tankopedia/[id]/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/tools/tankopedia/[id]/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk_pages]/[...locale]/tools/tankopedia/[id]/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/tools/tankopedia/[id]/index.astro diff --git a/packages/website/src/pages/[...chunk_pages]/[...locale]/tools/tankopedia/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/tools/tankopedia/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk_pages]/[...locale]/tools/tankopedia/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/tools/tankopedia/index.astro diff --git a/packages/website/src/pages/[...chunk_pages]/[...locale]/tools/tier-list/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/tools/tier-list/index.astro similarity index 100% rename from packages/website/src/pages/[...chunk_pages]/[...locale]/tools/tier-list/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/tools/tier-list/index.astro From 59108d763794333d2e8577f9df69d46b0c9facd0 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sat, 1 Aug 2026 15:08:33 -0500 Subject: [PATCH 026/147] update (4) --- packages/core/src/blitzkit/assertSecret.ts | 2 +- packages/website/src/core/blitzkit/api.ts | 6 +-- .../src/core/blitzkit/chunkedStaticPaths.ts | 15 +++++++ .../src/core/blitzkit/mixStaticPaths.ts | 32 +++++++++++++++ packages/website/src/core/blitzkit/vfs.ts | 5 +++ .../src/pages/[...chunk_api]/_index.ts | 3 ++ .../[...chunk_api]/flags/circle/[id].webp.ts | 39 +++++++++++++++++++ 7 files changed, 96 insertions(+), 6 deletions(-) create mode 100644 packages/website/src/core/blitzkit/chunkedStaticPaths.ts create mode 100644 packages/website/src/core/blitzkit/mixStaticPaths.ts create mode 100644 packages/website/src/core/blitzkit/vfs.ts create mode 100644 packages/website/src/pages/[...chunk_api]/_index.ts create mode 100644 packages/website/src/pages/[...chunk_api]/flags/circle/[id].webp.ts diff --git a/packages/core/src/blitzkit/assertSecret.ts b/packages/core/src/blitzkit/assertSecret.ts index 934379286..7217e0e92 100644 --- a/packages/core/src/blitzkit/assertSecret.ts +++ b/packages/core/src/blitzkit/assertSecret.ts @@ -1,4 +1,4 @@ -export function assertSecret(secret?: Type) { +export function assertSecret(secret?: string) { if (secret === undefined) throw new Error("Missing secret"); return secret; } diff --git a/packages/website/src/core/blitzkit/api.ts b/packages/website/src/core/blitzkit/api.ts index d96a3b9e3..21e5c4b9f 100644 --- a/packages/website/src/core/blitzkit/api.ts +++ b/packages/website/src/core/blitzkit/api.ts @@ -1,17 +1,13 @@ import { ClientBlitzKitAPI, - LocalVFS, ServerBlitzKitAPI, type BlitzKitAPI, } from "@blitzkit/core"; +import { vfs } from "./vfs"; let _api: BlitzKitAPI; if (import.meta.env.SSR) { - const vfs = new LocalVFS( - "/run/media/tresabhi/Windows/Program Files/WindowsApps/7458BE2C.WorldofTanksBlitz_11.19.272.0_x64__x4tje2y229k00", - ); - _api = await new ServerBlitzKitAPI(vfs).init(); } else { _api = new ClientBlitzKitAPI(); diff --git a/packages/website/src/core/blitzkit/chunkedStaticPaths.ts b/packages/website/src/core/blitzkit/chunkedStaticPaths.ts new file mode 100644 index 000000000..76c9a365e --- /dev/null +++ b/packages/website/src/core/blitzkit/chunkedStaticPaths.ts @@ -0,0 +1,15 @@ +import { assertSecret } from "@blitzkit/core"; +import type { GetStaticPaths } from "astro"; + +export function chunkedStaticPaths(chunk: string) { + return function () { + const chunksEnv = assertSecret(import.meta.env.CHUNKS); + const chunks = chunksEnv.split(","); + + if (chunksEnv !== "*" && !chunks.includes(chunk)) { + return []; + } + + return [{ params: { [`chunk_${chunk}`]: chunk } }]; + } satisfies GetStaticPaths; +} diff --git a/packages/website/src/core/blitzkit/mixStaticPaths.ts b/packages/website/src/core/blitzkit/mixStaticPaths.ts new file mode 100644 index 000000000..4fe056fc3 --- /dev/null +++ b/packages/website/src/core/blitzkit/mixStaticPaths.ts @@ -0,0 +1,32 @@ +import type { + GetStaticPathsItem, + GetStaticPathsOptions, + GetStaticPathsResult, +} from "astro"; + +type Promisable = T | Promise; + +export function mixStaticPaths< + TypeA extends GetStaticPathsItem, + TypeB extends GetStaticPathsItem, +>( + a: (options: GetStaticPathsOptions) => Promisable, + b: (options: GetStaticPathsOptions) => Promisable, +) { + return async function (options: GetStaticPathsOptions) { + const pathsA = await a(options); + const pathsB = await b(options); + const paths: GetStaticPathsResult = []; + + for (const pathA of pathsA) { + for (const pathB of pathsB) { + paths.push({ + params: { ...pathA.params, ...pathB.params }, + props: { ...pathA.props, ...pathB.props }, + }); + } + } + + return paths as (TypeA & TypeB)[]; + }; +} diff --git a/packages/website/src/core/blitzkit/vfs.ts b/packages/website/src/core/blitzkit/vfs.ts new file mode 100644 index 000000000..a08a49d21 --- /dev/null +++ b/packages/website/src/core/blitzkit/vfs.ts @@ -0,0 +1,5 @@ +import { LocalVFS } from "@blitzkit/core"; + +export const vfs = new LocalVFS( + "/run/media/tresabhi/Windows/Program Files/WindowsApps/7458BE2C.WorldofTanksBlitz_11.19.272.0_x64__x4tje2y229k00", +); diff --git a/packages/website/src/pages/[...chunk_api]/_index.ts b/packages/website/src/pages/[...chunk_api]/_index.ts new file mode 100644 index 000000000..22ddd5188 --- /dev/null +++ b/packages/website/src/pages/[...chunk_api]/_index.ts @@ -0,0 +1,3 @@ +import { chunkedStaticPaths } from "../../core/blitzkit/chunkedStaticPaths"; + +export const getStaticPaths = chunkedStaticPaths("api"); diff --git a/packages/website/src/pages/[...chunk_api]/flags/circle/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/flags/circle/[id].webp.ts new file mode 100644 index 000000000..c32a13501 --- /dev/null +++ b/packages/website/src/pages/[...chunk_api]/flags/circle/[id].webp.ts @@ -0,0 +1,39 @@ +import type { APIContext, GetStaticPathsItem } from "astro"; +import sharp from "sharp"; +import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; +import { vfs } from "../../../../core/blitzkit/vfs"; +import { getStaticPaths as _getStaticPaths } from "../../_index"; + +export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { + const paths: GetStaticPathsItem[] = []; + + const circleFiles = await vfs + .dir(`Data/Gfx/Lobby/flags`) + .then((files) => + files.filter( + (flag) => + flag.startsWith("flag_profile-stat_") && + !flag.endsWith("@2x.packed.webp"), + ), + ); + + for (const flag of circleFiles) { + const path = `Data/Gfx/Lobby/flags/${flag}`; + const id = flag.match(/flag_profile-stat_(.+)\.packed\.webp/)![1]; + + paths.push({ + params: { id }, + props: { path }, + }); + } + + return paths; +}); + +export async function GET({ props }: APIContext<{ path: string }>) { + const image = sharp(await vfs.file(props.path)); + const content = await image.trim({ threshold: 100 }).toBuffer(); + const bytes = new Uint8Array(content); + + return new Response(bytes); +} From 90ad923672a2ebb4e6080d7167575e32ce27f08a Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sun, 2 Aug 2026 20:22:32 -0500 Subject: [PATCH 027/147] consumable icons --- .../buildAssets/consumableProvisionIcons.ts | 36 ------ .../src/core/blitz/extractPackedIcon.ts | 31 +++++ .../src/core/blitz/parsePackedSpriteRect.ts | 0 .../icons/consumables/[id].webp.ts | 112 ++++++++++++++++++ 4 files changed, 143 insertions(+), 36 deletions(-) create mode 100644 packages/website/src/core/blitz/extractPackedIcon.ts rename packages/{scripts => website}/src/core/blitz/parsePackedSpriteRect.ts (100%) create mode 100644 packages/website/src/pages/[...chunk_api]/icons/consumables/[id].webp.ts diff --git a/packages/scripts/src/buildAssets/consumableProvisionIcons.ts b/packages/scripts/src/buildAssets/consumableProvisionIcons.ts index b30fabdc9..a934a5ad8 100644 --- a/packages/scripts/src/buildAssets/consumableProvisionIcons.ts +++ b/packages/scripts/src/buildAssets/consumableProvisionIcons.ts @@ -14,42 +14,6 @@ interface Mappings { const listItemsPattern = //g; -async function extractPackedIcon( - texture: sharp.Sharp, - sizes: number[], - context: string, -) { - const [left, top, width, height] = sizes; - const bounds = [left, top, width, height]; - - if (!bounds.every(Number.isInteger)) { - throw new Error( - `Invalid RIFF sprite bounds for ${context}: ${bounds.join(" ")}`, - ); - } - - const metadata = await texture.metadata(); - - if (metadata.width === undefined || metadata.height === undefined) { - throw new Error(`Failed to read image dimensions for ${context}`); - } - - if ( - left < 0 || - top < 0 || - width <= 0 || - height <= 0 || - left + width > metadata.width || - top + height > metadata.height - ) { - throw new Error( - `Out-of-bounds RIFF sprite for ${context}: ${bounds.join(" ")} in ${metadata.width}x${metadata.height}`, - ); - } - - return await texture.clone().extract({ left, top, width, height }).toBuffer(); -} - export async function consumableProvisionIcons() { console.log("Building consumable and provision icons..."); diff --git a/packages/website/src/core/blitz/extractPackedIcon.ts b/packages/website/src/core/blitz/extractPackedIcon.ts new file mode 100644 index 000000000..b2b1f2ca0 --- /dev/null +++ b/packages/website/src/core/blitz/extractPackedIcon.ts @@ -0,0 +1,31 @@ +import type sharp from "sharp"; + +export async function extractPackedIcon(texture: sharp.Sharp, sizes: number[]) { + const [left, top, width, height] = sizes; + const bounds = [left, top, width, height]; + + if (!bounds.every(Number.isInteger)) { + throw new Error(`Invalid RIFF sprite bounds: ${bounds.join(" ")}`); + } + + const metadata = await texture.metadata(); + + if (metadata.width === undefined || metadata.height === undefined) { + throw new Error(`Failed to read image dimensions`); + } + + if ( + left < 0 || + top < 0 || + width <= 0 || + height <= 0 || + left + width > metadata.width || + top + height > metadata.height + ) { + throw new Error( + `Out-of-bounds RIFF sprite: ${bounds.join(" ")} in ${metadata.width}x${metadata.height}`, + ); + } + + return await texture.clone().extract({ left, top, width, height }).toBuffer(); +} diff --git a/packages/scripts/src/core/blitz/parsePackedSpriteRect.ts b/packages/website/src/core/blitz/parsePackedSpriteRect.ts similarity index 100% rename from packages/scripts/src/core/blitz/parsePackedSpriteRect.ts rename to packages/website/src/core/blitz/parsePackedSpriteRect.ts diff --git a/packages/website/src/pages/[...chunk_api]/icons/consumables/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/icons/consumables/[id].webp.ts new file mode 100644 index 000000000..8b02390b8 --- /dev/null +++ b/packages/website/src/pages/[...chunk_api]/icons/consumables/[id].webp.ts @@ -0,0 +1,112 @@ +import type { ConsumablesCommon } from "@blitzkit/core"; +import type { APIContext, GetStaticPathsItem } from "astro"; +import sharp from "sharp"; +import { extractPackedIcon } from "../../../../core/blitz/extractPackedIcon"; +import { parsePackedSpriteRect } from "../../../../core/blitz/parsePackedSpriteRect"; +import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; +import { vfs } from "../../../../core/blitzkit/vfs"; +import { getStaticPaths as _getStaticPaths } from "../../_index"; + +interface Mappings { + Header: { version: number }; + StyleSheets: { + selectors: string[]; + properties: { "bg-sprite": string }; + }[]; +} + +const listItemsPattern = //g; + +export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { + const paths: GetStaticPathsItem[] = []; + + const styles = [ + "UI/Styles/Lobby/Inventory/InventoryNormalStyles.yaml", + "UI/Styles/Lobby/Inventory/Event/InventoryBigStyles.yaml", + "UI/Styles/Lobby/Inventory/Event/InventoryNormalStyles.yaml", + "UI/Screens3/Lobby/Inventory/Equipment/EquipmentItemImage.style.yaml", + "UI/Screens/Battle/Styles/BattleEquipmentStyles.yaml", + ]; + const styleSheets = await Promise.all( + styles.map(async (path) => + Object.values((await vfs.yaml(`Data/${path}`)).StyleSheets), + ), + ).then((array) => array.flat()); + + for (const match of ( + await vfs.text(`Data/XML/item_defs/vehicles/common/consumables/list.xml`) + ).matchAll(listItemsPattern)) { + const consumablesCommon = await vfs.xml<{ + root: ConsumablesCommon; + }>(`Data/XML/item_defs/vehicles/common/consumables/${match[1]}.xml`); + + for (const consumable of Object.values(consumablesCommon.root)) { + const styleSheet = styleSheets.find((styleSheet) => + styleSheet.selectors.some((selector) => + selector.includes(`${consumable.icon} `), + ), + ); + + if (!styleSheet) { + console.warn( + `No style sheet found for consumable ${consumable.icon}; skipping...`, + ); + continue; + } + + const path = styleSheet.properties["bg-sprite"] + .replace("~res:/", "") + .replace(".psd", "") + .replace(".txt", ""); + + paths.push({ + props: { path }, + params: { id: consumable.id }, + }); + } + } + + return paths; +}); + +export async function GET({ props }: APIContext<{ path: string }>) { + let buffer: Buffer; + + if (await vfs.resolve(`Data/${props.path}.packed.webp`)) { + const packedBuffer = await vfs.file(`Data/${props.path}.packed.webp`); + const consumablesTexture = sharp(packedBuffer); + const sizes = parsePackedSpriteRect(packedBuffer); + + if (sizes) { + buffer = await extractPackedIcon(consumablesTexture, sizes); + } else { + buffer = await consumablesTexture.trim({ threshold: 80 }).toBuffer(); + } + } else { + const consumablesTexture = sharp( + await vfs.file( + `${`Data/${props.path}` + .split("/") + .slice(0, -1) + .join("/")}/texture0.packed.webp`, + ), + ); + const sizes = (await vfs.text(`Data/${props.path}.txt`)) + .split("\n")[4] + .split(" ") + .map(Number); + buffer = await consumablesTexture + .clone() + .extract({ + left: sizes[0], + top: sizes[1], + width: sizes[2], + height: sizes[3], + }) + .toBuffer(); + } + + const bytes = new Uint8Array(buffer); + + return new Response(bytes); +} From 8a3e57637d7b2e31d6c2d5b7af3ffe5ed4f94201 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sun, 2 Aug 2026 20:24:39 -0500 Subject: [PATCH 028/147] provision icons --- .../buildAssets/consumableProvisionIcons.ts | 189 ------------------ .../icons/provisions/[id].webp.ts | 112 +++++++++++ 2 files changed, 112 insertions(+), 189 deletions(-) delete mode 100644 packages/scripts/src/buildAssets/consumableProvisionIcons.ts create mode 100644 packages/website/src/pages/[...chunk_api]/icons/provisions/[id].webp.ts diff --git a/packages/scripts/src/buildAssets/consumableProvisionIcons.ts b/packages/scripts/src/buildAssets/consumableProvisionIcons.ts deleted file mode 100644 index a934a5ad8..000000000 --- a/packages/scripts/src/buildAssets/consumableProvisionIcons.ts +++ /dev/null @@ -1,189 +0,0 @@ -import sharp from "sharp"; -import { parsePackedSpriteRect } from "../core/blitz/parsePackedSpriteRect"; -import { AssetUploader } from "../core/github/assetUploader"; -import { vfs } from "./constants"; -import { ConsumablesCommon, ProvisionsCommon } from "./definitions"; - -interface Mappings { - Header: { version: number }; - StyleSheets: { - selectors: string[]; - properties: { "bg-sprite": string }; - }[]; -} - -const listItemsPattern = //g; - -export async function consumableProvisionIcons() { - console.log("Building consumable and provision icons..."); - - using uploader = new AssetUploader("consumable and provision icons"); - const styles = [ - "UI/Styles/Lobby/Inventory/InventoryNormalStyles.yaml", - "UI/Styles/Lobby/Inventory/Event/InventoryBigStyles.yaml", - "UI/Styles/Lobby/Inventory/Event/InventoryNormalStyles.yaml", - "UI/Screens3/Lobby/Inventory/Equipment/EquipmentItemImage.style.yaml", - "UI/Screens/Battle/Styles/BattleEquipmentStyles.yaml", - ]; - const styleSheets = await Promise.all( - styles.map(async (path) => - Object.values((await vfs.yaml(`Data/${path}`)).StyleSheets), - ), - ).then((array) => array.flat()); - - for (const match of ( - await vfs.text(`Data/XML/item_defs/vehicles/common/consumables/list.xml`) - ).matchAll(listItemsPattern)) { - const consumablesCommon = await vfs.xml<{ - root: ConsumablesCommon; - }>(`Data/XML/item_defs/vehicles/common/consumables/${match[1]}.xml`); - - for (const consumable of Object.values(consumablesCommon.root)) { - const styleSheet = styleSheets.find((styleSheet) => - styleSheet.selectors.some((selector) => - selector.includes(`${consumable.icon} `), - ), - ); - - if (!styleSheet) { - console.warn( - `No style sheet found for consumable ${consumable.icon}; skipping...`, - ); - continue; - } - - const configPath = styleSheet.properties["bg-sprite"] - .replace("~res:/", "") - .replace(".psd", "") - .replace(".txt", ""); - - if (await vfs.resolve(`Data/${configPath}.packed.webp`)) { - const packedBuffer = await vfs.file(`Data/${configPath}.packed.webp`); - const consumablesTexture = sharp(packedBuffer); - const sizes = parsePackedSpriteRect(packedBuffer); - let content: Buffer; - - if (sizes) { - content = await extractPackedIcon( - consumablesTexture, - sizes, - `consumable ${consumable.icon}`, - ); - } else { - content = await consumablesTexture.trim({ threshold: 80 }).toBuffer(); - } - - await uploader.add({ - path: `icons/consumables/${consumable.id}.webp`, - content, - }); - } else { - const consumablesTexture = sharp( - await vfs.file( - `${`Data/${configPath}` - .split("/") - .slice(0, -1) - .join("/")}/texture0.packed.webp`, - ), - ); - const sizes = (await vfs.text(`Data/${configPath}.txt`)) - .split("\n")[4] - .split(" ") - .map(Number); - const content = await consumablesTexture - .clone() - .extract({ - left: sizes[0], - top: sizes[1], - width: sizes[2], - height: sizes[3], - }) - .toBuffer(); - - await uploader.add({ - path: `icons/consumables/${consumable.id}.webp`, - content, - }); - } - } - } - - for (const match of ( - await vfs.text(`Data/XML/item_defs/vehicles/common/provisions/list.xml`) - ).matchAll(listItemsPattern)) { - const provisionsCommon = await vfs.xml<{ - root: ProvisionsCommon; - }>(`Data/XML/item_defs/vehicles/common/provisions/${match[1]}.xml`); - - for (const provision of Object.values(provisionsCommon.root)) { - const styleSheet = styleSheets.find((styleSheet) => - styleSheet.selectors.some((selector) => - selector.includes(`${provision.icon} `), - ), - ); - - if (!styleSheet) { - console.warn( - `No style sheet found for provision ${provision.icon}; skipping...`, - ); - continue; - } - - const configPath = styleSheet.properties["bg-sprite"] - .replace("~res:/", "") - .replace(".psd", "") - .replace(".txt", ""); - - if (await vfs.resolve(`Data/${configPath}.packed.webp`)) { - const packedBuffer = await vfs.file(`Data/${configPath}.packed.webp`); - const provisionsTexture = sharp(packedBuffer); - const sizes = parsePackedSpriteRect(packedBuffer); - let content: Buffer; - - if (sizes) { - content = await extractPackedIcon( - provisionsTexture, - sizes, - `provision ${provision.icon}`, - ); - } else { - content = await provisionsTexture.trim({ threshold: 80 }).toBuffer(); - } - - await uploader.add({ - path: `icons/provisions/${provision.id}.webp`, - content, - }); - } else { - const provisionsTexture = sharp( - await vfs.file( - `${`Data/${configPath}` - .split("/") - .slice(0, -1) - .join("/")}/texture0.packed.webp`, - ), - ); - const sizes = (await vfs.text(`Data/${configPath}.txt`)) - .split("\n")[4] - .split(" ") - .map(Number); - const content = await provisionsTexture - .clone() - .extract({ - left: sizes[0], - top: sizes[1], - width: sizes[2], - height: sizes[3], - }) - .toBuffer(); - - await uploader.add({ - path: `icons/provisions/${provision.id}.webp`, - content, - }); - } - } - } - - await uploader.flush(); -} diff --git a/packages/website/src/pages/[...chunk_api]/icons/provisions/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/icons/provisions/[id].webp.ts new file mode 100644 index 000000000..db38f389a --- /dev/null +++ b/packages/website/src/pages/[...chunk_api]/icons/provisions/[id].webp.ts @@ -0,0 +1,112 @@ +import type { ProvisionsCommon } from "@blitzkit/core"; +import type { APIContext, GetStaticPathsItem } from "astro"; +import sharp from "sharp"; +import { extractPackedIcon } from "../../../../core/blitz/extractPackedIcon"; +import { parsePackedSpriteRect } from "../../../../core/blitz/parsePackedSpriteRect"; +import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; +import { vfs } from "../../../../core/blitzkit/vfs"; +import { getStaticPaths as _getStaticPaths } from "../../_index"; + +interface Mappings { + Header: { version: number }; + StyleSheets: { + selectors: string[]; + properties: { "bg-sprite": string }; + }[]; +} + +const listItemsPattern = //g; + +export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { + const paths: GetStaticPathsItem[] = []; + + const styles = [ + "UI/Styles/Lobby/Inventory/InventoryNormalStyles.yaml", + "UI/Styles/Lobby/Inventory/Event/InventoryBigStyles.yaml", + "UI/Styles/Lobby/Inventory/Event/InventoryNormalStyles.yaml", + "UI/Screens3/Lobby/Inventory/Equipment/EquipmentItemImage.style.yaml", + "UI/Screens/Battle/Styles/BattleEquipmentStyles.yaml", + ]; + const styleSheets = await Promise.all( + styles.map(async (path) => + Object.values((await vfs.yaml(`Data/${path}`)).StyleSheets), + ), + ).then((array) => array.flat()); + + for (const match of ( + await vfs.text(`Data/XML/item_defs/vehicles/common/provisions/list.xml`) + ).matchAll(listItemsPattern)) { + const provisionsCommon = await vfs.xml<{ + root: ProvisionsCommon; + }>(`Data/XML/item_defs/vehicles/common/provisions/${match[1]}.xml`); + + for (const provision of Object.values(provisionsCommon.root)) { + const styleSheet = styleSheets.find((styleSheet) => + styleSheet.selectors.some((selector) => + selector.includes(`${provision.icon} `), + ), + ); + + if (!styleSheet) { + console.warn( + `No style sheet found for provision ${provision.icon}; skipping...`, + ); + continue; + } + + const path = styleSheet.properties["bg-sprite"] + .replace("~res:/", "") + .replace(".psd", "") + .replace(".txt", ""); + + paths.push({ + props: { path }, + params: { id: provision.id }, + }); + } + } + + return paths; +}); + +export async function GET({ props }: APIContext<{ path: string }>) { + let buffer: Buffer; + + if (await vfs.resolve(`Data/${props.path}.packed.webp`)) { + const packedBuffer = await vfs.file(`Data/${props.path}.packed.webp`); + const provisionsTexture = sharp(packedBuffer); + const sizes = parsePackedSpriteRect(packedBuffer); + + if (sizes) { + buffer = await extractPackedIcon(provisionsTexture, sizes); + } else { + buffer = await provisionsTexture.trim({ threshold: 80 }).toBuffer(); + } + } else { + const provisionsTexture = sharp( + await vfs.file( + `${`Data/${props.path}` + .split("/") + .slice(0, -1) + .join("/")}/texture0.packed.webp`, + ), + ); + const sizes = (await vfs.text(`Data/${props.path}.txt`)) + .split("\n")[4] + .split(" ") + .map(Number); + buffer = await provisionsTexture + .clone() + .extract({ + left: sizes[0], + top: sizes[1], + width: sizes[2], + height: sizes[3], + }) + .toBuffer(); + } + + const bytes = new Uint8Array(buffer); + + return new Response(bytes); +} From b060e65af566b20effc83e405b5b7d5abd786925 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sun, 2 Aug 2026 20:28:57 -0500 Subject: [PATCH 029/147] dynamically import client api --- .../core/src/statistics/getTierWeights.ts | 22 --- packages/core/src/statistics/index.ts | 17 +-- packages/scripts/src/applyPatches.ts | 140 +++++++++--------- .../website/src/components/TankCard/index.tsx | 6 +- packages/website/src/core/blitzkit/api.ts | 8 +- 5 files changed, 82 insertions(+), 111 deletions(-) delete mode 100644 packages/core/src/statistics/getTierWeights.ts diff --git a/packages/core/src/statistics/getTierWeights.ts b/packages/core/src/statistics/getTierWeights.ts deleted file mode 100644 index 6144b078f..000000000 --- a/packages/core/src/statistics/getTierWeights.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { fetchTankDefinitions } from '../blitzkit'; -import type { BlitzStats } from './compositeStats/constants'; - -export async function getTierWeights( - stats: Record, - order: number[] = Object.keys(stats).map(parseInt), -) { - const tankDefinitions = await fetchTankDefinitions(); - const tierWeights: TierWeightsRecord = {}; - - order.forEach((id) => { - const entry = tankDefinitions.tanks[id]; - - if (!entry) return; - if (!tierWeights[entry.tier]) tierWeights[entry.tier] = 0; - - tierWeights[entry.tier]! += stats[id].battles; - }); - - return tierWeights; -} -export type TierWeightsRecord = Partial>; diff --git a/packages/core/src/statistics/index.ts b/packages/core/src/statistics/index.ts index 81288f813..adad33644 100644 --- a/packages/core/src/statistics/index.ts +++ b/packages/core/src/statistics/index.ts @@ -1,9 +1,8 @@ -export * from './calculateWN8'; -export * from './compositeStats'; -export * from './deltaBkrlBlitzStats'; -export * from './deltaTankStats'; -export * from './getBkniPercentile'; -export * from './getTierWeights'; -export * from './getWN8Percentile'; -export * from './percentiles'; -export * from './sumAllStats'; +export * from "./calculateWN8"; +export * from "./compositeStats"; +export * from "./deltaBkrlBlitzStats"; +export * from "./deltaTankStats"; +export * from "./getBkniPercentile"; +export * from "./getWN8Percentile"; +export * from "./percentiles"; +export * from "./sumAllStats"; diff --git a/packages/scripts/src/applyPatches.ts b/packages/scripts/src/applyPatches.ts index 2c3833188..18aaf6ba3 100644 --- a/packages/scripts/src/applyPatches.ts +++ b/packages/scripts/src/applyPatches.ts @@ -5,7 +5,6 @@ import { parse as parsePath } from "path"; import ProgressBar from "progress"; import { parse as parseYaml, stringify as stringifyYaml } from "yaml"; import { PATCHES_ROOT, vfs } from "./buildAssets/constants"; -import { writeDVPL } from "./core/blitz/writeDVPL"; const versionTextFile = await vfs.text(`Data/version.txt`); const currentVersion = versionTextFile @@ -24,83 +23,80 @@ while (true) { )}/dlc/s${currentVersion}_${patchIndex}.yaml`, ); - console.log(patchIndex, response.status) + console.log(patchIndex, response.status); if (!response.ok) break; - - console.log(`Applying patch ${patchIndex}...`); - const data = parseYaml(await response.text()); - const dvpm = await fetch( - `${assertSecret(import.meta.env.WOTB_DLC_CDN)}/dlc/${data.dx11}`, - ).then((response) => response.arrayBuffer()); - const dvpd = await fetch( - `${assertSecret(import.meta.env.WOTB_DLC_CDN)}/dlc/${data.dx11.replace( - ".dvpm", - ".dvpd", - )}`, - ).then((response) => response.arrayBuffer()); - const files = await dvp(dvpm, dvpd); - const bar = new ProgressBar( - `Patching ${files.length} files :bar`, - files.length, + console.log(`Applying patch ${patchIndex}...`); + + const data = parseYaml(await response.text()); + const dvpm = await fetch( + `${assertSecret(import.meta.env.WOTB_DLC_CDN)}/dlc/${data.dx11}`, + ).then((response) => response.arrayBuffer()); + const dvpd = await fetch( + `${assertSecret(import.meta.env.WOTB_DLC_CDN)}/dlc/${data.dx11.replace( + ".dvpm", + ".dvpd", + )}`, + ).then((response) => response.arrayBuffer()); + const files = await dvp(dvpm, dvpd); + const bar = new ProgressBar( + `Patching ${files.length} files :bar`, + files.length, + ); + + for (const { path, data } of files) { + console.log(`Patching "${path}"...`); + + const { dir } = parsePath(path); + + try { + await mkdir(`${PATCHES_ROOT}/Data/${dir}`, { recursive: true }); + } catch (error) { + console.warn(`Failed to make directory "${dir}"`); + } + + const buffer = Buffer.from(data); + + await writeFile(`${PATCHES_ROOT}/Data/${path}`, new Uint8Array(buffer)); + + bar.tick(); + } + + console.log("patches done for patch"); + + if ("dynamicContentLocalizationsDir" in data) { + console.log( + `${assertSecret(import.meta.env.WOTB_DLC_CDN)}/dlc/${ + data.dynamicContentLocalizationsDir + }/en.yaml`, ); + const localizationsResponse = await fetch( + `${assertSecret(import.meta.env.WOTB_DLC_CDN)}/dlc/${ + data.dynamicContentLocalizationsDir + }/en.yaml`, + ); + console.log("fetched"); + const newStrings = parseYaml(await localizationsResponse.text()); + console.log("got new strings"); + const oldStrings = + await vfs.yaml>(`Data/Strings/en.yaml`); + console.log("got old strings"); + const patchedStrings = { ...oldStrings, ...newStrings }; + const patchedContent = stringifyYaml(patchedStrings); + console.log("stringified"); - for (const { path, data } of files) { - console.log(`Patching "${path}"...`); + await mkdir(`${PATCHES_ROOT}/Data/Strings`, { recursive: true }); + console.log("mkdir"); + await writeFile( + `${PATCHES_ROOT}/Data/Strings/en.yaml`, + new Uint8Array(Buffer.from(patchedContent)), + ); + console.log("file"); + } - const { dir } = parsePath(path); - - try { - await mkdir(`${PATCHES_ROOT}/Data/${dir}`, { recursive: true }); - } catch (error) { - console.warn(`Failed to make directory "${dir}"`); - } - - const buffer = Buffer.from(data); - - await writeFile( - `${PATCHES_ROOT}/Data/${path}`, - new Uint8Array(buffer), - ); - - bar.tick(); - } - - console.log('patches done for patch') - - if ("dynamicContentLocalizationsDir" in data) { - console.log( - `${assertSecret(import.meta.env.WOTB_DLC_CDN)}/dlc/${ - data.dynamicContentLocalizationsDir - }/en.yaml`, - ); - const localizationsResponse = await fetch( - `${assertSecret(import.meta.env.WOTB_DLC_CDN)}/dlc/${ - data.dynamicContentLocalizationsDir - }/en.yaml`, - ); - console.log('fetched') - const newStrings = parseYaml(await localizationsResponse.text()); - console.log('got new strings') - const oldStrings = - await vfs.yaml>(`Data/Strings/en.yaml`); - console.log('got old strings') - const patchedStrings = { ...oldStrings, ...newStrings }; - const patchedContent = stringifyYaml(patchedStrings); - console.log('stringified') - - await mkdir(`${PATCHES_ROOT}/Data/Strings`, { recursive: true }); - console.log('mkdir') - await writeFile( - `${PATCHES_ROOT}/Data/Strings/en.yaml`, - new Uint8Array((Buffer.from(patchedContent))), - ); - console.log('file') - } - - patchIndex++; + patchIndex++; } -vfs.dispose() +vfs.dispose(); process.exit(0); diff --git a/packages/website/src/components/TankCard/index.tsx b/packages/website/src/components/TankCard/index.tsx index 6d3cfa96d..c09fc8477 100644 --- a/packages/website/src/components/TankCard/index.tsx +++ b/packages/website/src/components/TankCard/index.tsx @@ -1,4 +1,4 @@ -import { asset, TankType, type TankDefinition } from "@blitzkit/core"; +import { TankType, type TankDefinition } from "@blitzkit/core"; import { Flex, Text, type TextProps } from "@radix-ui/themes"; import { uniq } from "lodash-es"; import { forwardRef, type ReactNode } from "react"; @@ -60,7 +60,7 @@ export const TankCard = forwardRef( className="tank-search-card" data-provide-link={provideLink} style={{ - backgroundImage: `url(${asset(`flags/scratched/${tank.nation}.webp`)})`, + backgroundImage: `url(/api/flags/scratched/${tank.nation}.webp)`, ...style, }} {...props} @@ -76,7 +76,7 @@ export const TankCard = forwardRef( > {unwrap(tank.name!)} diff --git a/packages/website/src/core/blitzkit/api.ts b/packages/website/src/core/blitzkit/api.ts index 21e5c4b9f..79af8cbe8 100644 --- a/packages/website/src/core/blitzkit/api.ts +++ b/packages/website/src/core/blitzkit/api.ts @@ -1,15 +1,13 @@ -import { - ClientBlitzKitAPI, - ServerBlitzKitAPI, - type BlitzKitAPI, -} from "@blitzkit/core"; +import { type BlitzKitAPI } from "@blitzkit/core"; import { vfs } from "./vfs"; let _api: BlitzKitAPI; if (import.meta.env.SSR) { + const { ServerBlitzKitAPI } = await import("@blitzkit/core"); _api = await new ServerBlitzKitAPI(vfs).init(); } else { + const { ClientBlitzKitAPI } = await import("@blitzkit/core"); _api = new ClientBlitzKitAPI(); } From b2fd85a5b0cf0764ac843e74eaf8aa60fd943f65 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Tue, 4 Aug 2026 13:07:27 -0500 Subject: [PATCH 030/147] some api paths --- packages/core/src/blitzkit/api/client.ts | 26 +++++++++++++++++-- packages/core/src/blitzkit/api/index.ts | 3 --- packages/core/src/blitzkit/api/server.ts | 6 ++--- packages/core/src/blitzkit/index.ts | 2 -- packages/core/src/blitzkit/vfs/index.ts | 5 ---- packages/website/src/core/blitzkit/api.ts | 14 +++++++--- packages/website/src/core/blitzkit/vfs.ts | 2 +- .../definitions/camouflage.pb.ts | 11 ++++++++ .../definitions/consumables.pb.ts | 14 ++++++++++ .../[...chunk_api]/definitions/game.pb.ts | 11 ++++++++ .../[...chunk_api]/definitions/models.pb.ts | 11 ++++++++ .../definitions/provisions.pb.ts | 11 ++++++++ .../[...chunk_api]/definitions/tanks.pb.ts | 11 ++++++++ 13 files changed, 106 insertions(+), 21 deletions(-) delete mode 100644 packages/core/src/blitzkit/api/index.ts delete mode 100644 packages/core/src/blitzkit/vfs/index.ts create mode 100644 packages/website/src/pages/[...chunk_api]/definitions/camouflage.pb.ts create mode 100644 packages/website/src/pages/[...chunk_api]/definitions/consumables.pb.ts create mode 100644 packages/website/src/pages/[...chunk_api]/definitions/game.pb.ts create mode 100644 packages/website/src/pages/[...chunk_api]/definitions/models.pb.ts create mode 100644 packages/website/src/pages/[...chunk_api]/definitions/provisions.pb.ts create mode 100644 packages/website/src/pages/[...chunk_api]/definitions/tanks.pb.ts diff --git a/packages/core/src/blitzkit/api/client.ts b/packages/core/src/blitzkit/api/client.ts index 82f751ce1..f4e1c6aed 100644 --- a/packages/core/src/blitzkit/api/client.ts +++ b/packages/core/src/blitzkit/api/client.ts @@ -1,9 +1,31 @@ import { fetchPB } from "../../protobuf"; -import { GameDefinitions } from "../../protos"; +import { + CamouflageDefinitions, + ConsumableDefinitions, + GameDefinitions, + ProvisionDefinitions, + TankDefinitions, +} from "../../protos"; import { BlitzKitAPI } from "./base"; export class ClientBlitzKitAPI extends BlitzKitAPI { - game() { + gameDefinitions() { return fetchPB("/api/definitions/game.pb", GameDefinitions); } + + consumableDefinitions() { + return fetchPB("/api/definitions/consumables.pb", ConsumableDefinitions); + } + + tankDefinitions() { + return fetchPB("/api/definitions/tanks.pb", TankDefinitions); + } + + camouflageDefinitions() { + return fetchPB("/api/definitions/camouflage.pb", CamouflageDefinitions); + } + + provisionDefinitions() { + return fetchPB("/api/definitions/provisions.pb", ProvisionDefinitions); + } } diff --git a/packages/core/src/blitzkit/api/index.ts b/packages/core/src/blitzkit/api/index.ts deleted file mode 100644 index 2087a06c6..000000000 --- a/packages/core/src/blitzkit/api/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./base"; -export * from "./client"; -export * from "./server"; diff --git a/packages/core/src/blitzkit/api/server.ts b/packages/core/src/blitzkit/api/server.ts index ec274ebdf..95edfbd04 100644 --- a/packages/core/src/blitzkit/api/server.ts +++ b/packages/core/src/blitzkit/api/server.ts @@ -1,5 +1,4 @@ import { - AbstractVFS, Armor, AssaultRanges, AvailableNationsYaml, @@ -59,6 +58,7 @@ import { VehicleDefinitionList, VehicleDefinitions, } from "@blitzkit/core"; +import { AbstractVFS } from "@blitzkit/core/src/blitzkit/vfs/abstract"; import { SUPPORTED_LOCALE_BLITZ_MAP } from "@blitzkit/i18n"; import locales from "@blitzkit/i18n/locales.json"; import { parse as parsePath } from "path"; @@ -147,8 +147,6 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { } async init() { - console.log("Initializing server; this will take a while..."); - console.log("Initializing virtual file system..."); await this.vfs.init(); @@ -1650,7 +1648,7 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { } } -export function Cache(disableInDev = false) { +function Cache(disableInDev = false) { const cache = new WeakMap>>(); return function ( diff --git a/packages/core/src/blitzkit/index.ts b/packages/core/src/blitzkit/index.ts index 88ba099ef..a60517d32 100644 --- a/packages/core/src/blitzkit/index.ts +++ b/packages/core/src/blitzkit/index.ts @@ -1,4 +1,3 @@ -export * from "./api"; export * from "./assertSecret"; export * from "./availableProvisions"; export * from "./averageDefinitions"; @@ -29,6 +28,5 @@ export * from "./tankNames"; export * from "./unionBoundingBox"; export * from "./uniqueGuns"; export * from "./unwrapBlitzkitResponse"; -export * from "./vfs"; export * from "./votes"; export * from "./youtubers"; diff --git a/packages/core/src/blitzkit/vfs/index.ts b/packages/core/src/blitzkit/vfs/index.ts deleted file mode 100644 index 939b46d1c..000000000 --- a/packages/core/src/blitzkit/vfs/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from "./abstract"; -export * from "./local"; -export * from "./mixed"; -export * from "./steam"; -export * from "./zip"; diff --git a/packages/website/src/core/blitzkit/api.ts b/packages/website/src/core/blitzkit/api.ts index 79af8cbe8..437d2dc8a 100644 --- a/packages/website/src/core/blitzkit/api.ts +++ b/packages/website/src/core/blitzkit/api.ts @@ -1,13 +1,19 @@ -import { type BlitzKitAPI } from "@blitzkit/core"; -import { vfs } from "./vfs"; +import type { BlitzKitAPI } from "@blitzkit/core/src/blitzkit/api/base"; let _api: BlitzKitAPI; if (import.meta.env.SSR) { - const { ServerBlitzKitAPI } = await import("@blitzkit/core"); + const { ServerBlitzKitAPI } = await import( + "@blitzkit/core/src/blitzkit/api/server" + ); + const { vfs } = await import("./vfs"); + _api = await new ServerBlitzKitAPI(vfs).init(); } else { - const { ClientBlitzKitAPI } = await import("@blitzkit/core"); + const { ClientBlitzKitAPI } = await import( + "@blitzkit/core/src/blitzkit/api/client" + ); + _api = new ClientBlitzKitAPI(); } diff --git a/packages/website/src/core/blitzkit/vfs.ts b/packages/website/src/core/blitzkit/vfs.ts index a08a49d21..34eb3e1e9 100644 --- a/packages/website/src/core/blitzkit/vfs.ts +++ b/packages/website/src/core/blitzkit/vfs.ts @@ -1,4 +1,4 @@ -import { LocalVFS } from "@blitzkit/core"; +import { LocalVFS } from "@blitzkit/core/src/blitzkit/vfs/local"; export const vfs = new LocalVFS( "/run/media/tresabhi/Windows/Program Files/WindowsApps/7458BE2C.WorldofTanksBlitz_11.19.272.0_x64__x4tje2y229k00", diff --git a/packages/website/src/pages/[...chunk_api]/definitions/camouflage.pb.ts b/packages/website/src/pages/[...chunk_api]/definitions/camouflage.pb.ts new file mode 100644 index 000000000..15dea60ce --- /dev/null +++ b/packages/website/src/pages/[...chunk_api]/definitions/camouflage.pb.ts @@ -0,0 +1,11 @@ +import { CamouflageDefinitions } from "@blitzkit/core"; +import { api } from "../../../core/blitzkit/api"; + +export { getStaticPaths } from "../_index"; + +export async function GET() { + const definitions = await api.camouflageDefinitions(); + const bytes = CamouflageDefinitions.encode(definitions).finish(); + + return new Response(bytes); +} diff --git a/packages/website/src/pages/[...chunk_api]/definitions/consumables.pb.ts b/packages/website/src/pages/[...chunk_api]/definitions/consumables.pb.ts new file mode 100644 index 000000000..da2ce067e --- /dev/null +++ b/packages/website/src/pages/[...chunk_api]/definitions/consumables.pb.ts @@ -0,0 +1,14 @@ +import { ConsumableDefinitions } from "@blitzkit/core"; +import { api } from "../../../core/blitzkit/api"; + +export { getStaticPaths } from "../_index"; + +export async function GET() { + const definitions = await api.consumableDefinitions(); + + console.dir(definitions, { depth: null }); + + const bytes = ConsumableDefinitions.encode(definitions).finish(); + + return new Response(bytes); +} diff --git a/packages/website/src/pages/[...chunk_api]/definitions/game.pb.ts b/packages/website/src/pages/[...chunk_api]/definitions/game.pb.ts new file mode 100644 index 000000000..86200f605 --- /dev/null +++ b/packages/website/src/pages/[...chunk_api]/definitions/game.pb.ts @@ -0,0 +1,11 @@ +import { GameDefinitions } from "@blitzkit/core"; +import { api } from "../../../core/blitzkit/api"; + +export { getStaticPaths } from "../_index"; + +export async function GET() { + const definitions = await api.gameDefinitions(); + const bytes = GameDefinitions.encode(definitions).finish(); + + return new Response(bytes); +} diff --git a/packages/website/src/pages/[...chunk_api]/definitions/models.pb.ts b/packages/website/src/pages/[...chunk_api]/definitions/models.pb.ts new file mode 100644 index 000000000..2399851bd --- /dev/null +++ b/packages/website/src/pages/[...chunk_api]/definitions/models.pb.ts @@ -0,0 +1,11 @@ +import { ModelDefinitions } from "@blitzkit/core"; +import { api } from "../../../core/blitzkit/api"; + +export { getStaticPaths } from "../_index"; + +export async function GET() { + const definitions = await api.modelDefinitions(); + const bytes = ModelDefinitions.encode(definitions).finish(); + + return new Response(bytes); +} diff --git a/packages/website/src/pages/[...chunk_api]/definitions/provisions.pb.ts b/packages/website/src/pages/[...chunk_api]/definitions/provisions.pb.ts new file mode 100644 index 000000000..c352900e1 --- /dev/null +++ b/packages/website/src/pages/[...chunk_api]/definitions/provisions.pb.ts @@ -0,0 +1,11 @@ +import { ProvisionDefinitions } from "@blitzkit/core"; +import { api } from "../../../core/blitzkit/api"; + +export { getStaticPaths } from "../_index"; + +export async function GET() { + const definitions = await api.provisionDefinitions(); + const bytes = ProvisionDefinitions.encode(definitions).finish(); + + return new Response(bytes); +} diff --git a/packages/website/src/pages/[...chunk_api]/definitions/tanks.pb.ts b/packages/website/src/pages/[...chunk_api]/definitions/tanks.pb.ts new file mode 100644 index 000000000..5976401f9 --- /dev/null +++ b/packages/website/src/pages/[...chunk_api]/definitions/tanks.pb.ts @@ -0,0 +1,11 @@ +import { TankDefinitions } from "@blitzkit/core"; +import { api } from "../../../core/blitzkit/api"; + +export { getStaticPaths } from "../_index"; + +export async function GET() { + const definitions = await api.tankDefinitions(); + const bytes = TankDefinitions.encode(definitions).finish(); + + return new Response(bytes); +} From 5f773931bc46c180779bee334f273ab907951f5a Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Tue, 4 Aug 2026 13:19:17 -0500 Subject: [PATCH 031/147] split giant class --- packages/core/src/blitzkit/api/server.ts | 1693 ----------------- packages/core/src/blitzkit/api/server/0.ts | 289 +++ packages/core/src/blitzkit/api/server/1.ts | 727 +++++++ packages/core/src/blitzkit/api/server/2.ts | 34 + packages/core/src/blitzkit/api/server/3.ts | 333 ++++ packages/core/src/blitzkit/api/server/4.ts | 21 + packages/core/src/blitzkit/api/server/5.ts | 42 + packages/core/src/blitzkit/api/server/6.ts | 114 ++ packages/core/src/blitzkit/api/server/7.ts | 117 ++ packages/core/src/blitzkit/api/server/8.ts | 24 + packages/core/src/blitzkit/api/server/9.ts | 58 + .../core/src/blitzkit/api/server/index.ts | 3 + packages/website/src/core/blitzkit/api.ts | 2 +- 13 files changed, 1763 insertions(+), 1694 deletions(-) delete mode 100644 packages/core/src/blitzkit/api/server.ts create mode 100644 packages/core/src/blitzkit/api/server/0.ts create mode 100644 packages/core/src/blitzkit/api/server/1.ts create mode 100644 packages/core/src/blitzkit/api/server/2.ts create mode 100644 packages/core/src/blitzkit/api/server/3.ts create mode 100644 packages/core/src/blitzkit/api/server/4.ts create mode 100644 packages/core/src/blitzkit/api/server/5.ts create mode 100644 packages/core/src/blitzkit/api/server/6.ts create mode 100644 packages/core/src/blitzkit/api/server/7.ts create mode 100644 packages/core/src/blitzkit/api/server/8.ts create mode 100644 packages/core/src/blitzkit/api/server/9.ts create mode 100644 packages/core/src/blitzkit/api/server/index.ts diff --git a/packages/core/src/blitzkit/api/server.ts b/packages/core/src/blitzkit/api/server.ts deleted file mode 100644 index 95edfbd04..000000000 --- a/packages/core/src/blitzkit/api/server.ts +++ /dev/null @@ -1,1693 +0,0 @@ -import { - Armor, - AssaultRanges, - AvailableNationsYaml, - BlitzCrewType, - BlitzModuleType, - BlitzStrings, - BlitzTankClass, - BlitzTankFilterDefinitionCategory, - CamouflageDefinitions, - CamouflagesXml, - CamouflagesYaml, - ChassisDefinitionsList, - CombatRolesYaml, - Consumable, - ConsumableDefinitions, - ConsumablesCommon, - ConsumableTankCategoryFilterCategory, - Crew, - CrewType, - EngineDefinitionsList, - Equalizer, - EquipmentDefinitions, - EquipmentSlot, - GameDefinitions, - GunDefinition, - GunDefinitionsList, - I18nString, - MapDefinitions, - MapsYaml, - ModelDefinitions, - ModuleType, - OptionalDevices, - OptionalDeviceSlots, - Provision, - ProvisionDefinitions, - ProvisionsCommon, - ResearchCost, - ShellDefinitionsList, - ShellKind, - ShellType, - SkillDefinitions, - sluggify, - SquadBattleTypeStylesYaml, - TankClass, - TankDefinitions, - TankmenAvatar, - TankParameters, - TankPrice, - TankPriceType, - TankType, - toUniqueId, - TurretDefinitionsList, - Unlock, - UnlocksListing, - Vector3, - VehicleDefinitionArmor, - VehicleDefinitionList, - VehicleDefinitions, -} from "@blitzkit/core"; -import { AbstractVFS } from "@blitzkit/core/src/blitzkit/vfs/abstract"; -import { SUPPORTED_LOCALE_BLITZ_MAP } from "@blitzkit/i18n"; -import locales from "@blitzkit/i18n/locales.json"; -import { parse as parsePath } from "path"; -import { Vector3Tuple } from "three"; -import { parse as parseYaml } from "yaml"; -import { BlitzKitAPI } from "./base"; - -export class ServerBlitzKitAPI extends BlitzKitAPI { - private vfs: AbstractVFS; - - private nationSlugDiscriminators = { - china: "cn", - european: "eu", - france: "fr", - germany: "de", - japan: "jp", - other: "ot", - uk: "gb", - usa: "us", - ussr: "ru", - }; - private blitzModuleTypeToBlitzkit: Record = - { - chassis: ModuleType.MODULE_TYPE_TRACKS, - engine: ModuleType.MODULE_TYPE_ENGINE, - gun: ModuleType.MODULE_TYPE_GUN, - turret: ModuleType.MODULE_TYPE_TURRET, - vehicle: ModuleType.MODULE_TYPE_VEHICLE, - }; - private blitzkitCrewTypeToBlitz: Record = { - [CrewType.CREW_TYPE_COMMANDER]: "commander", - [CrewType.CREW_TYPE_DRIVER]: "driver", - [CrewType.CREW_TYPE_GUNNER]: "gunner", - [CrewType.CREW_TYPE_LOADER]: "loader", - [CrewType.CREW_TYPE_RADIOMAN]: "radioman", - }; - private blitzCrewTypeToBlitzkit: Record = { - commander: CrewType.CREW_TYPE_COMMANDER, - driver: CrewType.CREW_TYPE_DRIVER, - gunner: CrewType.CREW_TYPE_GUNNER, - loader: CrewType.CREW_TYPE_LOADER, - radioman: CrewType.CREW_TYPE_RADIOMAN, - }; - private blitzTankClassToBlitzkit: Record = { - lightTank: TankClass.TANK_CLASS_LIGHT, - "AT-SPG": TankClass.TANK_CLASS_TANK_DESTROYER, - heavyTank: TankClass.TANK_CLASS_HEAVY, - mediumTank: TankClass.TANK_CLASS_MEDIUM, - }; - private blitzShellKindToBlitzkit: Record = { - ARMOR_PIERCING: ShellType.SHELL_TYPE_AP, - ARMOR_PIERCING_CR: ShellType.SHELL_TYPE_APCR, - HIGH_EXPLOSIVE: ShellType.SHELL_TYPE_HE, - HOLLOW_CHARGE: ShellType.SHELL_TYPE_HEAT, - }; - private blitzTankFilterDefinitionCategoryToBlitzkit: Record< - BlitzTankFilterDefinitionCategory, - ConsumableTankCategoryFilterCategory - > = { - clip: ConsumableTankCategoryFilterCategory.CONSUMABLE_TANK_CATEGORY_FILTER_CATEGORY_CLIP, - }; - - private botPattern = /^.+((tutorial_bot(\d+)?)|(TU))$/; - - private stringsI18n: Record> = {}; - private nationsDir?: string[]; - private tankStringIdMap: Record = {}; - private consumablesCommon: ConsumablesCommon = {}; - private provisionsCommon: ProvisionsCommon = {}; - - // TODO: suffix with "yaml"s and "xml"s - private optionalDevices?: { root: OptionalDevices }; - private optionalDeviceSlots?: { root: OptionalDeviceSlots }; - private tankmenAvatar?: { root: TankmenAvatar }; - private mapsYaml?: MapsYaml; - private camouflagesXml?: { root: CamouflagesXml }; - private camouflagesYaml?: CamouflagesYaml; - private squadBattleTypeStyles?: SquadBattleTypeStylesYaml; - private gameTypeSelectorStyles?: SquadBattleTypeStylesYaml; - private combatRoles?: CombatRolesYaml; - private tierEqualizer?: string[][]; - - constructor(vfs: AbstractVFS) { - super(); - this.vfs = vfs; - } - - async init() { - console.log("Initializing virtual file system..."); - await this.vfs.init(); - - console.log("Fetching game data..."); - this.nationsDir = await this.vfs - .dir("Data/XML/item_defs/vehicles") - .then((files) => files.filter((nation) => nation !== "common")); - - this.optionalDevices = await this.vfs.xml<{ root: OptionalDevices }>( - "Data/XML/item_defs/vehicles/common/optional_devices.xml", - ); - this.optionalDeviceSlots = await this.vfs.xml<{ - root: OptionalDeviceSlots; - }>("Data/XML/item_defs/vehicles/common/optional_device_slots.xml"); - this.tankmenAvatar = await this.vfs.xml<{ root: TankmenAvatar }>( - "Data/XML/item_defs/tankmen/avatar.xml", - ); - this.mapsYaml = await this.vfs.yaml("Data/maps.yaml"); - this.camouflagesXml = await this.vfs.xml<{ root: CamouflagesXml }>( - "Data/XML/item_defs/vehicles/common/camouflages.xml", - ); - this.camouflagesYaml = await this.vfs.yaml( - "Data/camouflages.yaml", - ); - this.squadBattleTypeStyles = await this.vfs.yaml( - `Data/UI/Screens3/Lobby/Hangar/Squad/SquadBattleType.yaml`, - ); - this.gameTypeSelectorStyles = - await this.vfs.yaml( - `Data/UI/Screens/Lobby/Hangar/GameTypeSelector.yaml`, - ); - this.combatRoles = await this.vfs.yaml( - `Data/XML/item_defs/vehicles/common/combat_roles.yaml`, - ); - this.tierEqualizer = await this.vfs.csv( - // spell:disable-next-line - `Data/XML/item_defs/vehicles/common/tier_equializer.csv`, - { delimiter: ";" }, - ); - - console.log("Fetching game localizations..."); - - if (import.meta.env.DEV) { - locales.supported = [locales.supported[0]]; - } - - let fetchedLocalizations = 0; - await Promise.all( - locales.supported.map(async ({ locale }) => { - const blitzLocale = SUPPORTED_LOCALE_BLITZ_MAP[locale]; - const cache = await fetch( - `https://stufficons.wgcdn.co/localizations/${blitzLocale}.yaml`, - ) - .then((response) => response.text()) - .then((string) => parseYaml(string) as BlitzStrings); - const preInstalled = await this.vfs.yaml( - `Data/Strings/${blitzLocale}.yaml`, - ); - - this.stringsI18n[locale] = { - ...cache, - ...preInstalled, - }; - - console.log( - `Fetched localizations for ${locale} (${++fetchedLocalizations}/${locales.supported.length})`, - ); - }), - ); - - console.log("Fetching consumable and provision data..."); - - for (const match of ( - await this.vfs.text( - `Data/XML/item_defs/vehicles/common/consumables/list.xml`, - ) - ).matchAll(//g)) { - if (match[1] === "prototypes") continue; - - Object.assign( - this.consumablesCommon, - ( - await this.vfs.xml<{ root: ConsumablesCommon }>( - `Data/XML/item_defs/vehicles/common/consumables/${match[1]}.xml`, - ) - ).root, - ); - } - - for (const match of ( - await this.vfs.text( - `Data/XML/item_defs/vehicles/common/provisions/list.xml`, - ) - ).matchAll(//g)) { - if (match[1] === "prototypes") continue; - - Object.assign( - this.provisionsCommon, - ( - await this.vfs.xml<{ root: ConsumablesCommon }>( - `Data/XML/item_defs/vehicles/common/provisions/${match[1]}.xml`, - ) - ).root, - ); - } - - console.log("Initialization complete, enjoy :)"); - - return this; - } - - private getString(name: string) { - const collection: Record = { - [locales.default]: this.stringsI18n[locales.default][name], - }; - - for (const { locale } of locales.supported) { - const localizedString = this.stringsI18n[locale][name]; - - if ( - localizedString === undefined || - localizedString === collection[locales.default] - ) { - continue; - } - - collection[locale] = localizedString; - } - - return { locales: collection } satisfies I18nString; - } - - private parseResearchCost(raw: number | string) { - if (typeof raw === "number") { - return { - research_cost_type: { $case: "xp", value: raw }, - } satisfies ResearchCost; - } else { - return { - research_cost_type: { - $case: "seasonal_tokens", - value: { - season: Number(/prx_season_(\d+):\d+/.exec(raw)![1]), - tokens: Number(/prx_season_\d+:(\d+)/.exec(raw)![1]), - }, - }, - } satisfies ResearchCost; - } - } - - @Cache() - async tankDefinitions() { - const tankDefinitions = TankDefinitions.create(); - - const gameModeNativeNames: Record = {}; - const squadBattleTypeGameModeNativeNameMatches = - this.squadBattleTypeStyles!.Prototypes[0].components.UIDataLocalBindingsComponent.data[1][2].matchAll( - /"(\d+)" -> "(battleType\/([a-zA-Z]+))"/g, - ); - const gameTypeGameModeNativeNameMatches = - this.gameTypeSelectorStyles!.Prototypes[0].components.UIDataLocalBindingsComponent.data[1][2].matchAll( - /eGameMode\.([a-zA-Z]+) -> "~res:\/Gfx\/UI\/Hangar\/GameTypes\/battle-type_([^"]+)"/g, - ); - - for (const match of squadBattleTypeGameModeNativeNameMatches) { - const id = Number(match[1]); - gameModeNativeNames[match[3]] = id; - } - - for (const match of gameTypeGameModeNativeNameMatches) { - Object.entries(gameModeNativeNames).forEach(([key, value]) => { - if (key.toLowerCase() === match[2].toLowerCase()) { - gameModeNativeNames[match[1]] = value; - } - }); - } - - const slugRequesters = new Map(); - const idToNation: Record = {}; - - for (const nation of this.nationsDir!) { - const tankList = await this.vfs.xml<{ root: VehicleDefinitionList }>( - `Data/XML/item_defs/vehicles/${nation}/list.xml`, - ); - - for (const tankKey in tankList.root) { - if (this.botPattern.test(tankKey)) continue; - - const tank = tankList.root[tankKey]; - const tankId = toUniqueId(nation, tank.id); - - const name = ( - (tank.shortUserString - ? this.getString(tank.shortUserString) - : undefined) ?? this.getString(tank.userString) - ).locales.en; - - let slug = sluggify(name); - - idToNation[tankId] = nation; - - if (slugRequesters.has(slug)) { - slugRequesters.get(slug)!.push({ id: tankId, key: tankKey }); - } else { - slugRequesters.set(slug, [{ id: tankId, key: tankKey }]); - } - } - } - - const slugs = new Map(); - - slugRequesters.forEach((requesters, slug) => { - if (requesters.length === 1) { - slugs.set(requesters[0].id, slug); - return; - } - - console.warn( - `Multiple tanks share slug ${slug}: ${requesters - .map(({ key }) => key) - .join(", ")}`, - ); - - if (requesters.length !== 2) { - throw new Error("Unresolvable number of duplicates :("); - } - - const nonCanonical = requesters.find(({ key }) => key.endsWith("TUR")); - - if (nonCanonical === undefined) { - console.log("Using nations to discriminate"); - // both are non-tutorial tanks, will have to discriminate using nation - - requesters.forEach(({ id, key }) => { - const nation = idToNation[id]; - const discriminator = - this.nationSlugDiscriminators[ - nation as keyof typeof this.nationSlugDiscriminators - ]; - - console.log(`Solution: ${key} -> ${slug}-${discriminator}`); - slugs.set(id, `${slug}-${discriminator}`); - }); - } else { - console.log("Using tutorial bot suffix to discriminate"); - - const canonical = requesters.find(({ key }) => !key.endsWith("TUR")); - - if (canonical === undefined) { - throw new Error( - "Two tutorial bots share the same slug? The world is truly broken.", - ); - } - - console.log(`Solution: ${canonical.key} -> ${slug}`); - slugs.set(canonical.id, slug); - console.log(`Solution: ${nonCanonical.key} -> ${slug}-tur`); - slugs.set(nonCanonical.id, `${slug}-tur`); - } - }); - - const tankXps = new Map(); - - for (const nation of this.nationsDir!) { - const tankList = await this.vfs.xml<{ root: VehicleDefinitionList }>( - `Data/XML/item_defs/vehicles/${nation}/list.xml`, - ); - const turretList = await this.vfs.xml<{ - root: TurretDefinitionsList; - }>(`Data/XML/item_defs/vehicles/${nation}/components/turrets.xml`); - const gunList = await this.vfs.xml<{ - root: GunDefinitionsList; - }>(`Data/XML/item_defs/vehicles/${nation}/components/guns.xml`); - const shellList = await this.vfs.xml<{ - root: ShellDefinitionsList; - }>(`Data/XML/item_defs/vehicles/${nation}/components/shells.xml`); - const enginesList = await this.vfs.xml<{ - root: EngineDefinitionsList; - }>(`Data/XML/item_defs/vehicles/${nation}/components/engines.xml`); - const chassisList = await this.vfs.xml<{ - root: ChassisDefinitionsList; - }>(`Data/XML/item_defs/vehicles/${nation}/components/chassis.xml`); - - function resolveUnlocks( - blitzModuleTypeToBlitzkit: Record, - unlocks?: BlitzModuleType, - ) { - if (!unlocks) return []; - - return Object.entries(unlocks) - .map(([type, list]) => - (Array.isArray(list) ? list : [list]).map((item) => { - const typeTyped = type as keyof BlitzModuleType; - let rawId: number; - - if (typeTyped === "chassis") { - rawId = chassisList.root.ids[item["#text"]]; - } else if (typeTyped === "engine") { - rawId = enginesList.root.ids[item["#text"]]; - } else if (typeTyped === "gun") { - rawId = gunList.root.ids[item["#text"]]; - } else if (typeTyped === "turret") { - rawId = turretList.root.ids[item["#text"]]; - } else if (typeTyped === "vehicle") { - rawId = tankList.root[item["#text"]].id; - } - - return { - id: toUniqueId(nation, rawId!), - type: blitzModuleTypeToBlitzkit[typeTyped], - cost: { - type: - typeof item.cost === "number" - ? "xp" - : item.cost.split(":")[0], - value: - typeof item.cost === "number" - ? item.cost - : Number(item.cost.split(":")[1]), - }, - } satisfies Unlock; - }), - ) - .flat(); - } - - for (const tankKey in tankList.root) { - if (this.botPattern.test(tankKey)) continue; - - const gunXps = new Map(); - const turretXps = new Map(); - const engineXps = new Map(); - const trackXps = new Map(); - const tank = tankList.root[tankKey]; - let tankPrice: TankPrice; - const tankDefinition = await this.vfs.xml<{ root: VehicleDefinitions }>( - `Data/XML/item_defs/vehicles/${nation}/${tankKey}.xml`, - ); - const tankParameters = await this.vfs.yaml( - `Data/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`, - ); - const tankId = toUniqueId(nation, tank.id); - - const tankTags = tank.tags.split(" "); - const equipment = tankDefinition.root.optDevicePreset; - - this.tankStringIdMap[`${nation}:${tankKey}`] = tankId; - - const slug = slugs.get(tankId); - - if (slug === undefined) { - throw new Error( - `Could not find slug for ${nation}/${tankKey} (${tankId})`, - ); - } - - if (tank.sellPrice) { - tankPrice = { - type: TankPriceType.TANK_PRICE_TYPE_GOLD, - value: tank.sellPrice["#text"] * 2, - }; - } else if (typeof tank.price === "number") { - tankPrice = { - type: TankPriceType.TANK_PRICE_TYPE_CREDITS, - value: tank.price, - }; - } else { - tankPrice = { - type: TankPriceType.TANK_PRICE_TYPE_CREDITS, - value: tank.price["#text"] * 400, - }; - } - - const crew: Crew[] = []; - const fixedCamouflage = tankTags.includes("eventCamouflage_user"); - const totalUnlocks: UnlocksListing[] = []; - - for (const crewKey in tankDefinition.root.crew) { - const value = tankDefinition.root.crew[crewKey as BlitzCrewType]; - let entry: Crew; - const index = crew.findIndex( - ({ type }) => this.blitzkitCrewTypeToBlitz[type] === crewKey, - ); - if (index === -1) { - if (crewKey === "#text") continue; - entry = { - type: this.blitzCrewTypeToBlitzkit[crewKey as BlitzCrewType], - count: 0, - substitute: [], - }; - crew.push(entry); - } else { - entry = crew[index]; - } - - if (typeof value === "string") { - entry.count++; - - if (value !== "") { - entry.substitute = value.split(/\n| /).map((member) => { - return this.blitzCrewTypeToBlitzkit[ - member.trim() as BlitzCrewType - ]; - }); - } - } else { - if (entry.count === undefined) { - entry.count = value.length; - } else { - entry.count += value.length; - } - } - } - - const camouflages = Object.entries( - this.camouflagesXml!.root.camouflages, - ) - .filter(([, camo]) => { - if (!camo.vehicleFilter.include) return false; - if (camo.unlockCostCategory !== "legendary-skins-gold") - return false; - - const includeArray = Array.isArray(camo.vehicleFilter.include) - ? camo.vehicleFilter.include - : [camo.vehicleFilter.include]; - - return includeArray.some((filter) => { - if ("vehicle" in filter && filter.vehicle?.name) { - return filter.vehicle.name === `${nation}:${tankKey}`; - } - - return false; - }); - }) - .map(([, camo]) => camo.id); - - let equalizerEntry = this.tierEqualizer!.find( - (line) => line[0] === `${nation}:${tankKey}`, - ); - - let equalizer: Equalizer | undefined; - - if (equalizerEntry) { - const [health, penetration, module_health, damage, armor] = - equalizerEntry?.slice(1).map(Number); - equalizer = { health, penetration, module_health, damage, armor }; - } - - tankDefinitions.tanks[tankId] = { - ancestors: [], - successors: [], - id: tankId, - dev_name: tankKey, - roles: {}, - camouflages: camouflages, - fixed_camouflage: fixedCamouflage, - crew, - weight: tankDefinition.root.hull.weight, - health: tankDefinition.root.hull.maxHealth, - speed_forwards: tankDefinition.root.speedLimits.forward, - speed_backwards: tankDefinition.root.speedLimits.backward, - equipment_preset: - typeof equipment === "string" ? equipment : equipment.at(-1)!, - max_consumables: tankDefinition.root.consumableSlots, - max_provisions: tankDefinition.root.provisionSlots, - name: - (tank.shortUserString - ? this.getString(tank.shortUserString) - : undefined) ?? this.getString(tank.userString), - slug, - nation, - type: tankTags.includes("collectible") - ? TankType.TANK_TYPE_COLLECTOR - : (typeof tank.price === "number" ? false : "gold" in tank.price) - ? TankType.TANK_TYPE_PREMIUM - : TankType.TANK_TYPE_RESEARCHABLE, - tier: tank.level, - class: this.blitzTankClassToBlitzkit[tankTags[0] as BlitzTankClass], - testing: tankTags.includes("testTank"), - deprecated: tankTags.includes("deprecated"), - price: tankPrice, - camouflage_still: tankDefinition.root.invisibility.still, - camouflage_moving: tankDefinition.root.invisibility.moving, - camouflage_on_fire: tankDefinition.root.invisibility.firePenalty, - turrets: [], - engines: [], - tracks: [], - equalizer, - }; - - if (tank.combatRole) { - Object.entries(tank.combatRole).forEach(([gameMode, role]) => { - const id = Object.entries(gameModeNativeNames).find( - ([key]) => key.toLowerCase() === gameMode.toLowerCase(), - )?.[1]; - - if (id === undefined) { - throw new Error( - `Unknown game mode in tank ${tankKey}: ${gameMode}`, - ); - } - - tankDefinitions.tanks[tankId].roles[id] = - this.combatRoles![role].id; - }); - } - - Object.keys(tankDefinition.root.chassis).forEach((key) => { - const track = tankDefinition.root.chassis[key]; - const trackId = toUniqueId(nation, chassisList.root.ids[key]); - const terrainResistances = track.terrainResistance - .split(" ") - .map(Number); - - totalUnlocks.push(track.unlocks); - tankDefinitions.tanks[tankId].tracks.push({ - id: trackId, - weight: track.weight, - name: this.getString(track.userString), - traverse_speed: track.rotationSpeed, - dispersion_move: track.shotDispersionFactors.vehicleMovement, - dispersion_traverse: track.shotDispersionFactors.vehicleRotation, - resistance_hard: terrainResistances[0], - resistance_medium: terrainResistances[1], - resistance_soft: terrainResistances[2], - tier: track.level, - unlocks: resolveUnlocks( - this.blitzModuleTypeToBlitzkit, - track.unlocks, - ), - }); - }); - - Object.keys(tankDefinition.root.engines).forEach((engineKey) => { - const engine = tankDefinition.root.engines[engineKey]; - const engineListEntry = enginesList.root.shared[engineKey]; - const engineId = toUniqueId(nation, enginesList.root.ids[engineKey]); - - totalUnlocks.push(engine.unlocks); - tankDefinitions.tanks[tankId].engines.push({ - id: engineId, - name: this.getString(engineListEntry.userString), - fire_chance: engineListEntry.fireStartingChance, - tier: engineListEntry.level, - weight: engineListEntry.weight, - power: engineListEntry.power, - unlocks: resolveUnlocks( - this.blitzModuleTypeToBlitzkit, - engine.unlocks, - ), - }); - }); - - let turretIndex = 0; - for (const turretKey in tankDefinition.root.turrets0) { - const turret = tankDefinition.root.turrets0[turretKey]; - const turretId = toUniqueId(nation, turretList.root.ids[turretKey]); - - totalUnlocks.push(turret.unlocks); - - tankDefinitions.tanks[tankId].turrets.push({ - id: turretId, - traverse_speed: turret.rotationSpeed, - name: this.getString(turret.userString), - tier: turret.level, - guns: [], - health: turret.maxHealth, - view_range: turret.circularVisionRadius, - weight: turret.weight, - unlocks: resolveUnlocks( - this.blitzModuleTypeToBlitzkit, - turret.unlocks, - ), - }); - - let gunIndex = 0; - for (const gunKey in turret.guns) { - const gun = turret.guns[gunKey]; - const gunId = toUniqueId(nation, gunList.root.ids[gunKey]); - const gunListEntry = gunList.root.shared[gunKey]; - const gunName = this.getString(gunListEntry.userString); - const gunType = - "clip" in gun - ? gun.pumpGunMode - ? "autoReloader" - : "autoLoader" - : "regular"; - const gunClipCount = gunType === "regular" ? 1 : gun.clip!.count; - const shotDispersionFactors = - gun.shotDispersionFactors ?? gunListEntry.shotDispersionFactors; - let assault_ranges: AssaultRanges | undefined; - - if (gun.extras?.trayShell) { - const types = gun.extras.trayShell.kinds - .split(" ") - .map((string) => { - const trimmed = string.trim(); - - if (trimmed in this.blitzShellKindToBlitzkit) { - return this.blitzShellKindToBlitzkit[trimmed as ShellKind]; - } - - throw new SyntaxError(`Invalid shell kind: ${trimmed}`); - }); - const sectorNames = Object.keys(gun.extras.trayShell.sectors); - - if (sectorNames.length !== 1 || sectorNames[0] !== "sector") { - throw new SyntaxError("Invalid tray shell sector"); - } - - const sector = gun.extras.trayShell.sectors.sector; - - assault_ranges = { - types, - ranges: sector.map((value) => ({ - factor: value.factor, - distance: value.distance, - })), - }; - } - - totalUnlocks.push(gun.unlocks); - - tankDefinitions.tanks[tankId].turrets[turretIndex].guns.push({ - id: gunId, - weight: gunListEntry.weight, - rotation_speed: gunListEntry.rotationSpeed, - name: gunName, - tier: gunListEntry.level, - shells: [], - camouflage_loss: - typeof gun.invisibilityFactorAtShot === "number" - ? gun.invisibilityFactorAtShot - : gun.invisibilityFactorAtShot.at(-1)!, - aim_time: gun.aimingTime ?? gunListEntry.aimingTime, - dispersion_base: - gun.shotDispersionRadius ?? gunListEntry.shotDispersionRadius, - dispersion_damaged: shotDispersionFactors.whileGunDamaged, - dispersion_shot: shotDispersionFactors.afterShot, - dispersion_traverse: shotDispersionFactors.turretRotation, - unlocks: resolveUnlocks( - this.blitzModuleTypeToBlitzkit, - gun.unlocks, - ), - shell_capacity: gun.maxAmmo ?? gunListEntry.maxAmmo, - assault_ranges, - burst: - gun.burst && gun.burst.count > 1 - ? { - count: gun.burst.count, - interval: 60 / gun.burst.rate, - } - : undefined, - gun_type: - gunType === "regular" - ? { - $case: "regular", - value: { - reload: gun.reloadTime, - }, - } - : gunType === "autoReloader" - ? { - $case: "auto_reloader", - value: { - intra_clip: 60 / gun.clip!.rate, - shell_count: gunClipCount, - shell_reloads: gun - .pumpGunReloadTimes!.split(" ") - .map(Number), - }, - } - : { - $case: "auto_loader", - value: { - intra_clip: 60 / gun.clip!.rate, - clip_reload: gun.reloadTime, - shell_count: gunClipCount, - }, - }, - } satisfies GunDefinition); - - for (const shellKey in gunListEntry.shots) { - const gunShellEntry = gunListEntry.shots[shellKey]; - const shell = shellList.root[shellKey]; - const shellId = toUniqueId(nation, shell.id); - const shellName = this.getString(shell.userString); - const penetrationRaw = gunShellEntry.piercingPower - .split(" ") - .filter((penetrationString) => penetrationString !== "") - .map(Number); - - tankDefinitions.tanks[tankId].turrets[turretIndex].guns[ - gunIndex - ].shells.push({ - id: shellId, - name: shellName, - velocity: gunShellEntry.speed, - armor_damage: shell.damage.armor, - module_damage: shell.damage.devices, - caliber: shell.caliber, - normalization: shell.normalizationAngle, - ricochet: shell.ricochetAngle, - type: this.blitzShellKindToBlitzkit[shell.kind], - explosion_radius: - shell.kind === "HIGH_EXPLOSIVE" - ? (shell.explosionRadius ?? 0) - : undefined, - icon: shell.icon, - penetration: { - near: penetrationRaw[0], - far: penetrationRaw[1], - }, - range: gunShellEntry.maxDistance, - }); - } - - gunIndex++; - } - - turretIndex++; - } - - for (const unlocks of totalUnlocks) { - if (unlocks === undefined) continue; - - for (const key in unlocks) { - const value = unlocks[key as keyof BlitzModuleType]; - - for (const vehicle of Array.isArray(value) ? value : [value]) { - switch (key as keyof BlitzModuleType) { - case "vehicle": { - const tankListEntry = tankList.root[vehicle["#text"]]; - const currentTank = tankDefinitions.tanks[tankId]; - const successorId = toUniqueId(nation, tankListEntry.id); - - tankXps.set( - successorId, - this.parseResearchCost(vehicle.cost), - ); - - if (currentTank.successors === undefined) { - currentTank.successors = []; - } - if (!currentTank.successors!.includes(successorId)) { - currentTank.successors!.push(successorId); - } - break; - } - - case "gun": { - gunXps.set( - toUniqueId(nation, gunList.root.ids[vehicle["#text"]]), - this.parseResearchCost(vehicle.cost), - ); - break; - } - - case "turret": { - turretXps.set( - toUniqueId(nation, turretList.root.ids[vehicle["#text"]]), - this.parseResearchCost(vehicle.cost), - ); - break; - } - - case "engine": { - engineXps.set( - toUniqueId(nation, enginesList.root.ids[vehicle["#text"]]), - this.parseResearchCost(vehicle.cost), - ); - break; - } - - case "chassis": { - trackXps.set( - toUniqueId(nation, chassisList.root.ids[vehicle["#text"]]), - this.parseResearchCost(vehicle.cost), - ); - break; - } - } - } - } - } - - Object.values(tankDefinitions.tanks[tankId].turrets).forEach( - (turret) => { - turret.research_cost = turretXps.get(turret.id); - - Object.values(turret.guns).forEach((gunRaw) => { - gunRaw.research_cost = gunXps.get(gunRaw.id); - }); - }, - ); - - Object.values(tankDefinitions.tanks[tankId].engines).forEach( - (engine) => { - engine.research_cost = engineXps.get(engine.id); - }, - ); - - Object.values(tankDefinitions.tanks[tankId].tracks).forEach((track) => { - track.research_cost = trackXps.get(track.id); - }); - } - } - - Object.values(tankDefinitions.tanks).forEach((tank) => { - tank.research_cost = tankXps.get(tank.id); - }); - - Object.values(tankDefinitions.tanks).forEach((tank) => { - tank.successors?.forEach((predecessorId) => { - if ( - !tankDefinitions.tanks[predecessorId].ancestors?.includes(tank.id) - ) { - tankDefinitions.tanks[predecessorId].ancestors?.push(tank.id); - } - }); - }); - - return tankDefinitions; - } - - @Cache() - async camouflageDefinitions() { - const camouflageDefinitions = CamouflageDefinitions.create(); - - for (const camoKey in this.camouflagesXml!.root.camouflages) { - const camo = this.camouflagesXml!.root.camouflages[camoKey]; - - const yamlEntry = this.camouflagesYaml![camoKey]; - const fullName = yamlEntry.userString - ? this.getString(yamlEntry.userString) - : undefined; - const shortName = yamlEntry.shortUserString - ? this.getString(yamlEntry.shortUserString) - : undefined; - const resolvedTankName = shortName ?? fullName; - const resolvedTankNameFull = - resolvedTankName === fullName ? undefined : fullName; - - camouflageDefinitions.camouflages[camo.id] = { - id: camo.id, - name: this.getString(camo.userString), - tank_name: resolvedTankName, - tank_name_full: resolvedTankNameFull, - }; - } - - return camouflageDefinitions; - } - - private vector3TupleToBlitzkit(tuple: Vector3Tuple) { - return { x: tuple[0], y: tuple[1], z: tuple[2] } satisfies Vector3; - } - - private assignArmor( - raw: VehicleDefinitionArmor[string], - id: number, - armor: Armor, - ) { - if (typeof raw === "number") { - armor.thickness[id] = raw; - } else if (Array.isArray(raw)) { - armor.thickness[id] = raw.at(-1)!; - } else { - if (!armor.spaced) armor.spaced = []; - armor.thickness[id] = raw["#text"]; - armor.spaced.push(id); - } - } - - @Cache() - async modelDefinitions() { - const modelDefinitions = ModelDefinitions.create(); - - for (const nation of this.nationsDir!) { - const tankList = await this.vfs.xml<{ root: VehicleDefinitionList }>( - `Data/XML/item_defs/vehicles/${nation}/list.xml`, - ); - const turretList = await this.vfs.xml<{ - root: TurretDefinitionsList; - }>(`Data/XML/item_defs/vehicles/${nation}/components/turrets.xml`); - const gunList = await this.vfs.xml<{ - root: GunDefinitionsList; - }>(`Data/XML/item_defs/vehicles/${nation}/components/guns.xml`); - const enginesList = await this.vfs.xml<{ - root: EngineDefinitionsList; - }>(`Data/XML/item_defs/vehicles/${nation}/components/engines.xml`); - const chassisList = await this.vfs.xml<{ - root: ChassisDefinitionsList; - }>(`Data/XML/item_defs/vehicles/${nation}/components/chassis.xml`); - - for (const tankKey in tankList.root) { - if (this.botPattern.test(tankKey)) continue; - - const gunXps = new Map(); - const turretXps = new Map(); - const engineXps = new Map(); - const trackXps = new Map(); - const tank = tankList.root[tankKey]; - const tankDefinition = await this.vfs.xml<{ root: VehicleDefinitions }>( - `Data/XML/item_defs/vehicles/${nation}/${tankKey}.xml`, - ); - const tankParameters = await this.vfs.yaml( - `Data/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`, - ); - const turretOrigin = tankDefinition.root.hull.turretPositions.turret - .split(" ") - .map(Number) as Vector3Tuple; - const tankId = toUniqueId(nation, tank.id); - - const hullArmor: Armor = { spaced: [], thickness: {} }; - this.tankStringIdMap[`${nation}:${tankKey}`] = tankId; - - Object.keys(tankDefinition.root.hull.armor) - .filter((name) => name.startsWith("armor_")) - .forEach((name) => { - const armorIdString = name.match(/armor_(\d+)/)?.[1]; - - if (armorIdString === undefined) { - throw new SyntaxError(`Invalid armor id: ${name}`); - } - - const armorId = parseInt(armorIdString); - const armorRaw = tankDefinition.root.hull.armor[name]; - - this.assignArmor(armorRaw, armorId, hullArmor); - }); - const crew: Crew[] = []; - - for (const crewKey in tankDefinition.root.crew) { - const value = tankDefinition.root.crew[crewKey as BlitzCrewType]; - let entry: Crew; - const index = crew.findIndex( - ({ type }) => this.blitzkitCrewTypeToBlitz[type] === crewKey, - ); - if (index === -1) { - if (crewKey === "#text") continue; - - entry = { - type: this.blitzCrewTypeToBlitzkit[crewKey as BlitzCrewType], - count: 0, - substitute: [], - }; - crew.push(entry); - } else { - entry = crew[index]; - } - - if (typeof value === "string") { - entry.count++; - - if (value !== "") { - entry.substitute = value.split(/\n| /).map((member) => { - return this.blitzCrewTypeToBlitzkit[ - member.trim() as BlitzCrewType - ]; - }); - } - } else { - if (entry.count === undefined) { - entry.count = value.length; - } else { - entry.count += value.length; - } - } - } - - modelDefinitions.models[tankId] = { - armor: hullArmor, - turret_origin: this.vector3TupleToBlitzkit(turretOrigin), - initial_turret_rotation: tankDefinition.root.hull - .turretInitialRotation - ? { - yaw: tankDefinition.root.hull.turretInitialRotation.yaw, - pitch: tankDefinition.root.hull.turretInitialRotation.pitch, - roll: tankDefinition.root.hull.turretInitialRotation.roll, - } - : undefined, - bounding_box: { - min: this.vector3TupleToBlitzkit( - tankParameters.collision.hull.bbox.min, - ), - max: this.vector3TupleToBlitzkit( - tankParameters.collision.hull.bbox.max, - ), - }, - turrets: {}, - tracks: {}, - }; - - for (const key in tankDefinition.root.chassis) { - const track = tankDefinition.root.chassis[key]; - const trackId = toUniqueId(nation, chassisList.root.ids[key]); - const trackArmorRaw = track.armor.leftTrack; - const hullOrigin = track.hullPosition - .split(" ") - .map(Number) as Vector3Tuple; - - modelDefinitions.models[tankId].tracks[trackId] = { - thickness: - typeof trackArmorRaw === "number" - ? trackArmorRaw - : trackArmorRaw["#text"], - origin: this.vector3TupleToBlitzkit(hullOrigin), - }; - } - - Object.keys(tankDefinition.root.turrets0).forEach((turretKey) => { - const turret = tankDefinition.root.turrets0[turretKey]; - const turretModel = Number( - parsePath(turret.models.undamaged).name.split("_")[1], - ); - const turretId = toUniqueId(nation, turretList.root.ids[turretKey]); - const turretYaw = ( - typeof turret.yawLimits === "string" - ? turret.yawLimits - : turret.yawLimits.at(-1)! - ) - .split(" ") - .map(Number) as [number, number]; - const gunOrigin = ( - typeof turret.gunPosition === "string" - ? turret.gunPosition - : turret.gunPosition[0] - ) - .split(" ") - .map(Number) as Vector3Tuple; - const turretArmor: Armor = { thickness: {}, spaced: [] }; - - Object.keys(turret.armor) - .filter((name) => name.startsWith("armor_")) - .forEach((name) => { - const armorIdString = name.match(/armor_(\d+)/)?.[1]; - - if (armorIdString === undefined) { - throw new SyntaxError(`Invalid armor id: ${name}`); - } - - const armorId = parseInt(armorIdString); - const armorRaw = turret.armor[name]; - - this.assignArmor(armorRaw, armorId, turretArmor); - }); - - turret.userString; - - modelDefinitions.models[tankId].turrets[turretId] = { - armor: turretArmor, - gun_origin: this.vector3TupleToBlitzkit(gunOrigin), - model_id: turretModel, - yaw: - -turretYaw[0] + turretYaw[1] === 360 - ? undefined - : { min: turretYaw[0], max: turretYaw[1] }, - guns: {}, - bounding_box: { - min: this.vector3TupleToBlitzkit( - tankParameters.collision[ - parsePath(turret.hitTester.collisionModel).name.toLowerCase() - ].bbox.min, - ), - max: this.vector3TupleToBlitzkit( - tankParameters.collision[ - parsePath(turret.hitTester.collisionModel).name.toLowerCase() - ].bbox.max, - ), - }, - }; - - Object.keys(turret.guns).forEach((gunKey, gunIndex) => { - const gun = turret.guns[gunKey]; - const gunId = toUniqueId(nation, gunList.root.ids[gunKey]); - const gunListEntry = gunList.root.shared[gunKey]; - const pitchLimitsRaw = gun.pitchLimits ?? gunListEntry.pitchLimits; - const gunPitch = ( - typeof pitchLimitsRaw === "string" - ? pitchLimitsRaw - : pitchLimitsRaw.at(-1)! - ) - .split(" ") - .map(Number) as [number, number]; - const gunModel = Number( - parsePath(gun.models.undamaged).name.split("_")[1], - ); - const front = gun.extraPitchLimits?.front - ? gun.extraPitchLimits.front.split(" ").map(Number) - : undefined; - const back = gun.extraPitchLimits?.back - ? gun.extraPitchLimits.back.split(" ").map(Number) - : undefined; - const transition = gun.extraPitchLimits?.transition - ? typeof gun.extraPitchLimits.transition === "number" - ? gun.extraPitchLimits.transition - : gun.extraPitchLimits.transition.at(-1)! - : undefined; - const gunArmor: Armor = { thickness: {}, spaced: [] }; - const maskName = `gun_${gunModel.toString().padStart(2, "0")}`; - const maskEnabled = - tankParameters.maskSlice?.[maskName]?.enabled ?? false; - let mask: number | undefined; - - if (maskEnabled) { - const maskRaw = tankParameters.maskSlice![maskName]!; - mask = maskRaw.planePosition[1]; - } else { - mask = undefined; - } - - Object.keys(gun.armor) - .filter((name) => name.startsWith("armor_")) - .forEach((name) => { - const armorIdString = name.match(/armor_(\d+)/)?.[1]; - if (armorIdString === undefined) { - throw new SyntaxError(`Invalid armor id: ${name}`); - } - const armorId = parseInt(armorIdString); - const armorRaw = gun.armor[name]; - - this.assignArmor(armorRaw, armorId, gunArmor); - }); - - modelDefinitions.models[tankId].turrets[turretId].guns[gunId] = { - armor: gunArmor, - model_id: gunModel, - mask, - thickness: - gun.armor.gun === undefined - ? 0 - : typeof gun.armor.gun === "number" - ? gun.armor.gun - : gun.armor.gun["#text"], - pitch: { - min: gunPitch[0], - max: gunPitch[1], - - front: front - ? { - min: front[0], - max: front[1], - range: front[2], - } - : undefined, - back: back - ? { - min: back[0], - max: back[1], - range: back[2], - } - : undefined, - transition, - }, - }; - }); - }); - } - } - - return modelDefinitions; - } - - @Cache() - async mapDefinitions() { - const mapDefinitions = MapDefinitions.create(); - - for (const key in this.mapsYaml!.maps) { - const map = this.mapsYaml!.maps[key]; - - mapDefinitions.maps[map.id] = { - id: map.id, - name: this.getString(`#maps:${key}:${map.localName}`), - }; - } - - return mapDefinitions; - } - - @Cache() - async equipmentDefinitions() { - const equipmentDefinitions = EquipmentDefinitions.create(); - - Object.entries(this.optionalDevices!.root).forEach( - ([optionalDeviceKey, optionalDeviceEntry]) => { - if (optionalDeviceKey === "nextAvailableId") return; - - equipmentDefinitions.equipments[optionalDeviceEntry.id] = { - name: this.getString(optionalDeviceEntry.userString), - description: this.getString(optionalDeviceEntry.description), - }; - }, - ); - - Object.entries(this.optionalDeviceSlots!.root.presets).forEach( - ([optionalDeviceSlotKey, optionalDeviceSlotEntry]) => { - if (optionalDeviceSlotKey === "emptyPreset") return; - - equipmentDefinitions.presets[optionalDeviceSlotKey] = { - slots: Object.values(optionalDeviceSlotEntry) - .map((level) => { - return Object.values(level).map((options) => { - return { - left: this.optionalDevices!.root[options.device0].id, - right: this.optionalDevices!.root[options.device1].id, - } satisfies EquipmentSlot; - }); - }) - .flat(), - }; - }, - ); - - return equipmentDefinitions; - } - - @Cache() - async consumableDefinitions() { - const consumableDefinitions = ConsumableDefinitions.create(); - - Object.entries(this.consumablesCommon).forEach(([key, consumable]) => { - const entry: Consumable = { - id: consumable.id, - game_mode_exclusive: "gameModeFilter" in consumable, - cooldown: consumable.script.cooldown, - duration: consumable.script.duration, - name: this.getString(consumable.userString), - exclude: [], - include: [], - }; - consumableDefinitions.consumables[consumable.id] = entry; - - const includeRaw = consumable.vehicleFilter?.include.vehicle; - const excludeRaw = consumable.vehicleFilter?.exclude?.vehicle; - - if (includeRaw) { - entry.include = []; - - if ("minLevel" in includeRaw) { - entry.include.push({ - filter_type: { - $case: "tiers", - value: { - min: includeRaw.minLevel, - max: includeRaw.maxLevel, - }, - }, - }); - } else if ("name" in includeRaw) { - entry.include.push({ - filter_type: { - $case: "ids", - value: { - ids: includeRaw.name.split(/ +/).map((key) => { - return this.tankStringIdMap[key]; - }), - }, - }, - }); - } else throw new SyntaxError("Unhandled include type"); - - if (consumable.vehicleFilter?.include.nations) { - entry.include.push({ - filter_type: { - $case: "nations", - value: { - nations: consumable.vehicleFilter.include.nations.split(" "), - }, - }, - }); - } - } - - if (excludeRaw) { - entry.exclude = []; - - if ("name" in excludeRaw) { - entry.exclude!.push({ - filter_type: { - $case: "ids", - value: { - ids: excludeRaw.name.split(/ +/).map((key) => { - return this.tankStringIdMap[key]; - }), - }, - }, - }); - } else if ("extendedTags" in excludeRaw) { - entry.exclude!.push({ - filter_type: { - $case: "categories", - value: { - categories: excludeRaw.extendedTags - .split(" ") - .map( - (item) => - this.blitzTankFilterDefinitionCategoryToBlitzkit[ - item as BlitzTankFilterDefinitionCategory - ], - ), - }, - }, - }); - } else throw new SyntaxError("Unhandled exclude type"); - - if (consumable.vehicleFilter?.exclude?.nations) { - entry.exclude!.push({ - filter_type: { - $case: "nations", - value: { - nations: consumable.vehicleFilter.exclude.nations.split(" "), - }, - }, - }); - } - } - }); - - return consumableDefinitions; - } - - @Cache() - async provisionDefinitions() { - const provisionDefinitions = ProvisionDefinitions.create(); - - Object.entries(this.provisionsCommon).forEach(([key, provision]) => { - const entry: Provision = { - id: provision.id, - exclude: [], - include: [], - game_mode_exclusive: "gameModeFilter" in provision, - name: this.getString(provision.userString), - }; - provisionDefinitions.provisions[provision.id] = entry; - - const includeRaw = provision.vehicleFilter?.include.vehicle; - const excludeRaw = provision.vehicleFilter?.exclude?.vehicle; - - if (includeRaw) { - entry.include = []; - - if ("minLevel" in includeRaw) { - entry.include.push({ - filter_type: { - $case: "tiers", - value: { - min: includeRaw.minLevel, - max: includeRaw.maxLevel, - }, - }, - }); - } else if ("name" in includeRaw) { - entry.include.push({ - filter_type: { - $case: "ids", - value: { - ids: includeRaw.name.split(/ +/).map((key) => { - return this.tankStringIdMap[key]; - }), - }, - }, - }); - } else throw new SyntaxError("Unhandled include type"); - - if (provision.vehicleFilter?.include.nations) { - entry.include.push({ - filter_type: { - $case: "nations", - value: { - nations: provision.vehicleFilter.include.nations.split(" "), - }, - }, - }); - } - } - - if (excludeRaw) { - entry.exclude = []; - - if ("name" in excludeRaw) { - entry.exclude!.push({ - filter_type: { - $case: "ids", - value: { - ids: excludeRaw.name - .split(/ +/) - .map((key) => this.tankStringIdMap[key]), - }, - }, - }); - } else if ("extendedTags" in excludeRaw) { - entry.exclude!.push({ - filter_type: { - $case: "categories", - value: { - categories: excludeRaw.extendedTags - .split(" ") - .map( - (item) => - this.blitzTankFilterDefinitionCategoryToBlitzkit[ - item as BlitzTankFilterDefinitionCategory - ], - ), - }, - }, - }); - } else throw new SyntaxError("Unhandled exclude type"); - - if (provision.vehicleFilter?.exclude?.nations) { - entry.exclude!.push({ - filter_type: { - $case: "nations", - value: { - nations: provision.vehicleFilter.exclude.nations.split(" "), - }, - }, - }); - } - } - - if (provision.script.bonusValues?.crewLevelIncrease !== undefined) { - provisionDefinitions.provisions[provision.id].crew = - provision.script.bonusValues?.crewLevelIncrease; - } - }); - - return provisionDefinitions; - } - - @Cache() - async skillDefinitions() { - const skillDefinitions = SkillDefinitions.create(); - - for (const tankClass in this.tankmenAvatar!.root.skillsByClasses) { - const skills = - this.tankmenAvatar!.root.skillsByClasses[ - Number(tankClass) as TankClass - ]; - - skillDefinitions.classes[ - this.blitzTankClassToBlitzkit[tankClass as BlitzTankClass] - ] = { skills: skills.split(" ") }; - } - - return skillDefinitions; - } - - @Cache() - async gameDefinitions() { - const gameDefinitions = GameDefinitions.create(); - - const consumableNativeNames: Record = {}; - const provisionNativeNames: Record = {}; - - Object.entries(this.consumablesCommon).forEach(([key, consumable]) => { - consumableNativeNames[key] = consumable.id; - }); - - Object.entries(this.provisionsCommon).forEach(([key, provision]) => { - provisionNativeNames[key] = provision.id; - }); - - const version = (await this.vfs.text("Data/version.txt")).split(" ")[0]; - - gameDefinitions.version = version; - gameDefinitions.nations = ( - await this.vfs.yaml("Data/available_nations.yaml") - ).available_nations; - - for (const match of this.squadBattleTypeStyles!.Prototypes[0].components.UIDataLocalBindingsComponent.data[1][2].matchAll( - /"(\d+)" -> "(battleType\/([a-zA-Z]+))"/g, - )) { - const id = Number(match[1]); - const name = this.getString(match[2]); - - gameDefinitions.gameModes[id] = { - name, - }; - } - - Object.entries(this.combatRoles!).forEach(([, value]) => { - gameDefinitions.roles[value.id] = { provisions: [], consumables: [] }; - - value.default_abilities.forEach((ability) => { - if (ability in consumableNativeNames) { - gameDefinitions.roles[value.id].consumables.push( - consumableNativeNames[ability], - ); - } else if (ability in provisionNativeNames) { - gameDefinitions.roles[value.id].provisions.push( - provisionNativeNames[ability], - ); - } else throw new Error(`Unknown ability ${ability}`); - }); - }); - - return gameDefinitions; - } -} - -function Cache(disableInDev = false) { - const cache = new WeakMap>>(); - - return function ( - target: (this: This, ...args: Arguments) => Promise, - // TODO: evaluate if we need context to make the error trace easier - // context: ClassMethodDecoratorContext< - // This, - // (this: This, ...args: Arguments) => Promise - // >, - ) { - return async function replacementMethod(this: This, ...args: Arguments) { - const key = args.join("-"); - let thisCache: Map>; - - if (cache.has(this as object)) { - thisCache = cache.get(this as object) as Map>; - } else { - thisCache = new Map(); - cache.set(this as object, thisCache); - } - - const disabled = disableInDev && import.meta.env.DEV; - - if (thisCache.has(key) && !disabled) { - return thisCache.get(key)!; - } - - const promise = (async () => { - try { - return await target.apply(this, args); - } catch (error) { - thisCache!.delete(key); - throw error; - } - })(); - - thisCache.set(key, promise); - - return promise; - }; - }; -} diff --git a/packages/core/src/blitzkit/api/server/0.ts b/packages/core/src/blitzkit/api/server/0.ts new file mode 100644 index 000000000..75eb3c240 --- /dev/null +++ b/packages/core/src/blitzkit/api/server/0.ts @@ -0,0 +1,289 @@ +import { + BlitzCrewType, + BlitzModuleType, + BlitzStrings, + BlitzTankClass, + BlitzTankFilterDefinitionCategory, + CamouflagesXml, + CamouflagesYaml, + CombatRolesYaml, + ConsumablesCommon, + ConsumableTankCategoryFilterCategory, + CrewType, + I18nString, + MapsYaml, + ModuleType, + OptionalDevices, + OptionalDeviceSlots, + ProvisionsCommon, + ShellKind, + ShellType, + SquadBattleTypeStylesYaml, + TankClass, + TankmenAvatar, +} from "@blitzkit/core"; +import { AbstractVFS } from "@blitzkit/core/src/blitzkit/vfs/abstract"; +import { SUPPORTED_LOCALE_BLITZ_MAP } from "@blitzkit/i18n"; +import locales from "@blitzkit/i18n/locales.json"; +import { parse as parseYaml } from "yaml"; +import { BlitzKitAPI } from "../base"; + +export abstract class ServerBlitzKitAPI0 extends BlitzKitAPI { + protected vfs: AbstractVFS; + + protected nationSlugDiscriminators = { + china: "cn", + european: "eu", + france: "fr", + germany: "de", + japan: "jp", + other: "ot", + uk: "gb", + usa: "us", + ussr: "ru", + }; + protected blitzModuleTypeToBlitzkit: Record< + keyof BlitzModuleType, + ModuleType + > = { + chassis: ModuleType.MODULE_TYPE_TRACKS, + engine: ModuleType.MODULE_TYPE_ENGINE, + gun: ModuleType.MODULE_TYPE_GUN, + turret: ModuleType.MODULE_TYPE_TURRET, + vehicle: ModuleType.MODULE_TYPE_VEHICLE, + }; + protected blitzkitCrewTypeToBlitz: Record = { + [CrewType.CREW_TYPE_COMMANDER]: "commander", + [CrewType.CREW_TYPE_DRIVER]: "driver", + [CrewType.CREW_TYPE_GUNNER]: "gunner", + [CrewType.CREW_TYPE_LOADER]: "loader", + [CrewType.CREW_TYPE_RADIOMAN]: "radioman", + }; + protected blitzCrewTypeToBlitzkit: Record = { + commander: CrewType.CREW_TYPE_COMMANDER, + driver: CrewType.CREW_TYPE_DRIVER, + gunner: CrewType.CREW_TYPE_GUNNER, + loader: CrewType.CREW_TYPE_LOADER, + radioman: CrewType.CREW_TYPE_RADIOMAN, + }; + protected blitzTankClassToBlitzkit: Record = { + lightTank: TankClass.TANK_CLASS_LIGHT, + "AT-SPG": TankClass.TANK_CLASS_TANK_DESTROYER, + heavyTank: TankClass.TANK_CLASS_HEAVY, + mediumTank: TankClass.TANK_CLASS_MEDIUM, + }; + protected blitzShellKindToBlitzkit: Record = { + ARMOR_PIERCING: ShellType.SHELL_TYPE_AP, + ARMOR_PIERCING_CR: ShellType.SHELL_TYPE_APCR, + HIGH_EXPLOSIVE: ShellType.SHELL_TYPE_HE, + HOLLOW_CHARGE: ShellType.SHELL_TYPE_HEAT, + }; + protected blitzTankFilterDefinitionCategoryToBlitzkit: Record< + BlitzTankFilterDefinitionCategory, + ConsumableTankCategoryFilterCategory + > = { + clip: ConsumableTankCategoryFilterCategory.CONSUMABLE_TANK_CATEGORY_FILTER_CATEGORY_CLIP, + }; + + protected botPattern = /^.+((tutorial_bot(\d+)?)|(TU))$/; + + protected stringsI18n: Record> = {}; + protected nationsDir?: string[]; + protected tankStringIdMap: Record = {}; + protected consumablesCommon: ConsumablesCommon = {}; + protected provisionsCommon: ProvisionsCommon = {}; + + // TODO: suffix with "yaml"s and "xml"s + protected optionalDevices?: { root: OptionalDevices }; + protected optionalDeviceSlots?: { root: OptionalDeviceSlots }; + protected tankmenAvatar?: { root: TankmenAvatar }; + protected mapsYaml?: MapsYaml; + protected camouflagesXml?: { root: CamouflagesXml }; + protected camouflagesYaml?: CamouflagesYaml; + protected squadBattleTypeStyles?: SquadBattleTypeStylesYaml; + protected gameTypeSelectorStyles?: SquadBattleTypeStylesYaml; + protected combatRoles?: CombatRolesYaml; + protected tierEqualizer?: string[][]; + + constructor(vfs: AbstractVFS) { + super(); + this.vfs = vfs; + } + + async init() { + console.log("Initializing virtual file system..."); + await this.vfs.init(); + + console.log("Fetching game data..."); + this.nationsDir = await this.vfs + .dir("Data/XML/item_defs/vehicles") + .then((files) => files.filter((nation) => nation !== "common")); + + this.optionalDevices = await this.vfs.xml<{ root: OptionalDevices }>( + "Data/XML/item_defs/vehicles/common/optional_devices.xml", + ); + this.optionalDeviceSlots = await this.vfs.xml<{ + root: OptionalDeviceSlots; + }>("Data/XML/item_defs/vehicles/common/optional_device_slots.xml"); + this.tankmenAvatar = await this.vfs.xml<{ root: TankmenAvatar }>( + "Data/XML/item_defs/tankmen/avatar.xml", + ); + this.mapsYaml = await this.vfs.yaml("Data/maps.yaml"); + this.camouflagesXml = await this.vfs.xml<{ root: CamouflagesXml }>( + "Data/XML/item_defs/vehicles/common/camouflages.xml", + ); + this.camouflagesYaml = await this.vfs.yaml( + "Data/camouflages.yaml", + ); + this.squadBattleTypeStyles = await this.vfs.yaml( + `Data/UI/Screens3/Lobby/Hangar/Squad/SquadBattleType.yaml`, + ); + this.gameTypeSelectorStyles = + await this.vfs.yaml( + `Data/UI/Screens/Lobby/Hangar/GameTypeSelector.yaml`, + ); + this.combatRoles = await this.vfs.yaml( + `Data/XML/item_defs/vehicles/common/combat_roles.yaml`, + ); + this.tierEqualizer = await this.vfs.csv( + // spell:disable-next-line + `Data/XML/item_defs/vehicles/common/tier_equializer.csv`, + { delimiter: ";" }, + ); + + console.log("Fetching game localizations..."); + + if (import.meta.env.DEV) { + locales.supported = [locales.supported[0]]; + } + + let fetchedLocalizations = 0; + await Promise.all( + locales.supported.map(async ({ locale }) => { + const blitzLocale = SUPPORTED_LOCALE_BLITZ_MAP[locale]; + const cache = await fetch( + `https://stufficons.wgcdn.co/localizations/${blitzLocale}.yaml`, + ) + .then((response) => response.text()) + .then((string) => parseYaml(string) as BlitzStrings); + const preInstalled = await this.vfs.yaml( + `Data/Strings/${blitzLocale}.yaml`, + ); + + this.stringsI18n[locale] = { + ...cache, + ...preInstalled, + }; + + console.log( + `Fetched localizations for ${locale} (${++fetchedLocalizations}/${locales.supported.length})`, + ); + }), + ); + + console.log("Fetching consumable and provision data..."); + + for (const match of ( + await this.vfs.text( + `Data/XML/item_defs/vehicles/common/consumables/list.xml`, + ) + ).matchAll(//g)) { + if (match[1] === "prototypes") continue; + + Object.assign( + this.consumablesCommon, + ( + await this.vfs.xml<{ root: ConsumablesCommon }>( + `Data/XML/item_defs/vehicles/common/consumables/${match[1]}.xml`, + ) + ).root, + ); + } + + for (const match of ( + await this.vfs.text( + `Data/XML/item_defs/vehicles/common/provisions/list.xml`, + ) + ).matchAll(//g)) { + if (match[1] === "prototypes") continue; + + Object.assign( + this.provisionsCommon, + ( + await this.vfs.xml<{ root: ConsumablesCommon }>( + `Data/XML/item_defs/vehicles/common/provisions/${match[1]}.xml`, + ) + ).root, + ); + } + + console.log("Initialization complete, enjoy :)"); + + return this; + } + + protected getString(name: string) { + const collection: Record = { + [locales.default]: this.stringsI18n[locales.default][name], + }; + + for (const { locale } of locales.supported) { + const localizedString = this.stringsI18n[locale][name]; + + if ( + localizedString === undefined || + localizedString === collection[locales.default] + ) { + continue; + } + + collection[locale] = localizedString; + } + + return { locales: collection } satisfies I18nString; + } +} + +export function Cache(disableInDev = false) { + const cache = new WeakMap>>(); + + return function ( + target: (this: This, ...args: Arguments) => Promise, + // TODO: evaluate if we need context to make the error trace easier + // context: ClassMethodDecoratorContext< + // This, + // (this: This, ...args: Arguments) => Promise + // >, + ) { + return async function replacementMethod(this: This, ...args: Arguments) { + const key = args.join("-"); + let thisCache: Map>; + + if (cache.has(this as object)) { + thisCache = cache.get(this as object) as Map>; + } else { + thisCache = new Map(); + cache.set(this as object, thisCache); + } + + const disabled = disableInDev && import.meta.env.DEV; + + if (thisCache.has(key) && !disabled) { + return thisCache.get(key)!; + } + + const promise = (async () => { + try { + return await target.apply(this, args); + } catch (error) { + thisCache!.delete(key); + throw error; + } + })(); + + thisCache.set(key, promise); + + return promise; + }; + }; +} diff --git a/packages/core/src/blitzkit/api/server/1.ts b/packages/core/src/blitzkit/api/server/1.ts new file mode 100644 index 000000000..3bb4423ed --- /dev/null +++ b/packages/core/src/blitzkit/api/server/1.ts @@ -0,0 +1,727 @@ +import { + AssaultRanges, + BlitzCrewType, + BlitzModuleType, + BlitzTankClass, + ChassisDefinitionsList, + Crew, + EngineDefinitionsList, + Equalizer, + GunDefinition, + GunDefinitionsList, + ModuleType, + ResearchCost, + ShellDefinitionsList, + ShellKind, + sluggify, + TankDefinitions, + TankParameters, + TankPrice, + TankPriceType, + TankType, + toUniqueId, + TurretDefinitionsList, + Unlock, + UnlocksListing, + VehicleDefinitionList, + VehicleDefinitions, +} from "@blitzkit/core"; +import { + Cache, + ServerBlitzKitAPI0, +} from "@blitzkit/core/src/blitzkit/api/server/0"; + +export abstract class ServerBlitzKitAPI1 extends ServerBlitzKitAPI0 { + private parseResearchCost(raw: number | string) { + if (typeof raw === "number") { + return { + research_cost_type: { $case: "xp", value: raw }, + } satisfies ResearchCost; + } else { + return { + research_cost_type: { + $case: "seasonal_tokens", + value: { + season: Number(/prx_season_(\d+):\d+/.exec(raw)![1]), + tokens: Number(/prx_season_\d+:(\d+)/.exec(raw)![1]), + }, + }, + } satisfies ResearchCost; + } + } + + @Cache() + async tankDefinitions() { + const tankDefinitions = TankDefinitions.create(); + + const gameModeNativeNames: Record = {}; + const squadBattleTypeGameModeNativeNameMatches = + this.squadBattleTypeStyles!.Prototypes[0].components.UIDataLocalBindingsComponent.data[1][2].matchAll( + /"(\d+)" -> "(battleType\/([a-zA-Z]+))"/g, + ); + const gameTypeGameModeNativeNameMatches = + this.gameTypeSelectorStyles!.Prototypes[0].components.UIDataLocalBindingsComponent.data[1][2].matchAll( + /eGameMode\.([a-zA-Z]+) -> "~res:\/Gfx\/UI\/Hangar\/GameTypes\/battle-type_([^"]+)"/g, + ); + + for (const match of squadBattleTypeGameModeNativeNameMatches) { + const id = Number(match[1]); + gameModeNativeNames[match[3]] = id; + } + + for (const match of gameTypeGameModeNativeNameMatches) { + Object.entries(gameModeNativeNames).forEach(([key, value]) => { + if (key.toLowerCase() === match[2].toLowerCase()) { + gameModeNativeNames[match[1]] = value; + } + }); + } + + const slugRequesters = new Map(); + const idToNation: Record = {}; + + for (const nation of this.nationsDir!) { + const tankList = await this.vfs.xml<{ root: VehicleDefinitionList }>( + `Data/XML/item_defs/vehicles/${nation}/list.xml`, + ); + + for (const tankKey in tankList.root) { + if (this.botPattern.test(tankKey)) continue; + + const tank = tankList.root[tankKey]; + const tankId = toUniqueId(nation, tank.id); + + const name = ( + (tank.shortUserString + ? this.getString(tank.shortUserString) + : undefined) ?? this.getString(tank.userString) + ).locales.en; + + let slug = sluggify(name); + + idToNation[tankId] = nation; + + if (slugRequesters.has(slug)) { + slugRequesters.get(slug)!.push({ id: tankId, key: tankKey }); + } else { + slugRequesters.set(slug, [{ id: tankId, key: tankKey }]); + } + } + } + + const slugs = new Map(); + + slugRequesters.forEach((requesters, slug) => { + if (requesters.length === 1) { + slugs.set(requesters[0].id, slug); + return; + } + + console.warn( + `Multiple tanks share slug ${slug}: ${requesters + .map(({ key }) => key) + .join(", ")}`, + ); + + if (requesters.length !== 2) { + throw new Error("Unresolvable number of duplicates :("); + } + + const nonCanonical = requesters.find(({ key }) => key.endsWith("TUR")); + + if (nonCanonical === undefined) { + console.log("Using nations to discriminate"); + // both are non-tutorial tanks, will have to discriminate using nation + + requesters.forEach(({ id, key }) => { + const nation = idToNation[id]; + const discriminator = + this.nationSlugDiscriminators[ + nation as keyof typeof this.nationSlugDiscriminators + ]; + + console.log(`Solution: ${key} -> ${slug}-${discriminator}`); + slugs.set(id, `${slug}-${discriminator}`); + }); + } else { + console.log("Using tutorial bot suffix to discriminate"); + + const canonical = requesters.find(({ key }) => !key.endsWith("TUR")); + + if (canonical === undefined) { + throw new Error( + "Two tutorial bots share the same slug? The world is truly broken.", + ); + } + + console.log(`Solution: ${canonical.key} -> ${slug}`); + slugs.set(canonical.id, slug); + console.log(`Solution: ${nonCanonical.key} -> ${slug}-tur`); + slugs.set(nonCanonical.id, `${slug}-tur`); + } + }); + + const tankXps = new Map(); + + for (const nation of this.nationsDir!) { + const tankList = await this.vfs.xml<{ root: VehicleDefinitionList }>( + `Data/XML/item_defs/vehicles/${nation}/list.xml`, + ); + const turretList = await this.vfs.xml<{ + root: TurretDefinitionsList; + }>(`Data/XML/item_defs/vehicles/${nation}/components/turrets.xml`); + const gunList = await this.vfs.xml<{ + root: GunDefinitionsList; + }>(`Data/XML/item_defs/vehicles/${nation}/components/guns.xml`); + const shellList = await this.vfs.xml<{ + root: ShellDefinitionsList; + }>(`Data/XML/item_defs/vehicles/${nation}/components/shells.xml`); + const enginesList = await this.vfs.xml<{ + root: EngineDefinitionsList; + }>(`Data/XML/item_defs/vehicles/${nation}/components/engines.xml`); + const chassisList = await this.vfs.xml<{ + root: ChassisDefinitionsList; + }>(`Data/XML/item_defs/vehicles/${nation}/components/chassis.xml`); + + function resolveUnlocks( + blitzModuleTypeToBlitzkit: Record, + unlocks?: BlitzModuleType, + ) { + if (!unlocks) return []; + + return Object.entries(unlocks) + .map(([type, list]) => + (Array.isArray(list) ? list : [list]).map((item) => { + const typeTyped = type as keyof BlitzModuleType; + let rawId: number; + + if (typeTyped === "chassis") { + rawId = chassisList.root.ids[item["#text"]]; + } else if (typeTyped === "engine") { + rawId = enginesList.root.ids[item["#text"]]; + } else if (typeTyped === "gun") { + rawId = gunList.root.ids[item["#text"]]; + } else if (typeTyped === "turret") { + rawId = turretList.root.ids[item["#text"]]; + } else if (typeTyped === "vehicle") { + rawId = tankList.root[item["#text"]].id; + } + + return { + id: toUniqueId(nation, rawId!), + type: blitzModuleTypeToBlitzkit[typeTyped], + cost: { + type: + typeof item.cost === "number" + ? "xp" + : item.cost.split(":")[0], + value: + typeof item.cost === "number" + ? item.cost + : Number(item.cost.split(":")[1]), + }, + } satisfies Unlock; + }), + ) + .flat(); + } + + for (const tankKey in tankList.root) { + if (this.botPattern.test(tankKey)) continue; + + const gunXps = new Map(); + const turretXps = new Map(); + const engineXps = new Map(); + const trackXps = new Map(); + const tank = tankList.root[tankKey]; + let tankPrice: TankPrice; + const tankDefinition = await this.vfs.xml<{ root: VehicleDefinitions }>( + `Data/XML/item_defs/vehicles/${nation}/${tankKey}.xml`, + ); + const tankParameters = await this.vfs.yaml( + `Data/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`, + ); + const tankId = toUniqueId(nation, tank.id); + + const tankTags = tank.tags.split(" "); + const equipment = tankDefinition.root.optDevicePreset; + + this.tankStringIdMap[`${nation}:${tankKey}`] = tankId; + + const slug = slugs.get(tankId); + + if (slug === undefined) { + throw new Error( + `Could not find slug for ${nation}/${tankKey} (${tankId})`, + ); + } + + if (tank.sellPrice) { + tankPrice = { + type: TankPriceType.TANK_PRICE_TYPE_GOLD, + value: tank.sellPrice["#text"] * 2, + }; + } else if (typeof tank.price === "number") { + tankPrice = { + type: TankPriceType.TANK_PRICE_TYPE_CREDITS, + value: tank.price, + }; + } else { + tankPrice = { + type: TankPriceType.TANK_PRICE_TYPE_CREDITS, + value: tank.price["#text"] * 400, + }; + } + + const crew: Crew[] = []; + const fixedCamouflage = tankTags.includes("eventCamouflage_user"); + const totalUnlocks: UnlocksListing[] = []; + + for (const crewKey in tankDefinition.root.crew) { + const value = tankDefinition.root.crew[crewKey as BlitzCrewType]; + let entry: Crew; + const index = crew.findIndex( + ({ type }) => this.blitzkitCrewTypeToBlitz[type] === crewKey, + ); + if (index === -1) { + if (crewKey === "#text") continue; + entry = { + type: this.blitzCrewTypeToBlitzkit[crewKey as BlitzCrewType], + count: 0, + substitute: [], + }; + crew.push(entry); + } else { + entry = crew[index]; + } + + if (typeof value === "string") { + entry.count++; + + if (value !== "") { + entry.substitute = value.split(/\n| /).map((member) => { + return this.blitzCrewTypeToBlitzkit[ + member.trim() as BlitzCrewType + ]; + }); + } + } else { + if (entry.count === undefined) { + entry.count = value.length; + } else { + entry.count += value.length; + } + } + } + + const camouflages = Object.entries( + this.camouflagesXml!.root.camouflages, + ) + .filter(([, camo]) => { + if (!camo.vehicleFilter.include) return false; + if (camo.unlockCostCategory !== "legendary-skins-gold") + return false; + + const includeArray = Array.isArray(camo.vehicleFilter.include) + ? camo.vehicleFilter.include + : [camo.vehicleFilter.include]; + + return includeArray.some((filter) => { + if ("vehicle" in filter && filter.vehicle?.name) { + return filter.vehicle.name === `${nation}:${tankKey}`; + } + + return false; + }); + }) + .map(([, camo]) => camo.id); + + let equalizerEntry = this.tierEqualizer!.find( + (line) => line[0] === `${nation}:${tankKey}`, + ); + + let equalizer: Equalizer | undefined; + + if (equalizerEntry) { + const [health, penetration, module_health, damage, armor] = + equalizerEntry?.slice(1).map(Number); + equalizer = { health, penetration, module_health, damage, armor }; + } + + tankDefinitions.tanks[tankId] = { + ancestors: [], + successors: [], + id: tankId, + dev_name: tankKey, + roles: {}, + camouflages: camouflages, + fixed_camouflage: fixedCamouflage, + crew, + weight: tankDefinition.root.hull.weight, + health: tankDefinition.root.hull.maxHealth, + speed_forwards: tankDefinition.root.speedLimits.forward, + speed_backwards: tankDefinition.root.speedLimits.backward, + equipment_preset: + typeof equipment === "string" ? equipment : equipment.at(-1)!, + max_consumables: tankDefinition.root.consumableSlots, + max_provisions: tankDefinition.root.provisionSlots, + name: + (tank.shortUserString + ? this.getString(tank.shortUserString) + : undefined) ?? this.getString(tank.userString), + slug, + nation, + type: tankTags.includes("collectible") + ? TankType.TANK_TYPE_COLLECTOR + : (typeof tank.price === "number" ? false : "gold" in tank.price) + ? TankType.TANK_TYPE_PREMIUM + : TankType.TANK_TYPE_RESEARCHABLE, + tier: tank.level, + class: this.blitzTankClassToBlitzkit[tankTags[0] as BlitzTankClass], + testing: tankTags.includes("testTank"), + deprecated: tankTags.includes("deprecated"), + price: tankPrice, + camouflage_still: tankDefinition.root.invisibility.still, + camouflage_moving: tankDefinition.root.invisibility.moving, + camouflage_on_fire: tankDefinition.root.invisibility.firePenalty, + turrets: [], + engines: [], + tracks: [], + equalizer, + }; + + if (tank.combatRole) { + Object.entries(tank.combatRole).forEach(([gameMode, role]) => { + const id = Object.entries(gameModeNativeNames).find( + ([key]) => key.toLowerCase() === gameMode.toLowerCase(), + )?.[1]; + + if (id === undefined) { + throw new Error( + `Unknown game mode in tank ${tankKey}: ${gameMode}`, + ); + } + + tankDefinitions.tanks[tankId].roles[id] = + this.combatRoles![role].id; + }); + } + + Object.keys(tankDefinition.root.chassis).forEach((key) => { + const track = tankDefinition.root.chassis[key]; + const trackId = toUniqueId(nation, chassisList.root.ids[key]); + const terrainResistances = track.terrainResistance + .split(" ") + .map(Number); + + totalUnlocks.push(track.unlocks); + tankDefinitions.tanks[tankId].tracks.push({ + id: trackId, + weight: track.weight, + name: this.getString(track.userString), + traverse_speed: track.rotationSpeed, + dispersion_move: track.shotDispersionFactors.vehicleMovement, + dispersion_traverse: track.shotDispersionFactors.vehicleRotation, + resistance_hard: terrainResistances[0], + resistance_medium: terrainResistances[1], + resistance_soft: terrainResistances[2], + tier: track.level, + unlocks: resolveUnlocks( + this.blitzModuleTypeToBlitzkit, + track.unlocks, + ), + }); + }); + + Object.keys(tankDefinition.root.engines).forEach((engineKey) => { + const engine = tankDefinition.root.engines[engineKey]; + const engineListEntry = enginesList.root.shared[engineKey]; + const engineId = toUniqueId(nation, enginesList.root.ids[engineKey]); + + totalUnlocks.push(engine.unlocks); + tankDefinitions.tanks[tankId].engines.push({ + id: engineId, + name: this.getString(engineListEntry.userString), + fire_chance: engineListEntry.fireStartingChance, + tier: engineListEntry.level, + weight: engineListEntry.weight, + power: engineListEntry.power, + unlocks: resolveUnlocks( + this.blitzModuleTypeToBlitzkit, + engine.unlocks, + ), + }); + }); + + let turretIndex = 0; + for (const turretKey in tankDefinition.root.turrets0) { + const turret = tankDefinition.root.turrets0[turretKey]; + const turretId = toUniqueId(nation, turretList.root.ids[turretKey]); + + totalUnlocks.push(turret.unlocks); + + tankDefinitions.tanks[tankId].turrets.push({ + id: turretId, + traverse_speed: turret.rotationSpeed, + name: this.getString(turret.userString), + tier: turret.level, + guns: [], + health: turret.maxHealth, + view_range: turret.circularVisionRadius, + weight: turret.weight, + unlocks: resolveUnlocks( + this.blitzModuleTypeToBlitzkit, + turret.unlocks, + ), + }); + + let gunIndex = 0; + for (const gunKey in turret.guns) { + const gun = turret.guns[gunKey]; + const gunId = toUniqueId(nation, gunList.root.ids[gunKey]); + const gunListEntry = gunList.root.shared[gunKey]; + const gunName = this.getString(gunListEntry.userString); + const gunType = + "clip" in gun + ? gun.pumpGunMode + ? "autoReloader" + : "autoLoader" + : "regular"; + const gunClipCount = gunType === "regular" ? 1 : gun.clip!.count; + const shotDispersionFactors = + gun.shotDispersionFactors ?? gunListEntry.shotDispersionFactors; + let assault_ranges: AssaultRanges | undefined; + + if (gun.extras?.trayShell) { + const types = gun.extras.trayShell.kinds + .split(" ") + .map((string) => { + const trimmed = string.trim(); + + if (trimmed in this.blitzShellKindToBlitzkit) { + return this.blitzShellKindToBlitzkit[trimmed as ShellKind]; + } + + throw new SyntaxError(`Invalid shell kind: ${trimmed}`); + }); + const sectorNames = Object.keys(gun.extras.trayShell.sectors); + + if (sectorNames.length !== 1 || sectorNames[0] !== "sector") { + throw new SyntaxError("Invalid tray shell sector"); + } + + const sector = gun.extras.trayShell.sectors.sector; + + assault_ranges = { + types, + ranges: sector.map((value) => ({ + factor: value.factor, + distance: value.distance, + })), + }; + } + + totalUnlocks.push(gun.unlocks); + + tankDefinitions.tanks[tankId].turrets[turretIndex].guns.push({ + id: gunId, + weight: gunListEntry.weight, + rotation_speed: gunListEntry.rotationSpeed, + name: gunName, + tier: gunListEntry.level, + shells: [], + camouflage_loss: + typeof gun.invisibilityFactorAtShot === "number" + ? gun.invisibilityFactorAtShot + : gun.invisibilityFactorAtShot.at(-1)!, + aim_time: gun.aimingTime ?? gunListEntry.aimingTime, + dispersion_base: + gun.shotDispersionRadius ?? gunListEntry.shotDispersionRadius, + dispersion_damaged: shotDispersionFactors.whileGunDamaged, + dispersion_shot: shotDispersionFactors.afterShot, + dispersion_traverse: shotDispersionFactors.turretRotation, + unlocks: resolveUnlocks( + this.blitzModuleTypeToBlitzkit, + gun.unlocks, + ), + shell_capacity: gun.maxAmmo ?? gunListEntry.maxAmmo, + assault_ranges, + burst: + gun.burst && gun.burst.count > 1 + ? { + count: gun.burst.count, + interval: 60 / gun.burst.rate, + } + : undefined, + gun_type: + gunType === "regular" + ? { + $case: "regular", + value: { + reload: gun.reloadTime, + }, + } + : gunType === "autoReloader" + ? { + $case: "auto_reloader", + value: { + intra_clip: 60 / gun.clip!.rate, + shell_count: gunClipCount, + shell_reloads: gun + .pumpGunReloadTimes!.split(" ") + .map(Number), + }, + } + : { + $case: "auto_loader", + value: { + intra_clip: 60 / gun.clip!.rate, + clip_reload: gun.reloadTime, + shell_count: gunClipCount, + }, + }, + } satisfies GunDefinition); + + for (const shellKey in gunListEntry.shots) { + const gunShellEntry = gunListEntry.shots[shellKey]; + const shell = shellList.root[shellKey]; + const shellId = toUniqueId(nation, shell.id); + const shellName = this.getString(shell.userString); + const penetrationRaw = gunShellEntry.piercingPower + .split(" ") + .filter((penetrationString) => penetrationString !== "") + .map(Number); + + tankDefinitions.tanks[tankId].turrets[turretIndex].guns[ + gunIndex + ].shells.push({ + id: shellId, + name: shellName, + velocity: gunShellEntry.speed, + armor_damage: shell.damage.armor, + module_damage: shell.damage.devices, + caliber: shell.caliber, + normalization: shell.normalizationAngle, + ricochet: shell.ricochetAngle, + type: this.blitzShellKindToBlitzkit[shell.kind], + explosion_radius: + shell.kind === "HIGH_EXPLOSIVE" + ? (shell.explosionRadius ?? 0) + : undefined, + icon: shell.icon, + penetration: { + near: penetrationRaw[0], + far: penetrationRaw[1], + }, + range: gunShellEntry.maxDistance, + }); + } + + gunIndex++; + } + + turretIndex++; + } + + for (const unlocks of totalUnlocks) { + if (unlocks === undefined) continue; + + for (const key in unlocks) { + const value = unlocks[key as keyof BlitzModuleType]; + + for (const vehicle of Array.isArray(value) ? value : [value]) { + switch (key as keyof BlitzModuleType) { + case "vehicle": { + const tankListEntry = tankList.root[vehicle["#text"]]; + const currentTank = tankDefinitions.tanks[tankId]; + const successorId = toUniqueId(nation, tankListEntry.id); + + tankXps.set( + successorId, + this.parseResearchCost(vehicle.cost), + ); + + if (currentTank.successors === undefined) { + currentTank.successors = []; + } + if (!currentTank.successors!.includes(successorId)) { + currentTank.successors!.push(successorId); + } + break; + } + + case "gun": { + gunXps.set( + toUniqueId(nation, gunList.root.ids[vehicle["#text"]]), + this.parseResearchCost(vehicle.cost), + ); + break; + } + + case "turret": { + turretXps.set( + toUniqueId(nation, turretList.root.ids[vehicle["#text"]]), + this.parseResearchCost(vehicle.cost), + ); + break; + } + + case "engine": { + engineXps.set( + toUniqueId(nation, enginesList.root.ids[vehicle["#text"]]), + this.parseResearchCost(vehicle.cost), + ); + break; + } + + case "chassis": { + trackXps.set( + toUniqueId(nation, chassisList.root.ids[vehicle["#text"]]), + this.parseResearchCost(vehicle.cost), + ); + break; + } + } + } + } + } + + Object.values(tankDefinitions.tanks[tankId].turrets).forEach( + (turret) => { + turret.research_cost = turretXps.get(turret.id); + + Object.values(turret.guns).forEach((gunRaw) => { + gunRaw.research_cost = gunXps.get(gunRaw.id); + }); + }, + ); + + Object.values(tankDefinitions.tanks[tankId].engines).forEach( + (engine) => { + engine.research_cost = engineXps.get(engine.id); + }, + ); + + Object.values(tankDefinitions.tanks[tankId].tracks).forEach((track) => { + track.research_cost = trackXps.get(track.id); + }); + } + } + + Object.values(tankDefinitions.tanks).forEach((tank) => { + tank.research_cost = tankXps.get(tank.id); + }); + + Object.values(tankDefinitions.tanks).forEach((tank) => { + tank.successors?.forEach((predecessorId) => { + if ( + !tankDefinitions.tanks[predecessorId].ancestors?.includes(tank.id) + ) { + tankDefinitions.tanks[predecessorId].ancestors?.push(tank.id); + } + }); + }); + + return tankDefinitions; + } +} diff --git a/packages/core/src/blitzkit/api/server/2.ts b/packages/core/src/blitzkit/api/server/2.ts new file mode 100644 index 000000000..32d271d2e --- /dev/null +++ b/packages/core/src/blitzkit/api/server/2.ts @@ -0,0 +1,34 @@ +import { CamouflageDefinitions } from "@blitzkit/core"; +import { Cache } from "@blitzkit/core/src/blitzkit/api/server/0"; +import { ServerBlitzKitAPI1 } from "@blitzkit/core/src/blitzkit/api/server/1"; + +export abstract class ServerBlitzKitAPI2 extends ServerBlitzKitAPI1 { + @Cache() + async camouflageDefinitions() { + const camouflageDefinitions = CamouflageDefinitions.create(); + + for (const camoKey in this.camouflagesXml!.root.camouflages) { + const camo = this.camouflagesXml!.root.camouflages[camoKey]; + + const yamlEntry = this.camouflagesYaml![camoKey]; + const fullName = yamlEntry.userString + ? this.getString(yamlEntry.userString) + : undefined; + const shortName = yamlEntry.shortUserString + ? this.getString(yamlEntry.shortUserString) + : undefined; + const resolvedTankName = shortName ?? fullName; + const resolvedTankNameFull = + resolvedTankName === fullName ? undefined : fullName; + + camouflageDefinitions.camouflages[camo.id] = { + id: camo.id, + name: this.getString(camo.userString), + tank_name: resolvedTankName, + tank_name_full: resolvedTankNameFull, + }; + } + + return camouflageDefinitions; + } +} diff --git a/packages/core/src/blitzkit/api/server/3.ts b/packages/core/src/blitzkit/api/server/3.ts new file mode 100644 index 000000000..4aed60738 --- /dev/null +++ b/packages/core/src/blitzkit/api/server/3.ts @@ -0,0 +1,333 @@ +import { + Armor, + BlitzCrewType, + ChassisDefinitionsList, + Crew, + EngineDefinitionsList, + GunDefinitionsList, + ModelDefinitions, + ResearchCost, + TankParameters, + toUniqueId, + TurretDefinitionsList, + Vector3, + VehicleDefinitionArmor, + VehicleDefinitionList, + VehicleDefinitions, +} from "@blitzkit/core"; +import { Cache } from "@blitzkit/core/src/blitzkit/api/server/0"; +import { ServerBlitzKitAPI2 } from "@blitzkit/core/src/blitzkit/api/server/2"; +import { parse as parsePath } from "path"; +import { Vector3Tuple } from "three"; + +export abstract class ServerBlitzKitAPI3 extends ServerBlitzKitAPI2 { + private vector3TupleToBlitzkit(tuple: Vector3Tuple) { + return { x: tuple[0], y: tuple[1], z: tuple[2] } satisfies Vector3; + } + + private assignArmor( + raw: VehicleDefinitionArmor[string], + id: number, + armor: Armor, + ) { + if (typeof raw === "number") { + armor.thickness[id] = raw; + } else if (Array.isArray(raw)) { + armor.thickness[id] = raw.at(-1)!; + } else { + if (!armor.spaced) armor.spaced = []; + armor.thickness[id] = raw["#text"]; + armor.spaced.push(id); + } + } + + @Cache() + async modelDefinitions() { + const modelDefinitions = ModelDefinitions.create(); + + for (const nation of this.nationsDir!) { + const tankList = await this.vfs.xml<{ root: VehicleDefinitionList }>( + `Data/XML/item_defs/vehicles/${nation}/list.xml`, + ); + const turretList = await this.vfs.xml<{ + root: TurretDefinitionsList; + }>(`Data/XML/item_defs/vehicles/${nation}/components/turrets.xml`); + const gunList = await this.vfs.xml<{ + root: GunDefinitionsList; + }>(`Data/XML/item_defs/vehicles/${nation}/components/guns.xml`); + const enginesList = await this.vfs.xml<{ + root: EngineDefinitionsList; + }>(`Data/XML/item_defs/vehicles/${nation}/components/engines.xml`); + const chassisList = await this.vfs.xml<{ + root: ChassisDefinitionsList; + }>(`Data/XML/item_defs/vehicles/${nation}/components/chassis.xml`); + + for (const tankKey in tankList.root) { + if (this.botPattern.test(tankKey)) continue; + + const gunXps = new Map(); + const turretXps = new Map(); + const engineXps = new Map(); + const trackXps = new Map(); + const tank = tankList.root[tankKey]; + const tankDefinition = await this.vfs.xml<{ root: VehicleDefinitions }>( + `Data/XML/item_defs/vehicles/${nation}/${tankKey}.xml`, + ); + const tankParameters = await this.vfs.yaml( + `Data/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`, + ); + const turretOrigin = tankDefinition.root.hull.turretPositions.turret + .split(" ") + .map(Number) as Vector3Tuple; + const tankId = toUniqueId(nation, tank.id); + + const hullArmor: Armor = { spaced: [], thickness: {} }; + this.tankStringIdMap[`${nation}:${tankKey}`] = tankId; + + Object.keys(tankDefinition.root.hull.armor) + .filter((name) => name.startsWith("armor_")) + .forEach((name) => { + const armorIdString = name.match(/armor_(\d+)/)?.[1]; + + if (armorIdString === undefined) { + throw new SyntaxError(`Invalid armor id: ${name}`); + } + + const armorId = parseInt(armorIdString); + const armorRaw = tankDefinition.root.hull.armor[name]; + + this.assignArmor(armorRaw, armorId, hullArmor); + }); + const crew: Crew[] = []; + + for (const crewKey in tankDefinition.root.crew) { + const value = tankDefinition.root.crew[crewKey as BlitzCrewType]; + let entry: Crew; + const index = crew.findIndex( + ({ type }) => this.blitzkitCrewTypeToBlitz[type] === crewKey, + ); + if (index === -1) { + if (crewKey === "#text") continue; + + entry = { + type: this.blitzCrewTypeToBlitzkit[crewKey as BlitzCrewType], + count: 0, + substitute: [], + }; + crew.push(entry); + } else { + entry = crew[index]; + } + + if (typeof value === "string") { + entry.count++; + + if (value !== "") { + entry.substitute = value.split(/\n| /).map((member) => { + return this.blitzCrewTypeToBlitzkit[ + member.trim() as BlitzCrewType + ]; + }); + } + } else { + if (entry.count === undefined) { + entry.count = value.length; + } else { + entry.count += value.length; + } + } + } + + modelDefinitions.models[tankId] = { + armor: hullArmor, + turret_origin: this.vector3TupleToBlitzkit(turretOrigin), + initial_turret_rotation: tankDefinition.root.hull + .turretInitialRotation + ? { + yaw: tankDefinition.root.hull.turretInitialRotation.yaw, + pitch: tankDefinition.root.hull.turretInitialRotation.pitch, + roll: tankDefinition.root.hull.turretInitialRotation.roll, + } + : undefined, + bounding_box: { + min: this.vector3TupleToBlitzkit( + tankParameters.collision.hull.bbox.min, + ), + max: this.vector3TupleToBlitzkit( + tankParameters.collision.hull.bbox.max, + ), + }, + turrets: {}, + tracks: {}, + }; + + for (const key in tankDefinition.root.chassis) { + const track = tankDefinition.root.chassis[key]; + const trackId = toUniqueId(nation, chassisList.root.ids[key]); + const trackArmorRaw = track.armor.leftTrack; + const hullOrigin = track.hullPosition + .split(" ") + .map(Number) as Vector3Tuple; + + modelDefinitions.models[tankId].tracks[trackId] = { + thickness: + typeof trackArmorRaw === "number" + ? trackArmorRaw + : trackArmorRaw["#text"], + origin: this.vector3TupleToBlitzkit(hullOrigin), + }; + } + + Object.keys(tankDefinition.root.turrets0).forEach((turretKey) => { + const turret = tankDefinition.root.turrets0[turretKey]; + const turretModel = Number( + parsePath(turret.models.undamaged).name.split("_")[1], + ); + const turretId = toUniqueId(nation, turretList.root.ids[turretKey]); + const turretYaw = ( + typeof turret.yawLimits === "string" + ? turret.yawLimits + : turret.yawLimits.at(-1)! + ) + .split(" ") + .map(Number) as [number, number]; + const gunOrigin = ( + typeof turret.gunPosition === "string" + ? turret.gunPosition + : turret.gunPosition[0] + ) + .split(" ") + .map(Number) as Vector3Tuple; + const turretArmor: Armor = { thickness: {}, spaced: [] }; + + Object.keys(turret.armor) + .filter((name) => name.startsWith("armor_")) + .forEach((name) => { + const armorIdString = name.match(/armor_(\d+)/)?.[1]; + + if (armorIdString === undefined) { + throw new SyntaxError(`Invalid armor id: ${name}`); + } + + const armorId = parseInt(armorIdString); + const armorRaw = turret.armor[name]; + + this.assignArmor(armorRaw, armorId, turretArmor); + }); + + turret.userString; + + modelDefinitions.models[tankId].turrets[turretId] = { + armor: turretArmor, + gun_origin: this.vector3TupleToBlitzkit(gunOrigin), + model_id: turretModel, + yaw: + -turretYaw[0] + turretYaw[1] === 360 + ? undefined + : { min: turretYaw[0], max: turretYaw[1] }, + guns: {}, + bounding_box: { + min: this.vector3TupleToBlitzkit( + tankParameters.collision[ + parsePath(turret.hitTester.collisionModel).name.toLowerCase() + ].bbox.min, + ), + max: this.vector3TupleToBlitzkit( + tankParameters.collision[ + parsePath(turret.hitTester.collisionModel).name.toLowerCase() + ].bbox.max, + ), + }, + }; + + Object.keys(turret.guns).forEach((gunKey, gunIndex) => { + const gun = turret.guns[gunKey]; + const gunId = toUniqueId(nation, gunList.root.ids[gunKey]); + const gunListEntry = gunList.root.shared[gunKey]; + const pitchLimitsRaw = gun.pitchLimits ?? gunListEntry.pitchLimits; + const gunPitch = ( + typeof pitchLimitsRaw === "string" + ? pitchLimitsRaw + : pitchLimitsRaw.at(-1)! + ) + .split(" ") + .map(Number) as [number, number]; + const gunModel = Number( + parsePath(gun.models.undamaged).name.split("_")[1], + ); + const front = gun.extraPitchLimits?.front + ? gun.extraPitchLimits.front.split(" ").map(Number) + : undefined; + const back = gun.extraPitchLimits?.back + ? gun.extraPitchLimits.back.split(" ").map(Number) + : undefined; + const transition = gun.extraPitchLimits?.transition + ? typeof gun.extraPitchLimits.transition === "number" + ? gun.extraPitchLimits.transition + : gun.extraPitchLimits.transition.at(-1)! + : undefined; + const gunArmor: Armor = { thickness: {}, spaced: [] }; + const maskName = `gun_${gunModel.toString().padStart(2, "0")}`; + const maskEnabled = + tankParameters.maskSlice?.[maskName]?.enabled ?? false; + let mask: number | undefined; + + if (maskEnabled) { + const maskRaw = tankParameters.maskSlice![maskName]!; + mask = maskRaw.planePosition[1]; + } else { + mask = undefined; + } + + Object.keys(gun.armor) + .filter((name) => name.startsWith("armor_")) + .forEach((name) => { + const armorIdString = name.match(/armor_(\d+)/)?.[1]; + if (armorIdString === undefined) { + throw new SyntaxError(`Invalid armor id: ${name}`); + } + const armorId = parseInt(armorIdString); + const armorRaw = gun.armor[name]; + + this.assignArmor(armorRaw, armorId, gunArmor); + }); + + modelDefinitions.models[tankId].turrets[turretId].guns[gunId] = { + armor: gunArmor, + model_id: gunModel, + mask, + thickness: + gun.armor.gun === undefined + ? 0 + : typeof gun.armor.gun === "number" + ? gun.armor.gun + : gun.armor.gun["#text"], + pitch: { + min: gunPitch[0], + max: gunPitch[1], + + front: front + ? { + min: front[0], + max: front[1], + range: front[2], + } + : undefined, + back: back + ? { + min: back[0], + max: back[1], + range: back[2], + } + : undefined, + transition, + }, + }; + }); + }); + } + } + + return modelDefinitions; + } +} diff --git a/packages/core/src/blitzkit/api/server/4.ts b/packages/core/src/blitzkit/api/server/4.ts new file mode 100644 index 000000000..576437619 --- /dev/null +++ b/packages/core/src/blitzkit/api/server/4.ts @@ -0,0 +1,21 @@ +import { MapDefinitions } from "@blitzkit/core"; +import { Cache } from "@blitzkit/core/src/blitzkit/api/server/0"; +import { ServerBlitzKitAPI3 } from "@blitzkit/core/src/blitzkit/api/server/3"; + +export abstract class ServerBlitzKitAPI4 extends ServerBlitzKitAPI3 { + @Cache() + async mapDefinitions() { + const mapDefinitions = MapDefinitions.create(); + + for (const key in this.mapsYaml!.maps) { + const map = this.mapsYaml!.maps[key]; + + mapDefinitions.maps[map.id] = { + id: map.id, + name: this.getString(`#maps:${key}:${map.localName}`), + }; + } + + return mapDefinitions; + } +} diff --git a/packages/core/src/blitzkit/api/server/5.ts b/packages/core/src/blitzkit/api/server/5.ts new file mode 100644 index 000000000..676514077 --- /dev/null +++ b/packages/core/src/blitzkit/api/server/5.ts @@ -0,0 +1,42 @@ +import { EquipmentDefinitions, EquipmentSlot } from "@blitzkit/core"; +import { Cache } from "@blitzkit/core/src/blitzkit/api/server/0"; +import { ServerBlitzKitAPI4 } from "@blitzkit/core/src/blitzkit/api/server/4"; + +export abstract class ServerBlitzKitAPI5 extends ServerBlitzKitAPI4 { + @Cache() + async equipmentDefinitions() { + const equipmentDefinitions = EquipmentDefinitions.create(); + + Object.entries(this.optionalDevices!.root).forEach( + ([optionalDeviceKey, optionalDeviceEntry]) => { + if (optionalDeviceKey === "nextAvailableId") return; + + equipmentDefinitions.equipments[optionalDeviceEntry.id] = { + name: this.getString(optionalDeviceEntry.userString), + description: this.getString(optionalDeviceEntry.description), + }; + }, + ); + + Object.entries(this.optionalDeviceSlots!.root.presets).forEach( + ([optionalDeviceSlotKey, optionalDeviceSlotEntry]) => { + if (optionalDeviceSlotKey === "emptyPreset") return; + + equipmentDefinitions.presets[optionalDeviceSlotKey] = { + slots: Object.values(optionalDeviceSlotEntry) + .map((level) => { + return Object.values(level).map((options) => { + return { + left: this.optionalDevices!.root[options.device0].id, + right: this.optionalDevices!.root[options.device1].id, + } satisfies EquipmentSlot; + }); + }) + .flat(), + }; + }, + ); + + return equipmentDefinitions; + } +} diff --git a/packages/core/src/blitzkit/api/server/6.ts b/packages/core/src/blitzkit/api/server/6.ts new file mode 100644 index 000000000..a94400138 --- /dev/null +++ b/packages/core/src/blitzkit/api/server/6.ts @@ -0,0 +1,114 @@ +import { + BlitzTankFilterDefinitionCategory, + Consumable, + ConsumableDefinitions, +} from "@blitzkit/core"; +import { Cache } from "@blitzkit/core/src/blitzkit/api/server/0"; +import { ServerBlitzKitAPI5 } from "@blitzkit/core/src/blitzkit/api/server/5"; + +export abstract class ServerBlitzKitAPI6 extends ServerBlitzKitAPI5 { + @Cache() + async consumableDefinitions() { + const consumableDefinitions = ConsumableDefinitions.create(); + + Object.entries(this.consumablesCommon).forEach(([key, consumable]) => { + const entry: Consumable = { + id: consumable.id, + game_mode_exclusive: "gameModeFilter" in consumable, + cooldown: consumable.script.cooldown, + duration: consumable.script.duration, + name: this.getString(consumable.userString), + exclude: [], + include: [], + }; + consumableDefinitions.consumables[consumable.id] = entry; + + const includeRaw = consumable.vehicleFilter?.include.vehicle; + const excludeRaw = consumable.vehicleFilter?.exclude?.vehicle; + + if (includeRaw) { + entry.include = []; + + if ("minLevel" in includeRaw) { + entry.include.push({ + filter_type: { + $case: "tiers", + value: { + min: includeRaw.minLevel, + max: includeRaw.maxLevel, + }, + }, + }); + } else if ("name" in includeRaw) { + entry.include.push({ + filter_type: { + $case: "ids", + value: { + ids: includeRaw.name.split(/ +/).map((key) => { + return this.tankStringIdMap[key]; + }), + }, + }, + }); + } else throw new SyntaxError("Unhandled include type"); + + if (consumable.vehicleFilter?.include.nations) { + entry.include.push({ + filter_type: { + $case: "nations", + value: { + nations: consumable.vehicleFilter.include.nations.split(" "), + }, + }, + }); + } + } + + if (excludeRaw) { + entry.exclude = []; + + if ("name" in excludeRaw) { + entry.exclude!.push({ + filter_type: { + $case: "ids", + value: { + ids: excludeRaw.name.split(/ +/).map((key) => { + return this.tankStringIdMap[key]; + }), + }, + }, + }); + } else if ("extendedTags" in excludeRaw) { + entry.exclude!.push({ + filter_type: { + $case: "categories", + value: { + categories: excludeRaw.extendedTags + .split(" ") + .map( + (item) => + this.blitzTankFilterDefinitionCategoryToBlitzkit[ + item as BlitzTankFilterDefinitionCategory + ], + ), + }, + }, + }); + } else throw new SyntaxError("Unhandled exclude type"); + + if (consumable.vehicleFilter?.exclude?.nations) { + entry.exclude!.push({ + filter_type: { + $case: "nations", + value: { + nations: consumable.vehicleFilter.exclude.nations.split(" "), + }, + }, + }); + } + } + }); + + return consumableDefinitions; + } +} diff --git a/packages/core/src/blitzkit/api/server/7.ts b/packages/core/src/blitzkit/api/server/7.ts new file mode 100644 index 000000000..52a7fdedd --- /dev/null +++ b/packages/core/src/blitzkit/api/server/7.ts @@ -0,0 +1,117 @@ +import { + BlitzTankFilterDefinitionCategory, + Provision, + ProvisionDefinitions, +} from "@blitzkit/core"; +import { Cache } from "@blitzkit/core/src/blitzkit/api/server/0"; +import { ServerBlitzKitAPI6 } from "@blitzkit/core/src/blitzkit/api/server/6"; + +export abstract class ServerBlitzKitAPI7 extends ServerBlitzKitAPI6 { + @Cache() + async provisionDefinitions() { + const provisionDefinitions = ProvisionDefinitions.create(); + + Object.entries(this.provisionsCommon).forEach(([key, provision]) => { + const entry: Provision = { + id: provision.id, + exclude: [], + include: [], + game_mode_exclusive: "gameModeFilter" in provision, + name: this.getString(provision.userString), + }; + provisionDefinitions.provisions[provision.id] = entry; + + const includeRaw = provision.vehicleFilter?.include.vehicle; + const excludeRaw = provision.vehicleFilter?.exclude?.vehicle; + + if (includeRaw) { + entry.include = []; + + if ("minLevel" in includeRaw) { + entry.include.push({ + filter_type: { + $case: "tiers", + value: { + min: includeRaw.minLevel, + max: includeRaw.maxLevel, + }, + }, + }); + } else if ("name" in includeRaw) { + entry.include.push({ + filter_type: { + $case: "ids", + value: { + ids: includeRaw.name.split(/ +/).map((key) => { + return this.tankStringIdMap[key]; + }), + }, + }, + }); + } else throw new SyntaxError("Unhandled include type"); + + if (provision.vehicleFilter?.include.nations) { + entry.include.push({ + filter_type: { + $case: "nations", + value: { + nations: provision.vehicleFilter.include.nations.split(" "), + }, + }, + }); + } + } + + if (excludeRaw) { + entry.exclude = []; + + if ("name" in excludeRaw) { + entry.exclude!.push({ + filter_type: { + $case: "ids", + value: { + ids: excludeRaw.name + .split(/ +/) + .map((key) => this.tankStringIdMap[key]), + }, + }, + }); + } else if ("extendedTags" in excludeRaw) { + entry.exclude!.push({ + filter_type: { + $case: "categories", + value: { + categories: excludeRaw.extendedTags + .split(" ") + .map( + (item) => + this.blitzTankFilterDefinitionCategoryToBlitzkit[ + item as BlitzTankFilterDefinitionCategory + ], + ), + }, + }, + }); + } else throw new SyntaxError("Unhandled exclude type"); + + if (provision.vehicleFilter?.exclude?.nations) { + entry.exclude!.push({ + filter_type: { + $case: "nations", + value: { + nations: provision.vehicleFilter.exclude.nations.split(" "), + }, + }, + }); + } + } + + if (provision.script.bonusValues?.crewLevelIncrease !== undefined) { + provisionDefinitions.provisions[provision.id].crew = + provision.script.bonusValues?.crewLevelIncrease; + } + }); + + return provisionDefinitions; + } +} diff --git a/packages/core/src/blitzkit/api/server/8.ts b/packages/core/src/blitzkit/api/server/8.ts new file mode 100644 index 000000000..1be55ec45 --- /dev/null +++ b/packages/core/src/blitzkit/api/server/8.ts @@ -0,0 +1,24 @@ +import { BlitzTankClass, SkillDefinitions, TankClass } from "@blitzkit/core"; + +import { Cache } from "@blitzkit/core/src/blitzkit/api/server/0"; +import { ServerBlitzKitAPI7 } from "@blitzkit/core/src/blitzkit/api/server/7"; + +export abstract class ServerBlitzKitAPI8 extends ServerBlitzKitAPI7 { + @Cache() + async skillDefinitions() { + const skillDefinitions = SkillDefinitions.create(); + + for (const tankClass in this.tankmenAvatar!.root.skillsByClasses) { + const skills = + this.tankmenAvatar!.root.skillsByClasses[ + Number(tankClass) as TankClass + ]; + + skillDefinitions.classes[ + this.blitzTankClassToBlitzkit[tankClass as BlitzTankClass] + ] = { skills: skills.split(" ") }; + } + + return skillDefinitions; + } +} diff --git a/packages/core/src/blitzkit/api/server/9.ts b/packages/core/src/blitzkit/api/server/9.ts new file mode 100644 index 000000000..b341554d9 --- /dev/null +++ b/packages/core/src/blitzkit/api/server/9.ts @@ -0,0 +1,58 @@ +import { AvailableNationsYaml, GameDefinitions } from "@blitzkit/core"; + +import { Cache } from "@blitzkit/core/src/blitzkit/api/server/0"; +import { ServerBlitzKitAPI8 } from "@blitzkit/core/src/blitzkit/api/server/8"; + +export abstract class ServerBlitzKitAPI9 extends ServerBlitzKitAPI8 { + @Cache() + async gameDefinitions() { + const gameDefinitions = GameDefinitions.create(); + + const consumableNativeNames: Record = {}; + const provisionNativeNames: Record = {}; + + Object.entries(this.consumablesCommon).forEach(([key, consumable]) => { + consumableNativeNames[key] = consumable.id; + }); + + Object.entries(this.provisionsCommon).forEach(([key, provision]) => { + provisionNativeNames[key] = provision.id; + }); + + const version = (await this.vfs.text("Data/version.txt")).split(" ")[0]; + + gameDefinitions.version = version; + gameDefinitions.nations = ( + await this.vfs.yaml("Data/available_nations.yaml") + ).available_nations; + + for (const match of this.squadBattleTypeStyles!.Prototypes[0].components.UIDataLocalBindingsComponent.data[1][2].matchAll( + /"(\d+)" -> "(battleType\/([a-zA-Z]+))"/g, + )) { + const id = Number(match[1]); + const name = this.getString(match[2]); + + gameDefinitions.gameModes[id] = { + name, + }; + } + + Object.entries(this.combatRoles!).forEach(([, value]) => { + gameDefinitions.roles[value.id] = { provisions: [], consumables: [] }; + + value.default_abilities.forEach((ability) => { + if (ability in consumableNativeNames) { + gameDefinitions.roles[value.id].consumables.push( + consumableNativeNames[ability], + ); + } else if (ability in provisionNativeNames) { + gameDefinitions.roles[value.id].provisions.push( + provisionNativeNames[ability], + ); + } else throw new Error(`Unknown ability ${ability}`); + }); + }); + + return gameDefinitions; + } +} diff --git a/packages/core/src/blitzkit/api/server/index.ts b/packages/core/src/blitzkit/api/server/index.ts new file mode 100644 index 000000000..076cd6444 --- /dev/null +++ b/packages/core/src/blitzkit/api/server/index.ts @@ -0,0 +1,3 @@ +import { ServerBlitzKitAPI9 } from "@blitzkit/core/src/blitzkit/api/server/9"; + +export class ServerBlitzKitAPI extends ServerBlitzKitAPI9 {} diff --git a/packages/website/src/core/blitzkit/api.ts b/packages/website/src/core/blitzkit/api.ts index 437d2dc8a..3342bd765 100644 --- a/packages/website/src/core/blitzkit/api.ts +++ b/packages/website/src/core/blitzkit/api.ts @@ -4,7 +4,7 @@ let _api: BlitzKitAPI; if (import.meta.env.SSR) { const { ServerBlitzKitAPI } = await import( - "@blitzkit/core/src/blitzkit/api/server" + "@blitzkit/core/src/blitzkit/api/server/index" ); const { vfs } = await import("./vfs"); From 7e9be14a9a6fb67f42cb142e9d9bf62d3b4b4cf3 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Tue, 4 Aug 2026 13:22:44 -0500 Subject: [PATCH 032/147] label all chunks --- packages/core/src/blitzkit/api/server/{0.ts => 0_base.ts} | 0 .../src/blitzkit/api/server/{1.ts => 1_tankDefinitions.ts} | 2 +- .../blitzkit/api/server/{2.ts => 2_camouflageDefinitions.ts} | 4 ++-- .../src/blitzkit/api/server/{3.ts => 3_modelDefinitions.ts} | 4 ++-- .../src/blitzkit/api/server/{4.ts => 4_mapDefinitions.ts} | 4 ++-- .../blitzkit/api/server/{5.ts => 5_equipmentDefinitions.ts} | 4 ++-- .../blitzkit/api/server/{6.ts => 6_consumableDefinitions.ts} | 4 ++-- .../blitzkit/api/server/{7.ts => 7_provisionDefinitions.ts} | 4 ++-- .../src/blitzkit/api/server/{8.ts => 8_skillDefinitions.ts} | 4 ++-- .../src/blitzkit/api/server/{9.ts => 9_gameDefinitions.ts} | 4 ++-- packages/core/src/blitzkit/api/server/index.ts | 2 +- 11 files changed, 18 insertions(+), 18 deletions(-) rename packages/core/src/blitzkit/api/server/{0.ts => 0_base.ts} (100%) rename packages/core/src/blitzkit/api/server/{1.ts => 1_tankDefinitions.ts} (99%) rename packages/core/src/blitzkit/api/server/{2.ts => 2_camouflageDefinitions.ts} (88%) rename packages/core/src/blitzkit/api/server/{3.ts => 3_modelDefinitions.ts} (98%) rename packages/core/src/blitzkit/api/server/{4.ts => 4_mapDefinitions.ts} (76%) rename packages/core/src/blitzkit/api/server/{5.ts => 5_equipmentDefinitions.ts} (90%) rename packages/core/src/blitzkit/api/server/{6.ts => 6_consumableDefinitions.ts} (95%) rename packages/core/src/blitzkit/api/server/{7.ts => 7_provisionDefinitions.ts} (95%) rename packages/core/src/blitzkit/api/server/{8.ts => 8_skillDefinitions.ts} (81%) rename packages/core/src/blitzkit/api/server/{9.ts => 9_gameDefinitions.ts} (92%) diff --git a/packages/core/src/blitzkit/api/server/0.ts b/packages/core/src/blitzkit/api/server/0_base.ts similarity index 100% rename from packages/core/src/blitzkit/api/server/0.ts rename to packages/core/src/blitzkit/api/server/0_base.ts diff --git a/packages/core/src/blitzkit/api/server/1.ts b/packages/core/src/blitzkit/api/server/1_tankDefinitions.ts similarity index 99% rename from packages/core/src/blitzkit/api/server/1.ts rename to packages/core/src/blitzkit/api/server/1_tankDefinitions.ts index 3bb4423ed..3f5ab137e 100644 --- a/packages/core/src/blitzkit/api/server/1.ts +++ b/packages/core/src/blitzkit/api/server/1_tankDefinitions.ts @@ -29,7 +29,7 @@ import { import { Cache, ServerBlitzKitAPI0, -} from "@blitzkit/core/src/blitzkit/api/server/0"; +} from "./0_base"; export abstract class ServerBlitzKitAPI1 extends ServerBlitzKitAPI0 { private parseResearchCost(raw: number | string) { diff --git a/packages/core/src/blitzkit/api/server/2.ts b/packages/core/src/blitzkit/api/server/2_camouflageDefinitions.ts similarity index 88% rename from packages/core/src/blitzkit/api/server/2.ts rename to packages/core/src/blitzkit/api/server/2_camouflageDefinitions.ts index 32d271d2e..30ce31d6a 100644 --- a/packages/core/src/blitzkit/api/server/2.ts +++ b/packages/core/src/blitzkit/api/server/2_camouflageDefinitions.ts @@ -1,6 +1,6 @@ import { CamouflageDefinitions } from "@blitzkit/core"; -import { Cache } from "@blitzkit/core/src/blitzkit/api/server/0"; -import { ServerBlitzKitAPI1 } from "@blitzkit/core/src/blitzkit/api/server/1"; +import { Cache } from "./0_base"; +import { ServerBlitzKitAPI1 } from "./1_tankDefinitions"; export abstract class ServerBlitzKitAPI2 extends ServerBlitzKitAPI1 { @Cache() diff --git a/packages/core/src/blitzkit/api/server/3.ts b/packages/core/src/blitzkit/api/server/3_modelDefinitions.ts similarity index 98% rename from packages/core/src/blitzkit/api/server/3.ts rename to packages/core/src/blitzkit/api/server/3_modelDefinitions.ts index 4aed60738..dfc96c1ab 100644 --- a/packages/core/src/blitzkit/api/server/3.ts +++ b/packages/core/src/blitzkit/api/server/3_modelDefinitions.ts @@ -15,8 +15,8 @@ import { VehicleDefinitionList, VehicleDefinitions, } from "@blitzkit/core"; -import { Cache } from "@blitzkit/core/src/blitzkit/api/server/0"; -import { ServerBlitzKitAPI2 } from "@blitzkit/core/src/blitzkit/api/server/2"; +import { Cache } from "./0_base"; +import { ServerBlitzKitAPI2 } from "./2_camouflageDefinitions"; import { parse as parsePath } from "path"; import { Vector3Tuple } from "three"; diff --git a/packages/core/src/blitzkit/api/server/4.ts b/packages/core/src/blitzkit/api/server/4_mapDefinitions.ts similarity index 76% rename from packages/core/src/blitzkit/api/server/4.ts rename to packages/core/src/blitzkit/api/server/4_mapDefinitions.ts index 576437619..944b3738d 100644 --- a/packages/core/src/blitzkit/api/server/4.ts +++ b/packages/core/src/blitzkit/api/server/4_mapDefinitions.ts @@ -1,6 +1,6 @@ import { MapDefinitions } from "@blitzkit/core"; -import { Cache } from "@blitzkit/core/src/blitzkit/api/server/0"; -import { ServerBlitzKitAPI3 } from "@blitzkit/core/src/blitzkit/api/server/3"; +import { Cache } from "./0_base"; +import { ServerBlitzKitAPI3 } from "./3_modelDefinitions"; export abstract class ServerBlitzKitAPI4 extends ServerBlitzKitAPI3 { @Cache() diff --git a/packages/core/src/blitzkit/api/server/5.ts b/packages/core/src/blitzkit/api/server/5_equipmentDefinitions.ts similarity index 90% rename from packages/core/src/blitzkit/api/server/5.ts rename to packages/core/src/blitzkit/api/server/5_equipmentDefinitions.ts index 676514077..95632096b 100644 --- a/packages/core/src/blitzkit/api/server/5.ts +++ b/packages/core/src/blitzkit/api/server/5_equipmentDefinitions.ts @@ -1,6 +1,6 @@ import { EquipmentDefinitions, EquipmentSlot } from "@blitzkit/core"; -import { Cache } from "@blitzkit/core/src/blitzkit/api/server/0"; -import { ServerBlitzKitAPI4 } from "@blitzkit/core/src/blitzkit/api/server/4"; +import { Cache } from "./0_base"; +import { ServerBlitzKitAPI4 } from "./4_mapDefinitions"; export abstract class ServerBlitzKitAPI5 extends ServerBlitzKitAPI4 { @Cache() diff --git a/packages/core/src/blitzkit/api/server/6.ts b/packages/core/src/blitzkit/api/server/6_consumableDefinitions.ts similarity index 95% rename from packages/core/src/blitzkit/api/server/6.ts rename to packages/core/src/blitzkit/api/server/6_consumableDefinitions.ts index a94400138..f4b333f61 100644 --- a/packages/core/src/blitzkit/api/server/6.ts +++ b/packages/core/src/blitzkit/api/server/6_consumableDefinitions.ts @@ -3,8 +3,8 @@ import { Consumable, ConsumableDefinitions, } from "@blitzkit/core"; -import { Cache } from "@blitzkit/core/src/blitzkit/api/server/0"; -import { ServerBlitzKitAPI5 } from "@blitzkit/core/src/blitzkit/api/server/5"; +import { Cache } from "./0_base"; +import { ServerBlitzKitAPI5 } from "./5_equipmentDefinitions"; export abstract class ServerBlitzKitAPI6 extends ServerBlitzKitAPI5 { @Cache() diff --git a/packages/core/src/blitzkit/api/server/7.ts b/packages/core/src/blitzkit/api/server/7_provisionDefinitions.ts similarity index 95% rename from packages/core/src/blitzkit/api/server/7.ts rename to packages/core/src/blitzkit/api/server/7_provisionDefinitions.ts index 52a7fdedd..59d1ec9fe 100644 --- a/packages/core/src/blitzkit/api/server/7.ts +++ b/packages/core/src/blitzkit/api/server/7_provisionDefinitions.ts @@ -3,8 +3,8 @@ import { Provision, ProvisionDefinitions, } from "@blitzkit/core"; -import { Cache } from "@blitzkit/core/src/blitzkit/api/server/0"; -import { ServerBlitzKitAPI6 } from "@blitzkit/core/src/blitzkit/api/server/6"; +import { Cache } from "./0_base"; +import { ServerBlitzKitAPI6 } from "./6_consumableDefinitions"; export abstract class ServerBlitzKitAPI7 extends ServerBlitzKitAPI6 { @Cache() diff --git a/packages/core/src/blitzkit/api/server/8.ts b/packages/core/src/blitzkit/api/server/8_skillDefinitions.ts similarity index 81% rename from packages/core/src/blitzkit/api/server/8.ts rename to packages/core/src/blitzkit/api/server/8_skillDefinitions.ts index 1be55ec45..30b6794a5 100644 --- a/packages/core/src/blitzkit/api/server/8.ts +++ b/packages/core/src/blitzkit/api/server/8_skillDefinitions.ts @@ -1,7 +1,7 @@ import { BlitzTankClass, SkillDefinitions, TankClass } from "@blitzkit/core"; -import { Cache } from "@blitzkit/core/src/blitzkit/api/server/0"; -import { ServerBlitzKitAPI7 } from "@blitzkit/core/src/blitzkit/api/server/7"; +import { Cache } from "./0_base"; +import { ServerBlitzKitAPI7 } from "./7_provisionDefinitions"; export abstract class ServerBlitzKitAPI8 extends ServerBlitzKitAPI7 { @Cache() diff --git a/packages/core/src/blitzkit/api/server/9.ts b/packages/core/src/blitzkit/api/server/9_gameDefinitions.ts similarity index 92% rename from packages/core/src/blitzkit/api/server/9.ts rename to packages/core/src/blitzkit/api/server/9_gameDefinitions.ts index b341554d9..683c126a5 100644 --- a/packages/core/src/blitzkit/api/server/9.ts +++ b/packages/core/src/blitzkit/api/server/9_gameDefinitions.ts @@ -1,7 +1,7 @@ import { AvailableNationsYaml, GameDefinitions } from "@blitzkit/core"; -import { Cache } from "@blitzkit/core/src/blitzkit/api/server/0"; -import { ServerBlitzKitAPI8 } from "@blitzkit/core/src/blitzkit/api/server/8"; +import { Cache } from "./0_base"; +import { ServerBlitzKitAPI8 } from "./8_skillDefinitions"; export abstract class ServerBlitzKitAPI9 extends ServerBlitzKitAPI8 { @Cache() diff --git a/packages/core/src/blitzkit/api/server/index.ts b/packages/core/src/blitzkit/api/server/index.ts index 076cd6444..33f1f2273 100644 --- a/packages/core/src/blitzkit/api/server/index.ts +++ b/packages/core/src/blitzkit/api/server/index.ts @@ -1,3 +1,3 @@ -import { ServerBlitzKitAPI9 } from "@blitzkit/core/src/blitzkit/api/server/9"; +import { ServerBlitzKitAPI9 } from "./9_gameDefinitions"; export class ServerBlitzKitAPI extends ServerBlitzKitAPI9 {} From 94c3608ca7418c825c8257f9c8ec094a587faf3c Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Tue, 4 Aug 2026 13:35:51 -0500 Subject: [PATCH 033/147] fix missing tank map --- packages/core/src/blitzkit/api/client.ts | 5 +++++ packages/core/src/blitzkit/api/server/0_base.ts | 17 ++++++++++++++++- .../blitzkit/api/server/1_tankDefinitions.ts | 11 +---------- .../api/server/7_provisionDefinitions.ts | 2 +- .../definitions/consumables.pb.ts | 3 --- 5 files changed, 23 insertions(+), 15 deletions(-) diff --git a/packages/core/src/blitzkit/api/client.ts b/packages/core/src/blitzkit/api/client.ts index f4e1c6aed..8ba62e128 100644 --- a/packages/core/src/blitzkit/api/client.ts +++ b/packages/core/src/blitzkit/api/client.ts @@ -3,6 +3,7 @@ import { CamouflageDefinitions, ConsumableDefinitions, GameDefinitions, + ModelDefinitions, ProvisionDefinitions, TankDefinitions, } from "../../protos"; @@ -28,4 +29,8 @@ export class ClientBlitzKitAPI extends BlitzKitAPI { provisionDefinitions() { return fetchPB("/api/definitions/provisions.pb", ProvisionDefinitions); } + + modelDefinitions() { + return fetchPB("/api/definitions/models.pb", ModelDefinitions); + } } diff --git a/packages/core/src/blitzkit/api/server/0_base.ts b/packages/core/src/blitzkit/api/server/0_base.ts index 75eb3c240..a41ec123d 100644 --- a/packages/core/src/blitzkit/api/server/0_base.ts +++ b/packages/core/src/blitzkit/api/server/0_base.ts @@ -21,6 +21,8 @@ import { SquadBattleTypeStylesYaml, TankClass, TankmenAvatar, + toUniqueId, + VehicleDefinitionList, } from "@blitzkit/core"; import { AbstractVFS } from "@blitzkit/core/src/blitzkit/vfs/abstract"; import { SUPPORTED_LOCALE_BLITZ_MAP } from "@blitzkit/i18n"; @@ -217,7 +219,20 @@ export abstract class ServerBlitzKitAPI0 extends BlitzKitAPI { ); } - console.log("Initialization complete, enjoy :)"); + for (const nation of this.nationsDir!) { + const tankList = await this.vfs.xml<{ root: VehicleDefinitionList }>( + `Data/XML/item_defs/vehicles/${nation}/list.xml`, + ); + + for (const tankKey in tankList.root) { + if (this.botPattern.test(tankKey)) continue; + + const tank = tankList.root[tankKey]; + const tankId = toUniqueId(nation, tank.id); + + this.tankStringIdMap[`${nation}:${tankKey}`] = tankId; + } + } return this; } diff --git a/packages/core/src/blitzkit/api/server/1_tankDefinitions.ts b/packages/core/src/blitzkit/api/server/1_tankDefinitions.ts index 3f5ab137e..9269a4900 100644 --- a/packages/core/src/blitzkit/api/server/1_tankDefinitions.ts +++ b/packages/core/src/blitzkit/api/server/1_tankDefinitions.ts @@ -15,7 +15,6 @@ import { ShellKind, sluggify, TankDefinitions, - TankParameters, TankPrice, TankPriceType, TankType, @@ -26,10 +25,7 @@ import { VehicleDefinitionList, VehicleDefinitions, } from "@blitzkit/core"; -import { - Cache, - ServerBlitzKitAPI0, -} from "./0_base"; +import { Cache, ServerBlitzKitAPI0 } from "./0_base"; export abstract class ServerBlitzKitAPI1 extends ServerBlitzKitAPI0 { private parseResearchCost(raw: number | string) { @@ -238,16 +234,11 @@ export abstract class ServerBlitzKitAPI1 extends ServerBlitzKitAPI0 { const tankDefinition = await this.vfs.xml<{ root: VehicleDefinitions }>( `Data/XML/item_defs/vehicles/${nation}/${tankKey}.xml`, ); - const tankParameters = await this.vfs.yaml( - `Data/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`, - ); const tankId = toUniqueId(nation, tank.id); const tankTags = tank.tags.split(" "); const equipment = tankDefinition.root.optDevicePreset; - this.tankStringIdMap[`${nation}:${tankKey}`] = tankId; - const slug = slugs.get(tankId); if (slug === undefined) { diff --git a/packages/core/src/blitzkit/api/server/7_provisionDefinitions.ts b/packages/core/src/blitzkit/api/server/7_provisionDefinitions.ts index 59d1ec9fe..32db2bcc1 100644 --- a/packages/core/src/blitzkit/api/server/7_provisionDefinitions.ts +++ b/packages/core/src/blitzkit/api/server/7_provisionDefinitions.ts @@ -11,7 +11,7 @@ export abstract class ServerBlitzKitAPI7 extends ServerBlitzKitAPI6 { async provisionDefinitions() { const provisionDefinitions = ProvisionDefinitions.create(); - Object.entries(this.provisionsCommon).forEach(([key, provision]) => { + Object.entries(this.provisionsCommon).forEach(([, provision]) => { const entry: Provision = { id: provision.id, exclude: [], diff --git a/packages/website/src/pages/[...chunk_api]/definitions/consumables.pb.ts b/packages/website/src/pages/[...chunk_api]/definitions/consumables.pb.ts index da2ce067e..80b6adcf8 100644 --- a/packages/website/src/pages/[...chunk_api]/definitions/consumables.pb.ts +++ b/packages/website/src/pages/[...chunk_api]/definitions/consumables.pb.ts @@ -5,9 +5,6 @@ export { getStaticPaths } from "../_index"; export async function GET() { const definitions = await api.consumableDefinitions(); - - console.dir(definitions, { depth: null }); - const bytes = ConsumableDefinitions.encode(definitions).finish(); return new Response(bytes); From 98134593105c7d60f0cbb267f2c80072510e3c4d Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Tue, 4 Aug 2026 13:44:19 -0500 Subject: [PATCH 034/147] tank icons --- packages/scripts/src/buildAssets/tankIcons.ts | 38 -------- .../pages/[...chunk_api]/tanks/[id]/_index.ts | 12 +++ .../tanks/[id]/icons/big.webp.ts | 92 +++++++++++++++++++ 3 files changed, 104 insertions(+), 38 deletions(-) create mode 100644 packages/website/src/pages/[...chunk_api]/tanks/[id]/_index.ts create mode 100644 packages/website/src/pages/[...chunk_api]/tanks/[id]/icons/big.webp.ts diff --git a/packages/scripts/src/buildAssets/tankIcons.ts b/packages/scripts/src/buildAssets/tankIcons.ts index 3e8995f8d..1534e800b 100644 --- a/packages/scripts/src/buildAssets/tankIcons.ts +++ b/packages/scripts/src/buildAssets/tankIcons.ts @@ -5,44 +5,6 @@ import { AssetUploader } from "../core/github/assetUploader"; import { vfs } from "./constants"; import type { VehicleDefinitionList } from "./definitions"; -async function extractPackedTankIcon(packedBuffer: Buffer, context: string) { - const texture = sharp(packedBuffer); - const spriteRect = parsePackedSpriteRect(packedBuffer); - - if (!spriteRect) { - throw new Error(`Missing RIFF sprite bounds for ${context}`); - } - - const [left, top, width, height] = spriteRect; - const bounds = [left, top, width, height]; - - if (!bounds.every(Number.isInteger)) { - throw new Error( - `Invalid RIFF sprite bounds for ${context}: ${bounds.join(" ")}`, - ); - } - - const metadata = await texture.metadata(); - - if (metadata.width === undefined || metadata.height === undefined) { - throw new Error(`Failed to read image dimensions for ${context}`); - } - - if ( - left < 0 || - top < 0 || - width <= 0 || - height <= 0 || - left + width > metadata.width || - top + height > metadata.height - ) { - throw new Error( - `Out-of-bounds RIFF sprite for ${context}: ${bounds.join(" ")} in ${metadata.width}x${metadata.height}`, - ); - } - - return await texture.extract({ left, top, width, height }).toBuffer(); -} export async function tankIcons() { console.log("Building tank icons..."); diff --git a/packages/website/src/pages/[...chunk_api]/tanks/[id]/_index.ts b/packages/website/src/pages/[...chunk_api]/tanks/[id]/_index.ts new file mode 100644 index 000000000..8ea202dcb --- /dev/null +++ b/packages/website/src/pages/[...chunk_api]/tanks/[id]/_index.ts @@ -0,0 +1,12 @@ +import { api } from "../../../../core/blitzkit/api"; +import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; +import { getStaticPaths as _getStaticPaths } from "../../_index"; + +export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { + const tanks = await api.tankDefinitions(); + + return Object.values(tanks.tanks).map((tank) => ({ + params: { id: tank.id }, + props: { id: tank.id }, + })); +}); diff --git a/packages/website/src/pages/[...chunk_api]/tanks/[id]/icons/big.webp.ts b/packages/website/src/pages/[...chunk_api]/tanks/[id]/icons/big.webp.ts new file mode 100644 index 000000000..53863090c --- /dev/null +++ b/packages/website/src/pages/[...chunk_api]/tanks/[id]/icons/big.webp.ts @@ -0,0 +1,92 @@ +import { + NATION_IDS, + type TankParameters, + type VehicleDefinitionList, +} from "@blitzkit/core"; +import type { APIContext } from "astro"; +import sharp from "sharp"; +import { parsePackedSpriteRect } from "../../../../../core/blitz/parsePackedSpriteRect"; +import { vfs } from "../../../../../core/blitzkit/vfs"; + +export { getStaticPaths } from "../_index"; + +export async function GET({ props }: APIContext<{ id: number }>) { + const nations = await vfs + .dir(`Data/XML/item_defs/vehicles`) + .then((files) => files.filter((nation) => nation !== "common")); + + for (const nation of nations) { + const tanks = await vfs.xml<{ root: VehicleDefinitionList }>( + `Data/XML/item_defs/vehicles/${nation}/list.xml`, + ); + + for (const tankKey in tanks.root) { + const tank = tanks.root[tankKey]; + + if (tankKey.includes("tutorial_bot")) continue; + + const nationVehicleId = tank.id; + const id = (nationVehicleId << 8) + (NATION_IDS[nation] << 4) + 1; + + if (id !== props.id) continue; + + const parameters = await vfs.yaml( + `Data/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`, + ); + const bigPath = `Data/${parameters.resourcesPath.bigIconPath + .replace(/~res:\//, "") + .replace(/\..+/, "")}.packed.webp`; + const big = await extractPackedTankIcon( + await vfs.file(bigPath), + `tank ${tankKey} big`, + ); + + return new Response(big); + } + } +} + +export async function extractPackedTankIcon( + bytes: Uint8Array, + context: string, +) { + const texture = sharp(bytes); + const spriteRect = parsePackedSpriteRect(bytes); + + if (!spriteRect) { + throw new Error(`Missing RIFF sprite bounds for ${context}`); + } + + const [left, top, width, height] = spriteRect; + const bounds = [left, top, width, height]; + + if (!bounds.every(Number.isInteger)) { + throw new Error( + `Invalid RIFF sprite bounds for ${context}: ${bounds.join(" ")}`, + ); + } + + const metadata = await texture.metadata(); + + if (metadata.width === undefined || metadata.height === undefined) { + throw new Error(`Failed to read image dimensions for ${context}`); + } + + if ( + left < 0 || + top < 0 || + width <= 0 || + height <= 0 || + left + width > metadata.width || + top + height > metadata.height + ) { + throw new Error( + `Out-of-bounds RIFF sprite for ${context}: ${bounds.join(" ")} in ${metadata.width}x${metadata.height}`, + ); + } + + const buffer = await texture.extract({ left, top, width, height }).toBuffer(); + const array = new Uint8Array(buffer); + + return array; +} From 1a7a1223d50014c394c0713b0acd8f4517deac12 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Tue, 4 Aug 2026 13:46:19 -0500 Subject: [PATCH 035/147] scratched flags --- packages/scripts/src/buildAssets/flags.ts | 73 ------------------- .../flags/scratched/[id].webp.ts | 40 ++++++++++ 2 files changed, 40 insertions(+), 73 deletions(-) delete mode 100644 packages/scripts/src/buildAssets/flags.ts create mode 100644 packages/website/src/pages/[...chunk_api]/flags/scratched/[id].webp.ts diff --git a/packages/scripts/src/buildAssets/flags.ts b/packages/scripts/src/buildAssets/flags.ts deleted file mode 100644 index 1168af119..000000000 --- a/packages/scripts/src/buildAssets/flags.ts +++ /dev/null @@ -1,73 +0,0 @@ -import sharp from "sharp"; -import { AssetUploader } from "../core/github/assetUploader"; -import { vfs } from "./constants"; - -export async function flags() { - console.log("Building flags..."); - - using uploader = new AssetUploader("flags"); - const circleFiles = await vfs - .dir(`Data/Gfx/Lobby/flags`) - .then((files) => - files.filter( - (flag) => - flag.startsWith("flag_profile-stat_") && - !flag.endsWith("@2x.packed.webp"), - ), - ); - const scratchedFiles = await vfs - .dir(`Data/Gfx/Lobby/flags`) - .then((files) => - files.filter( - (flag) => - flag.startsWith("flag_tutor-tank_") && - !flag.endsWith("@2x.packed.webp"), - ), - ); - const fadedFiles = await vfs - .dir(`Data/Gfx/Lobby/flags`) - .then((files) => - files.filter( - (flag) => - flag.startsWith("flag_filter_") && flag.endsWith("@2x.packed.webp"), - ), - ); - - for (const flag of circleFiles) { - const image = sharp(await vfs.file(`Data/Gfx/Lobby/flags/${flag}`)); - const content = await image.trim({ threshold: 100 }).toBuffer(); - const name = flag.match(/flag_profile-stat_(.+)\.packed\.webp/)![1]; - - uploader.add({ - content, - path: `flags/circle/${name}.webp`, - }); - } - - for (const flag of scratchedFiles) { - const content = await vfs.file(`Data/Gfx/Lobby/flags/${flag}`); - const name = flag.match(/flag_tutor-tank_(.+)\.packed\.webp/)![1]; - - uploader.add({ - path: `flags/scratched/${name}.webp`, - content, - }); - } - - for (const flag of fadedFiles) { - const content = await sharp(await vfs.file(`Data/Gfx/Lobby/flags/${flag}`)) - .trim({ - threshold: 100, - background: { r: 0, g: 0, b: 0, alpha: 0 }, - }) - .toBuffer(); - const name = flag.match(/flag_filter_(.+)@2x\.packed\.webp/)![1]; - - uploader.add({ - content, - path: `flags/fade_small/${name}.webp`, - }); - } - - await uploader.flush(); -} diff --git a/packages/website/src/pages/[...chunk_api]/flags/scratched/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/flags/scratched/[id].webp.ts new file mode 100644 index 000000000..c56db0a06 --- /dev/null +++ b/packages/website/src/pages/[...chunk_api]/flags/scratched/[id].webp.ts @@ -0,0 +1,40 @@ +import type { APIContext, GetStaticPathsItem } from "astro"; +import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; +import { vfs } from "../../../../core/blitzkit/vfs"; +import { getStaticPaths as _getStaticPaths } from "../../_index"; + +export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { + const paths: GetStaticPathsItem[] = []; + + const scratchedFiles = await vfs + .dir("Data/Gfx/Lobby/flags") + .then((files) => + files.filter( + (flag) => + flag.startsWith("flag_tutor-tank_") && + !flag.endsWith("@2x.packed.webp"), + ), + ); + + for (const flag of scratchedFiles) { + const path = `Data/Gfx/Lobby/flags/${flag}`; + const id = flag.match(/flag_tutor-tank_(.+)\.packed\.webp/)![1]; + + paths.push({ + params: { id }, + props: { path }, + }); + } + + return paths; +}); + +export async function GET({ props }: APIContext<{ path: string }>) { + const bytes = new Uint8Array(await vfs.file(props.path)); + + return new Response(bytes, { + headers: { + "Content-Type": "image/webp", + }, + }); +} From 33ebbbeb65ca05fe92b199d9b23557a8419833df Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Tue, 4 Aug 2026 14:02:52 -0500 Subject: [PATCH 036/147] imports --- .../blitzkit/api/server/8_skillDefinitions.ts | 7 +-- packages/core/src/types/tankmenAvatar.ts | 4 +- .../src/components/CrewSkillManager.tsx | 12 +++--- .../ModuleButtons/ConsumableButton.tsx | 7 ++- .../ModuleButtons/EquipmentButton.tsx | 7 ++- .../ModuleButtons/ProvisionButton.tsx | 7 ++- .../src/components/ProvisionsManager.tsx | 4 +- .../components/Tankopedia/CalloutsSection.tsx | 5 ++- .../Characteristics/components/Crew.tsx | 4 +- .../Characteristics/components/Firepower.tsx | 4 +- .../components/CanvasContent.tsx | 4 +- .../components/ModelChunk.tsx | 4 +- .../GunFlexibilityVisualizer/index.tsx | 4 +- .../components/InfoWithDelta.tsx | 16 +++---- .../components/ShellSwitcher.tsx | 3 +- .../components/RicochetVisualizer/index.tsx | 4 +- .../components/Characteristics/index.tsx | 7 ++- .../components/Consumables.tsx | 12 +++--- .../components/Equalizer.tsx | 15 +------ .../components/Equipment.tsx | 6 +-- .../components/Miscellaneous.tsx | 7 ++- .../components/Modules.tsx | 9 ++-- .../components/Provisions.tsx | 4 +- .../components/Tankopedia/GameModeSection.tsx | 23 ++++------ .../HeroSection/components/Options/index.tsx | 9 ++-- .../TankSandbox/components/Control.tsx | 4 +- .../TankSandbox/components/InitialAligner.tsx | 4 +- .../Tankopedia/HeroSection/index.tsx | 4 +- .../Tankopedia/MetaSection/index.tsx | 43 ++++++++----------- .../TechTreeSection/components/Node.tsx | 8 ++-- .../Tankopedia/TechTreeSection/index.tsx | 8 ++-- .../core/awaitables/equipmentDefinitions.ts | 3 -- .../src/core/awaitables/skillDefinitions.ts | 3 -- .../website/src/core/blitzkit/hasEquipment.ts | 8 ++-- packages/website/src/hooks/useEquipment.ts | 14 +++--- .../[...locale]/tanks/[slug]/_index.tsx | 35 ++++++++------- .../[...locale]/tanks/[slug]/index.astro | 31 ++++++------- packages/website/src/stores/duel/index.ts | 4 +- packages/website/src/stores/tankopedia.ts | 4 +- 39 files changed, 153 insertions(+), 208 deletions(-) delete mode 100644 packages/website/src/core/awaitables/equipmentDefinitions.ts delete mode 100644 packages/website/src/core/awaitables/skillDefinitions.ts diff --git a/packages/core/src/blitzkit/api/server/8_skillDefinitions.ts b/packages/core/src/blitzkit/api/server/8_skillDefinitions.ts index 30b6794a5..9198618fa 100644 --- a/packages/core/src/blitzkit/api/server/8_skillDefinitions.ts +++ b/packages/core/src/blitzkit/api/server/8_skillDefinitions.ts @@ -1,4 +1,4 @@ -import { BlitzTankClass, SkillDefinitions, TankClass } from "@blitzkit/core"; +import { BlitzTankClass, SkillDefinitions } from "@blitzkit/core"; import { Cache } from "./0_base"; import { ServerBlitzKitAPI7 } from "./7_provisionDefinitions"; @@ -9,10 +9,7 @@ export abstract class ServerBlitzKitAPI8 extends ServerBlitzKitAPI7 { const skillDefinitions = SkillDefinitions.create(); for (const tankClass in this.tankmenAvatar!.root.skillsByClasses) { - const skills = - this.tankmenAvatar!.root.skillsByClasses[ - Number(tankClass) as TankClass - ]; + const skills = this.tankmenAvatar!.root.skillsByClasses[tankClass]; skillDefinitions.classes[ this.blitzTankClassToBlitzkit[tankClass as BlitzTankClass] diff --git a/packages/core/src/types/tankmenAvatar.ts b/packages/core/src/types/tankmenAvatar.ts index b57550cbc..a254e35a7 100644 --- a/packages/core/src/types/tankmenAvatar.ts +++ b/packages/core/src/types/tankmenAvatar.ts @@ -1,8 +1,6 @@ -import { TankClass } from "@blitzkit/core"; - export interface TankmenAvatar { roles: unknown; - skillsByClasses: Record; + skillsByClasses: Record; skills: { [name: string]: { userString: string; diff --git a/packages/website/src/components/CrewSkillManager.tsx b/packages/website/src/components/CrewSkillManager.tsx index 87e459f6b..2a0a17a2a 100644 --- a/packages/website/src/components/CrewSkillManager.tsx +++ b/packages/website/src/components/CrewSkillManager.tsx @@ -1,14 +1,14 @@ -import { asset, permanentSkills, romanize } from '@blitzkit/core'; -import { Flex } from '@radix-ui/themes'; -import { awaitableSkillDefinitions } from '../core/awaitables/skillDefinitions'; -import { GenericTankComponentButton } from './ModuleButtons/GenericTankComponentButton'; +import { permanentSkills, romanize } from "@blitzkit/core"; +import { Flex } from "@radix-ui/themes"; +import { api } from "../core/blitzkit/api"; +import { GenericTankComponentButton } from "./ModuleButtons/GenericTankComponentButton"; interface CrewSkillManagerProps { skillLevels: Record; onChange?: (skillLevels: Record) => void; } -const skillDefinitions = await awaitableSkillDefinitions; +const skillDefinitions = await api.skillDefinitions(); export function CrewSkillManager({ skillLevels, @@ -27,7 +27,7 @@ export function CrewSkillManager({ special={!permanentSkills.includes(skill)} selected={level > 0} discriminator={level === 0 ? undefined : romanize(level)} - icon={asset(`icons/skills/${skill}.webp`)} + icon={`/api/icons/skills/${skill}.webp`} onClick={(event) => { if (!onChange) return; diff --git a/packages/website/src/components/ModuleButtons/ConsumableButton.tsx b/packages/website/src/components/ModuleButtons/ConsumableButton.tsx index f6ddf6749..92069d40f 100644 --- a/packages/website/src/components/ModuleButtons/ConsumableButton.tsx +++ b/packages/website/src/components/ModuleButtons/ConsumableButton.tsx @@ -1,7 +1,6 @@ -import { asset } from "@blitzkit/core"; import { ClockIcon, ReloadIcon } from "@radix-ui/react-icons"; import { Flex, Text } from "@radix-ui/themes"; -import { awaitableConsumableDefinitions } from "../../core/awaitables/consumableDefinitions"; +import { api } from "../../core/blitzkit/api"; import { useDelta } from "../../hooks/useDelta"; import { useLocale } from "../../hooks/useLocale"; import { GenericTankComponentButton } from "./GenericTankComponentButton"; @@ -13,7 +12,7 @@ interface ConsumableButtonProps extends TankComponentButtonProps { cooldown?: number; } -const consumableDefinitions = await awaitableConsumableDefinitions; +const consumableDefinitions = await api.consumableDefinitions(); export function ConsumableButton({ consumable, @@ -29,7 +28,7 @@ export function ConsumableButton({ {(cooldown || duration) && ( diff --git a/packages/website/src/components/ModuleButtons/EquipmentButton.tsx b/packages/website/src/components/ModuleButtons/EquipmentButton.tsx index 337937832..8d839cf0f 100644 --- a/packages/website/src/components/ModuleButtons/EquipmentButton.tsx +++ b/packages/website/src/components/ModuleButtons/EquipmentButton.tsx @@ -1,5 +1,4 @@ -import { asset } from "@blitzkit/core"; -import { awaitableEquipmentDefinitions } from "../../core/awaitables/equipmentDefinitions"; +import { api } from "../../core/blitzkit/api"; import { useLocale } from "../../hooks/useLocale"; import { GenericTankComponentButton } from "./GenericTankComponentButton"; import type { TankComponentButtonProps } from "./TankComponentButton"; @@ -8,7 +7,7 @@ interface EquipmentButtonProps extends TankComponentButtonProps { equipment: number; } -const equipmentDefinitions = await awaitableEquipmentDefinitions; +const equipmentDefinitions = await api.equipmentDefinitions(); export function EquipmentButton({ equipment, ...props }: EquipmentButtonProps) { const { unwrap } = useLocale(); @@ -16,7 +15,7 @@ export function EquipmentButton({ equipment, ...props }: EquipmentButtonProps) { return ( ); diff --git a/packages/website/src/components/ModuleButtons/ProvisionButton.tsx b/packages/website/src/components/ModuleButtons/ProvisionButton.tsx index a3876df40..fb3225c83 100644 --- a/packages/website/src/components/ModuleButtons/ProvisionButton.tsx +++ b/packages/website/src/components/ModuleButtons/ProvisionButton.tsx @@ -1,5 +1,4 @@ -import { asset } from "@blitzkit/core"; -import { awaitableProvisionDefinitions } from "../../core/awaitables/provisionDefinitions"; +import { api } from "../../core/blitzkit/api"; import { useLocale } from "../../hooks/useLocale"; import { GenericTankComponentButton } from "./GenericTankComponentButton"; import type { TankComponentButtonProps } from "./TankComponentButton"; @@ -8,7 +7,7 @@ interface ProvisionButtonProps extends TankComponentButtonProps { provision: number; } -const provisionDefinitions = await awaitableProvisionDefinitions; +const provisionDefinitions = await api.provisionDefinitions(); export function ProvisionButton({ provision, ...props }: ProvisionButtonProps) { const { unwrap } = useLocale(); @@ -16,7 +15,7 @@ export function ProvisionButton({ provision, ...props }: ProvisionButtonProps) { return ( ); diff --git a/packages/website/src/components/ProvisionsManager.tsx b/packages/website/src/components/ProvisionsManager.tsx index 7ede38889..9d61baf15 100644 --- a/packages/website/src/components/ProvisionsManager.tsx +++ b/packages/website/src/components/ProvisionsManager.tsx @@ -1,5 +1,5 @@ import { Flex } from "@radix-ui/themes"; -import { awaitableProvisionDefinitions } from "../core/awaitables/provisionDefinitions"; +import { api } from "../core/blitzkit/api"; import { ProvisionButton } from "./ModuleButtons/ProvisionButton"; interface ProvisionsManagerProps { @@ -9,7 +9,7 @@ interface ProvisionsManagerProps { onChange?: (provisions: number[]) => void; } -const provisionDefinitions = await awaitableProvisionDefinitions; +const provisionDefinitions = await api.provisionDefinitions(); export function ProvisionsManager({ provisions, diff --git a/packages/website/src/components/Tankopedia/CalloutsSection.tsx b/packages/website/src/components/Tankopedia/CalloutsSection.tsx index 4bc97c35c..ac10eaf67 100644 --- a/packages/website/src/components/Tankopedia/CalloutsSection.tsx +++ b/packages/website/src/components/Tankopedia/CalloutsSection.tsx @@ -1,15 +1,16 @@ -import { assertSecret, fetchTankDefinitions } from "@blitzkit/core"; +import { assertSecret } from "@blitzkit/core"; import { MinusCircledIcon } from "@radix-ui/react-icons"; import { Callout, Flex } from "@radix-ui/themes"; import { uniq } from "lodash-es"; import { useEffect } from "react"; +import { api } from "../../core/blitzkit/api"; import { Duel } from "../../stores/duel"; import { TankopediaPersistent } from "../../stores/tankopediaPersistent"; import { AesonPlug } from "../AesonPlug"; import { ExperimentIcon } from "../ExperimentIcon"; import { MAX_RECENTLY_VIEWED } from "../TankSearch/constants"; -const tankDefinitions = await fetchTankDefinitions(); +const tankDefinitions = await api.tankDefinitions(); export function CalloutsSection() { const tank = Duel.use((state) => state.protagonist.tank); diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Crew.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Crew.tsx index 471b27da0..ff4118528 100644 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Crew.tsx +++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Crew.tsx @@ -3,7 +3,7 @@ import { literals } from "@blitzkit/i18n"; import { AccessibilityIcon, InfoCircledIcon } from "@radix-ui/react-icons"; import { Flex, Heading, IconButton, Popover, Text } from "@radix-ui/themes"; import { Fragment } from "react/jsx-runtime"; -import { awaitableProvisionDefinitions } from "../../../../../../core/awaitables/provisionDefinitions"; +import { api } from "../../../../../../core/blitzkit/api"; import { useEquipment } from "../../../../../../hooks/useEquipment"; import { useLocale } from "../../../../../../hooks/useLocale"; import { Duel } from "../../../../../../stores/duel"; @@ -11,7 +11,7 @@ import { InfoWithDelta } from "./InfoWithDelta"; import { StatsTableWrapper } from "./StatsTableWrapper"; import type { StatsAcceptorProps } from "./TraverseVisualizer"; -const provisionDefinitions = await awaitableProvisionDefinitions; +const provisionDefinitions = await api.provisionDefinitions(); export function Crew({ stats }: StatsAcceptorProps) { const tank = Duel.use((state) => state.protagonist.tank); diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Firepower.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Firepower.tsx index 4fc066c22..3e2378d5f 100644 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Firepower.tsx +++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Firepower.tsx @@ -1,4 +1,4 @@ -import { asset, isExplosive } from "@blitzkit/core"; +import { isExplosive } from "@blitzkit/core"; import { literals } from "@blitzkit/i18n"; import { Flex, Heading, IconButton, Tooltip } from "@radix-ui/themes"; import { useLocale } from "../../../../../../hooks/useLocale"; @@ -62,7 +62,7 @@ export function Firepower({ alt={unwrap(thisShell.name!)} width={16} height={16} - src={asset(`icons/shells/${thisShell.icon}.webp`)} + src={`/api/icons/shells/${thisShell.icon}.webp`} /> diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/GunFlexibilityVisualizer/components/CanvasContent.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/GunFlexibilityVisualizer/components/CanvasContent.tsx index b062628bc..2f1d7342c 100644 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/GunFlexibilityVisualizer/components/CanvasContent.tsx +++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/GunFlexibilityVisualizer/components/CanvasContent.tsx @@ -1,7 +1,7 @@ import type { QuicklimeEvent } from "quicklime"; import { useEffect, useRef } from "react"; import type { Group } from "three"; -import { awaitableModelDefinitions } from "../../../../../../../../core/awaitables/modelDefinitions"; +import { api } from "../../../../../../../../core/blitzkit/api"; import { modelTransformEvent, type ModelTransformEventData, @@ -9,7 +9,7 @@ import { import { Duel } from "../../../../../../../../stores/duel"; import { ModelChunk } from "./ModelChunk"; -const modelDefinitions = await awaitableModelDefinitions; +const modelDefinitions = await api.modelDefinitions(); export function CanvasContent() { const hullWrapper = useRef(null); diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/GunFlexibilityVisualizer/components/ModelChunk.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/GunFlexibilityVisualizer/components/ModelChunk.tsx index 366971625..a32c75cb9 100644 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/GunFlexibilityVisualizer/components/ModelChunk.tsx +++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/GunFlexibilityVisualizer/components/ModelChunk.tsx @@ -1,11 +1,11 @@ import { mauveDark } from "@radix-ui/colors"; import { LineBasicMaterial, MeshBasicMaterial } from "three"; -import { awaitableModelDefinitions } from "../../../../../../../../core/awaitables/modelDefinitions"; +import { api } from "../../../../../../../../core/blitzkit/api"; import { jsxTree } from "../../../../../../../../core/blitzkit/jsxTree"; import { useModel } from "../../../../../../../../hooks/useModel"; import { Duel } from "../../../../../../../../stores/duel"; -const modelDefinitions = await awaitableModelDefinitions; +const modelDefinitions = await api.modelDefinitions(); const surfaceMaterial: Record = { gun: new MeshBasicMaterial({ diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/GunFlexibilityVisualizer/index.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/GunFlexibilityVisualizer/index.tsx index a278a230a..171c1a73d 100644 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/GunFlexibilityVisualizer/index.tsx +++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/GunFlexibilityVisualizer/index.tsx @@ -3,11 +3,11 @@ import { Box, Card, Flex } from "@radix-ui/themes"; import { clamp } from "lodash-es"; import { useRef, useState } from "react"; import { degToRad, radToDeg } from "three/src/math/MathUtils.js"; -import { awaitableModelDefinitions } from "../../../../../../../core/awaitables/modelDefinitions"; import { DEFAULT_PITCH_TRANSITION, applyPitchYawLimits, } from "../../../../../../../core/blitz/applyPitchYawLimits"; +import { api } from "../../../../../../../core/blitzkit/api"; import { hasEquipment } from "../../../../../../../core/blitzkit/hasEquipment"; import { modelTransformEvent } from "../../../../../../../core/blitzkit/modelTransform"; import { Var } from "../../../../../../../core/radix/var"; @@ -20,7 +20,7 @@ import { FlexibilityCanvas } from "./components/FlexibilityCanvas"; const ANGLE_COEFFICIENT = 1 / 10; -const modelDefinition = await awaitableModelDefinitions; +const modelDefinition = await api.modelDefinitions(); function mag(x: number) { return (1 / Math.PI) * Math.atan(ANGLE_COEFFICIENT * x) + 1 / 2; diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/InfoWithDelta.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/InfoWithDelta.tsx index f29d6993a..7d910738d 100644 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/InfoWithDelta.tsx +++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/InfoWithDelta.tsx @@ -2,11 +2,7 @@ import { createDefaultSkills } from "@blitzkit/core"; import { Flex, Progress, Text } from "@radix-ui/themes"; import { clamp } from "lodash-es"; import { memo, useMemo, type ComponentProps, type ReactNode } from "react"; -import { awaitableEquipmentDefinitions } from "../../../../../../core/awaitables/equipmentDefinitions"; -import { awaitableModelDefinitions } from "../../../../../../core/awaitables/modelDefinitions"; -import { awaitableProvisionDefinitions } from "../../../../../../core/awaitables/provisionDefinitions"; -import { awaitableSkillDefinitions } from "../../../../../../core/awaitables/skillDefinitions"; -import { awaitableTankDefinitions } from "../../../../../../core/awaitables/tankDefinitions"; +import { api } from "../../../../../../core/blitzkit/api"; import { tankCharacteristics, type TankCharacteristics, @@ -41,11 +37,11 @@ const [ modelDefinitions, skillDefinitions, ] = await Promise.all([ - awaitableTankDefinitions, - awaitableProvisionDefinitions, - awaitableEquipmentDefinitions, - awaitableModelDefinitions, - awaitableSkillDefinitions, + api.tankDefinitions(), + api.provisionDefinitions(), + api.equipmentDefinitions(), + api.modelDefinitions(), + api.skillDefinitions(), ]); export const InfoWithDelta = memo( diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/RicochetVisualizer/components/ShellSwitcher.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/RicochetVisualizer/components/ShellSwitcher.tsx index 52d3b431d..68667a0a0 100644 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/RicochetVisualizer/components/ShellSwitcher.tsx +++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/RicochetVisualizer/components/ShellSwitcher.tsx @@ -1,4 +1,3 @@ -import { asset } from "@blitzkit/core"; import { Flex, IconButton, Tooltip } from "@radix-ui/themes"; import { useLocale } from "../../../../../../../../hooks/useLocale"; import { Duel } from "../../../../../../../../stores/duel"; @@ -38,7 +37,7 @@ export function ShellSwitcher() { {unwrap(thisShell.name!)} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/RicochetVisualizer/index.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/RicochetVisualizer/index.tsx index a688de0d7..52deb82c8 100644 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/RicochetVisualizer/index.tsx +++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/RicochetVisualizer/index.tsx @@ -1,4 +1,4 @@ -import { asset, isExplosive } from "@blitzkit/core"; +import { isExplosive } from "@blitzkit/core"; import { literals } from "@blitzkit/i18n"; import { Box, Flex, Text } from "@radix-ui/themes"; import { useRef, useState } from "react"; @@ -124,7 +124,7 @@ export function RicochetVisualizer({ stats }: StatsAcceptorProps) { /> state.protagonist); const consumables = Duel.use((state) => state.protagonist.consumables); const consumablesList = Object.values( - consumableDefinitions.consumables + consumableDefinitions.consumables, ).filter((consumable) => checkConsumableProvisionInclusivity( consumable, protagonist.tank, - protagonist.gun - ) + protagonist.gun, + ), ); const cooldownBooster = Duel.use( - (state) => state.protagonist.cooldownBooster + (state) => state.protagonist.cooldownBooster, ); const hasConsumableDeliverySystem = useEquipment(118); const hasHighEndConsumables = useEquipment(101); diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Equalizer.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Equalizer.tsx index 1ec26568a..6940f664b 100644 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Equalizer.tsx +++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Equalizer.tsx @@ -1,15 +1,4 @@ -import { asset } from "@blitzkit/core"; -import { InfoCircledIcon } from "@radix-ui/react-icons"; -import { - Badge, - Box, - Flex, - Heading, - Link, - Popover, - Switch, - Text, -} from "@radix-ui/themes"; +import { Badge, Box, Flex, Heading, Switch, Text } from "@radix-ui/themes"; import { useLocale } from "../../../../hooks/useLocale"; import { Duel } from "../../../../stores/duel"; import { Tankopedia } from "../../../../stores/tankopedia"; @@ -44,7 +33,7 @@ export function Equalizer() { style={{ cursor: "pointer", borderRadius: "var(--radius-3)", - backgroundImage: `url(${asset("icons/game_mode_banners/45.webp")})`, + backgroundImage: "url(/api/icons/game_mode_banners/45.webp)", backgroundSize: "cover", backgroundPosition: "center", overflow: "hidden", diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Equipment.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Equipment.tsx index 69a428b3f..17871441e 100644 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Equipment.tsx +++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Equipment.tsx @@ -1,18 +1,18 @@ import { Button, Flex, Heading } from "@radix-ui/themes"; -import { awaitableEquipmentDefinitions } from "../../../../core/awaitables/equipmentDefinitions"; +import { api } from "../../../../core/blitzkit/api"; import { useLocale } from "../../../../hooks/useLocale"; import { Duel } from "../../../../stores/duel"; import { EquipmentManager } from "../../../EquipmentManager"; import { ConfigurationChildWrapper } from "./ConfigurationChildWrapper"; -const equipmentDefinitions = await awaitableEquipmentDefinitions; +const equipmentDefinitions = await api.equipmentDefinitions(); export function Equipment() { const protagonist = Duel.use((state) => state.protagonist); const equipmentPreset = equipmentDefinitions.presets[protagonist.tank.equipment_preset]; const equipmentMatrix = Duel.use( - (state) => state.protagonist.equipmentMatrix + (state) => state.protagonist.equipmentMatrix, ); const { strings } = useLocale(); diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Miscellaneous.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Miscellaneous.tsx index 16423a884..783b36646 100644 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Miscellaneous.tsx +++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Miscellaneous.tsx @@ -1,4 +1,3 @@ -import { asset } from "@blitzkit/core"; import { Flex, Heading } from "@radix-ui/themes"; import { useLocale } from "../../../../hooks/useLocale"; import { Duel } from "../../../../stores/duel"; @@ -8,7 +7,7 @@ import { ConfigurationChildWrapper } from "./ConfigurationChildWrapper"; export function Miscellaneous() { const camouflage = Duel.use((state) => state.protagonist.camouflage); const cooldownBooster = Duel.use( - (state) => state.protagonist.cooldownBooster + (state) => state.protagonist.cooldownBooster, ); const { tank } = Duel.use((state) => state.protagonist); const { strings } = useLocale(); @@ -22,7 +21,7 @@ export function Miscellaneous() { {!tank.fixed_camouflage && ( )} 0} banner={ cooldownBooster === 0 diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Modules.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Modules.tsx index a290cf245..ef2e7fb2a 100644 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Modules.tsx +++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Modules.tsx @@ -1,5 +1,4 @@ import { - asset, EngineDefinition, formatCompact, GunDefinition, @@ -20,7 +19,7 @@ import { Text, Tooltip, } from "@radix-ui/themes"; -import { awaitableTankDefinitions } from "../../../../core/awaitables/tankDefinitions"; +import { api } from "../../../../core/blitzkit/api"; import { useLocale } from "../../../../hooks/useLocale"; import { Duel } from "../../../../stores/duel"; import { LinkI18n } from "../../../LinkI18n"; @@ -111,7 +110,7 @@ function ModuleButton({ {unlock.cost!.type}{node}; } -const tankDefinitions = await awaitableTankDefinitions; +const tankDefinitions = await api.tankDefinitions(); export function Modules() { const tank = Duel.use((state) => state.protagonist.tank); diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Provisions.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Provisions.tsx index 258b23141..0be27f492 100644 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Provisions.tsx +++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Provisions.tsx @@ -1,12 +1,12 @@ import { availableProvisions } from "@blitzkit/core"; import { Button, Flex, Heading } from "@radix-ui/themes"; -import { awaitableProvisionDefinitions } from "../../../../core/awaitables/provisionDefinitions"; +import { api } from "../../../../core/blitzkit/api"; import { useLocale } from "../../../../hooks/useLocale"; import { Duel } from "../../../../stores/duel"; import { ProvisionsManager } from "../../../ProvisionsManager"; import { ConfigurationChildWrapper } from "./ConfigurationChildWrapper"; -const provisionDefinitions = await awaitableProvisionDefinitions; +const provisionDefinitions = await api.provisionDefinitions(); export function Provisions() { const { tank, gun } = Duel.use((state) => state.protagonist); diff --git a/packages/website/src/components/Tankopedia/GameModeSection.tsx b/packages/website/src/components/Tankopedia/GameModeSection.tsx index a43a3fb94..3aadfb03e 100644 --- a/packages/website/src/components/Tankopedia/GameModeSection.tsx +++ b/packages/website/src/components/Tankopedia/GameModeSection.tsx @@ -1,16 +1,13 @@ -import { asset } from "@blitzkit/core"; import { Box, Flex, Heading, Text } from "@radix-ui/themes"; -import { awaitableConsumableDefinitions } from "../../core/awaitables/consumableDefinitions"; -import { awaitableGameDefinitions } from "../../core/awaitables/gameDefinitions"; -import { awaitableProvisionDefinitions } from "../../core/awaitables/provisionDefinitions"; +import { api } from "../../core/blitzkit/api"; import { useLocale } from "../../hooks/useLocale"; import { Duel } from "../../stores/duel"; const [gameDefinitions, consumableDefinitions, provisionDefinitions] = await Promise.all([ - awaitableGameDefinitions, - awaitableConsumableDefinitions, - awaitableProvisionDefinitions, + api.gameDefinitions(), + api.consumableDefinitions(), + api.provisionDefinitions(), ]); export function GameModeSection() { @@ -40,9 +37,7 @@ export function GameModeSection() { direction="column" style={{ borderRadius: "var(--radius-3)", - background: `url(${asset( - `icons/game_mode_banners/${id}.webp`, - )})`, + background: `url(/api/icons/game_mode_banners/${id}.webp)`, backgroundSize: "cover", backgroundPosition: "center", }} @@ -50,9 +45,7 @@ export function GameModeSection() { {unwrap(provisions.name!)} @@ -108,7 +101,7 @@ export function GameModeSection() { height: "1.5em", objectFit: "contain", }} - src={asset(`icons/consumables/${id}.webp`)} + src={`/api/icons/consumables/${id}.webp`} alt={unwrap(consumable.name!)} /> {unwrap(consumable.name!)} 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 10841eeda..f3c2310cd 100644 --- a/packages/website/src/components/Tankopedia/HeroSection/components/Options/index.tsx +++ b/packages/website/src/components/Tankopedia/HeroSection/components/Options/index.tsx @@ -1,5 +1,4 @@ import { - asset, resolvePenetrationCoefficient, TIER_ROMAN_NUMERALS, uniqueGuns, @@ -140,7 +139,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) { Antagonist Gun {unwrap(thisShell.name!)} Calibrated Shells Enhanced Armor = { const inspectModeInitialPosition = new Vector3(-8, 2, -13); -const modelDefinitions = await awaitableModelDefinitions; +const modelDefinitions = await api.modelDefinitions(); interface ControlsProps { autoRotate?: boolean; diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/InitialAligner.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/InitialAligner.tsx index 26eb65286..351f2d19b 100644 --- a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/InitialAligner.tsx +++ b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/InitialAligner.tsx @@ -1,11 +1,11 @@ import { useEffect } from "react"; import { degToRad } from "three/src/math/MathUtils.js"; -import { awaitableModelDefinitions } from "../../../../../../core/awaitables/modelDefinitions"; import { applyPitchYawLimits } from "../../../../../../core/blitz/applyPitchYawLimits"; +import { api } from "../../../../../../core/blitzkit/api"; import { modelTransformEvent } from "../../../../../../core/blitzkit/modelTransform"; import { Duel } from "../../../../../../stores/duel"; -const modelDefinitions = await awaitableModelDefinitions; +const modelDefinitions = await api.modelDefinitions(); export function InitialAligner() { const tank = Duel.use((state) => state.protagonist.tank); diff --git a/packages/website/src/components/Tankopedia/HeroSection/index.tsx b/packages/website/src/components/Tankopedia/HeroSection/index.tsx index af403e531..11f13d58e 100644 --- a/packages/website/src/components/Tankopedia/HeroSection/index.tsx +++ b/packages/website/src/components/Tankopedia/HeroSection/index.tsx @@ -1,7 +1,7 @@ import { Box, Flex } from "@radix-ui/themes"; import { times } from "lodash-es"; import { useEffect, useMemo, useRef } from "react"; -import { awaitableTankDefinitions } from "../../../core/awaitables/tankDefinitions"; +import { api } from "../../../core/blitzkit/api"; import { defaultEqualizer } from "../../../core/blitzkit/tankToDuelMember"; import { useFullScreen } from "../../../hooks/useFullScreen"; import { Duel } from "../../../stores/duel"; @@ -12,7 +12,7 @@ import { Options } from "./components/Options"; import { TankSandbox } from "./components/TankSandbox"; import { Title } from "./components/TankSandbox/Title"; -const tankDefinitions = await awaitableTankDefinitions; +const tankDefinitions = await api.tankDefinitions(); export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) { const equalize = Duel.use((state) => state.equalize); diff --git a/packages/website/src/components/Tankopedia/MetaSection/index.tsx b/packages/website/src/components/Tankopedia/MetaSection/index.tsx index d795d1926..69f72f40d 100644 --- a/packages/website/src/components/Tankopedia/MetaSection/index.tsx +++ b/packages/website/src/components/Tankopedia/MetaSection/index.tsx @@ -1,9 +1,4 @@ -import { - asset, - TankPriceType, - TankType, - TIER_ROMAN_NUMERALS, -} from "@blitzkit/core"; +import { TankPriceType, TankType, TIER_ROMAN_NUMERALS } from "@blitzkit/core"; import { Box, Flex } from "@radix-ui/themes"; import { Var } from "../../../core/radix/var"; import { useLocale } from "../../../hooks/useLocale"; @@ -40,7 +35,7 @@ export function MetaSection() { > { strings.common.nations[ @@ -135,7 +130,7 @@ export function MetaSection() { gold @@ -152,14 +147,12 @@ export function MetaSection() { {TankPriceType[protagonist.price!.type]} @@ -169,14 +162,12 @@ export function MetaSection() { {TankPriceType[protagonist.price!.type]} @@ -192,7 +183,7 @@ export function MetaSection() { xp diff --git a/packages/website/src/components/Tankopedia/TechTreeSection/components/Node.tsx b/packages/website/src/components/Tankopedia/TechTreeSection/components/Node.tsx index 9a6ceb7f7..78c06a86a 100644 --- a/packages/website/src/components/Tankopedia/TechTreeSection/components/Node.tsx +++ b/packages/website/src/components/Tankopedia/TechTreeSection/components/Node.tsx @@ -1,8 +1,8 @@ -import { asset, formatCompact, TIER_ROMAN_NUMERALS } from "@blitzkit/core"; +import { formatCompact, TIER_ROMAN_NUMERALS } from "@blitzkit/core"; import { literals } from "@blitzkit/i18n/src/literals"; import { Box, Flex, Skeleton, Text } from "@radix-ui/themes"; import { awaitableAverageDefinitions } from "../../../../core/awaitables/averageDefinitions"; -import { awaitableTankDefinitions } from "../../../../core/awaitables/tankDefinitions"; +import { api } from "../../../../core/blitzkit/api"; import { useLocale } from "../../../../hooks/useLocale"; import { Tankopedia } from "../../../../stores/tankopedia"; import type { MaybeSkeletonComponentProps } from "../../../../types/maybeSkeletonComponentProps"; @@ -15,7 +15,7 @@ type NodeProps = MaybeSkeletonComponentProps & { }; const [tankDefinitions, averageDefinitions] = await Promise.all([ - awaitableTankDefinitions, + api.tankDefinitions(), awaitableAverageDefinitions, ]); @@ -58,7 +58,7 @@ export function Node({ id, highlight, nextIds, skeleton }: NodeProps) { mx={highlight ? "2" : "0"} style={{ background: highlight - ? `url(${asset(`flags/fade_small/${tank.nation}.webp`)})` + ? `url(${`/api/flags/fade/${tank.nation}.webp`})` : undefined, backgroundPosition: "center", backgroundSize: "cover", diff --git a/packages/website/src/components/Tankopedia/TechTreeSection/index.tsx b/packages/website/src/components/Tankopedia/TechTreeSection/index.tsx index 8c7a906f2..32bc47533 100644 --- a/packages/website/src/components/Tankopedia/TechTreeSection/index.tsx +++ b/packages/website/src/components/Tankopedia/TechTreeSection/index.tsx @@ -1,9 +1,9 @@ -import { asset, TankType } from "@blitzkit/core"; +import { TankType } from "@blitzkit/core"; import { literals } from "@blitzkit/i18n/src/literals"; import { CaretLeftIcon, CaretRightIcon, PlusIcon } from "@radix-ui/react-icons"; import { Flex, Heading, IconButton, ScrollArea, Text } from "@radix-ui/themes"; import { Fragment, useEffect, useMemo, useRef, useState } from "react"; -import { awaitableTankDefinitions } from "../../../core/awaitables/tankDefinitions"; +import { api } from "../../../core/blitzkit/api"; import { useLocale } from "../../../hooks/useLocale"; import { Duel } from "../../../stores/duel"; import { Tankopedia } from "../../../stores/tankopedia"; @@ -13,7 +13,7 @@ import { Node } from "./components/Node"; type Line = number[]; -const tankDefinitions = await awaitableTankDefinitions; +const tankDefinitions = await api.tankDefinitions(); export const XP_MULTIPLIERS = [1, 2, 3, 4, 5, 10]; @@ -105,7 +105,7 @@ export function TechTreeSection({ skeleton }: MaybeSkeletonComponentProps) { XP state[antagonist ? 'antagonist' : 'protagonist']!, + (state) => state[antagonist ? "antagonist" : "protagonist"]!, ); const preset = equipmentDefinitions.presets[member.tank.equipment_preset]; const equipmentMatrix = Duel.use( (state) => - state[antagonist ? 'antagonist' : 'protagonist']!.equipmentMatrix, + state[antagonist ? "antagonist" : "protagonist"]!.equipmentMatrix, ); const value = useMemo(() => { return preset.slots.some((slot, index) => { @@ -21,7 +21,7 @@ export function useEquipment(id: number, antagonist = false) { if (choice === 0) return false; - const equipped = slot[choice === -1 ? 'left' : 'right']; + const equipped = slot[choice === -1 ? "left" : "right"]; return equipped === id; }); diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/tanks/[slug]/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/tanks/[slug]/_index.tsx index 78ed0623c..4b24fe18e 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/tanks/[slug]/_index.tsx +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/tanks/[slug]/_index.tsx @@ -1,23 +1,22 @@ import { TankType } from "@blitzkit/core"; -import { PageWrapper } from "../../../../components/PageWrapper"; -import { CalloutsSection } from "../../../../components/Tankopedia/CalloutsSection"; -import { CharacteristicsSection } from "../../../../components/Tankopedia/CharacteristicsSection"; -import { GameModeSection } from "../../../../components/Tankopedia/GameModeSection"; -import { GuideSection } from "../../../../components/Tankopedia/GuideSection"; -import { HeroSection } from "../../../../components/Tankopedia/HeroSection"; -import { MetaSection } from "../../../../components/Tankopedia/MetaSection"; -import { TechTreeSection } from "../../../../components/Tankopedia/TechTreeSection"; -import { VideoSection } from "../../../../components/Tankopedia/VideoSection"; -import { awaitableModelDefinitions } from "../../../../core/awaitables/modelDefinitions"; -import { awaitableTankDefinitions } from "../../../../core/awaitables/tankDefinitions"; +import { PageWrapper } from "../../../../../components/PageWrapper"; +import { CalloutsSection } from "../../../../../components/Tankopedia/CalloutsSection"; +import { CharacteristicsSection } from "../../../../../components/Tankopedia/CharacteristicsSection"; +import { GameModeSection } from "../../../../../components/Tankopedia/GameModeSection"; +import { GuideSection } from "../../../../../components/Tankopedia/GuideSection"; +import { HeroSection } from "../../../../../components/Tankopedia/HeroSection"; +import { MetaSection } from "../../../../../components/Tankopedia/MetaSection"; +import { TechTreeSection } from "../../../../../components/Tankopedia/TechTreeSection"; +import { VideoSection } from "../../../../../components/Tankopedia/VideoSection"; +import { api } from "../../../../../core/blitzkit/api"; import { LocaleProvider, type LocaleAcceptorProps, -} from "../../../../hooks/useLocale"; -import { Duel } from "../../../../stores/duel"; -import { Tankopedia } from "../../../../stores/tankopedia"; -import type { MaybeSkeletonComponentProps } from "../../../../types/maybeSkeletonComponentProps"; -import type { TankGuide } from "../../../../types/tankGuide"; +} from "../../../../../hooks/useLocale"; +import { Duel } from "../../../../../stores/duel"; +import { Tankopedia } from "../../../../../stores/tankopedia"; +import type { MaybeSkeletonComponentProps } from "../../../../../types/maybeSkeletonComponentProps"; +import type { TankGuide } from "../../../../../types/tankGuide"; type PageProps = MaybeSkeletonComponentProps & LocaleAcceptorProps & { @@ -26,8 +25,8 @@ type PageProps = MaybeSkeletonComponentProps & }; const [tankDefinitions, modelDefinitions] = await Promise.all([ - awaitableTankDefinitions, - awaitableModelDefinitions, + api.tankDefinitions(), + api.modelDefinitions(), ]); export function Page({ id, skeleton, locale, guide }: PageProps) { diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/tanks/[slug]/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/tanks/[slug]/index.astro index 4933da71d..9f6193fb3 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/tanks/[slug]/index.astro +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/tanks/[slug]/index.astro @@ -2,30 +2,25 @@ import { AstroSeo } from "@astrolib/seo"; import { assertSecret, - asset, createDefaultProvisions, createDefaultSkills, - fetchEquipmentDefinitions, - fetchModelDefinitions, - fetchProvisionDefinitions, - fetchSkillDefinitions, - fetchTankDefinitions, TIER_ROMAN_NUMERALS, } from "@blitzkit/core"; import { unwrapper } from "@blitzkit/i18n"; import { literals } from "@blitzkit/i18n/src/literals"; import type { GetStaticPaths } from "astro"; import { load } from "cheerio"; -import { tankCharacteristics } from "../../../../core/blitzkit/tankCharacteristics"; -import { getStrings } from "../../../../core/i18n/getStrings"; -import BaseLayout from "../../../../layouts/BaseLayout.astro"; -import { genericDefaultEquipmentMatrix } from "../../../../stores/duel/constants"; -import type { TankGuide } from "../../../../types/tankGuide"; +import { api } from "../../../../../core/blitzkit/api"; +import { tankCharacteristics } from "../../../../../core/blitzkit/tankCharacteristics"; +import { getStrings } from "../../../../../core/i18n/getStrings"; +import BaseLayout from "../../../../../layouts/BaseLayout.astro"; +import { genericDefaultEquipmentMatrix } from "../../../../../stores/duel/constants"; +import type { TankGuide } from "../../../../../types/tankGuide"; import { getStaticPaths as parentGetStaticPaths } from "../index.astro"; import { Page } from "./_index"; export const getStaticPaths = (async () => { - const tankDefinitions = await fetchTankDefinitions(); + const tankDefinitions = await api.tankDefinitions(); const parentPaths = parentGetStaticPaths(); return Object.values(tankDefinitions.tanks) @@ -40,11 +35,11 @@ export const getStaticPaths = (async () => { const { id, locale } = Astro.props; const strings = getStrings(locale); -const tankDefinitions = await fetchTankDefinitions(); -const skillDefinitions = await fetchSkillDefinitions(); -const provisionDefinitions = await fetchProvisionDefinitions(); -const equipmentDefinitions = await fetchEquipmentDefinitions(); -const tankModelDefinitions = await fetchModelDefinitions(); +const tankDefinitions = await api.tankDefinitions(); +const skillDefinitions = await api.skillDefinitions(); +const provisionDefinitions = await api.provisionDefinitions(); +const equipmentDefinitions = await api.equipmentDefinitions(); +const tankModelDefinitions = await api.modelDefinitions(); const tank = tankDefinitions.tanks[id]; const tankModelDefinition = tankModelDefinitions.models[tank.id]; @@ -61,7 +56,7 @@ const title = literals(strings.website.tools.tankopedia.tank_seo_title, { }); const url = import.meta.env.SITE + Astro.url.pathname; -const icon = asset(`icons/tanks/big/${id}.webp`); +const icon = `/api/tanks/${id}/icons/big.webp`; const turret = tank.turrets.at(-1)!; const gun = turret.guns.at(-1)!; const shell = gun.shells[0]; diff --git a/packages/website/src/stores/duel/index.ts b/packages/website/src/stores/duel/index.ts index b8a904161..fef1c2463 100644 --- a/packages/website/src/stores/duel/index.ts +++ b/packages/website/src/stores/duel/index.ts @@ -8,7 +8,7 @@ import { type TurretDefinition, } from "@blitzkit/core"; import { Varuna } from "varuna"; -import { awaitableProvisionDefinitions } from "../../core/awaitables/provisionDefinitions"; +import { api } from "../../core/blitzkit/api"; import { tankToDuelMember } from "../../core/blitzkit/tankToDuelMember"; type EquipmentMatrixItem = -1 | 0 | 1; @@ -44,7 +44,7 @@ export interface DuelStore { equalize: boolean; } -const provisionDefinitions = await awaitableProvisionDefinitions; +const provisionDefinitions = await api.provisionDefinitions(); export const Duel = new Varuna< DuelStore, diff --git a/packages/website/src/stores/tankopedia.ts b/packages/website/src/stores/tankopedia.ts index d5f54b26c..d2eb8a066 100644 --- a/packages/website/src/stores/tankopedia.ts +++ b/packages/website/src/stores/tankopedia.ts @@ -8,7 +8,7 @@ import { Varuna } from "varuna"; import type { ArmorType } from "../components/Armor/components/SpacedArmorScene"; import type { ExternalModuleVariant } from "../components/Armor/components/SpacedArmorSceneComponent"; import type { XP_MULTIPLIERS } from "../components/Tankopedia/TechTreeSection"; -import { awaitableSkillDefinitions } from "../core/awaitables/skillDefinitions"; +import { api } from "../core/blitzkit/api"; import { TankopediaDisplay } from "./tankopediaPersistent/constants"; export interface ShotLayerBase { @@ -105,7 +105,7 @@ interface Tankopedia { statSearch?: string; } -const skillDefinitions = await awaitableSkillDefinitions; +const skillDefinitions = await api.skillDefinitions(); export const Tankopedia = new Varuna((model) => ({ disturbed: false, From 48c4a4c76df60b1c244c4b0971488892e5268400 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Tue, 4 Aug 2026 14:12:32 -0500 Subject: [PATCH 037/147] currencies --- .../scripts/src/buildAssets/currencies.ts | 62 -------------- .../icons/currencies/[id].webp.ts | 80 +++++++++++++++++++ 2 files changed, 80 insertions(+), 62 deletions(-) delete mode 100644 packages/scripts/src/buildAssets/currencies.ts create mode 100644 packages/website/src/pages/[...chunk_api]/icons/currencies/[id].webp.ts diff --git a/packages/scripts/src/buildAssets/currencies.ts b/packages/scripts/src/buildAssets/currencies.ts deleted file mode 100644 index 78d10ede1..000000000 --- a/packages/scripts/src/buildAssets/currencies.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { assertSecret, BlitzGlossary } from "@blitzkit/core"; -import sharp from "sharp"; -import { AssetUploader } from "../core/github/assetUploader"; -import { vfs } from "./constants"; - -const ICONS = [ - ["currency_silver_m.packed.webp", "silver"], - ["currency_premium_xl.packed.webp", "premium"], - ["currency_gold_m.packed.webp", "gold"], - ["currency_free-xp_xl.packed.webp", "free-xp"], - ["currency_elite-xp_xl.packed.webp", "elite-xp"], - ["currency_crew-xp_xl.packed.webp", "crew-xp"], - ["currency_battle-xp_xl.packed.webp", "xp"], -]; - -export async function currencies() { - console.log("Building currency icons..."); - - using uploader = new AssetUploader("currency icons"); - - for (const [file, name] of ICONS) { - const content = await sharp( - await vfs.file(`Data/Gfx/Lobby/currency/${file}`), - ) - .trim() - .toBuffer(); - - await uploader.add({ - content, - path: `icons/currencies/${name}.webp`, - }); - } - - const glossary = await fetch(assertSecret(import.meta.env.WOTB_GLOSSARY)) - .then((response) => response.json() as Promise) - .then((glossary) => - Object.values(glossary) - .entries() - .filter(([key]) => /^prx_season_\d+$/.test(`${key}`)), - ); - - for (const [key, item] of glossary) { - if (item.image_url === null) throw new Error(`No image_url for ${key}`); - - const imageRaw = await fetch(item.image_url).then((response) => - response.arrayBuffer(), - ); - const content = await sharp(imageRaw) - .trim({ - threshold: 100, - background: { r: 0, g: 0, b: 0, alpha: 0 }, - }) - .toBuffer(); - - await uploader.add({ - content, - path: `icons/currencies/${key}.webp`, - }); - } - - await uploader.flush(); -} diff --git a/packages/website/src/pages/[...chunk_api]/icons/currencies/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/icons/currencies/[id].webp.ts new file mode 100644 index 000000000..f6581c798 --- /dev/null +++ b/packages/website/src/pages/[...chunk_api]/icons/currencies/[id].webp.ts @@ -0,0 +1,80 @@ +import { assertSecret, type BlitzGlossary } from "@blitzkit/core"; +import type { APIContext, GetStaticPathsItem } from "astro"; +import sharp from "sharp"; +import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; +import { vfs } from "../../../../core/blitzkit/vfs"; +import { getStaticPaths as _getStaticPaths } from "../../_index"; + +const ICONS = [ + ["currency_silver_m.packed.webp", "silver"], + ["currency_premium_xl.packed.webp", "premium"], + ["currency_gold_m.packed.webp", "gold"], + ["currency_free-xp_xl.packed.webp", "free-xp"], + ["currency_elite-xp_xl.packed.webp", "elite-xp"], + ["currency_crew-xp_xl.packed.webp", "crew-xp"], + ["currency_battle-xp_xl.packed.webp", "xp"], +]; + +export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { + const paths: GetStaticPathsItem[] = []; + + console.log("Building currency icons..."); + + for (const [file, id] of ICONS) { + paths.push({ + params: { id }, + props: { + local: true, + source: `Data/Gfx/Lobby/currency/${file}`, + }, + }); + } + + const glossary = await fetch(assertSecret(import.meta.env.WOTB_GLOSSARY)) + .then((response) => response.json() as Promise) + .then((glossary) => + Object.values(glossary) + .entries() + .filter(([key]) => /^prx_season_\d+$/.test(`${key}`)), + ); + + for (const [id, item] of glossary) { + if (!item.image_url) continue; + + paths.push({ + params: { id }, + props: { + local: false, + source: item.image_url, + }, + }); + } + + return paths; +}); + +export async function GET({ + props, +}: APIContext<{ local: boolean; source: string }>) { + let buffer: Buffer; + + if (props.local) { + buffer = await sharp(await vfs.file(props.source)) + .trim() + .toBuffer(); + } else { + const imageRaw = await fetch(props.source).then((response) => + response.arrayBuffer(), + ); + buffer = await sharp(imageRaw) + .trim({ + threshold: 100, + background: { r: 0, g: 0, b: 0, alpha: 0 }, + }) + .toBuffer(); + } + + const bytes = new Uint8Array(buffer); + + return new Response(bytes); +} From 487d149587696cd83ccee7894a94924bffead64a Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Tue, 4 Aug 2026 14:17:12 -0500 Subject: [PATCH 038/147] shell icons --- packages/scripts/src/buildAssets/constants.ts | 14 ++--- .../scripts/src/buildAssets/shellIcons.ts | 42 -------------- .../[...chunk_api]/icons/shells/[id].webp.ts | 55 +++++++++++++++++++ 3 files changed, 61 insertions(+), 50 deletions(-) delete mode 100644 packages/scripts/src/buildAssets/shellIcons.ts create mode 100644 packages/website/src/pages/[...chunk_api]/icons/shells/[id].webp.ts diff --git a/packages/scripts/src/buildAssets/constants.ts b/packages/scripts/src/buildAssets/constants.ts index 1cc136550..3e5cf4720 100644 --- a/packages/scripts/src/buildAssets/constants.ts +++ b/packages/scripts/src/buildAssets/constants.ts @@ -1,11 +1,9 @@ -import { - AbstractVFS, - assertSecret, - LocalVFS, - MixedVFS, - SteamVFS, - ZipVFS, -} from "@blitzkit/core"; +import { assertSecret } from "@blitzkit/core"; +import { AbstractVFS } from "@blitzkit/core/src/blitzkit/vfs/abstract"; +import { LocalVFS } from "@blitzkit/core/src/blitzkit/vfs/local"; +import { MixedVFS } from "@blitzkit/core/src/blitzkit/vfs/mixed"; +import { SteamVFS } from "@blitzkit/core/src/blitzkit/vfs/steam"; +import { ZipVFS } from "@blitzkit/core/src/blitzkit/vfs/zip"; export const PATCHES_ROOT = "../../temp/patches"; diff --git a/packages/scripts/src/buildAssets/shellIcons.ts b/packages/scripts/src/buildAssets/shellIcons.ts deleted file mode 100644 index fabe047e1..000000000 --- a/packages/scripts/src/buildAssets/shellIcons.ts +++ /dev/null @@ -1,42 +0,0 @@ -import sharp from "sharp"; -import { AssetUploader } from "../core/github/assetUploader"; -import { vfs } from "./constants"; - -export async function shellIcons() { - console.log("Building shell icons..."); - - using uploader = new AssetUploader("shell icons"); - const image = sharp( - await vfs.file( - `Data/Gfx/Shared/tank-supply/ammunition/big/texture0.packed.webp`, - ), - ); - const files = await vfs - .dir(`Data/Gfx/Shared/tank-supply/ammunition/big`) - .then((files) => files.filter((file) => file.endsWith("_l.txt"))); - - for (const file of files) { - const name = file.match(/(.+)_l\.txt/)![1]; - const sizes = ( - await vfs.text(`Data/Gfx/Shared/tank-supply/ammunition/big/${file}`) - ) - .split("\n")[4] - .split(" ") - .map(Number); - - await uploader.add({ - content: await image - .clone() - .extract({ - left: sizes[0], - top: sizes[1], - width: sizes[2], - height: sizes[3], - }) - .toBuffer(), - path: `icons/shells/${name}.webp`, - }); - } - - await uploader.flush(); -} diff --git a/packages/website/src/pages/[...chunk_api]/icons/shells/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/icons/shells/[id].webp.ts new file mode 100644 index 000000000..ad614b1a3 --- /dev/null +++ b/packages/website/src/pages/[...chunk_api]/icons/shells/[id].webp.ts @@ -0,0 +1,55 @@ +import type { APIContext, GetStaticPathsItem } from "astro"; +import sharp from "sharp"; +import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; +import { vfs } from "../../../../core/blitzkit/vfs"; +import { getStaticPaths as _getStaticPaths } from "../../_index"; + +export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { + const paths: GetStaticPathsItem[] = []; + + const image = sharp( + await vfs.file( + `Data/Gfx/Shared/tank-supply/ammunition/big/texture0.packed.webp`, + ), + ); + const files = await vfs + .dir(`Data/Gfx/Shared/tank-supply/ammunition/big`) + .then((files) => files.filter((file) => file.endsWith("_l.txt"))); + + for (const file of files) { + const id = file.match(/(.+)_l\.txt/)![1]; + const sizes = ( + await vfs.text(`Data/Gfx/Shared/tank-supply/ammunition/big/${file}`) + ) + .split("\n")[4] + .split(" ") + .map(Number); + + paths.push({ + params: { id }, + props: { sizes }, + }); + } + + return paths; +}); + +export async function GET({ props }: APIContext<{ sizes: number[] }>) { + const image = sharp( + await vfs.file( + `Data/Gfx/Shared/tank-supply/ammunition/big/texture0.packed.webp`, + ), + ); + const buffer = await image + .clone() + .extract({ + left: props.sizes[0], + top: props.sizes[1], + width: props.sizes[2], + height: props.sizes[3], + }) + .toBuffer(); + const bytes = new Uint8Array(buffer); + + return new Response(bytes); +} From e5e747f7a01df1e876818c8c3bb1d360e4c0b5f8 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Tue, 4 Aug 2026 14:28:34 -0500 Subject: [PATCH 039/147] equipment icons --- .../scripts/src/buildAssets/equipmentIcons.ts | 7 -- packages/website/src/hooks/useModel.ts | 3 +- .../icons/equipment/[id].webp.ts | 99 +++++++++++++++++++ 3 files changed, 100 insertions(+), 9 deletions(-) create mode 100644 packages/website/src/pages/[...chunk_api]/icons/equipment/[id].webp.ts diff --git a/packages/scripts/src/buildAssets/equipmentIcons.ts b/packages/scripts/src/buildAssets/equipmentIcons.ts index aebe2c847..280d9ec87 100644 --- a/packages/scripts/src/buildAssets/equipmentIcons.ts +++ b/packages/scripts/src/buildAssets/equipmentIcons.ts @@ -3,14 +3,7 @@ import { AssetUploader } from "../core/github/assetUploader"; import { vfs } from "./constants"; import { OptionalDevices } from "./definitions"; -interface Mappings { - Header: { version: number }; - StyleSheets: { - selectors: string[]; - properties: { "bg-sprite": string }; - }[]; -} export async function equipmentIcons() { console.log("Building equipment icons..."); diff --git a/packages/website/src/hooks/useModel.ts b/packages/website/src/hooks/useModel.ts index c8d532bd5..9b9e78dd3 100644 --- a/packages/website/src/hooks/useModel.ts +++ b/packages/website/src/hooks/useModel.ts @@ -1,11 +1,10 @@ -import { asset } from "@blitzkit/core"; import { useLoader } from "@react-three/fiber"; import { Mesh, MeshStandardMaterial } from "three"; import { GLTFLoader } from "three-stdlib"; import { useDispose } from "./useDispose"; export function useModel(id: number) { - const path = asset(`3d/tanks/models/${id}.glb`); + const path = `/api/tanks/${id}/model.glb`; const gltf = useLoader(GLTFLoader, path); useDispose(gltf, path); diff --git a/packages/website/src/pages/[...chunk_api]/icons/equipment/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/icons/equipment/[id].webp.ts new file mode 100644 index 000000000..f47fc889b --- /dev/null +++ b/packages/website/src/pages/[...chunk_api]/icons/equipment/[id].webp.ts @@ -0,0 +1,99 @@ +import type { OptionalDevices } from "@blitzkit/core"; +import type { APIContext, GetStaticPathsItem } from "astro"; +import sharp from "sharp"; +import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; +import { vfs } from "../../../../core/blitzkit/vfs"; +import { getStaticPaths as _getStaticPaths } from "../../_index"; + +interface Mappings { + Header: { version: number }; + + StyleSheets: { + selectors: string[]; + properties: { "bg-sprite": string }; + }[]; +} + +export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { + const paths: GetStaticPathsItem[] = []; + + console.log("Building equipment icons..."); + + const optionalDevices = await vfs.xml<{ root: OptionalDevices }>( + `Data/XML/item_defs/vehicles/common/optional_devices.xml`, + ); + const mappings = await vfs.yaml( + `Data/UI/Screens3/Lobby/Inventory/OptionalDevices/OptionalDevicesItemImage.style.yaml`, + ); + + for (const optionalDeviceKey in optionalDevices.root) { + const optionalDevice = optionalDevices.root[optionalDeviceKey]; + + if (optionalDeviceKey === "nextAvailableId") continue; + + const mapping = mappings.StyleSheets.find((mapping) => + mapping.selectors.includes( + `.optional_device_item.${optionalDevice.icon} #Img`, + ), + ); + + if (!mapping) { + console.warn(`No mapping found for ${optionalDevice.icon}; skipping...`); + continue; + } + + const configPathRaw = mapping.properties["bg-sprite"]; + const configPath = configPathRaw.replace("~res:/", ""); + + if (configPath.startsWith("Gfx/Lobby")) { + const configPathWebp = configPath.replace(".txt", ""); + + paths.push({ + params: { id: optionalDevice.id }, + props: { + sizes: undefined, + source: `Data/${configPathWebp}.packed.webp`, + }, + }); + } else { + const config = await vfs.text(`Data/${configPath}`); + const sizes = config.split("\n")[4].split(" ").map(Number); + + paths.push({ + params: { id: optionalDevice.id }, + props: { + sizes, + source: `Data/Gfx/UI/InventoryIcons/Big/OptionalDevices/texture0.packed.webp`, + }, + }); + } + } + + console.log(paths); + + return paths; +}); + +export async function GET({ + props, +}: APIContext<{ sizes?: number[]; source: string }>) { + const image = sharp(await vfs.file(props.source)); + let buffer: Buffer; + + if (props.sizes) { + buffer = await image + .extract({ + left: props.sizes[0], + top: props.sizes[1], + width: props.sizes[2], + height: props.sizes[3], + }) + .toBuffer(); + } else { + buffer = await image.trim().toBuffer(); + } + + const bytes = new Uint8Array(buffer); + + return new Response(bytes); +} From aaae8fb8ff396058f22cc301df4c5f74cd08a0d1 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Tue, 4 Aug 2026 19:40:57 -0500 Subject: [PATCH 040/147] module icons --- .../scripts/src/buildAssets/moduleIcons.ts | 34 ---------------- .../[...chunk_api]/icons/modules/[id].webp.ts | 40 +++++++++++++++++++ 2 files changed, 40 insertions(+), 34 deletions(-) delete mode 100644 packages/scripts/src/buildAssets/moduleIcons.ts create mode 100644 packages/website/src/pages/[...chunk_api]/icons/modules/[id].webp.ts diff --git a/packages/scripts/src/buildAssets/moduleIcons.ts b/packages/scripts/src/buildAssets/moduleIcons.ts deleted file mode 100644 index 01b9790b1..000000000 --- a/packages/scripts/src/buildAssets/moduleIcons.ts +++ /dev/null @@ -1,34 +0,0 @@ -import sharp from "sharp"; -import { AssetUploader } from "../core/github/assetUploader"; -import { vfs } from "./constants"; - -export async function moduleIcons() { - console.log("Building module icons..."); - - using uploader = new AssetUploader("module icons"); - const files = await vfs - .dir(`Data/Gfx/UI/ModulesTechTree`) - .then((files) => - files.filter( - (file) => - !file.endsWith("@2x.packed.webp") && file.startsWith("vehicle"), - ), - ); - - for (const file of files) { - const nameRaw = file.match(/vehicle(.+)\.packed\.webp/)![1]; - const name = nameRaw[0].toLowerCase() + nameRaw.slice(1); - const content = await sharp( - await vfs.file(`Data/Gfx/UI/ModulesTechTree/${file}`), - ) - .trim() - .toBuffer(); - - await uploader.add({ - content, - path: `icons/modules/${name}.webp`, - }); - } - - await uploader.flush(); -} diff --git a/packages/website/src/pages/[...chunk_api]/icons/modules/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/icons/modules/[id].webp.ts new file mode 100644 index 000000000..9237a57a2 --- /dev/null +++ b/packages/website/src/pages/[...chunk_api]/icons/modules/[id].webp.ts @@ -0,0 +1,40 @@ +import type { APIContext, GetStaticPathsItem } from "astro"; +import sharp from "sharp"; +import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; +import { vfs } from "../../../../core/blitzkit/vfs"; +import { getStaticPaths as _getStaticPaths } from "../../_index"; + +export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { + const paths: GetStaticPathsItem[] = []; + + const files = await vfs + .dir(`Data/Gfx/UI/ModulesTechTree`) + .then((files) => + files.filter( + (file) => + !file.endsWith("@2x.packed.webp") && file.startsWith("vehicle"), + ), + ); + + for (const file of files) { + const nameRaw = file.match(/vehicle(.+)\.packed\.webp/)![1]; + const id = nameRaw[0].toLowerCase() + nameRaw.slice(1); + const path = `Data/Gfx/UI/ModulesTechTree/${file}`; + + paths.push({ + params: { id }, + props: { path }, + }); + } + + return paths; +}); + +export async function GET({ props }: APIContext<{ path: string }>) { + const buffer = await sharp(await vfs.file(props.path)) + .trim() + .toBuffer(); + const bytes = new Uint8Array(buffer); + + return new Response(bytes); +} From 36dbb2457235c526edbb793e0c69e4167ca1fa9a Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Tue, 4 Aug 2026 19:42:06 -0500 Subject: [PATCH 041/147] skill icons --- .../scripts/src/buildAssets/skillIcons.ts | 25 ------------- .../[...chunk_api]/icons/skills/[id].webp.ts | 37 +++++++++++++++++++ 2 files changed, 37 insertions(+), 25 deletions(-) delete mode 100644 packages/scripts/src/buildAssets/skillIcons.ts create mode 100644 packages/website/src/pages/[...chunk_api]/icons/skills/[id].webp.ts diff --git a/packages/scripts/src/buildAssets/skillIcons.ts b/packages/scripts/src/buildAssets/skillIcons.ts deleted file mode 100644 index 02c27a2a3..000000000 --- a/packages/scripts/src/buildAssets/skillIcons.ts +++ /dev/null @@ -1,25 +0,0 @@ -import sharp from "sharp"; -import { AssetUploader } from "../core/github/assetUploader"; -import { vfs } from "./constants"; - -export async function skillIcons() { - console.log("Building skill icons..."); - - const avatar = await vfs.xml<{ root: TankmenAvatar }>( - `Data/XML/item_defs/tankmen/avatar.xml`, - ); - const uploader = new AssetUploader("skill icons"); - - for (const key in avatar.root.skills) { - const skill = avatar.root.skills[key]; - const icon = Array.isArray(skill.icon) ? skill.icon[0] : skill.icon; - const name = icon.name.split("/").at(-1)!.replace(/_\d$/, ""); - const path = `Data${icon.name.replace("~res:", "")}.packed.webp`; - const image = sharp(await vfs.file(path)).trim(); - const content = await image.toBuffer(); - - await uploader.add({ content, path: `icons/skills/${name}.webp` }); - } - - await uploader.flush(); -} diff --git a/packages/website/src/pages/[...chunk_api]/icons/skills/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/icons/skills/[id].webp.ts new file mode 100644 index 000000000..37fbd1e87 --- /dev/null +++ b/packages/website/src/pages/[...chunk_api]/icons/skills/[id].webp.ts @@ -0,0 +1,37 @@ +import type { TankmenAvatar } from "@blitzkit/core"; +import type { APIContext, GetStaticPathsItem } from "astro"; +import sharp from "sharp"; +import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; +import { vfs } from "../../../../core/blitzkit/vfs"; +import { getStaticPaths as _getStaticPaths } from "../../_index"; + +export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { + const paths: GetStaticPathsItem[] = []; + + const avatar = await vfs.xml<{ root: TankmenAvatar }>( + `Data/XML/item_defs/tankmen/avatar.xml`, + ); + + for (const key in avatar.root.skills) { + const skill = avatar.root.skills[key]; + const icon = Array.isArray(skill.icon) ? skill.icon[0] : skill.icon; + const id = icon.name.split("/").at(-1)!.replace(/_\d$/, ""); + const path = `Data${icon.name.replace("~res:", "")}.packed.webp`; + + paths.push({ + params: { id }, + props: { path }, + }); + } + + return paths; +}); + +export async function GET({ props }: APIContext<{ path: string }>) { + const buffer = await sharp(await vfs.file(props.path)) + .trim() + .toBuffer(); + const bytes = new Uint8Array(buffer); + + return new Response(bytes); +} From 996c9c05df3a0a38a539a7735ad02a5e962bf3e4 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Tue, 4 Aug 2026 19:45:20 -0500 Subject: [PATCH 042/147] game mode banners --- cspell.json | 3 +- .../components/Equalizer.tsx | 2 +- .../components/Tankopedia/GameModeSection.tsx | 4 +-- .../gamemodes/[id]/banner.webp.ts} | 34 +++++++++++-------- 4 files changed, 25 insertions(+), 18 deletions(-) rename packages/{scripts/src/buildAssets/gameModeBanners.ts => website/src/pages/[...chunk_api]/gamemodes/[id]/banner.webp.ts} (57%) diff --git a/cspell.json b/cspell.json index 4ec147b19..a67dd4960 100644 --- a/cspell.json +++ b/cspell.json @@ -1,5 +1,6 @@ { "words": [ + "gamemodes", "Tankopedia" ] -} \ No newline at end of file +} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Equalizer.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Equalizer.tsx index 6940f664b..9b9714ac8 100644 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Equalizer.tsx +++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Equalizer.tsx @@ -33,7 +33,7 @@ export function Equalizer() { style={{ cursor: "pointer", borderRadius: "var(--radius-3)", - backgroundImage: "url(/api/icons/game_mode_banners/45.webp)", + backgroundImage: "url(/api/gamemodes/45/banner.webp)", backgroundSize: "cover", backgroundPosition: "center", overflow: "hidden", diff --git a/packages/website/src/components/Tankopedia/GameModeSection.tsx b/packages/website/src/components/Tankopedia/GameModeSection.tsx index 3aadfb03e..86ae4629c 100644 --- a/packages/website/src/components/Tankopedia/GameModeSection.tsx +++ b/packages/website/src/components/Tankopedia/GameModeSection.tsx @@ -37,7 +37,7 @@ export function GameModeSection() { direction="column" style={{ borderRadius: "var(--radius-3)", - background: `url(/api/icons/game_mode_banners/${id}.webp)`, + background: `url(/api/gamemodes/${id}/banner.webp)`, backgroundSize: "cover", backgroundPosition: "center", }} @@ -45,7 +45,7 @@ export function GameModeSection() { { + const paths: GetStaticPathsItem[] = []; - using uploader = new AssetUploader("game mode banners"); const gameTypeSelectorStyles = await vfs.yaml( `Data/UI/Screens/Lobby/Hangar/GameTypeSelector.yaml`, ); @@ -34,15 +35,20 @@ export async function gameModeBanners() { path = `/Gfx/UI/Hangar/GameTypes/battle-type_${name.toLowerCase()}`; } - const content = await sharp(await vfs.file(`Data${path}.packed.webp`)) - .trim({ background: { r: 0, g: 0, b: 0, alpha: 0 } }) - .toBuffer(); - - await uploader.add({ - path: `icons/game_mode_banners/${id}.webp`, - content, + paths.push({ + params: { id }, + props: { path: `Data${path}.packed.webp` }, }); } - await uploader.flush(); + return paths; +}); + +export async function GET({ props }: APIContext<{ path: string }>) { + const buffer = await sharp(await vfs.file(props.path)) + .trim({ background: { r: 0, g: 0, b: 0, alpha: 0 } }) + .toBuffer(); + const bytes = new Uint8Array(buffer); + + return new Response(bytes); } From 129a2c52f9aac4de434e39fcb90b9eb9802a508f Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Tue, 4 Aug 2026 19:49:53 -0500 Subject: [PATCH 043/147] camo icon --- packages/core/src/blitzkit/vfs/abstract.ts | 2 +- .../scripts/src/buildAssets/camouflageIcons.ts | 14 -------------- .../src/pages/[...chunk_api]/icons/camo.webp.ts | 8 ++++++++ 3 files changed, 9 insertions(+), 15 deletions(-) delete mode 100644 packages/scripts/src/buildAssets/camouflageIcons.ts create mode 100644 packages/website/src/pages/[...chunk_api]/icons/camo.webp.ts diff --git a/packages/core/src/blitzkit/vfs/abstract.ts b/packages/core/src/blitzkit/vfs/abstract.ts index 9cae1309b..e4f9a0cb3 100644 --- a/packages/core/src/blitzkit/vfs/abstract.ts +++ b/packages/core/src/blitzkit/vfs/abstract.ts @@ -14,7 +14,7 @@ export abstract class AbstractVFS { abstract has(path: string): Promise; - abstract raw(path: string): Promise; + abstract raw(path: string): Promise>; async resolve(path: string) { const normalized = normalize(path); diff --git a/packages/scripts/src/buildAssets/camouflageIcons.ts b/packages/scripts/src/buildAssets/camouflageIcons.ts deleted file mode 100644 index 897165169..000000000 --- a/packages/scripts/src/buildAssets/camouflageIcons.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { AssetUploader } from "../core/github/assetUploader"; -import { vfs } from "./constants"; - -export async function camouflageIcons() { - console.log("Building camouflage icons..."); - - using uploader = new AssetUploader("camo icons"); - const content = await vfs.file( - `Data/Gfx/UI/Hangar/IconCamouflage.packed.webp`, - ); - - await uploader.add({ content, path: "icons/camo.webp" }); - await uploader.flush(); -} diff --git a/packages/website/src/pages/[...chunk_api]/icons/camo.webp.ts b/packages/website/src/pages/[...chunk_api]/icons/camo.webp.ts new file mode 100644 index 000000000..acc583993 --- /dev/null +++ b/packages/website/src/pages/[...chunk_api]/icons/camo.webp.ts @@ -0,0 +1,8 @@ +import { vfs } from "../../../core/blitzkit/vfs"; + +export { getStaticPaths } from "../_index"; + +export async function GET() { + const bytes = await vfs.file("Data/Gfx/UI/Hangar/IconCamouflage.packed.webp"); + return new Response(bytes); +} From be0804ae59d9db1f62547e17465aa52fb07ed564 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Tue, 4 Aug 2026 19:52:06 -0500 Subject: [PATCH 044/147] booster icons --- .../scripts/src/buildAssets/boosterIcons.ts | 46 ---------------- .../icons/boosters/[id].webp.ts | 53 +++++++++++++++++++ .../[...chunk_api]/icons/shells/[id].webp.ts | 5 -- 3 files changed, 53 insertions(+), 51 deletions(-) delete mode 100644 packages/scripts/src/buildAssets/boosterIcons.ts create mode 100644 packages/website/src/pages/[...chunk_api]/icons/boosters/[id].webp.ts diff --git a/packages/scripts/src/buildAssets/boosterIcons.ts b/packages/scripts/src/buildAssets/boosterIcons.ts deleted file mode 100644 index e2cda995c..000000000 --- a/packages/scripts/src/buildAssets/boosterIcons.ts +++ /dev/null @@ -1,46 +0,0 @@ -import sharp from "sharp"; -import { AssetUploader } from "../core/github/assetUploader"; -import { vfs } from "./constants"; - -export async function boosterIcons() { - console.log("Building booster icons..."); - - const uploader = new AssetUploader("booster icons"); - const boosterFiles = await vfs - .dir("Data/Gfx/Shared/boosters") - .then((files) => - files.filter( - (file) => !file.includes("@2x.txt") && !file.startsWith("texture0"), - ), - ); - const image = sharp( - await vfs.file(`Data/Gfx/Shared/boosters/texture0.packed.webp`), - ); - - for (const file of boosterFiles) { - const name = file.match(/booster_(.+).txt/)?.[1]; - - if (name === undefined) continue; - - const sizes = (await vfs.text(`Data/Gfx/Shared/boosters/${file}`)) - .split("\n")[4] - .split(" ") - .map(Number); - const content = await image - .clone() - .extract({ - left: sizes[0], - top: sizes[1], - width: sizes[2], - height: sizes[3], - }) - .toBuffer(); - - await uploader.add({ - path: `icons/boosters/${name}.webp`, - content, - }); - } - - await uploader.flush(); -} diff --git a/packages/website/src/pages/[...chunk_api]/icons/boosters/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/icons/boosters/[id].webp.ts new file mode 100644 index 000000000..2e3d176bb --- /dev/null +++ b/packages/website/src/pages/[...chunk_api]/icons/boosters/[id].webp.ts @@ -0,0 +1,53 @@ +import type { APIContext, GetStaticPathsItem } from "astro"; +import sharp from "sharp"; +import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; +import { vfs } from "../../../../core/blitzkit/vfs"; +import { getStaticPaths as _getStaticPaths } from "../../_index"; + +export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { + const paths: GetStaticPathsItem[] = []; + + const boosterFiles = await vfs + .dir("Data/Gfx/Shared/boosters") + .then((files) => + files.filter( + (file) => !file.includes("@2x.txt") && !file.startsWith("texture0"), + ), + ); + + for (const file of boosterFiles) { + const id = file.match(/booster_(.+).txt/)?.[1]; + + if (id === undefined) continue; + + const sizes = (await vfs.text(`Data/Gfx/Shared/boosters/${file}`)) + .split("\n")[4] + .split(" ") + .map(Number); + + paths.push({ + params: { id }, + props: { sizes }, + }); + } + + return paths; +}); + +export async function GET({ props }: APIContext<{ sizes: number[] }>) { + const image = sharp( + await vfs.file(`Data/Gfx/Shared/boosters/texture0.packed.webp`), + ); + const buffer = await image + .clone() + .extract({ + left: props.sizes[0], + top: props.sizes[1], + width: props.sizes[2], + height: props.sizes[3], + }) + .toBuffer(); + const bytes = new Uint8Array(buffer); + + return new Response(bytes); +} diff --git a/packages/website/src/pages/[...chunk_api]/icons/shells/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/icons/shells/[id].webp.ts index ad614b1a3..f8c520d04 100644 --- a/packages/website/src/pages/[...chunk_api]/icons/shells/[id].webp.ts +++ b/packages/website/src/pages/[...chunk_api]/icons/shells/[id].webp.ts @@ -7,11 +7,6 @@ import { getStaticPaths as _getStaticPaths } from "../../_index"; export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { const paths: GetStaticPathsItem[] = []; - const image = sharp( - await vfs.file( - `Data/Gfx/Shared/tank-supply/ammunition/big/texture0.packed.webp`, - ), - ); const files = await vfs .dir(`Data/Gfx/Shared/tank-supply/ammunition/big`) .then((files) => files.filter((file) => file.endsWith("_l.txt"))); From 9968b2c83dfd9736b1c76bc3751d0d324f05cfa9 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Tue, 4 Aug 2026 19:54:08 -0500 Subject: [PATCH 045/147] small tank icons --- .../components/ModuleButtons/ModuleButton.tsx | 13 +++--- .../website/src/components/SmallTankIcon.tsx | 8 ++-- .../tanks/[id]/icons/big.webp.ts | 20 +++------ .../tanks/[id]/icons/small.webp.ts | 43 +++++++++++++++++++ 4 files changed, 58 insertions(+), 26 deletions(-) create mode 100644 packages/website/src/pages/[...chunk_api]/tanks/[id]/icons/small.webp.ts diff --git a/packages/website/src/components/ModuleButtons/ModuleButton.tsx b/packages/website/src/components/ModuleButtons/ModuleButton.tsx index 663293425..3f343b0fe 100644 --- a/packages/website/src/components/ModuleButtons/ModuleButton.tsx +++ b/packages/website/src/components/ModuleButtons/ModuleButton.tsx @@ -1,6 +1,5 @@ -import { asset } from '@blitzkit/core'; -import { GenericTankComponentButton } from './GenericTankComponentButton'; -import type { TankComponentButtonProps } from './TankComponentButton'; +import { GenericTankComponentButton } from "./GenericTankComponentButton"; +import type { TankComponentButtonProps } from "./TankComponentButton"; interface ModuleProps extends TankComponentButtonProps { module: string; @@ -9,11 +8,11 @@ interface ModuleProps extends TankComponentButtonProps { export function ModuleButton({ module, ...props }: ModuleProps) { return ( diff --git a/packages/website/src/components/SmallTankIcon.tsx b/packages/website/src/components/SmallTankIcon.tsx index 9bf36d757..b133d4b07 100644 --- a/packages/website/src/components/SmallTankIcon.tsx +++ b/packages/website/src/components/SmallTankIcon.tsx @@ -1,5 +1,3 @@ -import { asset } from '@blitzkit/core'; - interface SmallTankIconProps { id: number; size?: number; @@ -13,16 +11,16 @@ export function SmallTankIcon({ id, size = HEIGHT }: SmallTankIconProps) {
{`Tank
diff --git a/packages/website/src/pages/[...chunk_api]/tanks/[id]/icons/big.webp.ts b/packages/website/src/pages/[...chunk_api]/tanks/[id]/icons/big.webp.ts index 53863090c..c2b517a63 100644 --- a/packages/website/src/pages/[...chunk_api]/tanks/[id]/icons/big.webp.ts +++ b/packages/website/src/pages/[...chunk_api]/tanks/[id]/icons/big.webp.ts @@ -36,40 +36,32 @@ export async function GET({ props }: APIContext<{ id: number }>) { const bigPath = `Data/${parameters.resourcesPath.bigIconPath .replace(/~res:\//, "") .replace(/\..+/, "")}.packed.webp`; - const big = await extractPackedTankIcon( - await vfs.file(bigPath), - `tank ${tankKey} big`, - ); + const big = await extractPackedTankIcon(await vfs.file(bigPath)); return new Response(big); } } } -export async function extractPackedTankIcon( - bytes: Uint8Array, - context: string, -) { +export async function extractPackedTankIcon(bytes: Uint8Array) { const texture = sharp(bytes); const spriteRect = parsePackedSpriteRect(bytes); if (!spriteRect) { - throw new Error(`Missing RIFF sprite bounds for ${context}`); + throw new Error(`Missing RIFF sprite bounds`); } const [left, top, width, height] = spriteRect; const bounds = [left, top, width, height]; if (!bounds.every(Number.isInteger)) { - throw new Error( - `Invalid RIFF sprite bounds for ${context}: ${bounds.join(" ")}`, - ); + throw new Error(`Invalid RIFF sprite bounds: ${bounds.join(" ")}`); } const metadata = await texture.metadata(); if (metadata.width === undefined || metadata.height === undefined) { - throw new Error(`Failed to read image dimensions for ${context}`); + throw new Error(`Failed to read image dimensions`); } if ( @@ -81,7 +73,7 @@ export async function extractPackedTankIcon( top + height > metadata.height ) { throw new Error( - `Out-of-bounds RIFF sprite for ${context}: ${bounds.join(" ")} in ${metadata.width}x${metadata.height}`, + `Out-of-bounds RIFF sprite: ${bounds.join(" ")} in ${metadata.width}x${metadata.height}`, ); } diff --git a/packages/website/src/pages/[...chunk_api]/tanks/[id]/icons/small.webp.ts b/packages/website/src/pages/[...chunk_api]/tanks/[id]/icons/small.webp.ts new file mode 100644 index 000000000..16725b661 --- /dev/null +++ b/packages/website/src/pages/[...chunk_api]/tanks/[id]/icons/small.webp.ts @@ -0,0 +1,43 @@ +import { + NATION_IDS, + type TankParameters, + type VehicleDefinitionList, +} from "@blitzkit/core"; +import type { APIContext } from "astro"; +import { vfs } from "../../../../../core/blitzkit/vfs"; +import { extractPackedTankIcon } from "./big.webp"; + +export { getStaticPaths } from "../_index"; + +export async function GET({ props }: APIContext<{ id: number }>) { + const nations = await vfs + .dir(`Data/XML/item_defs/vehicles`) + .then((files) => files.filter((nation) => nation !== "common")); + + for (const nation of nations) { + const tanks = await vfs.xml<{ root: VehicleDefinitionList }>( + `Data/XML/item_defs/vehicles/${nation}/list.xml`, + ); + + for (const tankKey in tanks.root) { + const tank = tanks.root[tankKey]; + + if (tankKey.includes("tutorial_bot")) continue; + + const nationVehicleId = tank.id; + const id = (nationVehicleId << 8) + (NATION_IDS[nation] << 4) + 1; + + if (id !== props.id) continue; + + const parameters = await vfs.yaml( + `Data/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`, + ); + const bigPath = `Data/${parameters.resourcesPath.smallIconPath + .replace(/~res:\//, "") + .replace(/\..+/, "")}.packed.webp`; + const big = await extractPackedTankIcon(await vfs.file(bigPath)); + + return new Response(big); + } + } +} From 9e4d9c796841c37db41c92f482ff1e91973ea46c Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Tue, 4 Aug 2026 20:18:05 -0500 Subject: [PATCH 046/147] tank model and collision --- bun.lock | 19 +- package.json | 1 + packages/core/src/blitzkit/api/client.ts | 10 + .../scripts/src/buildAssets/tankArmors.ts | 36 ---- .../src/core/blitz/extractModel/constants.ts | 15 -- .../scripts/src/core/blitz/readBaseColor.ts | 50 ----- packages/scripts/src/core/blitz/readNormal.ts | 29 --- .../src/core/blitz/readRoughnessMetallic.ts | 24 --- .../src/core/blitz/readTexture/constants.ts | 6 - .../src/core/blitz/readTexture/index.ts | 17 -- packages/website/package.json | 2 + .../components/DynamicArmorSwitcher.tsx | 5 +- .../TechTreeSection/components/Node.tsx | 6 +- .../Tankopedia/TechTreeSection/index.tsx | 2 +- packages/website/src/hooks/useArmor.ts | 3 +- .../definitions/equipment.pb.ts | 11 ++ .../[...chunk_api]/definitions/skills.pb.ts | 11 ++ .../tanks/[id]/collision.glb.ts} | 63 ++++++- .../[...chunk_api]/tanks/[id]/model.glb.ts} | 174 +++++++++++++++++- 19 files changed, 278 insertions(+), 206 deletions(-) delete mode 100644 packages/scripts/src/buildAssets/tankArmors.ts delete mode 100644 packages/scripts/src/core/blitz/extractModel/constants.ts delete mode 100644 packages/scripts/src/core/blitz/readBaseColor.ts delete mode 100644 packages/scripts/src/core/blitz/readNormal.ts delete mode 100644 packages/scripts/src/core/blitz/readRoughnessMetallic.ts delete mode 100644 packages/scripts/src/core/blitz/readTexture/constants.ts delete mode 100644 packages/scripts/src/core/blitz/readTexture/index.ts create mode 100644 packages/website/src/pages/[...chunk_api]/definitions/equipment.pb.ts create mode 100644 packages/website/src/pages/[...chunk_api]/definitions/skills.pb.ts rename packages/{scripts/src/core/blitz/extractArmor.ts => website/src/pages/[...chunk_api]/tanks/[id]/collision.glb.ts} (72%) rename packages/{scripts/src/core/blitz/extractModel/index.ts => website/src/pages/[...chunk_api]/tanks/[id]/model.glb.ts} (65%) diff --git a/bun.lock b/bun.lock index 3bc4bf582..ff4e2722a 100644 --- a/bun.lock +++ b/bun.lock @@ -5,6 +5,7 @@ "": { "name": "blitzkit", "dependencies": { + "@gltf-transform/extensions": "^4.4.2", "lz4js": "^0.2.0", "sharp": "^0.34.5", "typescript": "^5.9.3", @@ -106,6 +107,8 @@ "@blitzkit/core": "workspace:*", "@blitzkit/i18n": "workspace:*", "@fontsource/inter": "^5.2.8", + "@gltf-transform/core": "^4.4.2", + "@gltf-transform/functions": "^4.4.2", "@radix-ui/colors": "^3.0.0", "@radix-ui/react-icons": "^1.3.2", "@radix-ui/themes": "^3.2.1", @@ -455,7 +458,7 @@ "@gltf-transform/core": ["@gltf-transform/core@4.2.1", "", { "dependencies": { "property-graph": "^3.0.0" } }, "sha512-qKhrQ29KJ9K6sz7xGoGRllqPBlROjPJMQPvCLBOfgMPH3S/Ph5E0e6fD5WmwTY5QPyBsDMRqKd1Vl0ncThTXGw=="], - "@gltf-transform/extensions": ["@gltf-transform/extensions@4.2.1", "", { "dependencies": { "@gltf-transform/core": "^4.2.1", "ktx-parse": "^1.0.1" } }, "sha512-ieHSJU9qvb3ucWDxgHFcff+C7fLtFPa6G0Wtc/ki7GsN6Rh7o9eoyqi7Ggj2LO4i3ynCwLLPb/onbJKw2fBtsA=="], + "@gltf-transform/extensions": ["@gltf-transform/extensions@4.4.2", "", { "dependencies": { "@gltf-transform/core": "^4.4.2", "ktx-parse": "^1.1.0" } }, "sha512-HJH1FM+edC5eNvl6xO0SOXJ/j/3oDoIpSu150OTdJaLBoM3TgCCGIfh4wyhgWAqZrkvgHKVGiZKxcKV5LkgPCQ=="], "@gltf-transform/functions": ["@gltf-transform/functions@4.2.1", "", { "dependencies": { "@gltf-transform/core": "^4.2.1", "@gltf-transform/extensions": "^4.2.1", "ktx-parse": "^1.0.1", "ndarray": "^1.0.19", "ndarray-lanczos": "^0.3.0", "ndarray-pixels": "^5.0.1" } }, "sha512-hFCI80N6zt7yHlqXbzlYEYQbFwK+2OOZNvacrRtrWiCWKI0k1Ad5+nZFsmuIZZV03aXb5XG6UF7JINT3wETS1Q=="], @@ -2521,6 +2524,16 @@ "@babel/preset-env/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "@blitzkit/scripts/@gltf-transform/extensions": ["@gltf-transform/extensions@4.2.1", "", { "dependencies": { "@gltf-transform/core": "^4.2.1", "ktx-parse": "^1.0.1" } }, "sha512-ieHSJU9qvb3ucWDxgHFcff+C7fLtFPa6G0Wtc/ki7GsN6Rh7o9eoyqi7Ggj2LO4i3ynCwLLPb/onbJKw2fBtsA=="], + + "@blitzkit/website/@gltf-transform/core": ["@gltf-transform/core@4.4.2", "", { "dependencies": { "property-graph": "^4.1.0" } }, "sha512-qsWKwNSwK+2s834Mt4xbYcyHqCrgNFP7hIv5s487JxebngRfDgelpghNF+kSswGb2/NuapasfK3UViFoSJJoMg=="], + + "@blitzkit/website/@gltf-transform/functions": ["@gltf-transform/functions@4.4.2", "", { "dependencies": { "@gltf-transform/core": "^4.4.2", "@gltf-transform/extensions": "^4.4.2", "ktx-parse": "^1.1.0", "ndarray": "^1.0.19", "ndarray-lanczos": "^0.3.0", "ndarray-pixels": "^5.0.1" } }, "sha512-dclXgv9TshMaWBqPDUYd4xTwBQ2PpuR8p0Y9pokrRzGQDUPXRP6lTDzbqT0UmEmxFSvRyPJjvOWUmzeiRafpvw=="], + + "@gltf-transform/extensions/@gltf-transform/core": ["@gltf-transform/core@4.4.2", "", { "dependencies": { "property-graph": "^4.1.0" } }, "sha512-qsWKwNSwK+2s834Mt4xbYcyHqCrgNFP7hIv5s487JxebngRfDgelpghNF+kSswGb2/NuapasfK3UViFoSJJoMg=="], + + "@gltf-transform/functions/@gltf-transform/extensions": ["@gltf-transform/extensions@4.2.1", "", { "dependencies": { "@gltf-transform/core": "^4.2.1", "ktx-parse": "^1.0.1" } }, "sha512-ieHSJU9qvb3ucWDxgHFcff+C7fLtFPa6G0Wtc/ki7GsN6Rh7o9eoyqi7Ggj2LO4i3ynCwLLPb/onbJKw2fBtsA=="], + "@jimp/core/file-type": ["file-type@16.5.4", "", { "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/types@14.1.0", "", { "dependencies": { "@octokit/openapi-types": "^25.1.0" } }, "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g=="], @@ -2701,6 +2714,10 @@ "yaml-language-server/yaml": ["yaml@2.2.2", "", {}, "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA=="], + "@blitzkit/website/@gltf-transform/core/property-graph": ["property-graph@4.1.0", "", {}, "sha512-AvPcP7XECNWy4LGmFQ77k7un4lSKM4eS29PTvW4ck95uYeLxXPWJM7hLuBqK91FaHqCcgJvIUCuNJjjxKE7VKQ=="], + + "@gltf-transform/extensions/@gltf-transform/core/property-graph": ["property-graph@4.1.0", "", {}, "sha512-AvPcP7XECNWy4LGmFQ77k7un4lSKM4eS29PTvW4ck95uYeLxXPWJM7hLuBqK91FaHqCcgJvIUCuNJjjxKE7VKQ=="], + "@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/package.json b/package.json index 9dd548e13..7205267e1 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "packages/*" ], "dependencies": { + "@gltf-transform/extensions": "^4.4.2", "lz4js": "^0.2.0", "sharp": "^0.34.5", "typescript": "^5.9.3" diff --git a/packages/core/src/blitzkit/api/client.ts b/packages/core/src/blitzkit/api/client.ts index 8ba62e128..38b57c408 100644 --- a/packages/core/src/blitzkit/api/client.ts +++ b/packages/core/src/blitzkit/api/client.ts @@ -2,9 +2,11 @@ import { fetchPB } from "../../protobuf"; import { CamouflageDefinitions, ConsumableDefinitions, + EquipmentDefinitions, GameDefinitions, ModelDefinitions, ProvisionDefinitions, + SkillDefinitions, TankDefinitions, } from "../../protos"; import { BlitzKitAPI } from "./base"; @@ -33,4 +35,12 @@ export class ClientBlitzKitAPI extends BlitzKitAPI { modelDefinitions() { return fetchPB("/api/definitions/models.pb", ModelDefinitions); } + + skillDefinitions() { + return fetchPB("/api/definitions/skills.pb", SkillDefinitions); + } + + equipmentDefinitions() { + return fetchPB("/api/definitions/equipment.pb", EquipmentDefinitions); + } } diff --git a/packages/scripts/src/buildAssets/tankArmors.ts b/packages/scripts/src/buildAssets/tankArmors.ts deleted file mode 100644 index 1deb46514..000000000 --- a/packages/scripts/src/buildAssets/tankArmors.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { toUniqueId } from "@blitzkit/core"; -import { NodeIO } from "@gltf-transform/core"; -import { extractArmor } from "../core/blitz/extractArmor"; -import { AssetUploader } from "../core/github/assetUploader"; -import { vfs } from "./constants"; -import { VehicleDefinitionList } from "./definitions"; - -export async function tankArmors() { - console.log("Building tank armors..."); - - using uploader = new AssetUploader("tank armors"); - const nodeIO = new NodeIO(); - const nations = await vfs - .dir(`Data/XML/item_defs/vehicles`) - .then((files) => files.filter((nation) => nation !== "common")); - - for (const nation of nations) { - const tanks = await vfs.xml<{ root: VehicleDefinitionList }>( - `Data/XML/item_defs/vehicles/${nation}/list.xml`, - ); - - for (const tankKey in tanks.root) { - const tank = tanks.root[tankKey]; - - if (tankKey.includes("tutorial_bot")) continue; - - const id = toUniqueId(nation, tank.id); - const model = await extractArmor(vfs, `${nation}-${tankKey}`); - const content = await nodeIO.writeBinary(model); - - await uploader.add({ path: `3d/tanks/armor/${id}.glb`, content }); - } - } - - await uploader.flush(); -} diff --git a/packages/scripts/src/core/blitz/extractModel/constants.ts b/packages/scripts/src/core/blitz/extractModel/constants.ts deleted file mode 100644 index 07775a4d2..000000000 --- a/packages/scripts/src/core/blitz/extractModel/constants.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { VertexAttribute, vertexAttributeVectorSizes } from "@blitzkit/core"; - -export const vertexAttributeGLTFName: Partial> = - { - [VertexAttribute.VERTEX]: "POSITION", - [VertexAttribute.NORMAL]: "NORMAL", - [VertexAttribute.TEXCOORD0]: "TEXCOORD_0", - [VertexAttribute.TEXCOORD1]: "TEXCOORD_1", - [VertexAttribute.TEXCOORD2]: "TEXCOORD_2", - [VertexAttribute.TEXCOORD3]: "TEXCOORD_3", - }; -export const vertexAttributeGltfVectorSizes = { - ...vertexAttributeVectorSizes, - [VertexAttribute.TANGENT]: 4, -} as const; diff --git a/packages/scripts/src/core/blitz/readBaseColor.ts b/packages/scripts/src/core/blitz/readBaseColor.ts deleted file mode 100644 index 50647add3..000000000 --- a/packages/scripts/src/core/blitz/readBaseColor.ts +++ /dev/null @@ -1,50 +0,0 @@ -import sharp from "sharp"; -import { readTexture } from "./readTexture"; - -export async function readBaseColor(path: string, occlusionPath?: string) { - let hasAlpha = false; - const baseRaw = await readTexture(path); - const occlusionRaw = occlusionPath - ? await readTexture(occlusionPath) - : undefined; - const base = await sharp(baseRaw.data, { raw: baseRaw }).raw().toBuffer(); - const occlusion = occlusionRaw - ? await sharp(occlusionRaw.data, { raw: occlusionRaw }) - .extractChannel(3) - .raw() - .toBuffer() - : undefined; - - const combined = Buffer.alloc(baseRaw.width * baseRaw.height * 4); - - for (let i = 0; i < baseRaw.width * baseRaw.height; i++) { - let c = 1; - - if (occlusionRaw) { - const x = i % baseRaw.width; - const y = Math.floor(i / baseRaw.width); - const u = Math.floor(x * (occlusionRaw.width / baseRaw.width)); - const v = Math.floor(y * (occlusionRaw.height / baseRaw.height)); - - const occlusionI = u + v * occlusionRaw.width; - - c = occlusion![occlusionI] / 255; - } - - const alpha = base[i * 4 + 3]; - hasAlpha ||= alpha < 255; - - combined[i * 4 + 0] = Math.round(base[i * 4 + 0] * c); - combined[i * 4 + 1] = Math.round(base[i * 4 + 1] * c); - combined[i * 4 + 2] = Math.round(base[i * 4 + 2] * c); - combined[i * 4 + 3] = alpha; - } - - const image = await sharp(combined, { - raw: { width: baseRaw.width, height: baseRaw.height, channels: 4 }, - }) - .webp() - .toBuffer(); - - return { hasAlpha, image }; -} diff --git a/packages/scripts/src/core/blitz/readNormal.ts b/packages/scripts/src/core/blitz/readNormal.ts deleted file mode 100644 index 8744e4e7e..000000000 --- a/packages/scripts/src/core/blitz/readNormal.ts +++ /dev/null @@ -1,29 +0,0 @@ -import sharp from "sharp"; -import { readTexture } from "./readTexture"; - -export async function readNormal(path: string, isBase: boolean) { - const raw = await readTexture(path); - - if (!isBase) { - return await sharp(raw.data, { raw }).removeAlpha().jpeg().toBuffer(); - } - - const bytes = 4 * raw.width * raw.height; - - for (let index = 0; index < bytes; index += 4) { - /** - * Red is always 255 and blue is always 0. Only alpha and green contain any - * sort of information. - */ - let x = raw.data[index + 3] * (2 / 255) - 1; - let y = raw.data[index + 1] * (2 / 255) - 1; - let z = Math.sqrt(Math.max(0, 1 - x ** 2 - y ** 2)); - - raw.data[index] = Math.round((x + 1) * (255 / 2)); - raw.data[index + 1] = Math.round((y + 1) * (255 / 2)); - raw.data[index + 2] = Math.round((z + 1) * (255 / 2)); - raw.data[index + 3] = 255; - } - - return await sharp(raw.data, { raw }).webp().toBuffer(); -} diff --git a/packages/scripts/src/core/blitz/readRoughnessMetallic.ts b/packages/scripts/src/core/blitz/readRoughnessMetallic.ts deleted file mode 100644 index 6c56ec43a..000000000 --- a/packages/scripts/src/core/blitz/readRoughnessMetallic.ts +++ /dev/null @@ -1,24 +0,0 @@ -import sharp from "sharp"; -import { readTexture } from "./readTexture"; - -export async function readRoughnessMetallic(path: string) { - const raw = await readTexture(path); - const blitz = sharp(raw.data, { raw: raw }); - const metallicness = await blitz?.extractChannel(1).raw().toBuffer(); - const roughness = await blitz?.extractChannel(3).raw().toBuffer(); - - const combined = Buffer.alloc(raw.width * raw.height * 3); - - for (let i = 0; i < raw.width * raw.height; i++) { - combined[i * 3 + 1] = roughness[i]; - combined[i * 3 + 2] = metallicness[i]; - } - - const image = await sharp(combined, { - raw: { width: raw.width, height: raw.height, channels: 3 }, - }) - .webp() - .toBuffer(); - - return image; -} diff --git a/packages/scripts/src/core/blitz/readTexture/constants.ts b/packages/scripts/src/core/blitz/readTexture/constants.ts deleted file mode 100644 index 560a59f5f..000000000 --- a/packages/scripts/src/core/blitz/readTexture/constants.ts +++ /dev/null @@ -1,6 +0,0 @@ -export enum TextureMutation { - Normal, - RoughnessMetallicness, - Albedo, - Miscellaneous, -} diff --git a/packages/scripts/src/core/blitz/readTexture/index.ts b/packages/scripts/src/core/blitz/readTexture/index.ts deleted file mode 100644 index 605c41640..000000000 --- a/packages/scripts/src/core/blitz/readTexture/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { DdsReadStream, PvrReadStream } from "@blitzkit/core"; -import { vfs } from "../../../buildAssets/constants"; - -export async function readTexture(path: string) { - const ddsTexturePath = path.replace(".tex", ".dx11.dds"); - const isDds = await vfs.resolve(ddsTexturePath); - const resolvedTexturePath = isDds - ? ddsTexturePath - : ddsTexturePath.replace(".dds", ".pvr"); - const file = await vfs.file(resolvedTexturePath); - - const raw = isDds - ? await new DdsReadStream(file.buffer as ArrayBuffer).dds() - : new PvrReadStream(file.buffer as ArrayBuffer).pvr(); - - return raw; -} diff --git a/packages/website/package.json b/packages/website/package.json index a32568a55..8ad371933 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -16,6 +16,8 @@ "@blitzkit/core": "workspace:*", "@blitzkit/i18n": "workspace:*", "@fontsource/inter": "^5.2.8", + "@gltf-transform/core": "^4.4.2", + "@gltf-transform/functions": "^4.4.2", "@radix-ui/colors": "^3.0.0", "@radix-ui/react-icons": "^1.3.2", "@radix-ui/themes": "^3.2.1", diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/DynamicArmorSwitcher.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/DynamicArmorSwitcher.tsx index 6da4bfb18..6a5bef873 100644 --- a/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/DynamicArmorSwitcher.tsx +++ b/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/DynamicArmorSwitcher.tsx @@ -1,4 +1,3 @@ -import { asset } from "@blitzkit/core"; import { IconButton } from "@radix-ui/themes"; import { useArmor } from "../../../../../../hooks/useArmor"; import { Duel } from "../../../../../../stores/duel"; @@ -7,7 +6,7 @@ import { Tankopedia } from "../../../../../../stores/tankopedia"; export function DynamicArmorSwitcher() { const tank = Duel.use((state) => state.protagonist.tank); const isDynamicArmorActive = Duel.use((state) => - state.protagonist.consumables.includes(73) + state.protagonist.consumables.includes(73), ); const { hasDynamicArmor } = useArmor(tank.id); @@ -38,7 +37,7 @@ export function DynamicArmorSwitcher() { > Dynamic Armor {unwrap(tank.name!)} XP Silver Silver) { + const nodeIO = new NodeIO(); + const nations = await vfs + .dir(`Data/XML/item_defs/vehicles`) + .then((files) => files.filter((nation) => nation !== "common")); + + for (const nation of nations) { + const tanks = await vfs.xml<{ root: VehicleDefinitionList }>( + `Data/XML/item_defs/vehicles/${nation}/list.xml`, + ); + + for (const tankKey in tanks.root) { + const tank = tanks.root[tankKey]; + + if (tankKey.includes("tutorial_bot")) continue; + + const id = toUniqueId(nation, tank.id); + + if (id !== props.id) continue; + + const model = await extractArmor(vfs, `${nation}-${tankKey}`); + const bytes = await nodeIO.writeBinary(model); + + return new Response(bytes); + } + } +} + +async function extractArmor(vfs: AbstractVFS, fileName: string) { const sc2Path = `Data/3d/Tanks/CollisionMeshes/${fileName}.sc2`; const scgPath = `Data/3d/Tanks/CollisionMeshes/${fileName}.scg`; const sc2 = new Sc2ReadStream( @@ -141,4 +172,18 @@ export async function extractArmor(vfs: AbstractVFS, fileName: string) { parseHierarchies(sc2["#hierarchy"], scene); return document; -} \ No newline at end of file +} + +export const vertexAttributeGLTFName: Partial> = + { + [VertexAttribute.VERTEX]: "POSITION", + [VertexAttribute.NORMAL]: "NORMAL", + [VertexAttribute.TEXCOORD0]: "TEXCOORD_0", + [VertexAttribute.TEXCOORD1]: "TEXCOORD_1", + [VertexAttribute.TEXCOORD2]: "TEXCOORD_2", + [VertexAttribute.TEXCOORD3]: "TEXCOORD_3", + }; +export const vertexAttributeGltfVectorSizes = { + ...vertexAttributeVectorSizes, + [VertexAttribute.TANGENT]: 4, +} as const; diff --git a/packages/scripts/src/core/blitz/extractModel/index.ts b/packages/website/src/pages/[...chunk_api]/tanks/[id]/model.glb.ts similarity index 65% rename from packages/scripts/src/core/blitz/extractModel/index.ts rename to packages/website/src/pages/[...chunk_api]/tanks/[id]/model.glb.ts index 3139a8608..6c7471e0c 100644 --- a/packages/scripts/src/core/blitz/extractModel/index.ts +++ b/packages/website/src/pages/[...chunk_api]/tanks/[id]/model.glb.ts @@ -1,24 +1,66 @@ import { bufferToBigInt, - ConfigArchive, - Hierarchy, + DdsReadStream, + PvrReadStream, Sc2ReadStream, ScgReadStream, - Textures, + toUniqueId, VertexAttribute, + type ConfigArchive, + type Hierarchy, + type TankParameters, + type Textures, + type VehicleDefinitionList, } from "@blitzkit/core"; -import { Document, Material, Node, Scene } from "@gltf-transform/core"; +import type { AbstractVFS } from "@blitzkit/core/src/blitzkit/vfs/abstract"; +import { Document, Material, Node, NodeIO, Scene } from "@gltf-transform/core"; +import { ALL_EXTENSIONS } from "@gltf-transform/extensions"; import { dedup, prune } from "@gltf-transform/functions"; +import type { APIContext } from "astro"; import { times } from "lodash-es"; import { dirname } from "path"; -import { AbstractVFS } from "../../vfs/abstract"; -import { readBaseColor } from "../readBaseColor"; -import { readNormal } from "../readNormal"; -import { readRoughnessMetallic } from "../readRoughnessMetallic"; +import sharp from "sharp"; +import { vfs } from "../../../../core/blitzkit/vfs"; import { vertexAttributeGLTFName, vertexAttributeGltfVectorSizes, -} from "./constants"; +} from "./collision.glb"; + +export { getStaticPaths } from "./_index"; + +export async function GET({ props }: APIContext<{ id: number }>) { + const nodeIO = new NodeIO().registerExtensions(ALL_EXTENSIONS); + const nations = await vfs + .dir(`Data/XML/item_defs/vehicles`) + .then((files) => files.filter((nation) => nation !== "common")); + + for (const nationIndex in nations) { + const nation = nations[nationIndex]; + const tanks = await vfs.xml<{ root: VehicleDefinitionList }>( + `Data/XML/item_defs/vehicles/${nation}/list.xml`, + ); + const entries = Object.entries(tanks.root); + + for (const [tankKey, tank] of entries) { + if (tankKey.includes("tutorial_bot")) continue; + + const id = toUniqueId(nation, tank.id); + + if (id !== props.id) continue; + + const parameters = await vfs.yaml( + `Data/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`, + ); + const model = await extractModel( + vfs, + parameters.resourcesPath.blitzModelPath.replace(/\.sc2$/, ""), + ); + const bytes = await nodeIO.writeBinary(model); + + return new Response(bytes); + } + } +} const ERROR_ON_UNKNOWN_COMPONENT = false; @@ -27,7 +69,7 @@ const omitMeshNames = { end: ["_POINT"], }; -export async function extractModel(vfs: AbstractVFS, path: string) { +async function extractModel(vfs: AbstractVFS, path: string) { const sc2Path = `Data/3d/${path}.sc2`; const scgPath = `Data/3d/${path}.scg`; const sc2 = new Sc2ReadStream( @@ -325,3 +367,115 @@ export async function extractModel(vfs: AbstractVFS, path: string) { return document; } + +async function readBaseColor(path: string, occlusionPath?: string) { + let hasAlpha = false; + const baseRaw = await readTexture(path); + const occlusionRaw = occlusionPath + ? await readTexture(occlusionPath) + : undefined; + const base = await sharp(baseRaw.data, { raw: baseRaw }).raw().toBuffer(); + const occlusion = occlusionRaw + ? await sharp(occlusionRaw.data, { raw: occlusionRaw }) + .extractChannel(3) + .raw() + .toBuffer() + : undefined; + + const combined = Buffer.alloc(baseRaw.width * baseRaw.height * 4); + + for (let i = 0; i < baseRaw.width * baseRaw.height; i++) { + let c = 1; + + if (occlusionRaw) { + const x = i % baseRaw.width; + const y = Math.floor(i / baseRaw.width); + const u = Math.floor(x * (occlusionRaw.width / baseRaw.width)); + const v = Math.floor(y * (occlusionRaw.height / baseRaw.height)); + + const occlusionI = u + v * occlusionRaw.width; + + c = occlusion![occlusionI] / 255; + } + + const alpha = base[i * 4 + 3]; + hasAlpha ||= alpha < 255; + + combined[i * 4 + 0] = Math.round(base[i * 4 + 0] * c); + combined[i * 4 + 1] = Math.round(base[i * 4 + 1] * c); + combined[i * 4 + 2] = Math.round(base[i * 4 + 2] * c); + combined[i * 4 + 3] = alpha; + } + + const image = await sharp(combined, { + raw: { width: baseRaw.width, height: baseRaw.height, channels: 4 }, + }) + .webp() + .toBuffer(); + + return { hasAlpha, image }; +} + +async function readRoughnessMetallic(path: string) { + const raw = await readTexture(path); + const blitz = sharp(raw.data, { raw: raw }); + const metallicness = await blitz?.extractChannel(1).raw().toBuffer(); + const roughness = await blitz?.extractChannel(3).raw().toBuffer(); + + const combined = Buffer.alloc(raw.width * raw.height * 3); + + for (let i = 0; i < raw.width * raw.height; i++) { + combined[i * 3 + 1] = roughness[i]; + combined[i * 3 + 2] = metallicness[i]; + } + + const image = await sharp(combined, { + raw: { width: raw.width, height: raw.height, channels: 3 }, + }) + .webp() + .toBuffer(); + + return image; +} + +async function readNormal(path: string, isBase: boolean) { + const raw = await readTexture(path); + + if (!isBase) { + return await sharp(raw.data, { raw }).removeAlpha().jpeg().toBuffer(); + } + + const bytes = 4 * raw.width * raw.height; + + for (let index = 0; index < bytes; index += 4) { + /** + * Red is always 255 and blue is always 0. Only alpha and green contain any + * sort of information. + */ + let x = raw.data[index + 3] * (2 / 255) - 1; + let y = raw.data[index + 1] * (2 / 255) - 1; + let z = Math.sqrt(Math.max(0, 1 - x ** 2 - y ** 2)); + + raw.data[index] = Math.round((x + 1) * (255 / 2)); + raw.data[index + 1] = Math.round((y + 1) * (255 / 2)); + raw.data[index + 2] = Math.round((z + 1) * (255 / 2)); + raw.data[index + 3] = 255; + } + + return await sharp(raw.data, { raw }).webp().toBuffer(); +} + +async function readTexture(path: string) { + const ddsTexturePath = path.replace(".tex", ".dx11.dds"); + const isDds = await vfs.resolve(ddsTexturePath); + const resolvedTexturePath = isDds + ? ddsTexturePath + : ddsTexturePath.replace(".dds", ".pvr"); + const file = await vfs.file(resolvedTexturePath); + + const raw = isDds + ? await new DdsReadStream(file.buffer as ArrayBuffer).dds() + : new PvrReadStream(file.buffer as ArrayBuffer).pvr(); + + return raw; +} From 85883cd2c542504eb6cc92220536b2fe1a96fd23 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Tue, 4 Aug 2026 20:23:50 -0500 Subject: [PATCH 047/147] faded flags --- .../[...chunk_api]/flags/fade/[id].webp.ts | 35 +++++++++++++++++++ .../flags/scratched/[id].webp.ts | 6 +--- 2 files changed, 36 insertions(+), 5 deletions(-) create mode 100644 packages/website/src/pages/[...chunk_api]/flags/fade/[id].webp.ts diff --git a/packages/website/src/pages/[...chunk_api]/flags/fade/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/flags/fade/[id].webp.ts new file mode 100644 index 000000000..e31a8ede0 --- /dev/null +++ b/packages/website/src/pages/[...chunk_api]/flags/fade/[id].webp.ts @@ -0,0 +1,35 @@ +import type { APIContext, GetStaticPathsItem } from "astro"; +import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; +import { vfs } from "../../../../core/blitzkit/vfs"; +import { getStaticPaths as _getStaticPaths } from "../../_index"; + +export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { + const paths: GetStaticPathsItem[] = []; + + const fadedFiles = await vfs + .dir(`Data/Gfx/Lobby/flags`) + .then((files) => + files.filter( + (flag) => + flag.startsWith("flag_filter_") && flag.endsWith("@2x.packed.webp"), + ), + ); + + for (const flag of fadedFiles) { + const path = `Data/Gfx/Lobby/flags/${flag}`; + const id = flag.match(/flag_filter_(.+)@2x\.packed\.webp/)![1]; + + paths.push({ + params: { id }, + props: { path }, + }); + } + + return paths; +}); + +export async function GET({ props }: APIContext<{ path: string }>) { + const bytes = new Uint8Array(await vfs.file(props.path)); + + return new Response(bytes); +} diff --git a/packages/website/src/pages/[...chunk_api]/flags/scratched/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/flags/scratched/[id].webp.ts index c56db0a06..9edbdf433 100644 --- a/packages/website/src/pages/[...chunk_api]/flags/scratched/[id].webp.ts +++ b/packages/website/src/pages/[...chunk_api]/flags/scratched/[id].webp.ts @@ -32,9 +32,5 @@ export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { export async function GET({ props }: APIContext<{ path: string }>) { const bytes = new Uint8Array(await vfs.file(props.path)); - return new Response(bytes, { - headers: { - "Content-Type": "image/webp", - }, - }); + return new Response(bytes); } From 6e759e52c9a3ab07803f0df8ce0bc50964282231 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Tue, 4 Aug 2026 20:25:47 -0500 Subject: [PATCH 048/147] use small tank icons --- packages/website/src/components/SmallTankIcon.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/website/src/components/SmallTankIcon.tsx b/packages/website/src/components/SmallTankIcon.tsx index b133d4b07..74fb89e33 100644 --- a/packages/website/src/components/SmallTankIcon.tsx +++ b/packages/website/src/components/SmallTankIcon.tsx @@ -17,7 +17,7 @@ export function SmallTankIcon({ id, size = HEIGHT }: SmallTankIconProps) { > {`Tank Date: Tue, 4 Aug 2026 20:28:54 -0500 Subject: [PATCH 049/147] update compare --- .../website/src/components/Compare/Body.tsx | 3 +- .../src/components/Compare/CompareTable.tsx | 27 ++++++---------- .../src/components/Compare/Controls.tsx | 8 ++--- .../src/components/Compare/TankControl.tsx | 6 ++-- .../[...locale]/compare/_index.tsx | 32 ++++++++----------- .../[...locale]/compare/index.astro | 6 ++-- 6 files changed, 35 insertions(+), 47 deletions(-) diff --git a/packages/website/src/components/Compare/Body.tsx b/packages/website/src/components/Compare/Body.tsx index d6bdc5193..a3e559ce4 100644 --- a/packages/website/src/components/Compare/Body.tsx +++ b/packages/website/src/components/Compare/Body.tsx @@ -1,4 +1,3 @@ -import { asset } from "@blitzkit/core"; import { Flex, Heading, IconButton, Table } from "@radix-ui/themes"; import type { TankCharacteristics } from "../../core/blitzkit/tankCharacteristics"; import { useLocale } from "../../hooks/useLocale"; @@ -58,7 +57,7 @@ export function Body({ stats }: BodyProps) { alt={unwrap(thisShell.name!)} width={16} height={16} - src={asset(`icons/shells/${thisShell.icon}.webp`)} + src={`/api/icons/shells/${thisShell.icon}.webp`} /> ))} diff --git a/packages/website/src/components/Compare/CompareTable.tsx b/packages/website/src/components/Compare/CompareTable.tsx index d3ca23c23..87123a411 100644 --- a/packages/website/src/components/Compare/CompareTable.tsx +++ b/packages/website/src/components/Compare/CompareTable.tsx @@ -1,4 +1,4 @@ -import { asset, availableProvisions, permanentSkills } from "@blitzkit/core"; +import { availableProvisions, permanentSkills } from "@blitzkit/core"; import { checkConsumableProvisionInclusivity } from "@blitzkit/core/src/blitzkit/checkConsumableProvisionInclusivity"; import { ComponentPlaceholderIcon } from "@radix-ui/react-icons"; import { @@ -11,10 +11,7 @@ import { Text, } from "@radix-ui/themes"; import { times } from "lodash-es"; -import { awaitableConsumableDefinitions } from "../../core/awaitables/consumableDefinitions"; -import { awaitableEquipmentDefinitions } from "../../core/awaitables/equipmentDefinitions"; -import { awaitableProvisionDefinitions } from "../../core/awaitables/provisionDefinitions"; -import { awaitableSkillDefinitions } from "../../core/awaitables/skillDefinitions"; +import { api } from "../../core/blitzkit/api"; import type { TankCharacteristics } from "../../core/blitzkit/tankCharacteristics"; import { Var } from "../../core/radix/var"; import { useLocale } from "../../hooks/useLocale"; @@ -42,10 +39,10 @@ const [ consumableDefinitions, provisionDefinitions, ] = await Promise.all([ - awaitableSkillDefinitions, - awaitableEquipmentDefinitions, - awaitableConsumableDefinitions, - awaitableProvisionDefinitions, + api.skillDefinitions(), + api.equipmentDefinitions(), + api.consumableDefinitions(), + api.provisionDefinitions(), ]); export function CompareTable({ stats }: CompareTableProps) { @@ -234,7 +231,7 @@ export function CompareTable({ stats }: CompareTableProps) { alt={module} width={24} height={24} - src={asset(`icons/modules/${module}.webp`)} + src={`/api/icons/modules/${module}.webp`} style={{ marginLeft: index > 0 ? -8 : 0, objectFit: "contain", @@ -323,9 +320,7 @@ export function CompareTable({ stats }: CompareTableProps) { .name!, )} key={provision} - src={asset( - `/icons/provisions/${provision}.webp`, - )} + src={`/api//icons/provisions/${provision}.webp`} style={{ left: "50%", /** @@ -530,9 +525,7 @@ export function CompareTable({ stats }: CompareTableProps) { ].name!, )} key={consumable} - src={asset( - `/icons/consumables/${consumable}.webp`, - )} + src={`/api/icons/consumables/${consumable}.webp`} style={{ left: "50%", /** @@ -624,7 +617,7 @@ export function CompareTable({ stats }: CompareTableProps) { variant={camouflage ? "solid" : "outline"} > void; } -const provisionDefinitions = await awaitableProvisionDefinitions; +const provisionDefinitions = await api.provisionDefinitions(); export function Controls({ addTankDialogOpen, @@ -56,7 +56,7 @@ export function Controls({ onSelect={(tank) => { CompareEphemeral.mutate((draft) => { draft.members.push( - tankToCompareMember(tank, provisionDefinitions) + tankToCompareMember(tank, provisionDefinitions), ); draft.sorting = undefined; }); @@ -67,7 +67,7 @@ export function Controls({ draft.members.push( ...tanks.map((tank) => { return tankToCompareMember(tank, provisionDefinitions); - }) + }), ); draft.sorting = undefined; }); diff --git a/packages/website/src/components/Compare/TankControl.tsx b/packages/website/src/components/Compare/TankControl.tsx index a9ebe9a4a..28ab0e129 100644 --- a/packages/website/src/components/Compare/TankControl.tsx +++ b/packages/website/src/components/Compare/TankControl.tsx @@ -1,7 +1,7 @@ import { ExternalLinkIcon, LoopIcon, TrashIcon } from "@radix-ui/react-icons"; import { Dialog, Flex, IconButton } from "@radix-ui/themes"; import { useState } from "react"; -import { awaitableProvisionDefinitions } from "../../core/awaitables/provisionDefinitions"; +import { api } from "../../core/blitzkit/api"; import { tankToCompareMember } from "../../core/blitzkit/tankToCompareMember"; import { useLocale } from "../../hooks/useLocale"; import { CompareEphemeral } from "../../stores/compareEphemeral"; @@ -13,7 +13,7 @@ interface TankControlProps { slug: string; } -const provisionDefinitions = await awaitableProvisionDefinitions; +const provisionDefinitions = await api.provisionDefinitions(); export function TankControl({ index, slug }: TankControlProps) { const { locale, strings } = useLocale(); @@ -61,7 +61,7 @@ export function TankControl({ index, slug }: TankControlProps) { CompareEphemeral.mutate((draft) => { draft.members[index] = tankToCompareMember( tank, - provisionDefinitions + provisionDefinitions, ); draft.sorting = undefined; }); diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/compare/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/compare/_index.tsx index b658aa085..25a8649fa 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/compare/_index.tsx +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/compare/_index.tsx @@ -1,23 +1,19 @@ import { createDefaultSkills } from "@blitzkit/core"; import { Flex, Heading, Text } from "@radix-ui/themes"; import { useEffect, useMemo, useState } from "react"; -import { CompareTable } from "../../../components/Compare/CompareTable"; -import { Controls } from "../../../components/Compare/Controls"; -import { DamageWarning } from "../../../components/DamageWarning"; -import { PageWrapper } from "../../../components/PageWrapper"; -import { awaitableEquipmentDefinitions } from "../../../core/awaitables/equipmentDefinitions"; -import { awaitableModelDefinitions } from "../../../core/awaitables/modelDefinitions"; -import { awaitableProvisionDefinitions } from "../../../core/awaitables/provisionDefinitions"; -import { awaitableSkillDefinitions } from "../../../core/awaitables/skillDefinitions"; -import { awaitableTankDefinitions } from "../../../core/awaitables/tankDefinitions"; -import { tankCharacteristics } from "../../../core/blitzkit/tankCharacteristics"; -import { tankToCompareMember } from "../../../core/blitzkit/tankToCompareMember"; +import { CompareTable } from "../../../../components/Compare/CompareTable"; +import { Controls } from "../../../../components/Compare/Controls"; +import { DamageWarning } from "../../../../components/DamageWarning"; +import { PageWrapper } from "../../../../components/PageWrapper"; +import { api } from "../../../../core/blitzkit/api"; +import { tankCharacteristics } from "../../../../core/blitzkit/tankCharacteristics"; +import { tankToCompareMember } from "../../../../core/blitzkit/tankToCompareMember"; import { type LocaleAcceptorProps, LocaleProvider, useLocale, -} from "../../../hooks/useLocale"; -import { CompareEphemeral } from "../../../stores/compareEphemeral"; +} from "../../../../hooks/useLocale"; +import { CompareEphemeral } from "../../../../stores/compareEphemeral"; const [ modelDefinitions, @@ -26,11 +22,11 @@ const [ skillDefinitions, tankDefinitions, ] = await Promise.all([ - awaitableModelDefinitions, - awaitableEquipmentDefinitions, - awaitableProvisionDefinitions, - awaitableSkillDefinitions, - awaitableTankDefinitions, + api.modelDefinitions(), + api.equipmentDefinitions(), + api.provisionDefinitions(), + api.skillDefinitions(), + api.tankDefinitions(), ]); export function Page({ locale }: LocaleAcceptorProps) { diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/compare/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/compare/index.astro index fe6f043cb..b61354c93 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/compare/index.astro +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/compare/index.astro @@ -1,7 +1,7 @@ --- -import ToolOpenGraph from "../../../components/ToolOpenGraph.astro"; -import { tools } from "../../../constants/tools"; -import BaseLayout from "../../../layouts/BaseLayout.astro"; +import ToolOpenGraph from "../../../../components/ToolOpenGraph.astro"; +import { tools } from "../../../../constants/tools"; +import BaseLayout from "../../../../layouts/BaseLayout.astro"; import { Page } from "./_index"; export { getStaticPaths } from "../index.astro"; From 7630cc52d354545d51332b4a5c50bc2cbe6088d0 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Tue, 4 Aug 2026 20:29:54 -0500 Subject: [PATCH 050/147] update performance --- .../website/src/components/Performance/Tanks.tsx | 4 ++-- .../[...locale]/performance/_index.tsx | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/website/src/components/Performance/Tanks.tsx b/packages/website/src/components/Performance/Tanks.tsx index 5496e1e5c..0659def29 100644 --- a/packages/website/src/components/Performance/Tanks.tsx +++ b/packages/website/src/components/Performance/Tanks.tsx @@ -11,7 +11,7 @@ import { } from "react"; import usePromise from "react-promise-suspense"; import { awaitableAverageDefinitions } from "../../core/awaitables/averageDefinitions"; -import { awaitableTankDefinitions } from "../../core/awaitables/tankDefinitions"; +import { api } from "../../core/blitzkit/api"; import { filterTanks } from "../../core/blitzkit/filterTanks"; import { useAveragesExclusionRatio } from "../../hooks/useAveragesExclusionRatio"; import { useLocale } from "../../hooks/useLocale"; @@ -27,7 +27,7 @@ const PREVIEW_COUNT = 10; const DEFAULT_LOADED_ROWS = 25; const [tankDefinitions, averageDefinitions] = await Promise.all([ - awaitableTankDefinitions, + api.tankDefinitions(), awaitableAverageDefinitions, ]); diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/performance/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/performance/_index.tsx index e0ce43b16..78a82af19 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/performance/_index.tsx +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/performance/_index.tsx @@ -1,12 +1,12 @@ -import { PageWrapper } from "../../../components/PageWrapper"; -import { PlayerCountControl } from "../../../components/Performance/PlayerCountControl"; -import { TankTable } from "../../../components/Performance/Table"; -import { FilterControl } from "../../../components/TankSearch/components/FilterControl"; +import { PageWrapper } from "../../../../components/PageWrapper"; +import { PlayerCountControl } from "../../../../components/Performance/PlayerCountControl"; +import { TankTable } from "../../../../components/Performance/Table"; +import { FilterControl } from "../../../../components/TankSearch/components/FilterControl"; import { LocaleProvider, type LocaleAcceptorProps, -} from "../../../hooks/useLocale"; -import type { MaybeSkeletonComponentProps } from "../../../types/maybeSkeletonComponentProps"; +} from "../../../../hooks/useLocale"; +import type { MaybeSkeletonComponentProps } from "../../../../types/maybeSkeletonComponentProps"; type PageProps = MaybeSkeletonComponentProps & LocaleAcceptorProps; From 36e8a20cd5a4a1182558ed3b9a25dc2e80302654 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Tue, 4 Aug 2026 20:31:49 -0500 Subject: [PATCH 051/147] update playlist --- .../website/src/components/PlaylistGenerator.tsx | 12 ++++++------ .../website/src/components/PlaylistTankEntry.tsx | 8 ++++---- .../src/core/blitzkit/generatePlaylist.ts | 4 ++-- .../[...locale]/playlist/_index.tsx | 16 ++++++++-------- .../[...locale]/playlist/index.astro | 6 +++--- 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/packages/website/src/components/PlaylistGenerator.tsx b/packages/website/src/components/PlaylistGenerator.tsx index 85b9c5d0a..c533495ca 100644 --- a/packages/website/src/components/PlaylistGenerator.tsx +++ b/packages/website/src/components/PlaylistGenerator.tsx @@ -12,7 +12,7 @@ import { } from "@radix-ui/themes"; import { useMemo, useState } from "react"; import usePromise from "react-promise-suspense"; -import { awaitableTankDefinitions } from "../core/awaitables/tankDefinitions"; +import { api } from "../core/blitzkit/api"; import { filterTanks } from "../core/blitzkit/filterTanks"; import { generatePlaylist } from "../core/blitzkit/generatePlaylist"; import { useLocale } from "../hooks/useLocale"; @@ -22,7 +22,7 @@ import type { MaybeSkeletonComponentProps } from "../types/maybeSkeletonComponen import { LinkI18n } from "./LinkI18n"; import { FilterControl } from "./TankSearch/components/FilterControl"; -const tankDefinitions = await awaitableTankDefinitions; +const tankDefinitions = await api.tankDefinitions(); const POOL_HEIGHT = "16rem"; @@ -33,12 +33,12 @@ export function PlaylistGenerator({ skeleton }: MaybeSkeletonComponentProps) { const filters = TankFilters.use(); const randomTankList = useMemo( () => fisherYates(Object.values(tankDefinitions.tanks)), - [filters] + [filters], ); const tanks = usePromise( () => filterTanks(filters, randomTankList, wargaming?.id), // react-promise-suspense has awful type annotations - [filters] as any + [filters] as any, ); const tanksTrimmed = useMemo(() => tanks.slice(0, 65), [filters]); @@ -58,7 +58,7 @@ export function PlaylistGenerator({ skeleton }: MaybeSkeletonComponentProps) { { count: tanks.length, time: Math.round(tanks.length * ((2 * 4) / 60)), - } + }, )} @@ -145,7 +145,7 @@ export function PlaylistGenerator({ skeleton }: MaybeSkeletonComponentProps) { backgroundRepeat: "no-repeat", }} /> - ) + ), )} ( ({ id, checked, then, now, index }) => { @@ -93,7 +93,7 @@ export const PlaylistTankEntry = memo( {now && now.last !== 0 ? rtf.format( Math.ceil((now.last - dateNow / 1000) / (60 * 60 * 24)), - "day" + "day", ) : strings.website.tools.playlist.table.unrecorded} @@ -103,5 +103,5 @@ export const PlaylistTankEntry = memo( (a, b) => a.checked === b.checked && a.then?.battles === b.then?.battles && - a.index === b.index + a.index === b.index, ); diff --git a/packages/website/src/core/blitzkit/generatePlaylist.ts b/packages/website/src/core/blitzkit/generatePlaylist.ts index ccd8a13c7..f0fd87bdb 100644 --- a/packages/website/src/core/blitzkit/generatePlaylist.ts +++ b/packages/website/src/core/blitzkit/generatePlaylist.ts @@ -7,10 +7,10 @@ import { import { App } from "../../stores/app"; import { Playlist } from "../../stores/playlist"; import { TankFilters } from "../../stores/tankFilters"; -import { awaitableTankDefinitions } from "../awaitables/tankDefinitions"; +import { api } from "./api"; import { filterTanks } from "./filterTanks"; -const tankDefinitions = await awaitableTankDefinitions; +const tankDefinitions = await api.tankDefinitions(); const tanks = Object.values(tankDefinitions.tanks); export async function generatePlaylist() { diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/playlist/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/playlist/_index.tsx index 0fe0c6f44..04e090b26 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/playlist/_index.tsx +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/playlist/_index.tsx @@ -1,11 +1,11 @@ -import { PageWrapper } from "../../../components/PageWrapper"; -import { PlaylistGenerator } from "../../../components/PlaylistGenerator"; -import { PlaylistOrder } from "../../../components/PlaylistOrder"; -import { LocaleProvider } from "../../../hooks/useLocale"; -import { App } from "../../../stores/app"; -import { Playlist } from "../../../stores/playlist"; -import { TankFilters } from "../../../stores/tankFilters"; -import type { MaybeSkeletonComponentProps } from "../../../types/maybeSkeletonComponentProps"; +import { PageWrapper } from "../../../../components/PageWrapper"; +import { PlaylistGenerator } from "../../../../components/PlaylistGenerator"; +import { PlaylistOrder } from "../../../../components/PlaylistOrder"; +import { LocaleProvider } from "../../../../hooks/useLocale"; +import { App } from "../../../../stores/app"; +import { Playlist } from "../../../../stores/playlist"; +import { TankFilters } from "../../../../stores/tankFilters"; +import type { MaybeSkeletonComponentProps } from "../../../../types/maybeSkeletonComponentProps"; interface Props { locale: string; diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/playlist/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/playlist/index.astro index 5d5989351..ad840e7cc 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/playlist/index.astro +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/playlist/index.astro @@ -1,7 +1,7 @@ --- -import ToolOpenGraph from "../../../components/ToolOpenGraph.astro"; -import { tools } from "../../../constants/tools"; -import BaseLayout from "../../../layouts/BaseLayout.astro"; +import ToolOpenGraph from "../../../../components/ToolOpenGraph.astro"; +import { tools } from "../../../../constants/tools"; +import BaseLayout from "../../../../layouts/BaseLayout.astro"; import { Page } from "./_index.tsx"; export { getStaticPaths } from "../index.astro"; From 6a05f110010a54c96996588a3fa90963ed7c8471 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Tue, 4 Aug 2026 20:34:11 -0500 Subject: [PATCH 052/147] update mixer --- .../MixerScene/components/Model.tsx | 4 +-- .../website/src/core/blitzkit/curateMixer.ts | 8 ++--- .../[...locale]/mixer/_index.tsx | 36 +++++++++---------- .../[...locale]/mixer/index.astro | 6 ++-- 4 files changed, 26 insertions(+), 28 deletions(-) diff --git a/packages/website/src/components/MixerScene/components/Model.tsx b/packages/website/src/components/MixerScene/components/Model.tsx index 0bfb2e1cb..85d9c04af 100644 --- a/packages/website/src/components/MixerScene/components/Model.tsx +++ b/packages/website/src/components/MixerScene/components/Model.tsx @@ -2,7 +2,7 @@ import { useThree, type ThreeEvent } from "@react-three/fiber"; import type { QuicklimeEvent } from "quicklime"; import { useCallback, useEffect, useRef } from "react"; import { Group, Vector2 } from "three"; -import { awaitableModelDefinitions } from "../../../core/awaitables/modelDefinitions"; +import { api } from "../../../core/blitzkit/api"; import { jsxTree } from "../../../core/blitzkit/jsxTree"; import { modelTransformEvent, @@ -12,7 +12,7 @@ import { controlsEnabledEvent } from "../../../core/controlsEnabled"; import { useModel } from "../../../hooks/useModel"; import { Mixer } from "../../../stores/mixer"; -const modelDefinitions = await awaitableModelDefinitions; +const modelDefinitions = await api.modelDefinitions(); export function Model() { const canvas = useThree((state) => state.gl.domElement); diff --git a/packages/website/src/core/blitzkit/curateMixer.ts b/packages/website/src/core/blitzkit/curateMixer.ts index 021c296a3..0782efd49 100644 --- a/packages/website/src/core/blitzkit/curateMixer.ts +++ b/packages/website/src/core/blitzkit/curateMixer.ts @@ -1,8 +1,8 @@ -import { awaitableModelDefinitions } from "../awaitables/modelDefinitions"; -import { awaitableTankDefinitions } from "../awaitables/tankDefinitions"; +import { api } from "./api"; + +const tankDefinitions = await api.tankDefinitions(); +const modelDefinitions = await api.modelDefinitions(); -const tankDefinitions = await awaitableTankDefinitions; -const modelDefinitions = await awaitableModelDefinitions; const tanks = Object.values(tankDefinitions.tanks).filter((tank) => { const tankModel = modelDefinitions.models[tank.id]; const turret = tank.turrets.at(-1)!; diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/mixer/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/mixer/_index.tsx index a0d414198..caabc2184 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/mixer/_index.tsx +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/mixer/_index.tsx @@ -1,5 +1,4 @@ import { - asset, GunDefinition, TankDefinition, TIER_ROMAN_NUMERALS, @@ -19,27 +18,26 @@ import { type ButtonProps, } from "@radix-ui/themes"; import { useRef, useState } from "react"; -import { MixerScene } from "../../../components/MixerScene"; -import { ModuleButton } from "../../../components/ModuleButtons/ModuleButton"; -import { PageWrapper } from "../../../components/PageWrapper"; -import { ScreenshotButton } from "../../../components/ScreenshotButton"; -import { TankSearch } from "../../../components/TankSearch"; -import { awaitableModelDefinitions } from "../../../core/awaitables/modelDefinitions"; -import { awaitableTankDefinitions } from "../../../core/awaitables/tankDefinitions"; -import { curateMixer } from "../../../core/blitzkit/curateMixer"; +import { MixerScene } from "../../../../components/MixerScene"; +import { ModuleButton } from "../../../../components/ModuleButtons/ModuleButton"; +import { PageWrapper } from "../../../../components/PageWrapper"; +import { ScreenshotButton } from "../../../../components/ScreenshotButton"; +import { TankSearch } from "../../../../components/TankSearch"; +import { api } from "../../../../core/blitzkit/api"; +import { curateMixer } from "../../../../core/blitzkit/curateMixer"; import { LocaleProvider, useLocale, type LocaleAcceptorProps, -} from "../../../hooks/useLocale"; -import { Mixer } from "../../../stores/mixer"; -import { Tankopedia } from "../../../stores/tankopedia"; -import type { MaybeSkeletonComponentProps } from "../../../types/maybeSkeletonComponentProps"; +} from "../../../../hooks/useLocale"; +import { Mixer } from "../../../../stores/mixer"; +import { Tankopedia } from "../../../../stores/tankopedia"; +import type { MaybeSkeletonComponentProps } from "../../../../types/maybeSkeletonComponentProps"; -const modelDefinition = await awaitableModelDefinitions.then( - ({ models }) => models[1] -); -const tankDefinitions = await awaitableTankDefinitions; +const modelDefinition = await api + .modelDefinitions() + .then(({ models }) => models[1]); +const tankDefinitions = await api.tankDefinitions(); export function Page({ locale, @@ -65,7 +63,7 @@ type ModuleButtonProps = Omit & onSelect: ( tank: TankDefinition, turret?: TurretDefinition, - gun?: GunDefinition + gun?: GunDefinition, ) => void; turret?: number; gun?: number; @@ -112,7 +110,7 @@ function ModuleChooser({ height: "4em", objectFit: "contain", }} - src={asset(`icons/tanks/big/${tank}.webp`)} + src={`/api/tanks/${tank}/icons/big.webp`} /> )} diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/mixer/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/mixer/index.astro index 70e118121..b1ea0989f 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/mixer/index.astro +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/mixer/index.astro @@ -1,7 +1,7 @@ --- -import ToolOpenGraph from "../../../components/ToolOpenGraph.astro"; -import { tools } from "../../../constants/tools"; -import BaseLayout from "../../../layouts/BaseLayout.astro"; +import ToolOpenGraph from "../../../../components/ToolOpenGraph.astro"; +import { tools } from "../../../../constants/tools"; +import BaseLayout from "../../../../layouts/BaseLayout.astro"; import { Page } from "./_index"; export { getStaticPaths } from "../index.astro"; From f09663e1c0a2246b9891bb8547fde072f45c2be3 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Tue, 4 Aug 2026 20:35:51 -0500 Subject: [PATCH 053/147] update guesser --- .../src/components/GuessBackground/index.tsx | 9 +++---- packages/website/src/components/Guesser.tsx | 10 +++---- .../[...locale]/guess/_index.tsx | 27 +++++++++---------- .../[...locale]/guess/index.astro | 6 ++--- 4 files changed, 25 insertions(+), 27 deletions(-) diff --git a/packages/website/src/components/GuessBackground/index.tsx b/packages/website/src/components/GuessBackground/index.tsx index 6923b9fbe..fce8951ab 100644 --- a/packages/website/src/components/GuessBackground/index.tsx +++ b/packages/website/src/components/GuessBackground/index.tsx @@ -1,7 +1,6 @@ import { Box } from "@radix-ui/themes"; import { useEffect } from "react"; -import { awaitableModelDefinitions } from "../../core/awaitables/modelDefinitions"; -import { awaitableProvisionDefinitions } from "../../core/awaitables/provisionDefinitions"; +import { api } from "../../core/blitzkit/api"; import { tankToDuelMember } from "../../core/blitzkit/tankToDuelMember"; import { Var } from "../../core/radix/var"; import { Duel } from "../../stores/duel"; @@ -10,8 +9,8 @@ import { Tankopedia } from "../../stores/tankopedia"; import "./index.css"; const [modelDefinitions, provisionDefinitions] = await Promise.all([ - awaitableModelDefinitions, - awaitableProvisionDefinitions, + api.modelDefinitions(), + api.provisionDefinitions(), ]); export function GuessBackground() { @@ -42,7 +41,7 @@ export function GuessBackground() { ? "accent-2" : guessState === GuessState.Correct ? "jade-3" - : "tomato-3" + : "tomato-3", ), }} > diff --git a/packages/website/src/components/Guesser.tsx b/packages/website/src/components/Guesser.tsx index 27c7915f0..93c02e1cf 100644 --- a/packages/website/src/components/Guesser.tsx +++ b/packages/website/src/components/Guesser.tsx @@ -1,5 +1,4 @@ import { - asset, SEARCH_KEYS, TankDefinition, TIER_ROMAN_NUMERALS, @@ -29,8 +28,8 @@ import { import fuzzysort from "fuzzysort"; import { debounce, times } from "lodash-es"; import { useCallback, useEffect, useRef, useState } from "react"; -import { awaitableTankDefinitions } from "../core/awaitables/tankDefinitions"; import { awaitableTankNames } from "../core/awaitables/tankNames"; +import { api } from "../core/blitzkit/api"; import { useLocale } from "../hooks/useLocale"; import { Guess, GuessState } from "../stores/guess"; import { classIcons } from "./ClassIcon"; @@ -40,7 +39,7 @@ const { go } = fuzzysort; const [tankNames, tankDefinitions] = await Promise.all([ awaitableTankNames, - awaitableTankDefinitions, + api.tankDefinitions(), ]); const ids = Object.keys(tankDefinitions.tanks); @@ -150,7 +149,7 @@ export function Guesser() { prefix={ } discriminator={ @@ -322,7 +321,8 @@ export function Guesser() { ? GuessState.Correct : GuessState.Incorrect; draft.totalGuesses++; - draft.correctGuesses += correct && draft.tiers.length === 10 ? 1 : 0; + draft.correctGuesses += + correct && draft.tiers.length === 10 ? 1 : 0; draft.streak = correct ? draft.streak + 1 : 0; }); } else { diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/guess/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/guess/_index.tsx index ac9c88c3b..4cdcb0744 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/guess/_index.tsx +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/guess/_index.tsx @@ -1,25 +1,24 @@ import { Box, Flex, Heading } from "@radix-ui/themes"; import { Suspense, useEffect } from "react"; -import { GuessBackground } from "../../../components/GuessBackground"; -import { Guesser } from "../../../components/Guesser"; -import { GuessRenderer } from "../../../components/GuessRenderer"; -import { GuessRendererLoader } from "../../../components/GuessRendererLoader"; -import { PageWrapper } from "../../../components/PageWrapper"; -import { awaitableModelDefinitions } from "../../../core/awaitables/modelDefinitions"; -import { awaitableTankDefinitions } from "../../../core/awaitables/tankDefinitions"; +import { GuessBackground } from "../../../../components/GuessBackground"; +import { Guesser } from "../../../../components/Guesser"; +import { GuessRenderer } from "../../../../components/GuessRenderer"; +import { GuessRendererLoader } from "../../../../components/GuessRendererLoader"; +import { PageWrapper } from "../../../../components/PageWrapper"; +import { api } from "../../../../core/blitzkit/api"; import { type LocaleAcceptorProps, LocaleProvider, useLocale, -} from "../../../hooks/useLocale"; -import { Duel } from "../../../stores/duel"; -import { Guess, GuessState } from "../../../stores/guess"; -import { Tankopedia } from "../../../stores/tankopedia"; -import type { MaybeSkeletonComponentProps } from "../../../types/maybeSkeletonComponentProps"; +} from "../../../../hooks/useLocale"; +import { Duel } from "../../../../stores/duel"; +import { Guess, GuessState } from "../../../../stores/guess"; +import { Tankopedia } from "../../../../stores/tankopedia"; +import type { MaybeSkeletonComponentProps } from "../../../../types/maybeSkeletonComponentProps"; const [tankDefinitions, modelDefinitions] = await Promise.all([ - awaitableTankDefinitions, - awaitableModelDefinitions, + api.tankDefinitions(), + api.modelDefinitions(), ]); const ids = Object.keys(tankDefinitions.tanks); diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/guess/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/guess/index.astro index 4e457b286..1816e5479 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/guess/index.astro +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/guess/index.astro @@ -1,7 +1,7 @@ --- -import ToolOpenGraph from "../../../components/ToolOpenGraph.astro"; -import { tools } from "../../../constants/tools"; -import BaseLayout from "../../../layouts/BaseLayout.astro"; +import ToolOpenGraph from "../../../../components/ToolOpenGraph.astro"; +import { tools } from "../../../../constants/tools"; +import BaseLayout from "../../../../layouts/BaseLayout.astro"; import { Page } from "./_index"; export { getStaticPaths } from "../index.astro"; From 851a1108d21375ed16aec6019b463b6079dae41e Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Tue, 4 Aug 2026 20:38:24 -0500 Subject: [PATCH 054/147] some updates to gallery --- packages/website/src/core/awaitables/gallery.ts | 2 +- .../[...chunk_default]/[...locale]/gallery/_index.tsx | 10 +++++----- .../[...chunk_default]/[...locale]/gallery/index.astro | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/website/src/core/awaitables/gallery.ts b/packages/website/src/core/awaitables/gallery.ts index 3571a940d..3a446670e 100644 --- a/packages/website/src/core/awaitables/gallery.ts +++ b/packages/website/src/core/awaitables/gallery.ts @@ -1,3 +1,3 @@ -import { fetchGallery } from '@blitzkit/core'; +import { fetchGallery } from "@blitzkit/core"; export const awaitableGallery = fetchGallery(); diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/gallery/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/gallery/_index.tsx index e18e63bfc..f8ac2e49a 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/gallery/_index.tsx +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/gallery/_index.tsx @@ -1,11 +1,11 @@ -import { GalleryList } from "../../../components/Gallery/List"; -import { GallerySearch } from "../../../components/Gallery/Search"; -import { PageWrapper } from "../../../components/PageWrapper"; +import { GalleryList } from "../../../../components/Gallery/List"; +import { GallerySearch } from "../../../../components/Gallery/Search"; +import { PageWrapper } from "../../../../components/PageWrapper"; import { LocaleProvider, type LocaleAcceptorProps, -} from "../../../hooks/useLocale"; -import type { MaybeSkeletonComponentProps } from "../../../types/maybeSkeletonComponentProps"; +} from "../../../../hooks/useLocale"; +import type { MaybeSkeletonComponentProps } from "../../../../types/maybeSkeletonComponentProps"; export function Page({ locale, diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/gallery/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/gallery/index.astro index b0ccfcfd1..c5e321f5f 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/gallery/index.astro +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/gallery/index.astro @@ -1,6 +1,6 @@ --- -import { tools } from "../../../constants/tools"; -import ToolLayout from "../../../layouts/ToolLayout.astro"; +import { tools } from "../../../../constants/tools"; +import ToolLayout from "../../../../layouts/ToolLayout.astro"; import type { getStaticPaths } from "../index.astro"; import { Page } from "./_index"; From bc2b9f1267012a5ebb3e16602d843fffcd04ad03 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Wed, 5 Aug 2026 13:01:51 -0500 Subject: [PATCH 055/147] glossary --- packages/core/src/blitz/fetchGlossary.ts | 4 +- packages/core/src/blitzkit/api/base.ts | 2 + packages/core/src/blitzkit/api/client.ts | 5 ++ .../api/server/10_galleryDefinitions.ts | 67 ++++++++++++++++ .../core/src/blitzkit/api/server/index.ts | 4 +- .../website/src/components/Gallery/Card.tsx | 6 +- .../website/src/components/Gallery/List.tsx | 8 +- .../website/src/core/awaitables/gallery.ts | 3 - .../[...chunk_api]/definitions/gallery.pb.ts | 11 +++ .../[...chunk_api]/gallery/avatars/[name].ts | 79 +++++++++++++++++++ 10 files changed, 174 insertions(+), 15 deletions(-) create mode 100644 packages/core/src/blitzkit/api/server/10_galleryDefinitions.ts delete mode 100644 packages/website/src/core/awaitables/gallery.ts create mode 100644 packages/website/src/pages/[...chunk_api]/definitions/gallery.pb.ts create mode 100644 packages/website/src/pages/[...chunk_api]/gallery/avatars/[name].ts diff --git a/packages/core/src/blitz/fetchGlossary.ts b/packages/core/src/blitz/fetchGlossary.ts index a6a92a333..00309ea98 100644 --- a/packages/core/src/blitz/fetchGlossary.ts +++ b/packages/core/src/blitz/fetchGlossary.ts @@ -4,8 +4,8 @@ import type { BlitzGlossary } from "../types"; export async function fetchGlossary(locale: string) { const response = await fetch( `${assertSecret( - import.meta.env.WOTB_GLOSSARY_DOMAIN - )}/${locale}${assertSecret(import.meta.env.WOTB_GLOSSARY_PATH)}` + import.meta.env.WOTB_GLOSSARY_DOMAIN, + )}/${locale}${assertSecret(import.meta.env.WOTB_GLOSSARY_PATH)}`, ); return (await response.json()) as BlitzGlossary; } diff --git a/packages/core/src/blitzkit/api/base.ts b/packages/core/src/blitzkit/api/base.ts index 49173042d..92038adc6 100644 --- a/packages/core/src/blitzkit/api/base.ts +++ b/packages/core/src/blitzkit/api/base.ts @@ -2,6 +2,7 @@ import { CamouflageDefinitions, ConsumableDefinitions, EquipmentDefinitions, + Gallery, GameDefinitions, MapDefinitions, ModelDefinitions, @@ -20,4 +21,5 @@ export abstract class BlitzKitAPI { abstract camouflageDefinitions(): Promise; abstract tankDefinitions(): Promise; abstract gameDefinitions(): Promise; + abstract galleryDefinitions(): Promise; } diff --git a/packages/core/src/blitzkit/api/client.ts b/packages/core/src/blitzkit/api/client.ts index 38b57c408..2a28275a4 100644 --- a/packages/core/src/blitzkit/api/client.ts +++ b/packages/core/src/blitzkit/api/client.ts @@ -3,6 +3,7 @@ import { CamouflageDefinitions, ConsumableDefinitions, EquipmentDefinitions, + Gallery, GameDefinitions, ModelDefinitions, ProvisionDefinitions, @@ -43,4 +44,8 @@ export class ClientBlitzKitAPI extends BlitzKitAPI { equipmentDefinitions() { return fetchPB("/api/definitions/equipment.pb", EquipmentDefinitions); } + + galleryDefinitions() { + return fetchPB("/api/definitions/gallery.pb", Gallery); + } } diff --git a/packages/core/src/blitzkit/api/server/10_galleryDefinitions.ts b/packages/core/src/blitzkit/api/server/10_galleryDefinitions.ts new file mode 100644 index 000000000..fede7e381 --- /dev/null +++ b/packages/core/src/blitzkit/api/server/10_galleryDefinitions.ts @@ -0,0 +1,67 @@ +import { Avatar, fetchGlossary, Gallery } from "@blitzkit/core"; +import { ServerBlitzKitAPI9 } from "@blitzkit/core/src/blitzkit/api/server/9_gameDefinitions"; +import locales from "@blitzkit/i18n/locales.json"; +import { extname } from "path"; +import { Cache } from "./0_base"; + +export abstract class ServerBlitzKitAPI10 extends ServerBlitzKitAPI9 { + @Cache() + async galleryDefinitions() { + const avatars: Record = {}; + + await Promise.all( + locales.supported.map(async (supported) => { + const glossary = await fetchGlossary( + supported.blitz ?? supported.locale, + ); + + console.log( + `Found ${Object.keys(glossary).length} things for ${supported.locale}`, + ); + + for (const key in glossary) { + const glossaryEntry = glossary[key]; + + if ( + !key.startsWith("avatar") || + key.endsWith("_part") || + glossaryEntry.image_url === null + ) { + continue; + } + + if (key in avatars) { + avatars[key].avatar.name!.locales[supported.locale] = + glossaryEntry.title; + } else { + const extension = extname(glossaryEntry.image_url); + + avatars[key] = { + url: glossaryEntry.image_url, + avatar: { + id: key, + name: { locales: { [supported.locale]: glossaryEntry.title } }, + extension, + }, + }; + } + } + }), + ); + + const gallery: Gallery = { avatars: [] }; + + await Promise.all( + Object.entries(avatars).map(async ([key, { url, avatar }]) => { + if (!(locales.default in avatars[key].avatar.name!.locales)) { + console.warn(`Avatar ${key} has no ${locales.default} name`); + return; + } + + gallery.avatars.push(avatar); + }), + ); + + return gallery; + } +} diff --git a/packages/core/src/blitzkit/api/server/index.ts b/packages/core/src/blitzkit/api/server/index.ts index 33f1f2273..3b9c459a9 100644 --- a/packages/core/src/blitzkit/api/server/index.ts +++ b/packages/core/src/blitzkit/api/server/index.ts @@ -1,3 +1,3 @@ -import { ServerBlitzKitAPI9 } from "./9_gameDefinitions"; +import { ServerBlitzKitAPI10 } from "@blitzkit/core/src/blitzkit/api/server/10_galleryDefinitions"; -export class ServerBlitzKitAPI extends ServerBlitzKitAPI9 {} +export class ServerBlitzKitAPI extends ServerBlitzKitAPI10 {} diff --git a/packages/website/src/components/Gallery/Card.tsx b/packages/website/src/components/Gallery/Card.tsx index 8345b5ded..aa524492d 100644 --- a/packages/website/src/components/Gallery/Card.tsx +++ b/packages/website/src/components/Gallery/Card.tsx @@ -1,4 +1,4 @@ -import { asset, Avatar } from "@blitzkit/core"; +import { Avatar } from "@blitzkit/core"; import { Cross1Icon, DownloadIcon } from "@radix-ui/react-icons"; import { Dialog, @@ -18,9 +18,7 @@ type GalleryCardProps = MaybeSkeletonComponentProps<{ }>; export function GalleryCard(props: GalleryCardProps) { - const src = asset( - `gallery/avatars/${!props.skeleton && `${props.avatar.id}${props.avatar.extension}`}`, - ); + const src = `/api/gallery/avatars/${!props.skeleton && `${props.avatar.id}${props.avatar.extension}`}`; const card = useRef(null!); const { unwrap } = useLocale(); diff --git a/packages/website/src/components/Gallery/List.tsx b/packages/website/src/components/Gallery/List.tsx index aa1ac26cf..7d92a9752 100644 --- a/packages/website/src/components/Gallery/List.tsx +++ b/packages/website/src/components/Gallery/List.tsx @@ -4,7 +4,7 @@ import { Flex, Text } from "@radix-ui/themes"; import fuzzysort from "fuzzysort"; import { times } from "lodash-es"; import { useMemo, useState } from "react"; -import { awaitableGallery } from "../../core/awaitables/gallery"; +import { api } from "../../core/blitzkit/api"; import { useLocale } from "../../hooks/useLocale"; import { Gallery } from "../../stores/gallery"; import type { MaybeSkeletonComponentProps } from "../../types/maybeSkeletonComponentProps"; @@ -16,7 +16,7 @@ export interface Avatar { id: string; } -const gallery = await awaitableGallery; +const gallery = await api.galleryDefinitions(); const DEFAULT_LOADED = 42; const PREVIEW_COUNT = 28; @@ -35,7 +35,7 @@ export function GalleryList({ skeleton }: MaybeSkeletonComponentProps) { .go(search, gallery.avatars, { keys: [ ...locales.supported.map( - (supported) => `name.locales.${supported.locale}` + (supported) => `name.locales.${supported.locale}`, ), "id", ], @@ -72,7 +72,7 @@ export function GalleryList({ skeleton }: MaybeSkeletonComponentProps) { skeleton onIntersection={() => setLoadedCards((state) => state + 2)} /> - ) + ), )}
diff --git a/packages/website/src/core/awaitables/gallery.ts b/packages/website/src/core/awaitables/gallery.ts deleted file mode 100644 index 3a446670e..000000000 --- a/packages/website/src/core/awaitables/gallery.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { fetchGallery } from "@blitzkit/core"; - -export const awaitableGallery = fetchGallery(); diff --git a/packages/website/src/pages/[...chunk_api]/definitions/gallery.pb.ts b/packages/website/src/pages/[...chunk_api]/definitions/gallery.pb.ts new file mode 100644 index 000000000..49453741e --- /dev/null +++ b/packages/website/src/pages/[...chunk_api]/definitions/gallery.pb.ts @@ -0,0 +1,11 @@ +import { Gallery } from "@blitzkit/core"; +import { api } from "../../../core/blitzkit/api"; + +export { getStaticPaths } from "../_index"; + +export async function GET() { + const definitions = await api.galleryDefinitions(); + const bytes = Gallery.encode(definitions).finish(); + + return new Response(bytes); +} diff --git a/packages/website/src/pages/[...chunk_api]/gallery/avatars/[name].ts b/packages/website/src/pages/[...chunk_api]/gallery/avatars/[name].ts new file mode 100644 index 000000000..032e1908d --- /dev/null +++ b/packages/website/src/pages/[...chunk_api]/gallery/avatars/[name].ts @@ -0,0 +1,79 @@ +import { fetchGlossary, type Avatar } from "@blitzkit/core"; +import locales from "@blitzkit/i18n/locales.json"; +import type { APIContext, GetStaticPathsItem } from "astro"; +import { extname } from "path"; +import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; +import { getStaticPaths as _getStaticPaths } from "../../_index"; + +export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { + const paths: GetStaticPathsItem[] = []; + + const avatars: Record = {}; + + await Promise.all( + locales.supported.map(async (supported) => { + const glossary = await fetchGlossary(supported.blitz ?? supported.locale); + + console.log( + `Found ${Object.keys(glossary).length} things for ${supported.locale}`, + ); + + for (const key in glossary) { + const glossaryEntry = glossary[key]; + + if ( + !key.startsWith("avatar") || + key.endsWith("_part") || + glossaryEntry.image_url === null + ) { + continue; + } + + if (key in avatars) { + avatars[key].avatar.name!.locales[supported.locale] = + glossaryEntry.title; + } else { + const extension = extname(glossaryEntry.image_url); + + avatars[key] = { + url: glossaryEntry.image_url, + avatar: { + id: key, + name: { locales: { [supported.locale]: glossaryEntry.title } }, + extension, + }, + }; + } + } + }), + ); + + await Promise.all( + Object.entries(avatars).map(async ([key, { url, avatar }]) => { + if (!(locales.default in avatars[key].avatar.name!.locales)) { + console.warn(`Avatar ${key} has no ${locales.default} name`); + return; + } + + const name = `${key}${avatar.extension}`; + + paths.push({ + params: { name }, + props: { url }, + }); + }), + ); + + return paths; +}); + +export async function GET({ props }: APIContext<{ url: string }>) { + if (import.meta.env.DEV) { + return Response.redirect(props.url); + } + + const response = await fetch(props.url); + const buffer = await response.arrayBuffer(); + + return new Response(buffer); +} From ed7dd76ee59443c01df7749910f0c52c609c5cb6 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Wed, 5 Aug 2026 13:04:11 -0500 Subject: [PATCH 056/147] update session --- .../[...locale]/session/_index.tsx | 18 +++++++++--------- .../[...locale]/session/index.astro | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/session/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/session/_index.tsx index 05ca5f1b3..5706a3ace 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/session/_index.tsx +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/session/_index.tsx @@ -33,21 +33,21 @@ import { } from "@radix-ui/themes"; import { debounce } from "lodash-es"; import { useEffect, useMemo, useRef, useState } from "react"; -import { PageWrapper } from "../../../components/PageWrapper"; -import { StickyRowHeaderCell } from "../../../components/StickyRowHeaderCell"; -import { StickyTableRoot } from "../../../components/StickyTableRoot"; -import { TankRowHeaderCell } from "../../../components/TankRowHeaderCell"; -import { awaitableAverageDefinitions } from "../../../core/awaitables/averageDefinitions"; -import { awaitableTankDefinitions } from "../../../core/awaitables/tankDefinitions"; +import { PageWrapper } from "../../../../components/PageWrapper"; +import { StickyRowHeaderCell } from "../../../../components/StickyRowHeaderCell"; +import { StickyTableRoot } from "../../../../components/StickyTableRoot"; +import { TankRowHeaderCell } from "../../../../components/TankRowHeaderCell"; +import { awaitableAverageDefinitions } from "../../../../core/awaitables/averageDefinitions"; +import { api } from "../../../../core/blitzkit/api"; import { type LocaleAcceptorProps, LocaleProvider, useLocale, -} from "../../../hooks/useLocale"; -import { Session, type SessionTracking } from "../../../stores/session"; +} from "../../../../hooks/useLocale"; +import { Session, type SessionTracking } from "../../../../stores/session"; const [tankDefinitions, averageDefinitions] = await Promise.all([ - awaitableTankDefinitions, + api.tankDefinitions(), awaitableAverageDefinitions, ]); diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/session/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/session/index.astro index ff8b3df9f..3c20b8923 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/session/index.astro +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/session/index.astro @@ -1,6 +1,6 @@ --- -import { tools } from "../../../constants/tools"; -import ToolLayout from "../../../layouts/ToolLayout.astro"; +import { tools } from "../../../../constants/tools"; +import ToolLayout from "../../../../layouts/ToolLayout.astro"; import type { getStaticPaths } from "../index.astro"; import { Page } from "./_index"; From d2041d0ee61dec075ca6619ddab2e2e39a73348f Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Wed, 5 Aug 2026 13:05:31 -0500 Subject: [PATCH 057/147] update tier list --- .../src/components/TierList/Row/index.tsx | 16 ++++++++-------- .../website/src/components/TierList/Tiles.tsx | 13 ++++++------- .../src/components/TierList/URLManager.tsx | 16 ++++++++-------- .../[...locale]/tier-list/_index.tsx | 14 +++++++------- .../[...locale]/tier-list/index.astro | 4 ++-- 5 files changed, 31 insertions(+), 32 deletions(-) diff --git a/packages/website/src/components/TierList/Row/index.tsx b/packages/website/src/components/TierList/Row/index.tsx index 17244ee36..107251a93 100644 --- a/packages/website/src/components/TierList/Row/index.tsx +++ b/packages/website/src/components/TierList/Row/index.tsx @@ -1,16 +1,16 @@ -import { Flex, Table } from '@radix-ui/themes'; -import { useEffect, useRef } from 'react'; -import { awaitableTankDefinitions } from '../../../core/awaitables/tankDefinitions'; -import { TierList } from '../../../stores/tierList'; -import { tierListRowElements } from '../Table/constants'; -import { TierListTile } from '../Tile'; -import { Header } from './components/Header'; +import { Flex, Table } from "@radix-ui/themes"; +import { useEffect, useRef } from "react"; +import { api } from "../../../core/blitzkit/api"; +import { TierList } from "../../../stores/tierList"; +import { tierListRowElements } from "../Table/constants"; +import { TierListTile } from "../Tile"; +import { Header } from "./components/Header"; interface TierListRowProps { index: number; } -const tankDefinitions = await awaitableTankDefinitions; +const tankDefinitions = await api.tankDefinitions(); export function TierListRow({ index }: TierListRowProps) { const tanks = TierList.use((state) => state.rows[index].tanks); diff --git a/packages/website/src/components/TierList/Tiles.tsx b/packages/website/src/components/TierList/Tiles.tsx index 45a7d16a8..1dabc0123 100644 --- a/packages/website/src/components/TierList/Tiles.tsx +++ b/packages/website/src/components/TierList/Tiles.tsx @@ -2,8 +2,7 @@ import { metaSortTank } from "@blitzkit/core"; import { times } from "lodash-es"; import { useMemo, useState } from "react"; import usePromise from "react-promise-suspense"; -import { awaitableGameDefinitions } from "../../core/awaitables/gameDefinitions"; -import { awaitableTankDefinitions } from "../../core/awaitables/tankDefinitions"; +import { api } from "../../core/blitzkit/api"; import { filterTanks } from "../../core/blitzkit/filterTanks"; import { TankFilters } from "../../stores/tankFilters"; import { TierList } from "../../stores/tierList"; @@ -12,8 +11,8 @@ import { TankCardWrapper } from "../TankSearch/components/TankCardWrapper"; import { TierListTile } from "./Tile"; const [tankDefinitions, gameDefinitions] = await Promise.all([ - awaitableTankDefinitions, - awaitableGameDefinitions, + api.tankDefinitions(), + api.gameDefinitions(), ]); const tanks = Object.values(tankDefinitions.tanks); @@ -27,14 +26,14 @@ export function TierListTiles() { const filteredTanks = usePromise( () => filterTanks(filters, tanks).then((tanks) => - tanks.filter(({ id }) => !placedTanks.has(id)) + tanks.filter(({ id }) => !placedTanks.has(id)), ), // react-promise-suspense has awful type annotations - [filters, placedTanks] as any + [filters, placedTanks] as any, ); const sorted = useMemo( () => metaSortTank(filteredTanks, gameDefinitions).reverse(), - [filters, placedTanks] + [filters, placedTanks], ); const [loadedTiles, setLoadedTiles] = useState(DEFAULT_LOADED_CARDS); diff --git a/packages/website/src/components/TierList/URLManager.tsx b/packages/website/src/components/TierList/URLManager.tsx index f861b535c..33693b317 100644 --- a/packages/website/src/components/TierList/URLManager.tsx +++ b/packages/website/src/components/TierList/URLManager.tsx @@ -1,9 +1,9 @@ -import { useEffect } from 'react'; -import { awaitableTankDefinitions } from '../../core/awaitables/tankDefinitions'; -import { generateTierListParams } from '../../core/blitzkit/generateTierListParams'; -import { TierList } from '../../stores/tierList'; +import { useEffect } from "react"; +import { api } from "../../core/blitzkit/api"; +import { generateTierListParams } from "../../core/blitzkit/generateTierListParams"; +import { TierList } from "../../stores/tierList"; -const tankDefinitions = await awaitableTankDefinitions; +const tankDefinitions = await api.tankDefinitions(); export function URLManager() { const rows = TierList.use((state) => state.rows); @@ -12,10 +12,10 @@ export function URLManager() { const params = new URLSearchParams(window.location.search); params.forEach((value, key) => { - if (key.startsWith('row-')) { + if (key.startsWith("row-")) { const index = Number.parseInt(key.slice(4), 10); const values = value - .split(',') + .split(",") .map(Number) .filter((id) => id in tankDefinitions.tanks); @@ -27,7 +27,7 @@ export function URLManager() { }, []); useEffect(() => { - window.history.replaceState(null, '', `?${generateTierListParams(rows)}`); + window.history.replaceState(null, "", `?${generateTierListParams(rows)}`); }, [rows]); return null; diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/tier-list/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/tier-list/_index.tsx index 37429bd3c..fb136c43b 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/tier-list/_index.tsx +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/tier-list/_index.tsx @@ -1,13 +1,13 @@ -import { PageWrapper } from "../../../components/PageWrapper"; -import { FilterControl } from "../../../components/TankSearch/components/FilterControl"; -import { TierListControls } from "../../../components/TierList/Controls"; -import { TierListTable } from "../../../components/TierList/Table"; -import { TierListTiles } from "../../../components/TierList/Tiles"; -import { URLManager } from "../../../components/TierList/URLManager"; +import { PageWrapper } from "../../../../components/PageWrapper"; +import { FilterControl } from "../../../../components/TankSearch/components/FilterControl"; +import { TierListControls } from "../../../../components/TierList/Controls"; +import { TierListTable } from "../../../../components/TierList/Table"; +import { TierListTiles } from "../../../../components/TierList/Tiles"; +import { URLManager } from "../../../../components/TierList/URLManager"; import { LocaleProvider, type LocaleAcceptorProps, -} from "../../../hooks/useLocale"; +} from "../../../../hooks/useLocale"; export function Page({ locale }: LocaleAcceptorProps) { return ( diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/tier-list/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/tier-list/index.astro index cc9598883..cd30d4053 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/tier-list/index.astro +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/tier-list/index.astro @@ -1,6 +1,6 @@ --- -import { tools } from "../../../constants/tools"; -import ToolLayout from "../../../layouts/ToolLayout.astro"; +import { tools } from "../../../../constants/tools"; +import ToolLayout from "../../../../layouts/ToolLayout.astro"; import { Page } from "./_index"; export { getStaticPaths } from "../index.astro"; From 645cfacecb489e4fe3a6ad9d9f56697775941ef4 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Wed, 5 Aug 2026 13:13:48 -0500 Subject: [PATCH 058/147] update embed and fix varuna bug --- .../src/components/Embeds/PreviewWrapper.tsx | 23 +++++----- .../src/components/Embeds/breakdown.tsx | 5 ++- packages/website/src/constants/embeds.ts | 45 ++++++++++--------- .../[...locale]/embed/[embed]/_index.tsx | 40 ++++++++--------- .../[...locale]/embed/[embed]/index.astro | 6 +-- .../[...locale]/embed/_index.tsx | 12 ++--- .../[...locale]/embed/index.astro | 4 +- .../src/stores/embedState/utilities.ts | 15 +++++-- 8 files changed, 81 insertions(+), 69 deletions(-) diff --git a/packages/website/src/components/Embeds/PreviewWrapper.tsx b/packages/website/src/components/Embeds/PreviewWrapper.tsx index 1c0f02772..a60486ec5 100644 --- a/packages/website/src/components/Embeds/PreviewWrapper.tsx +++ b/packages/website/src/components/Embeds/PreviewWrapper.tsx @@ -1,7 +1,7 @@ -import { Box } from '@radix-ui/themes'; -import { useEffect, useRef } from 'react'; -import { embedPreviews } from '../../constants/embeds'; -import { useEmbedStateCurry } from '../../stores/embedState/utilities'; +import { Box } from "@radix-ui/themes"; +import { useEffect, useRef } from "react"; +import { embedPreviews } from "../../constants/embeds"; +import { useEmbedStateCurry } from "../../stores/embedState/utilities"; interface PreviewWrapperProps { name: keyof typeof embedPreviews; @@ -10,17 +10,20 @@ interface PreviewWrapperProps { export function PreviewWrapper({ name, naked }: PreviewWrapperProps) { const { useEmbedState } = useEmbedStateCurry(); - const width = useEmbedState('width'); - const height = useEmbedState('height'); + + const width = useEmbedState("width"); + const height = useEmbedState("height"); + const Preview = embedPreviews[name]; + const wrapper = useRef(null); useEffect(() => { if (!wrapper.current) return; - wrapper.current.classList.remove('preview-container-animated'); + wrapper.current.classList.remove("preview-container-animated"); void wrapper.current.offsetWidth; - wrapper.current.classList.add('preview-container-animated'); + wrapper.current.classList.add("preview-container-animated"); }, [width, height]); if (naked) { @@ -29,7 +32,7 @@ export function PreviewWrapper({ name, naked }: PreviewWrapperProps) { position="absolute" top="0" left="50%" - style={{ transform: 'translate(-50%, 0) scale(0.75)' }} + style={{ transform: "translate(-50%, 0) scale(0.75)" }} width={`${width}px`} height={`${height}px`} > @@ -44,7 +47,7 @@ export function PreviewWrapper({ name, naked }: PreviewWrapperProps) { top="50%" left="50%" overflow="hidden" - style={{ transform: naked ? 'scale(0.25)' : 'translate(-50%, -50%)' }} + style={{ transform: naked ? "scale(0.25)" : "translate(-50%, -50%)" }} ref={wrapper} className="preview-container-animated" width={`${width}px`} diff --git a/packages/website/src/components/Embeds/breakdown.tsx b/packages/website/src/components/Embeds/breakdown.tsx index 662762432..2b7340eb4 100644 --- a/packages/website/src/components/Embeds/breakdown.tsx +++ b/packages/website/src/components/Embeds/breakdown.tsx @@ -14,7 +14,7 @@ import { ContextMenu } from "@radix-ui/themes"; import { useEffect, useMemo, useState } from "react"; import { breakdownConfig } from "../../constants/embeds"; import { awaitableAverageDefinitions } from "../../core/awaitables/averageDefinitions"; -import { awaitableTankDefinitions } from "../../core/awaitables/tankDefinitions"; +import { api } from "../../core/blitzkit/api"; import { EmbedBreakdown } from "../../stores/embedBreakdown"; import { useEmbedStateCurry } from "../../stores/embedState/utilities"; import { BreakdownEmbedCard, BreakdownEmbedWrapper } from "../TanksEmbed"; @@ -30,7 +30,8 @@ export const compositeStatsKeysOptions = compositeStatsKeys.map((value) => ({ })); const [tankDefinitions, averageDefinitions] = await Promise.all([ - awaitableTankDefinitions, + api.tankDefinitions(), + awaitableAverageDefinitions, ]); diff --git a/packages/website/src/constants/embeds.ts b/packages/website/src/constants/embeds.ts index 8284acea7..2194bee65 100644 --- a/packages/website/src/constants/embeds.ts +++ b/packages/website/src/constants/embeds.ts @@ -1,16 +1,16 @@ -import { type CompositeStatsKey } from '@blitzkit/core'; -import { grayA, grayDark, grayDarkA } from '@radix-ui/colors'; -import type { ReactNode } from 'react'; +import { type CompositeStatsKey } from "@blitzkit/core"; +import { grayA, grayDark, grayDarkA } from "@radix-ui/colors"; +import type { ReactNode } from "react"; import { BreakdownPreview, BreakdownRenderer, compositeStatsKeysOptions, -} from '../components/Embeds/breakdown'; +} from "../components/Embeds/breakdown"; import type { EmbedConfig, ExtractEmbedConfigTypes, -} from '../stores/embedState'; -import { EmbedItemType } from '../stores/embedState/constants'; +} from "../stores/embedState"; +import { EmbedItemType } from "../stores/embedState/constants"; export const breakdownConfig = { width: { type: EmbedItemType.Slider, default: 320, min: 128, max: 640 }, @@ -18,7 +18,7 @@ export const breakdownConfig = { showTotal: { type: EmbedItemType.Boolean, default: true, pad: true }, - listGap: { type: EmbedItemType.Size, default: '2' }, + listGap: { type: EmbedItemType.Size, default: "2" }, listMaxTanks: { type: EmbedItemType.Slider, default: 4, @@ -27,15 +27,15 @@ export const breakdownConfig = { pad: true, }, - cardRadius: { type: EmbedItemType.Radius, default: '2' }, + cardRadius: { type: EmbedItemType.Radius, default: "2" }, cardHeaderBackgroundColor: { type: EmbedItemType.Color, default: grayDarkA.grayA3, }, - cardHeaderPadding: { type: EmbedItemType.Size, default: '1' }, + cardHeaderPadding: { type: EmbedItemType.Size, default: "1" }, cardTitle: { type: EmbedItemType.RichText, - default: { color: grayDark.gray12, weight: 'bold', size: '3' }, + default: { color: grayDark.gray12, weight: "bold", size: "3" }, }, cardTitleTypeColor: { type: EmbedItemType.Boolean, default: true }, cardTitleClassIcon: { type: EmbedItemType.Boolean, default: true }, @@ -45,45 +45,45 @@ export const breakdownConfig = { }, cardBodyPadding: { type: EmbedItemType.Size, - default: '2', + default: "2", pad: true, }, - columnGap: { type: EmbedItemType.Size, default: '0' }, + columnGap: { type: EmbedItemType.Size, default: "0" }, columnValue: { type: EmbedItemType.RichText, - default: { color: grayDark.gray12, size: '3', weight: 'regular' }, + default: { color: grayDark.gray12, size: "3", weight: "regular" }, }, columnLabel: { type: EmbedItemType.RichText, - default: { color: grayDark.gray11, size: '2', weight: 'regular' }, + default: { color: grayDark.gray11, size: "2", weight: "regular" }, pad: true, }, column1: { type: EmbedItemType.Enum, - default: 'cumulative_battles' satisfies CompositeStatsKey, + default: "cumulative_battles" satisfies CompositeStatsKey, options: compositeStatsKeysOptions, }, column2: { type: EmbedItemType.Enum, - default: 'normalized_wins' satisfies CompositeStatsKey, + default: "normalized_wins" satisfies CompositeStatsKey, options: compositeStatsKeysOptions, }, column3: { type: EmbedItemType.Enum, - default: 'cumulative_wn8' satisfies CompositeStatsKey, + default: "cumulative_wn8" satisfies CompositeStatsKey, options: compositeStatsKeysOptions, }, column4: { type: EmbedItemType.Enum, - default: 'normalized_damage_dealt' satisfies CompositeStatsKey, + default: "normalized_damage_dealt" satisfies CompositeStatsKey, options: compositeStatsKeysOptions, }, - column1CustomLabel: { type: EmbedItemType.String, default: '' }, - column2CustomLabel: { type: EmbedItemType.String, default: '' }, - column3CustomLabel: { type: EmbedItemType.String, default: '' }, - column4CustomLabel: { type: EmbedItemType.String, default: '' }, + column1CustomLabel: { type: EmbedItemType.String, default: "" }, + column2CustomLabel: { type: EmbedItemType.String, default: "" }, + column3CustomLabel: { type: EmbedItemType.String, default: "" }, + column4CustomLabel: { type: EmbedItemType.String, default: "" }, } satisfies EmbedConfig; export const embedConfigurations = { @@ -93,6 +93,7 @@ export const embedConfigurations = { export const embedPreviews = { breakdown: BreakdownPreview, } satisfies Record ReactNode>; + export function extractEmbedConfigDefaults( config: Config, ) { diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/embed/[embed]/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/embed/[embed]/_index.tsx index d1f6ca7c7..416514749 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/embed/[embed]/_index.tsx +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/embed/[embed]/_index.tsx @@ -8,32 +8,32 @@ import { } from "@radix-ui/react-icons"; import { Box, Button, Flex, Heading, ScrollArea, Text } from "@radix-ui/themes"; import { useEffect, useRef, useState, type ReactNode } from "react"; -import { CopyButton } from "../../../../components/CopyButton"; -import { Boolean } from "../../../../components/Embeds/Boolean"; -import { Color } from "../../../../components/Embeds/Color"; -import { Enum } from "../../../../components/Embeds/Enum"; -import { GenerateURL } from "../../../../components/Embeds/GenerateURL"; -import { Import } from "../../../../components/Embeds/Import"; -import { PreviewWrapper } from "../../../../components/Embeds/PreviewWrapper"; -import { Radius } from "../../../../components/Embeds/Radius"; -import { RichText } from "../../../../components/Embeds/RichText"; -import { Size } from "../../../../components/Embeds/Size"; -import { SizeWithout0 } from "../../../../components/Embeds/SizeWithout0"; -import { Slider } from "../../../../components/Embeds/Slider"; -import { TextController } from "../../../../components/Embeds/TextController"; -import { PageWrapper } from "../../../../components/PageWrapper"; +import { CopyButton } from "../../../../../components/CopyButton"; +import { Boolean } from "../../../../../components/Embeds/Boolean"; +import { Color } from "../../../../../components/Embeds/Color"; +import { Enum } from "../../../../../components/Embeds/Enum"; +import { GenerateURL } from "../../../../../components/Embeds/GenerateURL"; +import { Import } from "../../../../../components/Embeds/Import"; +import { PreviewWrapper } from "../../../../../components/Embeds/PreviewWrapper"; +import { Radius } from "../../../../../components/Embeds/Radius"; +import { RichText } from "../../../../../components/Embeds/RichText"; +import { Size } from "../../../../../components/Embeds/Size"; +import { SizeWithout0 } from "../../../../../components/Embeds/SizeWithout0"; +import { Slider } from "../../../../../components/Embeds/Slider"; +import { TextController } from "../../../../../components/Embeds/TextController"; +import { PageWrapper } from "../../../../../components/PageWrapper"; import { embedConfigurations, extractEmbedConfigDefaults, -} from "../../../../constants/embeds"; -import { NAVBAR_HEIGHT } from "../../../../constants/navbar"; +} from "../../../../../constants/embeds"; +import { NAVBAR_HEIGHT } from "../../../../../constants/navbar"; import { LocaleProvider, useLocale, type LocaleAcceptorProps, -} from "../../../../hooks/useLocale"; -import { EmbedState, type EmbedConfig } from "../../../../stores/embedState"; -import { EmbedItemType } from "../../../../stores/embedState/constants"; +} from "../../../../../hooks/useLocale"; +import { EmbedState, type EmbedConfig } from "../../../../../stores/embedState"; +import { EmbedItemType } from "../../../../../stores/embedState/constants"; export interface EmbedPreviewControllerProps { configKey: string; @@ -58,7 +58,7 @@ export function Page({ embed, locale }: PageProps & LocaleAcceptorProps) { function Content({ embed }: PageProps) { const config = embedConfigurations[embed] as EmbedConfig; const [backgroundImage, setBackgroundImage] = useState( - "/assets/images/backgrounds/embed-default.webp" + "/assets/images/backgrounds/embed-default.webp", ); const fileInput = useRef(null!); const { strings } = useLocale(); diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/embed/[embed]/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/embed/[embed]/index.astro index 85c9e5c26..d53acc9bf 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/embed/[embed]/index.astro +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/embed/[embed]/index.astro @@ -2,8 +2,8 @@ import { AstroSeo } from "@astrolib/seo"; import type { GetStaticPaths } from "astro"; import { capitalize } from "lodash-es"; -import { embedConfigurations } from "../../../../constants/embeds"; -import BaseLayout from "../../../../layouts/BaseLayout.astro"; +import { embedConfigurations } from "../../../../../constants/embeds"; +import BaseLayout from "../../../../../layouts/BaseLayout.astro"; import { getStaticPaths as parentGetStaticPaths } from "../index.astro"; import { Page } from "./_index"; @@ -17,7 +17,7 @@ export const getStaticPaths = (() => { ...parent.params, embed: embed as keyof typeof embedConfigurations, }, - })) + })), ) .flat(); }) satisfies GetStaticPaths; diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/embed/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/embed/_index.tsx index 73f504434..54a307810 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/embed/_index.tsx +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/embed/_index.tsx @@ -1,14 +1,14 @@ import { Box, Flex, Link, Text } from "@radix-ui/themes"; -import { PreviewWrapper } from "../../../components/Embeds/PreviewWrapper"; -import { LinkI18n } from "../../../components/LinkI18n"; -import { PageWrapper } from "../../../components/PageWrapper"; -import { embedConfigurations } from "../../../constants/embeds"; -import { Var } from "../../../core/radix/var"; +import { PreviewWrapper } from "../../../../components/Embeds/PreviewWrapper"; +import { LinkI18n } from "../../../../components/LinkI18n"; +import { PageWrapper } from "../../../../components/PageWrapper"; +import { embedConfigurations } from "../../../../constants/embeds"; +import { Var } from "../../../../core/radix/var"; import { type LocaleAcceptorProps, LocaleProvider, useLocale, -} from "../../../hooks/useLocale"; +} from "../../../../hooks/useLocale"; export function Page({ locale }: LocaleAcceptorProps) { return ( diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/embed/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/embed/index.astro index 13ff94238..674665781 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/embed/index.astro +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/embed/index.astro @@ -1,6 +1,6 @@ --- -import { tools } from "../../../constants/tools"; -import ToolLayout from "../../../layouts/ToolLayout.astro"; +import { tools } from "../../../../constants/tools"; +import ToolLayout from "../../../../layouts/ToolLayout.astro"; import { Page } from "./_index"; export { getStaticPaths } from "../index.astro"; diff --git a/packages/website/src/stores/embedState/utilities.ts b/packages/website/src/stores/embedState/utilities.ts index adb92cb80..1889e8819 100644 --- a/packages/website/src/stores/embedState/utilities.ts +++ b/packages/website/src/stores/embedState/utilities.ts @@ -1,8 +1,15 @@ -import type { TextProps } from '@radix-ui/themes'; -import { EmbedState, type EmbedConfig, type ExtractEmbedConfigTypes } from '.'; -import type { EmbedConfigItemType, EmbedItemType } from './constants'; +import type { TextProps } from "@radix-ui/themes"; +import { EmbedState, type EmbedConfig, type ExtractEmbedConfigTypes } from "."; +import { + breakdownConfig, + extractEmbedConfigDefaults, +} from "../../constants/embeds"; +import type { EmbedConfigItemType, EmbedItemType } from "./constants"; export function useEmbedStateCurry() { + // TODO: find a way to initialize with the correct state preset + EmbedState.useInitialization(extractEmbedConfigDefaults(breakdownConfig)); + function useEmbedState(key: Key) { return EmbedState.use((state) => { return (state as ExtractEmbedConfigTypes)[key]; @@ -18,7 +25,7 @@ export function useEmbedStateCurry() { >(key: Key) { const { color, ...state } = useEmbedState( key, - ) as EmbedConfigItemType['default']; + ) as EmbedConfigItemType["default"]; return { style: { color }, From dd769a9aa5296a0385b7ef3c9169651bfebbfa05 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Wed, 5 Aug 2026 13:15:47 -0500 Subject: [PATCH 059/147] update settings and docs --- .../[...locale]/docs/[directory]/[document]/_index.tsx | 4 ++-- .../docs/[directory]/[document]/index.astro | 10 +++++----- .../[...chunk_default]/[...locale]/settings/_index.tsx | 8 ++++---- .../[...locale]/settings/index.astro | 4 ++-- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/docs/[directory]/[document]/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/docs/[directory]/[document]/_index.tsx index 5c1415000..77ecbaa6c 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/docs/[directory]/[document]/_index.tsx +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/docs/[directory]/[document]/_index.tsx @@ -1,5 +1,5 @@ -import { Markdown } from "../../../../../components/Markdown"; -import { PageWrapper } from "../../../../../components/PageWrapper"; +import { Markdown } from "../../../../../../components/Markdown"; +import { PageWrapper } from "../../../../../../components/PageWrapper"; interface PageProps { content: string; diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/docs/[directory]/[document]/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/docs/[directory]/[document]/index.astro index d3811142b..9858033b6 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/docs/[directory]/[document]/index.astro +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/docs/[directory]/[document]/index.astro @@ -2,14 +2,14 @@ import { AstroSeo } from "@astrolib/seo"; import type { GetStaticPaths } from "astro"; import { readFile } from "fs/promises"; -import BaseLayout from "../../../../../layouts/BaseLayout.astro"; +import BaseLayout from "../../../../../../layouts/BaseLayout.astro"; import { getStaticPaths as parentGetStaticPaths } from "../../../index.astro"; import { Page } from "./_index"; export const getStaticPaths = (async () => { const parentPaths = parentGetStaticPaths(); - const ROOT = "../../../../../../../../docs/"; - const files = import.meta.glob("../../../../../../../../docs/**/*.md", { + const ROOT = "../../../../../../../../../docs/"; + const files = import.meta.glob("../../../../../../../../../docs/**/*.md", { eager: true, }); @@ -19,7 +19,7 @@ export const getStaticPaths = (async () => { const couple = file.replace(ROOT, "").split("/"); couple[1] = couple[1].replace(".md", ""); const content = await readFile( - `../../docs/${couple[0]}/${couple[1]}.md` + `../../docs/${couple[0]}/${couple[1]}.md`, ).then((buffer) => buffer.toString()); return parentPaths.map((parent) => ({ @@ -30,7 +30,7 @@ export const getStaticPaths = (async () => { }, props: { ...parent.props, content }, })); - }) + }), ) ).flat(); }) satisfies GetStaticPaths; diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/settings/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/settings/_index.tsx index 2279cf3af..c5054b676 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/settings/_index.tsx +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/settings/_index.tsx @@ -1,12 +1,12 @@ import { Flex, Switch, Text } from "@radix-ui/themes"; -import { LocaleSwitcher } from "../../../components/LocaleSwitcher"; -import { PageWrapper } from "../../../components/PageWrapper"; +import { LocaleSwitcher } from "../../../../components/LocaleSwitcher"; +import { PageWrapper } from "../../../../components/PageWrapper"; import { LocaleProvider, useLocale, type LocaleAcceptorProps, -} from "../../../hooks/useLocale"; -import { App } from "../../../stores/app"; +} from "../../../../hooks/useLocale"; +import { App } from "../../../../stores/app"; export function Page({ locale }: LocaleAcceptorProps) { return ( diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/settings/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/settings/index.astro index 48671bfdb..f82a650b5 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/settings/index.astro +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/settings/index.astro @@ -1,7 +1,7 @@ --- import { AstroSeo } from "@astrolib/seo"; -import { getStrings } from "../../../core/i18n/getStrings"; -import BaseLayout from "../../../layouts/BaseLayout.astro"; +import { getStrings } from "../../../../core/i18n/getStrings"; +import BaseLayout from "../../../../layouts/BaseLayout.astro"; import { Page } from "./_index"; export { getStaticPaths } from "../index.astro"; From c7ee3dbf7e9d7a4dddfc75fa269441b9efb349f8 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Wed, 5 Aug 2026 13:16:32 -0500 Subject: [PATCH 060/147] update changelogs --- .../[...locale]/docs/patches/index.astro | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/docs/patches/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/docs/patches/index.astro index 5fd4da06b..43631f651 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/docs/patches/index.astro +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/docs/patches/index.astro @@ -3,21 +3,21 @@ import { AstroSeo } from "@astrolib/seo"; import { MagicWandIcon, StarFilledIcon } from "@radix-ui/react-icons"; import { Flex, Heading, Link } from "@radix-ui/themes"; import { compareVersions } from "compare-versions"; -import { PageWrapper } from "../../../../components/PageWrapper"; -import { getStrings } from "../../../../core/i18n/getStrings"; -import BaseLayout from "../../../../layouts/BaseLayout.astro"; +import { PageWrapper } from "../../../../../components/PageWrapper"; +import { getStrings } from "../../../../../core/i18n/getStrings"; +import BaseLayout from "../../../../../layouts/BaseLayout.astro"; export { getStaticPaths } from "../../index.astro"; // this can't be used in template literal :( -const ROOT = "../../../../../../../docs/changelogs/"; +const ROOT = "../../../../../../../../docs/changelogs/"; const { locale } = Astro.props; const strings = getStrings(locale); const versions = Object.keys( - import.meta.glob("../../../../../../../docs/changelogs/*.md", { + import.meta.glob("../../../../../../../../docs/changelogs/*.md", { eager: true, - }) + }), ) .map((file) => file.replace(ROOT, "").replace(".md", "")) .sort(compareVersions) From 24297dcae877ff615134f08b676bbd92db68a4fe Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Wed, 5 Aug 2026 13:18:27 -0500 Subject: [PATCH 061/147] remove historic averages --- .github/workflows/averages.yml | 1 + package.json | 3 ++- packages/scripts/src/averages.ts | 21 +-------------------- 3 files changed, 4 insertions(+), 21 deletions(-) diff --git a/.github/workflows/averages.yml b/.github/workflows/averages.yml index 167edc311..0d72fc263 100644 --- a/.github/workflows/averages.yml +++ b/.github/workflows/averages.yml @@ -51,4 +51,5 @@ jobs: DATABASE_URL: ${{ secrets.DATABASE_URL }} VIDEOS_SHEET: ${{ secrets.VIDEOS_SHEET }} GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }} + run: bun run build:averages --production diff --git a/package.json b/package.json index 7205267e1..4d5cfbcd0 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "license": "BlitzKit License", "private": true, "scripts": { - "dev": "cd packages/website && bun run dev" + "dev": "cd packages/website && bun run dev", + "build:averages": "cd packages/scripts && bun run build:averages" }, "type": "module", "workspaces": [ diff --git a/packages/scripts/src/averages.ts b/packages/scripts/src/averages.ts index 5429544fc..4f2ae28ca 100644 --- a/packages/scripts/src/averages.ts +++ b/packages/scripts/src/averages.ts @@ -2,7 +2,6 @@ import { AverageDefinitions, AverageDefinitionsAllStats, AverageDefinitionsEntry, - type AverageDefinitionsManifest, type IndividualTankStats, REGIONS, type Region, @@ -23,14 +22,6 @@ interface DataPoint { w: number; } -type OptionalSecondLevel = { - [P in keyof T]: P extends "samples" - ? T[P] - : { - [K in keyof T[P]]?: T[P][K]; - }; -}; - const MINUTE = 60 * 1000; const HOUR = 60 * MINUTE; const DAY = 24 * HOUR; @@ -223,26 +214,16 @@ async function postWork() { }); const time = Date.now(); - const latest = Math.round(time / DAY); const averageDefinitions = { averages, samples, time, } satisfies AverageDefinitions; - const manifest: AverageDefinitionsManifest = { - version: 1, - latest, - }; - await uploader.add({ - path: `averages/${latest}.pb`, + path: "averages.pb", content: AverageDefinitions.encode(averageDefinitions).finish(), }); - await uploader.add({ - path: "averages/manifest.json", - content: new TextEncoder().encode(JSON.stringify(manifest)), - }); await uploader.flush(); } From 84290b1c0e70fed9d72cf06cce977cb38d97bcff Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Wed, 5 Aug 2026 13:19:27 -0500 Subject: [PATCH 062/147] remove assets --- .github/workflows/assets.yml | 83 ----------------- packages/scripts/src/buildAssets/constants.ts | 43 --------- .../scripts/src/buildAssets/equipmentIcons.ts | 73 --------------- packages/scripts/src/buildAssets/glossary.ts | 89 ------------------- packages/scripts/src/buildAssets/index.ts | 64 ------------- packages/scripts/src/buildAssets/migration.ts | 34 ------- .../scripts/src/buildAssets/popularTanks.ts | 77 ---------------- packages/scripts/src/buildAssets/tankIcons.ts | 64 ------------- .../scripts/src/buildAssets/tankModels.ts | 44 --------- 9 files changed, 571 deletions(-) delete mode 100644 .github/workflows/assets.yml delete mode 100644 packages/scripts/src/buildAssets/constants.ts delete mode 100644 packages/scripts/src/buildAssets/equipmentIcons.ts delete mode 100644 packages/scripts/src/buildAssets/glossary.ts delete mode 100644 packages/scripts/src/buildAssets/index.ts delete mode 100644 packages/scripts/src/buildAssets/migration.ts delete mode 100644 packages/scripts/src/buildAssets/popularTanks.ts delete mode 100644 packages/scripts/src/buildAssets/tankIcons.ts delete mode 100644 packages/scripts/src/buildAssets/tankModels.ts diff --git a/.github/workflows/assets.yml b/.github/workflows/assets.yml deleted file mode 100644 index e9280a7a9..000000000 --- a/.github/workflows/assets.yml +++ /dev/null @@ -1,83 +0,0 @@ -name: Assets - -on: - push: - branches: production - - workflow_dispatch: - inputs: - asset_repo: - type: choice - required: false - description: What asset repo to write to - options: - - blitzkit/assets - - blitzkit/assets-preview - - blitzkit/assets-dev - default: blitzkit/assets - - asset_source: - type: string - required: false - description: "Archive URL or Steam to download (example: steam:APP_ID/DEPOT_ID or zip:URL_TO_ZIP)" - default: "steam:444200/444202" - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.inputs.asset_repo }} - cancel-in-progress: true - -env: - DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }} - DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }} - DATABASE_URL: ${{ secrets.DATABASE_URL }} - VIDEOS_SHEET: ${{ secrets.VIDEOS_SHEET }} - GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }} - WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }} - WOTB_DLC_CDN: ${{ secrets.WOTB_DLC_CDN }} - PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }} - PUBLIC_ASSET_REPO: ${{ github.event.inputs.asset_repo != '' && github.event.inputs.asset_repo || secrets.PUBLIC_ASSET_REPO }} - PUBLIC_ASSET_BRANCH: ${{ secrets.PUBLIC_ASSET_BRANCH }} - WOTB_GLOSSARY_DOMAIN: ${{ secrets.WOTB_GLOSSARY_DOMAIN }} - WOTB_GLOSSARY_PATH: ${{ secrets.WOTB_GLOSSARY_PATH }} - PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }} - ALLOW_ROBOTS: true - PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }} - PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }} - PUBLIC_PATREON_CLIENT_ID: ${{ secrets.PUBLIC_PATREON_CLIENT_ID }} - PUBLIC_PATREON_REDIRECT_URI: ${{ secrets.PUBLIC_PATREON_REDIRECT_URI }} - PATREON_CLIENT_SECRET: ${{ secrets.PATREON_CLIENT_SECRET }} - PUBLIC_NITROPAY_ID: ${{ secrets.PUBLIC_NITROPAY_ID }} - PUBLIC_DEBUG_MISSING_I18N: false - PUBLIC_DISCORD_SERVER_ID: ${{ secrets.PUBLIC_DISCORD_SERVER_ID }} - PUBLIC_ASSET_FAST_USER: blitzkit - PUBLIC_ASSET_FAST_REPO: assets - PUBLIC_ASSET_BASE: ${{ secrets.PUBLIC_ASSET_BASE }} - GH_TOKEN: ${{ secrets.GH_TOKEN }} - STEAM_USERNAME: ${{ secrets.STEAM_USERNAME }} - STEAM_PASSWORD: ${{ secrets.STEAM_PASSWORD }} - -jobs: - build_assets: - name: Build assets - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v6 - with: - submodules: recursive - token: ${{ secrets.GH_TOKEN }} - - uses: arduino/setup-protoc@v3 - with: - repo-token: ${{ secrets.GH_TOKEN }} - - uses: oven-sh/setup-bun@v2 - - - run: bun run setup - - - name: AppArmor hack - run: echo 0 | sudo tee /proc/sys/kernel/apparmor_restrict_unprivileged_userns - - - name: Install patches - run: bun run setup:patches --source=${{ github.event.inputs.asset_source || 'steam:444200/444202' }} - - - name: Build assets - run: bun run build:assets --all-targets --source=${{ github.event.inputs.asset_source || 'steam:444200/444202' }} diff --git a/packages/scripts/src/buildAssets/constants.ts b/packages/scripts/src/buildAssets/constants.ts deleted file mode 100644 index 3e5cf4720..000000000 --- a/packages/scripts/src/buildAssets/constants.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { assertSecret } from "@blitzkit/core"; -import { AbstractVFS } from "@blitzkit/core/src/blitzkit/vfs/abstract"; -import { LocalVFS } from "@blitzkit/core/src/blitzkit/vfs/local"; -import { MixedVFS } from "@blitzkit/core/src/blitzkit/vfs/mixed"; -import { SteamVFS } from "@blitzkit/core/src/blitzkit/vfs/steam"; -import { ZipVFS } from "@blitzkit/core/src/blitzkit/vfs/zip"; - -export const PATCHES_ROOT = "../../temp/patches"; - -const steamPattern = /steam:(\d+)\/(\d+)/; -const zipPattern = /zip:(.+)/; -const localePattern = /local:"?(.+)"?/; - -const source = Bun.argv - .find((arg) => arg.startsWith("--source=")) - ?.split("=")[1]; - -let resolved: AbstractVFS; - -if (!source) { - throw new Error("No source specified"); -} else if (steamPattern.test(source)) { - const [, app, depot] = source.match(steamPattern)!.map(Number); - - resolved = await new SteamVFS( - assertSecret(import.meta.env.STEAM_USERNAME), - assertSecret(import.meta.env.STEAM_PASSWORD), - app, - depot, - ).init(); -} else if (zipPattern.test(source)) { - const [, url] = source.match(zipPattern)!; - resolved = await new ZipVFS(url).init(); -} else if (localePattern.test(source)) { - const [, path] = source.match(localePattern)!; - resolved = await new LocalVFS(path).init(); -} else { - throw new Error("No valid source specified"); -} - -resolved = new MixedVFS([new LocalVFS(PATCHES_ROOT), resolved]); - -export const vfs = resolved; diff --git a/packages/scripts/src/buildAssets/equipmentIcons.ts b/packages/scripts/src/buildAssets/equipmentIcons.ts deleted file mode 100644 index 280d9ec87..000000000 --- a/packages/scripts/src/buildAssets/equipmentIcons.ts +++ /dev/null @@ -1,73 +0,0 @@ -import sharp from "sharp"; -import { AssetUploader } from "../core/github/assetUploader"; -import { vfs } from "./constants"; -import { OptionalDevices } from "./definitions"; - - - -export async function equipmentIcons() { - console.log("Building equipment icons..."); - - using uploader = new AssetUploader("equipment icons"); - const optionalDevices = await vfs.xml<{ root: OptionalDevices }>( - `Data/XML/item_defs/vehicles/common/optional_devices.xml`, - ); - const mappings = await vfs.yaml( - `Data/UI/Screens3/Lobby/Inventory/OptionalDevices/OptionalDevicesItemImage.style.yaml`, - ); - const image = sharp( - await vfs.file( - `Data/Gfx/UI/InventoryIcons/Big/OptionalDevices/texture0.packed.webp`, - ), - ); - - for (const optionalDeviceKey in optionalDevices.root) { - const optionalDevice = optionalDevices.root[optionalDeviceKey]; - - if (optionalDeviceKey === "nextAvailableId") continue; - - const mapping = mappings.StyleSheets.find((mapping) => - mapping.selectors.includes( - `.optional_device_item.${optionalDevice.icon} #Img`, - ), - ); - - if (!mapping) { - console.warn(`No mapping found for ${optionalDevice.icon}; skipping...`); - continue; - } - - const configPathRaw = mapping.properties["bg-sprite"]; - const configPath = configPathRaw.replace("~res:/", ""); - - if (configPath.startsWith("Gfx/Lobby")) { - const configPathWebp = configPath.replace(".txt", ""); - const image = sharp(await vfs.file(`Data/${configPathWebp}.packed.webp`)); - const content = await image.trim().toBuffer(); - - await uploader.add({ - path: `icons/equipment/${optionalDevice.id}.webp`, - content, - }); - } else { - const config = await vfs.text(`Data/${configPath}`); - const sizes = config.split("\n")[4].split(" ").map(Number); - const content = await image - .clone() - .extract({ - left: sizes[0], - top: sizes[1], - width: sizes[2], - height: sizes[3], - }) - .toBuffer(); - - await uploader.add({ - path: `icons/equipment/${optionalDevice.id}.webp`, - content, - }); - } - } - - await uploader.flush(); -} diff --git a/packages/scripts/src/buildAssets/glossary.ts b/packages/scripts/src/buildAssets/glossary.ts deleted file mode 100644 index ed3b83cfd..000000000 --- a/packages/scripts/src/buildAssets/glossary.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { Avatar, fetchGlossary, Gallery } from "@blitzkit/core"; -import locales from "@blitzkit/i18n/locales.json"; -import { extname } from "path"; -import ProgressBar from "progress"; -import { AssetUploader } from "../core/github/assetUploader"; - -export async function glossary() { - console.log("Building glossary..."); - - using uploader = new AssetUploader("glossary"); - const avatars: Record = {}; - - await Promise.all( - locales.supported.map(async (supported) => { - const glossary = await fetchGlossary(supported.blitz ?? supported.locale); - - console.log( - `Found ${Object.keys(glossary).length} things for ${supported.locale}`, - ); - - for (const key in glossary) { - const glossaryEntry = glossary[key]; - - if ( - !key.startsWith("avatar") || - key.endsWith("_part") || - glossaryEntry.image_url === null - ) { - continue; - } - - if (key in avatars) { - avatars[key].avatar.name!.locales[supported.locale] = - glossaryEntry.title; - } else { - const extension = extname(glossaryEntry.image_url); - - avatars[key] = { - url: glossaryEntry.image_url, - avatar: { - id: key, - name: { locales: { [supported.locale]: glossaryEntry.title } }, - extension, - }, - }; - } - } - }), - ); - - const gallery: Gallery = { avatars: [] }; - const totalCount = Object.keys(avatars).length; - - const bar = new ProgressBar( - `Fetching avatars of ${totalCount} things :bar`, - totalCount, - ); - - await Promise.all( - Object.entries(avatars).map(async ([key, { url, avatar }]) => { - if (!(locales.default in avatars[key].avatar.name!.locales)) { - console.warn(`Avatar ${key} has no ${locales.default} name`); - bar.tick(); - - return; - } - - gallery.avatars.push(avatar); - - const content = await fetch(url) - .then((response) => response.arrayBuffer()) - .then((buffer) => new Uint8Array(buffer)); - - await uploader.add({ - path: `gallery/avatars/${key}${avatar.extension}`, - content, - }); - - bar.tick(); - }), - ); - - await uploader.add({ - path: "definitions/gallery.pb", - content: Gallery.encode(gallery).finish(), - }); - - await uploader.flush(); -} diff --git a/packages/scripts/src/buildAssets/index.ts b/packages/scripts/src/buildAssets/index.ts deleted file mode 100644 index 495765e0c..000000000 --- a/packages/scripts/src/buildAssets/index.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { config } from "dotenv"; -import { argv } from "process"; -import { boosterIcons } from "./boosterIcons"; -import { camouflageIcons } from "./camouflageIcons"; -import { vfs } from "./constants"; -import { consumableProvisionIcons } from "./consumableProvisionIcons"; -import { currencies } from "./currencies"; -import { definitions } from "./definitions"; -import { equipmentIcons } from "./equipmentIcons"; -import { flags } from "./flags"; -import { gameModeBanners } from "./gameModeBanners"; -import { glossary } from "./glossary"; -import { migration } from "./migration"; -import { moduleIcons } from "./moduleIcons"; -import { popularTanks } from "./popularTanks"; -import { shellIcons } from "./shellIcons"; -import { skillIcons } from "./skillIcons"; -import { tankArmors } from "./tankArmors"; -import { tankIcons } from "./tankIcons"; -import { tankModels } from "./tankModels"; - -config(); - -const allTargets = argv.includes("--all-targets"); -const targets = argv - .find((argument) => argument.startsWith("--target")) - ?.split("=")[1] - .split(","); - -if (!targets && !allTargets) throw new Error("No target(s) specified"); - -const methods = [ - definitions, - tankIcons, - tankModels, - tankArmors, - gameModeBanners, - currencies, - skillIcons, - flags, - shellIcons, - moduleIcons, - equipmentIcons, - consumableProvisionIcons, - camouflageIcons, - boosterIcons, - glossary, - migration, - popularTanks, -]; - -for (const method of methods) { - if (allTargets || targets?.includes(method.name)) { - try { - await method(); - } catch (error) { - console.warn(`Failed method ${method.name}; skipping...`); - console.error(error); - } - } -} - -vfs.dispose(); -process.exit(0); diff --git a/packages/scripts/src/buildAssets/migration.ts b/packages/scripts/src/buildAssets/migration.ts deleted file mode 100644 index ca806f961..000000000 --- a/packages/scripts/src/buildAssets/migration.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { toUniqueId } from "@blitzkit/core"; -import { AssetUploader } from "../core/github/assetUploader"; -import { vfs } from "./constants"; -import { VehicleDefinitionList } from "./definitions"; - -export async function migration() { - console.log("Building migration map..."); - - using uploader = new AssetUploader("migration map"); - const map: Record = {}; - const nations = await vfs - .dir(`Data/XML/item_defs/vehicles`) - .then((files) => files.filter((nation) => nation !== "common")); - - for (const nation of nations) { - const tankList = await vfs.xml<{ root: VehicleDefinitionList }>( - `Data/XML/item_defs/vehicles/${nation}/list.xml`, - ); - - for (const tankKey in tankList.root) { - const tank = tankList.root[tankKey]; - const tankId = toUniqueId(nation, tank.id); - const davaKey = `${nation}:${tankKey}`; - - map[davaKey] = tankId; - } - } - - await uploader.add({ - path: "definitions/migration.json", - content: new TextEncoder().encode(JSON.stringify(map)), - }); - await uploader.flush(); -} diff --git a/packages/scripts/src/buildAssets/popularTanks.ts b/packages/scripts/src/buildAssets/popularTanks.ts deleted file mode 100644 index 9d281d4d2..000000000 --- a/packages/scripts/src/buildAssets/popularTanks.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { - assertSecret, - fetchTankDefinitions, - PopularTanks, -} from "@blitzkit/core"; -import { google } from "googleapis"; -import { AssetUploader } from "../core/github/assetUploader"; - -export async function popularTanks() { - using uploader = new AssetUploader("popular tanks"); - const tankDefinitions = await fetchTankDefinitions(); - - const auth = await google.auth.getClient({ - keyFile: import.meta.env.GOOGLE_APPLICATION_CREDENTIALS, - scopes: ["https://www.googleapis.com/auth/analytics.readonly"], - }); - const analytics = google.analyticsdata({ version: "v1beta", auth }); - const report = await analytics.properties.runReport({ - property: `properties/${assertSecret( - import.meta.env.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID, - )}`, - requestBody: { - dateRanges: [{ startDate: "7daysAgo", endDate: "today" }], - dimensions: [{ name: "pagePath" }], - metrics: [{ name: "screenPageViews" }], - orderBys: [{ metric: { metricName: "screenPageViews" }, desc: true }], - dimensionFilter: { - filter: { - fieldName: "pagePath", - stringFilter: { - matchType: "BEGINS_WITH", - value: "/tanks/", - }, - }, - }, - }, - }); - - if (!report.data.rows) { - throw new Error("No rows in report"); - } - - const tanks = report.data.rows - .filter( - (row) => - row.dimensionValues && - row.dimensionValues[0].value && - row.dimensionValues[0].value !== "/tanks/" && - row.metricValues && - row.metricValues[0].value, - ) - .map((row) => ({ - id: Number( - row.dimensionValues![0].value!.match( - /\/tools\/tankopedia\/(\d+)\/?/, - )?.[1], - ), - views: Number(row.metricValues![0].value!), - })) - .filter((row) => { - if (!(row.id in tankDefinitions.tanks)) { - console.warn("Skipping unknown popular tank", row.id); - return false; - } - - return row.id in tankDefinitions.tanks; - }) - .slice(0, 8) - .map(({ id, views }) => ({ id, views })); - const popularTanks: PopularTanks = { tanks }; - - await uploader.add({ - path: "definitions/popular-tanks.pb", - content: PopularTanks.encode(popularTanks).finish(), - }); - await uploader.flush(); -} diff --git a/packages/scripts/src/buildAssets/tankIcons.ts b/packages/scripts/src/buildAssets/tankIcons.ts deleted file mode 100644 index 1534e800b..000000000 --- a/packages/scripts/src/buildAssets/tankIcons.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { NATION_IDS } from "@blitzkit/core"; -import sharp from "sharp"; -import { parsePackedSpriteRect } from "../core/blitz/parsePackedSpriteRect"; -import { AssetUploader } from "../core/github/assetUploader"; -import { vfs } from "./constants"; -import type { VehicleDefinitionList } from "./definitions"; - - -export async function tankIcons() { - console.log("Building tank icons..."); - - using uploader = new AssetUploader("tank icons"); - const nations = await vfs - .dir(`Data/XML/item_defs/vehicles`) - .then((files) => files.filter((nation) => nation !== "common")); - - for (const nation of nations) { - const tanks = await vfs.xml<{ root: VehicleDefinitionList }>( - `Data/XML/item_defs/vehicles/${nation}/list.xml`, - ); - - for (const tankKey in tanks.root) { - const tank = tanks.root[tankKey]; - - if (tankKey.includes("tutorial_bot")) continue; - - const nationVehicleId = tank.id; - const id = (nationVehicleId << 8) + (NATION_IDS[nation] << 4) + 1; - - const parameters = await vfs.yaml( - `Data/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`, - ); - const smallPath = `Data/${parameters.resourcesPath.smallIconPath - .replace(/~res:\//, "") - .replace(/\..+/, "")}.packed.webp`; - const bigPath = `Data/${parameters.resourcesPath.bigIconPath - .replace(/~res:\//, "") - .replace(/\..+/, "")}.packed.webp`; - const big = await extractPackedTankIcon( - await vfs.file(bigPath), - `tank ${tankKey} big`, - ); - const small = await extractPackedTankIcon( - await vfs.file(smallPath), - `tank ${tankKey} small`, - ); - - if (big) { - await uploader.add({ - content: big, - path: `icons/tanks/big/${id}.webp`, - }); - } - if (small) { - await uploader.add({ - content: small, - path: `icons/tanks/small/${id}.webp`, - }); - } - } - } - - await uploader.flush(); -} diff --git a/packages/scripts/src/buildAssets/tankModels.ts b/packages/scripts/src/buildAssets/tankModels.ts deleted file mode 100644 index e0f609ef7..000000000 --- a/packages/scripts/src/buildAssets/tankModels.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { toUniqueId } from "@blitzkit/core"; -import { NodeIO } from "@gltf-transform/core"; -import { ALL_EXTENSIONS } from "@gltf-transform/extensions"; -import { extractModel } from "../core/blitz/extractModel"; -import { AssetUploader } from "../core/github/assetUploader"; -import { vfs } from "./constants"; -import { VehicleDefinitionList } from "./definitions"; -import { TankParameters } from "./tankIcons"; - -export async function tankModels() { - console.log("Building tank models..."); - - using uploader = new AssetUploader("tank models"); - const nodeIO = new NodeIO().registerExtensions(ALL_EXTENSIONS); - const nations = await vfs - .dir(`Data/XML/item_defs/vehicles`) - .then((files) => files.filter((nation) => nation !== "common")); - - for (const nationIndex in nations) { - const nation = nations[nationIndex]; - const tanks = await vfs.xml<{ root: VehicleDefinitionList }>( - `Data/XML/item_defs/vehicles/${nation}/list.xml`, - ); - const entries = Object.entries(tanks.root); - - for (const [tankKey, tank] of entries) { - if (tankKey.includes("tutorial_bot")) continue; - - const id = toUniqueId(nation, tank.id); - const parameters = await vfs.yaml( - `Data/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`, - ); - const model = await extractModel( - vfs, - parameters.resourcesPath.blitzModelPath.replace(/\.sc2$/, ""), - ); - const content = Buffer.from(await nodeIO.writeBinary(model)); - - await uploader.add({ path: `3d/tanks/models/${id}.glb`, content }); - } - } - - await uploader.flush(); -} From b52919ebfe2d92b63d8d95f40b1393e8f2981d31 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Wed, 5 Aug 2026 13:22:33 -0500 Subject: [PATCH 063/147] update matrix --- .github/workflows/website.yml | 29 ++++++++++++++++++++++------- package.json | 1 + packages/website/astro.config.ts | 12 +----------- packages/website/package.json | 8 ++++---- 4 files changed, 28 insertions(+), 22 deletions(-) diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index 6c2254b23..3398956f9 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -36,21 +36,36 @@ env: jobs: build: name: Build website + runs-on: ubuntu-latest + + strategy: + matrix: + chunk: [default, api] + steps: - - uses: actions/checkout@v6 + - name: Checkout + uses: actions/checkout@v6 with: submodules: recursive token: ${{ secrets.GH_TOKEN }} - - uses: oven-sh/setup-bun@v2 - - uses: arduino/setup-protoc@v3 + + - name: Setup bun + uses: oven-sh/setup-bun@v2 + + - name: Setup protoc + uses: arduino/setup-protoc@v3 with: repo-token: ${{ secrets.GH_TOKEN }} - - uses: actions/configure-pages@v6 - - run: bun run setup - - name: Build website - run: bun run build:website + - name: Configure pages + uses: actions/configure-pages@v6 + + - name: Install dependencies + run: bun install + + - name: Build + run: bun run build - name: Upload artifact uses: actions/upload-pages-artifact@v5 diff --git a/package.json b/package.json index 4d5cfbcd0..1e63fe21c 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "private": true, "scripts": { "dev": "cd packages/website && bun run dev", + "build": "cd packages/website && bun run build", "build:averages": "cd packages/scripts && bun run build:averages" }, "type": "module", diff --git a/packages/website/astro.config.ts b/packages/website/astro.config.ts index 89243fbbf..b38d10811 100644 --- a/packages/website/astro.config.ts +++ b/packages/website/astro.config.ts @@ -1,4 +1,3 @@ -import node from "@astrojs/node"; import react from "@astrojs/react"; import { wrapper } from "@blitzkit/i18n"; import locales from "@blitzkit/i18n/locales.json"; @@ -10,14 +9,6 @@ import { defineConfig } from "astro/config"; import packageJSON from "../../package.json"; import { tools } from "./src/constants/tools"; -const POSSIBLE_TARGETS = ["app", "web"]; - -const target = process.env.PUBLIC_TARGET; - -if (!target || !POSSIBLE_TARGETS.includes(target)) { - throw new Error("Invalid target"); -} - export default defineConfig({ devToolbar: { enabled: false }, @@ -26,13 +17,12 @@ export default defineConfig({ esbuild: { target: "es2022" }, }, - output: target === "web" ? "static" : "server", + output: "static", site: "https://blitzkit.app", outDir: "../../dist/website", prefetch: { defaultStrategy: "hover", prefetchAll: true }, build: { concurrency: 4 }, - adapter: target === "app" ? node({ mode: "standalone" }) : undefined, integrations: [ favicons({ diff --git a/packages/website/package.json b/packages/website/package.json index 8ad371933..0beb690f9 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -2,10 +2,10 @@ "name": "@blitzkit/website", "type": "module", "scripts": { - "dev": "PUBLIC_TARGET=web astro dev", - "build": "PUBLIC_TARGET=web astro build", - "preview": "PUBLIC_TARGET=web astro preview", - "lint": "PUBLIC_TARGET=web astro check", + "dev": "astro dev", + "build": "astro build", + "preview": "astro preview", + "lint": "astro check", "astro": "astro" }, "dependencies": { From 7ef4e0b9af9b1711736c64b3b71bb408e9c7bf6a Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Wed, 5 Aug 2026 13:29:55 -0500 Subject: [PATCH 064/147] envs --- .github/workflows/website.yml | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index 3398956f9..f50782584 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -17,21 +17,22 @@ concurrency: cancel-in-progress: ${{ github.ref == 'refs/heads/main' }} env: - PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }} - PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }} - ALLOW_ROBOTS: ${{ secrets.ALLOW_ROBOTS }} + # blitzkit + PUBLIC_BRANCH: main + PUBLIC_PROMOTE_OPENTEST: false + PUBLIC_STATIC_ASSET_BASE: https://assets.blitzkit.app + PUBLIC_GUIDE_WEBSITE: https://guidesblitz.com + + # blitz WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }} - PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }} - PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }} - GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }} WOTB_GLOSSARY_DOMAIN: ${{ secrets.WOTB_GLOSSARY_DOMAIN }} WOTB_GLOSSARY_PATH: ${{ secrets.WOTB_GLOSSARY_PATH }} - PUBLIC_DISCORD_SERVER_ID: ${{ secrets.PUBLIC_DISCORD_SERVER_ID }} - PUBLIC_ASSET_BASE: ${{ secrets.PUBLIC_ASSET_BASE }} - PUBLIC_BRANCH: ${{ secrets.PUBLIC_BRANCH }} - PUBLIC_GUIDE_WEBSITE: ${{ secrets.PUBLIC_GUIDE_WEBSITE }} - SERVICING: ${{ secrets.SERVICING }} - GH_TOKEN: ${{ secrets.GH_TOKEN }} + + # google + PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }} + + # wargaming + PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }} jobs: build: @@ -66,6 +67,8 @@ jobs: - name: Build run: bun run build + env: + BUILD_CHUNK: ${{ matrix.chunk }} - name: Upload artifact uses: actions/upload-pages-artifact@v5 From f587bc098351a70abd1624ba74cc7eb3a33aca38 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Wed, 5 Aug 2026 13:30:20 -0500 Subject: [PATCH 065/147] fix legacy redirect --- .../[...locale]/tools/performance/index.astro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/tools/performance/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/tools/performance/index.astro index 37a006ac9..d8ae8c3eb 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/tools/performance/index.astro +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/tools/performance/index.astro @@ -1,5 +1,5 @@ --- -import LegacyRedirect from "../../../../layouts/LegacyRedirect.astro"; +import LegacyRedirect from "../../../../../layouts/LegacyRedirect.astro"; export { getStaticPaths } from "../../index.astro"; From f1f29b85b7ef2448e410ac01922e6fe350a0bec5 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Wed, 5 Aug 2026 13:32:33 -0500 Subject: [PATCH 066/147] s'more fixes --- .github/workflows/website.yml | 3 +++ .../[...locale]/tools/compare/index.astro | 2 +- .../[...locale]/tools/embed/[embed]/index.astro | 6 +++--- .../[...locale]/tools/embed/index.astro | 2 +- .../[...locale]/tools/gallery/index.astro | 2 +- .../[...locale]/tools/session/index.astro | 2 +- .../[...locale]/tools/tankopedia/[id]/index.astro | 8 ++++---- .../[...locale]/tools/tankopedia/index.astro | 2 +- .../[...locale]/tools/tier-list/index.astro | 2 +- 9 files changed, 16 insertions(+), 13 deletions(-) diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index f50782584..e16e08cb2 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -45,6 +45,9 @@ jobs: chunk: [default, api] steps: + - name: Free disk space + uses: jlumbroso/free-disk-space@main + - name: Checkout uses: actions/checkout@v6 with: diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/tools/compare/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/tools/compare/index.astro index cf0884483..9282dd96d 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/tools/compare/index.astro +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/tools/compare/index.astro @@ -1,5 +1,5 @@ --- -import LegacyRedirect from "../../../../layouts/LegacyRedirect.astro"; +import LegacyRedirect from "../../../../../layouts/LegacyRedirect.astro"; export { getStaticPaths } from "../../index.astro"; diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/tools/embed/[embed]/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/tools/embed/[embed]/index.astro index 4e8240656..4222d3a2b 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/tools/embed/[embed]/index.astro +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/tools/embed/[embed]/index.astro @@ -1,7 +1,7 @@ --- import type { GetStaticPaths } from "astro"; -import { embedConfigurations } from "../../../../../constants/embeds"; -import LegacyRedirect from "../../../../../layouts/LegacyRedirect.astro"; +import { embedConfigurations } from "../../../../../../constants/embeds"; +import LegacyRedirect from "../../../../../../layouts/LegacyRedirect.astro"; import { getStaticPaths as parentGetStaticPaths } from "../index.astro"; export const getStaticPaths = (() => { @@ -14,7 +14,7 @@ export const getStaticPaths = (() => { ...parent.params, embed: embed as keyof typeof embedConfigurations, }, - })) + })), ) .flat(); }) satisfies GetStaticPaths; diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/tools/embed/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/tools/embed/index.astro index 8096fbdd8..17fbfd9cd 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/tools/embed/index.astro +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/tools/embed/index.astro @@ -1,5 +1,5 @@ --- -import LegacyRedirect from "../../../../layouts/LegacyRedirect.astro"; +import LegacyRedirect from "../../../../../layouts/LegacyRedirect.astro"; export { getStaticPaths } from "../../index.astro"; diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/tools/gallery/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/tools/gallery/index.astro index 13a26f486..af5d8d0a2 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/tools/gallery/index.astro +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/tools/gallery/index.astro @@ -1,5 +1,5 @@ --- -import LegacyRedirect from "../../../../layouts/LegacyRedirect.astro"; +import LegacyRedirect from "../../../../../layouts/LegacyRedirect.astro"; export { getStaticPaths } from "../../index.astro"; diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/tools/session/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/tools/session/index.astro index f0777caef..279220d1b 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/tools/session/index.astro +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/tools/session/index.astro @@ -1,5 +1,5 @@ --- -import LegacyRedirect from "../../../../layouts/LegacyRedirect.astro"; +import LegacyRedirect from "../../../../../layouts/LegacyRedirect.astro"; export { getStaticPaths } from "../../index.astro"; diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/tools/tankopedia/[id]/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/tools/tankopedia/[id]/index.astro index d5a571f74..2c0979e68 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/tools/tankopedia/[id]/index.astro +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/tools/tankopedia/[id]/index.astro @@ -1,11 +1,11 @@ --- -import { fetchTankDefinitions } from "@blitzkit/core"; import type { GetStaticPaths } from "astro"; -import LegacyRedirect from "../../../../../layouts/LegacyRedirect.astro"; +import { api } from "../../../../../../core/blitzkit/api"; +import LegacyRedirect from "../../../../../../layouts/LegacyRedirect.astro"; import { getStaticPaths as parentGetStaticPaths } from "../index.astro"; export const getStaticPaths = (async () => { - const tankDefinitions = await fetchTankDefinitions(); + const tankDefinitions = await api.tankDefinitions(); const parentPaths = parentGetStaticPaths(); return Object.values(tankDefinitions.tanks) @@ -20,7 +20,7 @@ export const getStaticPaths = (async () => { const { id } = Astro.params; const { locale } = Astro.props; -const tankDefinitions = await fetchTankDefinitions(); +const tankDefinitions = await api.tankDefinitions(); const tank = tankDefinitions.tanks[id]; --- diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/tools/tankopedia/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/tools/tankopedia/index.astro index ce837a8b7..d95e9d358 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/tools/tankopedia/index.astro +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/tools/tankopedia/index.astro @@ -1,5 +1,5 @@ --- -import LegacyRedirect from "../../../../layouts/LegacyRedirect.astro"; +import LegacyRedirect from "../../../../../layouts/LegacyRedirect.astro"; export { getStaticPaths } from "../../index.astro"; diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/tools/tier-list/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/tools/tier-list/index.astro index 32f3c7180..bddc2c516 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/tools/tier-list/index.astro +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/tools/tier-list/index.astro @@ -1,5 +1,5 @@ --- -import LegacyRedirect from "../../../../layouts/LegacyRedirect.astro"; +import LegacyRedirect from "../../../../../layouts/LegacyRedirect.astro"; export { getStaticPaths } from "../../index.astro"; From e6d7f78ea4a034f094e5d581e38717ad4c3dcc21 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Wed, 5 Aug 2026 13:38:50 -0500 Subject: [PATCH 067/147] game dir --- .github/workflows/website.yml | 45 +++++++++++++++++++ packages/website/src/core/blitzkit/vfs.ts | 5 +-- .../embed/[embed]/host/index.astro | 2 +- 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index e16e08cb2..4efb4311b 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -27,6 +27,7 @@ env: WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }} WOTB_GLOSSARY_DOMAIN: ${{ secrets.WOTB_GLOSSARY_DOMAIN }} WOTB_GLOSSARY_PATH: ${{ secrets.WOTB_GLOSSARY_PATH }} + GAME_DIR: temp/game # google PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }} @@ -34,6 +35,12 @@ env: # wargaming PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }} + # steam + STEAM_USERNAME: ${{ secrets.STEAM_USERNAME }} + STEAM_PASSWORD: ${{ secrets.STEAM_PASSWORD }} + STEAM_APP: 444202 + STEAM_OS: windows + jobs: build: name: Build website @@ -41,6 +48,7 @@ jobs: runs-on: ubuntu-latest strategy: + fail-fast: false matrix: chunk: [default, api] @@ -68,6 +76,43 @@ jobs: - name: Install dependencies run: bun install + - name: Setup DepotDownloader + run: | + REPO_USER="SteamRE" + REPO_NAME="DepotDownloader" + TEMP="temp" + + OUT="$TEMP/$REPO_NAME" + ZIP="$TEMP/$REPO_NAME.zip" + + URL=$( + curl -s "https://api.github.com/repos/$REPO_USER/$REPO_NAME/releases/latest" \ + | jq -r '.assets[] + | select(.name | contains("linux-x64")) + | .browser_download_url' \ + | head -n 1 + ) + + mkdir -p "$TEMP" + curl -L "$URL" -o "$ZIP" + unzip -p "$ZIP" "$REPO_NAME" > "$OUT" + rm "$ZIP" + + chmod +x "$OUT" + + - name: Download game + run: | + TEMP="temp" + + DEPOT_DOWNLOADER="$TEMP/DepotDownloader" + GAME="$TEMP/game" + + set -a + source .env + set +a + + $DEPOT_DOWNLOADER -os $STEAM_OS -app $STEAM_APP -username $STEAM_USERNAME -password $STEAM_PASSWORD -dir $GAME + - name: Build run: bun run build env: diff --git a/packages/website/src/core/blitzkit/vfs.ts b/packages/website/src/core/blitzkit/vfs.ts index 34eb3e1e9..19bd8e044 100644 --- a/packages/website/src/core/blitzkit/vfs.ts +++ b/packages/website/src/core/blitzkit/vfs.ts @@ -1,5 +1,4 @@ +import { assertSecret } from "@blitzkit/core"; import { LocalVFS } from "@blitzkit/core/src/blitzkit/vfs/local"; -export const vfs = new LocalVFS( - "/run/media/tresabhi/Windows/Program Files/WindowsApps/7458BE2C.WorldofTanksBlitz_11.19.272.0_x64__x4tje2y229k00", -); +export const vfs = new LocalVFS(assertSecret(import.meta.env.GAME_DIR)); diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/embed/[embed]/host/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/embed/[embed]/host/index.astro index 00fb8f464..73f575172 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/embed/[embed]/host/index.astro +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/embed/[embed]/host/index.astro @@ -1,5 +1,5 @@ --- -import EmbedLayout from "../../../../../layouts/EmbedLayout.astro"; +import EmbedLayout from "../../../../../../layouts/EmbedLayout.astro"; import type { getStaticPaths } from "../index.astro"; import { Page } from "./_index"; From 8169dfd85d1f956f07d751b6378ef5ea0607072b Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Wed, 5 Aug 2026 13:43:10 -0500 Subject: [PATCH 068/147] no .env --- .github/workflows/website.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index 4efb4311b..3ba5c5feb 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -107,10 +107,6 @@ jobs: DEPOT_DOWNLOADER="$TEMP/DepotDownloader" GAME="$TEMP/game" - set -a - source .env - set +a - $DEPOT_DOWNLOADER -os $STEAM_OS -app $STEAM_APP -username $STEAM_USERNAME -password $STEAM_PASSWORD -dir $GAME - name: Build From db6c4679b7280aeaa6288a426447676cc10fc58e Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Wed, 5 Aug 2026 13:50:25 -0500 Subject: [PATCH 069/147] fix app id --- .github/workflows/website.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index 3ba5c5feb..52485f6ff 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -38,7 +38,7 @@ env: # steam STEAM_USERNAME: ${{ secrets.STEAM_USERNAME }} STEAM_PASSWORD: ${{ secrets.STEAM_PASSWORD }} - STEAM_APP: 444202 + STEAM_APP: 444200 STEAM_OS: windows jobs: From af58b4614dbea13a9d5417ef0abe5abe3df4c2c4 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Wed, 5 Aug 2026 14:09:00 -0500 Subject: [PATCH 070/147] master deploy --- .github/workflows/website.yml | 52 +++++++++++++------ .../[...locale]/auth/[provider]/index.astro | 6 +-- 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index 52485f6ff..ab7a78c5f 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -9,8 +9,6 @@ on: permissions: contents: read - pages: write - id-token: write concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -50,7 +48,14 @@ jobs: strategy: fail-fast: false matrix: - chunk: [default, api] + include: + - chunk: default + node: blitzkit/blitzkit.github.io + path: dist/website + + - chunk: api + node: blitzkit/api + path: dist/website/api steps: - name: Free disk space @@ -115,20 +120,33 @@ jobs: BUILD_CHUNK: ${{ matrix.chunk }} - name: Upload artifact - uses: actions/upload-pages-artifact@v5 + id: upload + uses: actions/upload-artifact@v4 with: - path: dist/website + name: chunk-${{ matrix.chunk }} + path: ${{ matrix.path }} - deploy-github-pages: - if: ${{ github.ref == 'refs/heads/production' }} - name: Deploy to GitHub Pages - runs-on: ubuntu-latest - needs: build - environment: - name: Production - url: ${{ steps.deployment.outputs.page_url }} + - name: Save artifact ID + run: | + echo "${{ steps.upload.outputs.artifact-id }}" - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v5 + - name: Trigger deployment + env: + TOKEN: ${{ secrets.GH_NODE_DEPLOY_TOKEN }} + ARTIFACT_ID: ${{ steps.upload.outputs.artifact-id }} + TARGET_REPO: ${{ matrix.node }} + run: | + curl \ + -X POST \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer $TOKEN" \ + "https://api.github.com/repos/$TARGET_REPO/dispatches" \ + -d "{ + \"event_type\": \"deploy-pages\", + \"client_payload\": { + \"source_repo\": \"${{ github.repository }}\", + \"run_id\": \"${{ github.run_id }}\", + \"artifact_id\": \"$ARTIFACT_ID\", + \"chunk\": \"${{ matrix.chunk }}\" + } + }" diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/auth/[provider]/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/auth/[provider]/index.astro index b38157251..0fe29196b 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/auth/[provider]/index.astro +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/auth/[provider]/index.astro @@ -1,8 +1,8 @@ --- import { Flex, Heading, Text } from "@radix-ui/themes"; import type { GetStaticPaths } from "astro"; -import { PageWrapper } from "../../../../components/PageWrapper"; -import BaseLayout from "../../../../layouts/BaseLayout.astro"; +import { PageWrapper } from "../../../../../components/PageWrapper"; +import BaseLayout from "../../../../../layouts/BaseLayout.astro"; import { getStaticPaths as parentGetStaticPaths } from "../../index.astro"; import { AUTH_PROVIDER_NAMES, AUTH_PROVIDERS, Authorize } from "./_index"; @@ -10,7 +10,7 @@ export const getStaticPaths = (() => { const parentPaths = parentGetStaticPaths(); return AUTH_PROVIDERS.map((provider) => - parentPaths.map((parent) => ({ params: { ...parent.params, provider } })) + parentPaths.map((parent) => ({ params: { ...parent.params, provider } })), ).flat(); }) satisfies GetStaticPaths; From b8742bae6645a950cac840ebfc8dc94791662c88 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Wed, 5 Aug 2026 14:17:33 -0500 Subject: [PATCH 071/147] fix linting errors --- package.json | 2 +- packages/website/src/components/Embeds/Boolean.tsx | 2 +- packages/website/src/components/Embeds/Color.tsx | 2 +- packages/website/src/components/Embeds/Enum.tsx | 2 +- packages/website/src/components/Embeds/Radius.tsx | 2 +- packages/website/src/components/Embeds/RichText.tsx | 6 ++++-- packages/website/src/components/Embeds/Size.tsx | 2 +- .../website/src/components/Embeds/SizeWithout0.tsx | 4 ++-- packages/website/src/components/Embeds/Slider.tsx | 2 +- .../website/src/components/Embeds/TextController.tsx | 2 +- packages/website/src/components/HomePageHotTanks.tsx | 4 ++-- .../src/components/ModuleButtons/ShellButton.tsx | 7 +++---- .../HeroSection/components/TankSandboxLoader.tsx | 3 +-- packages/website/src/core/blitzkit/isHalloween.ts | 1 - packages/website/src/hooks/useEccentricity.ts | 12 ++++++------ packages/website/src/hooks/useEquipmentPreset.ts | 4 ++-- .../src/pages/[...chunk_api]/tanks/[id]/model.glb.ts | 6 ++---- .../[...locale]/auth/[provider]/_index.tsx | 2 +- .../[...locale]/embed/[embed]/host/_index.tsx | 10 +++++----- 19 files changed, 36 insertions(+), 39 deletions(-) diff --git a/package.json b/package.json index 1e63fe21c..e790351de 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "blitzkit", - "version": "1.30.8", + "version": "1.31.0", "license": "BlitzKit License", "private": true, "scripts": { diff --git a/packages/website/src/components/Embeds/Boolean.tsx b/packages/website/src/components/Embeds/Boolean.tsx index c098c9db8..f90db59c8 100644 --- a/packages/website/src/components/Embeds/Boolean.tsx +++ b/packages/website/src/components/Embeds/Boolean.tsx @@ -1,5 +1,5 @@ import { Switch } from "@radix-ui/themes"; -import type { EmbedPreviewControllerProps } from "../../pages/[...locale]/embed/[embed]/_index"; +import type { EmbedPreviewControllerProps } from "../../pages/[...chunk_default]/[...locale]/embed/[embed]/_index"; import { EmbedState } from "../../stores/embedState"; export function Boolean({ configKey }: EmbedPreviewControllerProps) { diff --git a/packages/website/src/components/Embeds/Color.tsx b/packages/website/src/components/Embeds/Color.tsx index 16ebe28e6..2b6003f87 100644 --- a/packages/website/src/components/Embeds/Color.tsx +++ b/packages/website/src/components/Embeds/Color.tsx @@ -1,4 +1,4 @@ -import type { EmbedPreviewControllerProps } from "../../pages/[...locale]/embed/[embed]/_index"; +import type { EmbedPreviewControllerProps } from "../../pages/[...chunk_default]/[...locale]/embed/[embed]/_index"; import { EmbedState } from "../../stores/embedState"; import { ColorControllerRaw } from "./ColorControllerRaw"; diff --git a/packages/website/src/components/Embeds/Enum.tsx b/packages/website/src/components/Embeds/Enum.tsx index 191e2ce33..9d75f8af5 100644 --- a/packages/website/src/components/Embeds/Enum.tsx +++ b/packages/website/src/components/Embeds/Enum.tsx @@ -1,6 +1,6 @@ import { Select } from "@radix-ui/themes"; import { useLocale } from "../../hooks/useLocale"; -import type { EmbedPreviewControllerProps } from "../../pages/[...locale]/embed/[embed]/_index"; +import type { EmbedPreviewControllerProps } from "../../pages/[...chunk_default]/[...locale]/embed/[embed]/_index"; import { EmbedState } from "../../stores/embedState"; import type { EmbedConfigItemType, diff --git a/packages/website/src/components/Embeds/Radius.tsx b/packages/website/src/components/Embeds/Radius.tsx index f7c76775c..ff6973c9b 100644 --- a/packages/website/src/components/Embeds/Radius.tsx +++ b/packages/website/src/components/Embeds/Radius.tsx @@ -1,5 +1,5 @@ import { Slider } from "@radix-ui/themes"; -import type { EmbedPreviewControllerProps } from "../../pages/[...locale]/embed/[embed]/_index"; +import type { EmbedPreviewControllerProps } from "../../pages/[...chunk_default]/[...locale]/embed/[embed]/_index"; import { EmbedState, type RadixRadius } from "../../stores/embedState"; export function Radius({ configKey }: EmbedPreviewControllerProps) { diff --git a/packages/website/src/components/Embeds/RichText.tsx b/packages/website/src/components/Embeds/RichText.tsx index 469be74e6..eff92bba7 100644 --- a/packages/website/src/components/Embeds/RichText.tsx +++ b/packages/website/src/components/Embeds/RichText.tsx @@ -1,6 +1,6 @@ import { FontBoldIcon, FontSizeIcon } from "@radix-ui/react-icons"; import { Flex, Slider, Text } from "@radix-ui/themes"; -import type { EmbedPreviewControllerProps } from "../../pages/[...locale]/embed/[embed]/_index"; +import type { EmbedPreviewControllerProps } from "../../pages/[...chunk_default]/[...locale]/embed/[embed]/_index"; import { EmbedState, type RadixSizeWithout0 } from "../../stores/embedState"; import { radixTextWeights, @@ -12,7 +12,9 @@ import { ColorControllerRaw } from "./ColorControllerRaw"; export function RichText({ configKey }: EmbedPreviewControllerProps) { const state = EmbedState.use( (state) => - state[configKey] as EmbedConfigItemType["default"] + state[ + configKey + ] as EmbedConfigItemType["default"], ); return ( diff --git a/packages/website/src/components/Embeds/Size.tsx b/packages/website/src/components/Embeds/Size.tsx index 8a28ecdf7..394d2df17 100644 --- a/packages/website/src/components/Embeds/Size.tsx +++ b/packages/website/src/components/Embeds/Size.tsx @@ -1,5 +1,5 @@ import { Slider } from "@radix-ui/themes"; -import type { EmbedPreviewControllerProps } from "../../pages/[...locale]/embed/[embed]/_index"; +import type { EmbedPreviewControllerProps } from "../../pages/[...chunk_default]/[...locale]/embed/[embed]/_index"; import { EmbedState, type RadixSize } from "../../stores/embedState"; export function Size({ configKey }: EmbedPreviewControllerProps) { diff --git a/packages/website/src/components/Embeds/SizeWithout0.tsx b/packages/website/src/components/Embeds/SizeWithout0.tsx index b2ae6356e..51ebef575 100644 --- a/packages/website/src/components/Embeds/SizeWithout0.tsx +++ b/packages/website/src/components/Embeds/SizeWithout0.tsx @@ -1,5 +1,5 @@ import { Slider } from "@radix-ui/themes"; -import type { EmbedPreviewControllerProps } from "../../pages/[...locale]/embed/[embed]/_index"; +import type { EmbedPreviewControllerProps } from "../../pages/[...chunk_default]/[...locale]/embed/[embed]/_index"; import { EmbedState, type RadixSizeWithout0 } from "../../stores/embedState"; export function SizeWithout0({ configKey }: EmbedPreviewControllerProps) { @@ -10,7 +10,7 @@ export function SizeWithout0({ configKey }: EmbedPreviewControllerProps) { max={9} value={[ EmbedState.use((state) => - parseInt(state[configKey] as RadixSizeWithout0) + parseInt(state[configKey] as RadixSizeWithout0), ), ]} onValueChange={([value]) => { diff --git a/packages/website/src/components/Embeds/Slider.tsx b/packages/website/src/components/Embeds/Slider.tsx index 0302fe82e..52af6d053 100644 --- a/packages/website/src/components/Embeds/Slider.tsx +++ b/packages/website/src/components/Embeds/Slider.tsx @@ -1,5 +1,5 @@ import { Slider as RadixSlider } from "@radix-ui/themes"; -import type { EmbedPreviewControllerProps } from "../../pages/[...locale]/embed/[embed]/_index"; +import type { EmbedPreviewControllerProps } from "../../pages/[...chunk_default]/[...locale]/embed/[embed]/_index"; import { EmbedState, type RadixSize } from "../../stores/embedState"; import type { EmbedConfigItemType, diff --git a/packages/website/src/components/Embeds/TextController.tsx b/packages/website/src/components/Embeds/TextController.tsx index b7f6d4bc8..d86a718ea 100644 --- a/packages/website/src/components/Embeds/TextController.tsx +++ b/packages/website/src/components/Embeds/TextController.tsx @@ -1,5 +1,5 @@ import { TextField } from "@radix-ui/themes"; -import type { EmbedPreviewControllerProps } from "../../pages/[...locale]/embed/[embed]/_index"; +import type { EmbedPreviewControllerProps } from "../../pages/[...chunk_default]/[...locale]/embed/[embed]/_index"; import { EmbedState } from "../../stores/embedState"; export function TextController({ configKey }: EmbedPreviewControllerProps) { diff --git a/packages/website/src/components/HomePageHotTanks.tsx b/packages/website/src/components/HomePageHotTanks.tsx index f7c999185..255f93a70 100644 --- a/packages/website/src/components/HomePageHotTanks.tsx +++ b/packages/website/src/components/HomePageHotTanks.tsx @@ -2,7 +2,7 @@ import { literals } from "@blitzkit/i18n/src/literals"; import { EyeOpenIcon } from "@radix-ui/react-icons"; import { Box, Flex, Heading, Text } from "@radix-ui/themes"; import { awaitablePopularTanks } from "../core/awaitables/popularTanks"; -import { awaitableTankDefinitions } from "../core/awaitables/tankDefinitions"; +import { api } from "../core/blitzkit/api"; import { LocaleProvider, useLocale, @@ -11,7 +11,7 @@ import { import { TankCard } from "./TankCard"; const [tankDefinitions, popularTanks] = await Promise.all([ - awaitableTankDefinitions, + api.tankDefinitions(), awaitablePopularTanks, ]); diff --git a/packages/website/src/components/ModuleButtons/ShellButton.tsx b/packages/website/src/components/ModuleButtons/ShellButton.tsx index 08c3fcfd8..b9452e71f 100644 --- a/packages/website/src/components/ModuleButtons/ShellButton.tsx +++ b/packages/website/src/components/ModuleButtons/ShellButton.tsx @@ -1,6 +1,5 @@ -import { asset } from '@blitzkit/core'; -import { GenericTankComponentButton } from './GenericTankComponentButton'; -import type { TankComponentButtonProps } from './TankComponentButton'; +import { GenericTankComponentButton } from "./GenericTankComponentButton"; +import type { TankComponentButtonProps } from "./TankComponentButton"; interface ShellButtonProps extends TankComponentButtonProps { shell: string; @@ -9,7 +8,7 @@ interface ShellButtonProps extends TankComponentButtonProps { export function ShellButton({ shell, ...props }: ShellButtonProps) { return ( ); diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandboxLoader.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandboxLoader.tsx index 0c4c33338..f72dc089f 100644 --- a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandboxLoader.tsx +++ b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandboxLoader.tsx @@ -1,4 +1,3 @@ -import { asset } from "@blitzkit/core"; import { Flex, type FlexProps, Progress } from "@radix-ui/themes"; import { useProgress } from "@react-three/drei"; @@ -12,7 +11,7 @@ export function TankSandboxLoader({ id, ...props }: TankSandboxLoaderProps) { return ( state.protagonist.tank); diff --git a/packages/website/src/hooks/useEquipmentPreset.ts b/packages/website/src/hooks/useEquipmentPreset.ts index c69f75da5..294f7f988 100644 --- a/packages/website/src/hooks/useEquipmentPreset.ts +++ b/packages/website/src/hooks/useEquipmentPreset.ts @@ -1,6 +1,6 @@ -import { awaitableEquipmentDefinitions } from "../core/awaitables/equipmentDefinitions"; +import { api } from "../core/blitzkit/api"; -const equipmentDefinitions = await awaitableEquipmentDefinitions; +const equipmentDefinitions = await api.equipmentDefinitions(); export function useEquipmentPreset(preset: string) { return equipmentDefinitions.presets[preset]; diff --git a/packages/website/src/pages/[...chunk_api]/tanks/[id]/model.glb.ts b/packages/website/src/pages/[...chunk_api]/tanks/[id]/model.glb.ts index 6c7471e0c..684f4b375 100644 --- a/packages/website/src/pages/[...chunk_api]/tanks/[id]/model.glb.ts +++ b/packages/website/src/pages/[...chunk_api]/tanks/[id]/model.glb.ts @@ -105,7 +105,7 @@ async function extractModel(vfs: AbstractVFS, path: string) { } if (textures) { - const { image: baseColor, hasAlpha } = await readBaseColor( + const baseColor = await readBaseColor( `Data/3d/${dirname(path)}/${textures.baseColorMap ?? textures.albedo}`, textures.miscMap ? `Data/3d/${dirname(path)}/${textures.miscMap}` @@ -369,7 +369,6 @@ async function extractModel(vfs: AbstractVFS, path: string) { } async function readBaseColor(path: string, occlusionPath?: string) { - let hasAlpha = false; const baseRaw = await readTexture(path); const occlusionRaw = occlusionPath ? await readTexture(occlusionPath) @@ -399,7 +398,6 @@ async function readBaseColor(path: string, occlusionPath?: string) { } const alpha = base[i * 4 + 3]; - hasAlpha ||= alpha < 255; combined[i * 4 + 0] = Math.round(base[i * 4 + 0] * c); combined[i * 4 + 1] = Math.round(base[i * 4 + 1] * c); @@ -413,7 +411,7 @@ async function readBaseColor(path: string, occlusionPath?: string) { .webp() .toBuffer(); - return { hasAlpha, image }; + return image; } async function readRoughnessMetallic(path: string) { diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/auth/[provider]/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/auth/[provider]/_index.tsx index c1bb1f1a7..311a80c44 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/auth/[provider]/_index.tsx +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/auth/[provider]/_index.tsx @@ -1,5 +1,5 @@ import { useEffect } from "react"; -import { App } from "../../../../stores/app"; +import { App } from "../../../../../stores/app"; export const AUTH_PROVIDERS = ["wargaming"] as const; diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/embed/[embed]/host/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/embed/[embed]/host/_index.tsx index 17691f545..3c8269285 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/embed/[embed]/host/_index.tsx +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/embed/[embed]/host/_index.tsx @@ -1,22 +1,22 @@ import { Box } from "@radix-ui/themes"; import { useMemo } from "react"; import { parse } from "urlon"; -import { BlitzKitTheme } from "../../../../../components/BlitzKitTheme"; +import { BlitzKitTheme } from "../../../../../../components/BlitzKitTheme"; import { embedConfigurations, embedRenderers, extractEmbedConfigDefaults, -} from "../../../../../constants/embeds"; +} from "../../../../../../constants/embeds"; import { LocaleProvider, type LocaleAcceptorProps, -} from "../../../../../hooks/useLocale"; +} from "../../../../../../hooks/useLocale"; import { EmbedState, type EmbedConfig, type ExtractEmbedConfigTypes, -} from "../../../../../stores/embedState"; -import { useEmbedStateCurry } from "../../../../../stores/embedState/utilities"; +} from "../../../../../../stores/embedState"; +import { useEmbedStateCurry } from "../../../../../../stores/embedState/utilities"; interface PageProps { embed: keyof typeof embedConfigurations; From e09b407e610955f6daffb5f8b4995cd1ee0fe264 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Wed, 5 Aug 2026 19:22:25 -0500 Subject: [PATCH 072/147] force bun --- .github/workflows/website.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index ab7a78c5f..fcf4999cb 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -115,7 +115,7 @@ jobs: $DEPOT_DOWNLOADER -os $STEAM_OS -app $STEAM_APP -username $STEAM_USERNAME -password $STEAM_PASSWORD -dir $GAME - name: Build - run: bun run build + run: bun run --bun build env: BUILD_CHUNK: ${{ matrix.chunk }} From 484cb8fcb50a196b65cdf105bc87918be669b5f0 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Wed, 5 Aug 2026 19:27:51 -0500 Subject: [PATCH 073/147] max downloads 64 --- .github/workflows/website.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index fcf4999cb..fa30caca6 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -112,7 +112,7 @@ jobs: DEPOT_DOWNLOADER="$TEMP/DepotDownloader" GAME="$TEMP/game" - $DEPOT_DOWNLOADER -os $STEAM_OS -app $STEAM_APP -username $STEAM_USERNAME -password $STEAM_PASSWORD -dir $GAME + $DEPOT_DOWNLOADER -os $STEAM_OS -app $STEAM_APP -username $STEAM_USERNAME -password $STEAM_PASSWORD -dir $GAME -max-downloads 64 - name: Build run: bun run --bun build From 6dbcbc2afc78f741e60d55bab5f1a8567b910bd7 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Wed, 5 Aug 2026 20:02:31 -0500 Subject: [PATCH 074/147] relative game dir --- .github/workflows/website.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index fa30caca6..e8a0fdab0 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -25,7 +25,7 @@ env: WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }} WOTB_GLOSSARY_DOMAIN: ${{ secrets.WOTB_GLOSSARY_DOMAIN }} WOTB_GLOSSARY_PATH: ${{ secrets.WOTB_GLOSSARY_PATH }} - GAME_DIR: temp/game + GAME_DIR: ../../temp/game # google PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }} @@ -112,7 +112,7 @@ jobs: DEPOT_DOWNLOADER="$TEMP/DepotDownloader" GAME="$TEMP/game" - $DEPOT_DOWNLOADER -os $STEAM_OS -app $STEAM_APP -username $STEAM_USERNAME -password $STEAM_PASSWORD -dir $GAME -max-downloads 64 + $DEPOT_DOWNLOADER -os $STEAM_OS -app $STEAM_APP -username $STEAM_USERNAME -password $STEAM_PASSWORD -dir $GAME -max-downloads 256 - name: Build run: bun run --bun build From 050cb7a834818423aebc5618eb53482a13839ddc Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Wed, 5 Aug 2026 20:17:05 -0500 Subject: [PATCH 075/147] chunk typo --- .github/workflows/website.yml | 2 +- packages/website/src/core/blitzkit/chunkedStaticPaths.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index e8a0fdab0..20840962e 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -117,7 +117,7 @@ jobs: - name: Build run: bun run --bun build env: - BUILD_CHUNK: ${{ matrix.chunk }} + CHUNK: ${{ matrix.chunk }} - name: Upload artifact id: upload diff --git a/packages/website/src/core/blitzkit/chunkedStaticPaths.ts b/packages/website/src/core/blitzkit/chunkedStaticPaths.ts index 76c9a365e..ea9f3be3e 100644 --- a/packages/website/src/core/blitzkit/chunkedStaticPaths.ts +++ b/packages/website/src/core/blitzkit/chunkedStaticPaths.ts @@ -3,7 +3,7 @@ import type { GetStaticPaths } from "astro"; export function chunkedStaticPaths(chunk: string) { return function () { - const chunksEnv = assertSecret(import.meta.env.CHUNKS); + const chunksEnv = assertSecret(import.meta.env.CHUNK); const chunks = chunksEnv.split(","); if (chunksEnv !== "*" && !chunks.includes(chunk)) { From 734db0957e0cf069034783d5596e24fa611e72a6 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Thu, 6 Aug 2026 14:21:14 -0500 Subject: [PATCH 076/147] flatten structure --- .github/workflows/website.yml | 4 ++-- packages/website/src/core/blitzkit/chunkedStaticPaths.ts | 2 +- .../[...chunk_api]/{ => api}/definitions/camouflage.pb.ts | 2 +- .../[...chunk_api]/{ => api}/definitions/consumables.pb.ts | 2 +- .../[...chunk_api]/{ => api}/definitions/equipment.pb.ts | 2 +- .../[...chunk_api]/{ => api}/definitions/gallery.pb.ts | 2 +- .../pages/[...chunk_api]/{ => api}/definitions/game.pb.ts | 2 +- .../[...chunk_api]/{ => api}/definitions/models.pb.ts | 2 +- .../[...chunk_api]/{ => api}/definitions/provisions.pb.ts | 2 +- .../[...chunk_api]/{ => api}/definitions/skills.pb.ts | 2 +- .../pages/[...chunk_api]/{ => api}/definitions/tanks.pb.ts | 2 +- .../[...chunk_api]/{ => api}/flags/circle/[id].webp.ts | 2 +- .../pages/[...chunk_api]/{ => api}/flags/fade/[id].webp.ts | 2 +- .../[...chunk_api]/{ => api}/flags/scratched/[id].webp.ts | 2 +- .../[...chunk_api]/{ => api}/gallery/avatars/[name].ts | 2 +- .../[...chunk_api]/{ => api}/gamemodes/[id]/banner.webp.ts | 2 +- .../[...chunk_api]/{ => api}/icons/boosters/[id].webp.ts | 2 +- .../src/pages/[...chunk_api]/{ => api}/icons/camo.webp.ts | 2 +- .../{ => api}/icons/consumables/[id].webp.ts | 2 +- .../[...chunk_api]/{ => api}/icons/currencies/[id].webp.ts | 2 +- .../[...chunk_api]/{ => api}/icons/equipment/[id].webp.ts | 2 +- .../[...chunk_api]/{ => api}/icons/modules/[id].webp.ts | 2 +- .../[...chunk_api]/{ => api}/icons/provisions/[id].webp.ts | 2 +- .../[...chunk_api]/{ => api}/icons/shells/[id].webp.ts | 2 +- .../[...chunk_api]/{ => api}/icons/skills/[id].webp.ts | 2 +- .../pages/[...chunk_api]/{ => api}/tanks/[id]/_index.ts | 2 +- .../[...chunk_api]/{ => api}/tanks/[id]/collision.glb.ts | 0 .../[...chunk_api]/{ => api}/tanks/[id]/icons/big.webp.ts | 0 .../{ => api}/tanks/[id]/icons/small.webp.ts | 0 .../pages/[...chunk_api]/{ => api}/tanks/[id]/model.glb.ts | 0 .../src/pages/[...chunk_default]/[...locale]/index.astro | 7 ++++--- packages/website/src/pages/[...chunk_default]/_index.ts | 3 +++ 32 files changed, 34 insertions(+), 30 deletions(-) rename packages/website/src/pages/[...chunk_api]/{ => api}/definitions/camouflage.pb.ts (86%) rename packages/website/src/pages/[...chunk_api]/{ => api}/definitions/consumables.pb.ts (86%) rename packages/website/src/pages/[...chunk_api]/{ => api}/definitions/equipment.pb.ts (86%) rename packages/website/src/pages/[...chunk_api]/{ => api}/definitions/gallery.pb.ts (84%) rename packages/website/src/pages/[...chunk_api]/{ => api}/definitions/game.pb.ts (85%) rename packages/website/src/pages/[...chunk_api]/{ => api}/definitions/models.pb.ts (85%) rename packages/website/src/pages/[...chunk_api]/{ => api}/definitions/provisions.pb.ts (86%) rename packages/website/src/pages/[...chunk_api]/{ => api}/definitions/skills.pb.ts (85%) rename packages/website/src/pages/[...chunk_api]/{ => api}/definitions/tanks.pb.ts (85%) rename packages/website/src/pages/[...chunk_api]/{ => api}/flags/circle/[id].webp.ts (94%) rename packages/website/src/pages/[...chunk_api]/{ => api}/flags/fade/[id].webp.ts (93%) rename packages/website/src/pages/[...chunk_api]/{ => api}/flags/scratched/[id].webp.ts (93%) rename packages/website/src/pages/[...chunk_api]/{ => api}/gallery/avatars/[name].ts (96%) rename packages/website/src/pages/[...chunk_api]/{ => api}/gamemodes/[id]/banner.webp.ts (96%) rename packages/website/src/pages/[...chunk_api]/{ => api}/icons/boosters/[id].webp.ts (95%) rename packages/website/src/pages/[...chunk_api]/{ => api}/icons/camo.webp.ts (80%) rename packages/website/src/pages/[...chunk_api]/{ => api}/icons/consumables/[id].webp.ts (98%) rename packages/website/src/pages/[...chunk_api]/{ => api}/icons/currencies/[id].webp.ts (96%) rename packages/website/src/pages/[...chunk_api]/{ => api}/icons/equipment/[id].webp.ts (97%) rename packages/website/src/pages/[...chunk_api]/{ => api}/icons/modules/[id].webp.ts (94%) rename packages/website/src/pages/[...chunk_api]/{ => api}/icons/provisions/[id].webp.ts (97%) rename packages/website/src/pages/[...chunk_api]/{ => api}/icons/shells/[id].webp.ts (94%) rename packages/website/src/pages/[...chunk_api]/{ => api}/icons/skills/[id].webp.ts (94%) rename packages/website/src/pages/[...chunk_api]/{ => api}/tanks/[id]/_index.ts (84%) rename packages/website/src/pages/[...chunk_api]/{ => api}/tanks/[id]/collision.glb.ts (100%) rename packages/website/src/pages/[...chunk_api]/{ => api}/tanks/[id]/icons/big.webp.ts (100%) rename packages/website/src/pages/[...chunk_api]/{ => api}/tanks/[id]/icons/small.webp.ts (100%) rename packages/website/src/pages/[...chunk_api]/{ => api}/tanks/[id]/model.glb.ts (100%) create mode 100644 packages/website/src/pages/[...chunk_default]/_index.ts diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index 20840962e..9028f131c 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -12,7 +12,8 @@ permissions: concurrency: group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: ${{ github.ref == 'refs/heads/main' }} + # cancel-in-progress: ${{ github.ref == 'refs/heads/main' }} + cancel-in-progress: true env: # blitzkit @@ -46,7 +47,6 @@ jobs: runs-on: ubuntu-latest strategy: - fail-fast: false matrix: include: - chunk: default diff --git a/packages/website/src/core/blitzkit/chunkedStaticPaths.ts b/packages/website/src/core/blitzkit/chunkedStaticPaths.ts index ea9f3be3e..5458b8945 100644 --- a/packages/website/src/core/blitzkit/chunkedStaticPaths.ts +++ b/packages/website/src/core/blitzkit/chunkedStaticPaths.ts @@ -10,6 +10,6 @@ export function chunkedStaticPaths(chunk: string) { return []; } - return [{ params: { [`chunk_${chunk}`]: chunk } }]; + return [{ params: { [`chunk_${chunk}`]: undefined } }]; } satisfies GetStaticPaths; } diff --git a/packages/website/src/pages/[...chunk_api]/definitions/camouflage.pb.ts b/packages/website/src/pages/[...chunk_api]/api/definitions/camouflage.pb.ts similarity index 86% rename from packages/website/src/pages/[...chunk_api]/definitions/camouflage.pb.ts rename to packages/website/src/pages/[...chunk_api]/api/definitions/camouflage.pb.ts index 15dea60ce..138d2c9ea 100644 --- a/packages/website/src/pages/[...chunk_api]/definitions/camouflage.pb.ts +++ b/packages/website/src/pages/[...chunk_api]/api/definitions/camouflage.pb.ts @@ -1,7 +1,7 @@ import { CamouflageDefinitions } from "@blitzkit/core"; import { api } from "../../../core/blitzkit/api"; -export { getStaticPaths } from "../_index"; +export { getStaticPaths } from "../../_index"; export async function GET() { const definitions = await api.camouflageDefinitions(); diff --git a/packages/website/src/pages/[...chunk_api]/definitions/consumables.pb.ts b/packages/website/src/pages/[...chunk_api]/api/definitions/consumables.pb.ts similarity index 86% rename from packages/website/src/pages/[...chunk_api]/definitions/consumables.pb.ts rename to packages/website/src/pages/[...chunk_api]/api/definitions/consumables.pb.ts index 80b6adcf8..adedc779a 100644 --- a/packages/website/src/pages/[...chunk_api]/definitions/consumables.pb.ts +++ b/packages/website/src/pages/[...chunk_api]/api/definitions/consumables.pb.ts @@ -1,7 +1,7 @@ import { ConsumableDefinitions } from "@blitzkit/core"; import { api } from "../../../core/blitzkit/api"; -export { getStaticPaths } from "../_index"; +export { getStaticPaths } from "../../_index"; export async function GET() { const definitions = await api.consumableDefinitions(); diff --git a/packages/website/src/pages/[...chunk_api]/definitions/equipment.pb.ts b/packages/website/src/pages/[...chunk_api]/api/definitions/equipment.pb.ts similarity index 86% rename from packages/website/src/pages/[...chunk_api]/definitions/equipment.pb.ts rename to packages/website/src/pages/[...chunk_api]/api/definitions/equipment.pb.ts index 3691eb377..2c13760d7 100644 --- a/packages/website/src/pages/[...chunk_api]/definitions/equipment.pb.ts +++ b/packages/website/src/pages/[...chunk_api]/api/definitions/equipment.pb.ts @@ -1,7 +1,7 @@ import { EquipmentDefinitions } from "@blitzkit/core"; import { api } from "../../../core/blitzkit/api"; -export { getStaticPaths } from "../_index"; +export { getStaticPaths } from "../../_index"; export async function GET() { const definitions = await api.equipmentDefinitions(); diff --git a/packages/website/src/pages/[...chunk_api]/definitions/gallery.pb.ts b/packages/website/src/pages/[...chunk_api]/api/definitions/gallery.pb.ts similarity index 84% rename from packages/website/src/pages/[...chunk_api]/definitions/gallery.pb.ts rename to packages/website/src/pages/[...chunk_api]/api/definitions/gallery.pb.ts index 49453741e..0755b4427 100644 --- a/packages/website/src/pages/[...chunk_api]/definitions/gallery.pb.ts +++ b/packages/website/src/pages/[...chunk_api]/api/definitions/gallery.pb.ts @@ -1,7 +1,7 @@ import { Gallery } from "@blitzkit/core"; import { api } from "../../../core/blitzkit/api"; -export { getStaticPaths } from "../_index"; +export { getStaticPaths } from "../../_index"; export async function GET() { const definitions = await api.galleryDefinitions(); diff --git a/packages/website/src/pages/[...chunk_api]/definitions/game.pb.ts b/packages/website/src/pages/[...chunk_api]/api/definitions/game.pb.ts similarity index 85% rename from packages/website/src/pages/[...chunk_api]/definitions/game.pb.ts rename to packages/website/src/pages/[...chunk_api]/api/definitions/game.pb.ts index 86200f605..080172bd9 100644 --- a/packages/website/src/pages/[...chunk_api]/definitions/game.pb.ts +++ b/packages/website/src/pages/[...chunk_api]/api/definitions/game.pb.ts @@ -1,7 +1,7 @@ import { GameDefinitions } from "@blitzkit/core"; import { api } from "../../../core/blitzkit/api"; -export { getStaticPaths } from "../_index"; +export { getStaticPaths } from "../../_index"; export async function GET() { const definitions = await api.gameDefinitions(); diff --git a/packages/website/src/pages/[...chunk_api]/definitions/models.pb.ts b/packages/website/src/pages/[...chunk_api]/api/definitions/models.pb.ts similarity index 85% rename from packages/website/src/pages/[...chunk_api]/definitions/models.pb.ts rename to packages/website/src/pages/[...chunk_api]/api/definitions/models.pb.ts index 2399851bd..cd1d1ef8b 100644 --- a/packages/website/src/pages/[...chunk_api]/definitions/models.pb.ts +++ b/packages/website/src/pages/[...chunk_api]/api/definitions/models.pb.ts @@ -1,7 +1,7 @@ import { ModelDefinitions } from "@blitzkit/core"; import { api } from "../../../core/blitzkit/api"; -export { getStaticPaths } from "../_index"; +export { getStaticPaths } from "../../_index"; export async function GET() { const definitions = await api.modelDefinitions(); diff --git a/packages/website/src/pages/[...chunk_api]/definitions/provisions.pb.ts b/packages/website/src/pages/[...chunk_api]/api/definitions/provisions.pb.ts similarity index 86% rename from packages/website/src/pages/[...chunk_api]/definitions/provisions.pb.ts rename to packages/website/src/pages/[...chunk_api]/api/definitions/provisions.pb.ts index c352900e1..5c36ca31f 100644 --- a/packages/website/src/pages/[...chunk_api]/definitions/provisions.pb.ts +++ b/packages/website/src/pages/[...chunk_api]/api/definitions/provisions.pb.ts @@ -1,7 +1,7 @@ import { ProvisionDefinitions } from "@blitzkit/core"; import { api } from "../../../core/blitzkit/api"; -export { getStaticPaths } from "../_index"; +export { getStaticPaths } from "../../_index"; export async function GET() { const definitions = await api.provisionDefinitions(); diff --git a/packages/website/src/pages/[...chunk_api]/definitions/skills.pb.ts b/packages/website/src/pages/[...chunk_api]/api/definitions/skills.pb.ts similarity index 85% rename from packages/website/src/pages/[...chunk_api]/definitions/skills.pb.ts rename to packages/website/src/pages/[...chunk_api]/api/definitions/skills.pb.ts index dd0baddec..9ae4d8cf9 100644 --- a/packages/website/src/pages/[...chunk_api]/definitions/skills.pb.ts +++ b/packages/website/src/pages/[...chunk_api]/api/definitions/skills.pb.ts @@ -1,7 +1,7 @@ import { SkillDefinitions } from "@blitzkit/core"; import { api } from "../../../core/blitzkit/api"; -export { getStaticPaths } from "../_index"; +export { getStaticPaths } from "../../_index"; export async function GET() { const definitions = await api.skillDefinitions(); diff --git a/packages/website/src/pages/[...chunk_api]/definitions/tanks.pb.ts b/packages/website/src/pages/[...chunk_api]/api/definitions/tanks.pb.ts similarity index 85% rename from packages/website/src/pages/[...chunk_api]/definitions/tanks.pb.ts rename to packages/website/src/pages/[...chunk_api]/api/definitions/tanks.pb.ts index 5976401f9..088c3035e 100644 --- a/packages/website/src/pages/[...chunk_api]/definitions/tanks.pb.ts +++ b/packages/website/src/pages/[...chunk_api]/api/definitions/tanks.pb.ts @@ -1,7 +1,7 @@ import { TankDefinitions } from "@blitzkit/core"; import { api } from "../../../core/blitzkit/api"; -export { getStaticPaths } from "../_index"; +export { getStaticPaths } from "../../_index"; export async function GET() { const definitions = await api.tankDefinitions(); diff --git a/packages/website/src/pages/[...chunk_api]/flags/circle/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/api/flags/circle/[id].webp.ts similarity index 94% rename from packages/website/src/pages/[...chunk_api]/flags/circle/[id].webp.ts rename to packages/website/src/pages/[...chunk_api]/api/flags/circle/[id].webp.ts index c32a13501..65c8056aa 100644 --- a/packages/website/src/pages/[...chunk_api]/flags/circle/[id].webp.ts +++ b/packages/website/src/pages/[...chunk_api]/api/flags/circle/[id].webp.ts @@ -2,7 +2,7 @@ import type { APIContext, GetStaticPathsItem } from "astro"; import sharp from "sharp"; import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; import { vfs } from "../../../../core/blitzkit/vfs"; -import { getStaticPaths as _getStaticPaths } from "../../_index"; +import { getStaticPaths as _getStaticPaths } from "../../../_index"; export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { const paths: GetStaticPathsItem[] = []; diff --git a/packages/website/src/pages/[...chunk_api]/flags/fade/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/api/flags/fade/[id].webp.ts similarity index 93% rename from packages/website/src/pages/[...chunk_api]/flags/fade/[id].webp.ts rename to packages/website/src/pages/[...chunk_api]/api/flags/fade/[id].webp.ts index e31a8ede0..f81a891a7 100644 --- a/packages/website/src/pages/[...chunk_api]/flags/fade/[id].webp.ts +++ b/packages/website/src/pages/[...chunk_api]/api/flags/fade/[id].webp.ts @@ -1,7 +1,7 @@ import type { APIContext, GetStaticPathsItem } from "astro"; import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; import { vfs } from "../../../../core/blitzkit/vfs"; -import { getStaticPaths as _getStaticPaths } from "../../_index"; +import { getStaticPaths as _getStaticPaths } from "../../../_index"; export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { const paths: GetStaticPathsItem[] = []; diff --git a/packages/website/src/pages/[...chunk_api]/flags/scratched/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/api/flags/scratched/[id].webp.ts similarity index 93% rename from packages/website/src/pages/[...chunk_api]/flags/scratched/[id].webp.ts rename to packages/website/src/pages/[...chunk_api]/api/flags/scratched/[id].webp.ts index 9edbdf433..95a79474e 100644 --- a/packages/website/src/pages/[...chunk_api]/flags/scratched/[id].webp.ts +++ b/packages/website/src/pages/[...chunk_api]/api/flags/scratched/[id].webp.ts @@ -1,7 +1,7 @@ import type { APIContext, GetStaticPathsItem } from "astro"; import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; import { vfs } from "../../../../core/blitzkit/vfs"; -import { getStaticPaths as _getStaticPaths } from "../../_index"; +import { getStaticPaths as _getStaticPaths } from "../../../_index"; export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { const paths: GetStaticPathsItem[] = []; diff --git a/packages/website/src/pages/[...chunk_api]/gallery/avatars/[name].ts b/packages/website/src/pages/[...chunk_api]/api/gallery/avatars/[name].ts similarity index 96% rename from packages/website/src/pages/[...chunk_api]/gallery/avatars/[name].ts rename to packages/website/src/pages/[...chunk_api]/api/gallery/avatars/[name].ts index 032e1908d..c68e171c0 100644 --- a/packages/website/src/pages/[...chunk_api]/gallery/avatars/[name].ts +++ b/packages/website/src/pages/[...chunk_api]/api/gallery/avatars/[name].ts @@ -3,7 +3,7 @@ import locales from "@blitzkit/i18n/locales.json"; import type { APIContext, GetStaticPathsItem } from "astro"; import { extname } from "path"; import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; -import { getStaticPaths as _getStaticPaths } from "../../_index"; +import { getStaticPaths as _getStaticPaths } from "../../../_index"; export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { const paths: GetStaticPathsItem[] = []; diff --git a/packages/website/src/pages/[...chunk_api]/gamemodes/[id]/banner.webp.ts b/packages/website/src/pages/[...chunk_api]/api/gamemodes/[id]/banner.webp.ts similarity index 96% rename from packages/website/src/pages/[...chunk_api]/gamemodes/[id]/banner.webp.ts rename to packages/website/src/pages/[...chunk_api]/api/gamemodes/[id]/banner.webp.ts index e0383102d..7009a13db 100644 --- a/packages/website/src/pages/[...chunk_api]/gamemodes/[id]/banner.webp.ts +++ b/packages/website/src/pages/[...chunk_api]/api/gamemodes/[id]/banner.webp.ts @@ -3,7 +3,7 @@ import type { APIContext, GetStaticPathsItem } from "astro"; import sharp from "sharp"; import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; import { vfs } from "../../../../core/blitzkit/vfs"; -import { getStaticPaths as _getStaticPaths } from "../../_index"; +import { getStaticPaths as _getStaticPaths } from "../../../_index"; export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { const paths: GetStaticPathsItem[] = []; diff --git a/packages/website/src/pages/[...chunk_api]/icons/boosters/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/api/icons/boosters/[id].webp.ts similarity index 95% rename from packages/website/src/pages/[...chunk_api]/icons/boosters/[id].webp.ts rename to packages/website/src/pages/[...chunk_api]/api/icons/boosters/[id].webp.ts index 2e3d176bb..c1edd70a0 100644 --- a/packages/website/src/pages/[...chunk_api]/icons/boosters/[id].webp.ts +++ b/packages/website/src/pages/[...chunk_api]/api/icons/boosters/[id].webp.ts @@ -2,7 +2,7 @@ import type { APIContext, GetStaticPathsItem } from "astro"; import sharp from "sharp"; import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; import { vfs } from "../../../../core/blitzkit/vfs"; -import { getStaticPaths as _getStaticPaths } from "../../_index"; +import { getStaticPaths as _getStaticPaths } from "../../../_index"; export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { const paths: GetStaticPathsItem[] = []; diff --git a/packages/website/src/pages/[...chunk_api]/icons/camo.webp.ts b/packages/website/src/pages/[...chunk_api]/api/icons/camo.webp.ts similarity index 80% rename from packages/website/src/pages/[...chunk_api]/icons/camo.webp.ts rename to packages/website/src/pages/[...chunk_api]/api/icons/camo.webp.ts index acc583993..881f4e7d0 100644 --- a/packages/website/src/pages/[...chunk_api]/icons/camo.webp.ts +++ b/packages/website/src/pages/[...chunk_api]/api/icons/camo.webp.ts @@ -1,6 +1,6 @@ import { vfs } from "../../../core/blitzkit/vfs"; -export { getStaticPaths } from "../_index"; +export { getStaticPaths } from "../../_index"; export async function GET() { const bytes = await vfs.file("Data/Gfx/UI/Hangar/IconCamouflage.packed.webp"); diff --git a/packages/website/src/pages/[...chunk_api]/icons/consumables/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/api/icons/consumables/[id].webp.ts similarity index 98% rename from packages/website/src/pages/[...chunk_api]/icons/consumables/[id].webp.ts rename to packages/website/src/pages/[...chunk_api]/api/icons/consumables/[id].webp.ts index 8b02390b8..279c85c10 100644 --- a/packages/website/src/pages/[...chunk_api]/icons/consumables/[id].webp.ts +++ b/packages/website/src/pages/[...chunk_api]/api/icons/consumables/[id].webp.ts @@ -5,7 +5,7 @@ import { extractPackedIcon } from "../../../../core/blitz/extractPackedIcon"; import { parsePackedSpriteRect } from "../../../../core/blitz/parsePackedSpriteRect"; import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; import { vfs } from "../../../../core/blitzkit/vfs"; -import { getStaticPaths as _getStaticPaths } from "../../_index"; +import { getStaticPaths as _getStaticPaths } from "../../../_index"; interface Mappings { Header: { version: number }; diff --git a/packages/website/src/pages/[...chunk_api]/icons/currencies/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/api/icons/currencies/[id].webp.ts similarity index 96% rename from packages/website/src/pages/[...chunk_api]/icons/currencies/[id].webp.ts rename to packages/website/src/pages/[...chunk_api]/api/icons/currencies/[id].webp.ts index f6581c798..fa0fe6d47 100644 --- a/packages/website/src/pages/[...chunk_api]/icons/currencies/[id].webp.ts +++ b/packages/website/src/pages/[...chunk_api]/api/icons/currencies/[id].webp.ts @@ -3,7 +3,7 @@ import type { APIContext, GetStaticPathsItem } from "astro"; import sharp from "sharp"; import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; import { vfs } from "../../../../core/blitzkit/vfs"; -import { getStaticPaths as _getStaticPaths } from "../../_index"; +import { getStaticPaths as _getStaticPaths } from "../../../_index"; const ICONS = [ ["currency_silver_m.packed.webp", "silver"], diff --git a/packages/website/src/pages/[...chunk_api]/icons/equipment/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/api/icons/equipment/[id].webp.ts similarity index 97% rename from packages/website/src/pages/[...chunk_api]/icons/equipment/[id].webp.ts rename to packages/website/src/pages/[...chunk_api]/api/icons/equipment/[id].webp.ts index f47fc889b..1399c472a 100644 --- a/packages/website/src/pages/[...chunk_api]/icons/equipment/[id].webp.ts +++ b/packages/website/src/pages/[...chunk_api]/api/icons/equipment/[id].webp.ts @@ -3,7 +3,7 @@ import type { APIContext, GetStaticPathsItem } from "astro"; import sharp from "sharp"; import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; import { vfs } from "../../../../core/blitzkit/vfs"; -import { getStaticPaths as _getStaticPaths } from "../../_index"; +import { getStaticPaths as _getStaticPaths } from "../../../_index"; interface Mappings { Header: { version: number }; diff --git a/packages/website/src/pages/[...chunk_api]/icons/modules/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/api/icons/modules/[id].webp.ts similarity index 94% rename from packages/website/src/pages/[...chunk_api]/icons/modules/[id].webp.ts rename to packages/website/src/pages/[...chunk_api]/api/icons/modules/[id].webp.ts index 9237a57a2..e9aceebaa 100644 --- a/packages/website/src/pages/[...chunk_api]/icons/modules/[id].webp.ts +++ b/packages/website/src/pages/[...chunk_api]/api/icons/modules/[id].webp.ts @@ -2,7 +2,7 @@ import type { APIContext, GetStaticPathsItem } from "astro"; import sharp from "sharp"; import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; import { vfs } from "../../../../core/blitzkit/vfs"; -import { getStaticPaths as _getStaticPaths } from "../../_index"; +import { getStaticPaths as _getStaticPaths } from "../../../_index"; export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { const paths: GetStaticPathsItem[] = []; diff --git a/packages/website/src/pages/[...chunk_api]/icons/provisions/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/api/icons/provisions/[id].webp.ts similarity index 97% rename from packages/website/src/pages/[...chunk_api]/icons/provisions/[id].webp.ts rename to packages/website/src/pages/[...chunk_api]/api/icons/provisions/[id].webp.ts index db38f389a..b6890ab4c 100644 --- a/packages/website/src/pages/[...chunk_api]/icons/provisions/[id].webp.ts +++ b/packages/website/src/pages/[...chunk_api]/api/icons/provisions/[id].webp.ts @@ -5,7 +5,7 @@ import { extractPackedIcon } from "../../../../core/blitz/extractPackedIcon"; import { parsePackedSpriteRect } from "../../../../core/blitz/parsePackedSpriteRect"; import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; import { vfs } from "../../../../core/blitzkit/vfs"; -import { getStaticPaths as _getStaticPaths } from "../../_index"; +import { getStaticPaths as _getStaticPaths } from "../../../_index"; interface Mappings { Header: { version: number }; diff --git a/packages/website/src/pages/[...chunk_api]/icons/shells/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/api/icons/shells/[id].webp.ts similarity index 94% rename from packages/website/src/pages/[...chunk_api]/icons/shells/[id].webp.ts rename to packages/website/src/pages/[...chunk_api]/api/icons/shells/[id].webp.ts index f8c520d04..f12bb2816 100644 --- a/packages/website/src/pages/[...chunk_api]/icons/shells/[id].webp.ts +++ b/packages/website/src/pages/[...chunk_api]/api/icons/shells/[id].webp.ts @@ -2,7 +2,7 @@ import type { APIContext, GetStaticPathsItem } from "astro"; import sharp from "sharp"; import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; import { vfs } from "../../../../core/blitzkit/vfs"; -import { getStaticPaths as _getStaticPaths } from "../../_index"; +import { getStaticPaths as _getStaticPaths } from "../../../_index"; export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { const paths: GetStaticPathsItem[] = []; diff --git a/packages/website/src/pages/[...chunk_api]/icons/skills/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/api/icons/skills/[id].webp.ts similarity index 94% rename from packages/website/src/pages/[...chunk_api]/icons/skills/[id].webp.ts rename to packages/website/src/pages/[...chunk_api]/api/icons/skills/[id].webp.ts index 37fbd1e87..4b15c4276 100644 --- a/packages/website/src/pages/[...chunk_api]/icons/skills/[id].webp.ts +++ b/packages/website/src/pages/[...chunk_api]/api/icons/skills/[id].webp.ts @@ -3,7 +3,7 @@ import type { APIContext, GetStaticPathsItem } from "astro"; import sharp from "sharp"; import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; import { vfs } from "../../../../core/blitzkit/vfs"; -import { getStaticPaths as _getStaticPaths } from "../../_index"; +import { getStaticPaths as _getStaticPaths } from "../../../_index"; export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { const paths: GetStaticPathsItem[] = []; diff --git a/packages/website/src/pages/[...chunk_api]/tanks/[id]/_index.ts b/packages/website/src/pages/[...chunk_api]/api/tanks/[id]/_index.ts similarity index 84% rename from packages/website/src/pages/[...chunk_api]/tanks/[id]/_index.ts rename to packages/website/src/pages/[...chunk_api]/api/tanks/[id]/_index.ts index 8ea202dcb..7d2956bbb 100644 --- a/packages/website/src/pages/[...chunk_api]/tanks/[id]/_index.ts +++ b/packages/website/src/pages/[...chunk_api]/api/tanks/[id]/_index.ts @@ -1,6 +1,6 @@ import { api } from "../../../../core/blitzkit/api"; import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; -import { getStaticPaths as _getStaticPaths } from "../../_index"; +import { getStaticPaths as _getStaticPaths } from "../../../_index"; export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { const tanks = await api.tankDefinitions(); diff --git a/packages/website/src/pages/[...chunk_api]/tanks/[id]/collision.glb.ts b/packages/website/src/pages/[...chunk_api]/api/tanks/[id]/collision.glb.ts similarity index 100% rename from packages/website/src/pages/[...chunk_api]/tanks/[id]/collision.glb.ts rename to packages/website/src/pages/[...chunk_api]/api/tanks/[id]/collision.glb.ts diff --git a/packages/website/src/pages/[...chunk_api]/tanks/[id]/icons/big.webp.ts b/packages/website/src/pages/[...chunk_api]/api/tanks/[id]/icons/big.webp.ts similarity index 100% rename from packages/website/src/pages/[...chunk_api]/tanks/[id]/icons/big.webp.ts rename to packages/website/src/pages/[...chunk_api]/api/tanks/[id]/icons/big.webp.ts diff --git a/packages/website/src/pages/[...chunk_api]/tanks/[id]/icons/small.webp.ts b/packages/website/src/pages/[...chunk_api]/api/tanks/[id]/icons/small.webp.ts similarity index 100% rename from packages/website/src/pages/[...chunk_api]/tanks/[id]/icons/small.webp.ts rename to packages/website/src/pages/[...chunk_api]/api/tanks/[id]/icons/small.webp.ts diff --git a/packages/website/src/pages/[...chunk_api]/tanks/[id]/model.glb.ts b/packages/website/src/pages/[...chunk_api]/api/tanks/[id]/model.glb.ts similarity index 100% rename from packages/website/src/pages/[...chunk_api]/tanks/[id]/model.glb.ts rename to packages/website/src/pages/[...chunk_api]/api/tanks/[id]/model.glb.ts diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/index.astro index 113b6e785..2bda9a01e 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/index.astro +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/index.astro @@ -4,14 +4,15 @@ import { assertSecret } from "@blitzkit/core"; import locales from "@blitzkit/i18n/locales.json"; import { literals } from "@blitzkit/i18n/src/literals"; import { Box, Flex, Link } from "@radix-ui/themes"; -import type { GetStaticPaths } from "astro"; import { AesonPlug } from "../../../components/AesonPlug"; import HomeHero from "../../../components/HomeHero.astro"; import { Plugs } from "../../../components/Plugs"; import ToolCard from "../../../components/ToolCard.astro"; import { tools, type Tool } from "../../../constants/tools"; +import { mixStaticPaths } from "../../../core/blitzkit/mixStaticPaths"; import { getStrings } from "../../../core/i18n/getStrings"; import BaseLayout from "../../../layouts/BaseLayout.astro"; +import { getStaticPaths as _getStaticPaths } from "../_index"; const MAX_TOOLS_PER_ROW = 2; @@ -38,14 +39,14 @@ for (const id in tools) { } } -export const getStaticPaths = (() => { +export const getStaticPaths = mixStaticPaths(_getStaticPaths, () => { return [ ...locales.supported .filter(({ locale }) => locale !== locales.default) .map(({ locale }) => ({ params: { locale }, props: { locale } })), { params: { locale: undefined }, props: { locale: locales.default } }, ]; -}) satisfies GetStaticPaths; +}); --- Date: Thu, 6 Aug 2026 14:29:13 -0500 Subject: [PATCH 077/147] fix imports --- .../api/definitions/camouflage.pb.ts | 2 +- .../api/definitions/consumables.pb.ts | 2 +- .../api/definitions/equipment.pb.ts | 2 +- .../api/definitions/gallery.pb.ts | 2 +- .../[...chunk_api]/api/definitions/game.pb.ts | 2 +- .../api/definitions/models.pb.ts | 2 +- .../api/definitions/provisions.pb.ts | 2 +- .../api/definitions/skills.pb.ts | 2 +- .../api/definitions/tanks.pb.ts | 2 +- .../api/flags/circle/[id].webp.ts | 4 +- .../api/flags/fade/[id].webp.ts | 4 +- .../api/flags/scratched/[id].webp.ts | 4 +- .../api/gallery/avatars/[name].ts | 2 +- .../api/gamemodes/[id]/banner.webp.ts | 4 +- .../api/icons/boosters/[id].webp.ts | 4 +- .../[...chunk_api]/api/icons/camo.webp.ts | 2 +- .../api/icons/consumables/[id].webp.ts | 8 ++-- .../api/icons/currencies/[id].webp.ts | 4 +- .../api/icons/equipment/[id].webp.ts | 4 +- .../api/icons/modules/[id].webp.ts | 4 +- .../api/icons/provisions/[id].webp.ts | 8 ++-- .../api/icons/shells/[id].webp.ts | 4 +- .../api/icons/skills/[id].webp.ts | 4 +- .../[...chunk_api]/api/tanks/[id]/_index.ts | 4 +- .../api/tanks/[id]/collision.glb.ts | 2 +- .../api/tanks/[id]/icons/big.webp.ts | 4 +- .../api/tanks/[id]/icons/small.webp.ts | 2 +- .../api/tanks/[id]/model.glb.ts | 2 +- .../[...locale]/auth/[provider]/index.astro | 16 ++++---- .../docs/[directory]/[document]/index.astro | 41 ++++++++----------- .../[...locale]/embed/[embed]/index.astro | 23 ++++------- .../[...locale]/tanks/[slug]/index.astro | 21 ++++------ .../tools/embed/[embed]/index.astro | 23 ++++------- .../tools/tankopedia/[id]/index.astro | 22 ++++------ 34 files changed, 99 insertions(+), 139 deletions(-) diff --git a/packages/website/src/pages/[...chunk_api]/api/definitions/camouflage.pb.ts b/packages/website/src/pages/[...chunk_api]/api/definitions/camouflage.pb.ts index 138d2c9ea..c4d2eb4a9 100644 --- a/packages/website/src/pages/[...chunk_api]/api/definitions/camouflage.pb.ts +++ b/packages/website/src/pages/[...chunk_api]/api/definitions/camouflage.pb.ts @@ -1,5 +1,5 @@ import { CamouflageDefinitions } from "@blitzkit/core"; -import { api } from "../../../core/blitzkit/api"; +import { api } from "../../../../core/blitzkit/api"; export { getStaticPaths } from "../../_index"; diff --git a/packages/website/src/pages/[...chunk_api]/api/definitions/consumables.pb.ts b/packages/website/src/pages/[...chunk_api]/api/definitions/consumables.pb.ts index adedc779a..c9310030e 100644 --- a/packages/website/src/pages/[...chunk_api]/api/definitions/consumables.pb.ts +++ b/packages/website/src/pages/[...chunk_api]/api/definitions/consumables.pb.ts @@ -1,5 +1,5 @@ import { ConsumableDefinitions } from "@blitzkit/core"; -import { api } from "../../../core/blitzkit/api"; +import { api } from "../../../../core/blitzkit/api"; export { getStaticPaths } from "../../_index"; diff --git a/packages/website/src/pages/[...chunk_api]/api/definitions/equipment.pb.ts b/packages/website/src/pages/[...chunk_api]/api/definitions/equipment.pb.ts index 2c13760d7..e76b07892 100644 --- a/packages/website/src/pages/[...chunk_api]/api/definitions/equipment.pb.ts +++ b/packages/website/src/pages/[...chunk_api]/api/definitions/equipment.pb.ts @@ -1,5 +1,5 @@ import { EquipmentDefinitions } from "@blitzkit/core"; -import { api } from "../../../core/blitzkit/api"; +import { api } from "../../../../core/blitzkit/api"; export { getStaticPaths } from "../../_index"; diff --git a/packages/website/src/pages/[...chunk_api]/api/definitions/gallery.pb.ts b/packages/website/src/pages/[...chunk_api]/api/definitions/gallery.pb.ts index 0755b4427..0b50dd3ff 100644 --- a/packages/website/src/pages/[...chunk_api]/api/definitions/gallery.pb.ts +++ b/packages/website/src/pages/[...chunk_api]/api/definitions/gallery.pb.ts @@ -1,5 +1,5 @@ import { Gallery } from "@blitzkit/core"; -import { api } from "../../../core/blitzkit/api"; +import { api } from "../../../../core/blitzkit/api"; export { getStaticPaths } from "../../_index"; diff --git a/packages/website/src/pages/[...chunk_api]/api/definitions/game.pb.ts b/packages/website/src/pages/[...chunk_api]/api/definitions/game.pb.ts index 080172bd9..a557213eb 100644 --- a/packages/website/src/pages/[...chunk_api]/api/definitions/game.pb.ts +++ b/packages/website/src/pages/[...chunk_api]/api/definitions/game.pb.ts @@ -1,5 +1,5 @@ import { GameDefinitions } from "@blitzkit/core"; -import { api } from "../../../core/blitzkit/api"; +import { api } from "../../../../core/blitzkit/api"; export { getStaticPaths } from "../../_index"; diff --git a/packages/website/src/pages/[...chunk_api]/api/definitions/models.pb.ts b/packages/website/src/pages/[...chunk_api]/api/definitions/models.pb.ts index cd1d1ef8b..49d265d09 100644 --- a/packages/website/src/pages/[...chunk_api]/api/definitions/models.pb.ts +++ b/packages/website/src/pages/[...chunk_api]/api/definitions/models.pb.ts @@ -1,5 +1,5 @@ import { ModelDefinitions } from "@blitzkit/core"; -import { api } from "../../../core/blitzkit/api"; +import { api } from "../../../../core/blitzkit/api"; export { getStaticPaths } from "../../_index"; diff --git a/packages/website/src/pages/[...chunk_api]/api/definitions/provisions.pb.ts b/packages/website/src/pages/[...chunk_api]/api/definitions/provisions.pb.ts index 5c36ca31f..b7884617f 100644 --- a/packages/website/src/pages/[...chunk_api]/api/definitions/provisions.pb.ts +++ b/packages/website/src/pages/[...chunk_api]/api/definitions/provisions.pb.ts @@ -1,5 +1,5 @@ import { ProvisionDefinitions } from "@blitzkit/core"; -import { api } from "../../../core/blitzkit/api"; +import { api } from "../../../../core/blitzkit/api"; export { getStaticPaths } from "../../_index"; diff --git a/packages/website/src/pages/[...chunk_api]/api/definitions/skills.pb.ts b/packages/website/src/pages/[...chunk_api]/api/definitions/skills.pb.ts index 9ae4d8cf9..161f46f71 100644 --- a/packages/website/src/pages/[...chunk_api]/api/definitions/skills.pb.ts +++ b/packages/website/src/pages/[...chunk_api]/api/definitions/skills.pb.ts @@ -1,5 +1,5 @@ import { SkillDefinitions } from "@blitzkit/core"; -import { api } from "../../../core/blitzkit/api"; +import { api } from "../../../../core/blitzkit/api"; export { getStaticPaths } from "../../_index"; diff --git a/packages/website/src/pages/[...chunk_api]/api/definitions/tanks.pb.ts b/packages/website/src/pages/[...chunk_api]/api/definitions/tanks.pb.ts index 088c3035e..ce74498a5 100644 --- a/packages/website/src/pages/[...chunk_api]/api/definitions/tanks.pb.ts +++ b/packages/website/src/pages/[...chunk_api]/api/definitions/tanks.pb.ts @@ -1,5 +1,5 @@ import { TankDefinitions } from "@blitzkit/core"; -import { api } from "../../../core/blitzkit/api"; +import { api } from "../../../../core/blitzkit/api"; export { getStaticPaths } from "../../_index"; diff --git a/packages/website/src/pages/[...chunk_api]/api/flags/circle/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/api/flags/circle/[id].webp.ts index 65c8056aa..0fc735f8c 100644 --- a/packages/website/src/pages/[...chunk_api]/api/flags/circle/[id].webp.ts +++ b/packages/website/src/pages/[...chunk_api]/api/flags/circle/[id].webp.ts @@ -1,7 +1,7 @@ import type { APIContext, GetStaticPathsItem } from "astro"; import sharp from "sharp"; -import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; -import { vfs } from "../../../../core/blitzkit/vfs"; +import { mixStaticPaths } from "../../../../../core/blitzkit/mixStaticPaths"; +import { vfs } from "../../../../../core/blitzkit/vfs"; import { getStaticPaths as _getStaticPaths } from "../../../_index"; export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { diff --git a/packages/website/src/pages/[...chunk_api]/api/flags/fade/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/api/flags/fade/[id].webp.ts index f81a891a7..fe6dadee5 100644 --- a/packages/website/src/pages/[...chunk_api]/api/flags/fade/[id].webp.ts +++ b/packages/website/src/pages/[...chunk_api]/api/flags/fade/[id].webp.ts @@ -1,6 +1,6 @@ import type { APIContext, GetStaticPathsItem } from "astro"; -import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; -import { vfs } from "../../../../core/blitzkit/vfs"; +import { mixStaticPaths } from "../../../../../core/blitzkit/mixStaticPaths"; +import { vfs } from "../../../../../core/blitzkit/vfs"; import { getStaticPaths as _getStaticPaths } from "../../../_index"; export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { diff --git a/packages/website/src/pages/[...chunk_api]/api/flags/scratched/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/api/flags/scratched/[id].webp.ts index 95a79474e..a002e6447 100644 --- a/packages/website/src/pages/[...chunk_api]/api/flags/scratched/[id].webp.ts +++ b/packages/website/src/pages/[...chunk_api]/api/flags/scratched/[id].webp.ts @@ -1,6 +1,6 @@ import type { APIContext, GetStaticPathsItem } from "astro"; -import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; -import { vfs } from "../../../../core/blitzkit/vfs"; +import { mixStaticPaths } from "../../../../../core/blitzkit/mixStaticPaths"; +import { vfs } from "../../../../../core/blitzkit/vfs"; import { getStaticPaths as _getStaticPaths } from "../../../_index"; export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { diff --git a/packages/website/src/pages/[...chunk_api]/api/gallery/avatars/[name].ts b/packages/website/src/pages/[...chunk_api]/api/gallery/avatars/[name].ts index c68e171c0..831bbd95c 100644 --- a/packages/website/src/pages/[...chunk_api]/api/gallery/avatars/[name].ts +++ b/packages/website/src/pages/[...chunk_api]/api/gallery/avatars/[name].ts @@ -2,7 +2,7 @@ import { fetchGlossary, type Avatar } from "@blitzkit/core"; import locales from "@blitzkit/i18n/locales.json"; import type { APIContext, GetStaticPathsItem } from "astro"; import { extname } from "path"; -import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; +import { mixStaticPaths } from "../../../../../core/blitzkit/mixStaticPaths"; import { getStaticPaths as _getStaticPaths } from "../../../_index"; export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { diff --git a/packages/website/src/pages/[...chunk_api]/api/gamemodes/[id]/banner.webp.ts b/packages/website/src/pages/[...chunk_api]/api/gamemodes/[id]/banner.webp.ts index 7009a13db..03587e42a 100644 --- a/packages/website/src/pages/[...chunk_api]/api/gamemodes/[id]/banner.webp.ts +++ b/packages/website/src/pages/[...chunk_api]/api/gamemodes/[id]/banner.webp.ts @@ -1,8 +1,8 @@ import type { SquadBattleTypeStylesYaml } from "@blitzkit/core"; import type { APIContext, GetStaticPathsItem } from "astro"; import sharp from "sharp"; -import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; -import { vfs } from "../../../../core/blitzkit/vfs"; +import { mixStaticPaths } from "../../../../../core/blitzkit/mixStaticPaths"; +import { vfs } from "../../../../../core/blitzkit/vfs"; import { getStaticPaths as _getStaticPaths } from "../../../_index"; export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { diff --git a/packages/website/src/pages/[...chunk_api]/api/icons/boosters/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/api/icons/boosters/[id].webp.ts index c1edd70a0..5ee73c77e 100644 --- a/packages/website/src/pages/[...chunk_api]/api/icons/boosters/[id].webp.ts +++ b/packages/website/src/pages/[...chunk_api]/api/icons/boosters/[id].webp.ts @@ -1,7 +1,7 @@ import type { APIContext, GetStaticPathsItem } from "astro"; import sharp from "sharp"; -import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; -import { vfs } from "../../../../core/blitzkit/vfs"; +import { mixStaticPaths } from "../../../../../core/blitzkit/mixStaticPaths"; +import { vfs } from "../../../../../core/blitzkit/vfs"; import { getStaticPaths as _getStaticPaths } from "../../../_index"; export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { diff --git a/packages/website/src/pages/[...chunk_api]/api/icons/camo.webp.ts b/packages/website/src/pages/[...chunk_api]/api/icons/camo.webp.ts index 881f4e7d0..4d644d246 100644 --- a/packages/website/src/pages/[...chunk_api]/api/icons/camo.webp.ts +++ b/packages/website/src/pages/[...chunk_api]/api/icons/camo.webp.ts @@ -1,4 +1,4 @@ -import { vfs } from "../../../core/blitzkit/vfs"; +import { vfs } from "../../../../core/blitzkit/vfs"; export { getStaticPaths } from "../../_index"; diff --git a/packages/website/src/pages/[...chunk_api]/api/icons/consumables/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/api/icons/consumables/[id].webp.ts index 279c85c10..24074e115 100644 --- a/packages/website/src/pages/[...chunk_api]/api/icons/consumables/[id].webp.ts +++ b/packages/website/src/pages/[...chunk_api]/api/icons/consumables/[id].webp.ts @@ -1,10 +1,10 @@ import type { ConsumablesCommon } from "@blitzkit/core"; import type { APIContext, GetStaticPathsItem } from "astro"; import sharp from "sharp"; -import { extractPackedIcon } from "../../../../core/blitz/extractPackedIcon"; -import { parsePackedSpriteRect } from "../../../../core/blitz/parsePackedSpriteRect"; -import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; -import { vfs } from "../../../../core/blitzkit/vfs"; +import { extractPackedIcon } from "../../../../../core/blitz/extractPackedIcon"; +import { parsePackedSpriteRect } from "../../../../../core/blitz/parsePackedSpriteRect"; +import { mixStaticPaths } from "../../../../../core/blitzkit/mixStaticPaths"; +import { vfs } from "../../../../../core/blitzkit/vfs"; import { getStaticPaths as _getStaticPaths } from "../../../_index"; interface Mappings { diff --git a/packages/website/src/pages/[...chunk_api]/api/icons/currencies/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/api/icons/currencies/[id].webp.ts index fa0fe6d47..c4b643c51 100644 --- a/packages/website/src/pages/[...chunk_api]/api/icons/currencies/[id].webp.ts +++ b/packages/website/src/pages/[...chunk_api]/api/icons/currencies/[id].webp.ts @@ -1,8 +1,8 @@ import { assertSecret, type BlitzGlossary } from "@blitzkit/core"; import type { APIContext, GetStaticPathsItem } from "astro"; import sharp from "sharp"; -import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; -import { vfs } from "../../../../core/blitzkit/vfs"; +import { mixStaticPaths } from "../../../../../core/blitzkit/mixStaticPaths"; +import { vfs } from "../../../../../core/blitzkit/vfs"; import { getStaticPaths as _getStaticPaths } from "../../../_index"; const ICONS = [ diff --git a/packages/website/src/pages/[...chunk_api]/api/icons/equipment/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/api/icons/equipment/[id].webp.ts index 1399c472a..c91b73476 100644 --- a/packages/website/src/pages/[...chunk_api]/api/icons/equipment/[id].webp.ts +++ b/packages/website/src/pages/[...chunk_api]/api/icons/equipment/[id].webp.ts @@ -1,8 +1,8 @@ import type { OptionalDevices } from "@blitzkit/core"; import type { APIContext, GetStaticPathsItem } from "astro"; import sharp from "sharp"; -import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; -import { vfs } from "../../../../core/blitzkit/vfs"; +import { mixStaticPaths } from "../../../../../core/blitzkit/mixStaticPaths"; +import { vfs } from "../../../../../core/blitzkit/vfs"; import { getStaticPaths as _getStaticPaths } from "../../../_index"; interface Mappings { diff --git a/packages/website/src/pages/[...chunk_api]/api/icons/modules/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/api/icons/modules/[id].webp.ts index e9aceebaa..cdd338714 100644 --- a/packages/website/src/pages/[...chunk_api]/api/icons/modules/[id].webp.ts +++ b/packages/website/src/pages/[...chunk_api]/api/icons/modules/[id].webp.ts @@ -1,7 +1,7 @@ import type { APIContext, GetStaticPathsItem } from "astro"; import sharp from "sharp"; -import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; -import { vfs } from "../../../../core/blitzkit/vfs"; +import { mixStaticPaths } from "../../../../../core/blitzkit/mixStaticPaths"; +import { vfs } from "../../../../../core/blitzkit/vfs"; import { getStaticPaths as _getStaticPaths } from "../../../_index"; export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { diff --git a/packages/website/src/pages/[...chunk_api]/api/icons/provisions/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/api/icons/provisions/[id].webp.ts index b6890ab4c..97cde6260 100644 --- a/packages/website/src/pages/[...chunk_api]/api/icons/provisions/[id].webp.ts +++ b/packages/website/src/pages/[...chunk_api]/api/icons/provisions/[id].webp.ts @@ -1,10 +1,10 @@ import type { ProvisionsCommon } from "@blitzkit/core"; import type { APIContext, GetStaticPathsItem } from "astro"; import sharp from "sharp"; -import { extractPackedIcon } from "../../../../core/blitz/extractPackedIcon"; -import { parsePackedSpriteRect } from "../../../../core/blitz/parsePackedSpriteRect"; -import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; -import { vfs } from "../../../../core/blitzkit/vfs"; +import { extractPackedIcon } from "../../../../../core/blitz/extractPackedIcon"; +import { parsePackedSpriteRect } from "../../../../../core/blitz/parsePackedSpriteRect"; +import { mixStaticPaths } from "../../../../../core/blitzkit/mixStaticPaths"; +import { vfs } from "../../../../../core/blitzkit/vfs"; import { getStaticPaths as _getStaticPaths } from "../../../_index"; interface Mappings { diff --git a/packages/website/src/pages/[...chunk_api]/api/icons/shells/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/api/icons/shells/[id].webp.ts index f12bb2816..bd63dbde8 100644 --- a/packages/website/src/pages/[...chunk_api]/api/icons/shells/[id].webp.ts +++ b/packages/website/src/pages/[...chunk_api]/api/icons/shells/[id].webp.ts @@ -1,7 +1,7 @@ import type { APIContext, GetStaticPathsItem } from "astro"; import sharp from "sharp"; -import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; -import { vfs } from "../../../../core/blitzkit/vfs"; +import { mixStaticPaths } from "../../../../../core/blitzkit/mixStaticPaths"; +import { vfs } from "../../../../../core/blitzkit/vfs"; import { getStaticPaths as _getStaticPaths } from "../../../_index"; export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { diff --git a/packages/website/src/pages/[...chunk_api]/api/icons/skills/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/api/icons/skills/[id].webp.ts index 4b15c4276..8caab5718 100644 --- a/packages/website/src/pages/[...chunk_api]/api/icons/skills/[id].webp.ts +++ b/packages/website/src/pages/[...chunk_api]/api/icons/skills/[id].webp.ts @@ -1,8 +1,8 @@ import type { TankmenAvatar } from "@blitzkit/core"; import type { APIContext, GetStaticPathsItem } from "astro"; import sharp from "sharp"; -import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; -import { vfs } from "../../../../core/blitzkit/vfs"; +import { mixStaticPaths } from "../../../../../core/blitzkit/mixStaticPaths"; +import { vfs } from "../../../../../core/blitzkit/vfs"; import { getStaticPaths as _getStaticPaths } from "../../../_index"; export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { diff --git a/packages/website/src/pages/[...chunk_api]/api/tanks/[id]/_index.ts b/packages/website/src/pages/[...chunk_api]/api/tanks/[id]/_index.ts index 7d2956bbb..55f270db2 100644 --- a/packages/website/src/pages/[...chunk_api]/api/tanks/[id]/_index.ts +++ b/packages/website/src/pages/[...chunk_api]/api/tanks/[id]/_index.ts @@ -1,5 +1,5 @@ -import { api } from "../../../../core/blitzkit/api"; -import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; +import { api } from "../../../../../core/blitzkit/api"; +import { mixStaticPaths } from "../../../../../core/blitzkit/mixStaticPaths"; import { getStaticPaths as _getStaticPaths } from "../../../_index"; export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { diff --git a/packages/website/src/pages/[...chunk_api]/api/tanks/[id]/collision.glb.ts b/packages/website/src/pages/[...chunk_api]/api/tanks/[id]/collision.glb.ts index 7bff4a081..85b6d1b67 100644 --- a/packages/website/src/pages/[...chunk_api]/api/tanks/[id]/collision.glb.ts +++ b/packages/website/src/pages/[...chunk_api]/api/tanks/[id]/collision.glb.ts @@ -11,7 +11,7 @@ import type { AbstractVFS } from "@blitzkit/core/src/blitzkit/vfs/abstract"; import { Accessor, Document, Node, NodeIO, Scene } from "@gltf-transform/core"; import type { APIContext } from "astro"; import { times } from "lodash-es"; -import { vfs } from "../../../../core/blitzkit/vfs"; +import { vfs } from "../../../../../core/blitzkit/vfs"; export { getStaticPaths } from "./_index"; diff --git a/packages/website/src/pages/[...chunk_api]/api/tanks/[id]/icons/big.webp.ts b/packages/website/src/pages/[...chunk_api]/api/tanks/[id]/icons/big.webp.ts index c2b517a63..14b74ba7d 100644 --- a/packages/website/src/pages/[...chunk_api]/api/tanks/[id]/icons/big.webp.ts +++ b/packages/website/src/pages/[...chunk_api]/api/tanks/[id]/icons/big.webp.ts @@ -5,8 +5,8 @@ import { } from "@blitzkit/core"; import type { APIContext } from "astro"; import sharp from "sharp"; -import { parsePackedSpriteRect } from "../../../../../core/blitz/parsePackedSpriteRect"; -import { vfs } from "../../../../../core/blitzkit/vfs"; +import { parsePackedSpriteRect } from "../../../../../../core/blitz/parsePackedSpriteRect"; +import { vfs } from "../../../../../../core/blitzkit/vfs"; export { getStaticPaths } from "../_index"; diff --git a/packages/website/src/pages/[...chunk_api]/api/tanks/[id]/icons/small.webp.ts b/packages/website/src/pages/[...chunk_api]/api/tanks/[id]/icons/small.webp.ts index 16725b661..7d250f16f 100644 --- a/packages/website/src/pages/[...chunk_api]/api/tanks/[id]/icons/small.webp.ts +++ b/packages/website/src/pages/[...chunk_api]/api/tanks/[id]/icons/small.webp.ts @@ -4,7 +4,7 @@ import { type VehicleDefinitionList, } from "@blitzkit/core"; import type { APIContext } from "astro"; -import { vfs } from "../../../../../core/blitzkit/vfs"; +import { vfs } from "../../../../../../core/blitzkit/vfs"; import { extractPackedTankIcon } from "./big.webp"; export { getStaticPaths } from "../_index"; diff --git a/packages/website/src/pages/[...chunk_api]/api/tanks/[id]/model.glb.ts b/packages/website/src/pages/[...chunk_api]/api/tanks/[id]/model.glb.ts index 684f4b375..181fb4cb5 100644 --- a/packages/website/src/pages/[...chunk_api]/api/tanks/[id]/model.glb.ts +++ b/packages/website/src/pages/[...chunk_api]/api/tanks/[id]/model.glb.ts @@ -20,7 +20,7 @@ import type { APIContext } from "astro"; import { times } from "lodash-es"; import { dirname } from "path"; import sharp from "sharp"; -import { vfs } from "../../../../core/blitzkit/vfs"; +import { vfs } from "../../../../../core/blitzkit/vfs"; import { vertexAttributeGLTFName, vertexAttributeGltfVectorSizes, diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/auth/[provider]/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/auth/[provider]/index.astro index 0fe29196b..e74158384 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/auth/[provider]/index.astro +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/auth/[provider]/index.astro @@ -1,18 +1,16 @@ --- import { Flex, Heading, Text } from "@radix-ui/themes"; -import type { GetStaticPaths } from "astro"; import { PageWrapper } from "../../../../../components/PageWrapper"; +import { mixStaticPaths } from "../../../../../core/blitzkit/mixStaticPaths"; import BaseLayout from "../../../../../layouts/BaseLayout.astro"; -import { getStaticPaths as parentGetStaticPaths } from "../../index.astro"; +import { getStaticPaths as _getStaticPaths } from "../../index.astro"; import { AUTH_PROVIDER_NAMES, AUTH_PROVIDERS, Authorize } from "./_index"; -export const getStaticPaths = (() => { - const parentPaths = parentGetStaticPaths(); - - return AUTH_PROVIDERS.map((provider) => - parentPaths.map((parent) => ({ params: { ...parent.params, provider } })), - ).flat(); -}) satisfies GetStaticPaths; +export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { + return AUTH_PROVIDERS.map((provider) => ({ + params: { provider }, + })); +}); const { provider } = Astro.params; const { locale } = Astro.props; diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/docs/[directory]/[document]/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/docs/[directory]/[document]/index.astro index 9858033b6..9c00a0faa 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/docs/[directory]/[document]/index.astro +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/docs/[directory]/[document]/index.astro @@ -1,39 +1,32 @@ --- import { AstroSeo } from "@astrolib/seo"; -import type { GetStaticPaths } from "astro"; import { readFile } from "fs/promises"; +import { mixStaticPaths } from "../../../../../../core/blitzkit/mixStaticPaths"; import BaseLayout from "../../../../../../layouts/BaseLayout.astro"; -import { getStaticPaths as parentGetStaticPaths } from "../../../index.astro"; +import { getStaticPaths as _getStaticPaths } from "../../../index.astro"; import { Page } from "./_index"; -export const getStaticPaths = (async () => { - const parentPaths = parentGetStaticPaths(); +export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { const ROOT = "../../../../../../../../../docs/"; const files = import.meta.glob("../../../../../../../../../docs/**/*.md", { eager: true, }); - return ( - await Promise.all( - Object.keys(files).map(async (file) => { - const couple = file.replace(ROOT, "").split("/"); - couple[1] = couple[1].replace(".md", ""); - const content = await readFile( - `../../docs/${couple[0]}/${couple[1]}.md`, - ).then((buffer) => buffer.toString()); + return await Promise.all( + Object.keys(files).map(async (file) => { + const couple = file.replace(ROOT, "").split("/"); + couple[1] = couple[1].replace(".md", ""); + const content = await readFile( + `../../docs/${couple[0]}/${couple[1]}.md`, + ).then((buffer) => buffer.toString()); - return parentPaths.map((parent) => ({ - params: { - ...parent.params, - directory: couple[0], - document: couple[1], - }, - props: { ...parent.props, content }, - })); - }), - ) - ).flat(); -}) satisfies GetStaticPaths; + return { + params: { directory: couple[0], document: couple[1] }, + props: { content }, + }; + }), + ); +}); const { content, locale } = Astro.props; diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/embed/[embed]/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/embed/[embed]/index.astro index d53acc9bf..9bb51e3e7 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/embed/[embed]/index.astro +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/embed/[embed]/index.astro @@ -1,26 +1,17 @@ --- import { AstroSeo } from "@astrolib/seo"; -import type { GetStaticPaths } from "astro"; import { capitalize } from "lodash-es"; import { embedConfigurations } from "../../../../../constants/embeds"; +import { mixStaticPaths } from "../../../../../core/blitzkit/mixStaticPaths"; import BaseLayout from "../../../../../layouts/BaseLayout.astro"; -import { getStaticPaths as parentGetStaticPaths } from "../index.astro"; +import { getStaticPaths as _getStaticPaths } from "../index.astro"; import { Page } from "./_index"; -export const getStaticPaths = (() => { - const parentPaths = parentGetStaticPaths(); - - return Object.keys(embedConfigurations) - .map((embed) => - parentPaths.map((parent) => ({ - params: { - ...parent.params, - embed: embed as keyof typeof embedConfigurations, - }, - })), - ) - .flat(); -}) satisfies GetStaticPaths; +export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { + return Object.keys(embedConfigurations).map((embed) => ({ + params: { embed: embed as keyof typeof embedConfigurations }, + })); +}); const name = capitalize(Astro.params.embed); const title = `BlitzKit - ${name} embed`; diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/tanks/[slug]/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/tanks/[slug]/index.astro index 9f6193fb3..3f5c07555 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/tanks/[slug]/index.astro +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/tanks/[slug]/index.astro @@ -8,30 +8,25 @@ import { } from "@blitzkit/core"; import { unwrapper } from "@blitzkit/i18n"; import { literals } from "@blitzkit/i18n/src/literals"; -import type { GetStaticPaths } from "astro"; import { load } from "cheerio"; import { api } from "../../../../../core/blitzkit/api"; +import { mixStaticPaths } from "../../../../../core/blitzkit/mixStaticPaths"; import { tankCharacteristics } from "../../../../../core/blitzkit/tankCharacteristics"; import { getStrings } from "../../../../../core/i18n/getStrings"; import BaseLayout from "../../../../../layouts/BaseLayout.astro"; import { genericDefaultEquipmentMatrix } from "../../../../../stores/duel/constants"; import type { TankGuide } from "../../../../../types/tankGuide"; -import { getStaticPaths as parentGetStaticPaths } from "../index.astro"; +import { getStaticPaths as _getStaticPaths } from "../index.astro"; import { Page } from "./_index"; -export const getStaticPaths = (async () => { +export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { const tankDefinitions = await api.tankDefinitions(); - const parentPaths = parentGetStaticPaths(); - return Object.values(tankDefinitions.tanks) - .map((tank) => - parentPaths.map((parent) => ({ - params: { ...parent.params, slug: tank.slug }, - props: { ...parent.props, id: tank.id }, - })), - ) - .flat(); -}) satisfies GetStaticPaths; + return Object.values(tankDefinitions.tanks).map((tank) => ({ + params: { slug: tank.slug }, + props: { id: tank.id }, + })); +}); const { id, locale } = Astro.props; const strings = getStrings(locale); diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/tools/embed/[embed]/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/tools/embed/[embed]/index.astro index 4222d3a2b..e1b439586 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/tools/embed/[embed]/index.astro +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/tools/embed/[embed]/index.astro @@ -1,23 +1,14 @@ --- -import type { GetStaticPaths } from "astro"; import { embedConfigurations } from "../../../../../../constants/embeds"; +import { mixStaticPaths } from "../../../../../../core/blitzkit/mixStaticPaths"; import LegacyRedirect from "../../../../../../layouts/LegacyRedirect.astro"; -import { getStaticPaths as parentGetStaticPaths } from "../index.astro"; +import { getStaticPaths as _getStaticPaths } from "../index.astro"; -export const getStaticPaths = (() => { - const parentPaths = parentGetStaticPaths(); - - return Object.keys(embedConfigurations) - .map((embed) => - parentPaths.map((parent) => ({ - params: { - ...parent.params, - embed: embed as keyof typeof embedConfigurations, - }, - })), - ) - .flat(); -}) satisfies GetStaticPaths; +export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { + return Object.keys(embedConfigurations).map((embed) => ({ + params: { embed }, + })); +}); const { embed } = Astro.params; const { locale } = Astro.props; diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/tools/tankopedia/[id]/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/tools/tankopedia/[id]/index.astro index 2c0979e68..3315a4174 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/tools/tankopedia/[id]/index.astro +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/tools/tankopedia/[id]/index.astro @@ -1,22 +1,14 @@ --- -import type { GetStaticPaths } from "astro"; import { api } from "../../../../../../core/blitzkit/api"; +import { mixStaticPaths } from "../../../../../../core/blitzkit/mixStaticPaths"; import LegacyRedirect from "../../../../../../layouts/LegacyRedirect.astro"; -import { getStaticPaths as parentGetStaticPaths } from "../index.astro"; +import { getStaticPaths as _getStaticPaths } from "../index.astro"; -export const getStaticPaths = (async () => { - const tankDefinitions = await api.tankDefinitions(); - const parentPaths = parentGetStaticPaths(); - - return Object.values(tankDefinitions.tanks) - .map((tank) => - parentPaths.map((parent) => ({ - params: { ...parent.params, id: tank.id }, - props: parent.props, - })), - ) - .flat(); -}) satisfies GetStaticPaths; +export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { + return Object.values(tankDefinitions.tanks).map(({ id }) => ({ + params: { id }, + })); +}); const { id } = Astro.params; const { locale } = Astro.props; From 13cda7aec0d8f0e88b7c209701b74565680d3bd3 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Thu, 6 Aug 2026 19:37:07 -0500 Subject: [PATCH 078/147] environment configs --- .gitignore | 2 +- environments/beta.json | 14 ++++++++++++++ environments/development.json | 11 +++++++++++ environments/opentest.json | 14 ++++++++++++++ environments/preview.json | 14 ++++++++++++++ environments/production.json | 14 ++++++++++++++ 6 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 environments/beta.json create mode 100644 environments/development.json create mode 100644 environments/opentest.json create mode 100644 environments/preview.json create mode 100644 environments/production.json diff --git a/.gitignore b/.gitignore index 2124182fd..bd34ba898 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ node_modules _node_modules package-lock.json -*.env +.env /test-results/ /playwright-report/ /playwright/.cache/ diff --git a/environments/beta.json b/environments/beta.json new file mode 100644 index 000000000..bc2910448 --- /dev/null +++ b/environments/beta.json @@ -0,0 +1,14 @@ +{ + "paths": { + "default": null, + "api": null + }, + "nodes": { + "default": "blitzkit/deploy-node-beta.blitzkit.app", + "api": "blitzkit/deploy-node-api.beta.blitzkit.app" + }, + "aliases": { + "api": "https://api.beta.blitzkit.app", + "static": "https://static.beta.blitzkit.app" + } +} diff --git a/environments/development.json b/environments/development.json new file mode 100644 index 000000000..06aff4bef --- /dev/null +++ b/environments/development.json @@ -0,0 +1,11 @@ +{ + "paths": { + "default": null, + "api": "api" + }, + "nodes": {}, + "aliases": { + "api": "/api", + "static": "https://static.blitzkit.app" + } +} diff --git a/environments/opentest.json b/environments/opentest.json new file mode 100644 index 000000000..4857ad043 --- /dev/null +++ b/environments/opentest.json @@ -0,0 +1,14 @@ +{ + "paths": { + "default": null, + "api": null + }, + "nodes": { + "default": "blitzkit/deploy-node-opentest.blitzkit.app", + "api": "blitzkit/deploy-node-api.opentest.blitzkit.app" + }, + "aliases": { + "api": "https://api.opentest.blitzkit.app", + "static": "https://static.opentest.blitzkit.app" + } +} diff --git a/environments/preview.json b/environments/preview.json new file mode 100644 index 000000000..79a76960b --- /dev/null +++ b/environments/preview.json @@ -0,0 +1,14 @@ +{ + "paths": { + "default": null, + "api": null + }, + "nodes": { + "default": "blitzkit/deploy-node-preview.blitzkit.app", + "api": "blitzkit/deploy-node-api.preview.blitzkit.app" + }, + "aliases": { + "api": "https://api.preview.blitzkit.app", + "static": "https://static.preview.blitzkit.app" + } +} diff --git a/environments/production.json b/environments/production.json new file mode 100644 index 000000000..17e40a56b --- /dev/null +++ b/environments/production.json @@ -0,0 +1,14 @@ +{ + "paths": { + "default": null, + "api": null + }, + "nodes": { + "default": "blitzkit/deploy-node-blitzkit.app", + "api": "blitzkit/deploy-node-api.blitzkit.app" + }, + "aliases": { + "api": "https://api.blitzkit.app", + "static": "https://static.blitzkit.app" + } +} From b26c5137f3f42feb64aceb1824701f99672dc4e5 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Thu, 6 Aug 2026 20:01:50 -0500 Subject: [PATCH 079/147] aliases --- environments/development.json | 2 +- packages/core/src/blitz/getLeagueFromScore.ts | 5 - packages/core/src/blitz/getRatingInfo.ts | 42 ---- packages/core/src/blitz/getRatingLeague.ts | 29 --- packages/core/src/blitz/getRatingNeighbors.ts | 27 --- packages/core/src/blitz/index.ts | 6 - .../core/src/blitz/isOnGoingRatingSeason.ts | 6 - .../src/blitz/searchCurrentRatingPlayers.ts | 40 ---- packages/core/src/blitzkit/alias.ts | 13 ++ packages/core/src/blitzkit/api/client.ts | 31 ++- .../api/server/10_galleryDefinitions.ts | 2 +- packages/core/src/blitzkit/environment.ts | 33 ++++ .../blitzkit/getArchivedLatestSeasonNumber.ts | 49 ----- .../src/blitzkit/getArchivedRatingInfo.ts | 30 --- packages/core/src/blitzkit/index.ts | 2 + packages/core/src/blitzkit/tankIcon.ts | 4 +- .../scripts/src/buildRatingLeaderboard.ts | 169 ----------------- packages/scripts/src/setupDepotDownloader.ts | 39 ---- .../website/src/components/Compare/Body.tsx | 6 +- .../src/components/Compare/CompareTable.tsx | 19 +- .../src/components/CrewSkillManager.tsx | 4 +- .../website/src/components/Gallery/Card.tsx | 7 +- packages/website/src/components/Guesser.tsx | 6 +- .../components/ModuleButtons/ModuleButton.tsx | 3 +- .../ModuleButtons/ProvisionButton.tsx | 3 +- .../components/ModuleButtons/ShellButton.tsx | 3 +- .../website/src/components/SmallTankIcon.tsx | 4 +- .../website/src/components/TankCard/index.tsx | 9 +- .../TankSearch/components/FilterControl.tsx | 39 ++-- .../Characteristics/components/Firepower.tsx | 4 +- .../components/ShellSwitcher.tsx | 3 +- .../components/RicochetVisualizer/index.tsx | 4 +- .../components/Equalizer.tsx | 3 +- .../components/Miscellaneous.tsx | 5 +- .../components/Modules.tsx | 8 +- .../components/Tankopedia/GameModeSection.tsx | 15 +- .../components/DynamicArmorSwitcher.tsx | 3 +- .../HeroSection/components/Options/index.tsx | 9 +- .../components/TankSandboxLoader.tsx | 3 +- .../MetaSection/components/Listing.tsx | 6 +- .../Stars/components/PartialStar.tsx | 25 --- .../MetaSection/components/Stars/index.tsx | 44 ----- .../components/Votes/components/StarRow.tsx | 18 -- .../Votes/components/VoteCaster.tsx | 179 ------------------ .../MetaSection/components/Votes/index.tsx | 91 --------- .../Tankopedia/MetaSection/index.tsx | 48 +++-- .../TechTreeSection/components/Node.tsx | 10 +- .../Tankopedia/TechTreeSection/index.tsx | 6 +- .../src/core/blitzkit/chunkedStaticPaths.ts | 7 +- packages/website/src/hooks/useModel.ts | 3 +- packages/website/src/hooks/useTankVotes.ts | 36 ---- .../[...locale]/mixer/_index.tsx | 3 +- .../[...locale]/tanks/[slug]/index.astro | 3 +- 53 files changed, 236 insertions(+), 932 deletions(-) delete mode 100644 packages/core/src/blitz/getLeagueFromScore.ts delete mode 100644 packages/core/src/blitz/getRatingInfo.ts delete mode 100644 packages/core/src/blitz/getRatingLeague.ts delete mode 100644 packages/core/src/blitz/getRatingNeighbors.ts delete mode 100644 packages/core/src/blitz/isOnGoingRatingSeason.ts delete mode 100644 packages/core/src/blitz/searchCurrentRatingPlayers.ts create mode 100644 packages/core/src/blitzkit/alias.ts create mode 100644 packages/core/src/blitzkit/environment.ts delete mode 100644 packages/core/src/blitzkit/getArchivedLatestSeasonNumber.ts delete mode 100644 packages/core/src/blitzkit/getArchivedRatingInfo.ts delete mode 100644 packages/scripts/src/buildRatingLeaderboard.ts delete mode 100644 packages/scripts/src/setupDepotDownloader.ts delete mode 100644 packages/website/src/components/Tankopedia/MetaSection/components/Stars/components/PartialStar.tsx delete mode 100644 packages/website/src/components/Tankopedia/MetaSection/components/Stars/index.tsx delete mode 100644 packages/website/src/components/Tankopedia/MetaSection/components/Votes/components/StarRow.tsx delete mode 100644 packages/website/src/components/Tankopedia/MetaSection/components/Votes/components/VoteCaster.tsx delete mode 100644 packages/website/src/components/Tankopedia/MetaSection/components/Votes/index.tsx delete mode 100644 packages/website/src/hooks/useTankVotes.ts diff --git a/environments/development.json b/environments/development.json index 06aff4bef..7d6265bf6 100644 --- a/environments/development.json +++ b/environments/development.json @@ -5,7 +5,7 @@ }, "nodes": {}, "aliases": { - "api": "/api", + "api": "https://api.beta.blitzkit.app", "static": "https://static.blitzkit.app" } } diff --git a/packages/core/src/blitz/getLeagueFromScore.ts b/packages/core/src/blitz/getLeagueFromScore.ts deleted file mode 100644 index 46d578d01..000000000 --- a/packages/core/src/blitz/getLeagueFromScore.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { LEAGUES } from "./rating"; - -export function getLeagueFromScore(score: number) { - return LEAGUES.find(({ minScore }) => score >= minScore)!; -} diff --git a/packages/core/src/blitz/getRatingInfo.ts b/packages/core/src/blitz/getRatingInfo.ts deleted file mode 100644 index 0b89949ef..000000000 --- a/packages/core/src/blitz/getRatingInfo.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { context } from "../blitzkit/context"; -import { patientFetch } from "../blitzkit/patientFetch"; -import type { RatingLeague, RatingReward } from "./rating"; -import type { Region } from "./regions"; -import { regionToRegionSubdomain } from "./regionToRegionSubdomain"; - -export type RatingInfo = - | { - detail: undefined; - - title: string; - icon: string | null; - start_at: string; - finish_at: string; - current_season: number; - updated_at: string; - count: number; - - rewards: RatingReward[]; - leagues: RatingLeague[]; - } - | { detail: { error: string } }; - -const cache: Partial> = {}; - -export async function getRatingInfo(region: Region) { - if (cache[region]) return cache[region]!; - - const response = await patientFetch( - context === "website" - ? `/api/${region}/rating/current/info` - : `https://${regionToRegionSubdomain( - region - )}.wotblitz.com/en/api/rating-leaderboards/season/`, - undefined - ); - const data = (await response.json()) as RatingInfo; - - cache[region] = data; - - return data; -} diff --git a/packages/core/src/blitz/getRatingLeague.ts b/packages/core/src/blitz/getRatingLeague.ts deleted file mode 100644 index 83c249f7d..000000000 --- a/packages/core/src/blitz/getRatingLeague.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { context } from "../blitzkit/context"; -import { patientFetch } from "../blitzkit/patientFetch"; -import type { RatingPlayer } from "../statistics"; -import type { LeagueTop } from "./rating"; -import type { Region } from "./regions"; -import { regionToRegionSubdomain } from "./regionToRegionSubdomain"; - -export async function getRatingLeague( - region: Region, - league: number, - usePatientFetcher = false -) { - const fetcher = (url: string) => - usePatientFetcher - ? patientFetch(url, undefined, { cache: "no-store" }) - : fetch(url, { cache: "no-store" }); - const response = await fetcher( - context === "website" - ? `/api/${region}/rating/current/league/${league}` - : `https://${regionToRegionSubdomain( - region - )}.wotblitz.com/en/api/rating-leaderboards/league/${league}/top/` - ); - const json = await response.json(); - - return context === "website" - ? (json as RatingPlayer[]) - : (json as LeagueTop).result; -} diff --git a/packages/core/src/blitz/getRatingNeighbors.ts b/packages/core/src/blitz/getRatingNeighbors.ts deleted file mode 100644 index 4c2d08523..000000000 --- a/packages/core/src/blitz/getRatingNeighbors.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { patientFetch } from "../blitzkit/patientFetch"; -import { withCORSProxy } from "../blitzkit/withCORSProxy"; -import type { RatingNeighbors } from "./rating"; -import type { Region } from "./regions"; -import { regionToRegionSubdomain } from "./regionToRegionSubdomain"; - -export async function getRatingNeighbors( - region: Region, - id: number, - count: number, - usePatientFetcher = false -) { - const fetcher = (url: string) => - usePatientFetcher - ? patientFetch(url, undefined, { cache: "no-store" }) - : fetch(url, { cache: "no-store" }); - const response = await fetcher( - withCORSProxy( - `https://${regionToRegionSubdomain( - region - )}.wotblitz.com/en/api/rating-leaderboards/user/${id}/?neighbors=${count}` - ) - ); - const json = (await response.json()) as RatingNeighbors; - - return json; -} diff --git a/packages/core/src/blitz/index.ts b/packages/core/src/blitz/index.ts index 6b8193288..00aaabe43 100644 --- a/packages/core/src/blitz/index.ts +++ b/packages/core/src/blitz/index.ts @@ -10,13 +10,8 @@ export * from "./getAccountAchievements"; export * from "./getAccountInfo"; export * from "./getClanAccountInfo"; export * from "./getClanInfo"; -export * from "./getLeagueFromScore"; -export * from "./getRatingInfo"; -export * from "./getRatingLeague"; -export * from "./getRatingNeighbors"; export * from "./getTankStats"; export * from "./idToRegion"; -export * from "./isOnGoingRatingSeason"; export * from "./isValidBlitzId"; export * from "./nations"; export * from "./normalizeExtra"; @@ -28,6 +23,5 @@ export * from "./regions"; export * from "./regionSubdomainToRegion"; export * from "./regionToRegionSubdomain"; export * from "./searchClansAcrossRegions"; -export * from "./searchCurrentRatingPlayers"; export * from "./searchPlayersAcrossRegions"; export * from "./toUniqueId"; diff --git a/packages/core/src/blitz/isOnGoingRatingSeason.ts b/packages/core/src/blitz/isOnGoingRatingSeason.ts deleted file mode 100644 index dbbfaa69e..000000000 --- a/packages/core/src/blitz/isOnGoingRatingSeason.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { getRatingInfo } from "./getRatingInfo"; - -export async function isOnGoingRatingSeason() { - const ratingInfo = await getRatingInfo("com"); - return ratingInfo.detail === undefined; -} diff --git a/packages/core/src/blitz/searchCurrentRatingPlayers.ts b/packages/core/src/blitz/searchCurrentRatingPlayers.ts deleted file mode 100644 index b40ae6e47..000000000 --- a/packages/core/src/blitz/searchCurrentRatingPlayers.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { context } from "../blitzkit/context"; -import { type Region } from "./regions"; -import { regionToRegionSubdomain } from "./regionToRegionSubdomain"; - -type CurrentRatingPlayerSearch = Record< - number, - { - spa_id: number; - nickname: string; - clan_tag: string; - } & ( - | { - skip: true; - } - | { - skip: false; - mmr: number; - season_number: number; - calibrationBattlesLeft: number; - number: number; - updated_at: string; - neighbors: []; - } - ) ->; - -export async function searchCurrentRatingPlayers(region: Region, name: string) { - const encodedSearch = encodeURIComponent(name); - const response = await fetch( - context === "website" - ? `/api/${region}/rating/current/search/${encodedSearch}` - : `https://${regionToRegionSubdomain( - region - )}.wotblitz.com/en/api/rating-leaderboards/search/?prefix=${encodedSearch}`, - { cache: "no-store" } - ); - const accountList = (await response.json()) as CurrentRatingPlayerSearch; - - return accountList; -} diff --git a/packages/core/src/blitzkit/alias.ts b/packages/core/src/blitzkit/alias.ts new file mode 100644 index 000000000..119ada258 --- /dev/null +++ b/packages/core/src/blitzkit/alias.ts @@ -0,0 +1,13 @@ +import { environmentConfig } from "@blitzkit/core"; + +export function alias(alias: string, path: string) { + const environment = environmentConfig(); + + if (!(alias in environment.aliases)) { + throw new Error(`Invalid alias "${alias}"`); + } + + const base = environment.aliases[alias]; + + return `${base}${path}`; +} diff --git a/packages/core/src/blitzkit/api/client.ts b/packages/core/src/blitzkit/api/client.ts index 2a28275a4..d71fb728d 100644 --- a/packages/core/src/blitzkit/api/client.ts +++ b/packages/core/src/blitzkit/api/client.ts @@ -1,3 +1,4 @@ +import { alias } from "@blitzkit/core"; import { fetchPB } from "../../protobuf"; import { CamouflageDefinitions, @@ -14,38 +15,50 @@ import { BlitzKitAPI } from "./base"; export class ClientBlitzKitAPI extends BlitzKitAPI { gameDefinitions() { - return fetchPB("/api/definitions/game.pb", GameDefinitions); + return fetchPB(alias("api", "/definitions/game.pb"), GameDefinitions); } consumableDefinitions() { - return fetchPB("/api/definitions/consumables.pb", ConsumableDefinitions); + return fetchPB( + alias("api", "/definitions/consumables.pb"), + ConsumableDefinitions, + ); } tankDefinitions() { - return fetchPB("/api/definitions/tanks.pb", TankDefinitions); + return fetchPB(alias("api", "/definitions/tanks.pb"), TankDefinitions); } camouflageDefinitions() { - return fetchPB("/api/definitions/camouflage.pb", CamouflageDefinitions); + return fetchPB( + alias("api", "/definitions/camouflage.pb"), + CamouflageDefinitions, + ); } provisionDefinitions() { - return fetchPB("/api/definitions/provisions.pb", ProvisionDefinitions); + return fetchPB( + alias("api", "/definitions/provisions.pb"), + ProvisionDefinitions, + ); } modelDefinitions() { - return fetchPB("/api/definitions/models.pb", ModelDefinitions); + return fetchPB(alias("api", "/definitions/models.pb"), ModelDefinitions); } skillDefinitions() { - return fetchPB("/api/definitions/skills.pb", SkillDefinitions); + return fetchPB(alias("api", "/definitions/skills.pb"), SkillDefinitions); } equipmentDefinitions() { - return fetchPB("/api/definitions/equipment.pb", EquipmentDefinitions); + return fetchPB( + alias("api", "/definitions/equipment.pb"), + EquipmentDefinitions, + ); } galleryDefinitions() { - return fetchPB("/api/definitions/gallery.pb", Gallery); + return fetchPB(alias("api", "/definitions/gallery.pb"), Gallery); } } diff --git a/packages/core/src/blitzkit/api/server/10_galleryDefinitions.ts b/packages/core/src/blitzkit/api/server/10_galleryDefinitions.ts index fede7e381..40cf9295e 100644 --- a/packages/core/src/blitzkit/api/server/10_galleryDefinitions.ts +++ b/packages/core/src/blitzkit/api/server/10_galleryDefinitions.ts @@ -1,8 +1,8 @@ import { Avatar, fetchGlossary, Gallery } from "@blitzkit/core"; -import { ServerBlitzKitAPI9 } from "@blitzkit/core/src/blitzkit/api/server/9_gameDefinitions"; import locales from "@blitzkit/i18n/locales.json"; import { extname } from "path"; import { Cache } from "./0_base"; +import { ServerBlitzKitAPI9 } from "./9_gameDefinitions"; export abstract class ServerBlitzKitAPI10 extends ServerBlitzKitAPI9 { @Cache() diff --git a/packages/core/src/blitzkit/environment.ts b/packages/core/src/blitzkit/environment.ts new file mode 100644 index 000000000..2fffb5c5c --- /dev/null +++ b/packages/core/src/blitzkit/environment.ts @@ -0,0 +1,33 @@ +import { assertSecret } from "@blitzkit/core"; + +interface EnvironmentConfig { + paths: Record; + nodes: Record; + aliases: Record; +} + +export const environments = import.meta.glob( + "../../../../environments/*.json", + { + eager: true, + import: "default", + }, +) as Record; +const root = "../../../../environments"; + +for (const key in environments) { + const trimmed = key.slice(root.length + 1, -5); + + environments[trimmed] = environments[key]; + delete environments[key]; +} + +export function environmentConfig() { + const environment = assertSecret(import.meta.env.ENVIRONMENT); + + if (!(environment in environments)) { + throw new Error(`Invalid environment "${environment}"`); + } + + return environments[environment]; +} diff --git a/packages/core/src/blitzkit/getArchivedLatestSeasonNumber.ts b/packages/core/src/blitzkit/getArchivedLatestSeasonNumber.ts deleted file mode 100644 index 460a81f01..000000000 --- a/packages/core/src/blitzkit/getArchivedLatestSeasonNumber.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { getRatingInfo } from "../blitz/getRatingInfo"; -import { assertSecret } from "./assertSecret"; - -interface GitHubTrees { - tree: { path: string; url: string }[]; -} - -let cachedLatestArchivedSeasonNumber: number | null = null; - -export async function getArchivedLatestSeasonNumber() { - if (cachedLatestArchivedSeasonNumber !== null) { - return cachedLatestArchivedSeasonNumber; - } - - const ratingInfo = await getRatingInfo("com"); - - if (ratingInfo.detail) { - const regionsURL = ( - (await fetch( - `https://api.github.com/repos/${assertSecret( - import.meta.env.PUBLIC_ASSET_REPO - )}/git/trees/main` - ).then((response) => response.json())) as GitHubTrees - ).tree.find(({ path }) => path === "regions")!.url; - const comURL = ( - (await fetch(regionsURL).then((response) => - response.json() - )) as GitHubTrees - ).tree.find(({ path }) => path === "com")!.url; - const ratingURL = ( - (await fetch(comURL).then((response) => response.json())) as { - tree: [{ url: string }]; - } - ).tree[0].url; - const latestSeason = Math.max( - ...( - (await fetch(ratingURL).then((response) => response.json())) as { - tree: { path: string }[]; - } - ).tree.map(({ path }) => Number(path)) - ); - - cachedLatestArchivedSeasonNumber = latestSeason; - } else { - cachedLatestArchivedSeasonNumber = ratingInfo.current_season - 1; - } - - return cachedLatestArchivedSeasonNumber; -} diff --git a/packages/core/src/blitzkit/getArchivedRatingInfo.ts b/packages/core/src/blitzkit/getArchivedRatingInfo.ts deleted file mode 100644 index 782838baf..000000000 --- a/packages/core/src/blitzkit/getArchivedRatingInfo.ts +++ /dev/null @@ -1,30 +0,0 @@ -// import { RatingInfo, Region, asset } from '@blitzkit/core'; - -// const cache: Record< -// Region, -// Record< -// number, -// RatingInfo & { -// detail: undefined; -// } -// > -// > = { -// com: {}, -// eu: {}, -// asia: {}, -// }; - -// export async function getArchivedRatingInfo(region: Region, season: number) { -// if (!cache[region][season]) { -// const info = await fetch( -// asset(`regions/${region}/rating/${season}/info.cdon.lz4`), -// ) -// .then((response) => response.arrayBuffer()) -// .then((buffer) => -// superDecompress(buffer), -// ); -// cache[region][season] = info; -// } - -// return cache[region][season]; -// } diff --git a/packages/core/src/blitzkit/index.ts b/packages/core/src/blitzkit/index.ts index a60517d32..700e57018 100644 --- a/packages/core/src/blitzkit/index.ts +++ b/packages/core/src/blitzkit/index.ts @@ -1,3 +1,4 @@ +export * from "./alias"; export * from "./assertSecret"; export * from "./availableProvisions"; export * from "./averageDefinitions"; @@ -7,6 +8,7 @@ export * from "./createDefaultSkills"; export * from "./degressiveStat"; export * from "./discoveredIdDefinitions"; export * from "./emblemURL"; +export * from "./environment"; export * from "./eventManager"; export * from "./getTimeDaysAgo"; export * from "./isExplosive"; diff --git a/packages/core/src/blitzkit/tankIcon.ts b/packages/core/src/blitzkit/tankIcon.ts index 6fef51e05..8eef1203d 100644 --- a/packages/core/src/blitzkit/tankIcon.ts +++ b/packages/core/src/blitzkit/tankIcon.ts @@ -1,5 +1,7 @@ +import { alias } from "@blitzkit/core"; + export const BLITZKIT_TANK_ICON_SIZE = { width: 40 * 16, height: 30 * 16 }; export function tankIcon(id: number, size: "big" | "small" = "big") { - return `/api/tanks/${id}/icons/${size}.webp`; + return alias("api", `/tanks/${id}/icons/${size}.webp`); } diff --git a/packages/scripts/src/buildRatingLeaderboard.ts b/packages/scripts/src/buildRatingLeaderboard.ts deleted file mode 100644 index 4cd4081e1..000000000 --- a/packages/scripts/src/buildRatingLeaderboard.ts +++ /dev/null @@ -1,169 +0,0 @@ -import { - getAccountInfo, - patientFetchJSON, - RatingInfo, - RatingLeaderboard, - RatingLeaderboardEntryV1, - RatingLeaderboardEntryV2, - RatingNeighbors, - RatingPlayer, - Region, - REGIONS, - regionToRegionSubdomain, -} from "@blitzkit/core"; -import { chunk } from "lodash-es"; -import { argv } from "process"; -import { AssetUploader } from "./core/github/assetUploader"; - -const region = argv - .find((arg) => arg.startsWith("--region=")) - ?.split("=")[1] as Region | undefined; - -if (region === undefined || !REGIONS.includes(region)) { - throw new Error("No valid region specified"); -} - -console.log(`Building rating leaderboard for ${region}`); - -const regionSubdomain = regionToRegionSubdomain(region); -const NEIGHBORS = 2 ** 8; -const PLAYERS = await patientFetchJSON( - `https://${regionSubdomain}.wotblitz.com/en/api/rating-leaderboards/season/` -).then((data) => (data.detail === undefined ? data.count : undefined)); - -if (PLAYERS === undefined) { - throw new Error("No current rating season running"); -} - -const players: Record = {}; -const entriesV1: RatingLeaderboardEntryV1[] = []; - -let registered = 0; - -async function branchFromPlayer(id: number) { - const { neighbors } = await patientFetchJSON( - `https://${regionSubdomain}.wotblitz.com/en/api/rating-leaderboards/user/${id}/?neighbors=${NEIGHBORS}` - ); - - const firstPlayer = neighbors[0]; - const lastPlayer = neighbors[neighbors.length - 1]; - - const branches: Promise[] = []; - if (!players[firstPlayer.number]) - branches.push(branchFromPlayer(firstPlayer.spa_id)); - if (!players[lastPlayer.number]) - branches.push(branchFromPlayer(lastPlayer.spa_id)); - - neighbors.forEach((neighbor) => { - if (players[neighbor.number]) return; - - players[neighbor.number] = neighbor; - registered++; - }); - - console.log( - `Registered ${registered} / ${PLAYERS} (${( - (registered / PLAYERS!) * - 100 - ).toFixed(2)}%)` - ); - - await Promise.all(branches); -} - -async function branchFromLeague(id: number) { - const { result } = await patientFetchJSON<{ result: RatingPlayer[] }>( - `https://${regionSubdomain}.wotblitz.com/en/api/rating-leaderboards/league/${id}/top/` - ); - - const firstPlayer = result[0]; - const lastPlayer = result.at(-1)!; - - await Promise.all([ - branchFromPlayer(firstPlayer.spa_id), - branchFromPlayer(lastPlayer.spa_id), - ]); -} - -console.log("Branching from leagues..."); -await Promise.all([ - branchFromLeague(0), - branchFromLeague(1), - branchFromLeague(2), - branchFromLeague(3), - branchFromLeague(4), -]); - -console.log("Converting to an array..."); -for (let index = 0; index < PLAYERS; index++) { - const listing = players[index + 1]; - if (listing) { - entriesV1[index] = { id: listing.spa_id, score: listing.score }; - } else { - console.warn(`Position ${index + 1} had no player`); - entriesV1[index] = { id: 0, score: 0 }; - } -} - -console.log("Requesting further stats..."); -const entries: RatingLeaderboardEntryV2[] = []; -let chunkIndex = 0; -for (const leaderboardChunk of chunk(entriesV1, 100)) { - console.log( - `Chunk ${chunkIndex + 1} of ${Math.ceil(entriesV1.length / 100)}` - ); - - const stats = await getAccountInfo( - region, - leaderboardChunk.map(({ id }) => id), - ["statistics.rating"] - ); - - stats.forEach((stat, index) => { - const trueIndex = chunkIndex * 100 + index; - - if ( - leaderboardChunk[index].id === 0 || - stat?.statistics.rating === undefined - ) { - entries[trueIndex] = { - battles: 0, - damage: 0, - id: 0, - kills: 0, - score: 0, - survived: 0, - wins: 0, - }; - } else { - entries[trueIndex] = { - id: leaderboardChunk[index].id, - score: leaderboardChunk[index].score, - battles: stat.statistics.rating.battles, - damage: stat.statistics.rating.damage_dealt, - kills: stat.statistics.rating.frags, - survived: stat.statistics.rating.survived_battles, - wins: stat.statistics.rating.wins, - }; - } - }); - - chunkIndex++; -} - -const info = await patientFetchJSON( - `https://${regionSubdomain}.wotblitz.com/en/api/rating-leaderboards/season/` -); -const normalizedServer = regionSubdomain === "na" ? "com" : regionSubdomain; -const content = RatingLeaderboard.encode({ - version: { $case: "v2", value: { entries } }, -}).finish(); - -using uploader = new AssetUploader(`rating leaderboard ${regionSubdomain}`); - -await uploader.add({ - path: `regions/${normalizedServer}/rating/${info.current_season}.pb`, - content, -}); - -await uploader.flush(); diff --git a/packages/scripts/src/setupDepotDownloader.ts b/packages/scripts/src/setupDepotDownloader.ts deleted file mode 100644 index 72ef9322b..000000000 --- a/packages/scripts/src/setupDepotDownloader.ts +++ /dev/null @@ -1,39 +0,0 @@ -import decompress from "decompress"; -import { existsSync } from "fs"; -import { mkdir, readdir, writeFile } from "fs/promises"; - -if (!existsSync("../../temp")) { - console.log("Creating temp directory..."); - await mkdir("../../temp"); -} - -console.log("Downloading depot downloader..."); -await fetch( - "https://api.github.com/repos/SteamRE/DepotDownloader/releases/latest" -) - .then( - (response) => - response.json() as Promise<{ - assets: { browser_download_url: string; name: string }[]; - }> - ) - .then(({ assets }) => { - const asset = assets.find(({ name }) => name.includes("linux-x64")); - if (!asset) throw new Error("No asset found"); - return fetch(asset.browser_download_url); - }) - .then((response) => response.arrayBuffer()) - .then((arrayBuffer) => - writeFile("../../temp/depotDownloader.zip", new Uint8Array(arrayBuffer)) - ); - -console.log("Unzipping..."); -await decompress( - "../../temp/depotDownloader.zip", - "../../temp/depotDownloader" -); - -console.log( - "Download complete; files:", - await readdir("../../temp/depotDownloader") -); diff --git a/packages/website/src/components/Compare/Body.tsx b/packages/website/src/components/Compare/Body.tsx index a3e559ce4..ee88cd4e4 100644 --- a/packages/website/src/components/Compare/Body.tsx +++ b/packages/website/src/components/Compare/Body.tsx @@ -1,3 +1,4 @@ +import { alias } from "@blitzkit/core"; import { Flex, Heading, IconButton, Table } from "@radix-ui/themes"; import type { TankCharacteristics } from "../../core/blitzkit/tankCharacteristics"; import { useLocale } from "../../hooks/useLocale"; @@ -57,7 +58,10 @@ export function Body({ stats }: BodyProps) { alt={unwrap(thisShell.name!)} width={16} height={16} - src={`/api/icons/shells/${thisShell.icon}.webp`} + src={alias( + "api", + `/icons/shells/${thisShell.icon}.webp`, + )} /> ))} diff --git a/packages/website/src/components/Compare/CompareTable.tsx b/packages/website/src/components/Compare/CompareTable.tsx index 87123a411..59d7ab555 100644 --- a/packages/website/src/components/Compare/CompareTable.tsx +++ b/packages/website/src/components/Compare/CompareTable.tsx @@ -1,4 +1,4 @@ -import { availableProvisions, permanentSkills } from "@blitzkit/core"; +import { alias, availableProvisions, permanentSkills } from "@blitzkit/core"; import { checkConsumableProvisionInclusivity } from "@blitzkit/core/src/blitzkit/checkConsumableProvisionInclusivity"; import { ComponentPlaceholderIcon } from "@radix-ui/react-icons"; import { @@ -231,7 +231,10 @@ export function CompareTable({ stats }: CompareTableProps) { alt={module} width={24} height={24} - src={`/api/icons/modules/${module}.webp`} + src={alias( + "api", + `/icons/modules/${module}.webp`, + )} style={{ marginLeft: index > 0 ? -8 : 0, objectFit: "contain", @@ -320,7 +323,10 @@ export function CompareTable({ stats }: CompareTableProps) { .name!, )} key={provision} - src={`/api//icons/provisions/${provision}.webp`} + src={alias( + "api", + `//icons/provisions/${provision}.webp`, + )} style={{ left: "50%", /** @@ -525,7 +531,10 @@ export function CompareTable({ stats }: CompareTableProps) { ].name!, )} key={consumable} - src={`/api/icons/consumables/${consumable}.webp`} + src={alias( + "api", + `/icons/consumables/${consumable}.webp`, + )} style={{ left: "50%", /** @@ -617,7 +626,7 @@ export function CompareTable({ stats }: CompareTableProps) { variant={camouflage ? "solid" : "outline"} > 0} discriminator={level === 0 ? undefined : romanize(level)} - icon={`/api/icons/skills/${skill}.webp`} + icon={alias("api", `/icons/skills/${skill}.webp`)} onClick={(event) => { if (!onChange) return; diff --git a/packages/website/src/components/Gallery/Card.tsx b/packages/website/src/components/Gallery/Card.tsx index aa524492d..dfc762b44 100644 --- a/packages/website/src/components/Gallery/Card.tsx +++ b/packages/website/src/components/Gallery/Card.tsx @@ -1,4 +1,4 @@ -import { Avatar } from "@blitzkit/core"; +import { alias, Avatar } from "@blitzkit/core"; import { Cross1Icon, DownloadIcon } from "@radix-ui/react-icons"; import { Dialog, @@ -18,7 +18,10 @@ type GalleryCardProps = MaybeSkeletonComponentProps<{ }>; export function GalleryCard(props: GalleryCardProps) { - const src = `/api/gallery/avatars/${!props.skeleton && `${props.avatar.id}${props.avatar.extension}`}`; + const src = alias( + "api", + `/gallery/avatars/${!props.skeleton && `${props.avatar.id}${props.avatar.extension}`}`, + ); const card = useRef(null!); const { unwrap } = useLocale(); diff --git a/packages/website/src/components/Guesser.tsx b/packages/website/src/components/Guesser.tsx index 93c02e1cf..09c1a3cd0 100644 --- a/packages/website/src/components/Guesser.tsx +++ b/packages/website/src/components/Guesser.tsx @@ -1,4 +1,5 @@ import { + alias, SEARCH_KEYS, TankDefinition, TIER_ROMAN_NUMERALS, @@ -149,7 +150,10 @@ export function Guesser() { prefix={ } discriminator={ diff --git a/packages/website/src/components/ModuleButtons/ModuleButton.tsx b/packages/website/src/components/ModuleButtons/ModuleButton.tsx index 3f343b0fe..007dd1e28 100644 --- a/packages/website/src/components/ModuleButtons/ModuleButton.tsx +++ b/packages/website/src/components/ModuleButtons/ModuleButton.tsx @@ -1,3 +1,4 @@ +import { alias } from "@blitzkit/core"; import { GenericTankComponentButton } from "./GenericTankComponentButton"; import type { TankComponentButtonProps } from "./TankComponentButton"; @@ -8,7 +9,7 @@ interface ModuleProps extends TankComponentButtonProps { export function ModuleButton({ module, ...props }: ModuleProps) { return ( ); diff --git a/packages/website/src/components/ModuleButtons/ShellButton.tsx b/packages/website/src/components/ModuleButtons/ShellButton.tsx index b9452e71f..3aeb7d9d4 100644 --- a/packages/website/src/components/ModuleButtons/ShellButton.tsx +++ b/packages/website/src/components/ModuleButtons/ShellButton.tsx @@ -1,3 +1,4 @@ +import { alias } from "@blitzkit/core"; import { GenericTankComponentButton } from "./GenericTankComponentButton"; import type { TankComponentButtonProps } from "./TankComponentButton"; @@ -8,7 +9,7 @@ interface ShellButtonProps extends TankComponentButtonProps { export function ShellButton({ shell, ...props }: ShellButtonProps) { return ( ); diff --git a/packages/website/src/components/SmallTankIcon.tsx b/packages/website/src/components/SmallTankIcon.tsx index 74fb89e33..144301eb2 100644 --- a/packages/website/src/components/SmallTankIcon.tsx +++ b/packages/website/src/components/SmallTankIcon.tsx @@ -1,3 +1,5 @@ +import { alias } from "@blitzkit/core"; + interface SmallTankIconProps { id: number; size?: number; @@ -17,7 +19,7 @@ export function SmallTankIcon({ id, size = HEIGHT }: SmallTankIconProps) { > {`Tank( className="tank-search-card" data-provide-link={provideLink} style={{ - backgroundImage: `url(/api/flags/scratched/${tank.nation}.webp)`, + backgroundImage: `url(${alias( + "api", + `/flags/scratched/${tank.nation}.webp`, + )})`, ...style, }} {...props} @@ -76,7 +79,7 @@ export const TankCard = forwardRef( > {unwrap(tank.name!)} diff --git a/packages/website/src/components/TankSearch/components/FilterControl.tsx b/packages/website/src/components/TankSearch/components/FilterControl.tsx index 1451bda88..d43a1c5b9 100644 --- a/packages/website/src/components/TankSearch/components/FilterControl.tsx +++ b/packages/website/src/components/TankSearch/components/FilterControl.tsx @@ -1,4 +1,5 @@ import { + alias, GunDefinition, ShellType, TANK_CLASSES, @@ -309,7 +310,7 @@ function NationsFilter() { width: "1.25em", height: "1.25em", }} - src={`/api/flags/circle/${nation}.webp`} + src={alias("api", `/flags/circle/${nation}.webp`)} /> ))} @@ -346,7 +347,7 @@ function NationsFilter() { right="0" mr="-2" style={{ - backgroundImage: `url(${`/api/flags/scratched/${nation}.webp`})`, + backgroundImage: `url(${alias("api", `/flags/scratched/${nation}.webp`)})`, backgroundSize: "100%", backgroundRepeat: "no-repeat", backgroundPosition: "5rem center", @@ -679,9 +680,10 @@ function IndividualShellFilter({ {shells[index] !== null && ( )} @@ -712,9 +714,12 @@ function IndividualShellFilter({ color={selected ? undefined : "gray"} > @@ -883,7 +888,7 @@ function ConsumablesFilter() { height: "1.25em", objectFit: "contain", }} - src={`/api/icons/consumables/${consumable}.webp`} + src={alias("api", `/icons/consumables/${consumable}.webp`)} /> ))} @@ -928,7 +933,7 @@ function ConsumablesFilter() { height: "1.25em", objectFit: "contain", }} - src={`/api/icons/consumables/${consumable}.webp`} + src={alias("api", `/icons/consumables/${consumable}.webp`)} /> {unwrap(consumableDefinition.name!)} @@ -977,7 +982,7 @@ function ProvisionsFilter() { height: "1.25em", objectFit: "contain", }} - src={`/api/icons/provisions/${provision}.webp`} + src={alias("api", `/icons/provisions/${provision}.webp`)} /> ))} @@ -1022,7 +1027,7 @@ function ProvisionsFilter() { height: "1.25em", objectFit: "contain", }} - src={`/api/icons/provisions/${provision}.webp`} + src={alias("api", `/icons/provisions/${provision}.webp`)} /> {unwrap(provisionDefinition.name!)} @@ -1151,7 +1156,10 @@ function GameModeAbilitiesFilter() { height: "1.25em", objectFit: "contain", }} - src={`/api/icons/consumables/${consumable}.webp`} + src={alias( + "api", + `/icons/consumables/${consumable}.webp`, + )} /> {unwrap(abilityDefinition.name!)} @@ -1200,7 +1208,10 @@ function GameModeAbilitiesFilter() { height: "1.25em", objectFit: "contain", }} - src={`/api/icons/provisions/${provision}.webp`} + src={alias( + "api", + `/icons/provisions/${provision}.webp`, + )} /> {unwrap(powerDefinition.name!)} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Firepower.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Firepower.tsx index 3e2378d5f..bcebbf061 100644 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Firepower.tsx +++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Firepower.tsx @@ -1,4 +1,4 @@ -import { isExplosive } from "@blitzkit/core"; +import { alias, isExplosive } from "@blitzkit/core"; import { literals } from "@blitzkit/i18n"; import { Flex, Heading, IconButton, Tooltip } from "@radix-ui/themes"; import { useLocale } from "../../../../../../hooks/useLocale"; @@ -62,7 +62,7 @@ export function Firepower({ alt={unwrap(thisShell.name!)} width={16} height={16} - src={`/api/icons/shells/${thisShell.icon}.webp`} + src={alias("api", `/icons/shells/${thisShell.icon}.webp`)} /> diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/RicochetVisualizer/components/ShellSwitcher.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/RicochetVisualizer/components/ShellSwitcher.tsx index 68667a0a0..69788ab6a 100644 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/RicochetVisualizer/components/ShellSwitcher.tsx +++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/RicochetVisualizer/components/ShellSwitcher.tsx @@ -1,3 +1,4 @@ +import { alias } from "@blitzkit/core"; import { Flex, IconButton, Tooltip } from "@radix-ui/themes"; import { useLocale } from "../../../../../../../../hooks/useLocale"; import { Duel } from "../../../../../../../../stores/duel"; @@ -37,7 +38,7 @@ export function ShellSwitcher() { {unwrap(thisShell.name!)} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/RicochetVisualizer/index.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/RicochetVisualizer/index.tsx index 52deb82c8..4b8cf3d5c 100644 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/RicochetVisualizer/index.tsx +++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/RicochetVisualizer/index.tsx @@ -1,4 +1,4 @@ -import { isExplosive } from "@blitzkit/core"; +import { alias, isExplosive } from "@blitzkit/core"; import { literals } from "@blitzkit/i18n"; import { Box, Flex, Text } from "@radix-ui/themes"; import { useRef, useState } from "react"; @@ -124,7 +124,7 @@ export function RicochetVisualizer({ stats }: StatsAcceptorProps) { /> {!tank.fixed_camouflage && ( )} 0} banner={ cooldownBooster === 0 diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Modules.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Modules.tsx index ef2e7fb2a..cd44eeb82 100644 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Modules.tsx +++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Modules.tsx @@ -1,4 +1,5 @@ import { + alias, EngineDefinition, formatCompact, GunDefinition, @@ -110,7 +111,7 @@ function ModuleButton({ {unlock.cost!.type} {unwrap(provisions.name!)} @@ -101,7 +108,7 @@ export function GameModeSection() { height: "1.5em", objectFit: "contain", }} - src={`/api/icons/consumables/${id}.webp`} + src={alias("api", `/icons/consumables/${id}.webp`)} alt={unwrap(consumable.name!)} /> {unwrap(consumable.name!)} diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/DynamicArmorSwitcher.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/DynamicArmorSwitcher.tsx index 6a5bef873..ec788d455 100644 --- a/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/DynamicArmorSwitcher.tsx +++ b/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/DynamicArmorSwitcher.tsx @@ -1,3 +1,4 @@ +import { alias } from "@blitzkit/core"; import { IconButton } from "@radix-ui/themes"; import { useArmor } from "../../../../../../hooks/useArmor"; import { Duel } from "../../../../../../stores/duel"; @@ -37,7 +38,7 @@ export function DynamicArmorSwitcher() { > Dynamic Armor {unwrap(thisShell.name!)} Calibrated Shells Enhanced Armor {label} diff --git a/packages/website/src/components/Tankopedia/MetaSection/components/Stars/components/PartialStar.tsx b/packages/website/src/components/Tankopedia/MetaSection/components/Stars/components/PartialStar.tsx deleted file mode 100644 index d2ee2bb22..000000000 --- a/packages/website/src/components/Tankopedia/MetaSection/components/Stars/components/PartialStar.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { StarFilledIcon, StarIcon } from '@radix-ui/react-icons'; -import { Box, type BoxProps } from '@radix-ui/themes'; - -type PartialStarProps = BoxProps & { - fill: number; -}; - -export function PartialStar({ fill, ...props }: PartialStarProps) { - return ( - - - {fill > 0 && ( - - - - )} - - ); -} diff --git a/packages/website/src/components/Tankopedia/MetaSection/components/Stars/index.tsx b/packages/website/src/components/Tankopedia/MetaSection/components/Stars/index.tsx deleted file mode 100644 index 06f4187d0..000000000 --- a/packages/website/src/components/Tankopedia/MetaSection/components/Stars/index.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import type { StarsInt } from '@blitzkit/core'; -import { type TextProps, Flex, Text } from '@radix-ui/themes'; -import { times } from 'lodash-es'; -import { PartialStar } from './components/PartialStar'; - -type StarsProps = TextProps & { - stars: number | null; - onCast?: (value: StarsInt) => void; -}; - -export function Stars({ stars, onCast, ...props }: StarsProps) { - let color: TextProps['color'] = 'green'; - - if (stars === null) color = 'gray'; - else if (stars < 1.5) color = 'tomato'; - else if (stars < 2.5) color = 'orange'; - else if (stars < 3.5) color = 'amber'; - else if (stars < 4.5) color = 'blue'; - - return ( - - - {stars !== null && - times(Math.floor(stars), (index) => ( - onCast?.((index + 1) as StarsInt)} - /> - ))} - {stars !== null && stars % 1 !== 0 && } - {times(5 - Math.ceil(stars ?? 0), (index) => ( - - onCast?.((Math.floor(stars ?? 0) + index + 1) as StarsInt) - } - /> - ))} - - - ); -} diff --git a/packages/website/src/components/Tankopedia/MetaSection/components/Votes/components/StarRow.tsx b/packages/website/src/components/Tankopedia/MetaSection/components/Votes/components/StarRow.tsx deleted file mode 100644 index 199e734b9..000000000 --- a/packages/website/src/components/Tankopedia/MetaSection/components/Votes/components/StarRow.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { Flex, Text } from '@radix-ui/themes'; -import { Stars } from '../../Stars'; - -interface StarRowProps { - stars: number | null; - children: string; -} - -export function StarRow({ stars, children }: StarRowProps) { - return ( - - - {children} - - - - ); -} diff --git a/packages/website/src/components/Tankopedia/MetaSection/components/Votes/components/VoteCaster.tsx b/packages/website/src/components/Tankopedia/MetaSection/components/Votes/components/VoteCaster.tsx deleted file mode 100644 index a036259cb..000000000 --- a/packages/website/src/components/Tankopedia/MetaSection/components/Votes/components/VoteCaster.tsx +++ /dev/null @@ -1,179 +0,0 @@ -import type { BlitzkitResponse, StarsInt } from "@blitzkit/core"; -import { PaperPlaneIcon } from "@radix-ui/react-icons"; -import { - AlertDialog, - Button, - Dialog, - Flex, - Spinner, - Text, -} from "@radix-ui/themes"; -import { useState } from "react"; -import { useLocale } from "../../../../../../hooks/useLocale"; -import { App } from "../../../../../../stores/app"; -import { Duel } from "../../../../../../stores/duel"; -import { Stars } from "../../Stars"; - -export function VoteCaster() { - const tank = Duel.use((state) => state.protagonist.tank); - const [easiness, setEasiness] = useState(null); - const [firepower, setFirepower] = useState(null); - const [maneuverability, setManeuverability] = useState(null); - const [survivability, setSurvivability] = useState(null); - // true = vote successful, false = vote failed, null = hide alert - const [postVote, setPostVote] = useState(null); - // true = open, false = closed, null = casting - const [showDialogue, setShowDialogue] = useState(false); - const { unwrap } = useLocale(); - - return ( - <> - - - - - - - Voting for {unwrap(tank.name!)} - - - - - Easiness{" "} - - {easiness === null ? "(empty)" : `(${easiness})`} - - - - - - - Firepower{" "} - - {firepower === null ? "(empty)" : `(${firepower})`} - - - - - - - Maneuverability{" "} - - {maneuverability === null - ? "(empty)" - : `(${maneuverability})`} - - - - - - - Survivability{" "} - - {survivability === null ? "(empty)" : `(${survivability})`} - - - - - - - - - - - - - - - - - setPostVote(null)} - > - {postVote === true && ( - - Vote casted - - It may take a few minutes for it to show up. - - - - - - - - - )} - - {postVote === false && ( - - Voting failed - - So sorry about this! If you're a nerd, feel free to check out the - console. - - - - - - - - - )} - - - ); -} diff --git a/packages/website/src/components/Tankopedia/MetaSection/components/Votes/index.tsx b/packages/website/src/components/Tankopedia/MetaSection/components/Votes/index.tsx deleted file mode 100644 index 5455a4327..000000000 --- a/packages/website/src/components/Tankopedia/MetaSection/components/Votes/index.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { formatCompact } from '@blitzkit/core'; -import { Flex, Heading, Skeleton, Text } from '@radix-ui/themes'; -import { memo } from 'react'; -import { useLocale } from '../../../../../hooks/useLocale'; -import { useTankVotes } from '../../../../../hooks/useTankVotes'; -import { App } from '../../../../../stores/app'; -import { Duel } from '../../../../../stores/duel'; -import { WargamingLoginButton } from '../../../../WargamingLoginButton'; -import { StarRow } from './components/StarRow'; -import { VoteCaster } from './components/VoteCaster'; - -export const Votes = memo( - () => { - const id = Duel.use((state) => state.protagonist.tank.id); - const votes = useTankVotes(id); - const wargaming = App.use((state) => state.logins.wargaming); - const { locale } = useLocale(); - - return ( - - - - {!votes && } - {votes && formatCompact(locale, votes.votes)}{' '} - {votes && (votes.votes > 1 ? 'votes' : 'vote')} - - - {!wargaming && ( - - Login to vote - - )} - {wargaming && } - - - - - - - - - - - - - - - - <> - {votes ? ( - wargaming ? ( - votes.last_updated ? ( - `You voted on ${new Date( - votes.last_updated, - ).toLocaleDateString()}` - ) : ( - `You haven't voted yet` - ) - ) : ( - 'Login to vote' - ) - ) : ( - - )} - - - - - - ); - }, - () => true, -); diff --git a/packages/website/src/components/Tankopedia/MetaSection/index.tsx b/packages/website/src/components/Tankopedia/MetaSection/index.tsx index 69f72f40d..ca25dbdfe 100644 --- a/packages/website/src/components/Tankopedia/MetaSection/index.tsx +++ b/packages/website/src/components/Tankopedia/MetaSection/index.tsx @@ -1,4 +1,9 @@ -import { TankPriceType, TankType, TIER_ROMAN_NUMERALS } from "@blitzkit/core"; +import { + alias, + TankPriceType, + TankType, + TIER_ROMAN_NUMERALS, +} from "@blitzkit/core"; import { Box, Flex } from "@radix-ui/themes"; import { Var } from "../../../core/radix/var"; import { useLocale } from "../../../hooks/useLocale"; @@ -35,7 +40,7 @@ export function MetaSection() { > { strings.common.nations[ @@ -130,7 +138,7 @@ export function MetaSection() { gold @@ -147,12 +155,15 @@ export function MetaSection() { {TankPriceType[protagonist.price!.type]} @@ -162,12 +173,15 @@ export function MetaSection() { {TankPriceType[protagonist.price!.type]} @@ -183,7 +197,7 @@ export function MetaSection() { xp diff --git a/packages/website/src/components/Tankopedia/TechTreeSection/components/Node.tsx b/packages/website/src/components/Tankopedia/TechTreeSection/components/Node.tsx index c5a66b044..5a710564e 100644 --- a/packages/website/src/components/Tankopedia/TechTreeSection/components/Node.tsx +++ b/packages/website/src/components/Tankopedia/TechTreeSection/components/Node.tsx @@ -1,4 +1,4 @@ -import { formatCompact, TIER_ROMAN_NUMERALS } from "@blitzkit/core"; +import { alias, formatCompact, TIER_ROMAN_NUMERALS } from "@blitzkit/core"; import { literals } from "@blitzkit/i18n/src/literals"; import { Box, Flex, Skeleton, Text } from "@radix-ui/themes"; import { awaitableAverageDefinitions } from "../../../../core/awaitables/averageDefinitions"; @@ -58,7 +58,7 @@ export function Node({ id, highlight, nextIds, skeleton }: NodeProps) { mx={highlight ? "2" : "0"} style={{ background: highlight - ? `url(${`/api/flags/fade/${tank.nation}.webp`})` + ? `url(${alias("api", `/flags/fade/${tank.nation}.webp`)})` : undefined, backgroundPosition: "center", backgroundSize: "cover", @@ -79,7 +79,7 @@ export function Node({ id, highlight, nextIds, skeleton }: NodeProps) { > {unwrap(tank.name!)} XP Silver XP Silver | TankVotes> = {}; - -export function useTankVotes(id: number) { - const [votes, setVotes] = useState(null); - const wargaming = App.use((state) => state.logins.wargaming); - - useEffect(() => { - if (!wargaming) return; - - if (id in cache) { - Promise.all([cache[id]]).then(([votes]) => setVotes(votes)); - } else { - const cacheable = fetch( - `/api/tank-voting/${id}/average${ - wargaming ? `?player=${wargaming.id}&token=${wargaming.token}` : '' - }`, - ) - .then( - (response) => response.json() as Promise>, - ) - .then(unwrapBlitzkitResponse); - cacheable.then(setVotes); - cache[id] = cacheable; - } - }, [id, wargaming]); - - return votes; -} diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/mixer/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/mixer/_index.tsx index caabc2184..2346fc2d9 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/mixer/_index.tsx +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/mixer/_index.tsx @@ -1,4 +1,5 @@ import { + alias, GunDefinition, TankDefinition, TIER_ROMAN_NUMERALS, @@ -110,7 +111,7 @@ function ModuleChooser({ height: "4em", objectFit: "contain", }} - src={`/api/tanks/${tank}/icons/big.webp`} + src={alias("api", `/tanks/${tank}/icons/big.webp`)} /> )} diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/tanks/[slug]/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/tanks/[slug]/index.astro index 3f5c07555..67727b72b 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/tanks/[slug]/index.astro +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/tanks/[slug]/index.astro @@ -1,6 +1,7 @@ --- import { AstroSeo } from "@astrolib/seo"; import { + alias, assertSecret, createDefaultProvisions, createDefaultSkills, @@ -51,7 +52,7 @@ const title = literals(strings.website.tools.tankopedia.tank_seo_title, { }); const url = import.meta.env.SITE + Astro.url.pathname; -const icon = `/api/tanks/${id}/icons/big.webp`; +const icon = alias("api", `/tanks/${id}/icons/big.webp`); const turret = tank.turrets.at(-1)!; const gun = turret.guns.at(-1)!; const shell = gun.shells[0]; From b367866a227f9d17313a4e87a045285fe1962f4d Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Thu, 6 Aug 2026 20:02:09 -0500 Subject: [PATCH 080/147] revert debug env --- environments/development.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/environments/development.json b/environments/development.json index 7d6265bf6..06aff4bef 100644 --- a/environments/development.json +++ b/environments/development.json @@ -5,7 +5,7 @@ }, "nodes": {}, "aliases": { - "api": "https://api.beta.blitzkit.app", + "api": "/api", "static": "https://static.blitzkit.app" } } From 10c8ac95a44344297a1771630fa6f0c635b98e8c Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Thu, 6 Aug 2026 20:10:29 -0500 Subject: [PATCH 081/147] move api --- .../{api => }/definitions/camouflage.pb.ts | 4 ++-- .../{api => }/definitions/consumables.pb.ts | 4 ++-- .../{api => }/definitions/equipment.pb.ts | 4 ++-- .../[...chunk_api]/{api => }/definitions/gallery.pb.ts | 4 ++-- .../[...chunk_api]/{api => }/definitions/game.pb.ts | 4 ++-- .../[...chunk_api]/{api => }/definitions/models.pb.ts | 4 ++-- .../{api => }/definitions/provisions.pb.ts | 4 ++-- .../[...chunk_api]/{api => }/definitions/skills.pb.ts | 4 ++-- .../[...chunk_api]/{api => }/definitions/tanks.pb.ts | 4 ++-- .../[...chunk_api]/{api => }/flags/circle/[id].webp.ts | 6 +++--- .../[...chunk_api]/{api => }/flags/fade/[id].webp.ts | 6 +++--- .../{api => }/flags/scratched/[id].webp.ts | 6 +++--- .../[...chunk_api]/{api => }/gallery/avatars/[name].ts | 4 ++-- .../{api => }/gamemodes/[id]/banner.webp.ts | 6 +++--- .../{api => }/icons/boosters/[id].webp.ts | 6 +++--- .../pages/[...chunk_api]/{api => }/icons/camo.webp.ts | 4 ++-- .../{api => }/icons/consumables/[id].webp.ts | 10 +++++----- .../{api => }/icons/currencies/[id].webp.ts | 6 +++--- .../{api => }/icons/equipment/[id].webp.ts | 6 +++--- .../{api => }/icons/modules/[id].webp.ts | 6 +++--- .../{api => }/icons/provisions/[id].webp.ts | 10 +++++----- .../[...chunk_api]/{api => }/icons/shells/[id].webp.ts | 6 +++--- .../[...chunk_api]/{api => }/icons/skills/[id].webp.ts | 6 +++--- .../[...chunk_api]/{api => }/tanks/[id]/_index.ts | 6 +++--- .../{api => }/tanks/[id]/collision.glb.ts | 2 +- .../{api => }/tanks/[id]/icons/big.webp.ts | 4 ++-- .../{api => }/tanks/[id]/icons/small.webp.ts | 2 +- .../[...chunk_api]/{api => }/tanks/[id]/model.glb.ts | 2 +- 28 files changed, 70 insertions(+), 70 deletions(-) rename packages/website/src/pages/[...chunk_api]/{api => }/definitions/camouflage.pb.ts (71%) rename packages/website/src/pages/[...chunk_api]/{api => }/definitions/consumables.pb.ts (71%) rename packages/website/src/pages/[...chunk_api]/{api => }/definitions/equipment.pb.ts (70%) rename packages/website/src/pages/[...chunk_api]/{api => }/definitions/gallery.pb.ts (68%) rename packages/website/src/pages/[...chunk_api]/{api => }/definitions/game.pb.ts (69%) rename packages/website/src/pages/[...chunk_api]/{api => }/definitions/models.pb.ts (69%) rename packages/website/src/pages/[...chunk_api]/{api => }/definitions/provisions.pb.ts (70%) rename packages/website/src/pages/[...chunk_api]/{api => }/definitions/skills.pb.ts (69%) rename packages/website/src/pages/[...chunk_api]/{api => }/definitions/tanks.pb.ts (69%) rename packages/website/src/pages/[...chunk_api]/{api => }/flags/circle/[id].webp.ts (82%) rename packages/website/src/pages/[...chunk_api]/{api => }/flags/fade/[id].webp.ts (79%) rename packages/website/src/pages/[...chunk_api]/{api => }/flags/scratched/[id].webp.ts (80%) rename packages/website/src/pages/[...chunk_api]/{api => }/gallery/avatars/[name].ts (93%) rename packages/website/src/pages/[...chunk_api]/{api => }/gamemodes/[id]/banner.webp.ts (89%) rename packages/website/src/pages/[...chunk_api]/{api => }/icons/boosters/[id].webp.ts (85%) rename packages/website/src/pages/[...chunk_api]/{api => }/icons/camo.webp.ts (59%) rename packages/website/src/pages/[...chunk_api]/{api => }/icons/consumables/[id].webp.ts (89%) rename packages/website/src/pages/[...chunk_api]/{api => }/icons/currencies/[id].webp.ts (90%) rename packages/website/src/pages/[...chunk_api]/{api => }/icons/equipment/[id].webp.ts (92%) rename packages/website/src/pages/[...chunk_api]/{api => }/icons/modules/[id].webp.ts (82%) rename packages/website/src/pages/[...chunk_api]/{api => }/icons/provisions/[id].webp.ts (89%) rename packages/website/src/pages/[...chunk_api]/{api => }/icons/shells/[id].webp.ts (85%) rename packages/website/src/pages/[...chunk_api]/{api => }/icons/skills/[id].webp.ts (82%) rename packages/website/src/pages/[...chunk_api]/{api => }/tanks/[id]/_index.ts (54%) rename packages/website/src/pages/[...chunk_api]/{api => }/tanks/[id]/collision.glb.ts (99%) rename packages/website/src/pages/[...chunk_api]/{api => }/tanks/[id]/icons/big.webp.ts (93%) rename packages/website/src/pages/[...chunk_api]/{api => }/tanks/[id]/icons/small.webp.ts (95%) rename packages/website/src/pages/[...chunk_api]/{api => }/tanks/[id]/model.glb.ts (99%) diff --git a/packages/website/src/pages/[...chunk_api]/api/definitions/camouflage.pb.ts b/packages/website/src/pages/[...chunk_api]/definitions/camouflage.pb.ts similarity index 71% rename from packages/website/src/pages/[...chunk_api]/api/definitions/camouflage.pb.ts rename to packages/website/src/pages/[...chunk_api]/definitions/camouflage.pb.ts index c4d2eb4a9..15dea60ce 100644 --- a/packages/website/src/pages/[...chunk_api]/api/definitions/camouflage.pb.ts +++ b/packages/website/src/pages/[...chunk_api]/definitions/camouflage.pb.ts @@ -1,7 +1,7 @@ import { CamouflageDefinitions } from "@blitzkit/core"; -import { api } from "../../../../core/blitzkit/api"; +import { api } from "../../../core/blitzkit/api"; -export { getStaticPaths } from "../../_index"; +export { getStaticPaths } from "../_index"; export async function GET() { const definitions = await api.camouflageDefinitions(); diff --git a/packages/website/src/pages/[...chunk_api]/api/definitions/consumables.pb.ts b/packages/website/src/pages/[...chunk_api]/definitions/consumables.pb.ts similarity index 71% rename from packages/website/src/pages/[...chunk_api]/api/definitions/consumables.pb.ts rename to packages/website/src/pages/[...chunk_api]/definitions/consumables.pb.ts index c9310030e..80b6adcf8 100644 --- a/packages/website/src/pages/[...chunk_api]/api/definitions/consumables.pb.ts +++ b/packages/website/src/pages/[...chunk_api]/definitions/consumables.pb.ts @@ -1,7 +1,7 @@ import { ConsumableDefinitions } from "@blitzkit/core"; -import { api } from "../../../../core/blitzkit/api"; +import { api } from "../../../core/blitzkit/api"; -export { getStaticPaths } from "../../_index"; +export { getStaticPaths } from "../_index"; export async function GET() { const definitions = await api.consumableDefinitions(); diff --git a/packages/website/src/pages/[...chunk_api]/api/definitions/equipment.pb.ts b/packages/website/src/pages/[...chunk_api]/definitions/equipment.pb.ts similarity index 70% rename from packages/website/src/pages/[...chunk_api]/api/definitions/equipment.pb.ts rename to packages/website/src/pages/[...chunk_api]/definitions/equipment.pb.ts index e76b07892..3691eb377 100644 --- a/packages/website/src/pages/[...chunk_api]/api/definitions/equipment.pb.ts +++ b/packages/website/src/pages/[...chunk_api]/definitions/equipment.pb.ts @@ -1,7 +1,7 @@ import { EquipmentDefinitions } from "@blitzkit/core"; -import { api } from "../../../../core/blitzkit/api"; +import { api } from "../../../core/blitzkit/api"; -export { getStaticPaths } from "../../_index"; +export { getStaticPaths } from "../_index"; export async function GET() { const definitions = await api.equipmentDefinitions(); diff --git a/packages/website/src/pages/[...chunk_api]/api/definitions/gallery.pb.ts b/packages/website/src/pages/[...chunk_api]/definitions/gallery.pb.ts similarity index 68% rename from packages/website/src/pages/[...chunk_api]/api/definitions/gallery.pb.ts rename to packages/website/src/pages/[...chunk_api]/definitions/gallery.pb.ts index 0b50dd3ff..49453741e 100644 --- a/packages/website/src/pages/[...chunk_api]/api/definitions/gallery.pb.ts +++ b/packages/website/src/pages/[...chunk_api]/definitions/gallery.pb.ts @@ -1,7 +1,7 @@ import { Gallery } from "@blitzkit/core"; -import { api } from "../../../../core/blitzkit/api"; +import { api } from "../../../core/blitzkit/api"; -export { getStaticPaths } from "../../_index"; +export { getStaticPaths } from "../_index"; export async function GET() { const definitions = await api.galleryDefinitions(); diff --git a/packages/website/src/pages/[...chunk_api]/api/definitions/game.pb.ts b/packages/website/src/pages/[...chunk_api]/definitions/game.pb.ts similarity index 69% rename from packages/website/src/pages/[...chunk_api]/api/definitions/game.pb.ts rename to packages/website/src/pages/[...chunk_api]/definitions/game.pb.ts index a557213eb..86200f605 100644 --- a/packages/website/src/pages/[...chunk_api]/api/definitions/game.pb.ts +++ b/packages/website/src/pages/[...chunk_api]/definitions/game.pb.ts @@ -1,7 +1,7 @@ import { GameDefinitions } from "@blitzkit/core"; -import { api } from "../../../../core/blitzkit/api"; +import { api } from "../../../core/blitzkit/api"; -export { getStaticPaths } from "../../_index"; +export { getStaticPaths } from "../_index"; export async function GET() { const definitions = await api.gameDefinitions(); diff --git a/packages/website/src/pages/[...chunk_api]/api/definitions/models.pb.ts b/packages/website/src/pages/[...chunk_api]/definitions/models.pb.ts similarity index 69% rename from packages/website/src/pages/[...chunk_api]/api/definitions/models.pb.ts rename to packages/website/src/pages/[...chunk_api]/definitions/models.pb.ts index 49d265d09..2399851bd 100644 --- a/packages/website/src/pages/[...chunk_api]/api/definitions/models.pb.ts +++ b/packages/website/src/pages/[...chunk_api]/definitions/models.pb.ts @@ -1,7 +1,7 @@ import { ModelDefinitions } from "@blitzkit/core"; -import { api } from "../../../../core/blitzkit/api"; +import { api } from "../../../core/blitzkit/api"; -export { getStaticPaths } from "../../_index"; +export { getStaticPaths } from "../_index"; export async function GET() { const definitions = await api.modelDefinitions(); diff --git a/packages/website/src/pages/[...chunk_api]/api/definitions/provisions.pb.ts b/packages/website/src/pages/[...chunk_api]/definitions/provisions.pb.ts similarity index 70% rename from packages/website/src/pages/[...chunk_api]/api/definitions/provisions.pb.ts rename to packages/website/src/pages/[...chunk_api]/definitions/provisions.pb.ts index b7884617f..c352900e1 100644 --- a/packages/website/src/pages/[...chunk_api]/api/definitions/provisions.pb.ts +++ b/packages/website/src/pages/[...chunk_api]/definitions/provisions.pb.ts @@ -1,7 +1,7 @@ import { ProvisionDefinitions } from "@blitzkit/core"; -import { api } from "../../../../core/blitzkit/api"; +import { api } from "../../../core/blitzkit/api"; -export { getStaticPaths } from "../../_index"; +export { getStaticPaths } from "../_index"; export async function GET() { const definitions = await api.provisionDefinitions(); diff --git a/packages/website/src/pages/[...chunk_api]/api/definitions/skills.pb.ts b/packages/website/src/pages/[...chunk_api]/definitions/skills.pb.ts similarity index 69% rename from packages/website/src/pages/[...chunk_api]/api/definitions/skills.pb.ts rename to packages/website/src/pages/[...chunk_api]/definitions/skills.pb.ts index 161f46f71..dd0baddec 100644 --- a/packages/website/src/pages/[...chunk_api]/api/definitions/skills.pb.ts +++ b/packages/website/src/pages/[...chunk_api]/definitions/skills.pb.ts @@ -1,7 +1,7 @@ import { SkillDefinitions } from "@blitzkit/core"; -import { api } from "../../../../core/blitzkit/api"; +import { api } from "../../../core/blitzkit/api"; -export { getStaticPaths } from "../../_index"; +export { getStaticPaths } from "../_index"; export async function GET() { const definitions = await api.skillDefinitions(); diff --git a/packages/website/src/pages/[...chunk_api]/api/definitions/tanks.pb.ts b/packages/website/src/pages/[...chunk_api]/definitions/tanks.pb.ts similarity index 69% rename from packages/website/src/pages/[...chunk_api]/api/definitions/tanks.pb.ts rename to packages/website/src/pages/[...chunk_api]/definitions/tanks.pb.ts index ce74498a5..5976401f9 100644 --- a/packages/website/src/pages/[...chunk_api]/api/definitions/tanks.pb.ts +++ b/packages/website/src/pages/[...chunk_api]/definitions/tanks.pb.ts @@ -1,7 +1,7 @@ import { TankDefinitions } from "@blitzkit/core"; -import { api } from "../../../../core/blitzkit/api"; +import { api } from "../../../core/blitzkit/api"; -export { getStaticPaths } from "../../_index"; +export { getStaticPaths } from "../_index"; export async function GET() { const definitions = await api.tankDefinitions(); diff --git a/packages/website/src/pages/[...chunk_api]/api/flags/circle/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/flags/circle/[id].webp.ts similarity index 82% rename from packages/website/src/pages/[...chunk_api]/api/flags/circle/[id].webp.ts rename to packages/website/src/pages/[...chunk_api]/flags/circle/[id].webp.ts index 0fc735f8c..c32a13501 100644 --- a/packages/website/src/pages/[...chunk_api]/api/flags/circle/[id].webp.ts +++ b/packages/website/src/pages/[...chunk_api]/flags/circle/[id].webp.ts @@ -1,8 +1,8 @@ import type { APIContext, GetStaticPathsItem } from "astro"; import sharp from "sharp"; -import { mixStaticPaths } from "../../../../../core/blitzkit/mixStaticPaths"; -import { vfs } from "../../../../../core/blitzkit/vfs"; -import { getStaticPaths as _getStaticPaths } from "../../../_index"; +import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; +import { vfs } from "../../../../core/blitzkit/vfs"; +import { getStaticPaths as _getStaticPaths } from "../../_index"; export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { const paths: GetStaticPathsItem[] = []; diff --git a/packages/website/src/pages/[...chunk_api]/api/flags/fade/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/flags/fade/[id].webp.ts similarity index 79% rename from packages/website/src/pages/[...chunk_api]/api/flags/fade/[id].webp.ts rename to packages/website/src/pages/[...chunk_api]/flags/fade/[id].webp.ts index fe6dadee5..e31a8ede0 100644 --- a/packages/website/src/pages/[...chunk_api]/api/flags/fade/[id].webp.ts +++ b/packages/website/src/pages/[...chunk_api]/flags/fade/[id].webp.ts @@ -1,7 +1,7 @@ import type { APIContext, GetStaticPathsItem } from "astro"; -import { mixStaticPaths } from "../../../../../core/blitzkit/mixStaticPaths"; -import { vfs } from "../../../../../core/blitzkit/vfs"; -import { getStaticPaths as _getStaticPaths } from "../../../_index"; +import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; +import { vfs } from "../../../../core/blitzkit/vfs"; +import { getStaticPaths as _getStaticPaths } from "../../_index"; export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { const paths: GetStaticPathsItem[] = []; diff --git a/packages/website/src/pages/[...chunk_api]/api/flags/scratched/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/flags/scratched/[id].webp.ts similarity index 80% rename from packages/website/src/pages/[...chunk_api]/api/flags/scratched/[id].webp.ts rename to packages/website/src/pages/[...chunk_api]/flags/scratched/[id].webp.ts index a002e6447..9edbdf433 100644 --- a/packages/website/src/pages/[...chunk_api]/api/flags/scratched/[id].webp.ts +++ b/packages/website/src/pages/[...chunk_api]/flags/scratched/[id].webp.ts @@ -1,7 +1,7 @@ import type { APIContext, GetStaticPathsItem } from "astro"; -import { mixStaticPaths } from "../../../../../core/blitzkit/mixStaticPaths"; -import { vfs } from "../../../../../core/blitzkit/vfs"; -import { getStaticPaths as _getStaticPaths } from "../../../_index"; +import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; +import { vfs } from "../../../../core/blitzkit/vfs"; +import { getStaticPaths as _getStaticPaths } from "../../_index"; export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { const paths: GetStaticPathsItem[] = []; diff --git a/packages/website/src/pages/[...chunk_api]/api/gallery/avatars/[name].ts b/packages/website/src/pages/[...chunk_api]/gallery/avatars/[name].ts similarity index 93% rename from packages/website/src/pages/[...chunk_api]/api/gallery/avatars/[name].ts rename to packages/website/src/pages/[...chunk_api]/gallery/avatars/[name].ts index 831bbd95c..032e1908d 100644 --- a/packages/website/src/pages/[...chunk_api]/api/gallery/avatars/[name].ts +++ b/packages/website/src/pages/[...chunk_api]/gallery/avatars/[name].ts @@ -2,8 +2,8 @@ import { fetchGlossary, type Avatar } from "@blitzkit/core"; import locales from "@blitzkit/i18n/locales.json"; import type { APIContext, GetStaticPathsItem } from "astro"; import { extname } from "path"; -import { mixStaticPaths } from "../../../../../core/blitzkit/mixStaticPaths"; -import { getStaticPaths as _getStaticPaths } from "../../../_index"; +import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; +import { getStaticPaths as _getStaticPaths } from "../../_index"; export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { const paths: GetStaticPathsItem[] = []; diff --git a/packages/website/src/pages/[...chunk_api]/api/gamemodes/[id]/banner.webp.ts b/packages/website/src/pages/[...chunk_api]/gamemodes/[id]/banner.webp.ts similarity index 89% rename from packages/website/src/pages/[...chunk_api]/api/gamemodes/[id]/banner.webp.ts rename to packages/website/src/pages/[...chunk_api]/gamemodes/[id]/banner.webp.ts index 03587e42a..e0383102d 100644 --- a/packages/website/src/pages/[...chunk_api]/api/gamemodes/[id]/banner.webp.ts +++ b/packages/website/src/pages/[...chunk_api]/gamemodes/[id]/banner.webp.ts @@ -1,9 +1,9 @@ import type { SquadBattleTypeStylesYaml } from "@blitzkit/core"; import type { APIContext, GetStaticPathsItem } from "astro"; import sharp from "sharp"; -import { mixStaticPaths } from "../../../../../core/blitzkit/mixStaticPaths"; -import { vfs } from "../../../../../core/blitzkit/vfs"; -import { getStaticPaths as _getStaticPaths } from "../../../_index"; +import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; +import { vfs } from "../../../../core/blitzkit/vfs"; +import { getStaticPaths as _getStaticPaths } from "../../_index"; export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { const paths: GetStaticPathsItem[] = []; diff --git a/packages/website/src/pages/[...chunk_api]/api/icons/boosters/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/icons/boosters/[id].webp.ts similarity index 85% rename from packages/website/src/pages/[...chunk_api]/api/icons/boosters/[id].webp.ts rename to packages/website/src/pages/[...chunk_api]/icons/boosters/[id].webp.ts index 5ee73c77e..2e3d176bb 100644 --- a/packages/website/src/pages/[...chunk_api]/api/icons/boosters/[id].webp.ts +++ b/packages/website/src/pages/[...chunk_api]/icons/boosters/[id].webp.ts @@ -1,8 +1,8 @@ import type { APIContext, GetStaticPathsItem } from "astro"; import sharp from "sharp"; -import { mixStaticPaths } from "../../../../../core/blitzkit/mixStaticPaths"; -import { vfs } from "../../../../../core/blitzkit/vfs"; -import { getStaticPaths as _getStaticPaths } from "../../../_index"; +import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; +import { vfs } from "../../../../core/blitzkit/vfs"; +import { getStaticPaths as _getStaticPaths } from "../../_index"; export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { const paths: GetStaticPathsItem[] = []; diff --git a/packages/website/src/pages/[...chunk_api]/api/icons/camo.webp.ts b/packages/website/src/pages/[...chunk_api]/icons/camo.webp.ts similarity index 59% rename from packages/website/src/pages/[...chunk_api]/api/icons/camo.webp.ts rename to packages/website/src/pages/[...chunk_api]/icons/camo.webp.ts index 4d644d246..acc583993 100644 --- a/packages/website/src/pages/[...chunk_api]/api/icons/camo.webp.ts +++ b/packages/website/src/pages/[...chunk_api]/icons/camo.webp.ts @@ -1,6 +1,6 @@ -import { vfs } from "../../../../core/blitzkit/vfs"; +import { vfs } from "../../../core/blitzkit/vfs"; -export { getStaticPaths } from "../../_index"; +export { getStaticPaths } from "../_index"; export async function GET() { const bytes = await vfs.file("Data/Gfx/UI/Hangar/IconCamouflage.packed.webp"); diff --git a/packages/website/src/pages/[...chunk_api]/api/icons/consumables/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/icons/consumables/[id].webp.ts similarity index 89% rename from packages/website/src/pages/[...chunk_api]/api/icons/consumables/[id].webp.ts rename to packages/website/src/pages/[...chunk_api]/icons/consumables/[id].webp.ts index 24074e115..8b02390b8 100644 --- a/packages/website/src/pages/[...chunk_api]/api/icons/consumables/[id].webp.ts +++ b/packages/website/src/pages/[...chunk_api]/icons/consumables/[id].webp.ts @@ -1,11 +1,11 @@ import type { ConsumablesCommon } from "@blitzkit/core"; import type { APIContext, GetStaticPathsItem } from "astro"; import sharp from "sharp"; -import { extractPackedIcon } from "../../../../../core/blitz/extractPackedIcon"; -import { parsePackedSpriteRect } from "../../../../../core/blitz/parsePackedSpriteRect"; -import { mixStaticPaths } from "../../../../../core/blitzkit/mixStaticPaths"; -import { vfs } from "../../../../../core/blitzkit/vfs"; -import { getStaticPaths as _getStaticPaths } from "../../../_index"; +import { extractPackedIcon } from "../../../../core/blitz/extractPackedIcon"; +import { parsePackedSpriteRect } from "../../../../core/blitz/parsePackedSpriteRect"; +import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; +import { vfs } from "../../../../core/blitzkit/vfs"; +import { getStaticPaths as _getStaticPaths } from "../../_index"; interface Mappings { Header: { version: number }; diff --git a/packages/website/src/pages/[...chunk_api]/api/icons/currencies/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/icons/currencies/[id].webp.ts similarity index 90% rename from packages/website/src/pages/[...chunk_api]/api/icons/currencies/[id].webp.ts rename to packages/website/src/pages/[...chunk_api]/icons/currencies/[id].webp.ts index c4b643c51..f6581c798 100644 --- a/packages/website/src/pages/[...chunk_api]/api/icons/currencies/[id].webp.ts +++ b/packages/website/src/pages/[...chunk_api]/icons/currencies/[id].webp.ts @@ -1,9 +1,9 @@ import { assertSecret, type BlitzGlossary } from "@blitzkit/core"; import type { APIContext, GetStaticPathsItem } from "astro"; import sharp from "sharp"; -import { mixStaticPaths } from "../../../../../core/blitzkit/mixStaticPaths"; -import { vfs } from "../../../../../core/blitzkit/vfs"; -import { getStaticPaths as _getStaticPaths } from "../../../_index"; +import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; +import { vfs } from "../../../../core/blitzkit/vfs"; +import { getStaticPaths as _getStaticPaths } from "../../_index"; const ICONS = [ ["currency_silver_m.packed.webp", "silver"], diff --git a/packages/website/src/pages/[...chunk_api]/api/icons/equipment/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/icons/equipment/[id].webp.ts similarity index 92% rename from packages/website/src/pages/[...chunk_api]/api/icons/equipment/[id].webp.ts rename to packages/website/src/pages/[...chunk_api]/icons/equipment/[id].webp.ts index c91b73476..f47fc889b 100644 --- a/packages/website/src/pages/[...chunk_api]/api/icons/equipment/[id].webp.ts +++ b/packages/website/src/pages/[...chunk_api]/icons/equipment/[id].webp.ts @@ -1,9 +1,9 @@ import type { OptionalDevices } from "@blitzkit/core"; import type { APIContext, GetStaticPathsItem } from "astro"; import sharp from "sharp"; -import { mixStaticPaths } from "../../../../../core/blitzkit/mixStaticPaths"; -import { vfs } from "../../../../../core/blitzkit/vfs"; -import { getStaticPaths as _getStaticPaths } from "../../../_index"; +import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; +import { vfs } from "../../../../core/blitzkit/vfs"; +import { getStaticPaths as _getStaticPaths } from "../../_index"; interface Mappings { Header: { version: number }; diff --git a/packages/website/src/pages/[...chunk_api]/api/icons/modules/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/icons/modules/[id].webp.ts similarity index 82% rename from packages/website/src/pages/[...chunk_api]/api/icons/modules/[id].webp.ts rename to packages/website/src/pages/[...chunk_api]/icons/modules/[id].webp.ts index cdd338714..9237a57a2 100644 --- a/packages/website/src/pages/[...chunk_api]/api/icons/modules/[id].webp.ts +++ b/packages/website/src/pages/[...chunk_api]/icons/modules/[id].webp.ts @@ -1,8 +1,8 @@ import type { APIContext, GetStaticPathsItem } from "astro"; import sharp from "sharp"; -import { mixStaticPaths } from "../../../../../core/blitzkit/mixStaticPaths"; -import { vfs } from "../../../../../core/blitzkit/vfs"; -import { getStaticPaths as _getStaticPaths } from "../../../_index"; +import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; +import { vfs } from "../../../../core/blitzkit/vfs"; +import { getStaticPaths as _getStaticPaths } from "../../_index"; export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { const paths: GetStaticPathsItem[] = []; diff --git a/packages/website/src/pages/[...chunk_api]/api/icons/provisions/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/icons/provisions/[id].webp.ts similarity index 89% rename from packages/website/src/pages/[...chunk_api]/api/icons/provisions/[id].webp.ts rename to packages/website/src/pages/[...chunk_api]/icons/provisions/[id].webp.ts index 97cde6260..db38f389a 100644 --- a/packages/website/src/pages/[...chunk_api]/api/icons/provisions/[id].webp.ts +++ b/packages/website/src/pages/[...chunk_api]/icons/provisions/[id].webp.ts @@ -1,11 +1,11 @@ import type { ProvisionsCommon } from "@blitzkit/core"; import type { APIContext, GetStaticPathsItem } from "astro"; import sharp from "sharp"; -import { extractPackedIcon } from "../../../../../core/blitz/extractPackedIcon"; -import { parsePackedSpriteRect } from "../../../../../core/blitz/parsePackedSpriteRect"; -import { mixStaticPaths } from "../../../../../core/blitzkit/mixStaticPaths"; -import { vfs } from "../../../../../core/blitzkit/vfs"; -import { getStaticPaths as _getStaticPaths } from "../../../_index"; +import { extractPackedIcon } from "../../../../core/blitz/extractPackedIcon"; +import { parsePackedSpriteRect } from "../../../../core/blitz/parsePackedSpriteRect"; +import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; +import { vfs } from "../../../../core/blitzkit/vfs"; +import { getStaticPaths as _getStaticPaths } from "../../_index"; interface Mappings { Header: { version: number }; diff --git a/packages/website/src/pages/[...chunk_api]/api/icons/shells/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/icons/shells/[id].webp.ts similarity index 85% rename from packages/website/src/pages/[...chunk_api]/api/icons/shells/[id].webp.ts rename to packages/website/src/pages/[...chunk_api]/icons/shells/[id].webp.ts index bd63dbde8..f8c520d04 100644 --- a/packages/website/src/pages/[...chunk_api]/api/icons/shells/[id].webp.ts +++ b/packages/website/src/pages/[...chunk_api]/icons/shells/[id].webp.ts @@ -1,8 +1,8 @@ import type { APIContext, GetStaticPathsItem } from "astro"; import sharp from "sharp"; -import { mixStaticPaths } from "../../../../../core/blitzkit/mixStaticPaths"; -import { vfs } from "../../../../../core/blitzkit/vfs"; -import { getStaticPaths as _getStaticPaths } from "../../../_index"; +import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; +import { vfs } from "../../../../core/blitzkit/vfs"; +import { getStaticPaths as _getStaticPaths } from "../../_index"; export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { const paths: GetStaticPathsItem[] = []; diff --git a/packages/website/src/pages/[...chunk_api]/api/icons/skills/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/icons/skills/[id].webp.ts similarity index 82% rename from packages/website/src/pages/[...chunk_api]/api/icons/skills/[id].webp.ts rename to packages/website/src/pages/[...chunk_api]/icons/skills/[id].webp.ts index 8caab5718..37fbd1e87 100644 --- a/packages/website/src/pages/[...chunk_api]/api/icons/skills/[id].webp.ts +++ b/packages/website/src/pages/[...chunk_api]/icons/skills/[id].webp.ts @@ -1,9 +1,9 @@ import type { TankmenAvatar } from "@blitzkit/core"; import type { APIContext, GetStaticPathsItem } from "astro"; import sharp from "sharp"; -import { mixStaticPaths } from "../../../../../core/blitzkit/mixStaticPaths"; -import { vfs } from "../../../../../core/blitzkit/vfs"; -import { getStaticPaths as _getStaticPaths } from "../../../_index"; +import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; +import { vfs } from "../../../../core/blitzkit/vfs"; +import { getStaticPaths as _getStaticPaths } from "../../_index"; export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { const paths: GetStaticPathsItem[] = []; diff --git a/packages/website/src/pages/[...chunk_api]/api/tanks/[id]/_index.ts b/packages/website/src/pages/[...chunk_api]/tanks/[id]/_index.ts similarity index 54% rename from packages/website/src/pages/[...chunk_api]/api/tanks/[id]/_index.ts rename to packages/website/src/pages/[...chunk_api]/tanks/[id]/_index.ts index 55f270db2..8ea202dcb 100644 --- a/packages/website/src/pages/[...chunk_api]/api/tanks/[id]/_index.ts +++ b/packages/website/src/pages/[...chunk_api]/tanks/[id]/_index.ts @@ -1,6 +1,6 @@ -import { api } from "../../../../../core/blitzkit/api"; -import { mixStaticPaths } from "../../../../../core/blitzkit/mixStaticPaths"; -import { getStaticPaths as _getStaticPaths } from "../../../_index"; +import { api } from "../../../../core/blitzkit/api"; +import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; +import { getStaticPaths as _getStaticPaths } from "../../_index"; export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { const tanks = await api.tankDefinitions(); diff --git a/packages/website/src/pages/[...chunk_api]/api/tanks/[id]/collision.glb.ts b/packages/website/src/pages/[...chunk_api]/tanks/[id]/collision.glb.ts similarity index 99% rename from packages/website/src/pages/[...chunk_api]/api/tanks/[id]/collision.glb.ts rename to packages/website/src/pages/[...chunk_api]/tanks/[id]/collision.glb.ts index 85b6d1b67..7bff4a081 100644 --- a/packages/website/src/pages/[...chunk_api]/api/tanks/[id]/collision.glb.ts +++ b/packages/website/src/pages/[...chunk_api]/tanks/[id]/collision.glb.ts @@ -11,7 +11,7 @@ import type { AbstractVFS } from "@blitzkit/core/src/blitzkit/vfs/abstract"; import { Accessor, Document, Node, NodeIO, Scene } from "@gltf-transform/core"; import type { APIContext } from "astro"; import { times } from "lodash-es"; -import { vfs } from "../../../../../core/blitzkit/vfs"; +import { vfs } from "../../../../core/blitzkit/vfs"; export { getStaticPaths } from "./_index"; diff --git a/packages/website/src/pages/[...chunk_api]/api/tanks/[id]/icons/big.webp.ts b/packages/website/src/pages/[...chunk_api]/tanks/[id]/icons/big.webp.ts similarity index 93% rename from packages/website/src/pages/[...chunk_api]/api/tanks/[id]/icons/big.webp.ts rename to packages/website/src/pages/[...chunk_api]/tanks/[id]/icons/big.webp.ts index 14b74ba7d..c2b517a63 100644 --- a/packages/website/src/pages/[...chunk_api]/api/tanks/[id]/icons/big.webp.ts +++ b/packages/website/src/pages/[...chunk_api]/tanks/[id]/icons/big.webp.ts @@ -5,8 +5,8 @@ import { } from "@blitzkit/core"; import type { APIContext } from "astro"; import sharp from "sharp"; -import { parsePackedSpriteRect } from "../../../../../../core/blitz/parsePackedSpriteRect"; -import { vfs } from "../../../../../../core/blitzkit/vfs"; +import { parsePackedSpriteRect } from "../../../../../core/blitz/parsePackedSpriteRect"; +import { vfs } from "../../../../../core/blitzkit/vfs"; export { getStaticPaths } from "../_index"; diff --git a/packages/website/src/pages/[...chunk_api]/api/tanks/[id]/icons/small.webp.ts b/packages/website/src/pages/[...chunk_api]/tanks/[id]/icons/small.webp.ts similarity index 95% rename from packages/website/src/pages/[...chunk_api]/api/tanks/[id]/icons/small.webp.ts rename to packages/website/src/pages/[...chunk_api]/tanks/[id]/icons/small.webp.ts index 7d250f16f..16725b661 100644 --- a/packages/website/src/pages/[...chunk_api]/api/tanks/[id]/icons/small.webp.ts +++ b/packages/website/src/pages/[...chunk_api]/tanks/[id]/icons/small.webp.ts @@ -4,7 +4,7 @@ import { type VehicleDefinitionList, } from "@blitzkit/core"; import type { APIContext } from "astro"; -import { vfs } from "../../../../../../core/blitzkit/vfs"; +import { vfs } from "../../../../../core/blitzkit/vfs"; import { extractPackedTankIcon } from "./big.webp"; export { getStaticPaths } from "../_index"; diff --git a/packages/website/src/pages/[...chunk_api]/api/tanks/[id]/model.glb.ts b/packages/website/src/pages/[...chunk_api]/tanks/[id]/model.glb.ts similarity index 99% rename from packages/website/src/pages/[...chunk_api]/api/tanks/[id]/model.glb.ts rename to packages/website/src/pages/[...chunk_api]/tanks/[id]/model.glb.ts index 181fb4cb5..684f4b375 100644 --- a/packages/website/src/pages/[...chunk_api]/api/tanks/[id]/model.glb.ts +++ b/packages/website/src/pages/[...chunk_api]/tanks/[id]/model.glb.ts @@ -20,7 +20,7 @@ import type { APIContext } from "astro"; import { times } from "lodash-es"; import { dirname } from "path"; import sharp from "sharp"; -import { vfs } from "../../../../../core/blitzkit/vfs"; +import { vfs } from "../../../../core/blitzkit/vfs"; import { vertexAttributeGLTFName, vertexAttributeGltfVectorSizes, From 754f39f623d2b68b706bb77a8cf227a03562782e Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Fri, 7 Aug 2026 12:34:58 -0500 Subject: [PATCH 082/147] environment based deploys --- .github/workflows/website.yml | 57 +++++++++++++++++++++++------------ 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index 9028f131c..edef0f6d0 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -2,20 +2,39 @@ name: Website on: push: - workflow_dispatch: + branches: + - production schedule: - cron: "0 4 * * *" + workflow_dispatch: + inputs: + environment: + description: Environment + + required: true + # default: production + default: beta + type: choice + + options: + - development + - beta + - production + - preview + - opentest + permissions: contents: read concurrency: group: ${{ github.workflow }}-${{ github.ref }} - # cancel-in-progress: ${{ github.ref == 'refs/heads/main' }} cancel-in-progress: true env: + ENVIRONMENT: ${{ inputs.environment || 'production' }} + # blitzkit PUBLIC_BRANCH: main PUBLIC_PROMOTE_OPENTEST: false @@ -48,14 +67,7 @@ jobs: strategy: matrix: - include: - - chunk: default - node: blitzkit/blitzkit.github.io - path: dist/website - - - chunk: api - node: blitzkit/api - path: dist/website/api + chunk: [default, api] steps: - name: Free disk space @@ -118,24 +130,31 @@ jobs: run: bun run --bun build env: CHUNK: ${{ matrix.chunk }} + ENVIRONMENT: ${{ env.ENVIRONMENT }} - name: Upload artifact id: upload uses: actions/upload-artifact@v4 with: name: chunk-${{ matrix.chunk }} - path: ${{ matrix.path }} - - - name: Save artifact ID - run: | - echo "${{ steps.upload.outputs.artifact-id }}" + path: dist/website - name: Trigger deployment + + if: github.ref_name == 'production' || github.ref_name == 'infrastructure' + env: TOKEN: ${{ secrets.GH_NODE_DEPLOY_TOKEN }} - ARTIFACT_ID: ${{ steps.upload.outputs.artifact-id }} - TARGET_REPO: ${{ matrix.node }} + CHUNK: ${{ matrix.chunk }} + run: | + ENV_FILE="environments/${ENVIRONMENT}.json" + TARGET_REPO=$(jq -r ".nodes.\"$CHUNK\"" "$ENV_FILE") + + if [ "$TARGET_REPO" == "null" ] || [ -z "$TARGET_REPO" ]; then + exit 1 + fi + curl \ -X POST \ -H "Accept: application/vnd.github+json" \ @@ -146,7 +165,7 @@ jobs: \"client_payload\": { \"source_repo\": \"${{ github.repository }}\", \"run_id\": \"${{ github.run_id }}\", - \"artifact_id\": \"$ARTIFACT_ID\", - \"chunk\": \"${{ matrix.chunk }}\" + \"artifact_id\": \"${{ steps.upload.outputs.artifact-id }}\", + \"chunk\": \"$CHUNK\" } }" From 58d0d92d4f149ac88f96ff7be611af9857f10a4b Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Fri, 7 Aug 2026 12:36:10 -0500 Subject: [PATCH 083/147] on push --- .github/workflows/website.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index edef0f6d0..aedd0d76e 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -2,8 +2,6 @@ name: Website on: push: - branches: - - production schedule: - cron: "0 4 * * *" From b171ce20117a8a69d740173e43ea7b0fa8264057 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Fri, 7 Aug 2026 12:46:49 -0500 Subject: [PATCH 084/147] beta environment as default for now --- .github/workflows/website.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index aedd0d76e..efb9bc211 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -31,7 +31,7 @@ concurrency: cancel-in-progress: true env: - ENVIRONMENT: ${{ inputs.environment || 'production' }} + ENVIRONMENT: ${{ inputs.environment || 'beta' }} # blitzkit PUBLIC_BRANCH: main From 408417ca684aca887f6c217775f5d4db900984ca Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Fri, 7 Aug 2026 13:30:25 -0500 Subject: [PATCH 085/147] bruh i am ignorant --- packages/core/src/blitzkit/api/client.ts | 5 +++++ .../src/pages/[...chunk_api]/definitions/maps.pb.ts | 11 +++++++++++ .../[...locale]/tools/tankopedia/[id]/index.astro | 2 ++ 3 files changed, 18 insertions(+) create mode 100644 packages/website/src/pages/[...chunk_api]/definitions/maps.pb.ts diff --git a/packages/core/src/blitzkit/api/client.ts b/packages/core/src/blitzkit/api/client.ts index d71fb728d..02f645fb9 100644 --- a/packages/core/src/blitzkit/api/client.ts +++ b/packages/core/src/blitzkit/api/client.ts @@ -6,6 +6,7 @@ import { EquipmentDefinitions, Gallery, GameDefinitions, + MapDefinitions, ModelDefinitions, ProvisionDefinitions, SkillDefinitions, @@ -61,4 +62,8 @@ export class ClientBlitzKitAPI extends BlitzKitAPI { galleryDefinitions() { return fetchPB(alias("api", "/definitions/gallery.pb"), Gallery); } + + mapDefinitions() { + return fetchPB(alias("api", "/definitions/maps.pb"), MapDefinitions); + } } diff --git a/packages/website/src/pages/[...chunk_api]/definitions/maps.pb.ts b/packages/website/src/pages/[...chunk_api]/definitions/maps.pb.ts new file mode 100644 index 000000000..ab00586af --- /dev/null +++ b/packages/website/src/pages/[...chunk_api]/definitions/maps.pb.ts @@ -0,0 +1,11 @@ +import { MapDefinitions } from "@blitzkit/core"; +import { api } from "../../../core/blitzkit/api"; + +export { getStaticPaths } from "../_index"; + +export async function GET() { + const definitions = await api.mapDefinitions(); + const bytes = MapDefinitions.encode(definitions).finish(); + + return new Response(bytes); +} diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/tools/tankopedia/[id]/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/tools/tankopedia/[id]/index.astro index 3315a4174..68ab37b9a 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/tools/tankopedia/[id]/index.astro +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/tools/tankopedia/[id]/index.astro @@ -5,6 +5,8 @@ import LegacyRedirect from "../../../../../../layouts/LegacyRedirect.astro"; import { getStaticPaths as _getStaticPaths } from "../index.astro"; export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { + const tankDefinitions = await api.tankDefinitions(); + return Object.values(tankDefinitions.tanks).map(({ id }) => ({ params: { id }, })); From ab87282103af8f6432eaf21ee960563d7285d485 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Fri, 7 Aug 2026 13:33:05 -0500 Subject: [PATCH 086/147] use PUBLIC_ENVIRONMENT --- .github/workflows/website.yml | 5 ++--- packages/core/src/blitzkit/environment.ts | 2 +- packages/website/src/constants/lightingEnvironments.ts | 7 ------- 3 files changed, 3 insertions(+), 11 deletions(-) delete mode 100644 packages/website/src/constants/lightingEnvironments.ts diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index efb9bc211..c701879c9 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -31,7 +31,7 @@ concurrency: cancel-in-progress: true env: - ENVIRONMENT: ${{ inputs.environment || 'beta' }} + PUBLIC_ENVIRONMENT: ${{ inputs.environment || 'beta' }} # blitzkit PUBLIC_BRANCH: main @@ -128,7 +128,6 @@ jobs: run: bun run --bun build env: CHUNK: ${{ matrix.chunk }} - ENVIRONMENT: ${{ env.ENVIRONMENT }} - name: Upload artifact id: upload @@ -146,7 +145,7 @@ jobs: CHUNK: ${{ matrix.chunk }} run: | - ENV_FILE="environments/${ENVIRONMENT}.json" + ENV_FILE="environments/${PUBLIC_ENVIRONMENT}.json" TARGET_REPO=$(jq -r ".nodes.\"$CHUNK\"" "$ENV_FILE") if [ "$TARGET_REPO" == "null" ] || [ -z "$TARGET_REPO" ]; then diff --git a/packages/core/src/blitzkit/environment.ts b/packages/core/src/blitzkit/environment.ts index 2fffb5c5c..b7bb18bca 100644 --- a/packages/core/src/blitzkit/environment.ts +++ b/packages/core/src/blitzkit/environment.ts @@ -23,7 +23,7 @@ for (const key in environments) { } export function environmentConfig() { - const environment = assertSecret(import.meta.env.ENVIRONMENT); + const environment = assertSecret(import.meta.env.PUBLIC_ENVIRONMENT); if (!(environment in environments)) { throw new Error(`Invalid environment "${environment}"`); diff --git a/packages/website/src/constants/lightingEnvironments.ts b/packages/website/src/constants/lightingEnvironments.ts deleted file mode 100644 index 4abf6a32d..000000000 --- a/packages/website/src/constants/lightingEnvironments.ts +++ /dev/null @@ -1,7 +0,0 @@ -export const ENVIRONMENTS = [ - 'lobby', - 'apartment', - 'warehouse', - 'dawn', - 'sunset', -] as const; From d29f91afbc7b362e2707850698824b21fe65c2d2 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Fri, 7 Aug 2026 13:33:28 -0500 Subject: [PATCH 087/147] don't fail fast for now --- .github/workflows/website.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index c701879c9..a5577f154 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -64,6 +64,7 @@ jobs: runs-on: ubuntu-latest strategy: + fail-fast: false matrix: chunk: [default, api] From ef84d5b3b5b865346b0bac44de6dad2a5942d885 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Fri, 7 Aug 2026 13:34:56 -0500 Subject: [PATCH 088/147] don't log paths --- .../src/pages/[...chunk_api]/icons/equipment/[id].webp.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/website/src/pages/[...chunk_api]/icons/equipment/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/icons/equipment/[id].webp.ts index f47fc889b..89acb818d 100644 --- a/packages/website/src/pages/[...chunk_api]/icons/equipment/[id].webp.ts +++ b/packages/website/src/pages/[...chunk_api]/icons/equipment/[id].webp.ts @@ -69,8 +69,6 @@ export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { } } - console.log(paths); - return paths; }); From 5c3077033e76b2f506da970b50e5256804b0572b Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Fri, 7 Aug 2026 14:33:10 -0500 Subject: [PATCH 089/147] more modern way of handling id discovery --- package.json | 3 +- packages/scripts/src/discoverIds.ts | 226 +++++++++------------------- 2 files changed, 76 insertions(+), 153 deletions(-) diff --git a/package.json b/package.json index e790351de..9c533bb44 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "scripts": { "dev": "cd packages/website && bun run dev", "build": "cd packages/website && bun run build", - "build:averages": "cd packages/scripts && bun run build:averages" + "build:averages": "cd packages/scripts && bun run build:averages", + "build:ids": "cd packages/scripts && bun run build:ids" }, "type": "module", "workspaces": [ diff --git a/packages/scripts/src/discoverIds.ts b/packages/scripts/src/discoverIds.ts index 27d5ac36c..8bb59e588 100644 --- a/packages/scripts/src/discoverIds.ts +++ b/packages/scripts/src/discoverIds.ts @@ -1,165 +1,87 @@ -import { - DidsReadStream, - DidsWriteStream, - DiscoveredIdsDefinitions, - MIN_REGION_IDS, - REGIONS, - Region, - asset, - getAccountInfo, - idToRegion, - retryAbleBlitzFetchEvent, -} from "@blitzkit/core"; -import { chunk, times, uniq } from "lodash-es"; -import { compress, decompress } from "lz4js"; -import { AssetUploader } from "./core/github/assetUploader"; +const MAX_IDS = 100; +const API_RATE = 10; -const CHUNK_SIZE = 2 ** 21; -const RUN_TIME = 1000 * 60 * 60 * 5.5; -const PROGRESS_UPDATE_FREQUENCY = 1000 * 60; -const MAX_REQUESTS = 10; -const ACCOUNTS_PER_CALL = 100; -const TERMINATION_THRESHOLD = 1000; -const startTime = Date.now(); -const indexableRegions = [...REGIONS]; -let ids: number[] = []; -const preDiscoveredManifest = (await fetch(asset("ids/manifest.json")).then( - (response) => response.json() -)) as DiscoveredIdsDefinitions; +const ACCOMMODATED_ID_COUNT = 10_000_000_000; +const BYTES_PER_ID = 64 / 8; +const MAX_BYTES_PER_BATCH = 100 * 1_000_000; -console.log( - `Fetching ${preDiscoveredManifest.chunks} pre-discovered chunks...` +const regions = ["eu", "com", "asia"]; +const seeds = [5e8, 10e8, 20e8].map(BigInt); + +export interface QuickInfo { + status: string; + data: Record; +} + +const batches = BigInt( + Math.ceil((ACCOMMODATED_ID_COUNT * BYTES_PER_ID) / MAX_BYTES_PER_BATCH), ); +const ids: bigint[] = []; -let chunkIndex = 0; -while (chunkIndex < preDiscoveredManifest.chunks) { - const preDiscovered = await fetch(asset(`ids/${chunkIndex}.dids.lz4`)).then( - async (response) => { - const buffer = await response.arrayBuffer(); - const decompressed = decompress(new Uint8Array(buffer)).buffer; - return new DidsReadStream(decompressed as ArrayBuffer).dids(); +const t0 = performance.now(); +const maxT = 5 * 60 * 60 * 1000; +const t1 = t0 + maxT; + +const unfilled = Array.from({ length: seeds.length }); +const offsets = unfilled.map(() => 0n); +const queues = unfilled.map(() => [] as bigint[]); + +function mountIds() { + for (let i = 0; i < regions.length; i++) { + const queue = queues[i]; + + if (queue.length >= MAX_IDS) continue; + + const availableSpace = MAX_IDS - queue.length; + + const seed = seeds[i]; + const offset = offsets[i]++; + + for (let j = 0n; j < availableSpace; j++) { + const id = seed + offset + j; + queue.push(id); } - ); - // no spread syntax: https://github.com/oven-sh/bun/issues/11734 - ids = ids.concat(preDiscovered); - - console.log( - `Pre-discovered ${preDiscovered.length} ids (chunk ${chunkIndex})` - ); - - chunkIndex++; + offsets[i] += BigInt(availableSpace); + } } -const regionalIdIndex: Record = { - asia: ids.findLast((id) => idToRegion(id) === "asia") ?? MIN_REGION_IDS.asia, - com: ids.findLast((id) => idToRegion(id) === "com") ?? MIN_REGION_IDS.com, - eu: ids.findLast((id) => idToRegion(id) === "eu") ?? MIN_REGION_IDS.eu, -}; -const zeroStreak: Record = { asia: 0, com: 0, eu: 0 }; -let regionIndex = 0; -let outOfTimeFlagged = false; +let i = 0; +while (performance.now() < t1) { + mountIds(); + await sleep(); -async function verify(region: Region, ids: number[]) { - const infos = await getAccountInfo(region, ids, undefined, { - // cheeky way of requesting nothing - fields: "account_id,-account_id", - }); - const filtered = ids.filter((_, index) => { - const info = infos[index]; - return info !== null; - }); + const region = regions[i]; + const queue = queues[i]; - return filtered; + const accountIds = queue.join(","); + const fields = "account_id%2C-account_id"; + const applicationId = import.meta.env.PUBLIC_WARGAMING_APPLICATION_ID; + + const url = `https://api.wotblitz.${ + region + }/wotb/account/info/?application_id=${applicationId}&fields=${ + fields + }&account_id=${accountIds}`; + const response = await fetch(url); + const data = (await response.json()) as QuickInfo; + + if (data.status !== "ok") continue; + + for (const key in data.data) { + const value = data.data[key]; + + if (value === null) continue; + + const id = BigInt(key); + + ids.push(id); + } + + queues[i] = []; + i = (i + 1) % regions.length; } -let discardAttempts = 0; -let lastProgressUpdate = 0; - -retryAbleBlitzFetchEvent.on(() => discardAttempts++); - -const interval = setInterval(async () => { - if (discardAttempts > 0) return discardAttempts--; - - const region = indexableRegions[regionIndex]; - const idsToVerify = times( - ACCOUNTS_PER_CALL, - (index) => regionalIdIndex[region] + index - ); - const verified = await verify(region, idsToVerify); - - if (Date.now() - lastProgressUpdate > PROGRESS_UPDATE_FREQUENCY) { - lastProgressUpdate = Date.now(); - console.log(`Discovered ${ids.length} ids in ${Date.now() - startTime}ms`); - } - - if (verified.length === 0) { - zeroStreak[region]++; - - if (zeroStreak[region] >= TERMINATION_THRESHOLD) { - const sliceableIndex = indexableRegions.indexOf(region); - - if (sliceableIndex !== -1) { - console.log(`Stopped discovery for ${region}`); - indexableRegions.splice(sliceableIndex, 1); - - if (indexableRegions.length === 0) { - console.log("All players discovered exhaustively!"); - await post(); - } - } - } - } else { - zeroStreak[region] = 0; - ids.push(...verified); - } - - if (Date.now() - startTime > RUN_TIME && !outOfTimeFlagged) { - outOfTimeFlagged = true; - console.log("Out of time"); - await post(); - } - - regionIndex = (regionIndex + 1) % indexableRegions.length; - regionalIdIndex[region] += ACCOUNTS_PER_CALL; -}, 1000 / MAX_REQUESTS); - -async function post() { - clearInterval(interval); - ids = uniq(ids).sort((a, b) => a - b); - console.log(`Uploading ${ids.length} ids...`); - - using uploader = new AssetUploader("discovered ids"); - const idsChunked = chunk(ids, CHUNK_SIZE); - - let index = 0; - for (const chunk of idsChunked) { - const didsWriteStream = new DidsWriteStream().dids(chunk); - const buffer = didsWriteStream.uint8Array; - const content = compress(buffer); - - console.log( - `Chunk ${index} with ${chunk.length} ids (compressed: ${content.length}; uncompressed: ${buffer.length})` - ); - - await uploader.add({ - content, - path: `ids/${index}.dids.lz4`, - }); - - index++; - } - - await uploader.add({ - content: new TextEncoder().encode( - JSON.stringify({ - chunks: idsChunked.length, - count: ids.length, - time: Date.now(), - } satisfies DiscoveredIdsDefinitions) - ), - path: "ids/manifest.json", - }); - - await uploader.flush(); +function sleep() { + return new Promise((resolve) => setTimeout(resolve, 1000 / API_RATE)); } From 09edd39a06ec4f9859c223cad4642cc14ffb13e2 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sat, 8 Aug 2026 12:28:45 -0500 Subject: [PATCH 090/147] more! --- bun.lock | 63 ++++++--- packages/scripts/package.json | 3 +- packages/scripts/src/discoverIds.ts | 87 ------------ packages/scripts/src/discoverIds/idArray.ts | 5 + packages/scripts/src/discoverIds/index.ts | 144 ++++++++++++++++++++ 5 files changed, 194 insertions(+), 108 deletions(-) delete mode 100644 packages/scripts/src/discoverIds.ts create mode 100644 packages/scripts/src/discoverIds/idArray.ts create mode 100644 packages/scripts/src/discoverIds/index.ts diff --git a/bun.lock b/bun.lock index ff4e2722a..73b200941 100644 --- a/bun.lock +++ b/bun.lock @@ -63,7 +63,7 @@ "@gltf-transform/functions": "^4.2.1", "@octokit/plugin-throttling": "^11.0.2", "@octokit/rest": "^22.0.0", - "@types/bun": "^1.3.0", + "@types/bun": "^1.3.14", "@types/decompress": "^4.2.7", "@types/lodash-es": "^4.17.12", "@types/lz4js": "^0.2.1", @@ -78,6 +78,7 @@ "fast-xml-parser": "^5.2.5", "googleapis": "^162.0.0", "lodash-es": "^4.17.21", + "lz4-napi": "^2.9.1", "progress": "^2.0.3", "puppeteer": "^24.22.3", "sharp": "^0.34.4", @@ -144,6 +145,40 @@ }, }, "packages": { + "@antoniomuso/lz4-napi-android-arm-eabi": ["@antoniomuso/lz4-napi-android-arm-eabi@2.9.1", "", { "os": "android", "cpu": "arm" }, "sha512-KcRvkSOymDvxFDT9AGFBc7g+J/Xv4qExMMHhtxjZ45qE2sEIVCXoSFmyLDiVVKztkVzoV24VXPPRLOa4WQOi2Q=="], + + "@antoniomuso/lz4-napi-android-arm64": ["@antoniomuso/lz4-napi-android-arm64@2.9.1", "", { "os": "android", "cpu": "arm64" }, "sha512-BNiddtI+5Z83w1jIufTFhTPrtxMVKb0UPezTq47XIE/7vkB6y5mHSWSlcHqEIFFIg2/PVdqyqec1CXv0bkpCPA=="], + + "@antoniomuso/lz4-napi-darwin-arm64": ["@antoniomuso/lz4-napi-darwin-arm64@2.9.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-nhLu+8g8Q6Yqd0rxRn4JkCMJBUs6mByiyT3Mhha2EgorsA2txx18JKm9qLhnXpDgFGWc94tLEVHqzDk0tLD9yA=="], + + "@antoniomuso/lz4-napi-darwin-x64": ["@antoniomuso/lz4-napi-darwin-x64@2.9.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-DqkN1D/IrqOaUBppCP7BrF9j5Usb6RqFyz16kf3SHPDxLG7pmemgwXmGWpcNrKQfp4Shx1VkbFWA61gMyE1+uw=="], + + "@antoniomuso/lz4-napi-freebsd-x64": ["@antoniomuso/lz4-napi-freebsd-x64@2.9.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-ORtZ27l+j0yDgPMgt9q/j10LCchtKS44gFohEWl8fokgr5rzDAYAtMyQZzHV8sqE2LUWHAU3Gk6Wh9s1GRl9ow=="], + + "@antoniomuso/lz4-napi-linux-arm-gnueabihf": ["@antoniomuso/lz4-napi-linux-arm-gnueabihf@2.9.1", "", { "os": "linux", "cpu": "arm" }, "sha512-GBZgQ/rY0AzjZA4jOKFXmLQKVPGXglVZ3U7SLcCu2nmuHTO73vsqiPeAHnYu7/org1km7yw3ii6XFlBRzyNKIA=="], + + "@antoniomuso/lz4-napi-linux-arm64-gnu": ["@antoniomuso/lz4-napi-linux-arm64-gnu@2.9.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-9lvGts14KHNT4pc9lKYUPI1PRdBkfbyT4HCjyDk8fJJzEG9ryvLvyhTeFr/IVpJOFmkSodZw1nxJMQDT5+zvlQ=="], + + "@antoniomuso/lz4-napi-linux-arm64-musl": ["@antoniomuso/lz4-napi-linux-arm64-musl@2.9.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-SsidKyLRHGB/rrgic+N7ZxFjjl+Cbav2sbuSj4AJsc37oVhyjzbiI5EEL9Rl+zL4ajL0d3utG3UbIUooThhykQ=="], + + "@antoniomuso/lz4-napi-linux-ppc64-gnu": ["@antoniomuso/lz4-napi-linux-ppc64-gnu@2.9.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-1Joz9RmBLiLIfI5Gk/wmJI/WwQy74prKDhLcCPFoa/FcTOJavJAopAzs1MQdnM7nGklEPOpKZWmKHZz1yQ4LMQ=="], + + "@antoniomuso/lz4-napi-linux-riscv64-gnu": ["@antoniomuso/lz4-napi-linux-riscv64-gnu@2.9.1", "", { "os": "linux", "cpu": "none" }, "sha512-3z6jSvhEpnSMlubcHwSn6W8Xziq5LYSuIcSpSo7wTEOopnr6GNnKcH2uTPFsRiqC/aC2TLpm17ktXbYTrYL7jg=="], + + "@antoniomuso/lz4-napi-linux-s390x-gnu": ["@antoniomuso/lz4-napi-linux-s390x-gnu@2.9.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-wtQg/ikF1JutKdWfUdGXvR0LG+eG49mS01luwJXG6sg40DX737T3BAsbjM9Fccvif5jQwC+KmzP8+tvxy2eMtQ=="], + + "@antoniomuso/lz4-napi-linux-x64-gnu": ["@antoniomuso/lz4-napi-linux-x64-gnu@2.9.1", "", { "os": "linux", "cpu": "x64" }, "sha512-Vw7vb3i1Q6QU2sI5GdgZdi4cVwN2xZ44Gw/WawIxCno1gX39VIF/j8T1pYwS7Dc67NeoA37LsYIPb3h49CiRRA=="], + + "@antoniomuso/lz4-napi-linux-x64-musl": ["@antoniomuso/lz4-napi-linux-x64-musl@2.9.1", "", { "os": "linux", "cpu": "x64" }, "sha512-oZVrvOPjnI7c07v1lx6HMLFi8xlW9WS0pHmkaisldybNUWmNI6vR44KoqBctGCFplvF5YVMK2xPs3W04UGzF8w=="], + + "@antoniomuso/lz4-napi-openharmony-arm64": ["@antoniomuso/lz4-napi-openharmony-arm64@2.9.1", "", { "os": "none", "cpu": "arm64" }, "sha512-mkDQv6C0JbMbCDn070Dv67SWfkDy8uD6DkqUT373LHjdaEXj/0eK2gAVykKyPdnC1RBhONgUCaV4HQOp63oH8Q=="], + + "@antoniomuso/lz4-napi-win32-arm64-msvc": ["@antoniomuso/lz4-napi-win32-arm64-msvc@2.9.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-EBsN9YzV3xpSPr6h/t6ntBetpdtO0nZw9o4sZ1vNOfLJ8FMdHxHiY7FonzhEyTnc4N3x3yMqPudD6zLVy/oNXg=="], + + "@antoniomuso/lz4-napi-win32-ia32-msvc": ["@antoniomuso/lz4-napi-win32-ia32-msvc@2.9.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-Fqe2JmcfjvVrC+BHjHPzoiIzNgjHpQvDO7duMbJh5fW9VvF7pKkjRAPDwhtFhkU/fBLVQywq5WbsFqtJ3pYG/w=="], + + "@antoniomuso/lz4-napi-win32-x64-msvc": ["@antoniomuso/lz4-napi-win32-x64-msvc@2.9.1", "", { "os": "win32", "cpu": "x64" }, "sha512-N6bAJ4pcYfbiuysHNyEl6uVnYxl0CBboPeA6Iz1GQWJb6mp8J/z4+E8bTvasMuRhIhEW5F6X+oJIwmOwSzHoIQ=="], + "@apideck/better-ajv-errors": ["@apideck/better-ajv-errors@0.3.6", "", { "dependencies": { "json-schema": "^0.4.0", "jsonpointer": "^5.0.0", "leven": "^3.1.0" }, "peerDependencies": { "ajv": ">=8" } }, "sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA=="], "@astrojs/check": ["@astrojs/check@0.9.4", "", { "dependencies": { "@astrojs/language-server": "^2.15.0", "chokidar": "^4.0.1", "kleur": "^4.1.5", "yargs": "^17.7.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "bin": { "astro-check": "dist/bin.js" } }, "sha512-IOheHwCtpUfvogHHsvu0AbeRZEnjJg3MopdLddkJE70mULItS/Vh37BHcI00mcOJcH1vhD3odbpvWokpxam7xA=="], @@ -456,11 +491,11 @@ "@fontsource/inter": ["@fontsource/inter@5.2.8", "", {}, "sha512-P6r5WnJoKiNVV+zvW2xM13gNdFhAEpQ9dQJHt3naLvfg+LkF2ldgSLiF4T41lf1SQCM9QmkqPTn4TH568IRagg=="], - "@gltf-transform/core": ["@gltf-transform/core@4.2.1", "", { "dependencies": { "property-graph": "^3.0.0" } }, "sha512-qKhrQ29KJ9K6sz7xGoGRllqPBlROjPJMQPvCLBOfgMPH3S/Ph5E0e6fD5WmwTY5QPyBsDMRqKd1Vl0ncThTXGw=="], + "@gltf-transform/core": ["@gltf-transform/core@4.4.2", "", { "dependencies": { "property-graph": "^4.1.0" } }, "sha512-qsWKwNSwK+2s834Mt4xbYcyHqCrgNFP7hIv5s487JxebngRfDgelpghNF+kSswGb2/NuapasfK3UViFoSJJoMg=="], "@gltf-transform/extensions": ["@gltf-transform/extensions@4.4.2", "", { "dependencies": { "@gltf-transform/core": "^4.4.2", "ktx-parse": "^1.1.0" } }, "sha512-HJH1FM+edC5eNvl6xO0SOXJ/j/3oDoIpSu150OTdJaLBoM3TgCCGIfh4wyhgWAqZrkvgHKVGiZKxcKV5LkgPCQ=="], - "@gltf-transform/functions": ["@gltf-transform/functions@4.2.1", "", { "dependencies": { "@gltf-transform/core": "^4.2.1", "@gltf-transform/extensions": "^4.2.1", "ktx-parse": "^1.0.1", "ndarray": "^1.0.19", "ndarray-lanczos": "^0.3.0", "ndarray-pixels": "^5.0.1" } }, "sha512-hFCI80N6zt7yHlqXbzlYEYQbFwK+2OOZNvacrRtrWiCWKI0k1Ad5+nZFsmuIZZV03aXb5XG6UF7JINT3wETS1Q=="], + "@gltf-transform/functions": ["@gltf-transform/functions@4.4.2", "", { "dependencies": { "@gltf-transform/core": "^4.4.2", "@gltf-transform/extensions": "^4.4.2", "ktx-parse": "^1.1.0", "ndarray": "^1.0.19", "ndarray-lanczos": "^0.3.0", "ndarray-pixels": "^5.0.1" } }, "sha512-dclXgv9TshMaWBqPDUYd4xTwBQ2PpuR8p0Y9pokrRzGQDUPXRP6lTDzbqT0UmEmxFSvRyPJjvOWUmzeiRafpvw=="], "@img/colour": ["@img/colour@1.0.0", "", {}, "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw=="], @@ -860,7 +895,7 @@ "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], - "@types/bun": ["@types/bun@1.3.9", "", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="], + "@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="], "@types/bytebuffer": ["@types/bytebuffer@5.0.49", "", { "dependencies": { "@types/long": "^3.0.0", "@types/node": "*" } }, "sha512-lV4YLiolMdD4upDmr4vnfiwV/FN9Jg33eNTSFMkHqyMKqTIAn5TmFTYsARwXwUXeFU7yzRpECmWV1SOhIvdkRQ=="], @@ -1076,7 +1111,7 @@ "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], - "bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="], + "bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="], "bytebuffer": ["bytebuffer@5.0.1", "", { "dependencies": { "long": "~3" } }, "sha512-IuzSdmADppkZ6DlpycMkm8l9zeEq16fWtLvunEwFiYciR/BHo4E8/xs5piFquG+Za8OWmMqHF8zuRviz2LHvRQ=="], @@ -1664,6 +1699,8 @@ "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + "lz4-napi": ["lz4-napi@2.9.1", "", { "optionalDependencies": { "@antoniomuso/lz4-napi-android-arm-eabi": "2.9.1", "@antoniomuso/lz4-napi-android-arm64": "2.9.1", "@antoniomuso/lz4-napi-darwin-arm64": "2.9.1", "@antoniomuso/lz4-napi-darwin-x64": "2.9.1", "@antoniomuso/lz4-napi-freebsd-x64": "2.9.1", "@antoniomuso/lz4-napi-linux-arm-gnueabihf": "2.9.1", "@antoniomuso/lz4-napi-linux-arm64-gnu": "2.9.1", "@antoniomuso/lz4-napi-linux-arm64-musl": "2.9.1", "@antoniomuso/lz4-napi-linux-ppc64-gnu": "2.9.1", "@antoniomuso/lz4-napi-linux-riscv64-gnu": "2.9.1", "@antoniomuso/lz4-napi-linux-s390x-gnu": "2.9.1", "@antoniomuso/lz4-napi-linux-x64-gnu": "2.9.1", "@antoniomuso/lz4-napi-linux-x64-musl": "2.9.1", "@antoniomuso/lz4-napi-openharmony-arm64": "2.9.1", "@antoniomuso/lz4-napi-win32-arm64-msvc": "2.9.1", "@antoniomuso/lz4-napi-win32-ia32-msvc": "2.9.1", "@antoniomuso/lz4-napi-win32-x64-msvc": "2.9.1" } }, "sha512-VjY1TAUQa3bo8j4vCcpQ0RBXh4mbC1OFHWH9rpOvx8qLYwaZTEzwxW/648ufD2cmz5/pFfPLDkX6N8+TqI9BYQ=="], + "lz4js": ["lz4js@0.2.0", "", {}, "sha512-gY2Ia9Lm7Ep8qMiuGRhvUq0Q7qUereeldZPP1PMEJxPtEWHJLqw9pgX68oHajBH0nzJK4MaZEA/YNV3jT8u8Bg=="], "lzma": ["lzma@2.3.2", "", { "bin": { "lzma.js": "bin/lzma.js" } }, "sha512-DcfiawQ1avYbW+hsILhF38IKAlnguc/fjHrychs9hdxe4qLykvhT5VTGNs5YRWgaNePh7NTxGD4uv4gKsRomCQ=="], @@ -1930,7 +1967,7 @@ "prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="], - "property-graph": ["property-graph@3.0.0", "", {}, "sha512-TnzxUsttmGtw+OiU0LDw+0FlMbJ8vV8pOjyDI7+Kdni4Tj0hW5BFh7TatQu7Y68hcvvFmiFOHilKShsA4R82fA=="], + "property-graph": ["property-graph@4.1.0", "", {}, "sha512-AvPcP7XECNWy4LGmFQ77k7un4lSKM4eS29PTvW4ck95uYeLxXPWJM7hLuBqK91FaHqCcgJvIUCuNJjjxKE7VKQ=="], "property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="], @@ -2524,16 +2561,6 @@ "@babel/preset-env/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - "@blitzkit/scripts/@gltf-transform/extensions": ["@gltf-transform/extensions@4.2.1", "", { "dependencies": { "@gltf-transform/core": "^4.2.1", "ktx-parse": "^1.0.1" } }, "sha512-ieHSJU9qvb3ucWDxgHFcff+C7fLtFPa6G0Wtc/ki7GsN6Rh7o9eoyqi7Ggj2LO4i3ynCwLLPb/onbJKw2fBtsA=="], - - "@blitzkit/website/@gltf-transform/core": ["@gltf-transform/core@4.4.2", "", { "dependencies": { "property-graph": "^4.1.0" } }, "sha512-qsWKwNSwK+2s834Mt4xbYcyHqCrgNFP7hIv5s487JxebngRfDgelpghNF+kSswGb2/NuapasfK3UViFoSJJoMg=="], - - "@blitzkit/website/@gltf-transform/functions": ["@gltf-transform/functions@4.4.2", "", { "dependencies": { "@gltf-transform/core": "^4.4.2", "@gltf-transform/extensions": "^4.4.2", "ktx-parse": "^1.1.0", "ndarray": "^1.0.19", "ndarray-lanczos": "^0.3.0", "ndarray-pixels": "^5.0.1" } }, "sha512-dclXgv9TshMaWBqPDUYd4xTwBQ2PpuR8p0Y9pokrRzGQDUPXRP6lTDzbqT0UmEmxFSvRyPJjvOWUmzeiRafpvw=="], - - "@gltf-transform/extensions/@gltf-transform/core": ["@gltf-transform/core@4.4.2", "", { "dependencies": { "property-graph": "^4.1.0" } }, "sha512-qsWKwNSwK+2s834Mt4xbYcyHqCrgNFP7hIv5s487JxebngRfDgelpghNF+kSswGb2/NuapasfK3UViFoSJJoMg=="], - - "@gltf-transform/functions/@gltf-transform/extensions": ["@gltf-transform/extensions@4.2.1", "", { "dependencies": { "@gltf-transform/core": "^4.2.1", "ktx-parse": "^1.0.1" } }, "sha512-ieHSJU9qvb3ucWDxgHFcff+C7fLtFPa6G0Wtc/ki7GsN6Rh7o9eoyqi7Ggj2LO4i3ynCwLLPb/onbJKw2fBtsA=="], - "@jimp/core/file-type": ["file-type@16.5.4", "", { "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/types@14.1.0", "", { "dependencies": { "@octokit/openapi-types": "^25.1.0" } }, "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g=="], @@ -2714,10 +2741,6 @@ "yaml-language-server/yaml": ["yaml@2.2.2", "", {}, "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA=="], - "@blitzkit/website/@gltf-transform/core/property-graph": ["property-graph@4.1.0", "", {}, "sha512-AvPcP7XECNWy4LGmFQ77k7un4lSKM4eS29PTvW4ck95uYeLxXPWJM7hLuBqK91FaHqCcgJvIUCuNJjjxKE7VKQ=="], - - "@gltf-transform/extensions/@gltf-transform/core/property-graph": ["property-graph@4.1.0", "", {}, "sha512-AvPcP7XECNWy4LGmFQ77k7un4lSKM4eS29PTvW4ck95uYeLxXPWJM7hLuBqK91FaHqCcgJvIUCuNJjjxKE7VKQ=="], - "@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/packages/scripts/package.json b/packages/scripts/package.json index 41678e3ad..ca48d6942 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -9,7 +9,7 @@ "@gltf-transform/functions": "^4.2.1", "@octokit/plugin-throttling": "^11.0.2", "@octokit/rest": "^22.0.0", - "@types/bun": "^1.3.0", + "@types/bun": "^1.3.14", "@types/decompress": "^4.2.7", "@types/lodash-es": "^4.17.12", "@types/lz4js": "^0.2.1", @@ -24,6 +24,7 @@ "fast-xml-parser": "^5.2.5", "googleapis": "^162.0.0", "lodash-es": "^4.17.21", + "lz4-napi": "^2.9.1", "progress": "^2.0.3", "puppeteer": "^24.22.3", "sharp": "^0.34.4", diff --git a/packages/scripts/src/discoverIds.ts b/packages/scripts/src/discoverIds.ts deleted file mode 100644 index 8bb59e588..000000000 --- a/packages/scripts/src/discoverIds.ts +++ /dev/null @@ -1,87 +0,0 @@ -const MAX_IDS = 100; -const API_RATE = 10; - -const ACCOMMODATED_ID_COUNT = 10_000_000_000; -const BYTES_PER_ID = 64 / 8; -const MAX_BYTES_PER_BATCH = 100 * 1_000_000; - -const regions = ["eu", "com", "asia"]; -const seeds = [5e8, 10e8, 20e8].map(BigInt); - -export interface QuickInfo { - status: string; - data: Record; -} - -const batches = BigInt( - Math.ceil((ACCOMMODATED_ID_COUNT * BYTES_PER_ID) / MAX_BYTES_PER_BATCH), -); -const ids: bigint[] = []; - -const t0 = performance.now(); -const maxT = 5 * 60 * 60 * 1000; -const t1 = t0 + maxT; - -const unfilled = Array.from({ length: seeds.length }); -const offsets = unfilled.map(() => 0n); -const queues = unfilled.map(() => [] as bigint[]); - -function mountIds() { - for (let i = 0; i < regions.length; i++) { - const queue = queues[i]; - - if (queue.length >= MAX_IDS) continue; - - const availableSpace = MAX_IDS - queue.length; - - const seed = seeds[i]; - const offset = offsets[i]++; - - for (let j = 0n; j < availableSpace; j++) { - const id = seed + offset + j; - queue.push(id); - } - - offsets[i] += BigInt(availableSpace); - } -} - -let i = 0; -while (performance.now() < t1) { - mountIds(); - await sleep(); - - const region = regions[i]; - const queue = queues[i]; - - const accountIds = queue.join(","); - const fields = "account_id%2C-account_id"; - const applicationId = import.meta.env.PUBLIC_WARGAMING_APPLICATION_ID; - - const url = `https://api.wotblitz.${ - region - }/wotb/account/info/?application_id=${applicationId}&fields=${ - fields - }&account_id=${accountIds}`; - const response = await fetch(url); - const data = (await response.json()) as QuickInfo; - - if (data.status !== "ok") continue; - - for (const key in data.data) { - const value = data.data[key]; - - if (value === null) continue; - - const id = BigInt(key); - - ids.push(id); - } - - queues[i] = []; - i = (i + 1) % regions.length; -} - -function sleep() { - return new Promise((resolve) => setTimeout(resolve, 1000 / API_RATE)); -} diff --git a/packages/scripts/src/discoverIds/idArray.ts b/packages/scripts/src/discoverIds/idArray.ts new file mode 100644 index 000000000..a9800c71a --- /dev/null +++ b/packages/scripts/src/discoverIds/idArray.ts @@ -0,0 +1,5 @@ +export class IdArray { + constructor( + private array: Uint32Array = new Uint32Array(), + ) {} +} diff --git a/packages/scripts/src/discoverIds/index.ts b/packages/scripts/src/discoverIds/index.ts new file mode 100644 index 000000000..9eb6e6603 --- /dev/null +++ b/packages/scripts/src/discoverIds/index.ts @@ -0,0 +1,144 @@ +import { $ } from "bun"; +import { uncompress } from "lz4-napi"; +import { mkdir, rm } from "node:fs/promises"; +import { IdArray } from "./idArray"; + +interface ManifestV1 { + version: 1; + last_verified: number; +} + +const TEMP = "../../temp"; +const WORKING_DIR = `${TEMP}/ids`; +const CHUNKS_DIR = `${WORKING_DIR}/chunks`; +const REPO = "blitzkit/ids"; + +const ACCOMMODATED_ID_COUNT = 10_000_000_000; // 10B ids +const MAX_BYTES_PER_CHUNK = 100 * 1_000_000; // 100MB + +const chunkCount = Math.ceil( + (ACCOMMODATED_ID_COUNT * Uint32Array.BYTES_PER_ELEMENT) / MAX_BYTES_PER_CHUNK, +); + +console.log( + `${chunkCount.toLocaleString()} will be needed for ${ACCOMMODATED_ID_COUNT.toLocaleString()} ids`, +); + +await mkdir(TEMP, { recursive: true }); +await rm(WORKING_DIR, { recursive: true, force: true }); + +await $`git clone https://github.com/${REPO} ${WORKING_DIR}`; + +await mkdir(CHUNKS_DIR, { recursive: true }); + +const discoveredChunks: IdArray[] = []; + +for (let i = 0; true; i++) { + const path = `${CHUNKS_DIR}/chunk-${i}.dat.lz4`; + const file = Bun.file(path); + + if (!(await file.exists())) break; + + const bytes = await file.bytes(); + const uncompressed = await uncompress(bytes); + + const array = new Uint32Array( + uncompressed.buffer, + uncompressed.byteOffset, + uncompressed.byteLength / Uint32Array.BYTES_PER_ELEMENT, + ); + const ids = new IdArray(array); + + discoveredChunks.push(ids); +} + +let chunks: IdArray[] = []; + +if (discoveredChunks.length === chunkCount) { + chunks = discoveredChunks; +} else { + chunks = Array.from({ length: chunkCount }).map(() => new IdArray()); +} + +// const MAX_IDS = 100; +// const API_RATE = 10; + +// const BYTES_PER_ID = 64 / 8; + +// const regions = ["eu", "com", "asia"]; +// const seeds = [5e8, 10e8, 20e8].map(BigInt); + +// export interface QuickInfo { +// status: string; +// data: Record; +// } + +// const ids: bigint[] = []; + +// const t0 = performance.now(); +// const maxT = 5 * 60 * 60 * 1000; +// const t1 = t0 + maxT; + +// const unfilled = Array.from({ length: seeds.length }); +// const offsets = unfilled.map(() => 0n); +// const queues = unfilled.map(() => [] as bigint[]); + +// function mountIds() { +// for (let i = 0; i < regions.length; i++) { +// const queue = queues[i]; + +// if (queue.length >= MAX_IDS) continue; + +// const availableSpace = MAX_IDS - queue.length; + +// const seed = seeds[i]; +// const offset = offsets[i]++; + +// for (let j = 0n; j < availableSpace; j++) { +// const id = seed + offset + j; +// queue.push(id); +// } + +// offsets[i] += BigInt(availableSpace); +// } +// } + +// let i = 0; +// while (performance.now() < t1) { +// mountIds(); +// await sleep(); + +// const region = regions[i]; +// const queue = queues[i]; + +// const accountIds = queue.join(","); +// const fields = "account_id%2C-account_id"; +// const applicationId = import.meta.env.PUBLIC_WARGAMING_APPLICATION_ID; + +// const url = `https://api.wotblitz.${ +// region +// }/wotb/account/info/?application_id=${applicationId}&fields=${ +// fields +// }&account_id=${accountIds}`; +// const response = await fetch(url); +// const data = (await response.json()) as QuickInfo; + +// if (data.status !== "ok") continue; + +// for (const key in data.data) { +// const value = data.data[key]; + +// if (value === null) continue; + +// const id = BigInt(key); + +// ids.push(id); +// } + +// queues[i] = []; +// i = (i + 1) % regions.length; +// } + +// function sleep() { +// return new Promise((resolve) => setTimeout(resolve, 1000 / API_RATE)); +// } From 389052c69294f8656e3613910968d03a324946ec Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sat, 8 Aug 2026 13:01:29 -0500 Subject: [PATCH 091/147] 1 sec bro --- packages/scripts/package.json | 2 +- packages/scripts/src/discoverIds/idArray.ts | 69 ++++++++++++++++++++- packages/scripts/src/discoverIds/index.ts | 62 ++++++++++++++---- 3 files changed, 118 insertions(+), 15 deletions(-) diff --git a/packages/scripts/package.json b/packages/scripts/package.json index ca48d6942..8f3dac71f 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -41,7 +41,7 @@ "setup:depotdownloader": "bun src/setupDepotDownloader.ts", "setup:patches": "bun src/applyPatches.ts", "build:reviews": "bun src/reviews.ts", - "build:ids": "bun src/discoverIds.ts", + "build:ids": "bun src/discoverIds/index.ts", "build:ratingleaderboard": "bun src/buildRatingLeaderboard.ts", "build:proxyclient": "bun src/buildProxyClient.ts", "build:catalogitemaccesssor": "bun src/buildCatalogItemAccessor.ts", diff --git a/packages/scripts/src/discoverIds/idArray.ts b/packages/scripts/src/discoverIds/idArray.ts index a9800c71a..05a043891 100644 --- a/packages/scripts/src/discoverIds/idArray.ts +++ b/packages/scripts/src/discoverIds/idArray.ts @@ -1,5 +1,72 @@ export class IdArray { constructor( - private array: Uint32Array = new Uint32Array(), + private array: Uint32Array = new Uint32Array(), + private _size = array.length, ) {} + + get(index: number) { + return this.array[index]; + } + + push(id: number) { + if (this._size === this.array.length) { + const bigger = new Uint32Array( + this.array.length === 0 ? 1 : this.array.length * 2, + ); + + bigger.set(this.array); + this.array = bigger; + } + + this.array[this._size++] = id; + } + + sort() { + this.array.subarray(0, this._size).sort(); + } + + size() { + return this._size; + } + + toBytes() { + return new Uint8Array( + this.array.buffer, + this.array.byteOffset, + this._size * Uint32Array.BYTES_PER_ELEMENT, + ); + } + + toArray() { + return Array.from(this.array.subarray(0, this._size)); + } + + static fromBytes(bytes: Uint8Array) { + if (bytes.byteLength % Uint32Array.BYTES_PER_ELEMENT !== 0) { + throw new Error("Misaligned bytes"); + } + + const size = bytes.byteLength / Uint32Array.BYTES_PER_ELEMENT; + const capacity = this.nextPowerOf2(size); + + const values = new Uint32Array(bytes.buffer, bytes.byteOffset, size); + + const uintArray = new Uint32Array(capacity); + uintArray.set(values); + + return new IdArray(uintArray, size); + } + + static fromArray(array: number[]) { + const uintArray = new Uint32Array(this.nextPowerOf2(array.length)); + + uintArray.set(array); + + return new IdArray(uintArray, array.length); + } + + private static nextPowerOf2(value: number): number { + if (value <= 1) return 1; + return 2 ** Math.ceil(Math.log2(value)); + } } diff --git a/packages/scripts/src/discoverIds/index.ts b/packages/scripts/src/discoverIds/index.ts index 9eb6e6603..f6a19b301 100644 --- a/packages/scripts/src/discoverIds/index.ts +++ b/packages/scripts/src/discoverIds/index.ts @@ -1,5 +1,5 @@ import { $ } from "bun"; -import { uncompress } from "lz4-napi"; +import { compress, uncompress } from "lz4-napi"; import { mkdir, rm } from "node:fs/promises"; import { IdArray } from "./idArray"; @@ -16,9 +16,10 @@ const REPO = "blitzkit/ids"; const ACCOMMODATED_ID_COUNT = 10_000_000_000; // 10B ids const MAX_BYTES_PER_CHUNK = 100 * 1_000_000; // 100MB -const chunkCount = Math.ceil( - (ACCOMMODATED_ID_COUNT * Uint32Array.BYTES_PER_ELEMENT) / MAX_BYTES_PER_CHUNK, -); +// const chunkCount = Math.ceil( +// (ACCOMMODATED_ID_COUNT * Uint32Array.BYTES_PER_ELEMENT) / MAX_BYTES_PER_CHUNK, +// ); +const chunkCount = 2; console.log( `${chunkCount.toLocaleString()} will be needed for ${ACCOMMODATED_ID_COUNT.toLocaleString()} ids`, @@ -27,11 +28,14 @@ console.log( await mkdir(TEMP, { recursive: true }); await rm(WORKING_DIR, { recursive: true, force: true }); -await $`git clone https://github.com/${REPO} ${WORKING_DIR}`; +console.log(`Cloning ${REPO} to ${WORKING_DIR}`); +await $`git clone --depth 1 https://github.com/${REPO} ${WORKING_DIR}`; await mkdir(CHUNKS_DIR, { recursive: true }); -const discoveredChunks: IdArray[] = []; +console.log(`Discovering pre-existing chunks...`); + +const discoveredChunks: (IdArray | null)[] = []; for (let i = 0; true; i++) { const path = `${CHUNKS_DIR}/chunk-${i}.dat.lz4`; @@ -42,22 +46,54 @@ for (let i = 0; true; i++) { const bytes = await file.bytes(); const uncompressed = await uncompress(bytes); - const array = new Uint32Array( - uncompressed.buffer, - uncompressed.byteOffset, - uncompressed.byteLength / Uint32Array.BYTES_PER_ELEMENT, - ); - const ids = new IdArray(array); + const ids = IdArray.fromBytes(new Uint8Array(uncompressed)); discoveredChunks.push(ids); } +console.log(`Found ${discoveredChunks.length} pre-existing chunks`); + let chunks: IdArray[] = []; if (discoveredChunks.length === chunkCount) { - chunks = discoveredChunks; + console.log("Pre-existing chunking is consistent, passing as-is..."); + + chunks = discoveredChunks as IdArray[]; } else { + console.log("Pre-existing chunking is not consistent, re-chunking..."); + chunks = Array.from({ length: chunkCount }).map(() => new IdArray()); + + for (let i = 0; i < discoveredChunks.length; i++) { + const ids = discoveredChunks[i]; + const size = ids!.size(); + + for (let j = 0; j < size; j++) { + const id = ids!.get(j); + const chunk = chunks[id % chunkCount]; + + chunk.push(id); + } + + discoveredChunks[i] = null; + } + + await rm(`${CHUNKS_DIR}/*`, { force: true }); +} + +console.log("Sorting chunks..."); + +for (let i = 0; i < chunks.length; i++) chunks[i].sort(); + +console.log("Saving chunks..."); + +for (let i = 0; i < chunks.length; i++) { + const path = `${CHUNKS_DIR}/chunk-${i}.dat.lz4`; + const chunk = chunks[i]; + const bytes = chunk.toBytes(); + const compressed = await compress(bytes); + + await Bun.write(path, compressed); } // const MAX_IDS = 100; From 81856276b07de29bab897dde5c49b5a87cf8937f Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sat, 8 Aug 2026 13:15:04 -0500 Subject: [PATCH 092/147] seed basing --- packages/scripts/src/discoverIds/index.ts | 53 +++++++++++++++++------ 1 file changed, 40 insertions(+), 13 deletions(-) diff --git a/packages/scripts/src/discoverIds/index.ts b/packages/scripts/src/discoverIds/index.ts index f6a19b301..65458f519 100644 --- a/packages/scripts/src/discoverIds/index.ts +++ b/packages/scripts/src/discoverIds/index.ts @@ -1,5 +1,5 @@ import { $ } from "bun"; -import { compress, uncompress } from "lz4-napi"; +import { compress, decompress } from "lz4js"; import { mkdir, rm } from "node:fs/promises"; import { IdArray } from "./idArray"; @@ -25,6 +25,10 @@ console.log( `${chunkCount.toLocaleString()} will be needed for ${ACCOMMODATED_ID_COUNT.toLocaleString()} ids`, ); +const t0 = performance.now(); +const maxT = 5 * 60 * 60 * 1000; +const t1 = t0 + maxT; + await mkdir(TEMP, { recursive: true }); await rm(WORKING_DIR, { recursive: true, force: true }); @@ -44,9 +48,9 @@ for (let i = 0; true; i++) { if (!(await file.exists())) break; const bytes = await file.bytes(); - const uncompressed = await uncompress(bytes); + const uncompressed = decompress(bytes); - const ids = IdArray.fromBytes(new Uint8Array(uncompressed)); + const ids = IdArray.fromBytes(uncompressed); discoveredChunks.push(ids); } @@ -78,20 +82,49 @@ if (discoveredChunks.length === chunkCount) { discoveredChunks[i] = null; } - await rm(`${CHUNKS_DIR}/*`, { force: true }); + console.log("Sorting chunks..."); + + for (let i = 0; i < chunks.length; i++) chunks[i].sort(); } -console.log("Sorting chunks..."); +const regions = [ + { domain: "eu", seed: 5e8, max: 10e8 - 1 }, + { domain: "com", seed: 10e8, max: 20e8 - 1 }, + { domain: "asia", seed: 20e8, max: 31e8 - 1 }, +]; -for (let i = 0; i < chunks.length; i++) chunks[i].sort(); +console.log("Finding new seed ids..."); + +for (let i = 0; i < chunkCount; i++) { + const chunk = chunks[i]; + const size = chunk.size(); + + for (let j = 0; j < size; j++) { + const id = chunk.get(j); + + for (const region of regions) { + if (id <= region.max && id >= region.seed) { + region.seed = id + 1; + } + } + } +} + +console.log("Found seeds:"); + +for (const region of regions) { + console.log(` ${region.domain}: ${region.seed.toLocaleString()}`); +} console.log("Saving chunks..."); +await rm(`${CHUNKS_DIR}/*`, { force: true }); + for (let i = 0; i < chunks.length; i++) { const path = `${CHUNKS_DIR}/chunk-${i}.dat.lz4`; const chunk = chunks[i]; const bytes = chunk.toBytes(); - const compressed = await compress(bytes); + const compressed = compress(bytes); await Bun.write(path, compressed); } @@ -99,8 +132,6 @@ for (let i = 0; i < chunks.length; i++) { // const MAX_IDS = 100; // const API_RATE = 10; -// const BYTES_PER_ID = 64 / 8; - // const regions = ["eu", "com", "asia"]; // const seeds = [5e8, 10e8, 20e8].map(BigInt); @@ -111,10 +142,6 @@ for (let i = 0; i < chunks.length; i++) { // const ids: bigint[] = []; -// const t0 = performance.now(); -// const maxT = 5 * 60 * 60 * 1000; -// const t1 = t0 + maxT; - // const unfilled = Array.from({ length: seeds.length }); // const offsets = unfilled.map(() => 0n); // const queues = unfilled.map(() => [] as bigint[]); From 21b37ced8d557b72b78c9c41bf54e5426c02c19c Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sat, 8 Aug 2026 13:30:49 -0500 Subject: [PATCH 093/147] ids update 2026-08-08T18:33:52.481Z --- packages/scripts/src/discoverIds/index.ts | 166 ++++++++++++---------- 1 file changed, 89 insertions(+), 77 deletions(-) diff --git a/packages/scripts/src/discoverIds/index.ts b/packages/scripts/src/discoverIds/index.ts index 65458f519..0081c2638 100644 --- a/packages/scripts/src/discoverIds/index.ts +++ b/packages/scripts/src/discoverIds/index.ts @@ -1,4 +1,5 @@ import { $ } from "bun"; +import { range } from "lodash-es"; import { compress, decompress } from "lz4js"; import { mkdir, rm } from "node:fs/promises"; import { IdArray } from "./idArray"; @@ -8,6 +9,11 @@ interface ManifestV1 { last_verified: number; } +export interface QuickInfo { + status: string; + data: Record; +} + const TEMP = "../../temp"; const WORKING_DIR = `${TEMP}/ids`; const CHUNKS_DIR = `${WORKING_DIR}/chunks`; @@ -16,6 +22,9 @@ const REPO = "blitzkit/ids"; const ACCOMMODATED_ID_COUNT = 10_000_000_000; // 10B ids const MAX_BYTES_PER_CHUNK = 100 * 1_000_000; // 100MB +const MAX_IDS_PER_CALL = 100; +const API_RATE = 10; // 10Hz + // const chunkCount = Math.ceil( // (ACCOMMODATED_ID_COUNT * Uint32Array.BYTES_PER_ELEMENT) / MAX_BYTES_PER_CHUNK, // ); @@ -26,7 +35,8 @@ console.log( ); const t0 = performance.now(); -const maxT = 5 * 60 * 60 * 1000; +// const maxT = 5 * 60 * 60 * 1000; // 5hr +const maxT = 5 * 1000; // 5s const t1 = t0 + maxT; await mkdir(TEMP, { recursive: true }); @@ -116,6 +126,80 @@ for (const region of regions) { console.log(` ${region.domain}: ${region.seed.toLocaleString()}`); } +console.log("Starting discovery loop..."); + +const queue: string[] = []; +const applicationId = import.meta.env.PUBLIC_WARGAMING_APPLICATION_ID; +const fields = "account_id%2C-account_id"; +let regionI = 0; + +function fillQueue() { + if (queue.length >= 1) return; + + regionI = (regionI + 1) % regions.length; + const region = regions[regionI]; + + const id0 = region.seed; + const id1 = Math.min(id0 + MAX_IDS_PER_CALL - 1, region.max); + const idRange = range(id0, id1 + 1); + + const accountIds = idRange.join(","); + const url = `https://api.wotblitz.${ + region.domain + }/wotb/account/info/?application_id=${applicationId}&fields=${ + fields + }&account_id=${accountIds}`; + + queue.push(url); + + if (idRange.length < MAX_IDS_PER_CALL) { + console.log( + `Removing region ${region.domain} removed due to ID range exhaustion...`, + ); + regions.splice(regionI, 1); + } +} + +function sleep() { + return new Promise((resolve) => setTimeout(resolve, 1000 / API_RATE)); +} + +while (performance.now() < t1 && regions.length > 0) { + await sleep(); + + fillQueue(); + + if (queue.length === 0) { + console.log("Queue empty, ending discovery loop..."); + break; + } + + const url = queue.shift()!; + const response = await fetch(url); + const data = (await response.json()) as QuickInfo; + + if (data.status !== "ok") { + console.warn("Failed API call; pushing back into queue..."); + queue.push(url); + + continue; + } + + for (const key in data.data) { + const value = data.data[key]; + + if (value === null) continue; + + const id = Number(key); + const chunkIndex = id % chunkCount; + const chunk = chunks[chunkIndex]; + + console.log(`${id} -> ${chunkIndex}`); + + chunk.push(id); + } +} + console.log("Saving chunks..."); await rm(`${CHUNKS_DIR}/*`, { force: true }); @@ -129,79 +213,7 @@ for (let i = 0; i < chunks.length; i++) { await Bun.write(path, compressed); } -// const MAX_IDS = 100; -// const API_RATE = 10; - -// const regions = ["eu", "com", "asia"]; -// const seeds = [5e8, 10e8, 20e8].map(BigInt); - -// export interface QuickInfo { -// status: string; -// data: Record; -// } - -// const ids: bigint[] = []; - -// const unfilled = Array.from({ length: seeds.length }); -// const offsets = unfilled.map(() => 0n); -// const queues = unfilled.map(() => [] as bigint[]); - -// function mountIds() { -// for (let i = 0; i < regions.length; i++) { -// const queue = queues[i]; - -// if (queue.length >= MAX_IDS) continue; - -// const availableSpace = MAX_IDS - queue.length; - -// const seed = seeds[i]; -// const offset = offsets[i]++; - -// for (let j = 0n; j < availableSpace; j++) { -// const id = seed + offset + j; -// queue.push(id); -// } - -// offsets[i] += BigInt(availableSpace); -// } -// } - -// let i = 0; -// while (performance.now() < t1) { -// mountIds(); -// await sleep(); - -// const region = regions[i]; -// const queue = queues[i]; - -// const accountIds = queue.join(","); -// const fields = "account_id%2C-account_id"; -// const applicationId = import.meta.env.PUBLIC_WARGAMING_APPLICATION_ID; - -// const url = `https://api.wotblitz.${ -// region -// }/wotb/account/info/?application_id=${applicationId}&fields=${ -// fields -// }&account_id=${accountIds}`; -// const response = await fetch(url); -// const data = (await response.json()) as QuickInfo; - -// if (data.status !== "ok") continue; - -// for (const key in data.data) { -// const value = data.data[key]; - -// if (value === null) continue; - -// const id = BigInt(key); - -// ids.push(id); -// } - -// queues[i] = []; -// i = (i + 1) % regions.length; -// } - -// function sleep() { -// return new Promise((resolve) => setTimeout(resolve, 1000 / API_RATE)); -// } +await $`cd ${WORKING_DIR}`; +await $`git add .`; +await $`git commit --amend -m "ids update ${new Date().toISOString()}"`; +await $`git push --force-with-lease`; From 0cd01fa258dcd166d136084f84f5d1b20e68b6a9 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sat, 8 Aug 2026 13:42:16 -0500 Subject: [PATCH 094/147] try streak detector --- packages/scripts/src/discoverIds/index.ts | 62 ++++++++++++++++------- 1 file changed, 43 insertions(+), 19 deletions(-) diff --git a/packages/scripts/src/discoverIds/index.ts b/packages/scripts/src/discoverIds/index.ts index 0081c2638..1d7252640 100644 --- a/packages/scripts/src/discoverIds/index.ts +++ b/packages/scripts/src/discoverIds/index.ts @@ -9,6 +9,13 @@ interface ManifestV1 { last_verified: number; } +interface RegionDescriptor { + domain: string; + seed: number; + max: number; + streak: number; +} + export interface QuickInfo { status: string; data: Record; @@ -24,11 +31,11 @@ const MAX_BYTES_PER_CHUNK = 100 * 1_000_000; // 100MB const MAX_IDS_PER_CALL = 100; const API_RATE = 10; // 10Hz +const MAX_DRY_STREAK = 2_000; -// const chunkCount = Math.ceil( -// (ACCOMMODATED_ID_COUNT * Uint32Array.BYTES_PER_ELEMENT) / MAX_BYTES_PER_CHUNK, -// ); -const chunkCount = 2; +const chunkCount = Math.ceil( + (ACCOMMODATED_ID_COUNT * Uint32Array.BYTES_PER_ELEMENT) / MAX_BYTES_PER_CHUNK, +); console.log( `${chunkCount.toLocaleString()} will be needed for ${ACCOMMODATED_ID_COUNT.toLocaleString()} ids`, @@ -36,7 +43,7 @@ console.log( const t0 = performance.now(); // const maxT = 5 * 60 * 60 * 1000; // 5hr -const maxT = 5 * 1000; // 5s +const maxT = 15 * 1000; const t1 = t0 + maxT; await mkdir(TEMP, { recursive: true }); @@ -97,10 +104,10 @@ if (discoveredChunks.length === chunkCount) { for (let i = 0; i < chunks.length; i++) chunks[i].sort(); } -const regions = [ - { domain: "eu", seed: 5e8, max: 10e8 - 1 }, - { domain: "com", seed: 10e8, max: 20e8 - 1 }, - { domain: "asia", seed: 20e8, max: 31e8 - 1 }, +const regions: RegionDescriptor[] = [ + { domain: "eu", seed: 5e8, max: 10e8 - 1, streak: 0 }, + { domain: "com", seed: 10e8, max: 20e8 - 1, streak: 0 }, + { domain: "asia", seed: 20e8, max: 31e8 - 1, streak: 0 }, ]; console.log("Finding new seed ids..."); @@ -128,7 +135,7 @@ for (const region of regions) { console.log("Starting discovery loop..."); -const queue: string[] = []; +const queue: { region: RegionDescriptor; url: string; size: number }[] = []; const applicationId = import.meta.env.PUBLIC_WARGAMING_APPLICATION_ID; const fields = "account_id%2C-account_id"; let regionI = 0; @@ -139,6 +146,14 @@ function fillQueue() { regionI = (regionI + 1) % regions.length; const region = regions[regionI]; + if (region.streak >= MAX_DRY_STREAK) { + console.log( + `Removing region ${region.domain} removed due to ${region.streak} dry streak...`, + ); + regions.splice(regionI, 1); + return; + } + const id0 = region.seed; const id1 = Math.min(id0 + MAX_IDS_PER_CALL - 1, region.max); const idRange = range(id0, id1 + 1); @@ -150,7 +165,7 @@ function fillQueue() { fields }&account_id=${accountIds}`; - queue.push(url); + queue.push({ region, url, size: idRange.length }); if (idRange.length < MAX_IDS_PER_CALL) { console.log( @@ -174,30 +189,38 @@ while (performance.now() < t1 && regions.length > 0) { break; } - const url = queue.shift()!; - const response = await fetch(url); + const request = queue.shift()!; + const response = await fetch(request.url); const data = (await response.json()) as QuickInfo; if (data.status !== "ok") { console.warn("Failed API call; pushing back into queue..."); - queue.push(url); + queue.push(request); continue; } + let foundIds = false; + for (const key in data.data) { const value = data.data[key]; if (value === null) continue; + foundIds = true; + const id = Number(key); const chunkIndex = id % chunkCount; const chunk = chunks[chunkIndex]; - console.log(`${id} -> ${chunkIndex}`); + // console.log(`${id} -> ${chunkIndex}`); chunk.push(id); } + + if (!foundIds) { + request.region.streak += request.size; + } } console.log("Saving chunks..."); @@ -213,7 +236,8 @@ for (let i = 0; i < chunks.length; i++) { await Bun.write(path, compressed); } -await $`cd ${WORKING_DIR}`; -await $`git add .`; -await $`git commit --amend -m "ids update ${new Date().toISOString()}"`; -await $`git push --force-with-lease`; +const git = $.cwd(WORKING_DIR); + +await git`git add .`; +await git`git commit --amend -m "ids update ${new Date().toISOString()}"`; +await git`git push --force-with-lease`; From 578d4a3ff20f37276e940fe5f019ad434d620c0c Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sat, 8 Aug 2026 13:52:07 -0500 Subject: [PATCH 095/147] max ids --- packages/scripts/src/discoverIds/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/scripts/src/discoverIds/index.ts b/packages/scripts/src/discoverIds/index.ts index 1d7252640..f67dc9814 100644 --- a/packages/scripts/src/discoverIds/index.ts +++ b/packages/scripts/src/discoverIds/index.ts @@ -31,7 +31,7 @@ const MAX_BYTES_PER_CHUNK = 100 * 1_000_000; // 100MB const MAX_IDS_PER_CALL = 100; const API_RATE = 10; // 10Hz -const MAX_DRY_STREAK = 2_000; +const MAX_DRY_STREAK = 10_000; const chunkCount = Math.ceil( (ACCOMMODATED_ID_COUNT * Uint32Array.BYTES_PER_ELEMENT) / MAX_BYTES_PER_CHUNK, From cd10d9ab4adb28d2dbb58328b18c5c4f45ed027c Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sat, 8 Aug 2026 13:57:35 -0500 Subject: [PATCH 096/147] some stats --- packages/scripts/src/discoverIds/index.ts | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/packages/scripts/src/discoverIds/index.ts b/packages/scripts/src/discoverIds/index.ts index f67dc9814..af137c30b 100644 --- a/packages/scripts/src/discoverIds/index.ts +++ b/packages/scripts/src/discoverIds/index.ts @@ -43,7 +43,7 @@ console.log( const t0 = performance.now(); // const maxT = 5 * 60 * 60 * 1000; // 5hr -const maxT = 15 * 1000; +const maxT = 60 * 1000; const t1 = t0 + maxT; await mkdir(TEMP, { recursive: true }); @@ -158,6 +158,8 @@ function fillQueue() { const id1 = Math.min(id0 + MAX_IDS_PER_CALL - 1, region.max); const idRange = range(id0, id1 + 1); + region.seed += idRange.length; + const accountIds = idRange.join(","); const url = `https://api.wotblitz.${ region.domain @@ -200,26 +202,26 @@ while (performance.now() < t1 && regions.length > 0) { continue; } - let foundIds = false; + let foundIds = 0; for (const key in data.data) { const value = data.data[key]; if (value === null) continue; - foundIds = true; + foundIds++; const id = Number(key); const chunkIndex = id % chunkCount; const chunk = chunks[chunkIndex]; - // console.log(`${id} -> ${chunkIndex}`); - chunk.push(id); } - if (!foundIds) { + if (foundIds === 0) { request.region.streak += request.size; + } else { + // console.log(`Found ${foundIds} ids in ${request.region.domain}`); } } @@ -227,12 +229,16 @@ console.log("Saving chunks..."); await rm(`${CHUNKS_DIR}/*`, { force: true }); +let totalFound = 0; + for (let i = 0; i < chunks.length; i++) { const path = `${CHUNKS_DIR}/chunk-${i}.dat.lz4`; const chunk = chunks[i]; const bytes = chunk.toBytes(); const compressed = compress(bytes); + totalFound += chunk.size(); + await Bun.write(path, compressed); } @@ -241,3 +247,7 @@ const git = $.cwd(WORKING_DIR); await git`git add .`; await git`git commit --amend -m "ids update ${new Date().toISOString()}"`; await git`git push --force-with-lease`; + +console.log( + `Committed ${totalFound.toLocaleString()} total ids across ${chunkCount} chunks`, +); From 7c9ef9e5e0593662b09eb373ea8a3397ae136ee2 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sat, 8 Aug 2026 13:59:47 -0500 Subject: [PATCH 097/147] quiet git add --- packages/scripts/src/discoverIds/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/scripts/src/discoverIds/index.ts b/packages/scripts/src/discoverIds/index.ts index af137c30b..7a8c81219 100644 --- a/packages/scripts/src/discoverIds/index.ts +++ b/packages/scripts/src/discoverIds/index.ts @@ -244,7 +244,7 @@ for (let i = 0; i < chunks.length; i++) { const git = $.cwd(WORKING_DIR); -await git`git add .`; +await git`git add .`.quiet(); await git`git commit --amend -m "ids update ${new Date().toISOString()}"`; await git`git push --force-with-lease`; From ffd74de79f0dc43dcfb8396c38bffadec8e7797c Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sat, 8 Aug 2026 14:02:20 -0500 Subject: [PATCH 098/147] quiet all --- packages/scripts/src/discoverIds/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/scripts/src/discoverIds/index.ts b/packages/scripts/src/discoverIds/index.ts index 7a8c81219..5b879603d 100644 --- a/packages/scripts/src/discoverIds/index.ts +++ b/packages/scripts/src/discoverIds/index.ts @@ -245,8 +245,8 @@ for (let i = 0; i < chunks.length; i++) { const git = $.cwd(WORKING_DIR); await git`git add .`.quiet(); -await git`git commit --amend -m "ids update ${new Date().toISOString()}"`; -await git`git push --force-with-lease`; +await git`git commit --amend -m "ids update ${new Date().toISOString()}"`.quiet(); +await git`git push --force-with-lease`.quiet(); console.log( `Committed ${totalFound.toLocaleString()} total ids across ${chunkCount} chunks`, From 6d527a7170fd9a3126e4b601bba00eb2f328287d Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sat, 8 Aug 2026 14:08:35 -0500 Subject: [PATCH 099/147] save on exit --- packages/scripts/src/discoverIds/index.ts | 50 ++++++++++++++--------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/packages/scripts/src/discoverIds/index.ts b/packages/scripts/src/discoverIds/index.ts index 5b879603d..0d7e42cdb 100644 --- a/packages/scripts/src/discoverIds/index.ts +++ b/packages/scripts/src/discoverIds/index.ts @@ -135,6 +135,9 @@ for (const region of regions) { console.log("Starting discovery loop..."); +process.on("SIGINT", save); +process.on("SIGTERM", save); + const queue: { region: RegionDescriptor; url: string; size: number }[] = []; const applicationId = import.meta.env.PUBLIC_WARGAMING_APPLICATION_ID; const fields = "account_id%2C-account_id"; @@ -225,29 +228,38 @@ while (performance.now() < t1 && regions.length > 0) { } } -console.log("Saving chunks..."); +let isSaving = false; +async function save() { + if (isSaving) return; -await rm(`${CHUNKS_DIR}/*`, { force: true }); + isSaving = true; -let totalFound = 0; + console.log("Saving chunks..."); -for (let i = 0; i < chunks.length; i++) { - const path = `${CHUNKS_DIR}/chunk-${i}.dat.lz4`; - const chunk = chunks[i]; - const bytes = chunk.toBytes(); - const compressed = compress(bytes); + await rm(`${CHUNKS_DIR}/*`, { force: true }); - totalFound += chunk.size(); + let totalFound = 0; - await Bun.write(path, compressed); + for (let i = 0; i < chunks.length; i++) { + const path = `${CHUNKS_DIR}/chunk-${i}.dat.lz4`; + const chunk = chunks[i]; + const bytes = chunk.toBytes(); + const compressed = compress(bytes); + + totalFound += chunk.size(); + + await Bun.write(path, compressed); + } + + const git = $.cwd(WORKING_DIR); + + await git`git add .`.quiet(); + await git`git commit --amend -m "ids update ${new Date().toISOString()}"`.quiet(); + await git`git push --force-with-lease`.quiet(); + + console.log( + `Committed ${totalFound.toLocaleString()} total ids across ${chunkCount} chunks`, + ); } -const git = $.cwd(WORKING_DIR); - -await git`git add .`.quiet(); -await git`git commit --amend -m "ids update ${new Date().toISOString()}"`.quiet(); -await git`git push --force-with-lease`.quiet(); - -console.log( - `Committed ${totalFound.toLocaleString()} total ids across ${chunkCount} chunks`, -); +await save(); From 953570461c840ae0989f336892e6e5c01f8b496b Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sat, 8 Aug 2026 14:09:48 -0500 Subject: [PATCH 100/147] safer exit --- packages/scripts/src/discoverIds/index.ts | 76 ++++++++++++----------- 1 file changed, 39 insertions(+), 37 deletions(-) diff --git a/packages/scripts/src/discoverIds/index.ts b/packages/scripts/src/discoverIds/index.ts index 0d7e42cdb..23f97aa72 100644 --- a/packages/scripts/src/discoverIds/index.ts +++ b/packages/scripts/src/discoverIds/index.ts @@ -2,6 +2,7 @@ import { $ } from "bun"; import { range } from "lodash-es"; import { compress, decompress } from "lz4js"; import { mkdir, rm } from "node:fs/promises"; +import { exit } from "node:process"; import { IdArray } from "./idArray"; interface ManifestV1 { @@ -42,8 +43,7 @@ console.log( ); const t0 = performance.now(); -// const maxT = 5 * 60 * 60 * 1000; // 5hr -const maxT = 60 * 1000; +const maxT = 5 * 60 * 60 * 1000; // 5hr const t1 = t0 + maxT; await mkdir(TEMP, { recursive: true }); @@ -133,11 +133,47 @@ for (const region of regions) { console.log(` ${region.domain}: ${region.seed.toLocaleString()}`); } -console.log("Starting discovery loop..."); +let isSaving = false; +async function save() { + if (isSaving) return; + + isSaving = true; + + console.log("Saving chunks..."); + + await rm(`${CHUNKS_DIR}/*`, { force: true }); + + let totalFound = 0; + + for (let i = 0; i < chunks.length; i++) { + const path = `${CHUNKS_DIR}/chunk-${i}.dat.lz4`; + const chunk = chunks[i]; + const bytes = chunk.toBytes(); + const compressed = compress(bytes); + + totalFound += chunk.size(); + + await Bun.write(path, compressed); + } + + const git = $.cwd(WORKING_DIR); + + await git`git add .`.quiet(); + await git`git commit --amend -m "ids update ${new Date().toISOString()}"`.quiet(); + await git`git push --force-with-lease`.quiet(); + + console.log( + `Committed ${totalFound.toLocaleString()} total ids across ${chunkCount} chunks`, + ); + + exit(0); +} process.on("SIGINT", save); process.on("SIGTERM", save); +console.log("Starting discovery loop..."); + const queue: { region: RegionDescriptor; url: string; size: number }[] = []; const applicationId = import.meta.env.PUBLIC_WARGAMING_APPLICATION_ID; const fields = "account_id%2C-account_id"; @@ -228,38 +264,4 @@ while (performance.now() < t1 && regions.length > 0) { } } -let isSaving = false; -async function save() { - if (isSaving) return; - - isSaving = true; - - console.log("Saving chunks..."); - - await rm(`${CHUNKS_DIR}/*`, { force: true }); - - let totalFound = 0; - - for (let i = 0; i < chunks.length; i++) { - const path = `${CHUNKS_DIR}/chunk-${i}.dat.lz4`; - const chunk = chunks[i]; - const bytes = chunk.toBytes(); - const compressed = compress(bytes); - - totalFound += chunk.size(); - - await Bun.write(path, compressed); - } - - const git = $.cwd(WORKING_DIR); - - await git`git add .`.quiet(); - await git`git commit --amend -m "ids update ${new Date().toISOString()}"`.quiet(); - await git`git push --force-with-lease`.quiet(); - - console.log( - `Committed ${totalFound.toLocaleString()} total ids across ${chunkCount} chunks`, - ); -} - await save(); From 5059e382b327f8973b5c9413638b59e15c13182e Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sat, 8 Aug 2026 20:45:09 -0500 Subject: [PATCH 101/147] some missing aliases --- .../src/components/ModuleButtons/ConsumableButton.tsx | 3 ++- .../src/components/ModuleButtons/EquipmentButton.tsx | 3 ++- .../src/components/TankSearch/components/FilterControl.tsx | 6 ++++-- packages/website/src/hooks/useArmor.ts | 3 ++- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/website/src/components/ModuleButtons/ConsumableButton.tsx b/packages/website/src/components/ModuleButtons/ConsumableButton.tsx index 92069d40f..96ab54bae 100644 --- a/packages/website/src/components/ModuleButtons/ConsumableButton.tsx +++ b/packages/website/src/components/ModuleButtons/ConsumableButton.tsx @@ -1,3 +1,4 @@ +import { alias } from "@blitzkit/core"; import { ClockIcon, ReloadIcon } from "@radix-ui/react-icons"; import { Flex, Text } from "@radix-ui/themes"; import { api } from "../../core/blitzkit/api"; @@ -28,7 +29,7 @@ export function ConsumableButton({ {(cooldown || duration) && ( diff --git a/packages/website/src/components/ModuleButtons/EquipmentButton.tsx b/packages/website/src/components/ModuleButtons/EquipmentButton.tsx index 8d839cf0f..b796d66d7 100644 --- a/packages/website/src/components/ModuleButtons/EquipmentButton.tsx +++ b/packages/website/src/components/ModuleButtons/EquipmentButton.tsx @@ -1,3 +1,4 @@ +import { alias } from "@blitzkit/core"; import { api } from "../../core/blitzkit/api"; import { useLocale } from "../../hooks/useLocale"; import { GenericTankComponentButton } from "./GenericTankComponentButton"; @@ -15,7 +16,7 @@ export function EquipmentButton({ equipment, ...props }: EquipmentButtonProps) { return ( ); diff --git a/packages/website/src/components/TankSearch/components/FilterControl.tsx b/packages/website/src/components/TankSearch/components/FilterControl.tsx index d43a1c5b9..ee16888ae 100644 --- a/packages/website/src/components/TankSearch/components/FilterControl.tsx +++ b/packages/website/src/components/TankSearch/components/FilterControl.tsx @@ -1070,8 +1070,10 @@ function GameModeAbilitiesFilter() { : rawPowers; const icons = [ - ...abilities.map((ability) => `api/icons/consumables/${ability}.webp`), - ...powers.map((power) => `api/icons/provisions/${power}.webp`), + ...abilities.map((ability) => + alias("api", `/icons/consumables/${ability}.webp`), + ), + ...powers.map((power) => alias("api", `/icons/provisions/${power}.webp`)), ]; return ( diff --git a/packages/website/src/hooks/useArmor.ts b/packages/website/src/hooks/useArmor.ts index 28a170925..94656886b 100644 --- a/packages/website/src/hooks/useArmor.ts +++ b/packages/website/src/hooks/useArmor.ts @@ -1,9 +1,10 @@ +import { alias } from "@blitzkit/core"; import { useLoader } from "@react-three/fiber"; import { GLTFLoader } from "three-stdlib"; import { useDispose } from "./useDispose"; export function useArmor(id: number) { - const path = `/api/tanks/${id}/collision.glb`; + const path = alias("api", `/tanks/${id}/collision.glb`); const gltf = useLoader(GLTFLoader, path); useDispose(gltf, path); From c59779bedb2a6924d1d320e9ddc27c3976d6984c Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sat, 8 Aug 2026 20:48:06 -0500 Subject: [PATCH 102/147] move 404 to pages chunk --- .../website/src/pages/{ => [...chunk_default]}/404.astro | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename packages/website/src/pages/{ => [...chunk_default]}/404.astro (80%) diff --git a/packages/website/src/pages/404.astro b/packages/website/src/pages/[...chunk_default]/404.astro similarity index 80% rename from packages/website/src/pages/404.astro rename to packages/website/src/pages/[...chunk_default]/404.astro index 05529036f..2d62ff493 100644 --- a/packages/website/src/pages/404.astro +++ b/packages/website/src/pages/[...chunk_default]/404.astro @@ -1,9 +1,9 @@ --- import { DiscordLogoIcon, HomeIcon } from "@radix-ui/react-icons"; import { Button, Flex, Heading, Link, Text } from "@radix-ui/themes"; -import { LinkI18n } from "../components/LinkI18n"; -import { PageWrapper } from "../components/PageWrapper"; -import BaseLayout from "../layouts/BaseLayout.astro"; +import { LinkI18n } from "../../components/LinkI18n"; +import { PageWrapper } from "../../components/PageWrapper"; +import BaseLayout from "../../layouts/BaseLayout.astro"; const { locale } = Astro.props; --- From c5bb537bf2ff1c4ac12e0d01150f8c772c2fc6a2 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sat, 8 Aug 2026 20:49:39 -0500 Subject: [PATCH 103/147] shell paths debug --- .../website/src/pages/[...chunk_api]/icons/shells/[id].webp.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/website/src/pages/[...chunk_api]/icons/shells/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/icons/shells/[id].webp.ts index f8c520d04..ba3d756e3 100644 --- a/packages/website/src/pages/[...chunk_api]/icons/shells/[id].webp.ts +++ b/packages/website/src/pages/[...chunk_api]/icons/shells/[id].webp.ts @@ -26,6 +26,8 @@ export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { }); } + console.log("Shell paths", paths); + return paths; }); From 5bda754b6e3333feb9313b14e2ecaa09562efd75 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sat, 8 Aug 2026 20:56:45 -0500 Subject: [PATCH 104/147] branch names --- .github/workflows/averages.yml | 55 ------------------- .github/workflows/reviews.yml | 38 ------------- .github/workflows/website.yml | 4 +- README.md | 1 - packages/i18n/strings/en.json | 9 +-- packages/website/src/components/Footer.astro | 12 ++-- packages/website/src/components/Plugs.tsx | 2 +- .../components/Tankopedia/CalloutsSection.tsx | 2 +- .../website/src/core/blitzkit/branches.ts | 19 ++----- .../[...locale]/index.astro | 4 +- 10 files changed, 22 insertions(+), 124 deletions(-) delete mode 100644 .github/workflows/averages.yml delete mode 100644 .github/workflows/reviews.yml diff --git a/.github/workflows/averages.yml b/.github/workflows/averages.yml deleted file mode 100644 index 0d72fc263..000000000 --- a/.github/workflows/averages.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: Averages - -on: - workflow_dispatch: - schedule: - - cron: "0 8 * * *" - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -env: - PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }} - PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }} - PUBLIC_ASSET_FAST_USER: ${{ secrets.PUBLIC_ASSET_FAST_USER }} - PUBLIC_ASSET_FAST_REPO: ${{ secrets.PUBLIC_ASSET_FAST_REPO }} - PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }} - ALLOW_ROBOTS: "true" - WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }} - PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }} - PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }} - GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }} - WOTB_GLOSSARY_DOMAIN: ${{ secrets.WOTB_GLOSSARY_DOMAIN }} - WOTB_GLOSSARY_PATH: ${{ secrets.WOTB_GLOSSARY_PATH }} - PUBLIC_DISCORD_SERVER_ID: ${{ secrets.PUBLIC_DISCORD_SERVER_ID }} - PUBLIC_ASSET_BASE: ${{ secrets.PUBLIC_ASSET_BASE }} - PUBLIC_BRANCH: ${{ secrets.PUBLIC_BRANCH }} - GH_TOKEN: ${{ secrets.GH_TOKEN }} - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v6 - with: - submodules: recursive - token: ${{ secrets.GH_TOKEN }} - - uses: oven-sh/setup-bun@v2 - - uses: arduino/setup-protoc@v3 - with: - repo-token: ${{ secrets.GH_TOKEN }} - - run: bun run setup - - - name: Build averages - env: - PUBLIC_ASSET_BRANCH: main - DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }} - - DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }} - DATABASE_URL: ${{ secrets.DATABASE_URL }} - VIDEOS_SHEET: ${{ secrets.VIDEOS_SHEET }} - GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }} - - run: bun run build:averages --production diff --git a/.github/workflows/reviews.yml b/.github/workflows/reviews.yml deleted file mode 100644 index efc7bb923..000000000 --- a/.github/workflows/reviews.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: Reviews - -on: - workflow_dispatch: - schedule: - - cron: 0 0 * * 2 - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -env: - DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }} - DATABASE_URL: ${{ secrets.DATABASE_URL }} - PUBLIC_ASSET_BRANCH: ${{ secrets.PUBLIC_ASSET_BRANCH }} - PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }} - GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }} - PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }} - GH_TOKEN: ${{ secrets.GH_TOKEN }} - PUBLIC_ASSET_BASE: ${{ secrets.PUBLIC_ASSET_BASE }} - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v6 - with: - submodules: recursive - token: ${{ secrets.GH_TOKEN }} - - uses: oven-sh/setup-bun@v2 - - uses: arduino/setup-protoc@v3 - with: - repo-token: ${{ secrets.GH_TOKEN }} - - run: bun run setup - - - name: Find reviews - run: bun run build:reviews diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index a5577f154..ca11f5914 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -31,10 +31,8 @@ concurrency: cancel-in-progress: true env: - PUBLIC_ENVIRONMENT: ${{ inputs.environment || 'beta' }} - # blitzkit - PUBLIC_BRANCH: main + PUBLIC_ENVIRONMENT: ${{ inputs.environment || 'beta' }} PUBLIC_PROMOTE_OPENTEST: false PUBLIC_STATIC_ASSET_BASE: https://assets.blitzkit.app PUBLIC_GUIDE_WEBSITE: https://guidesblitz.com diff --git a/README.md b/README.md index a3c6446fd..15581e128 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,6 @@ If you intend to contribute to the code, note that you may need to check out sen | `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_BRANCH` | | `main` | The branch that the website is operating on. This is completely nominal. | | `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. | diff --git a/packages/i18n/strings/en.json b/packages/i18n/strings/en.json index 070868b14..534e2fa8d 100644 --- a/packages/i18n/strings/en.json +++ b/packages/i18n/strings/en.json @@ -43,10 +43,11 @@ }, "branches": { - "local": "dev", - "dev": "beta", - "opentest": "opentest", - "preview": "preview" + "beta": "Beta", + "development": "Development", + "opentest": "OpenTest", + "preview": "Preview", + "production": "Production" }, "locales": { diff --git a/packages/website/src/components/Footer.astro b/packages/website/src/components/Footer.astro index ec9e7c750..3bcd19c6c 100644 --- a/packages/website/src/components/Footer.astro +++ b/packages/website/src/components/Footer.astro @@ -12,7 +12,7 @@ export interface Props { } const gameDefinitions = await api.gameDefinitions(); -const PUBLIC_ASSET_BRANCH = assertSecret(import.meta.env.PUBLIC_BRANCH); +const environment = assertSecret(import.meta.env.PUBLIC_ENVIRONMENT); const { locale } = Astro.props; const strings = getStrings(locale); --- @@ -135,7 +135,7 @@ const strings = getStrings(locale); BlitzKit { - PUBLIC_ASSET_BRANCH !== "preview" && ( + environment !== "preview" && ( WoTB @@ -149,14 +149,14 @@ const strings = getStrings(locale); {packageJson.version} { - PUBLIC_ASSET_BRANCH !== "preview" && ( + environment !== "preview" && ( {gameDefinitions.version} ) } - {PUBLIC_ASSET_BRANCH} + {environment} @@ -168,7 +168,7 @@ const strings = getStrings(locale); { - PUBLIC_ASSET_BRANCH !== "preview" && ( + environment !== "preview" && ( <> WoTB @@ -182,7 +182,7 @@ const strings = getStrings(locale); CDN - {PUBLIC_ASSET_BRANCH} + {environment} diff --git a/packages/website/src/components/Plugs.tsx b/packages/website/src/components/Plugs.tsx index f5c4d838b..9322fcc6b 100644 --- a/packages/website/src/components/Plugs.tsx +++ b/packages/website/src/components/Plugs.tsx @@ -5,7 +5,7 @@ import { Button, Code, Flex, Link, Text } from "@radix-ui/themes"; export function Plugs() { const promoteOpentest = assertSecret(import.meta.env.PUBLIC_PROMOTE_OPENTEST) === "true" && - assertSecret(import.meta.env.PUBLIC_BRANCH) !== "opentest"; + assertSecret(import.meta.env.PUBLIC_ENVIRONMENT) !== "opentest"; const promotions = [promoteOpentest]; if (promotions.every((promotion) => !promotion)) return null; diff --git a/packages/website/src/components/Tankopedia/CalloutsSection.tsx b/packages/website/src/components/Tankopedia/CalloutsSection.tsx index ac10eaf67..b3e2d5b39 100644 --- a/packages/website/src/components/Tankopedia/CalloutsSection.tsx +++ b/packages/website/src/components/Tankopedia/CalloutsSection.tsx @@ -15,7 +15,7 @@ const tankDefinitions = await api.tankDefinitions(); export function CalloutsSection() { const tank = Duel.use((state) => state.protagonist.tank); const promoteAeson = - assertSecret(import.meta.env.PUBLIC_BRANCH) === "preview"; + assertSecret(import.meta.env.PUBLIC_ENVIRONMENT) === "preview"; useEffect(() => { TankopediaPersistent.mutate((draft) => { diff --git a/packages/website/src/core/blitzkit/branches.ts b/packages/website/src/core/blitzkit/branches.ts index 1861f11c8..8f6f2ed77 100644 --- a/packages/website/src/core/blitzkit/branches.ts +++ b/packages/website/src/core/blitzkit/branches.ts @@ -3,19 +3,12 @@ import { STRINGS } from "@blitzkit/i18n"; import locales from "@blitzkit/i18n/locales.json"; export function resolveBranchName(locale: string = locales.default) { - if (!(locale in STRINGS)) throw new Error(`Unsupported locale: ${locale}`); - const strings = STRINGS[locale]; - const secret = assertSecret(import.meta.env.PUBLIC_BRANCH); + const environment = assertSecret(import.meta.env.PUBLIC_ENVIRONMENT); - if ( - assertSecret(import.meta.env.MODE) === "development" && - secret === "dev" - ) { - return strings.common.branches.local; - } else { - return (strings.common.branches as Record)[secret] as - | string - | undefined; - } + if (environment === "production") return undefined; + + return strings.common.branches[ + environment as keyof typeof strings.common.branches + ]; } diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/index.astro index 2bda9a01e..1f43e7990 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/index.astro +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/index.astro @@ -16,7 +16,7 @@ import { getStaticPaths as _getStaticPaths } from "../_index"; const MAX_TOOLS_PER_ROW = 2; -const PUBLIC_ASSET_BRANCH = assertSecret(import.meta.env.PUBLIC_BRANCH); +const environment = assertSecret(import.meta.env.PUBLIC_ENVIRONMENT); const { locale } = Astro.props; const strings = getStrings(locale); const rows: Tool[][] = []; @@ -88,7 +88,7 @@ export const getStaticPaths = mixStaticPaths(_getStaticPaths, () => { { - PUBLIC_ASSET_BRANCH === "preview" && ( + environment === "preview" && ( From 8c9a41d72b244a20389f40095ccf620165f2c2bb Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sun, 9 Aug 2026 13:35:21 -0500 Subject: [PATCH 105/147] add getStaticPaths for 404 page --- packages/website/src/pages/[...chunk_default]/404.astro | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/website/src/pages/[...chunk_default]/404.astro b/packages/website/src/pages/[...chunk_default]/404.astro index 2d62ff493..c252cd803 100644 --- a/packages/website/src/pages/[...chunk_default]/404.astro +++ b/packages/website/src/pages/[...chunk_default]/404.astro @@ -5,6 +5,8 @@ import { LinkI18n } from "../../components/LinkI18n"; import { PageWrapper } from "../../components/PageWrapper"; import BaseLayout from "../../layouts/BaseLayout.astro"; +export { getStaticPaths } from "./_index"; + const { locale } = Astro.props; --- From 62f68f1f105f0acf99a3a6e4627287102f5dfc55 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sun, 9 Aug 2026 13:55:15 -0500 Subject: [PATCH 106/147] add manifests --- packages/scripts/src/discoverIds/index.ts | 40 +++++++++++++---------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/packages/scripts/src/discoverIds/index.ts b/packages/scripts/src/discoverIds/index.ts index 23f97aa72..c18ee6b99 100644 --- a/packages/scripts/src/discoverIds/index.ts +++ b/packages/scripts/src/discoverIds/index.ts @@ -8,6 +8,7 @@ import { IdArray } from "./idArray"; interface ManifestV1 { version: 1; last_verified: number; + chunks: number; } interface RegionDescriptor { @@ -34,14 +35,6 @@ const MAX_IDS_PER_CALL = 100; const API_RATE = 10; // 10Hz const MAX_DRY_STREAK = 10_000; -const chunkCount = Math.ceil( - (ACCOMMODATED_ID_COUNT * Uint32Array.BYTES_PER_ELEMENT) / MAX_BYTES_PER_CHUNK, -); - -console.log( - `${chunkCount.toLocaleString()} will be needed for ${ACCOMMODATED_ID_COUNT.toLocaleString()} ids`, -); - const t0 = performance.now(); const maxT = 5 * 60 * 60 * 1000; // 5hr const t1 = t0 + maxT; @@ -58,12 +51,14 @@ console.log(`Discovering pre-existing chunks...`); const discoveredChunks: (IdArray | null)[] = []; -for (let i = 0; true; i++) { +const manifest: ManifestV1 = await Bun.file( + `${WORKING_DIR}/manifest.json`, +).json(); + +for (let i = 0; i < manifest.chunks; i++) { const path = `${CHUNKS_DIR}/chunk-${i}.dat.lz4`; const file = Bun.file(path); - if (!(await file.exists())) break; - const bytes = await file.bytes(); const uncompressed = decompress(bytes); @@ -72,18 +67,22 @@ for (let i = 0; true; i++) { discoveredChunks.push(ids); } -console.log(`Found ${discoveredChunks.length} pre-existing chunks`); +console.log(`Found ${manifest.chunks} pre-existing chunks`); + +manifest.chunks = Math.ceil( + (ACCOMMODATED_ID_COUNT * Uint32Array.BYTES_PER_ELEMENT) / MAX_BYTES_PER_CHUNK, +); let chunks: IdArray[] = []; -if (discoveredChunks.length === chunkCount) { +if (discoveredChunks.length === manifest.chunks) { console.log("Pre-existing chunking is consistent, passing as-is..."); chunks = discoveredChunks as IdArray[]; } else { console.log("Pre-existing chunking is not consistent, re-chunking..."); - chunks = Array.from({ length: chunkCount }).map(() => new IdArray()); + chunks = Array.from({ length: manifest.chunks }).map(() => new IdArray()); for (let i = 0; i < discoveredChunks.length; i++) { const ids = discoveredChunks[i]; @@ -91,7 +90,7 @@ if (discoveredChunks.length === chunkCount) { for (let j = 0; j < size; j++) { const id = ids!.get(j); - const chunk = chunks[id % chunkCount]; + const chunk = chunks[id % manifest.chunks]; chunk.push(id); } @@ -112,7 +111,7 @@ const regions: RegionDescriptor[] = [ console.log("Finding new seed ids..."); -for (let i = 0; i < chunkCount; i++) { +for (let i = 0; i < manifest.chunks; i++) { const chunk = chunks[i]; const size = chunk.size(); @@ -156,6 +155,11 @@ async function save() { await Bun.write(path, compressed); } + await Bun.write( + `${WORKING_DIR}/manifest.json`, + JSON.stringify(manifest, null, 2), + ); + const git = $.cwd(WORKING_DIR); await git`git add .`.quiet(); @@ -163,7 +167,7 @@ async function save() { await git`git push --force-with-lease`.quiet(); console.log( - `Committed ${totalFound.toLocaleString()} total ids across ${chunkCount} chunks`, + `Committed ${totalFound.toLocaleString()} total ids across ${manifest.chunks} chunks`, ); exit(0); @@ -251,7 +255,7 @@ while (performance.now() < t1 && regions.length > 0) { foundIds++; const id = Number(key); - const chunkIndex = id % chunkCount; + const chunkIndex = id % manifest.chunks; const chunk = chunks[chunkIndex]; chunk.push(id); From f882ea29e2dabf44f49cec660c510359a68291a3 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sun, 9 Aug 2026 14:06:40 -0500 Subject: [PATCH 107/147] total saved --- packages/scripts/src/discoverIds/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/scripts/src/discoverIds/index.ts b/packages/scripts/src/discoverIds/index.ts index c18ee6b99..fff3e09ac 100644 --- a/packages/scripts/src/discoverIds/index.ts +++ b/packages/scripts/src/discoverIds/index.ts @@ -9,6 +9,7 @@ interface ManifestV1 { version: 1; last_verified: number; chunks: number; + total_count: number; } interface RegionDescriptor { @@ -155,6 +156,8 @@ async function save() { await Bun.write(path, compressed); } + manifest.total_count = totalFound; + await Bun.write( `${WORKING_DIR}/manifest.json`, JSON.stringify(manifest, null, 2), From fb34e6ce2f242436f575df91adcad73c33cd1a26 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sun, 9 Aug 2026 14:12:46 -0500 Subject: [PATCH 108/147] shell file paths list --- .../website/src/pages/[...chunk_api]/icons/shells/[id].webp.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/website/src/pages/[...chunk_api]/icons/shells/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/icons/shells/[id].webp.ts index ba3d756e3..f0c0496db 100644 --- a/packages/website/src/pages/[...chunk_api]/icons/shells/[id].webp.ts +++ b/packages/website/src/pages/[...chunk_api]/icons/shells/[id].webp.ts @@ -11,6 +11,8 @@ export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { .dir(`Data/Gfx/Shared/tank-supply/ammunition/big`) .then((files) => files.filter((file) => file.endsWith("_l.txt"))); + console.log("Shell file paths", files); + for (const file of files) { const id = file.match(/(.+)_l\.txt/)![1]; const sizes = ( From 9bc753b51263aa68e18a99ff4ee2f5ca4418070f Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sun, 9 Aug 2026 14:39:05 -0500 Subject: [PATCH 109/147] i was going to add verification but eh, no right now... --- packages/scripts/src/discoverIds/index.ts | 29 ++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/packages/scripts/src/discoverIds/index.ts b/packages/scripts/src/discoverIds/index.ts index fff3e09ac..59d436569 100644 --- a/packages/scripts/src/discoverIds/index.ts +++ b/packages/scripts/src/discoverIds/index.ts @@ -179,8 +179,6 @@ async function save() { process.on("SIGINT", save); process.on("SIGTERM", save); -console.log("Starting discovery loop..."); - const queue: { region: RegionDescriptor; url: string; size: number }[] = []; const applicationId = import.meta.env.PUBLIC_WARGAMING_APPLICATION_ID; const fields = "account_id%2C-account_id"; @@ -227,7 +225,13 @@ function sleep() { return new Promise((resolve) => setTimeout(resolve, 1000 / API_RATE)); } -while (performance.now() < t1 && regions.length > 0) { +function withinTimeLimit() { + return performance.now() < t1; +} + +console.log("Starting discovery loop..."); + +while (withinTimeLimit() && regions.length > 0) { await sleep(); fillQueue(); @@ -271,4 +275,23 @@ while (performance.now() < t1 && regions.length > 0) { } } +// console.log("Starting verification loop..."); + +// while (withinTimeLimit()) { +// const verificationIndex = (manifest.last_verified + 1) % manifest.chunks; +// manifest.last_verified = verificationIndex; +// const chunk = chunks[verificationIndex]; +// const size = chunk.size(); + +// let i0 = 0; +// while (withinTimeLimit() && i0 < size) { +// await sleep(); + +// const i1 = Math.min(i0 + MAX_IDS_PER_CALL - 1, size - 1); +// const indices = range(i0, i1 + 1); +// } + +// console.log(`Verification complete for chunk ${verificationIndex}`); +// } + await save(); From 625c6cc649cdb7f3f06187253c76d91a951ca8cf Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sun, 9 Aug 2026 14:44:16 -0500 Subject: [PATCH 110/147] workflow for discovering ids --- .github/workflows/ids.yaml | 34 +++++++++++++++++++ .../workflows/{website.yml => website.yaml} | 0 package.json | 3 +- packages/scripts/package.json | 13 +------ 4 files changed, 36 insertions(+), 14 deletions(-) create mode 100644 .github/workflows/ids.yaml rename .github/workflows/{website.yml => website.yaml} (100%) diff --git a/.github/workflows/ids.yaml b/.github/workflows/ids.yaml new file mode 100644 index 000000000..40dc9f80e --- /dev/null +++ b/.github/workflows/ids.yaml @@ -0,0 +1,34 @@ +name: Discover IDs + +on: + push: + branches: infrastructure + + schedule: + - cron: 0 */6 * * * + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }} + +jobs: + discover: + name: Discover IDs + + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Setup bun + uses: oven-sh/setup-bun@v2 + + - name: Install dependencies + run: bun install + + - name: Discover IDs + run: bun run discover-ids diff --git a/.github/workflows/website.yml b/.github/workflows/website.yaml similarity index 100% rename from .github/workflows/website.yml rename to .github/workflows/website.yaml diff --git a/package.json b/package.json index 9c533bb44..9af30f596 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,7 @@ "scripts": { "dev": "cd packages/website && bun run dev", "build": "cd packages/website && bun run build", - "build:averages": "cd packages/scripts && bun run build:averages", - "build:ids": "cd packages/scripts && bun run build:ids" + "discover-ids": "cd packages/scripts && bun run discover-ids" }, "type": "module", "workspaces": [ diff --git a/packages/scripts/package.json b/packages/scripts/package.json index 8f3dac71f..d72a23bc7 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -36,18 +36,7 @@ }, "private": true, "scripts": { - "build:assets": "bun src/buildAssets/index.ts", - "build:averages": "bun src/averages.ts", - "setup:depotdownloader": "bun src/setupDepotDownloader.ts", - "setup:patches": "bun src/applyPatches.ts", - "build:reviews": "bun src/reviews.ts", - "build:ids": "bun src/discoverIds/index.ts", - "build:ratingleaderboard": "bun src/buildRatingLeaderboard.ts", - "build:proxyclient": "bun src/buildProxyClient.ts", - "build:catalogitemaccesssor": "bun src/buildCatalogItemAccessor.ts", - "prunestrings": "bun src/pruneStrings.ts", - "build:protos": "bun src/protos.ts", - "patchnotes": "bun src/patchNotes.ts" + "discover-ids": "bun src/discoverIds/index.ts" }, "type": "module" } From 577d2323a8040b37f5b39c495b5745f2c15cecca Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sun, 9 Aug 2026 14:45:12 -0500 Subject: [PATCH 111/147] checkout recursive --- .github/workflows/ids.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ids.yaml b/.github/workflows/ids.yaml index 40dc9f80e..5931e9f6e 100644 --- a/.github/workflows/ids.yaml +++ b/.github/workflows/ids.yaml @@ -23,6 +23,9 @@ jobs: steps: - name: Checkout uses: actions/checkout@v6 + with: + submodules: recursive + token: ${{ secrets.GH_TOKEN }} - name: Setup bun uses: oven-sh/setup-bun@v2 From 968c3957d02c9719f2f6170f69c21a2eee639e7d Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sun, 9 Aug 2026 14:46:35 -0500 Subject: [PATCH 112/147] 60s, just for testing --- packages/scripts/src/discoverIds/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/scripts/src/discoverIds/index.ts b/packages/scripts/src/discoverIds/index.ts index 59d436569..03986e9fd 100644 --- a/packages/scripts/src/discoverIds/index.ts +++ b/packages/scripts/src/discoverIds/index.ts @@ -37,7 +37,8 @@ const API_RATE = 10; // 10Hz const MAX_DRY_STREAK = 10_000; const t0 = performance.now(); -const maxT = 5 * 60 * 60 * 1000; // 5hr +// const maxT = 5 * 60 * 60 * 1000; // 5hr +const maxT = 60 * 1000; // 60s const t1 = t0 + maxT; await mkdir(TEMP, { recursive: true }); From e27cc3f6ed209eb73c9b377613e689a4ced1cd75 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sun, 9 Aug 2026 14:51:26 -0500 Subject: [PATCH 113/147] github actions bot --- .github/workflows/ids.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/ids.yaml b/.github/workflows/ids.yaml index 5931e9f6e..353533967 100644 --- a/.github/workflows/ids.yaml +++ b/.github/workflows/ids.yaml @@ -33,5 +33,10 @@ jobs: - name: Install dependencies run: bun install + - name: Setup Git + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" + - name: Discover IDs run: bun run discover-ids From 0df27c38a157bc0657e451716f21580bbb98a32b Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sun, 9 Aug 2026 14:58:25 -0500 Subject: [PATCH 114/147] ids token --- .github/workflows/ids.yaml | 6 +++--- packages/scripts/src/discoverIds/index.ts | 7 ++++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ids.yaml b/.github/workflows/ids.yaml index 353533967..ae2b3f06e 100644 --- a/.github/workflows/ids.yaml +++ b/.github/workflows/ids.yaml @@ -11,9 +11,6 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true -env: - PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }} - jobs: discover: name: Discover IDs @@ -40,3 +37,6 @@ jobs: - name: Discover IDs run: bun run discover-ids + env: + PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }} + GH_TOKEN: ${{ secrets.IDS_TOKEN }} diff --git a/packages/scripts/src/discoverIds/index.ts b/packages/scripts/src/discoverIds/index.ts index 03986e9fd..e4b610a6d 100644 --- a/packages/scripts/src/discoverIds/index.ts +++ b/packages/scripts/src/discoverIds/index.ts @@ -46,7 +46,12 @@ await rm(WORKING_DIR, { recursive: true, force: true }); console.log(`Cloning ${REPO} to ${WORKING_DIR}`); -await $`git clone --depth 1 https://github.com/${REPO} ${WORKING_DIR}`; +const token = process.env.GH_TOKEN; +const repoUrl = token + ? `https://x-access-token:${token}@github.com/${REPO}.git` + : `https://github.com/${REPO}.git`; + +await $`git clone --depth 1 ${repoUrl} ${WORKING_DIR}`; await mkdir(CHUNKS_DIR, { recursive: true }); console.log(`Discovering pre-existing chunks...`); From 6ae175e5e04fdfaa90e2ea7846e8b18fa566a77a Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sun, 9 Aug 2026 15:02:04 -0500 Subject: [PATCH 115/147] reset author --- packages/scripts/src/discoverIds/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/scripts/src/discoverIds/index.ts b/packages/scripts/src/discoverIds/index.ts index e4b610a6d..5542fc971 100644 --- a/packages/scripts/src/discoverIds/index.ts +++ b/packages/scripts/src/discoverIds/index.ts @@ -172,7 +172,7 @@ async function save() { const git = $.cwd(WORKING_DIR); await git`git add .`.quiet(); - await git`git commit --amend -m "ids update ${new Date().toISOString()}"`.quiet(); + await git`git commit --amend --reset-author -m "ids update ${new Date().toISOString()}"`.quiet(); await git`git push --force-with-lease`.quiet(); console.log( From f45b24f7ab05f1f19b9c6a6e2a3a4a9b2f4b9b78 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sun, 9 Aug 2026 15:05:06 -0500 Subject: [PATCH 116/147] 5hr 30min runtime --- packages/scripts/src/discoverIds/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/scripts/src/discoverIds/index.ts b/packages/scripts/src/discoverIds/index.ts index 5542fc971..9fad88999 100644 --- a/packages/scripts/src/discoverIds/index.ts +++ b/packages/scripts/src/discoverIds/index.ts @@ -37,8 +37,7 @@ const API_RATE = 10; // 10Hz const MAX_DRY_STREAK = 10_000; const t0 = performance.now(); -// const maxT = 5 * 60 * 60 * 1000; // 5hr -const maxT = 60 * 1000; // 60s +const maxT = 5.5 * 60 * 60 * 1000; // 5hr 30min const t1 = t0 + maxT; await mkdir(TEMP, { recursive: true }); From b52c1fc2d939210e7f8c140c44fbb680ad3fb579 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sun, 9 Aug 2026 16:12:26 -0500 Subject: [PATCH 117/147] ok cool --- packages/scripts/src/discoverIds/index.ts | 73 ++++++++++++----------- 1 file changed, 39 insertions(+), 34 deletions(-) diff --git a/packages/scripts/src/discoverIds/index.ts b/packages/scripts/src/discoverIds/index.ts index 9fad88999..2fd80f889 100644 --- a/packages/scripts/src/discoverIds/index.ts +++ b/packages/scripts/src/discoverIds/index.ts @@ -16,7 +16,7 @@ interface RegionDescriptor { domain: string; seed: number; max: number; - streak: number; + dry_streak: number; } export interface QuickInfo { @@ -110,9 +110,9 @@ if (discoveredChunks.length === manifest.chunks) { } const regions: RegionDescriptor[] = [ - { domain: "eu", seed: 5e8, max: 10e8 - 1, streak: 0 }, - { domain: "com", seed: 10e8, max: 20e8 - 1, streak: 0 }, - { domain: "asia", seed: 20e8, max: 31e8 - 1, streak: 0 }, + { domain: "eu", seed: 5e8, max: 10e8 - 1, dry_streak: 0 }, + { domain: "com", seed: 10e8, max: 20e8 - 1, dry_streak: 0 }, + { domain: "asia", seed: 20e8, max: 31e8 - 1, dry_streak: 0 }, ]; console.log("Finding new seed ids..."); @@ -192,38 +192,42 @@ let regionI = 0; function fillQueue() { if (queue.length >= 1) return; - regionI = (regionI + 1) % regions.length; - const region = regions[regionI]; + while (regions.length > 0) { + regionI = (regionI + 1) % regions.length; + const region = regions[regionI]; + + if (region.dry_streak >= MAX_DRY_STREAK) { + console.log( + `Removing region ${region.domain} removed due to ${region.dry_streak} dry streak...`, + ); + regions.splice(regionI, 1); + continue; + } + + const id0 = region.seed; + const id1 = Math.min(id0 + MAX_IDS_PER_CALL - 1, region.max); + const idRange = range(id0, id1 + 1); + + region.seed += idRange.length; + + const accountIds = idRange.join(","); + const url = `https://api.wotblitz.${ + region.domain + }/wotb/account/info/?application_id=${applicationId}&fields=${ + fields + }&account_id=${accountIds}`; + + queue.push({ region, url, size: idRange.length }); + + if (idRange.length < MAX_IDS_PER_CALL) { + console.log( + `Removing region ${region.domain} removed due to ID range exhaustion...`, + ); + regions.splice(regionI, 1); + } - if (region.streak >= MAX_DRY_STREAK) { - console.log( - `Removing region ${region.domain} removed due to ${region.streak} dry streak...`, - ); - regions.splice(regionI, 1); return; } - - const id0 = region.seed; - const id1 = Math.min(id0 + MAX_IDS_PER_CALL - 1, region.max); - const idRange = range(id0, id1 + 1); - - region.seed += idRange.length; - - const accountIds = idRange.join(","); - const url = `https://api.wotblitz.${ - region.domain - }/wotb/account/info/?application_id=${applicationId}&fields=${ - fields - }&account_id=${accountIds}`; - - queue.push({ region, url, size: idRange.length }); - - if (idRange.length < MAX_IDS_PER_CALL) { - console.log( - `Removing region ${region.domain} removed due to ID range exhaustion...`, - ); - regions.splice(regionI, 1); - } } function sleep() { @@ -274,8 +278,9 @@ while (withinTimeLimit() && regions.length > 0) { } if (foundIds === 0) { - request.region.streak += request.size; + request.region.dry_streak += request.size; } else { + request.region.dry_streak = 0; // console.log(`Found ${foundIds} ids in ${request.region.domain}`); } } From 14d16e5dafe1ac30be237a5002414db8dbfa5d7c Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sun, 9 Aug 2026 19:50:53 -0500 Subject: [PATCH 118/147] shell files raw debug --- .../src/pages/[...chunk_api]/icons/shells/[id].webp.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/website/src/pages/[...chunk_api]/icons/shells/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/icons/shells/[id].webp.ts index f0c0496db..917866910 100644 --- a/packages/website/src/pages/[...chunk_api]/icons/shells/[id].webp.ts +++ b/packages/website/src/pages/[...chunk_api]/icons/shells/[id].webp.ts @@ -11,6 +11,11 @@ export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { .dir(`Data/Gfx/Shared/tank-supply/ammunition/big`) .then((files) => files.filter((file) => file.endsWith("_l.txt"))); + console.log( + "Shell files raw", + await vfs.dir(`Data/Gfx/Shared/tank-supply/ammunition/big`), + ); + console.log("Shell file paths", files); for (const file of files) { From f1a1c7858823d9c60f1ce840bc10f7092a5cabb9 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sun, 9 Aug 2026 20:08:08 -0500 Subject: [PATCH 119/147] remove stub --- packages/scripts/stub/equalizer.csv | 619 ---------------------------- 1 file changed, 619 deletions(-) delete mode 100644 packages/scripts/stub/equalizer.csv diff --git a/packages/scripts/stub/equalizer.csv b/packages/scripts/stub/equalizer.csv deleted file mode 100644 index 8daa17247..000000000 --- a/packages/scripts/stub/equalizer.csv +++ /dev/null @@ -1,619 +0,0 @@ -name;maxHealthFactor;piercingPowerFactor;moduleHealthFactor;damageFactor;armorFactor -china:Ch01_Type59;1.3;1.33;1.5;1.29;1.35 -china:Ch02_Type62;1.5;1.6;1.5;1.27;1.61 -china:Ch03_WZ-111;1.4;1.17;1.4;1.3;1.35 -china:Ch04_T34_1;1.5;1.54;1.4;1.3;1.61 -china:Ch05_T34_2;1.3;1.33;1.5;1.29;1.34 -china:Ch07_Vickers_MkE_Type_BT26;5.5;5.07;3.3;7.05;8.8 -china:Ch08_Type97_Chi_Ha;3.7;3.71;2.2;3.13;5.56 -china:Ch09_M5;2.9;2.91;1.9;2.54;3.47 -china:Ch10_IS2;1.6;1.44;1.4;1.33;1.61 -china:Ch11_110;1.4;1.17;1.4;1.29;1.35 -china:Ch12_111_1_2_3;1.2;1.02;1.4;1.17;1.14 -china:Ch14_T34_3;1.3;1.37;1.5;1.13;1.17 -china:Ch15_59_16;1.9;1.77;1.6;1.57;2.24 -china:Ch15_59_16_BP;1.9;1.7;1.6;1.56;2.24 -china:Ch16_WZ_131;1.6;1.52;1.5;1.28;1.61 -china:Ch17_WZ131_1_WZ132;1.4;1.22;1.5;1.29;1.34 -china:Ch18_WZ-120;1.1;1.09;1.4;1.11;1.17 -china:Ch20_Type58;1.9;1.88;1.5;1.57;1.86 -china:Ch21_T34;2.7;2.1;1.7;1.97;2.12 -china:Ch23_112;1.4;1.17;1.4;1.31;1.35 -china:Ch24_Type64;1.9;1.86;1.6;1.57;2.35 -china:Ch26_59_Patton;1.3;1.33;1.5;1.1;1.16 -china:Ch28_WZ_132A;1.1;1.04;1.5;1.11;1.24 -china:Ch34_WZ-131G_FT;2.1;1.57;1.4;1.51;1.73 -china:Ch34_WZ-131G_FT_BP;2.1;1.57;1.4;1.51;2.02 -china:Ch35_T-34-2G_FT;1.8;1.48;1.4;1.25;1.61 -china:Ch36_WZ-111-1G_FT;1.6;1.16;1.4;1.25;1.35 -china:Ch37_WZ-111G_FT;1.2;1.05;1.4;1.15;1.13 -china:Ch39_WZ120_1G_FT;1.6;1.16;1.4;1.24;1.35 -china:Ch40_WZ120G_FT;1.1;1.05;1.4;1.08;1.13 -china:Ch42_M41D;1.7;1.49;1.5;1.24;1.61 -china:Ch43_WZ-122_2;1.3;1.21;1.5;1.31;1.35 -china:Ch45_WZ_114;1.2;1.02;1.4;1.19;1.13 -china:Ch47_BZ_176;1.2;1.03;1.4;1.17;1.13 -china:Ch49_BZ_68;1.2;1.02;1.4;1.2;1.16 -china:Ch50_BZ_166;1.4;1.17;1.4;1.3;1.35 -china:Ch51_BZ_58;1.6;1.46;1.4;1.29;1.62 -china:Ch56_BZ_74_1;1.2;1.02;1.4;1.16;1.14 -china:Ch59_BZ58_2;1.2;1.02;1.4;1.19;1.13 -china:Ch60_Type59_spc;1.3;1.37;1.5;1.32;1.35 -china:Ch_LT_vz38;4.3;4.29;2.7;3.99;4.88 -china:Ch_WZ-112v2;1.4;1.17;1.4;1.31;1.35 -china:Type_58BP;1.9;1.88;1.5;1.57;2.04 -european:Cz02_T50;1.1;1.03;1.4;1.12;1.19 -european:Cz05_T34-100;1.5;1.45;1.4;1.29;1.61 -european:Cz07_TVP_46;1.3;1.12;1.5;1.29;1.35 -european:Cz08_T-25;1.9;1.83;1.5;1.56;2.02 -european:Cz08_T-25BP;1.9;1.83;1.5;1.55;2.02 -european:Cz08_T-25BPS;1.9;1.83;1.5;1.57;2.02 -european:Cz13_T-27;1.1;1.07;1.4;1.12;1.19 -european:Cz14_Skoda_T-56;1.4;1.17;1.4;1.33;1.35 -european:Cz15_Skoda_T-45;1.7;1.44;1.4;1.27;1.61 -european:Cz16_TNH_T_vz51;1.2;1.02;1.4;1.17;1.15 -european:Cz18_TNH_105_1000;1.4;1.16;1.4;1.28;1.35 -european:Cz19_Vz_44_1;1.7;1.43;1.4;1.3;1.61 -european:Cz20_ShPTK_TVP_100;1.5;1.16;1.4;1.24;1.34 -european:Cz29_Skoda_P-JS;2.2;1.67;1.4;1.56;1.78 -european:Cz29_Skoda_P-JS_BP;2.2;1.67;1.4;1.57;1.79 -european:F01_Vickers_MkF;5.5;5.07;3.3;6.16;8.8 -european:It09_P43_ter;1.5;1.54;1.4;1.3;1.61 -european:It10_P43_BD;1.9;1.74;1.5;1.57;1.82 -european:It10_P43_bis;1.9;1.74;1.5;1.57;1.82 -european:It10_P43_bis_BP;1.9;1.7;1.5;1.58;1.82 -european:It116_Carro_d_assalto_P88;1.6;1.33;1.4;1.28;1.61 -european:It117_ProgettoCC55mod54;1.3;1.13;1.4;1.29;1.35 -european:It119_ProgettoC50mod66;1.1;1.02;1.4;1.18;1.13 -european:It12_Prototipo_Standard_B;1.1;1.04;1.4;1.14;1.17 -european:It13_Progetto_M35_mod_46;1.3;1.33;1.5;1.27;1.35 -european:It14_P44_Pantera;1.3;1.21;1.5;1.26;1.35 -european:It15_Semovente_M41;3.2;1.98;1.5;1.97;2.13 -european:It16_Bassoto;2;1.59;1.4;1.53;1.8 -european:It17_Semovente_mod_56;1.7;1.4;1.4;1.28;1.62 -european:It18_Semovente_mod_64;1.4;1.2;1.4;1.24;1.35 -european:It19_Controcarro_1MK2_Ciclope;1.1;1.05;1.4;1.19;1.13 -european:It22_Progetto_C45_mod_71;1.3;1.14;1.4;1.23;1.35 -european:It30_CC_mod_64_Prem;1.4;1.17;1.4;1.25;1.35 -european:Pl03_PzV_Pudel;1.9;1.57;1.5;1.56;1.92 -european:Pl05_50TP_Tyszkiewicza;1.2;1.02;1.4;1.18;1.13 -european:Pl06_10TP;4.3;4.29;2.7;4.28;7.66 -european:Pl07_14TP;3.6;3.57;2.1;3.12;5.56 -european:Pl08_50TP_prot;1.2;1.02;1.4;1.17;1.14 -european:Pl10_40TP_Habicha;2.1;1.64;1.4;1.57;1.83 -european:Pl119_CS_52_LIS;1.5;1.56;1.4;1.31;1.61 -european:Pl11_45TP_Habicha;1.6;1.44;1.4;1.3;1.61 -european:Pl13_53TP_Markowskiego;1.4;1.14;1.4;1.32;1.35 -european:Pl17_DS_PZlnz;2.6;2.28;1.7;1.95;2.12 -european:Pl18_BUGI;1.9;1.57;1.5;1.58;1.94 -european:Pl20_CS_44;1.5;1.54;1.4;1.3;1.61 -european:Pl22_CS_59;1.1;1.04;1.4;1.1;1.21 -european:Pl23_CS_53;1.3;1.31;1.5;1.29;1.35 -european:Pl24_56TP;1.4;1.15;1.4;1.29;1.35 -european:Pl27_SDP_Wz_66T;1.5;1.17;1.4;1.27;1.35 -european:Pl28_SDP_Wz_60;1.1;1.05;1.4;1.11;1.12 -european:Pl30_SDP_Wz58T;1.4;1.16;1.4;1.26;1.35 -european:Pl31_SDP_Wz_57D;1.7;1.43;1.4;1.28;1.62 -european:Pl32_SDP_Wz_44;2;1.57;1.4;1.52;1.76 -european:Pl33_SDP_Wz_40;3.2;2.13;1.5;1.96;2.13 -european:S02_StrvM42;2.7;2.22;1.7;1.95;2.11 -european:S04_Lago-I;3.1;2.69;1.9;2.54;2.32 -european:S07_Strv74BP;1.9;1.67;1.5;1.55;1.9 -european:S07_Strv_74;1.9;1.67;1.5;1.55;1.9 -european:S13_Leo;1.5;1.58;1.4;1.3;1.61 -european:S13_Leo_NY;1.5;1.58;1.4;1.3;1.61 -european:S17_EMIL_1952E2;1.2;1.02;1.4;1.16;1.12 -european:S18_EMIL_1951E1;1.3;1.16;1.4;1.29;1.35 -european:S23_Strv_81;1.3;1.14;1.5;1.26;1.35 -european:S25_EMIL_51;1.3;1.12;1.4;1.29;1.35 -european:S26_Lancen_C;1.3;1.22;1.5;1.31;1.35 -european:S32_Bofors_Tornvagn;1.1;1.02;1.4;1.17;1.12 -france:AMX38;4.4;4.29;2.7;4.15;5.95 -france:AMX40;3;2.88;1.9;2.52;3.53 -france:AMX50_Foch;1.2;1.05;1.4;1.11;1.14 -france:AMX_12t;1.9;1.64;1.6;1.6;2.02 -france:AMX_13_75;1.7;1.61;1.5;1.25;1.61 -france:AMX_13_90;1.4;1.37;1.5;1.29;1.34 -france:AMX_50_100;1.4;1.12;1.4;1.26;1.35 -france:AMX_50_120;1.2;1.02;1.4;1.12;1.17 -france:AMX_AC_Mle1946;1.7;1.45;1.4;1.24;1.61 -france:AMX_AC_Mle1948;1.5;1.16;1.4;1.24;1.35 -france:AMX_M4_1945;1.6;1.47;1.4;1.28;1.61 -france:ARL_44;2.1;1.57;1.4;1.53;1.82 -france:ARL_44BP;2.1;1.53;1.4;1.58;1.81 -france:ARL_V39;2;1.59;1.4;1.52;1.82 -france:Achilles_FR;2.1;1.6;1.4;1.52;1.83 -france:B1;4.1;2.8;1.6;2.54;3.64 -france:BDR_G1B;3.2;1.91;1.5;1.95;2.13 -france:Bat-Chat-25CL;1.1;1.05;1.5;1.1;1.22 -france:D1;4.3;4.62;2.7;3.8;6.52 -france:D2;3.7;3.64;2.2;3;5.52 -france:ELC_AMX;2.8;1.97;2.3;2.31;2.09 -france:F113_PzV_FR;1.9;1.65;1.5;1.56;1.89 -france:F114_Projet_4_1;1.1;1.03;1.4;1.09;1.22 -france:F115_Lorraine_50t;1.2;1.02;1.4;1.13;1.16 -france:F116_Bat_Chat_Bourrasque;1.3;1.28;1.5;1.31;1.34 -france:F117_Alt_Proto_AMX_30;1.3;1.15;1.5;1.29;1.35 -france:F11_Renault_G1R;3.2;2;1.5;1.96;2.12 -france:F120_Project_Louis;1.1;1.04;1.4;1.12;1.14 -france:F123_ERAC_105_Proto;1.3;1.22;1.5;1.3;1.35 -france:F126_AMX_13_M24;1.5;1.58;1.4;1.32;1.62 -france:F127_ELC_AMX_901_Proto;1.4;1.28;1.5;1.28;1.34 -france:F140_Sentinelle;1.4;1.15;1.4;1.28;1.35 -france:F19_Lorraine40t;1.3;1.17;1.5;1.3;1.34 -france:F19_Lorraine40t_Com;1.3;1.17;1.5;1.29;1.34 -france:F34_ARL_V39_BP;2.1;1.59;1.4;1.51;1.82 -france:F68_AMX_Chasseur_de_char_46;1.3;1.17;1.5;1.25;1.33 -france:F69_AMX_13D;1.4;1.16;1.5;1.28;1.34 -france:F69_AMX_13_57GF;1.6;1.49;1.5;1.26;1.61 -france:F71_AMX_30_prototype;1.1;1.06;1.4;1.12;1.19 -france:F73_M4A1_Revalorise;1.3;1.23;1.5;1.27;1.34 -france:F74_AMX_M4_1949;1.4;1.12;1.4;1.28;1.35 -france:F75_Char_de_25t;1.1;1.02;1.5;1.11;1.22 -france:F84_Somua_SM;1.4;1.12;1.4;1.14;1.14 -france:F89_Canon_dassaut_105;1.6;1.15;1.4;1.26;1.35 -france:FCM_36Pak40;4.2;2.95;1.9;3.18;5.52 -france:FCM_50t;1.3;1.17;1.5;1.25;1.35 -france:F_AMX-12BP;1.9;1.64;1.6;1.6;2.02 -france:RenaultFT_AC;5.5;4.37;2.2;4.49;8.51 -france:RenaultR35;5.5;5.28;3.3;6.76;7.8 -france:RenaultUE57;4.2;2.79;1.9;2.68;5.95 -france:S_35CA;3.2;2.07;1.5;1.96;2.11 -france:Somua_Sau_40;3.7;2.75;1.8;2.54;3.62 -germany:B-1bis_captured;4.1;2.96;1.6;2.55;3.89 -germany:DW_II;3.7;2.52;1.6;2.52;3.53 -germany:DickerMax;2.1;1.57;1.4;1.53;2.02 -germany:E-25;1.8;1.46;1.4;1.24;1.61 -germany:E-50;1.1;1.09;1.4;1.1;1.15 -germany:E-75;1.2;1.02;1.4;1.17;1.14 -germany:Ferdinand;1.4;1.16;1.4;1.25;1.35 -germany:G100_Gtraktor_Krupp;4.9;3.31;1.9;3.49;5.75 -germany:G103_RU_251;1.4;1.3;1.5;1.25;1.33 -germany:G106_PzKpfwPanther_AusfF;1.3;1.21;1.5;1.26;1.35 -germany:G109_Steyr_WT;1.8;1.37;1.4;1.27;1.6 -germany:G110_Mauschen;1.2;1.02;1.4;1.18;1.12 -germany:G112_JagdPanzer_105;1.2;1.05;1.4;1.04;1.18 -germany:G113_SP_I_C;1.7;1.49;1.5;1.28;1.58 -germany:G114_Rheinmetall_Scorpion;1.6;1.16;1.4;1.24;1.33 -germany:G115_VK10001P;1.4;1.15;1.4;1.33;1.35 -germany:G118_VK4503;1.7;1.3;1.4;1.27;1.61 -germany:G119_Panzer58_SH;1.3;1.22;1.5;1.28;1.35 -germany:G119_Pz58_Mutz;1.3;1.17;1.5;1.26;1.35 -germany:G120_M41_90;1.4;1.29;1.5;1.26;1.34 -germany:G126_HWK_12;1.1;1.05;1.5;1.08;1.29 -germany:G136_Tiger_131;2.1;1.68;1.4;1.55;1.85 -germany:G138_VK16801P;1.5;1.15;1.4;1.16;1.13 -germany:G140_HWK_30;1.4;1.27;1.5;1.27;1.34 -germany:G142_M48RPz;1.3;1.18;1.5;1.27;1.35 -germany:G147_mPanzer_64;1.7;1.49;1.5;1.29;1.61 -germany:G148_Elephant;1.4;1.16;1.4;1.21;1.35 -germany:G154_Kpz_07_RH;1.3;1.15;1.5;1.26;1.35 -germany:G162_Project_Kpz_07P_E;1.1;1.02;1.4;1.16;1.14 -germany:G164_Kpz_Pr_68_P;1.3;1.22;1.5;1.3;1.34 -germany:G165_Erich_Koncept;1.1;1.06;1.4;1.14;1.19 -germany:G166_LKpz_70_K;1.1;1.02;1.5;1.11;1.32 -germany:G167_Tiger_Maus;1.2;1.03;1.4;1.11;1.13 -germany:G168_KJpz_T_III;1.2;1.05;1.4;1.05;1.29 -germany:G190_VK_1602_Quby;1.4;1.36;1.5;1.25;1.34 -germany:G20_Marder_II;4.2;3.01;1.9;3.18;5.54 -germany:G85_Auf_Panther;1.5;1.49;1.5;1.25;1.61 -germany:G_E-75RT;1.4;1.14;1.4;1.3;1.35 -germany:G_E-75TS;1.2;1.03;1.4;1.13;1.13 -germany:G_VK3502_BP;2.1;1.68;1.4;1.57;1.79 -germany:H39_captured;4.3;5.58;2.7;3.99;6.04 -germany:Hetzer;3.7;2.6;1.8;2.54;3.47 -germany:Indien_Panzer;1.3;1.17;1.5;1.25;1.35 -germany:JagdPanther;1.8;1.37;1.4;1.25;1.62 -germany:JagdPantherII;1.6;1.16;1.4;1.23;1.35 -germany:JagdPzIV;2.1;1.58;1.4;1.49;1.8 -germany:JagdPzIV_AusfBP;2;1.58;1.4;1.49;1.8 -germany:JagdTiger;1.1;1.05;1.4;1.04;1.12 -germany:JagdTiger_SdKfz_185;1.4;1.17;1.4;1.21;1.35 -germany:JagdTiger_SdKfz_185_Snowstorm;1.4;1.33;1.4;1.21;1.35 -germany:Krupp-38D;2.6;2.22;1.7;1.95;2.12 -germany:Lowe;1.4;1.11;1.4;1.29;1.35 -germany:Nashorn;2;1.51;1.4;1.51;2.24 -germany:Nashorn_BP;2;1.51;1.4;1.5;2.24 -germany:Panther_II;1.3;1.21;1.5;1.26;1.35 -germany:Panther_M10;1.5;1.54;1.4;1.26;1.61 -germany:PanzerJager_I;5.5;4.33;2.2;4.49;8.8 -germany:Pro_Ag_A;1.1;1.02;1.4;1.1;1.21 -germany:Pz35t;4.4;4.9;2.7;3.94;6.05 -germany:Pz38_NA;2.9;2.78;1.9;2.55;3.65 -germany:Pz38t;3.6;3.88;2.1;3.05;5.58 -germany:PzII;5.5;5.28;3.3;6.67;6.81 -germany:PzIII;3.7;3.69;2.2;3.26;5.49 -germany:PzIII_A;3.7;3.09;2.1;2.98;5.58 -germany:PzIII_IV;2.7;2.22;1.7;1.95;2.12 -germany:PzII_J;3.6;5.5;2.1;2.83;5.46 -germany:PzII_Luchs;2.9;2.94;1.9;2.55;2.2 -germany:PzIV;2.7;2.22;1.7;1.95;2.13 -germany:PzIV_Hydro;2.5;2.22;1.7;1.95;2.11 -germany:PzIV_schmalturm;1.9;1.65;1.5;1.53;1.87 -germany:PzV;1.5;1.33;1.4;1.26;1.61 -germany:PzVI;1.7;1.3;1.4;1.28;1.62 -germany:PzVIB_Tiger_II;1.4;1.14;1.4;1.27;1.35 -germany:PzVI_AusfBP;2.1;1.68;1.4;1.56;1.79 -germany:PzVI_Tiger_P;1.7;1.3;1.4;1.28;1.62 -germany:PzV_PzIV;2.6;2.22;1.7;1.95;2.12 -germany:Pz_II_AusfG;3.8;2.9;2.1;2.85;5.55 -germany:Pz_IV_AusfA;3.6;3.2;2.2;4.05;5.74 -germany:Pz_IV_AusfBT;2.7;2.53;1.7;1.96;2.12 -germany:Pz_IV_AusfD;3.1;2.43;1.9;2.54;3.09 -germany:Pz_Sfl_IVc;3;1.61;1.5;1.98;2.08 -germany:RhB_Waffentrager;1.6;1.26;1.4;1.28;1.33 -germany:S01_Frankentank;1.7;1.33;1.4;1.37;1.62 -germany:S35_captured;3.8;2.85;2.1;2.93;5.52 -germany:StuGIII;3.2;1.93;1.5;1.97;2.12 -germany:Sturer_Emil;1.8;1.31;1.4;1.28;1.61 -germany:T-15;3.8;4.72;2.1;2.82;5.55 -germany:T-25;2.5;2.13;1.7;1.95;2.11 -germany:VK1602;2.8;2.9;2.3;1.96;2.11 -germany:VK2801;1.9;1.53;1.6;1.58;1.95 -germany:VK2801_BP;1.9;1.53;1.6;1.59;1.95 -germany:VK3001H;3.2;1.75;1.5;1.96;2.13 -germany:VK3001P;1.9;1.61;1.5;1.57;1.81 -germany:VK3002DB;1.5;1.58;1.4;1.28;1.61 -germany:VK3002DB_BP;1.9;1.65;1.5;1.56;1.87 -germany:VK3002DB_V1;1.9;1.65;1.5;1.56;1.84 -germany:VK3002M;1.9;1.65;1.5;1.57;1.91 -germany:VK3601H;2.1;1.64;1.4;1.57;1.78 -germany:VK4502A;1.3;1.17;1.4;1.29;1.35 -germany:VK4502P;1.2;1.02;1.4;1.18;1.13 -germany:Waffentrager_IV;1.2;1.06;1.4;1.12;1.19 -germany:Waffentrager_IV_CL;1.2;1.05;1.4;1.06;1.19 -japan:Chi_Ha;3.6;3.8;2.2;3.2;5.54 -japan:Chi_He;3;2.72;1.9;2.55;3.24 -japan:Chi_Ni;4.4;4.62;2.5;4.09;6.52 -japan:Chi_Nu;2.6;2.02;1.7;1.94;2.12 -japan:Chi_Nu_Kai;2.6;2.02;1.7;1.95;2.11 -japan:Chi_Ri;1.5;1.58;1.4;1.25;1.61 -japan:Chi_To;1.9;1.61;1.5;1.55;1.9 -japan:Firefly_GuP;1.9;1.49;1.5;1.55;2.01 -japan:G09_Hetzer_GuP;3.7;2.6;1.8;2.54;3.47 -japan:Ha_Go;5.7;5.36;3.3;5.99;8.2 -japan:IS-2_GuP;1.6;1.44;1.4;1.33;1.61 -japan:J21_Ho_Ri;1.2;1.05;1.4;1.11;1.13 -japan:J22_Ho_Ri_type96;1.5;1.16;1.4;1.25;1.35 -japan:J23_Chi_To_SPG;1.8;1.37;1.4;1.25;1.62 -japan:J24_Type_57;1.4;1.16;1.4;1.33;1.35 -japan:J26_Type_68;1.2;1.02;1.4;1.13;1.13 -japan:J27_Chi_Se;1.4;1.15;1.4;1.27;1.35 -japan:J28_Ju_To;1.7;1.41;1.4;1.32;1.62 -japan:J29_Ju_Nu;2.1;1.73;1.4;1.57;1.8 -japan:J29_Ju_Nu_BP;2.1;1.73;1.4;1.57;1.8 -japan:J30_Mitsu_108;3.2;2.37;1.5;1.97;2.13 -japan:J31_Type_5_Ka_Ri;1.5;1.16;1.4;1.26;1.35 -japan:J36_Type_63_HT;1.4;1.15;1.4;1.29;1.35 -japan:J38_Type_95_Ji_Ro;1.5;1.16;1.4;1.22;1.35 -japan:Ke_Ho;2.9;2.71;1.9;2.55;1 -japan:Ke_Ni;3.8;4.07;2.1;2.67;5.71 -japan:Ke_Ni_B;4.5;4.85;2.1;2.14;5.71 -japan:PzVI_GuP;2.1;1.64;1.4;1.54;1.81 -japan:Pz_IV_AusfH;2.6;2.22;1.7;1.95;2.12 -japan:STA_1;1.3;1.15;1.5;1.28;1.34 -japan:Type_61;1.1;1.02;1.4;1.1;1.24 -other:AMX_dracula;1.5;1.58;1.4;1.3;1.61 -other:Akawara;1.3;1.28;1.5;1.28;1.35 -other:Oth06_Sega_Lupus;1.7;1.44;1.4;1.34;1.62 -other:Oth08_WH_Vindicator;1.8;1.74;1.4;1.24;1.61 -other:Oth09_WH_Predator;1.7;1.47;1.4;1.25;1.62 -other:Oth11_T-55A;1.1;1.1;1.4;1.09;1.16 -other:Oth12_Sega_Hafen;1.6;1.66;1.4;1.27;1.61 -other:Oth14_MdM_AshKnight;1.7;1.47;1.4;1.27;1.62 -other:Oth15_RustCrab;2.6;2.22;1.7;1.96;2.12 -other:Oth16_KV_TG;1.6;1.76;1.4;1.27;1.61 -other:Oth17_BDT-5A;2.6;2.22;1.7;1.95;2.12 -other:Oth18_BDT-5B;1.9;1.85;1.5;1.55;1.94 -other:Oth19_BDT-5C;1.5;1.66;1.4;1.25;1.61 -other:Oth20_M4FL10;1.5;1.67;1.4;1.27;1.61 -other:Oth22_Sega_Vulcan;1.7;1.41;1.4;1.33;1.62 -other:Oth23_Werewolf;1.6;1.44;1.4;1.33;1.61 -other:Oth24_StuH_42HW;3.2;2.6;1.5;1.97;2.13 -other:Oth25_HWT20_FalconFire;1.7;1.47;1.4;1.25;1.62 -other:Oth26_StrvM42HW;2.7;2.22;1.7;1.96;2.11 -other:Oth27_M24_AMX;1.9;1.64;1.6;1.6;2.02 -other:Oth28_Sturmfeur_HW;1.7;1.41;1.4;1.31;1.61 -other:Oth29_A15HW;2.8;1.83;2.3;1.96;2.12 -other:Oth30_T54MS;1.3;1.2;1.5;1.3;1.35 -other:Oth31_T150BP;2.1;1.71;1.4;1.56;1.79 -other:Oth33_Blizzard_BP;2;1.68;1.6;1.56;2.21 -other:Oth34_PanzerMummy;1.7;1.39;1.4;1.3;1.61 -other:Oth35_PanzerMejai;3.2;2.27;1.5;1.95;2.13 -other:Oth36_Indien_Panzer_S1;1.5;1.56;1.4;1.28;1.61 -other:Oth37_T26_E4_SuperPershing_S1;1.3;1.25;1.5;1.3;1.35 -other:Oth38_50TP_Tyszkiewicza_S1;1.1;1.02;1.4;1.17;1.14 -other:Oth39_T20_S2;1.5;1.62;1.4;1.28;1.61 -other:Oth40_TVP_46_S2;1.3;1.15;1.5;1.28;1.35 -other:Oth41_Centurion_Mk3_S2;1.1;1.05;1.4;1.11;1.18 -other:Oth42_ForestWitch_HW23;1.7;1.42;1.4;1.32;1.61 -other:Oth44_Charioteer_T;1.6;1.17;1.4;1.27;1.35 -other:Oth45_Sandstriker;1.4;1.16;1.4;1.33;1.35 -other:Oth46_Charioteer_S3;1.8;1.37;1.4;1.27;1.61 -other:Oth47_AMX_13_90_S3;1.4;1.3;1.5;1.28;1.34 -other:Oth48_EMIL_1951E1_S3;1.1;1.02;1.4;1.15;1.14 -other:Oth49_TM_04_NT;1.5;1.63;1.4;1.34;1.61 -other:Oth50_STT;1.6;1.55;1.5;1.28;1.61 -other:Oth51_FD_24;1.3;1.18;1.5;1.28;1.35 -other:Oth52_Toxique;1.3;1.32;1.5;1.31;1.35 -other:Oth53_Rammer;1.6;1.46;1.4;1.31;1.61 -other:Oth61_Pz_VM_Set;4.1;2.58;1.6;2.54;3.71 -other:Oth62_Pz_VlD_Set;3.2;1.97;1.5;1.96;2.13 -other:Oth63_Pz_VllF_Set;2.1;1.64;1.4;1.57;1.84 -other:Oth64_Pz_VlllK_Set;1.6;1.44;1.4;1.31;1.61 -other:Oth65_PzVIllM_Tigeom;2.1;1.67;1.4;1.59;1.86 -other:Oth66_E100ANN_S4;1.7;1.47;1.4;1.33;1.62 -other:Oth67_FV215b_183ANN_S4;1.5;1.16;1.4;1.24;1.35 -other:Oth68_KV2ANN_S4;1.2;1.03;1.4;1.23;1.14 -other:Oth69_Souleater;1.5;1.17;1.4;1.26;1.35 -other:Oth70_Lightbringer;1.6;1.47;1.4;1.29;1.61 -other:Oth71_Titan_Strife;1.3;1.16;1.4;1.28;1.35 -other:Oth72_Hazard_I;1.5;1.57;1.4;1.3;1.61 -other:Oth73_Nemini;1.4;1.17;1.4;1.31;1.35 -other:Oth74_Argonaut;1.1;1.05;1.4;1.06;1.18 -other:Oth75_Braniaq_DM5;1.1;1.06;1.4;1.14;1.18 -other:Oth76_Destiny;1.2;1.02;1.4;1.17;1.15 -other:Oth77_Prowler;1.4;1.16;1.4;1.3;1.35 -other:Oth78_AC_17_P_Sterope;1.2;1.02;1.4;1.13;1.12 -other:Oth80_AC_16_Celeno;1.2;1.02;1.4;1.13;1.12 -other:Oth81_AC_15_Taygete;1.4;1.24;1.4;1.33;1.35 -other:Oth82_AC_14_Pleione;1.4;1.15;1.4;1.29;1.35 -other:Oth83_AC_13_Merope;1.7;1.4;1.4;1.32;1.61 -other:Oth84_FVE_Defender;1.1;1.12;1.4;1.16;1.15 -other:Oth85_Ghoul;1.3;1.36;1.5;1.28;1.35 -other:Oth86_Survivalist;1.1;1.06;1.4;1.1;1.12 -other:Oth92_JagdPantherII_Titan;1.4;1.17;1.4;1.24;1.35 -other:Oth93_Convoyer;1.4;1.16;1.4;1.24;1.35 -other:Sega_Edelweiss;1.5;1.62;1.4;1.28;1.61 -other:Sega_Nameless;1.7;1.41;1.4;1.29;1.62 -other:Strv_74A2;1.9;1.8;1.5;1.54;1.9 -other:Van_Helsing;1.8;1.59;1.4;1.25;1.61 -other:WZ-135G_FT;1.8;1.5;1.4;1.3;1.61 -uk:GB03_Cruiser_Mk_I;4.4;4.9;2.7;3.96;9.12 -uk:GB04_Valentine;2.9;1.85;1.9;2.53;3.37 -uk:GB05_Vickers_Medium_Mk_II;6.5;3.84;2.2;3.8;12.37 -uk:GB06_Vickers_Medium_Mk_III;4.9;3.9;1.9;2.88;5.74 -uk:GB07_Matilda;4.1;2.83;1.6;2.56;3.64 -uk:GB08_Churchill_I;3.3;1.8;1.5;1.96;2.13 -uk:GB09_Churchill_VII;2.1;1.7;1.4;1.54;1.76 -uk:GB09_Churchill_VII_BP;2.1;1.67;1.4;1.54;1.79 -uk:GB101_FV1066_Senlac;1.4;1.16;1.5;1.24;1.33 -uk:GB106_Cobra;1.1;1.05;1.4;1.14;1.16 -uk:GB108_A46;1.5;1.56;1.5;1.25;1.6 -uk:GB109_GSOR_1008;1.6;1.16;1.4;1.25;1.34 -uk:GB109_GSOR_1008_Fearless;1.6;1.18;1.4;1.24;1.34 -uk:GB10_Black_Prince;1.7;1.47;1.4;1.23;1.62 -uk:GB110_FV4201_Chieftain_Prototype;1.1;1.02;1.4;1.13;1.15 -uk:GB111_Charlemagne;1.4;1.13;1.4;1.31;1.35 -uk:GB112_Caliban;1.4;1.16;1.4;1.45;1.35 -uk:GB114_Vickers_MBT_Mk3;1.3;1.28;1.5;1.12;1.14 -uk:GB116_Harry_Hopkins;2.5;1.97;2.3;1.96;2.12 -uk:GB11_Caernarvon;1.3;1.13;1.4;1.23;1.35 -uk:GB11_Caernarvon_D;1.4;1.16;1.4;1.3;1.35 -uk:GB128_Nemesis;1.3;1.29;1.5;1.28;1.35 -uk:GB12_Conqueror;1.1;1.02;1.4;1.1;1.13 -uk:GB19_Sherman_Firefly;1.9;1.49;1.5;1.55;2.01 -uk:GB20_Crusader;2.7;1.83;2.3;1.96;2.11 -uk:GB21_A27BP;1.9;1.7;1.5;1.55;1.94 -uk:GB21_Cromwell;1.9;1.7;1.5;1.55;1.94 -uk:GB22_Comet;1.5;1.58;1.4;1.25;1.61 -uk:GB23_Centurion;1.3;1.12;1.5;1.25;1.35 -uk:GB24_Centurion_Mk3;1.1;1.02;1.4;1.14;1.15 -uk:GB32_Tortoise;1.1;1.05;1.4;1.05;1.11 -uk:GB35_Sentinel_AC_IV;1.9;1.7;1.5;1.56;1.94 -uk:GB39_Universal_CarrierQF2;5.5;4.89;2.2;3.99;11.07 -uk:GB40_Gun_Carrier_BP;2;1.56;1.4;1.52;1.77 -uk:GB40_Gun_Carrier_Churchill;2.1;1.48;1.4;1.51;1.79 -uk:GB41_Challenger;1.8;1.35;1.4;1.26;1.61 -uk:GB42_Valentine_AT;4.3;2.68;1.9;2.43;5.48 -uk:GB44_Archer_Custom;3.2;1.76;1.5;1.98;2.08 -uk:GB51_Excelsior;3.2;2.27;1.5;1.97;2.13 -uk:GB52_FV201_A45;1.7;1.47;1.4;1.24;1.61 -uk:GB57_Alecto;3.7;2.93;1.8;2.54;1 -uk:GB58_Cruiser_Mk_III;4.4;4.9;2.5;4.26;6.52 -uk:GB59_Cruiser_Mk_IV;3.7;3.82;2.2;3.05;5.52 -uk:GB60_Covenanter;3;3;1.9;2.55;2.68 -uk:GB63_TOG_II;1.9;1.53;1.4;1.54;1.8 -uk:GB63_TOG_II_BP;1.9;1.53;1.4;1.57;1.79 -uk:GB68_Matilda_Black_Prince;2.5;1.96;1.7;1.98;2.12 -uk:GB69_Cruiser_Mk_II;5.9;5.88;3.3;5.32;8.2 -uk:GB71_AT_15A;1.7;1.58;1.4;1.22;1.62 -uk:GB72_AT15;1.4;1.19;1.4;1.19;1.35 -uk:GB73_AT2;3.2;2.45;1.5;1.97;2.13 -uk:GB74_AT8;2;1.6;1.4;1.51;1.76 -uk:GB75_AT7;1.7;1.33;1.4;1.23;1.62 -uk:GB76_Mk_VIC;4.3;5.58;2.7;4.01;9.12 -uk:GB80_Charioteer;1.6;1.16;1.4;1.27;1.34 -uk:GB81_FV4004;1.2;1.05;1.4;1.11;1.2 -uk:GB85_Cromwell_Berlin;1.9;1.7;1.5;1.55;1.94 -uk:GB87_Chieftain_T95_turret;1.4;1.17;1.4;1.25;1.35 -uk:GB93_Caernarvon_AX;1.3;1.13;1.4;1.25;1.35 -uk:GB94_Centurion_Mk5-1_RAAC;1.3;1.12;1.5;1.28;1.35 -uk:GB97_Chimera;1.3;1.15;1.5;1.32;1.35 -uk:GB99_Turtle_Mk1;1.4;1.18;1.4;1.22;1.35 -uk:GB_Centurion_MkDF;1.3;1.12;1.5;1.29;1.35 -uk:GB_Churchill_MkVI;2.1;1.73;1.4;1.54;1.77 -uk:GB_Churchill_MkVIII;2.1;1.64;1.4;1.56;1.79 -uk:GB_FV301;1.4;1.07;1.5;1.25;1.35 -uk:GB_Sherman_V;2.6;2.28;1.7;1.96;2.11 -uk:GB_Vickers_Cruiser;1.1;1;1.5;1.11;1.2 -uk:Sentinel_AC1;3;2.88;1.9;2.55;3.47 -usa:A05_M4_105BD;2.7;2.4;1.7;1.96;2.12 -usa:A100_T49;1.4;1.53;1.5;1.29;1.34 -usa:A102_T28_concept;1.7;1.47;1.4;1.27;1.62 -usa:A104_M4A3E8A;1.9;1.88;1.5;1.54;1.84 -usa:A111_T25_Pilot;1.3;1.27;1.5;1.26;1.35 -usa:A115_Chrysler_K;1.4;1.16;1.4;1.3;1.35 -usa:A117_T26E5;1.4;1.15;1.4;1.27;1.35 -usa:A121_T26E3;1.5;1.54;1.4;1.31;1.61 -usa:A122_TS-5;1.4;1.15;1.4;1.23;1.35 -usa:A124_T54E2;1.4;1.15;1.4;1.29;1.35 -usa:A125_AEP_1;1.1;1.02;1.4;1.15;1.14 -usa:A130_Super_Hellcat;1.7;1.51;1.4;1.24;1.6 -usa:A132_T77;1.3;1.15;1.4;1.29;1.35 -usa:A136_T42;1.5;1.54;1.4;1.29;1.61 -usa:A140_ASTRON_REX_105;1.3;1.31;1.5;1.31;1.35 -usa:A141_M-4-Y;1.4;1.16;1.4;1.27;1.35 -usa:A142_Pawlack_Tank;2.1;1.68;1.4;1.58;1.85 -usa:A146_TL_7_120;1.1;1.02;1.4;1.14;1.14 -usa:A150_MBT_B;1.2;1.02;1.4;1.21;1.13 -usa:A151_TS_60;1.3;1.14;1.4;1.06;1.15 -usa:A175_ATAC;1.1;1.02;1.5;1.11;1.32 -usa:A68_T28D;1.4;1.2;1.4;1.28;1.35 -usa:A77_T6M;3;2.72;1.9;2.52;3.24 -usa:A94_T37;1.9;1.73;1.6;1.55;2.21 -usa:A99_T92E1;1.1;1.1;1.5;1.2;1.2 -usa:M-3-Y;1.4;1.14;1.4;1.28;1.35 -usa:M-3-Y-Midas;1.4;1.14;1.4;1.28;1.35 -usa:M-5-Y;1.1;1.02;1.4;1.2;1.14 -usa:M-7-Y;1.7;1.32;1.4;1.32;1.61 -usa:M103;1.1;1.02;1.4;1.14;1.13 -usa:M10_Wolverine;3.2;2.18;1.5;1.96;2.13 -usa:M18E2BP;1.9;1.65;1.5;1.57;2.02 -usa:M18_Hellcat;2.1;1.58;1.4;1.51;2.99 -usa:M22_Locust;3.8;4.23;2.1;3;5.59 -usa:M24_Chaffee;1.9;1.73;1.6;1.55;2.36 -usa:M24_Chaffee_BP;1.9;1.73;1.6;1.55;2.36 -usa:M2_lt;5.5;5.28;3.3;6.76;7.39 -usa:M2_med;3.6;4.29;2.2;2.93;5.54 -usa:M36_BP;2.1;1.58;1.4;1.53;1.85 -usa:M36_Slagger;2.1;1.58;1.4;1.54;1.85 -usa:M3_Grant;3;2.38;1.9;2.55;3.13 -usa:M3_Stuart;4.4;4.29;2.7;4.23;5.89 -usa:M41_Bulldog;1.6;1.49;1.5;1.25;1.61 -usa:M46_CL;1.1;1.04;1.4;1.08;1.17 -usa:M46_Patton;1.1;1.08;1.4;1.09;1.17 -usa:M4A2E4;2.6;2.28;1.7;1.96;2.13 -usa:M4A3E8_BP;1.9;1.88;1.5;1.55;1.84 -usa:M4A3E8_Sherman;1.9;1.88;1.5;1.55;1.84 -usa:M4_Sherman;2.7;1.98;1.7;1.95;2.12 -usa:M5_Stuart;3.7;3.48;2.1;2.94;5.57 -usa:M6;2.1;1.6;1.4;1.58;1.8 -usa:M6A2E1;1.6;1.32;1.4;1.33;1.62 -usa:M6A2E1_exp;1.4;1.07;1.4;1.33;1.35 -usa:M6E2BP;2.1;1.6;1.4;1.57;1.77 -usa:M6E2V2_BP;2.1;1.64;1.4;1.53;1.78 -usa:M7_med;3.1;2.38;1.9;2.54;2.98 -usa:M8A1;4;2.73;1.8;2.54;2.87 -usa:Pershing;1.3;1.33;1.5;1.25;1.35 -usa:Ram-II;2.5;2.22;1.7;1.97;2.12 -usa:Sherman_Jumbo;1.9;1.88;1.5;1.56;1.8 -usa:T14;3.2;2.35;1.5;1.97;2.13 -usa:T18;5.5;4.48;2.2;4.43;5.65 -usa:T1_E6;4.4;5;2.7;4.12;8.54 -usa:T1_hvy;3.3;2;1.5;1.96;2.13 -usa:T20;1.5;1.54;1.4;1.28;1.61 -usa:T21;1.9;1.81;1.6;1.57;2.27 -usa:T21_BP;1.9;1.81;1.6;1.56;2.24 -usa:T23E3;1.5;1.54;1.4;1.25;1.61 -usa:T25_2;1.7;1.4;1.4;1.23;1.61 -usa:T25_AT;1.7;1.44;1.4;1.26;1.61 -usa:T26_E4_SuperPershing;1.3;1.2;1.5;1.29;1.35 -usa:T28;1.4;1.16;1.4;1.23;1.35 -usa:T28_Prototype;1.4;1.16;1.4;1.24;1.35 -usa:T29;1.7;1.32;1.4;1.31;1.61 -usa:T2_lt;4.3;5.45;2.7;3.9;8.26 -usa:T2_med;4.6;5;2.5;4.23;7.11 -usa:T30;1.1;1.05;1.4;1.12;1.17 -usa:T32;1.4;1.16;1.4;1.29;1.35 -usa:T34_hvy;1.3;1.07;1.4;1.3;1.35 -usa:T34_hvy_Freedom;1.4;1.07;1.4;1.31;1.35 -usa:T40;4;2.38;1.8;2.54;3.13 -usa:T49;3.2;2.18;1.5;1.97;2.08 -usa:T49_Fearless;1.4;1.53;1.5;1.28;1.34 -usa:T54E1;1.1;1.06;1.4;1.1;1.16 -usa:T69;1.3;1.33;1.5;1.27;1.35 -usa:T71;1.6;1.49;1.5;1.27;1.6 -usa:T7_Combat_Car;4.4;5.58;2.7;4.26;8.54 -usa:T82;4.1;13.5;1.9;2.78;5.78 -usa:T95;1.2;1.05;1.4;1.11;1.12 -usa:T95E2;1.3;1.33;1.5;1.25;1.35 -ussr:A-20;3.1;2.72;1.9;2.54;3 -ussr:A-32;3;2.85;1.9;2.52;3.43 -ussr:AT-1;5.5;3.99;2.2;3.99;8.51 -ussr:BT-2;4.3;4.71;2.7;4.18;6.52 -ussr:BT-7;3.6;2.98;2.1;3.06;5.51 -ussr:BT-SV;3.8;2.86;2.1;3.16;5.58 -ussr:Churchill_LL;3.2;2.27;1.5;1.97;2.13 -ussr:GAZ-74b;4;2.48;1.8;2.53;3.05 -ussr:IS;1.6;1.44;1.4;1.32;1.62 -ussr:IS-2Sh;1.4;1.15;1.4;1.31;1.35 -ussr:IS-3;1.4;1.16;1.4;1.3;1.35 -ussr:IS8;1.2;1.02;1.4;1.12;1.15 -ussr:ISU-152;1.6;1.13;1.4;1.38;1.35 -ussr:K-91;1.1;1.02;1.4;1.1;1.12 -ussr:KV-13;1.5;1.67;1.4;1.26;1.61 -ussr:KV-1s;2.3;1.6;1.4;1.6;1.82 -ussr:KV-1s-BP;2.3;1.5;1.4;1.64;1.83 -ussr:KV-220;3.2;2.27;1.5;1.96;2.13 -ussr:KV-3;1.7;1.44;1.4;1.33;1.62 -ussr:KV-5;1.5;1.15;1.4;1.29;1.35 -ussr:KV1;3.2;2.11;1.5;1.95;2.13 -ussr:KV2;2.4;2.25;1.4;1.52;1.85 -ussr:KV4;1.4;1.13;1.4;1.3;1.35 -ussr:LTP;3.8;2.88;2.1;3.16;5.56 -ussr:M3_Stuart_LL;3.7;5.04;2.1;2.86;5.51 -ussr:MS-1obr21;4.3;4.71;2.7;4.18;6.98 -ussr:MT25;2;1.79;1.6;1.56;2.06 -ussr:Matilda_II_LL;2.5;2.22;1.7;1.96;2.12 -ussr:Object252;1.4;1.14;1.4;1.33;1.35 -ussr:Object252_Fearless;1.4;1.41;1.4;1.33;1.35 -ussr:Object_704;1.1;1.05;1.4;1.1;1.13 -ussr:R105_BT_7A;3.8;6.27;2.1;3.86;5.64 -ussr:R107_LTB;1.6;1.56;1.5;1.28;1.61 -ussr:R109_T54S;1.4;1.34;1.5;1.29;1.34 -ussr:R111_ISU130;1.5;1.16;1.4;1.23;1.35 -ussr:R112_T54_45;1.3;1.37;1.5;1.31;1.35 -ussr:R113_Object_730;1.4;1.15;1.4;1.31;1.35 -ussr:R115_IS-3_auto;1.4;1.15;1.4;1.3;1.35 -ussr:R116_ISU122C_Berlin;1.8;1.45;1.4;1.22;1.62 -ussr:R127_T44_100_P;1.3;1.33;1.5;1.27;1.35 -ussr:R131_LTG;1.6;1.61;1.5;1.28;1.61 -ussr:R134_Object_252U;1.4;1.14;1.4;1.33;1.35 -ussr:R140_M4A2_Loza;1.9;1.88;1.5;1.55;1.84 -ussr:R146_STG;1.3;1.33;1.5;1.33;1.35 -ussr:R154_T-34E-43;2.6;2.22;1.7;1.96;2.11 -ussr:R158_LT_432;1.4;1.34;1.5;1.28;1.34 -ussr:R159_SU_130PM;1.6;1.16;1.4;1.26;1.34 -ussr:R160_object_244;2.1;1.71;1.4;1.55;1.82 -ussr:R172_Object_752;1.1;1.02;1.4;1.13;1.13 -ussr:R175_IS_2E;1.6;1.47;1.4;1.31;1.61 -ussr:R180_Object_274A;1.3;1.31;1.5;1.3;1.35 -ussr:R181_Object_452K;1.2;1.02;1.4;1.15;1.13 -ussr:R187_Object_590;1.3;1.32;1.5;1.29;1.35 -ussr:R192_K_2;1.4;1.12;1.4;1.33;1.35 -ussr:R194_ST_62_var_2;1.3;1.31;1.5;1.28;1.35 -ussr:R217_Object_Kust;1.5;1.16;1.4;1.27;1.35 -ussr:R43_T-70_57;3.8;3.41;2.1;3.43;5.49 -ussr:R63_ST_IBD;1.2;1.02;1.4;1.17;1.14 -ussr:R71_IS_2B;1.6;1.44;1.4;1.33;1.61 -ussr:R98_T44_85;1.5;1.64;1.4;1.26;1.61 -ussr:R_Object_84;1.1;1.07;1.5;1.12;1.17 -ussr:ST_I;1.2;1.02;1.4;1.16;1.13 -ussr:SU-100;2.1;1.57;1.4;1.55;1.81 -ussr:SU-101;1.5;1.15;1.4;1.26;1.35 -ussr:SU-152;1.8;2;1.4;1.38;1.61 -ussr:SU-76;4.2;3.6;1.9;3.18;5.56 -ussr:SU-85;3.2;1.99;1.5;1.97;2.12 -ussr:SU100M1;1.7;1.35;1.4;1.25;1.61 -ussr:SU100Y;2.1;1.53;1.4;1.57;1.89 -ussr:SU122_44;1.8;1.45;1.4;1.25;1.61 -ussr:SU122_54;1.2;1.05;1.4;1.03;1.14 -ussr:SU76I;4.1;3.31;1.9;3.18;5.47 -ussr:SU_85I;3.1;2.3;1.5;1.98;2.12 -ussr:T-127;3.8;2.76;2.1;3.16;5.52 -ussr:T-26;5.5;5.07;3.3;6.16;8.2 -ussr:T-28;3;2.85;1.9;2.54;3.09 -ussr:T-34;2.7;2.01;1.7;1.96;2.12 -ussr:T-34-85;1.9;1.71;1.5;1.58;1.87 -ussr:T-34-85_Rudy;1.5;1.67;1.4;1.26;1.61 -ussr:T-34-85_Victory;1.9;1.88;1.5;1.57;1.87 -ussr:T-43;1.5;1.6;1.4;1.25;1.61 -ussr:T-44;1.3;1.33;1.5;1.34;1.35 -ussr:T-46;3.8;4.32;2.1;2.83;5.6 -ussr:T-54;1.1;1.1;1.4;1.09;1.16 -ussr:T150;2.1;1.55;1.4;1.59;1.82 -ussr:T34_85BP;1.9;1.88;1.5;1.57;1.87 -ussr:T34_85TUR;1.5;1.58;1.4;1.29;1.62 -ussr:Tetrarch_LL;4.3;3.88;2.7;3.88;8.51 -ussr:Valentine_LL;2.9;4.55;1.9;2.55;3.42 From dafa834d28433631294b30a356b5dad544bf3f2a Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sun, 9 Aug 2026 20:10:04 -0500 Subject: [PATCH 120/147] some script cleanup --- package.json | 3 +- packages/scripts/package.json | 9 +- packages/scripts/src/averages.ts | 229 ------------- .../scripts/src/buildCatalogItemAccessor.ts | 62 ---- packages/scripts/src/buildProxyClient.ts | 40 --- packages/scripts/src/patchNotes.ts | 319 ------------------ packages/scripts/src/pruneStrings.ts | 50 --- 7 files changed, 7 insertions(+), 705 deletions(-) delete mode 100644 packages/scripts/src/averages.ts delete mode 100644 packages/scripts/src/buildCatalogItemAccessor.ts delete mode 100644 packages/scripts/src/buildProxyClient.ts delete mode 100644 packages/scripts/src/patchNotes.ts delete mode 100644 packages/scripts/src/pruneStrings.ts diff --git a/package.json b/package.json index 9af30f596..2ed4ceb5f 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "scripts": { "dev": "cd packages/website && bun run dev", "build": "cd packages/website && bun run build", - "discover-ids": "cd packages/scripts && bun run discover-ids" + "discover-ids": "cd packages/scripts && bun run discover-ids", + "build-protos": "cd packages/scripts && bun run build-protos" }, "type": "module", "workspaces": [ diff --git a/packages/scripts/package.json b/packages/scripts/package.json index d72a23bc7..393c95942 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -1,5 +1,10 @@ { "name": "@blitzkit/scripts", + "private": true, + "scripts": { + "discover-ids": "bun src/discoverIds/index.ts", + "build-protos": "bun src/protos.ts" + }, "dependencies": { "@blitzkit/closed": "workspace:*", "@blitzkit/core": "workspace:*", @@ -34,9 +39,5 @@ "unzipit": "^1.4.3", "yaml": "^2.8.1" }, - "private": true, - "scripts": { - "discover-ids": "bun src/discoverIds/index.ts" - }, "type": "module" } diff --git a/packages/scripts/src/averages.ts b/packages/scripts/src/averages.ts deleted file mode 100644 index 4f2ae28ca..000000000 --- a/packages/scripts/src/averages.ts +++ /dev/null @@ -1,229 +0,0 @@ -import { - AverageDefinitions, - AverageDefinitionsAllStats, - AverageDefinitionsEntry, - type IndividualTankStats, - REGIONS, - type Region, - Samples, - averageDefinitionsAllStatsKeys, - emptySamples, - getAccountInfo, - getTankStats, - idToRegion, -} from "@blitzkit/core"; -import { chunk, times } from "lodash-es"; -import { AssetUploader } from "./core/github/assetUploader"; -import { fetchPreDiscoveredIds } from "./core/github/fetchPreDiscoveredIds"; - -interface DataPoint { - x: number; - y: number; - w: number; -} - -const MINUTE = 60 * 1000; -const HOUR = 60 * MINUTE; -const DAY = 24 * HOUR; -// const MAX_ACTIVITY_TIME = 120 * DAY; -// const MIN_BATTLES = 5000; -const RUN_TIME = 5 * HOUR + 55 * MINUTE; -const THREADS = 10; -const PLAYER_IDS_PER_CALL = 100; - -const startTime = Date.now(); -const preDiscoveredIds = await fetchPreDiscoveredIds(); -const playerIds: Record = { - asia: chunk( - preDiscoveredIds.filter((id) => idToRegion(id) === "asia"), - PLAYER_IDS_PER_CALL, - ), - com: chunk( - preDiscoveredIds.filter((id) => idToRegion(id) === "com"), - PLAYER_IDS_PER_CALL, - ), - eu: chunk( - preDiscoveredIds.filter((id) => idToRegion(id) === "eu"), - PLAYER_IDS_PER_CALL, - ), -}; -let regionIndex = 0; -const samples: Samples = { ...emptySamples }; -const availableRegions = [...REGIONS]; -const tankIds: number[] = []; -const tanksSorted: Record = {}; -let postWorkRequested = false; - -times(THREADS, async () => { - while (availableRegions.length > 0 && startTime + RUN_TIME > Date.now()) { - const region = availableRegions[regionIndex]; - const regionIds = playerIds[region]; - const [ids] = regionIds.splice( - Math.floor(Math.random() * regionIds.length), - 1, - ); - const accountInfo = await getAccountInfo(region, ids, undefined, { - fields: "last_battle_time,statistics.all.battles", - }); - - const filteredIds = ids.filter((_, index) => { - const info = accountInfo[index]; - - if (info === null) return false; - - const timeSinceLastActivity = Date.now() - info.last_battle_time * 1000; - - samples.total++; - if (timeSinceLastActivity <= 120 * DAY) samples.d_120++; - if (timeSinceLastActivity <= 90 * DAY) samples.d_90++; - if (timeSinceLastActivity <= 60 * DAY) samples.d_60++; - if (timeSinceLastActivity <= 30 * DAY) samples.d_30++; - if (timeSinceLastActivity <= 7 * DAY) samples.d_7++; - if (timeSinceLastActivity <= 1 * DAY) samples.d_1++; - - return true; - - // return ( - // timeSinceLastActivity <= MAX_ACTIVITY_TIME && - // info.statistics.all.battles > MIN_BATTLES - // ); - }); - - const players = await Promise.all( - filteredIds.map((id) => - getTankStats(region, id, { - fields: "last_battle_time,battle_life_time,all,tank_id", - }), - ), - ); - - players.forEach((tanks) => { - tanks?.forEach((tank) => { - if (tank.all.battles === 0) return; - - if (!tankIds.includes(tank.tank_id)) { - console.log( - `Found new tank: ${tank.tank_id} (${tankIds.length + 1})`, - ); - tankIds.push(tank.tank_id); - tanksSorted[tank.tank_id] = []; - } - - tanksSorted[tank.tank_id].push(tank); - }); - }); - - if (regionIds.length === 0 && availableRegions.includes(region)) { - availableRegions.splice(availableRegions.indexOf(region), 1); - } - regionIndex = (regionIndex + 1) % availableRegions.length; - } - - if (!postWorkRequested) { - postWorkRequested = true; - postWork(); - } -}); - -async function postWork() { - console.log( - `Generating statistics based on ${samples.d_120.toLocaleString()} players (${samples.total.toLocaleString()} checked in total) and ${ - tankIds.length - } tanks...`, - ); - - using uploader = new AssetUploader("averages"); - const averages: Record = {}; - - tankIds.forEach((id) => { - const tanksSortedEntry = tanksSorted[id]; - // const tanks = tanksSortedEntry.filter( - // (tank) => Date.now() - tank.last_battle_time * 1000 <= MAX_ACTIVITY_TIME, - // ); - const tanks = tanksSortedEntry; - const entry: { - mu: Partial>; - sigma: Partial>; - r: Partial>; - samples: Samples; - } = { - mu: {}, - sigma: {}, - r: {}, - samples: { ...emptySamples }, - }; - - tanks.forEach((tank) => { - const timeSinceLastActivity = Date.now() - tank.last_battle_time * 1000; - - entry.samples!.total++; - if (timeSinceLastActivity <= 120 * DAY) entry.samples!.d_120++; - if (timeSinceLastActivity <= 90 * DAY) entry.samples!.d_90++; - if (timeSinceLastActivity <= 60 * DAY) entry.samples!.d_60++; - if (timeSinceLastActivity <= 30 * DAY) entry.samples!.d_30++; - if (timeSinceLastActivity <= 7 * DAY) entry.samples!.d_7++; - if (timeSinceLastActivity <= 1 * DAY) entry.samples!.d_1++; - }); - - const dataWY = tanks.map((tank) => ({ - w: tank.all.battles, - y: tank.all.wins / tank.all.battles, - })); - - averageDefinitionsAllStatsKeys.forEach((key) => { - const data: DataPoint[] = dataWY.map(({ w, y }, index) => ({ - w, - x: - (key === "battle_life_time" - ? tanks[index].battle_life_time - : tanks[index].all[key]) / w, - y, - })); - - if (data.length === 0) return; - - function sum(slicer: (data: DataPoint) => number) { - return data.reduce( - (accumulator, data) => accumulator + slicer(data), - 0, - ); - } - - const sum_w = sum(({ w }) => w); - const sum_wx = sum(({ w, x }) => w * x); - const sum_wy = sum(({ w, y }) => w * y); - const x_bar = sum_wx / sum_w; - const y_bar = sum_wy / sum_w; - // undo normalization for mu to get a simple average - const mu = sum(({ w, x }) => w * x) / samples.d_120; - - const sigma_numerator = sum(({ w, x }) => w * (x - x_bar) ** 2); - const sigma = Math.sqrt(sigma_numerator / sum_w); - - const r_numerator = sum(({ w, x, y }) => w * (x - x_bar) * (y - y_bar)); - const r_denominator_x = sum(({ w, x }) => w * (x - x_bar) ** 2); - const r_denominator_y = sum(({ w, y }) => w * (y - y_bar) ** 2); - const r = r_numerator / Math.sqrt(r_denominator_x * r_denominator_y); - - entry.mu[key] = mu; - entry.sigma[key] = sigma; - entry.r[key] = isNaN(r) ? 0 : r; - }); - - averages[id] = entry as AverageDefinitionsEntry; - }); - - const time = Date.now(); - const averageDefinitions = { - averages, - samples, - time, - } satisfies AverageDefinitions; - - await uploader.add({ - path: "averages.pb", - content: AverageDefinitions.encode(averageDefinitions).finish(), - }); - - await uploader.flush(); -} diff --git a/packages/scripts/src/buildCatalogItemAccessor.ts b/packages/scripts/src/buildCatalogItemAccessor.ts deleted file mode 100644 index 8fb850e70..000000000 --- a/packages/scripts/src/buildCatalogItemAccessor.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { readdir, readFile, writeFile } from "fs/promises"; - -const SOURCES = "../../packages/closed/protos"; -const TARGET = "../../packages/closed/src/unreal/catalogItemAccessor.ts"; - -const componentPattern = /^message (\w+)Component {/gm; - -const files = await readdir(SOURCES).then((files) => - files.filter( - (file) => - file.startsWith("blitz_static_") && file.endsWith("_component.proto") - ) -); - -const imports: { name: string; file: string }[] = []; - -for (const file of files) { - const content = await readFile(`${SOURCES}/${file}`).then((buffer) => - buffer.toString() - ); - const components = content.matchAll(componentPattern); - - for (const match of components) { - const name = match[1]; - const duplicate = imports.find((_import) => _import.name === name); - - if (duplicate) { - console.warn( - `Duplicate component ${name} across ${duplicate.file}.proto and ${file}` - ); - continue; - } - - const importFile = file.replace(".proto", ""); - - imports.push({ name, file: importFile }); - } -} - -let content = ""; - -for (const _import of imports) { - content += `import { ${_import.name}Component } from "@protos/${_import.file}";\n`; -} - -content += - 'import { BaseCatalogItemAccessor } from "./baseCatalogItemAccessor";\n\n'; -content += - "export class CatalogItemAccessor extends BaseCatalogItemAccessor {\n"; - -for (const _import of imports) { - const strippedName = _import.name.replace("BlitzStatic", ""); - const defaultName = strippedName[0].toLowerCase() + strippedName.slice(1); - - content += ` ${strippedName}(name = "${defaultName}Component") {\n`; - content += ` return this.component(name, ${_import.name}Component);\n`; - content += " }\n"; -} - -content += "}\n"; - -await writeFile(TARGET, content); diff --git a/packages/scripts/src/buildProxyClient.ts b/packages/scripts/src/buildProxyClient.ts deleted file mode 100644 index 2f0cee0ab..000000000 --- a/packages/scripts/src/buildProxyClient.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { readFile, writeFile } from "fs/promises"; - -const IMPORT = "@protos/auto_legacy_proxy_client"; -const SOURCE = "../../packages/closed/protos/auto_legacy_proxy_client.proto"; -const TARGET = "../../packages/closed/src/unreal/methodPopulatedProxyClient.ts"; - -const requestPattern = /^message (\w+)Request {/gm; -const source = await readFile(SOURCE).then((buffer) => buffer.toString()); -const requests = source.matchAll(requestPattern); - -const imports: string[] = []; -const names: string[] = []; - -for (const match of requests) { - const name = match[1]; - - imports.push(`${name}Request`, `${name}Response`); - names.push(name); -} - -let content = ""; - -content += "import {\n"; -content += imports.map((i) => ` ${i},`).join("\n"); -content += `\n} from "${IMPORT}";\n`; - -content += 'import { BaseProxyClient } from "./baseProxyClient";\n\n'; - -content += - "export abstract class MethodPopulatedProxyClient extends BaseProxyClient {\n"; -content += names - .map((name) => { - return ` ${name}(data: ${name}Request) { - return this.request("${name}Request", ${name}Request, data, ${name}Response); - }\n`; - }) - .join(""); -content += "}\n"; - -await writeFile(TARGET, content); diff --git a/packages/scripts/src/patchNotes.ts b/packages/scripts/src/patchNotes.ts deleted file mode 100644 index ba02e8fe1..000000000 --- a/packages/scripts/src/patchNotes.ts +++ /dev/null @@ -1,319 +0,0 @@ -import { - availableProvisions, - createDefaultProvisions, - createDefaultSkills, - fetchConsumableDefinitions, - fetchEquipmentDefinitions, - fetchModelDefinitions, - fetchProvisionDefinitions, - fetchSkillDefinitions, - fetchTankDefinitions, - TankDefinition, - tankIcon, -} from "@blitzkit/core"; -import { checkConsumableProvisionInclusivity } from "@blitzkit/core/src/blitzkit/checkConsumableProvisionInclusivity"; -import strings from "@blitzkit/i18n/strings/en.json"; -import { writeFile } from "fs/promises"; -import { isEqual } from "lodash-es"; -import { tankCharacteristics } from "../../website/src/core/blitzkit/tankCharacteristics"; -import { genericDefaultEquipmentMatrix } from "../../website/src/stores/duel/constants"; - -const domain = "https://opentest.blitzkit.app"; - -import.meta.env.PUBLIC_ASSET_BASE = "https://blitzkit.github.io/assets"; -const currentTankDefinitions = await fetchTankDefinitions(); -const currentConsumableDefinitions = await fetchConsumableDefinitions(); -const currentProvisionDefinitions = await fetchProvisionDefinitions(); -const currentEquipmentDefinitions = await fetchEquipmentDefinitions(); -const currentSkillDefinitions = await fetchSkillDefinitions(); -const currentModelDefinitions = await fetchModelDefinitions(); - -import.meta.env.PUBLIC_ASSET_BASE = "https://blitzkit.github.io/assets-preview"; -const previewTankDefinitions = await fetchTankDefinitions(); -const previewConsumableDefinitions = await fetchConsumableDefinitions(); -const previewProvisionDefinitions = await fetchProvisionDefinitions(); -const previewEquipmentDefinitions = await fetchEquipmentDefinitions(); -const previewSkillDefinitions = await fetchSkillDefinitions(); -const previewModelDefinitions = await fetchModelDefinitions(); - -let notes = "# BlitzKit OpenTest Patch Notes\n\n"; - -const newTanks: TankDefinition[] = []; - -for (const id in previewTankDefinitions.tanks) { - if (!(id in currentTankDefinitions.tanks)) { - newTanks.push(previewTankDefinitions.tanks[id]); - } -} - -newTanks.sort((a, b) => b.tier - a.tier); - -import.meta.env.PUBLIC_ASSET_BASE = "https://blitzkit.github.io/assets-preview"; -if (newTanks.length > 0) { - notes += "## New Tanks\n\n"; - - for (const tank of newTanks) { - notes += `### [${tank.name!.locales.en}](${domain}/tanks/${tank.slug})\n`; - notes += `![](${tankIcon(tank.id)})\n\n`; - } - - notes += "\n"; -} - -const changedTanks: [TankDefinition, TankDefinition][] = []; - -for (const stringId in previewTankDefinitions.tanks) { - const id = Number(stringId); - - if (newTanks.some((tank) => tank.id === id)) continue; - - const current = currentTankDefinitions.tanks[id]; - const preview = previewTankDefinitions.tanks[id]; - - if (isEqual(current, preview)) continue; - - changedTanks.push([current, preview]); -} - -changedTanks.sort((a, b) => b[1].tier - a[1].tier); - -import.meta.env.PUBLIC_ASSET_BASE = "https://blitzkit.github.io/assets"; -if (changedTanks.length > 0) { - notes += "## Changed Tanks\n\n"; - - for (const [current, preview] of changedTanks) { - const currentTurret = current.turrets.at(-1)!; - const previewTurret = preview.turrets.at(-1)!; - const currentGun = currentTurret.guns.at(-1)!; - const previewGun = previewTurret.guns.at(-1)!; - const currentShell = currentGun.shells[0]; - const previewShell = previewGun.shells[0]; - const currentEngine = current.engines.at(-1)!; - const previewEngine = preview.engines.at(-1)!; - const currentTracks = current.tracks.at(-1)!; - const previewTracks = preview.tracks.at(-1)!; - const currentTankModel = currentModelDefinitions.models[current.id]; - const previewTankModel = previewModelDefinitions.models[preview.id]; - - const currentConsumables = Object.values( - currentConsumableDefinitions.consumables, - ).filter((consumable) => - checkConsumableProvisionInclusivity(consumable, current, currentGun), - ); - const previewConsumables = Object.values( - previewConsumableDefinitions.consumables, - ).filter((consumable) => - checkConsumableProvisionInclusivity(consumable, preview, previewGun), - ); - const addedConsumables = previewConsumables.filter( - (previewConsumable) => - !currentConsumables.some( - (currentConsumable) => currentConsumable.id === previewConsumable.id, - ), - ); - const removedConsumables = currentConsumables.filter( - (currentConsumable) => - !previewConsumables.some( - (previewConsumable) => previewConsumable.id === currentConsumable.id, - ), - ); - - const currentProvisions = availableProvisions( - current, - currentGun, - currentProvisionDefinitions, - ); - const previewProvisions = availableProvisions( - preview, - previewGun, - previewProvisionDefinitions, - ); - const addedProvisions = previewProvisions.filter( - (previewProvision) => - !currentProvisions.some( - (currentProvision) => currentProvision.id === previewProvision.id, - ), - ); - const removedProvisions = currentProvisions.filter( - (currentProvision) => - !previewProvisions.some( - (previewProvision) => previewProvision.id === currentProvision.id, - ), - ); - - const currentEquipment = currentEquipmentDefinitions.presets[ - current.equipment_preset - ].slots - .map(({ left, right }) => [left, right]) - .flat(); - const previewEquipment = previewEquipmentDefinitions.presets[ - preview.equipment_preset - ].slots - .map(({ left, right }) => [left, right]) - .flat(); - const addedEquipment = previewEquipment - .filter( - (previewEquipment) => - !currentEquipment.some( - (currentEquipment) => currentEquipment === previewEquipment, - ), - ) - .map((id) => previewEquipmentDefinitions.equipments[id]); - const removedEquipment = currentEquipment - .filter( - (currentEquipment) => - !previewEquipment.some( - (previewEquipment) => previewEquipment === currentEquipment, - ), - ) - .map((id) => currentEquipmentDefinitions.equipments[id]); - - const equalize = false; - const currentCharacteristics = tankCharacteristics( - { - tank: current, - gun: currentGun, - shell: currentShell, - applyDynamicArmor: false, - applyReactiveArmor: false, - applySpallLiner: false, - assaultDistance: 0, - crewSkills: createDefaultSkills(currentSkillDefinitions), - camouflage: true, - consumables: [], - engine: currentEngine, - provisions: createDefaultProvisions( - current, - currentGun, - currentProvisionDefinitions, - ), - equipmentMatrix: genericDefaultEquipmentMatrix, - stockEngine: current.engines[0], - stockGun: current.turrets[0].guns[0], - stockTrack: current.tracks[0], - stockTurret: current.turrets[0], - track: currentTracks, - turret: currentTurret, - equalize, - }, - { - equipmentDefinitions: currentEquipmentDefinitions, - provisionDefinitions: currentProvisionDefinitions, - tankModelDefinition: currentTankModel, - }, - ); - const previewCharacteristics = tankCharacteristics( - { - tank: preview, - gun: previewGun, - shell: previewShell, - applyDynamicArmor: false, - applyReactiveArmor: false, - applySpallLiner: false, - assaultDistance: 0, - crewSkills: createDefaultSkills(previewSkillDefinitions), - camouflage: true, - consumables: [], - engine: previewEngine, - provisions: createDefaultProvisions( - preview, - previewGun, - previewProvisionDefinitions, - ), - equipmentMatrix: genericDefaultEquipmentMatrix, - stockEngine: preview.engines[0], - stockGun: preview.turrets[0].guns[0], - stockTrack: preview.tracks[0], - stockTurret: preview.turrets[0], - track: previewTracks, - turret: previewTurret, - equalize, - }, - { - equipmentDefinitions: previewEquipmentDefinitions, - provisionDefinitions: previewProvisionDefinitions, - tankModelDefinition: previewTankModel, - }, - ); - const changedCharacteristics: string[] = []; - - for (const key in currentCharacteristics) { - const characteristicKey = key as keyof typeof currentCharacteristics; - if ( - currentCharacteristics[characteristicKey] !== - previewCharacteristics[characteristicKey] - ) { - changedCharacteristics.push(characteristicKey); - } - } - - notes += `### [${preview.name!.locales.en}](${domain}/tanks/${preview.slug})\n`; - notes += `![](${tankIcon(preview.id)})\n\n`; - - if (addedConsumables.length + removedConsumables.length > 0) { - notes += `Consumables: `; - notes += [ - ...addedConsumables.map( - (consumable) => `🟢 ${consumable.name!.locales.en}`, - ), - ...removedConsumables.map( - (consumable) => `🔴 ${consumable.name!.locales.en}`, - ), - ].join(", "); - notes += `\n`; - } - - if (addedProvisions.length + removedProvisions.length > 0) { - notes += `Provisions: `; - notes += [ - ...addedProvisions.map( - (provision) => `🟢 ${provision.name!.locales.en}`, - ), - ...removedProvisions.map( - (provision) => `🔴 ${provision.name!.locales.en}`, - ), - ].join(", "); - notes += `\n`; - } - - if (addedEquipment.length + removedEquipment.length > 0) { - notes += `-Equipment: `; - notes += [ - ...addedEquipment.map( - (equipment) => `🟢 ${equipment.name!.locales.en}`, - ), - ...removedEquipment.map( - (equipment) => `🔴 ${equipment.name!.locales.en}`, - ), - ].join(", "); - notes += `\n`; - } - - if (changedCharacteristics.length > 0) { - notes += `Characteristics:\n`; - - for (const characteristicUntyped of changedCharacteristics) { - const characteristic = - characteristicUntyped as keyof typeof currentCharacteristics; - const name = - strings.website.tools.tankopedia.characteristics.values[ - characteristic - ]; - const before = currentCharacteristics[characteristic]; - const after = previewCharacteristics[characteristic]; - - notes += `- ${ - typeof before === "number" && typeof after === "number" - ? after > before - ? "⬆️" - : "🔻" - : "✏️" - } ${name}: \`${ - typeof before === "number" ? before.toFixed(2) : before - }\` → \`${typeof after === "number" ? after.toFixed(2) : after}\`\n`; - } - } - } -} - -await writeFile("../../temp/patch-notes.md", notes); diff --git a/packages/scripts/src/pruneStrings.ts b/packages/scripts/src/pruneStrings.ts deleted file mode 100644 index ce6e5c8a0..000000000 --- a/packages/scripts/src/pruneStrings.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { type TranslationTree } from "@blitzkit/i18n"; -import locales from "@blitzkit/i18n/locales.json"; -import { writeFile } from "fs/promises"; - -const ROOT = "@blitzkit/i18n/strings"; -const WORKING_ROOT = "../../packages/i18n/strings"; - -const defaultStrings = require(`${ROOT}/${locales.default}.json`).default; -const nonDefaultLocales = locales.supported.filter( - ({ locale }) => locale !== locales.default -); - -function prune(defaultStrings: TranslationTree, strings: TranslationTree) { - for (const key in strings) { - if (key in defaultStrings) { - if (typeof defaultStrings[key] === typeof strings[key]) { - if (typeof strings[key] === "string") { - if ( - // are equivalent regardless of %s flavor - (defaultStrings[key] as string).replaceAll(/%s\d/g, "%s") === - (strings[key] as string).replaceAll(/%s\d/g, "%s") || - // string is empty - strings[key] === "" || - // string has old flavor of %s - strings[key].match(/%s([^\d]|$)/g) !== null - ) { - delete strings[key]; - } - } else { - prune(defaultStrings[key] as TranslationTree, strings[key]); - } - } else { - delete strings[key]; - } - } else { - delete strings[key]; - } - } - - return strings; -} - -for (const locale of nonDefaultLocales) { - console.log(`Pruning ${locale}...`); - - const strings = require(`${ROOT}/${locale}.json`).default; - const pruned = prune(defaultStrings, strings); - - writeFile(`${WORKING_ROOT}/${locale}.json`, JSON.stringify(pruned, null, 2)); -} From ea21636c4eb91c5f53813e4f0dcefa65be33f096 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sun, 9 Aug 2026 20:26:39 -0500 Subject: [PATCH 121/147] abstract away some code --- .../src/core/blitzkit/gitObjectStorage.ts | 58 ++++++++++++ .../{discoverIds => core/blitzkit}/idArray.ts | 0 packages/scripts/src/discoverIds/constants.ts | 9 ++ packages/scripts/src/discoverIds/index.ts | 89 +++++-------------- packages/scripts/src/discoverIds/types.ts | 18 ++++ 5 files changed, 109 insertions(+), 65 deletions(-) create mode 100644 packages/scripts/src/core/blitzkit/gitObjectStorage.ts rename packages/scripts/src/{discoverIds => core/blitzkit}/idArray.ts (100%) create mode 100644 packages/scripts/src/discoverIds/constants.ts create mode 100644 packages/scripts/src/discoverIds/types.ts diff --git a/packages/scripts/src/core/blitzkit/gitObjectStorage.ts b/packages/scripts/src/core/blitzkit/gitObjectStorage.ts new file mode 100644 index 000000000..f712c7949 --- /dev/null +++ b/packages/scripts/src/core/blitzkit/gitObjectStorage.ts @@ -0,0 +1,58 @@ +import { $ } from "bun"; +import { mkdir, rm } from "node:fs/promises"; + +export class GitObjectStorage { + constructor( + private repo: string, + private base: string, + private token?: string, + ) {} + + async init() { + await rm(this.base, { recursive: true, force: true }); + + console.log(`Cloning ${this.repo} to ${this.base}`); + + const url = this.token + ? `https://x-access-token:${this.token}@github.com/${this.repo}.git` + : `https://github.com/${this.repo}.git`; + + await $`git clone --depth 1 ${url} ${this.base}`; + + return this; + } + + async commit() { + const shell = $.cwd(this.base); + + await shell`git add .`.quiet(); + await shell`git commit --amend --reset-author -m "ids update ${new Date().toISOString()}"`.quiet(); + await shell`git push --force-with-lease`; + } + + async mkdir(path: string) { + await mkdir(`${this.base}/${path}`, { recursive: true }); + } + + async rm(path: string) { + await rm(`${this.base}/${path}`, { force: true }); + } + + async write(path: string, bytes: Uint8Array | string) { + await Bun.write(`${this.base}/${path}`, bytes); + } + + file(path: string) { + return Bun.file(path); + } + + async bytes(path: string) { + const file = this.file(path); + return await file.bytes(); + } + + async json(path: string) { + const file = this.file(path); + return (await file.json()) as Type; + } +} diff --git a/packages/scripts/src/discoverIds/idArray.ts b/packages/scripts/src/core/blitzkit/idArray.ts similarity index 100% rename from packages/scripts/src/discoverIds/idArray.ts rename to packages/scripts/src/core/blitzkit/idArray.ts diff --git a/packages/scripts/src/discoverIds/constants.ts b/packages/scripts/src/discoverIds/constants.ts new file mode 100644 index 000000000..512686632 --- /dev/null +++ b/packages/scripts/src/discoverIds/constants.ts @@ -0,0 +1,9 @@ +export const WORKING_DIR = `../../temp/ids`; +export const REPO = "blitzkit/ids"; + +export const ACCOMMODATED_ID_COUNT = 10_000_000_000; // 10B ids +export const MAX_BYTES_PER_CHUNK = 100 * 1_000_000; // 100MB + +export const MAX_IDS_PER_CALL = 100; +export const API_RATE = 10; // 10Hz +export const MAX_DRY_STREAK = 10_000; diff --git a/packages/scripts/src/discoverIds/index.ts b/packages/scripts/src/discoverIds/index.ts index 2fd80f889..d03db05c6 100644 --- a/packages/scripts/src/discoverIds/index.ts +++ b/packages/scripts/src/discoverIds/index.ts @@ -1,73 +1,40 @@ -import { $ } from "bun"; import { range } from "lodash-es"; import { compress, decompress } from "lz4js"; -import { mkdir, rm } from "node:fs/promises"; import { exit } from "node:process"; -import { IdArray } from "./idArray"; +import { GitObjectStorage } from "../core/blitzkit/gitObjectStorage"; +import { IdArray } from "../core/blitzkit/idArray"; +import { + ACCOMMODATED_ID_COUNT, + API_RATE, + MAX_BYTES_PER_CHUNK, + MAX_DRY_STREAK, + MAX_IDS_PER_CALL, + REPO, + WORKING_DIR, +} from "./constants"; +import { ManifestV1, QuickInfo, RegionDescriptor } from "./types"; -interface ManifestV1 { - version: 1; - last_verified: number; - chunks: number; - total_count: number; -} - -interface RegionDescriptor { - domain: string; - seed: number; - max: number; - dry_streak: number; -} - -export interface QuickInfo { - status: string; - data: Record; -} - -const TEMP = "../../temp"; -const WORKING_DIR = `${TEMP}/ids`; -const CHUNKS_DIR = `${WORKING_DIR}/chunks`; -const REPO = "blitzkit/ids"; - -const ACCOMMODATED_ID_COUNT = 10_000_000_000; // 10B ids -const MAX_BYTES_PER_CHUNK = 100 * 1_000_000; // 100MB - -const MAX_IDS_PER_CALL = 100; -const API_RATE = 10; // 10Hz -const MAX_DRY_STREAK = 10_000; +const storage = await new GitObjectStorage( + REPO, + WORKING_DIR, + import.meta.env.GH_TOKEN, +).init(); const t0 = performance.now(); const maxT = 5.5 * 60 * 60 * 1000; // 5hr 30min const t1 = t0 + maxT; -await mkdir(TEMP, { recursive: true }); -await rm(WORKING_DIR, { recursive: true, force: true }); - -console.log(`Cloning ${REPO} to ${WORKING_DIR}`); - -const token = process.env.GH_TOKEN; -const repoUrl = token - ? `https://x-access-token:${token}@github.com/${REPO}.git` - : `https://github.com/${REPO}.git`; - -await $`git clone --depth 1 ${repoUrl} ${WORKING_DIR}`; -await mkdir(CHUNKS_DIR, { recursive: true }); +await storage.mkdir("chunks"); console.log(`Discovering pre-existing chunks...`); const discoveredChunks: (IdArray | null)[] = []; -const manifest: ManifestV1 = await Bun.file( - `${WORKING_DIR}/manifest.json`, -).json(); +const manifest = await storage.json("manifest.json"); for (let i = 0; i < manifest.chunks; i++) { - const path = `${CHUNKS_DIR}/chunk-${i}.dat.lz4`; - const file = Bun.file(path); - - const bytes = await file.bytes(); + const bytes = await storage.bytes(`chunks/chunk-${i}.dat.lz4`); const uncompressed = decompress(bytes); - const ids = IdArray.fromBytes(uncompressed); discoveredChunks.push(ids); @@ -146,33 +113,25 @@ async function save() { console.log("Saving chunks..."); - await rm(`${CHUNKS_DIR}/*`, { force: true }); + await storage.rm("chunks/*"); let totalFound = 0; for (let i = 0; i < chunks.length; i++) { - const path = `${CHUNKS_DIR}/chunk-${i}.dat.lz4`; const chunk = chunks[i]; const bytes = chunk.toBytes(); const compressed = compress(bytes); totalFound += chunk.size(); - await Bun.write(path, compressed); + await storage.write(`chunks/chunk-${i}.dat.lz4`, compressed); } manifest.total_count = totalFound; - await Bun.write( - `${WORKING_DIR}/manifest.json`, - JSON.stringify(manifest, null, 2), - ); + await storage.write("manifest.json", JSON.stringify(manifest, null, 2)); - const git = $.cwd(WORKING_DIR); - - await git`git add .`.quiet(); - await git`git commit --amend --reset-author -m "ids update ${new Date().toISOString()}"`.quiet(); - await git`git push --force-with-lease`.quiet(); + await storage.commit(); console.log( `Committed ${totalFound.toLocaleString()} total ids across ${manifest.chunks} chunks`, diff --git a/packages/scripts/src/discoverIds/types.ts b/packages/scripts/src/discoverIds/types.ts new file mode 100644 index 000000000..31ad70b97 --- /dev/null +++ b/packages/scripts/src/discoverIds/types.ts @@ -0,0 +1,18 @@ +export interface ManifestV1 { + version: 1; + last_verified: number; + chunks: number; + total_count: number; +} + +export interface RegionDescriptor { + domain: string; + seed: number; + max: number; + dry_streak: number; +} + +export interface QuickInfo { + status: string; + data: Record; +} From cf76ae713634742b30ed9509f94fe544c605bbb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Sun, 9 Aug 2026 22:13:07 -0500 Subject: [PATCH 122/147] add dispatch --- .github/workflows/ids.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ids.yaml b/.github/workflows/ids.yaml index ae2b3f06e..91abc55cc 100644 --- a/.github/workflows/ids.yaml +++ b/.github/workflows/ids.yaml @@ -7,6 +7,8 @@ on: schedule: - cron: 0 */6 * * * + workflow_dispatch: + concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true From 01a5f1bd4733b382152deccc10b61df0bf9e26ff Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sun, 9 Aug 2026 22:18:36 -0500 Subject: [PATCH 123/147] 5hr 55min --- packages/scripts/src/discoverIds/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/scripts/src/discoverIds/index.ts b/packages/scripts/src/discoverIds/index.ts index d03db05c6..b47075e11 100644 --- a/packages/scripts/src/discoverIds/index.ts +++ b/packages/scripts/src/discoverIds/index.ts @@ -21,7 +21,7 @@ const storage = await new GitObjectStorage( ).init(); const t0 = performance.now(); -const maxT = 5.5 * 60 * 60 * 1000; // 5hr 30min +const maxT = (5 * 60 + 55) * 60 * 1000; // 5hr 55min const t1 = t0 + maxT; await storage.mkdir("chunks"); From 84ecaec5d3124528c5533c11db3206e86153f989 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Sun, 9 Aug 2026 22:20:53 -0500 Subject: [PATCH 124/147] base --- packages/scripts/src/core/blitzkit/gitObjectStorage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/scripts/src/core/blitzkit/gitObjectStorage.ts b/packages/scripts/src/core/blitzkit/gitObjectStorage.ts index f712c7949..6ae14d105 100644 --- a/packages/scripts/src/core/blitzkit/gitObjectStorage.ts +++ b/packages/scripts/src/core/blitzkit/gitObjectStorage.ts @@ -43,7 +43,7 @@ export class GitObjectStorage { } file(path: string) { - return Bun.file(path); + return Bun.file(`${this.base}/${path}`); } async bytes(path: string) { From 9b7aa13d61ebf9ee5a9f377ef97a706d1d5a4317 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Mon, 10 Aug 2026 10:39:31 -0500 Subject: [PATCH 125/147] fix occlusion baking --- .../[...chunk_api]/tanks/[id]/model.glb.ts | 66 ++++++++----------- 1 file changed, 27 insertions(+), 39 deletions(-) diff --git a/packages/website/src/pages/[...chunk_api]/tanks/[id]/model.glb.ts b/packages/website/src/pages/[...chunk_api]/tanks/[id]/model.glb.ts index 684f4b375..0aa75db99 100644 --- a/packages/website/src/pages/[...chunk_api]/tanks/[id]/model.glb.ts +++ b/packages/website/src/pages/[...chunk_api]/tanks/[id]/model.glb.ts @@ -107,9 +107,6 @@ async function extractModel(vfs: AbstractVFS, path: string) { if (textures) { const baseColor = await readBaseColor( `Data/3d/${dirname(path)}/${textures.baseColorMap ?? textures.albedo}`, - textures.miscMap - ? `Data/3d/${dirname(path)}/${textures.miscMap}` - : undefined, ); material.setBaseColorTexture( document @@ -180,6 +177,19 @@ async function extractModel(vfs: AbstractVFS, path: string) { } } + if (textures.miscMap) { + material.setOcclusionTexture( + document + .createTexture(node.materialName) + .setMimeType("image/webp") + .setImage( + await readOcclusion( + `Data/3d/${dirname(path)}/${textures.miscMap}`, + ), + ), + ); + } + if (textures.baseRMMap) { material.setMetallicRoughnessTexture( document @@ -368,46 +378,24 @@ async function extractModel(vfs: AbstractVFS, path: string) { return document; } -async function readBaseColor(path: string, occlusionPath?: string) { - const baseRaw = await readTexture(path); - const occlusionRaw = occlusionPath - ? await readTexture(occlusionPath) - : undefined; - const base = await sharp(baseRaw.data, { raw: baseRaw }).raw().toBuffer(); - const occlusion = occlusionRaw - ? await sharp(occlusionRaw.data, { raw: occlusionRaw }) - .extractChannel(3) - .raw() - .toBuffer() - : undefined; +async function readBaseColor(path: string) { + const raw = await readTexture(path); + const image = await sharp(raw.data, { raw: raw }).webp().toBuffer(); - const combined = Buffer.alloc(baseRaw.width * baseRaw.height * 4); + return image; +} - for (let i = 0; i < baseRaw.width * baseRaw.height; i++) { - let c = 1; +async function readOcclusion(path: string) { + const raw = await readTexture(path); - if (occlusionRaw) { - const x = i % baseRaw.width; - const y = Math.floor(i / baseRaw.width); - const u = Math.floor(x * (occlusionRaw.width / baseRaw.width)); - const v = Math.floor(y * (occlusionRaw.height / baseRaw.height)); - - const occlusionI = u + v * occlusionRaw.width; - - c = occlusion![occlusionI] / 255; - } - - const alpha = base[i * 4 + 3]; - - combined[i * 4 + 0] = Math.round(base[i * 4 + 0] * c); - combined[i * 4 + 1] = Math.round(base[i * 4 + 1] * c); - combined[i * 4 + 2] = Math.round(base[i * 4 + 2] * c); - combined[i * 4 + 3] = alpha; - } - - const image = await sharp(combined, { - raw: { width: baseRaw.width, height: baseRaw.height, channels: 4 }, + const image = await sharp(raw.data, { + raw: { + width: raw.width, + height: raw.height, + channels: raw.channels, + }, }) + .extractChannel(3) .webp() .toBuffer(); From 7739dbd0a3b2851f5066ea781e227004617ab938 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Mon, 10 Aug 2026 16:42:35 -0500 Subject: [PATCH 126/147] faster lighting --- .../TankSandbox/components/Lighting.tsx | 23 +++++++++++-------- .../components/SceneProps/index.tsx | 5 ++-- .../components/TankSandbox/index.tsx | 2 -- .../[...chunk_api]/tanks/[id]/model.glb.ts | 12 +++------- 4 files changed, 19 insertions(+), 23 deletions(-) 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 75ee0f3fc..bf2f1f93f 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,3 +1,4 @@ +import { ContactShadows } from "@react-three/drei"; import { useFrame } from "@react-three/fiber"; import { clamp, times } from "lodash-es"; import { Quicklime } from "quicklime"; @@ -12,25 +13,24 @@ import { HemisphereLight, SpotLight, type Group } from "three"; import { degToRad, lerp } from "three/src/math/MathUtils.js"; import { Tankopedia } from "../../../../../../stores/tankopedia"; import { TankopediaPersistent } from "../../../../../../stores/tankopediaPersistent"; -import { HelpingSpotLight } from "../../../../../HelpingSpotLight"; const ANGLE = degToRad(12); const REVEAL_ANIMATION_TIME = 3; const TRANSITION_ANIMATION_TIME = 0.5; -const LIGHTS_COUNT = 4; +const LIGHTS_COUNT = 3; const THETA_OFFSET = degToRad(180 - 45); const LIGHT_DISTANCE = 20; const LIGHT_HEIGHT_0 = 4; const LIGHT_HEIGHT_1 = 6; -const INTENSITY_0 = 6 * 9; -const INTENSITY_1 = 3 * 9; -const HEMISPHERE_INTENSITY = 2; +const INTENSITY_0 = 2 ** 6.5; +const INTENSITY_1 = 2 ** 3.5; +const HEMISPHERE_INTENSITY = 2 ** 1.2; export const transitionEvent = new Quicklime(0); export function Lighting() { - const wrapper = useRef(null!); + const wrapper = useRef(null); const highGraphics = TankopediaPersistent.use((state) => state.highGraphics); const requestedDisplay = Tankopedia.use((state) => state.requestedDisplay); @@ -61,6 +61,8 @@ export function Lighting() { return ( <> + + {animate && ( - + {times(LIGHTS_COUNT, (index) => { const x = index / (LIGHTS_COUNT - 1); @@ -86,8 +88,7 @@ export function Lighting() { const intensity = lerp(INTENSITY_0, INTENSITY_1, x); return ( - ; animationTime: RefObject; - wrapper: RefObject; + wrapper: RefObject; } function Animator({ stop, t0, animationTime, wrapper }: AnimatorProps) { const requestedDisplay = Tankopedia.use((state) => state.requestedDisplay); const apply = useCallback((t: number) => { + if (wrapper.current === null) return; + for (const child of wrapper.current.children) { if (child instanceof SpotLight) { child.angle = ANGLE * t; 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 76cb0675e..c8cbf3d16 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 @@ -3,7 +3,7 @@ import { Quicklime, type QuicklimeEvent } from "quicklime"; import { useEffect, useRef } from "react"; import { Mesh, MeshStandardMaterial } from "three"; -const SIZE = 2 ** 4; +const SIZE = 2 ** 5; export const screenshotReadyEvent = new Quicklime(false); @@ -26,6 +26,7 @@ export function SceneProps() { return ( ); 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 b8b7dcdfa..732bf7927 100644 --- a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/index.tsx +++ b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/index.tsx @@ -5,7 +5,6 @@ import { useImperativeHandle, useRef, } from "react"; -import { Fog } from "three"; import { applyPitchYawLimits } from "../../../../../core/blitz/applyPitchYawLimits"; import { modelTransformEvent } from "../../../../../core/blitzkit/modelTransform"; import { Pose, poseEvent } from "../../../../../core/blitzkit/pose"; @@ -150,7 +149,6 @@ export const TankSandbox = forwardRef( Date: Mon, 10 Aug 2026 16:58:40 -0500 Subject: [PATCH 127/147] flip lighitng and add metalness --- .../HeroSection/components/TankSandbox/components/Lighting.tsx | 2 +- .../components/TankSandbox/components/SceneProps/index.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 bf2f1f93f..2a78f3097 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 @@ -81,7 +81,7 @@ export function Lighting() { const x = index / (LIGHTS_COUNT - 1); const theta = 2 * Math.PI * (index / LIGHTS_COUNT) + THETA_OFFSET; const position = [ - LIGHT_DISTANCE * Math.sin(theta), + -LIGHT_DISTANCE * Math.sin(theta), lerp(LIGHT_HEIGHT_0, LIGHT_HEIGHT_1, x), LIGHT_DISTANCE * Math.cos(theta), ] as const; 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 c8cbf3d16..f306c6eb1 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 @@ -37,7 +37,7 @@ export function SceneProps() { ref={material} color="black" roughness={1} - metalness={1} + metalness={0} /> ); From c130da7f0e5dda78d6a6e77ce0ba24a1ca3b17db Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Mon, 10 Aug 2026 17:50:48 -0500 Subject: [PATCH 128/147] heheheha --- .../components/TankSandbox/components/Lighting.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) 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 2a78f3097..dc8d20d7c 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 @@ -14,18 +14,18 @@ import { degToRad, lerp } from "three/src/math/MathUtils.js"; import { Tankopedia } from "../../../../../../stores/tankopedia"; import { TankopediaPersistent } from "../../../../../../stores/tankopediaPersistent"; -const ANGLE = degToRad(12); +const ANGLE = degToRad(15); const REVEAL_ANIMATION_TIME = 3; const TRANSITION_ANIMATION_TIME = 0.5; const LIGHTS_COUNT = 3; const THETA_OFFSET = degToRad(180 - 45); const LIGHT_DISTANCE = 20; -const LIGHT_HEIGHT_0 = 4; -const LIGHT_HEIGHT_1 = 6; -const INTENSITY_0 = 2 ** 6.5; -const INTENSITY_1 = 2 ** 3.5; -const HEMISPHERE_INTENSITY = 2 ** 1.2; +const LIGHT_HEIGHT_0 = 7; +const LIGHT_HEIGHT_1 = 10; +const INTENSITY_0 = 2 ** 6; +const INTENSITY_1 = 2 ** 3; +const HEMISPHERE_INTENSITY = 2 ** 0.7; export const transitionEvent = new Quicklime(0); From dcda627518a7f5d12ffed1cd8c5d63e2d127300c Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Tue, 11 Aug 2026 09:45:17 -0500 Subject: [PATCH 129/147] dvpl free dir --- bun.lock | 2 +- packages/core/package.json | 2 +- packages/core/src/blitzkit/vfs/abstract.ts | 12 ++++++++++-- packages/core/src/blitzkit/vfs/local.ts | 2 +- packages/core/src/blitzkit/vfs/mixed.ts | 2 +- packages/core/src/blitzkit/vfs/steam.ts | 2 +- packages/core/src/blitzkit/vfs/zip.ts | 2 +- 7 files changed, 16 insertions(+), 8 deletions(-) diff --git a/bun.lock b/bun.lock index 73b200941..22cfe2bfd 100644 --- a/bun.lock +++ b/bun.lock @@ -30,7 +30,7 @@ "@octokit/plugin-throttling": "^11.0.1", "@octokit/rest": "^22.0.0", "@types/adm-zip": "^0.5.7", - "@types/bun": "^1.3.1", + "@types/bun": "^1.3.14", "@types/lodash-es": "^4.17.12", "@types/lz4js": "^0.2.1", "@types/three": "^0.180.0", diff --git a/packages/core/package.json b/packages/core/package.json index 540a568ee..d7e3bf264 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -9,7 +9,7 @@ "@octokit/plugin-throttling": "^11.0.1", "@octokit/rest": "^22.0.0", "@types/adm-zip": "^0.5.7", - "@types/bun": "^1.3.1", + "@types/bun": "^1.3.14", "@types/lodash-es": "^4.17.12", "@types/lz4js": "^0.2.1", "@types/three": "^0.180.0", diff --git a/packages/core/src/blitzkit/vfs/abstract.ts b/packages/core/src/blitzkit/vfs/abstract.ts index e4f9a0cb3..ea69982d3 100644 --- a/packages/core/src/blitzkit/vfs/abstract.ts +++ b/packages/core/src/blitzkit/vfs/abstract.ts @@ -46,7 +46,15 @@ export abstract class AbstractVFS { return buffer; } - abstract dir(path: string): Promise; + protected abstract _dir(path: string): Promise; + + async dir(path: string) { + const files = await this._dir(path); + + return files.map((file) => + file.endsWith(".dvpl") ? file.slice(0, -5) : file, + ); + } async text(path: string) { const file = await this.file(path); @@ -63,7 +71,7 @@ export abstract class AbstractVFS { return this.xmlParser.parse(file) as Type; } - async _csv(text: string, options: Options = {}) { + protected async _csv(text: string, options: Options = {}) { const parsed = parseCSV(text, options); return parsed; } diff --git a/packages/core/src/blitzkit/vfs/local.ts b/packages/core/src/blitzkit/vfs/local.ts index 0d2fa18ae..c82510048 100644 --- a/packages/core/src/blitzkit/vfs/local.ts +++ b/packages/core/src/blitzkit/vfs/local.ts @@ -23,7 +23,7 @@ export class LocalVFS extends AbstractVFS { return array; } - async dir(path: string) { + async _dir(path: string) { try { return await readdir(`${this.base}/${path}`); } catch (_) { diff --git a/packages/core/src/blitzkit/vfs/mixed.ts b/packages/core/src/blitzkit/vfs/mixed.ts index 1d3358ddb..d510e4115 100644 --- a/packages/core/src/blitzkit/vfs/mixed.ts +++ b/packages/core/src/blitzkit/vfs/mixed.ts @@ -35,7 +35,7 @@ export class MixedVFS extends AbstractVFS { throw new Error(`File not found: ${path}`); } - async dir(path: string) { + async _dir(path: string) { const files = new Set(); for (const vfs of this.vfses) { diff --git a/packages/core/src/blitzkit/vfs/steam.ts b/packages/core/src/blitzkit/vfs/steam.ts index 79acd450d..9ab2c5047 100644 --- a/packages/core/src/blitzkit/vfs/steam.ts +++ b/packages/core/src/blitzkit/vfs/steam.ts @@ -89,7 +89,7 @@ export class SteamVFS extends AbstractVFS { return new Uint8Array(downloaded.file); } - async dir(path: string) { + async _dir(path: string) { const parentSegments = path.split("/").length; const children = new Set(); diff --git a/packages/core/src/blitzkit/vfs/zip.ts b/packages/core/src/blitzkit/vfs/zip.ts index e181df62a..53d3e2856 100644 --- a/packages/core/src/blitzkit/vfs/zip.ts +++ b/packages/core/src/blitzkit/vfs/zip.ts @@ -33,7 +33,7 @@ export class ZipVFS extends AbstractVFS { return new Uint8Array(arrayBuffer); } - async dir(path: string) { + async _dir(path: string) { const parentSegments = path.split("/").length; const children = new Set(); From 1a17d607fbeb6964a7b836c4586def1e88dec51a Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Tue, 11 Aug 2026 09:56:58 -0500 Subject: [PATCH 130/147] move light target up --- .../components/TankSandbox/components/Lighting.tsx | 6 ++++++ 1 file changed, 6 insertions(+) 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 dc8d20d7c..6eecfc59c 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 @@ -87,8 +87,13 @@ export function Lighting() { ] as const; const intensity = lerp(INTENSITY_0, INTENSITY_1, x); + const light = useRef(null!); + + // useHelper(light, SpotLightHelper); + return ( ); })} From 0d1088fefbb47c5d7763c54ffb8c865753fd460a Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Tue, 11 Aug 2026 10:01:34 -0500 Subject: [PATCH 131/147] non pbr lihging --- .../TankSandbox/components/Lighting.tsx | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) 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 6eecfc59c..5b1ff800e 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 @@ -11,8 +11,11 @@ import { } from "react"; import { HemisphereLight, SpotLight, type Group } from "three"; import { degToRad, lerp } from "three/src/math/MathUtils.js"; +import { useModel } from "../../../../../../hooks/useModel"; +import { Duel } from "../../../../../../stores/duel"; import { Tankopedia } from "../../../../../../stores/tankopedia"; import { TankopediaPersistent } from "../../../../../../stores/tankopediaPersistent"; +import { TankopediaDisplay } from "../../../../../../stores/tankopediaPersistent/constants"; const ANGLE = degToRad(15); const REVEAL_ANIMATION_TIME = 3; @@ -26,6 +29,7 @@ const LIGHT_HEIGHT_1 = 10; const INTENSITY_0 = 2 ** 6; const INTENSITY_1 = 2 ** 3; const HEMISPHERE_INTENSITY = 2 ** 0.7; +const NON_PBR_FACTOR = 2; export const transitionEvent = new Quicklime(0); @@ -122,14 +126,21 @@ interface AnimatorProps { function Animator({ stop, t0, animationTime, wrapper }: AnimatorProps) { const requestedDisplay = Tankopedia.use((state) => state.requestedDisplay); + const id = Duel.use((state) => state.protagonist.tank.id); + const display = Tankopedia.use((state) => state.display); + + const { hasPbr } = useModel(id); + const factor = + !hasPbr && display !== TankopediaDisplay.StaticArmor ? NON_PBR_FACTOR : 1; + const apply = useCallback((t: number) => { if (wrapper.current === null) return; for (const child of wrapper.current.children) { if (child instanceof SpotLight) { - child.angle = ANGLE * t; + child.angle = ANGLE * t * factor; } else if (child instanceof HemisphereLight) { - child.intensity = HEMISPHERE_INTENSITY * t; + child.intensity = HEMISPHERE_INTENSITY * t * factor; } } From 32e4bf3fd252648bc8acbed2340f60a860880f83 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Tue, 11 Aug 2026 10:16:14 -0500 Subject: [PATCH 132/147] fading out fragment --- .../components/SceneProps/index.tsx | 18 ++++++++++++------ .../SceneProps/shaders/fragment.glsl | 16 ++++++++++++++++ .../components/SceneProps/shaders/vertex.glsl | 8 ++++++++ 3 files changed, 36 insertions(+), 6 deletions(-) create mode 100644 packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/SceneProps/shaders/fragment.glsl create mode 100644 packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/SceneProps/shaders/vertex.glsl 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 f306c6eb1..87736566b 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 @@ -1,7 +1,9 @@ import { invalidate } from "@react-three/fiber"; import { Quicklime, type QuicklimeEvent } from "quicklime"; import { useEffect, useRef } from "react"; -import { Mesh, MeshStandardMaterial } from "three"; +import { Mesh, MeshStandardMaterial, Vector3 } from "three"; +import fragmentShader from "./shaders/fragment.glsl?raw"; +import vertexShader from "./shaders/vertex.glsl?raw"; const SIZE = 2 ** 5; @@ -33,11 +35,15 @@ export function SceneProps() { ref={mesh} > - ); diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/SceneProps/shaders/fragment.glsl b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/SceneProps/shaders/fragment.glsl new file mode 100644 index 000000000..7f83b2e0d --- /dev/null +++ b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/SceneProps/shaders/fragment.glsl @@ -0,0 +1,16 @@ +varying vec3 vWorldPosition; +uniform float size; +uniform vec3 color; + +void main() { + vec3 scaled = vWorldPosition / size; + float radius = length(scaled.xz); + + if(radius >= 1.0) { + discard; + } + + float a = 1.0 - radius; + + gl_FragColor = vec4(color, a); +} \ No newline at end of file diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/SceneProps/shaders/vertex.glsl b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/SceneProps/shaders/vertex.glsl new file mode 100644 index 000000000..eb9f48934 --- /dev/null +++ b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/SceneProps/shaders/vertex.glsl @@ -0,0 +1,8 @@ +varying vec3 vWorldPosition; + +void main() { + vec4 worldPos = modelMatrix * vec4(position, 1.0); + vWorldPosition = worldPos.xyz; + + gl_Position = projectionMatrix * viewMatrix * worldPos; +} \ No newline at end of file From 404e6625c0de0befd3099e2811a0cd6f1c3c9b5b Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Tue, 11 Aug 2026 10:35:41 -0500 Subject: [PATCH 133/147] remmove lghts --- .../components/TankSandbox/components/SceneProps/index.tsx | 1 - 1 file changed, 1 deletion(-) 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 87736566b..05cb208a2 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 @@ -39,7 +39,6 @@ export function SceneProps() { fragmentShader={fragmentShader} vertexShader={vertexShader} transparent - lights uniforms={{ size: { value: SIZE / 2 }, color: { value: new Vector3(1, 1, 1).multiplyScalar(2 ** -3) }, From 72de42a9abb541708e28256f1fc40615194e80f0 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Tue, 11 Aug 2026 10:38:36 -0500 Subject: [PATCH 134/147] performant ground --- .../TankSandbox/components/SceneProps/index.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 05cb208a2..d51a2fb79 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 @@ -1,11 +1,12 @@ +import { mauveDark } from "@radix-ui/colors"; import { invalidate } from "@react-three/fiber"; import { Quicklime, type QuicklimeEvent } from "quicklime"; import { useEffect, useRef } from "react"; -import { Mesh, MeshStandardMaterial, Vector3 } from "three"; +import { Color, Mesh, MeshStandardMaterial } from "three"; import fragmentShader from "./shaders/fragment.glsl?raw"; import vertexShader from "./shaders/vertex.glsl?raw"; -const SIZE = 2 ** 5; +const SIZE = 2 ** 4; export const screenshotReadyEvent = new Quicklime(false); @@ -30,7 +31,6 @@ export function SceneProps() { @@ -41,7 +41,7 @@ export function SceneProps() { transparent uniforms={{ size: { value: SIZE / 2 }, - color: { value: new Vector3(1, 1, 1).multiplyScalar(2 ** -3) }, + color: { value: new Color(mauveDark.mauve8) }, }} /> From 486171f4db3f3fb06c297f11f99f5dc79412d85d Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Tue, 11 Aug 2026 10:52:33 -0500 Subject: [PATCH 135/147] shadow focus and res --- .../components/TankSandbox/components/Lighting.tsx | 7 +++++++ .../components/TankSandbox/components/SceneProps/index.tsx | 3 +-- 2 files changed, 8 insertions(+), 2 deletions(-) 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 5b1ff800e..ad641e620 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 @@ -23,14 +23,19 @@ const TRANSITION_ANIMATION_TIME = 0.5; const LIGHTS_COUNT = 3; const THETA_OFFSET = degToRad(180 - 45); + const LIGHT_DISTANCE = 20; const LIGHT_HEIGHT_0 = 7; const LIGHT_HEIGHT_1 = 10; + const INTENSITY_0 = 2 ** 6; const INTENSITY_1 = 2 ** 3; const HEMISPHERE_INTENSITY = 2 ** 0.7; const NON_PBR_FACTOR = 2; +const SHADOW_RESOLUTION = 2 ** 9; +const SHADOW_FOCUS = 2 ** 0; + export const transitionEvent = new Quicklime(0); export function Lighting() { @@ -107,6 +112,8 @@ export function Lighting() { color="#ffffff" angle={0} target-position={[0, 1.5, 0]} + shadow-mapSize={[SHADOW_RESOLUTION, SHADOW_RESOLUTION]} + shadow-focus={SHADOW_FOCUS} /> ); })} 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 d51a2fb79..5150a8a87 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 @@ -2,7 +2,7 @@ import { mauveDark } from "@radix-ui/colors"; import { invalidate } from "@react-three/fiber"; import { Quicklime, type QuicklimeEvent } from "quicklime"; import { useEffect, useRef } from "react"; -import { Color, Mesh, MeshStandardMaterial } from "three"; +import { Color, Mesh } from "three"; import fragmentShader from "./shaders/fragment.glsl?raw"; import vertexShader from "./shaders/vertex.glsl?raw"; @@ -12,7 +12,6 @@ export const screenshotReadyEvent = new Quicklime(false); export function SceneProps() { const mesh = useRef(null!); - const material = useRef(null); useEffect(() => { function handleScreenshotReady(event: QuicklimeEvent) { From 051e9ec74aa0bee8ae1022190c529627347ff70c Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Tue, 11 Aug 2026 10:58:35 -0500 Subject: [PATCH 136/147] ground with shadows --- .../components/SceneProps/index.tsx | 60 +++++++++++++++---- .../SceneProps/shaders/fragment.glsl | 16 ----- .../components/SceneProps/shaders/vertex.glsl | 8 --- 3 files changed, 49 insertions(+), 35 deletions(-) delete mode 100644 packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/SceneProps/shaders/fragment.glsl delete mode 100644 packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/SceneProps/shaders/vertex.glsl 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 5150a8a87..ca26e8c94 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 @@ -1,10 +1,8 @@ import { mauveDark } from "@radix-ui/colors"; import { invalidate } from "@react-three/fiber"; import { Quicklime, type QuicklimeEvent } from "quicklime"; -import { useEffect, useRef } from "react"; -import { Color, Mesh } from "three"; -import fragmentShader from "./shaders/fragment.glsl?raw"; -import vertexShader from "./shaders/vertex.glsl?raw"; +import { useCallback, useEffect, useRef } from "react"; +import { Mesh, type WebGLProgramParametersWithUniforms } from "three"; const SIZE = 2 ** 4; @@ -26,22 +24,62 @@ export function SceneProps() { }; }, []); + const handleBeforeCompile = useCallback( + (shader: WebGLProgramParametersWithUniforms) => { + shader.uniforms.size = { value: SIZE / 2 }; + + shader.vertexShader = ` + varying vec3 vWorldPosition; + + ${shader.vertexShader} + `.replace( + "#include ", + + ` + #include + vWorldPosition = (modelMatrix * vec4(transformed, 1.0)).xyz; + `, + ); + + shader.fragmentShader = ` + varying vec3 vWorldPosition; + uniform float size; + + ${shader.fragmentShader} + `.replace( + "#include ", + + ` + #include + + vec3 scaled = vWorldPosition / size; + float radius = length(scaled.xz); + + if (radius >= 1.0) { + discard; + } + + diffuseColor.a *= (1.0 - radius); + `, + ); + }, + [], + ); + return ( - ); diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/SceneProps/shaders/fragment.glsl b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/SceneProps/shaders/fragment.glsl deleted file mode 100644 index 7f83b2e0d..000000000 --- a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/SceneProps/shaders/fragment.glsl +++ /dev/null @@ -1,16 +0,0 @@ -varying vec3 vWorldPosition; -uniform float size; -uniform vec3 color; - -void main() { - vec3 scaled = vWorldPosition / size; - float radius = length(scaled.xz); - - if(radius >= 1.0) { - discard; - } - - float a = 1.0 - radius; - - gl_FragColor = vec4(color, a); -} \ No newline at end of file diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/SceneProps/shaders/vertex.glsl b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/SceneProps/shaders/vertex.glsl deleted file mode 100644 index eb9f48934..000000000 --- a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/SceneProps/shaders/vertex.glsl +++ /dev/null @@ -1,8 +0,0 @@ -varying vec3 vWorldPosition; - -void main() { - vec4 worldPos = modelMatrix * vec4(position, 1.0); - vWorldPosition = worldPos.xyz; - - gl_Position = projectionMatrix * viewMatrix * worldPos; -} \ No newline at end of file From 3da1ed4d6c845c04508e6d519770754e9530e90f Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Tue, 11 Aug 2026 11:30:36 -0500 Subject: [PATCH 137/147] ground and sky color --- .../components/TankSandbox/components/Lighting.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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 ad641e620..2c4f74d71 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,3 +1,4 @@ +import { blueDark, tomatoDark } from "@radix-ui/colors"; import { ContactShadows } from "@react-three/drei"; import { useFrame } from "@react-three/fiber"; import { clamp, times } from "lodash-es"; @@ -84,7 +85,11 @@ export function Lighting() { )} - + {times(LIGHTS_COUNT, (index) => { const x = index / (LIGHTS_COUNT - 1); From d485a463dd858b0f6be955f354c60bb96b45b8f4 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Tue, 11 Aug 2026 14:07:57 -0500 Subject: [PATCH 138/147] orange ground --- .../components/TankSandbox/components/Lighting.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 2c4f74d71..781158cab 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,4 +1,4 @@ -import { blueDark, tomatoDark } from "@radix-ui/colors"; +import { blueDark, orangeDark } from "@radix-ui/colors"; import { ContactShadows } from "@react-three/drei"; import { useFrame } from "@react-three/fiber"; import { clamp, times } from "lodash-es"; @@ -88,7 +88,7 @@ export function Lighting() { {times(LIGHTS_COUNT, (index) => { From bae2f253bc324961bccd33a5b32492e758234204 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Tue, 11 Aug 2026 19:37:57 -0500 Subject: [PATCH 139/147] screenshot friendly contact shadows --- .../components/TankSandbox/components/Lighting.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) 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 781158cab..d40f1ed97 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 @@ -71,7 +71,14 @@ export function Lighting() { return ( <> - + {animate && ( Date: Tue, 11 Aug 2026 19:45:09 -0500 Subject: [PATCH 140/147] fix tank modes not switching --- .../components/TankSandbox/components/Lighting.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) 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 d40f1ed97..af8d0d161 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,6 +1,6 @@ import { blueDark, orangeDark } from "@radix-ui/colors"; import { ContactShadows } from "@react-three/drei"; -import { useFrame } from "@react-three/fiber"; +import { invalidate, useFrame } from "@react-three/fiber"; import { clamp, times } from "lodash-es"; import { Quicklime } from "quicklime"; import { @@ -78,6 +78,8 @@ export function Lighting() { height={1.5} blur={2 ** 0.5} opacity={2 ** -0.4} + resolution={2 ** 7} + depthWrite={false} /> {animate && ( @@ -152,8 +154,12 @@ function Animator({ stop, t0, animationTime, wrapper }: AnimatorProps) { const factor = !hasPbr && display !== TankopediaDisplay.StaticArmor ? NON_PBR_FACTOR : 1; + useEffect(() => { + invalidate(); + }, [requestedDisplay === display]); + const apply = useCallback((t: number) => { - if (wrapper.current === null) return; + if (!wrapper.current) return; for (const child of wrapper.current.children) { if (child instanceof SpotLight) { From 08c909fbe29254ec7e032e1473eff1a8b4232a56 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Tue, 11 Aug 2026 19:46:26 -0500 Subject: [PATCH 141/147] rename ids to data-ids --- packages/scripts/src/discoverIds/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/scripts/src/discoverIds/constants.ts b/packages/scripts/src/discoverIds/constants.ts index 512686632..022a3cce9 100644 --- a/packages/scripts/src/discoverIds/constants.ts +++ b/packages/scripts/src/discoverIds/constants.ts @@ -1,5 +1,5 @@ export const WORKING_DIR = `../../temp/ids`; -export const REPO = "blitzkit/ids"; +export const REPO = "blitzkit/data-ids"; export const ACCOMMODATED_ID_COUNT = 10_000_000_000; // 10B ids export const MAX_BYTES_PER_CHUNK = 100 * 1_000_000; // 100MB From b3e439669b313ee4ab91a9290c849759cae8b7c6 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Tue, 11 Aug 2026 20:03:57 -0500 Subject: [PATCH 142/147] start of performance --- package.json | 3 +- packages/core/src/streams/dids.ts | 51 ------------------- packages/core/src/streams/index.ts | 20 ++++---- packages/scripts/package.json | 3 +- packages/scripts/src/discoverIds/constants.ts | 3 -- packages/scripts/src/discoverIds/index.ts | 24 ++------- packages/scripts/src/discoverIds/time.ts | 14 +++++ packages/scripts/src/discoverIds/types.ts | 4 +- .../src/recordPerformance/constants.ts | 1 + .../scripts/src/recordPerformance/index.ts | 22 ++++++++ .../scripts/src/recordPerformance/types.ts | 6 +++ 11 files changed, 64 insertions(+), 87 deletions(-) delete mode 100644 packages/core/src/streams/dids.ts create mode 100644 packages/scripts/src/discoverIds/time.ts create mode 100644 packages/scripts/src/recordPerformance/constants.ts create mode 100644 packages/scripts/src/recordPerformance/index.ts create mode 100644 packages/scripts/src/recordPerformance/types.ts diff --git a/package.json b/package.json index 2ed4ceb5f..340587062 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "dev": "cd packages/website && bun run dev", "build": "cd packages/website && bun run build", "discover-ids": "cd packages/scripts && bun run discover-ids", - "build-protos": "cd packages/scripts && bun run build-protos" + "build-protos": "cd packages/scripts && bun run build-protos", + "record-performance": "cd packages/scripts && bun run record-performance" }, "type": "module", "workspaces": [ diff --git a/packages/core/src/streams/dids.ts b/packages/core/src/streams/dids.ts deleted file mode 100644 index e2c70e042..000000000 --- a/packages/core/src/streams/dids.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { times } from 'lodash-es'; -import { ReadStream, WriteStream } from './buffer'; - -export class DidsReadStream extends ReadStream { - dids() { - this.magic(); - this.header(); - return this.body(); - } - - magic() { - const magic = this.utf8(4); - if (magic !== 'DIDS') { - throw new Error(`Invalid DIDS magic number: "${magic}"`); - } - } - - header() { - return { - version: this.uint16(), - }; - } - - body() { - const count = this.uint32(); - return times(count, () => this.uint32()); - } -} - -export class DidsWriteStream extends WriteStream { - dids(ids: number[]) { - this.magic(); - this.header(); - this.body(ids); - - return this; - } - - magic() { - this.utf8('DIDS'); - } - - header() { - this.uint16(1); - } - - body(ids: number[]) { - this.uint32(ids.length); - ids.forEach((id) => this.uint32(id)); - } -} diff --git a/packages/core/src/streams/index.ts b/packages/core/src/streams/index.ts index bd8949a2d..3215fdab9 100644 --- a/packages/core/src/streams/index.ts +++ b/packages/core/src/streams/index.ts @@ -1,11 +1,9 @@ -export * from './buffer'; -export * from './bufferToBigInt'; -export * from './dds'; -export * from './dids'; -export * from './pvr'; -export * from './rts'; -export * from './sc2'; -export * from './scg'; -export * from './scpg'; -export * from './windows'; - +export * from "./buffer"; +export * from "./bufferToBigInt"; +export * from "./dds"; +export * from "./pvr"; +export * from "./rts"; +export * from "./sc2"; +export * from "./scg"; +export * from "./scpg"; +export * from "./windows"; diff --git a/packages/scripts/package.json b/packages/scripts/package.json index 393c95942..a401cc068 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -3,7 +3,8 @@ "private": true, "scripts": { "discover-ids": "bun src/discoverIds/index.ts", - "build-protos": "bun src/protos.ts" + "build-protos": "bun src/protos.ts", + "record-performance": "bun src/recordPerformance/index.ts" }, "dependencies": { "@blitzkit/closed": "workspace:*", diff --git a/packages/scripts/src/discoverIds/constants.ts b/packages/scripts/src/discoverIds/constants.ts index 022a3cce9..ee523a5e1 100644 --- a/packages/scripts/src/discoverIds/constants.ts +++ b/packages/scripts/src/discoverIds/constants.ts @@ -1,6 +1,3 @@ -export const WORKING_DIR = `../../temp/ids`; -export const REPO = "blitzkit/data-ids"; - export const ACCOMMODATED_ID_COUNT = 10_000_000_000; // 10B ids export const MAX_BYTES_PER_CHUNK = 100 * 1_000_000; // 100MB diff --git a/packages/scripts/src/discoverIds/index.ts b/packages/scripts/src/discoverIds/index.ts index b47075e11..d7d09290d 100644 --- a/packages/scripts/src/discoverIds/index.ts +++ b/packages/scripts/src/discoverIds/index.ts @@ -5,32 +5,26 @@ import { GitObjectStorage } from "../core/blitzkit/gitObjectStorage"; import { IdArray } from "../core/blitzkit/idArray"; import { ACCOMMODATED_ID_COUNT, - API_RATE, MAX_BYTES_PER_CHUNK, MAX_DRY_STREAK, MAX_IDS_PER_CALL, - REPO, - WORKING_DIR, } from "./constants"; -import { ManifestV1, QuickInfo, RegionDescriptor } from "./types"; +import { sleep, withinTimeLimit } from "./time"; +import { IdsManifest, QuickInfo, RegionDescriptor } from "./types"; const storage = await new GitObjectStorage( - REPO, - WORKING_DIR, + "blitzkit/data-ids", + "../../temp/ids", import.meta.env.GH_TOKEN, ).init(); -const t0 = performance.now(); -const maxT = (5 * 60 + 55) * 60 * 1000; // 5hr 55min -const t1 = t0 + maxT; - await storage.mkdir("chunks"); console.log(`Discovering pre-existing chunks...`); const discoveredChunks: (IdArray | null)[] = []; -const manifest = await storage.json("manifest.json"); +const manifest = await storage.json("manifest.json"); for (let i = 0; i < manifest.chunks; i++) { const bytes = await storage.bytes(`chunks/chunk-${i}.dat.lz4`); @@ -189,14 +183,6 @@ function fillQueue() { } } -function sleep() { - return new Promise((resolve) => setTimeout(resolve, 1000 / API_RATE)); -} - -function withinTimeLimit() { - return performance.now() < t1; -} - console.log("Starting discovery loop..."); while (withinTimeLimit() && regions.length > 0) { diff --git a/packages/scripts/src/discoverIds/time.ts b/packages/scripts/src/discoverIds/time.ts new file mode 100644 index 000000000..3f2afecad --- /dev/null +++ b/packages/scripts/src/discoverIds/time.ts @@ -0,0 +1,14 @@ +import { API_RATE } from "./constants"; + +const MAX_COMPUTE_TIME = (5 * 60 + 55) * 60 * 1000; // 5hr 55min + +const t0 = performance.now(); +const t1 = t0 + MAX_COMPUTE_TIME; + +export function withinTimeLimit() { + return performance.now() < t1; +} + +export function sleep() { + return new Promise((resolve) => setTimeout(resolve, 1000 / API_RATE)); +} diff --git a/packages/scripts/src/discoverIds/types.ts b/packages/scripts/src/discoverIds/types.ts index 31ad70b97..53eedba0b 100644 --- a/packages/scripts/src/discoverIds/types.ts +++ b/packages/scripts/src/discoverIds/types.ts @@ -1,4 +1,6 @@ -export interface ManifestV1 { +export type IdsManifest = IdsManifestV1; + +interface IdsManifestV1 { version: 1; last_verified: number; chunks: number; diff --git a/packages/scripts/src/recordPerformance/constants.ts b/packages/scripts/src/recordPerformance/constants.ts new file mode 100644 index 000000000..afbdd7c4c --- /dev/null +++ b/packages/scripts/src/recordPerformance/constants.ts @@ -0,0 +1 @@ +export const IDS_API_BASE = "https://ids.blitzkit.app"; diff --git a/packages/scripts/src/recordPerformance/index.ts b/packages/scripts/src/recordPerformance/index.ts new file mode 100644 index 000000000..fc3fba8cc --- /dev/null +++ b/packages/scripts/src/recordPerformance/index.ts @@ -0,0 +1,22 @@ +import { GitObjectStorage } from "../core/blitzkit/gitObjectStorage"; +import { sleep, withinTimeLimit } from "../discoverIds/time"; +import { IdsManifest } from "../discoverIds/types"; +import { IDS_API_BASE } from "./constants"; +import { PerformanceManifest } from "./types"; + +const storage = await new GitObjectStorage( + "blitzkit/data-performance", + "../../temp/performance", + import.meta.env.GH_TOKEN, +).init(); + +const manifest = await storage.json("manifest.json"); +const idsManifest = await fetch(`${IDS_API_BASE}/manifest.json`).then( + (response) => response.json() as Promise, +); + +while (withinTimeLimit()) { + await sleep(); +} + +// storage.commit(); diff --git a/packages/scripts/src/recordPerformance/types.ts b/packages/scripts/src/recordPerformance/types.ts new file mode 100644 index 000000000..e5c12d10a --- /dev/null +++ b/packages/scripts/src/recordPerformance/types.ts @@ -0,0 +1,6 @@ +export type PerformanceManifest = PerformanceManifestV1; + +interface PerformanceManifestV1 { + version: 1; + last_updated: number; +} From 8dc9a6b46d229767020710edddde183fd9ce11b5 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Tue, 11 Aug 2026 20:11:12 -0500 Subject: [PATCH 143/147] start of recording --- packages/scripts/src/core/blitzkit/idArray.ts | 7 +++++++ packages/scripts/src/discoverIds/index.ts | 5 ++--- .../scripts/src/recordPerformance/index.ts | 21 ++++++++++++++++++- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/packages/scripts/src/core/blitzkit/idArray.ts b/packages/scripts/src/core/blitzkit/idArray.ts index 05a043891..e37aebdb3 100644 --- a/packages/scripts/src/core/blitzkit/idArray.ts +++ b/packages/scripts/src/core/blitzkit/idArray.ts @@ -1,3 +1,5 @@ +import { decompress } from "lz4js"; + export class IdArray { constructor( private array: Uint32Array = new Uint32Array(), @@ -57,6 +59,11 @@ export class IdArray { return new IdArray(uintArray, size); } + static fromCompressed(bytes: Uint8Array) { + const decompressed = decompress(bytes); + return this.fromBytes(decompressed); + } + static fromArray(array: number[]) { const uintArray = new Uint32Array(this.nextPowerOf2(array.length)); diff --git a/packages/scripts/src/discoverIds/index.ts b/packages/scripts/src/discoverIds/index.ts index d7d09290d..c28e2e6a3 100644 --- a/packages/scripts/src/discoverIds/index.ts +++ b/packages/scripts/src/discoverIds/index.ts @@ -1,5 +1,5 @@ import { range } from "lodash-es"; -import { compress, decompress } from "lz4js"; +import { compress } from "lz4js"; import { exit } from "node:process"; import { GitObjectStorage } from "../core/blitzkit/gitObjectStorage"; import { IdArray } from "../core/blitzkit/idArray"; @@ -28,8 +28,7 @@ const manifest = await storage.json("manifest.json"); for (let i = 0; i < manifest.chunks; i++) { const bytes = await storage.bytes(`chunks/chunk-${i}.dat.lz4`); - const uncompressed = decompress(bytes); - const ids = IdArray.fromBytes(uncompressed); + const ids = IdArray.fromCompressed(bytes); discoveredChunks.push(ids); } diff --git a/packages/scripts/src/recordPerformance/index.ts b/packages/scripts/src/recordPerformance/index.ts index fc3fba8cc..4eb650eaa 100644 --- a/packages/scripts/src/recordPerformance/index.ts +++ b/packages/scripts/src/recordPerformance/index.ts @@ -1,4 +1,6 @@ import { GitObjectStorage } from "../core/blitzkit/gitObjectStorage"; +import { IdArray } from "../core/blitzkit/idArray"; +import { API_RATE } from "../discoverIds/constants"; import { sleep, withinTimeLimit } from "../discoverIds/time"; import { IdsManifest } from "../discoverIds/types"; import { IDS_API_BASE } from "./constants"; @@ -16,7 +18,24 @@ const idsManifest = await fetch(`${IDS_API_BASE}/manifest.json`).then( ); while (withinTimeLimit()) { - await sleep(); + manifest.last_updated = (manifest.last_updated + 1) % idsManifest.chunks; + + const url = `${IDS_API_BASE}/chunks/chunk-${manifest.last_updated}.dat.lz4`; + + const response = await fetch(url); + const buffer = await response.arrayBuffer(); + const bytes = new Uint8Array(buffer); + + const ids = IdArray.fromCompressed(bytes); + const size = ids.size(); + + console.log( + `Chunk ${manifest.last_updated} contains ${size} ids, estimated to take ${(size / API_RATE / 60).toFixed(0)}min`, + ); + + for (let i = 0; i < size; i++) { + await sleep(); + } } // storage.commit(); From 877ba9408db88da2ede7d4987198cfee7bd2b291 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Tue, 11 Aug 2026 20:29:50 -0500 Subject: [PATCH 144/147] some more recording --- packages/core/src/blitzkit/environment.ts | 32 +++++++++++++------ .../scripts/src/recordPerformance/index.ts | 25 ++++++++++++++- .../scripts/src/recordPerformance/types.ts | 20 ++++++++++++ 3 files changed, 66 insertions(+), 11 deletions(-) diff --git a/packages/core/src/blitzkit/environment.ts b/packages/core/src/blitzkit/environment.ts index b7bb18bca..077dd0d20 100644 --- a/packages/core/src/blitzkit/environment.ts +++ b/packages/core/src/blitzkit/environment.ts @@ -6,23 +6,35 @@ interface EnvironmentConfig { aliases: Record; } -export const environments = import.meta.glob( - "../../../../environments/*.json", - { +let hasReadEnvironments = false; +const environments: Record = {}; + +/** + * This is set up this way to avoid errors in runtimes where + * `import.meta.glob` does not exist. + */ +function readEnvironments() { + if (hasReadEnvironments) return; + + hasReadEnvironments = true; + + const glob = import.meta.glob("../../../../environments/*.json", { eager: true, import: "default", - }, -) as Record; -const root = "../../../../environments"; + }) as Record; + const root = "../../../../environments"; -for (const key in environments) { - const trimmed = key.slice(root.length + 1, -5); + for (const key in glob) { + const trimmed = key.slice(root.length + 1, -5); - environments[trimmed] = environments[key]; - delete environments[key]; + environments[trimmed] = glob[key]; + delete environments[key]; + } } export function environmentConfig() { + readEnvironments(); + const environment = assertSecret(import.meta.env.PUBLIC_ENVIRONMENT); if (!(environment in environments)) { diff --git a/packages/scripts/src/recordPerformance/index.ts b/packages/scripts/src/recordPerformance/index.ts index 4eb650eaa..fd228a8aa 100644 --- a/packages/scripts/src/recordPerformance/index.ts +++ b/packages/scripts/src/recordPerformance/index.ts @@ -1,10 +1,13 @@ +import { idToRegion } from "@blitzkit/core"; import { GitObjectStorage } from "../core/blitzkit/gitObjectStorage"; import { IdArray } from "../core/blitzkit/idArray"; import { API_RATE } from "../discoverIds/constants"; import { sleep, withinTimeLimit } from "../discoverIds/time"; import { IdsManifest } from "../discoverIds/types"; import { IDS_API_BASE } from "./constants"; -import { PerformanceManifest } from "./types"; +import { PerformanceManifest, QuickStatistics } from "./types"; + +const applicationId = import.meta.env.PUBLIC_WARGAMING_APPLICATION_ID; const storage = await new GitObjectStorage( "blitzkit/data-performance", @@ -35,6 +38,26 @@ while (withinTimeLimit()) { for (let i = 0; i < size; i++) { await sleep(); + + const id = ids.get(i); + const domain = idToRegion(id); + + const url = `https://api.wotblitz.${ + domain + }/wotb/tanks/stats/?application_id=${applicationId}&account_id=${id}`; + const response = await fetch(url); + const json = (await response.json()) as QuickStatistics; + + const data = json.data[id]; + + if (json.status !== "ok" || data === null) { + console.log(`Failed player ${id}; skipping...`); + continue; + } + + for (const tank of data) { + console.log(new Date(tank.battle_life_time * 1000)); + } } } diff --git a/packages/scripts/src/recordPerformance/types.ts b/packages/scripts/src/recordPerformance/types.ts index e5c12d10a..316815a5e 100644 --- a/packages/scripts/src/recordPerformance/types.ts +++ b/packages/scripts/src/recordPerformance/types.ts @@ -4,3 +4,23 @@ interface PerformanceManifestV1 { version: 1; last_updated: number; } + +export interface QuickStatistics { + status: string; + data: Record; +} + +interface QuickStatisticsTank { + last_battle_time: number; + battle_life_time: number; + tank_id: number; + + all: Record; +} + +interface Observations { + count: number; + observations: Record; +} + +interface Observation {} From 7e184b8be05d478b53b59ffbea27ebeba275580d Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Wed, 12 Aug 2026 11:36:55 -0500 Subject: [PATCH 145/147] eh whatever, this'll make it into the next update --- packages/scripts/src/recordPerformance/constants.ts | 9 +++++++++ packages/scripts/src/recordPerformance/types.ts | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/scripts/src/recordPerformance/constants.ts b/packages/scripts/src/recordPerformance/constants.ts index afbdd7c4c..0c7f3517b 100644 --- a/packages/scripts/src/recordPerformance/constants.ts +++ b/packages/scripts/src/recordPerformance/constants.ts @@ -1 +1,10 @@ export const IDS_API_BASE = "https://ids.blitzkit.app"; + +export const PARAMETERS = [ + "spotted", + "hits", + "frags", + "wins", + "losses", + "capture_points", +]; diff --git a/packages/scripts/src/recordPerformance/types.ts b/packages/scripts/src/recordPerformance/types.ts index 316815a5e..17666b41d 100644 --- a/packages/scripts/src/recordPerformance/types.ts +++ b/packages/scripts/src/recordPerformance/types.ts @@ -18,7 +18,7 @@ interface QuickStatisticsTank { all: Record; } -interface Observations { +interface ChunkObservations { count: number; observations: Record; } From 52630596b81592d14f0edc9bb0cddcfe13b812c1 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Wed, 12 Aug 2026 11:44:26 -0500 Subject: [PATCH 146/147] finalize workflows --- .github/workflows/ids.yaml | 3 ++- .github/workflows/website.yaml | 22 ++++++++++++++++------ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ids.yaml b/.github/workflows/ids.yaml index 91abc55cc..c5c88b464 100644 --- a/.github/workflows/ids.yaml +++ b/.github/workflows/ids.yaml @@ -2,7 +2,7 @@ name: Discover IDs on: push: - branches: infrastructure + branches: production schedule: - cron: 0 */6 * * * @@ -23,6 +23,7 @@ jobs: - name: Checkout uses: actions/checkout@v6 with: + ref: production submodules: recursive token: ${{ secrets.GH_TOKEN }} diff --git a/.github/workflows/website.yaml b/.github/workflows/website.yaml index ca11f5914..5ac92d6cf 100644 --- a/.github/workflows/website.yaml +++ b/.github/workflows/website.yaml @@ -2,19 +2,30 @@ name: Website on: push: + branches: production schedule: - cron: "0 4 * * *" workflow_dispatch: inputs: + branch: + description: Branch + + type: choice + required: true + default: production + + options: + - production + - main + environment: description: Environment - required: true - # default: production - default: beta type: choice + required: true + default: production options: - development @@ -32,7 +43,7 @@ concurrency: env: # blitzkit - PUBLIC_ENVIRONMENT: ${{ inputs.environment || 'beta' }} + PUBLIC_ENVIRONMENT: ${{ inputs.environment || 'production' }} PUBLIC_PROMOTE_OPENTEST: false PUBLIC_STATIC_ASSET_BASE: https://assets.blitzkit.app PUBLIC_GUIDE_WEBSITE: https://guidesblitz.com @@ -73,6 +84,7 @@ jobs: - name: Checkout uses: actions/checkout@v6 with: + ref: ${{ inputs.branch || 'production' }} submodules: recursive token: ${{ secrets.GH_TOKEN }} @@ -137,8 +149,6 @@ jobs: - name: Trigger deployment - if: github.ref_name == 'production' || github.ref_name == 'infrastructure' - env: TOKEN: ${{ secrets.GH_NODE_DEPLOY_TOKEN }} CHUNK: ${{ matrix.chunk }} From 3152c1884034ffee5f4fb7bf06a48a8ebcf36a25 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Wed, 12 Aug 2026 11:46:45 -0500 Subject: [PATCH 147/147] simplify robots.txt --- README.md | 31 +++++++++++------------ environments/beta.json | 3 +++ environments/development.json | 3 +++ environments/opentest.json | 3 +++ environments/preview.json | 3 +++ environments/production.json | 3 +++ packages/core/src/blitzkit/environment.ts | 2 ++ packages/website/src/pages/robots.txt.ts | 18 +++++++------ 8 files changed, 42 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 15581e128..da6ce8ef5 100644 --- a/README.md +++ b/README.md @@ -24,19 +24,18 @@ If you intend to contribute to the code, note that you may need to check out sen ## 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/). | -| `ALLOW_ROBOTS` | | `true` | Toggles between `Allow: /` and `Disallow: /` in the website's `/robots.txt`. This is usually `false` for all BlitzKit's subdomains. | -| `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` | | | | +| 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` | | | | diff --git a/environments/beta.json b/environments/beta.json index bc2910448..d5ff3f5d5 100644 --- a/environments/beta.json +++ b/environments/beta.json @@ -1,4 +1,7 @@ { + "robots": false, + "canonical": "https://blitzkit.app", + "paths": { "default": null, "api": null diff --git a/environments/development.json b/environments/development.json index 06aff4bef..742768ff4 100644 --- a/environments/development.json +++ b/environments/development.json @@ -1,4 +1,7 @@ { + "robots": false, + "canonical": "https://blitzkit.app", + "paths": { "default": null, "api": "api" diff --git a/environments/opentest.json b/environments/opentest.json index 4857ad043..0c29b792f 100644 --- a/environments/opentest.json +++ b/environments/opentest.json @@ -1,4 +1,7 @@ { + "robots": false, + "canonical": "https://blitzkit.app", + "paths": { "default": null, "api": null diff --git a/environments/preview.json b/environments/preview.json index 79a76960b..a3d980559 100644 --- a/environments/preview.json +++ b/environments/preview.json @@ -1,4 +1,7 @@ { + "robots": false, + "canonical": "https://blitzkit.app", + "paths": { "default": null, "api": null diff --git a/environments/production.json b/environments/production.json index 17e40a56b..89d8e9a5e 100644 --- a/environments/production.json +++ b/environments/production.json @@ -1,4 +1,7 @@ { + "robots": true, + "canonical": "https://blitzkit.app", + "paths": { "default": null, "api": null diff --git a/packages/core/src/blitzkit/environment.ts b/packages/core/src/blitzkit/environment.ts index 077dd0d20..633fe76b2 100644 --- a/packages/core/src/blitzkit/environment.ts +++ b/packages/core/src/blitzkit/environment.ts @@ -1,6 +1,8 @@ import { assertSecret } from "@blitzkit/core"; interface EnvironmentConfig { + robots: boolean; + canonical: string; paths: Record; nodes: Record; aliases: Record; diff --git a/packages/website/src/pages/robots.txt.ts b/packages/website/src/pages/robots.txt.ts index e056aa4a8..a051e9ad7 100644 --- a/packages/website/src/pages/robots.txt.ts +++ b/packages/website/src/pages/robots.txt.ts @@ -1,20 +1,22 @@ -import type { APIContext } from 'astro'; - -const ALLOW_ROBOTS = import.meta.env.ALLOW_ROBOTS; +import { environmentConfig } from "@blitzkit/core"; +import type { APIContext } from "astro"; export async function GET({ request }: APIContext) { + const config = environmentConfig(); + const flags: Record = { - 'User-agent': '*', - Allow: ALLOW_ROBOTS ? '/' : null, - Disallow: ALLOW_ROBOTS ? null : '/', + "User-agent": "*", + Allow: config.robots ? "/" : null, + Disallow: config.robots ? null : "/", space: null, + Sitemap: `${new URL(request.url).origin}/sitemap-index.xml`, }; return new Response( Object.entries(flags) - .map(([key, value]) => (value === null ? '' : `${key}: ${value}`)) - .join('\n'), + .map(([key, value]) => (value === null ? "" : `${key}: ${value}`)) + .join("\n"), ); }