diff --git a/.github/workflows/assets.yml b/.github/workflows/assets.yml deleted file mode 100644 index f71f06861..000000000 --- a/.github/workflows/assets.yml +++ /dev/null @@ -1,86 +0,0 @@ -name: Assets - -on: - push: - branches: main - - 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" - - schedule: - - cron: "0 2 * * *" - -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/.github/workflows/averages.yml b/.github/workflows/averages.yml deleted file mode 100644 index 167edc311..000000000 --- a/.github/workflows/averages.yml +++ /dev/null @@ -1,54 +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/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/ids.yaml b/.github/workflows/ids.yaml new file mode 100644 index 000000000..c5c88b464 --- /dev/null +++ b/.github/workflows/ids.yaml @@ -0,0 +1,45 @@ +name: Discover IDs + +on: + push: + branches: production + + schedule: + - cron: 0 */6 * * * + + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + discover: + name: Discover IDs + + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + ref: production + submodules: recursive + token: ${{ secrets.GH_TOKEN }} + + - name: Setup bun + uses: oven-sh/setup-bun@v2 + + - 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 + env: + PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }} + GH_TOKEN: ${{ secrets.IDS_TOKEN }} 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/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.yaml b/.github/workflows/website.yaml new file mode 100644 index 000000000..5ac92d6cf --- /dev/null +++ b/.github/workflows/website.yaml @@ -0,0 +1,177 @@ +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 + + type: choice + required: true + default: production + + options: + - development + - beta + - production + - preview + - opentest + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + # blitzkit + PUBLIC_ENVIRONMENT: ${{ inputs.environment || 'production' }} + PUBLIC_PROMOTE_OPENTEST: false + PUBLIC_STATIC_ASSET_BASE: https://assets.blitzkit.app + PUBLIC_GUIDE_WEBSITE: https://guidesblitz.com + + # blitz + 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 }} + + # wargaming + PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }} + + # steam + STEAM_USERNAME: ${{ secrets.STEAM_USERNAME }} + STEAM_PASSWORD: ${{ secrets.STEAM_PASSWORD }} + STEAM_APP: 444200 + STEAM_OS: windows + +jobs: + build: + name: Build website + + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + chunk: [default, api] + + steps: + - name: Free disk space + uses: jlumbroso/free-disk-space@main + + - name: Checkout + uses: actions/checkout@v6 + with: + ref: ${{ inputs.branch || 'production' }} + submodules: recursive + token: ${{ secrets.GH_TOKEN }} + + - name: Setup bun + uses: oven-sh/setup-bun@v2 + + - name: Setup protoc + uses: arduino/setup-protoc@v3 + with: + repo-token: ${{ secrets.GH_TOKEN }} + + - name: Configure pages + uses: actions/configure-pages@v6 + + - 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" + + $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 + env: + CHUNK: ${{ matrix.chunk }} + + - name: Upload artifact + id: upload + uses: actions/upload-artifact@v4 + with: + name: chunk-${{ matrix.chunk }} + path: dist/website + + - name: Trigger deployment + + env: + TOKEN: ${{ secrets.GH_NODE_DEPLOY_TOKEN }} + CHUNK: ${{ matrix.chunk }} + + run: | + ENV_FILE="environments/${PUBLIC_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" \ + -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\": \"${{ steps.upload.outputs.artifact-id }}\", + \"chunk\": \"$CHUNK\" + } + }" diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml deleted file mode 100644 index 1c739206c..000000000 --- a/.github/workflows/website.yml +++ /dev/null @@ -1,72 +0,0 @@ -name: Website - -on: - push: - workflow_dispatch: - - schedule: - - cron: "0 4 * * *" - -permissions: - contents: read - pages: write - id-token: write - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - 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 }} - 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: - build: - name: Build website - 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 }} - - uses: actions/configure-pages@v6 - - run: bun run setup - - - name: Build website - run: bun run build:website - - - name: Upload artifact - uses: actions/upload-pages-artifact@v5 - with: - path: dist/website - - deploy-github-pages: - if: ${{ github.ref == 'refs/heads/main' }} - name: Deploy to GitHub Pages - runs-on: ubuntu-latest - needs: build - environment: - name: Production - url: ${{ steps.deployment.outputs.page_url }} - - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v5 diff --git a/.gitignore b/.gitignore index 9e20f4a27..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/ @@ -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..da6ce8ef5 100644 --- a/README.md +++ b/README.md @@ -8,15 +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/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/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 @@ -26,22 +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_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` | | | | +| 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/bun.lock b/bun.lock index fe8a7edec..22cfe2bfd 100644 --- a/bun.lock +++ b/bun.lock @@ -5,51 +5,12 @@ "": { "name": "blitzkit", "dependencies": { - "@prisma/client": "^6.18.0", + "@gltf-transform/extensions": "^4.4.2", "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,9 +29,8 @@ "@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/bun": "^1.3.14", "@types/lodash-es": "^4.17.12", "@types/lz4js": "^0.2.1", "@types/three": "^0.180.0", @@ -103,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", @@ -118,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", @@ -147,6 +108,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", @@ -182,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=="], @@ -394,10 +391,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 +407,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=="], @@ -510,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.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=="], + "@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=="], @@ -672,20 +653,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 +805,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 +861,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 +875,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=="], @@ -992,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=="], @@ -1018,8 +921,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 +959,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 +971,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 +1073,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=="], @@ -1216,12 +1111,10 @@ "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=="], - "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 +1125,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 +1151,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 +1181,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 +1203,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 +1243,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 +1275,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 +1299,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 +1307,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 +1363,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 +1455,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 +1513,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 +1683,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 +1691,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=="], @@ -1848,22 +1699,20 @@ "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=="], "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 +1859,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 +1905,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 +1923,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 +1943,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=="], @@ -2132,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=="], @@ -2152,8 +1987,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 +2003,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 +2101,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 +2155,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 +2187,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 +2269,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 +2309,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 +2389,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 +2533,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 +2547,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 +2561,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 +2587,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 +2599,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 +2609,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 +2617,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 +2627,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 +2645,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 +2671,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 +2681,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 +2729,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 +2741,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 +2923,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/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/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 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/environments/beta.json b/environments/beta.json new file mode 100644 index 000000000..d5ff3f5d5 --- /dev/null +++ b/environments/beta.json @@ -0,0 +1,17 @@ +{ + "robots": false, + "canonical": "https://blitzkit.app", + + "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..742768ff4 --- /dev/null +++ b/environments/development.json @@ -0,0 +1,14 @@ +{ + "robots": false, + "canonical": "https://blitzkit.app", + + "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..0c29b792f --- /dev/null +++ b/environments/opentest.json @@ -0,0 +1,17 @@ +{ + "robots": false, + "canonical": "https://blitzkit.app", + + "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..a3d980559 --- /dev/null +++ b/environments/preview.json @@ -0,0 +1,17 @@ +{ + "robots": false, + "canonical": "https://blitzkit.app", + + "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..89d8e9a5e --- /dev/null +++ b/environments/production.json @@ -0,0 +1,17 @@ +{ + "robots": true, + "canonical": "https://blitzkit.app", + + "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" + } +} diff --git a/package.json b/package.json index a61810c71..340587062 100644 --- a/package.json +++ b/package.json @@ -1,51 +1,22 @@ { "name": "blitzkit", - "version": "1.30.8", + "version": "1.31.0", "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: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", - "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", + "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", + "record-performance": "cd packages/scripts && bun run record-performance" }, "type": "module", "workspaces": [ "packages/*" ], "dependencies": { - "@prisma/client": "^6.18.0", + "@gltf-transform/extensions": "^4.4.2", "lz4js": "^0.2.0", - "prisma": "^6.18.0", "sharp": "^0.34.5", "typescript": "^5.9.3" } 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/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 96ddf0003..000000000 Binary files a/packages/bot/src/core/blitzkit/jsxToSvg/Roboto-Black.ttf and /dev/null differ 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 7a876e28e..000000000 Binary files a/packages/bot/src/core/blitzkit/jsxToSvg/Roboto-Bold.ttf and /dev/null differ diff --git a/packages/bot/src/core/blitzkit/jsxToSvg/Roboto.ttf b/packages/bot/src/core/blitzkit/jsxToSvg/Roboto.ttf deleted file mode 100644 index 3033308a6..000000000 Binary files a/packages/bot/src/core/blitzkit/jsxToSvg/Roboto.ttf and /dev/null differ 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/core/package.json b/packages/core/package.json index 7f5046a56..d7e3bf264 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -8,9 +8,8 @@ "@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/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/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/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/base.ts b/packages/core/src/blitzkit/api/base.ts new file mode 100644 index 000000000..92038adc6 --- /dev/null +++ b/packages/core/src/blitzkit/api/base.ts @@ -0,0 +1,25 @@ +import { + CamouflageDefinitions, + ConsumableDefinitions, + EquipmentDefinitions, + Gallery, + GameDefinitions, + MapDefinitions, + ModelDefinitions, + ProvisionDefinitions, + SkillDefinitions, + TankDefinitions, +} from "@blitzkit/core"; + +export abstract class BlitzKitAPI { + 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; + abstract galleryDefinitions(): 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..02f645fb9 --- /dev/null +++ b/packages/core/src/blitzkit/api/client.ts @@ -0,0 +1,69 @@ +import { alias } from "@blitzkit/core"; +import { fetchPB } from "../../protobuf"; +import { + CamouflageDefinitions, + ConsumableDefinitions, + EquipmentDefinitions, + Gallery, + GameDefinitions, + MapDefinitions, + ModelDefinitions, + ProvisionDefinitions, + SkillDefinitions, + TankDefinitions, +} from "../../protos"; +import { BlitzKitAPI } from "./base"; + +export class ClientBlitzKitAPI extends BlitzKitAPI { + gameDefinitions() { + return fetchPB(alias("api", "/definitions/game.pb"), GameDefinitions); + } + + consumableDefinitions() { + return fetchPB( + alias("api", "/definitions/consumables.pb"), + ConsumableDefinitions, + ); + } + + tankDefinitions() { + return fetchPB(alias("api", "/definitions/tanks.pb"), TankDefinitions); + } + + camouflageDefinitions() { + return fetchPB( + alias("api", "/definitions/camouflage.pb"), + CamouflageDefinitions, + ); + } + + provisionDefinitions() { + return fetchPB( + alias("api", "/definitions/provisions.pb"), + ProvisionDefinitions, + ); + } + + modelDefinitions() { + return fetchPB(alias("api", "/definitions/models.pb"), ModelDefinitions); + } + + skillDefinitions() { + return fetchPB(alias("api", "/definitions/skills.pb"), SkillDefinitions); + } + + equipmentDefinitions() { + return fetchPB( + alias("api", "/definitions/equipment.pb"), + EquipmentDefinitions, + ); + } + + galleryDefinitions() { + return fetchPB(alias("api", "/definitions/gallery.pb"), Gallery); + } + + mapDefinitions() { + return fetchPB(alias("api", "/definitions/maps.pb"), MapDefinitions); + } +} diff --git a/packages/core/src/blitzkit/api/server/0_base.ts b/packages/core/src/blitzkit/api/server/0_base.ts new file mode 100644 index 000000000..a41ec123d --- /dev/null +++ b/packages/core/src/blitzkit/api/server/0_base.ts @@ -0,0 +1,304 @@ +import { + BlitzCrewType, + BlitzModuleType, + BlitzStrings, + BlitzTankClass, + BlitzTankFilterDefinitionCategory, + CamouflagesXml, + CamouflagesYaml, + CombatRolesYaml, + ConsumablesCommon, + ConsumableTankCategoryFilterCategory, + CrewType, + I18nString, + MapsYaml, + ModuleType, + OptionalDevices, + OptionalDeviceSlots, + ProvisionsCommon, + ShellKind, + ShellType, + 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"; +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, + ); + } + + 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; + } + + 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/10_galleryDefinitions.ts b/packages/core/src/blitzkit/api/server/10_galleryDefinitions.ts new file mode 100644 index 000000000..40cf9295e --- /dev/null +++ b/packages/core/src/blitzkit/api/server/10_galleryDefinitions.ts @@ -0,0 +1,67 @@ +import { Avatar, fetchGlossary, Gallery } from "@blitzkit/core"; +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() + 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/1_tankDefinitions.ts b/packages/core/src/blitzkit/api/server/1_tankDefinitions.ts new file mode 100644 index 000000000..9269a4900 --- /dev/null +++ b/packages/core/src/blitzkit/api/server/1_tankDefinitions.ts @@ -0,0 +1,718 @@ +import { + AssaultRanges, + BlitzCrewType, + BlitzModuleType, + BlitzTankClass, + ChassisDefinitionsList, + Crew, + EngineDefinitionsList, + Equalizer, + GunDefinition, + GunDefinitionsList, + ModuleType, + ResearchCost, + ShellDefinitionsList, + ShellKind, + sluggify, + TankDefinitions, + TankPrice, + TankPriceType, + TankType, + toUniqueId, + TurretDefinitionsList, + Unlock, + UnlocksListing, + VehicleDefinitionList, + VehicleDefinitions, +} from "@blitzkit/core"; +import { Cache, ServerBlitzKitAPI0 } from "./0_base"; + +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 tankId = toUniqueId(nation, tank.id); + + const tankTags = tank.tags.split(" "); + const equipment = tankDefinition.root.optDevicePreset; + + 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_camouflageDefinitions.ts b/packages/core/src/blitzkit/api/server/2_camouflageDefinitions.ts new file mode 100644 index 000000000..30ce31d6a --- /dev/null +++ b/packages/core/src/blitzkit/api/server/2_camouflageDefinitions.ts @@ -0,0 +1,34 @@ +import { CamouflageDefinitions } from "@blitzkit/core"; +import { Cache } from "./0_base"; +import { ServerBlitzKitAPI1 } from "./1_tankDefinitions"; + +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_modelDefinitions.ts b/packages/core/src/blitzkit/api/server/3_modelDefinitions.ts new file mode 100644 index 000000000..dfc96c1ab --- /dev/null +++ b/packages/core/src/blitzkit/api/server/3_modelDefinitions.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 "./0_base"; +import { ServerBlitzKitAPI2 } from "./2_camouflageDefinitions"; +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_mapDefinitions.ts b/packages/core/src/blitzkit/api/server/4_mapDefinitions.ts new file mode 100644 index 000000000..944b3738d --- /dev/null +++ b/packages/core/src/blitzkit/api/server/4_mapDefinitions.ts @@ -0,0 +1,21 @@ +import { MapDefinitions } from "@blitzkit/core"; +import { Cache } from "./0_base"; +import { ServerBlitzKitAPI3 } from "./3_modelDefinitions"; + +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_equipmentDefinitions.ts b/packages/core/src/blitzkit/api/server/5_equipmentDefinitions.ts new file mode 100644 index 000000000..95632096b --- /dev/null +++ b/packages/core/src/blitzkit/api/server/5_equipmentDefinitions.ts @@ -0,0 +1,42 @@ +import { EquipmentDefinitions, EquipmentSlot } from "@blitzkit/core"; +import { Cache } from "./0_base"; +import { ServerBlitzKitAPI4 } from "./4_mapDefinitions"; + +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_consumableDefinitions.ts b/packages/core/src/blitzkit/api/server/6_consumableDefinitions.ts new file mode 100644 index 000000000..f4b333f61 --- /dev/null +++ b/packages/core/src/blitzkit/api/server/6_consumableDefinitions.ts @@ -0,0 +1,114 @@ +import { + BlitzTankFilterDefinitionCategory, + Consumable, + ConsumableDefinitions, +} from "@blitzkit/core"; +import { Cache } from "./0_base"; +import { ServerBlitzKitAPI5 } from "./5_equipmentDefinitions"; + +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_provisionDefinitions.ts b/packages/core/src/blitzkit/api/server/7_provisionDefinitions.ts new file mode 100644 index 000000000..32db2bcc1 --- /dev/null +++ b/packages/core/src/blitzkit/api/server/7_provisionDefinitions.ts @@ -0,0 +1,117 @@ +import { + BlitzTankFilterDefinitionCategory, + Provision, + ProvisionDefinitions, +} from "@blitzkit/core"; +import { Cache } from "./0_base"; +import { ServerBlitzKitAPI6 } from "./6_consumableDefinitions"; + +export abstract class ServerBlitzKitAPI7 extends ServerBlitzKitAPI6 { + @Cache() + async provisionDefinitions() { + const provisionDefinitions = ProvisionDefinitions.create(); + + Object.entries(this.provisionsCommon).forEach(([, 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_skillDefinitions.ts b/packages/core/src/blitzkit/api/server/8_skillDefinitions.ts new file mode 100644 index 000000000..9198618fa --- /dev/null +++ b/packages/core/src/blitzkit/api/server/8_skillDefinitions.ts @@ -0,0 +1,21 @@ +import { BlitzTankClass, SkillDefinitions } from "@blitzkit/core"; + +import { Cache } from "./0_base"; +import { ServerBlitzKitAPI7 } from "./7_provisionDefinitions"; + +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[tankClass]; + + skillDefinitions.classes[ + this.blitzTankClassToBlitzkit[tankClass as BlitzTankClass] + ] = { skills: skills.split(" ") }; + } + + return skillDefinitions; + } +} diff --git a/packages/core/src/blitzkit/api/server/9_gameDefinitions.ts b/packages/core/src/blitzkit/api/server/9_gameDefinitions.ts new file mode 100644 index 000000000..683c126a5 --- /dev/null +++ b/packages/core/src/blitzkit/api/server/9_gameDefinitions.ts @@ -0,0 +1,58 @@ +import { AvailableNationsYaml, GameDefinitions } from "@blitzkit/core"; + +import { Cache } from "./0_base"; +import { ServerBlitzKitAPI8 } from "./8_skillDefinitions"; + +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..3b9c459a9 --- /dev/null +++ b/packages/core/src/blitzkit/api/server/index.ts @@ -0,0 +1,3 @@ +import { ServerBlitzKitAPI10 } from "@blitzkit/core/src/blitzkit/api/server/10_galleryDefinitions"; + +export class ServerBlitzKitAPI extends ServerBlitzKitAPI10 {} 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/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/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/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/environment.ts b/packages/core/src/blitzkit/environment.ts new file mode 100644 index 000000000..633fe76b2 --- /dev/null +++ b/packages/core/src/blitzkit/environment.ts @@ -0,0 +1,47 @@ +import { assertSecret } from "@blitzkit/core"; + +interface EnvironmentConfig { + robots: boolean; + canonical: string; + paths: Record; + nodes: Record; + aliases: Record; +} + +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"; + + for (const key in glob) { + const trimmed = key.slice(root.length + 1, -5); + + environments[trimmed] = glob[key]; + delete environments[key]; + } +} + +export function environmentConfig() { + readEnvironments(); + + const environment = assertSecret(import.meta.env.PUBLIC_ENVIRONMENT); + + if (!(environment in environments)) { + throw new Error(`Invalid environment "${environment}"`); + } + + return environments[environment]; +} 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/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/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/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 fdae4b28d..700e57018 100644 --- a/packages/core/src/blitzkit/index.ts +++ b/packages/core/src/blitzkit/index.ts @@ -1,38 +1,28 @@ +export * from "./alias"; export * from "./assertSecret"; -export * from "./asset"; export * from "./availableProvisions"; export * from "./averageDefinitions"; -// export * from './camouflageDefinitions'; -// export * from './checkConsumableProvisionInclusivity'; export * from "./coefficient"; -export * from "./consumableDefinitions"; -// export * from './context'; +export * from "./createDefaultProvisions"; export * from "./createDefaultSkills"; export * from "./degressiveStat"; export * from "./discoveredIdDefinitions"; export * from "./emblemURL"; -export * from "./equipmentDefinitions"; +export * from "./environment"; export * from "./eventManager"; -export * from "./gameDefinitions"; -export * from "./getArchivedLatestSeasonNumber"; -export * from "./sluggify"; -// 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"; @@ -41,10 +31,4 @@ export * from "./unionBoundingBox"; export * from "./uniqueGuns"; export * from "./unwrapBlitzkitResponse"; 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/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/core/src/blitzkit/tankDefinitions/index.ts b/packages/core/src/blitzkit/tankDefinitions/index.ts index 18bdfcd2a..b04bfcf75 100644 --- a/packages/core/src/blitzkit/tankDefinitions/index.ts +++ b/packages/core/src/blitzkit/tankDefinitions/index.ts @@ -1,9 +1 @@ -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'; +export * from "./constants"; diff --git a/packages/core/src/blitzkit/tankIcon.ts b/packages/core/src/blitzkit/tankIcon.ts index 546ef5f8f..8eef1203d 100644 --- a/packages/core/src/blitzkit/tankIcon.ts +++ b/packages/core/src/blitzkit/tankIcon.ts @@ -1,7 +1,7 @@ -import { asset } from "./asset"; +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 asset(`icons/tanks/${size}/${id}.webp`); + return alias("api", `/tanks/${id}/icons/${size}.webp`); } 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/scripts/src/core/vfs/abstract.ts b/packages/core/src/blitzkit/vfs/abstract.ts similarity index 83% rename from packages/scripts/src/core/vfs/abstract.ts rename to packages/core/src/blitzkit/vfs/abstract.ts index 9cae1309b..ea69982d3 100644 --- a/packages/scripts/src/core/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); @@ -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/scripts/src/core/vfs/local.ts b/packages/core/src/blitzkit/vfs/local.ts similarity index 95% rename from packages/scripts/src/core/vfs/local.ts rename to packages/core/src/blitzkit/vfs/local.ts index 0d2fa18ae..c82510048 100644 --- a/packages/scripts/src/core/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/scripts/src/core/vfs/mixed.ts b/packages/core/src/blitzkit/vfs/mixed.ts similarity index 96% rename from packages/scripts/src/core/vfs/mixed.ts rename to packages/core/src/blitzkit/vfs/mixed.ts index 1d3358ddb..d510e4115 100644 --- a/packages/scripts/src/core/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/scripts/src/core/vfs/steam.ts b/packages/core/src/blitzkit/vfs/steam.ts similarity index 98% rename from packages/scripts/src/core/vfs/steam.ts rename to packages/core/src/blitzkit/vfs/steam.ts index 79acd450d..9ab2c5047 100644 --- a/packages/scripts/src/core/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/scripts/src/core/vfs/zip.ts b/packages/core/src/blitzkit/vfs/zip.ts similarity index 97% rename from packages/scripts/src/core/vfs/zip.ts rename to packages/core/src/blitzkit/vfs/zip.ts index e181df62a..53d3e2856 100644 --- a/packages/scripts/src/core/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(); 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/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/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/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/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/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/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/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/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/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 c47180e45..a4038c044 100644 --- a/packages/core/src/types/index.ts +++ b/packages/core/src/types/index.ts @@ -1,2 +1,26 @@ -export * from './glossary'; -export * from './tanksStats'; +export * from "./availableNationsYaml"; +export * from "./blitzModuleType"; +export * from "./blitzStrings"; +export * from "./blitzTankFilterDefinitionCategory"; +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/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/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/core/src/types/shellDefinitionsList.ts b/packages/core/src/types/shellDefinitionsList.ts new file mode 100644 index 000000000..d9e5f3e6e --- /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; +}; + +export type ShellKind = + | "ARMOR_PIERCING" + | "ARMOR_PIERCING_CR" + | "HIGH_EXPLOSIVE" + | "HOLLOW_CHARGE"; 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/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/tankmenAvatar.ts b/packages/core/src/types/tankmenAvatar.ts new file mode 100644 index 000000000..a254e35a7 --- /dev/null +++ b/packages/core/src/types/tankmenAvatar.ts @@ -0,0 +1,18 @@ +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/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/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/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/i18n/strings/en.json b/packages/i18n/strings/en.json index 8e5746268..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": { @@ -204,536 +205,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", @@ -838,15 +309,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/packages/scripts/package.json b/packages/scripts/package.json index 8e788dc2d..a401cc068 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -1,5 +1,11 @@ { "name": "@blitzkit/scripts", + "private": true, + "scripts": { + "discover-ids": "bun src/discoverIds/index.ts", + "build-protos": "bun src/protos.ts", + "record-performance": "bun src/recordPerformance/index.ts" + }, "dependencies": { "@blitzkit/closed": "workspace:*", "@blitzkit/core": "workspace:*", @@ -9,7 +15,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 +30,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", @@ -33,21 +40,5 @@ "unzipit": "^1.4.3", "yaml": "^2.8.1" }, - "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.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", - "prunestrings": "bun src/pruneStrings.ts", - "build:protos": "bun src/protos.ts", - "patchnotes": "bun src/patchNotes.ts" - }, "type": "module" } 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/scripts/src/averages.ts b/packages/scripts/src/averages.ts deleted file mode 100644 index 5429544fc..000000000 --- a/packages/scripts/src/averages.ts +++ /dev/null @@ -1,248 +0,0 @@ -import { - AverageDefinitions, - AverageDefinitionsAllStats, - AverageDefinitionsEntry, - type AverageDefinitionsManifest, - 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; -} - -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; -// 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 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`, - content: AverageDefinitions.encode(averageDefinitions).finish(), - }); - await uploader.add({ - path: "averages/manifest.json", - content: new TextEncoder().encode(JSON.stringify(manifest)), - }); - - await uploader.flush(); -} 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/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/scripts/src/buildAssets/constants.ts b/packages/scripts/src/buildAssets/constants.ts deleted file mode 100644 index d295c7ad6..000000000 --- a/packages/scripts/src/buildAssets/constants.ts +++ /dev/null @@ -1,43 +0,0 @@ -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"; - -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/consumableProvisionIcons.ts b/packages/scripts/src/buildAssets/consumableProvisionIcons.ts deleted file mode 100644 index a9031bb24..000000000 --- a/packages/scripts/src/buildAssets/consumableProvisionIcons.ts +++ /dev/null @@ -1,225 +0,0 @@ -import sharp from "sharp"; -import { AssetUploader } from "../core/github/assetUploader"; -import { parsePackedSpriteRect } from "../core/blitz/parsePackedSpriteRect"; -import { vfs } from "./constants"; -import { ConsumablesCommon, ProvisionsCommon } from "./definitions"; - -interface Mappings { - Header: { version: number }; - StyleSheets: { - selectors: string[]; - properties: { "bg-sprite": string }; - }[]; -} - -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..."); - - 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/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/scripts/src/buildAssets/definitions.ts b/packages/scripts/src/buildAssets/definitions.ts deleted file mode 100644 index b2b10160d..000000000 --- a/packages/scripts/src/buildAssets/definitions.ts +++ /dev/null @@ -1,1839 +0,0 @@ -import { - Armor, - AssaultRanges, - type BlitzTankClass, - CamouflageDefinitions, - Consumable, - ConsumableDefinitions, - ConsumableTankCategoryFilterCategory, - Crew, - CrewType, - Equalizer, - EquipmentDefinitions, - EquipmentSlot, - GameDefinitions, - GunDefinition, - I18nString, - MapDefinitions, - ModelDefinitions, - ModuleType, - Provision, - ProvisionDefinitions, - ResearchCost, - ShellType, - SkillDefinitions, - sluggify, - TankClass, - TankDefinitions, - TankPrice, - TankPriceType, - TankType, - toUniqueId, - 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"; - -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 { - 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, - ConsumableTankCategoryFilterCategory -> = { - clip: ConsumableTankCategoryFilterCategory.CONSUMABLE_TANK_CATEGORY_FILTER_CATEGORY_CLIP, -}; -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; -} -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: ""; - }, - ]; - }; - }; -} -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; -} -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 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; - 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 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; -} - -interface AvailableNationsYaml { - available_nations: string[]; -} - -export interface SquadBattleTypeStyles { - Prototypes: { - components: { - UIDataLocalBindingsComponent: { - data: [string, string, string][]; - }; - }; - }[]; -} - -type CombatRolesYaml = Record< - string, - { - id: number; - default_abilities: string[]; - } ->; - -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 const botPattern = /^.+((tutorial_bot(\d+)?)|(TU))$/; -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() { - 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: {} }; - 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`) - ).matchAll(//g)) { - if (match[1] === "prototypes") continue; - - Object.assign( - consumables, - ( - 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( - provisions, - ( - 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[][]; - - 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]); - 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( - /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 nations) { - 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 nations) { - 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 = camouflagesXmlEntries - .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 = equalizerDefinitions.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); - }); - - 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(consumables).forEach(([key, consumable]) => { - consumableNativeNames[key] = consumable.id; - - 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(provisions).forEach(([key, provision]) => { - provisionNativeNames[key] = provision.id; - - 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(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] - ] = { - skills: skills.split(" "), - }; - }); - - Object.entries(maps.maps).forEach(([key, map]) => { - mapDefinitions.maps[map.id] = { - id: map.id, - name: getString(`#maps:${key}:${map.localName}`), - }; - }); - - await uploader.add({ - content: GameDefinitions.encode(gameDefinitions).finish(), - path: "definitions/game.pb", - }); - 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(); -} 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/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/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/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/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/scripts/src/buildAssets/skillIcons.ts b/packages/scripts/src/buildAssets/skillIcons.ts deleted file mode 100644 index 4b3264ae1..000000000 --- a/packages/scripts/src/buildAssets/skillIcons.ts +++ /dev/null @@ -1,45 +0,0 @@ -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 }>( - `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/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/buildAssets/tankIcons.ts b/packages/scripts/src/buildAssets/tankIcons.ts deleted file mode 100644 index fb74388d9..000000000 --- a/packages/scripts/src/buildAssets/tankIcons.ts +++ /dev/null @@ -1,135 +0,0 @@ -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); - - 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..."); - - 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(); -} 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/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/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/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/scripts/src/core/blitzkit/gitObjectStorage.ts b/packages/scripts/src/core/blitzkit/gitObjectStorage.ts new file mode 100644 index 000000000..6ae14d105 --- /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(`${this.base}/${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/core/blitzkit/idArray.ts b/packages/scripts/src/core/blitzkit/idArray.ts new file mode 100644 index 000000000..e37aebdb3 --- /dev/null +++ b/packages/scripts/src/core/blitzkit/idArray.ts @@ -0,0 +1,79 @@ +import { decompress } from "lz4js"; + +export class IdArray { + constructor( + 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 fromCompressed(bytes: Uint8Array) { + const decompressed = decompress(bytes); + return this.fromBytes(decompressed); + } + + 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.ts b/packages/scripts/src/discoverIds.ts deleted file mode 100644 index 27d5ac36c..000000000 --- a/packages/scripts/src/discoverIds.ts +++ /dev/null @@ -1,165 +0,0 @@ -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 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; - -console.log( - `Fetching ${preDiscoveredManifest.chunks} pre-discovered chunks...` -); - -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(); - } - ); - - // 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++; -} - -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; - -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; - }); - - return filtered; -} - -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(); -} diff --git a/packages/scripts/src/discoverIds/constants.ts b/packages/scripts/src/discoverIds/constants.ts new file mode 100644 index 000000000..ee523a5e1 --- /dev/null +++ b/packages/scripts/src/discoverIds/constants.ts @@ -0,0 +1,6 @@ +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 new file mode 100644 index 000000000..c28e2e6a3 --- /dev/null +++ b/packages/scripts/src/discoverIds/index.ts @@ -0,0 +1,251 @@ +import { range } from "lodash-es"; +import { compress } from "lz4js"; +import { exit } from "node:process"; +import { GitObjectStorage } from "../core/blitzkit/gitObjectStorage"; +import { IdArray } from "../core/blitzkit/idArray"; +import { + ACCOMMODATED_ID_COUNT, + MAX_BYTES_PER_CHUNK, + MAX_DRY_STREAK, + MAX_IDS_PER_CALL, +} from "./constants"; +import { sleep, withinTimeLimit } from "./time"; +import { IdsManifest, QuickInfo, RegionDescriptor } from "./types"; + +const storage = await new GitObjectStorage( + "blitzkit/data-ids", + "../../temp/ids", + import.meta.env.GH_TOKEN, +).init(); + +await storage.mkdir("chunks"); + +console.log(`Discovering pre-existing chunks...`); + +const discoveredChunks: (IdArray | null)[] = []; + +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 ids = IdArray.fromCompressed(bytes); + + discoveredChunks.push(ids); +} + +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 === 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: manifest.chunks }).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 % manifest.chunks]; + + chunk.push(id); + } + + discoveredChunks[i] = null; + } + + console.log("Sorting chunks..."); + + for (let i = 0; i < chunks.length; i++) chunks[i].sort(); +} + +const regions: RegionDescriptor[] = [ + { 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..."); + +for (let i = 0; i < manifest.chunks; 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()}`); +} + +let isSaving = false; +async function save() { + if (isSaving) return; + + isSaving = true; + + console.log("Saving chunks..."); + + await storage.rm("chunks/*"); + + let totalFound = 0; + + for (let i = 0; i < chunks.length; i++) { + const chunk = chunks[i]; + const bytes = chunk.toBytes(); + const compressed = compress(bytes); + + totalFound += chunk.size(); + + await storage.write(`chunks/chunk-${i}.dat.lz4`, compressed); + } + + manifest.total_count = totalFound; + + await storage.write("manifest.json", JSON.stringify(manifest, null, 2)); + + await storage.commit(); + + console.log( + `Committed ${totalFound.toLocaleString()} total ids across ${manifest.chunks} chunks`, + ); + + exit(0); +} + +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"; +let regionI = 0; + +function fillQueue() { + if (queue.length >= 1) return; + + 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); + } + + return; + } +} + +console.log("Starting discovery loop..."); + +while (withinTimeLimit() && regions.length > 0) { + await sleep(); + + fillQueue(); + + if (queue.length === 0) { + console.log("Queue empty, ending discovery loop..."); + break; + } + + 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(request); + + continue; + } + + let foundIds = 0; + + for (const key in data.data) { + const value = data.data[key]; + + if (value === null) continue; + + foundIds++; + + const id = Number(key); + const chunkIndex = id % manifest.chunks; + const chunk = chunks[chunkIndex]; + + chunk.push(id); + } + + if (foundIds === 0) { + request.region.dry_streak += request.size; + } else { + request.region.dry_streak = 0; + // console.log(`Found ${foundIds} ids in ${request.region.domain}`); + } +} + +// 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(); 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 new file mode 100644 index 000000000..53eedba0b --- /dev/null +++ b/packages/scripts/src/discoverIds/types.ts @@ -0,0 +1,20 @@ +export type IdsManifest = IdsManifestV1; + +interface IdsManifestV1 { + 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; +} 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)); -} diff --git a/packages/scripts/src/recordPerformance/constants.ts b/packages/scripts/src/recordPerformance/constants.ts new file mode 100644 index 000000000..0c7f3517b --- /dev/null +++ b/packages/scripts/src/recordPerformance/constants.ts @@ -0,0 +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/index.ts b/packages/scripts/src/recordPerformance/index.ts new file mode 100644 index 000000000..fd228a8aa --- /dev/null +++ b/packages/scripts/src/recordPerformance/index.ts @@ -0,0 +1,64 @@ +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, QuickStatistics } from "./types"; + +const applicationId = import.meta.env.PUBLIC_WARGAMING_APPLICATION_ID; + +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()) { + 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(); + + 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)); + } + } +} + +// storage.commit(); diff --git a/packages/scripts/src/recordPerformance/types.ts b/packages/scripts/src/recordPerformance/types.ts new file mode 100644 index 000000000..17666b41d --- /dev/null +++ b/packages/scripts/src/recordPerformance/types.ts @@ -0,0 +1,26 @@ +export type PerformanceManifest = PerformanceManifestV1; + +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 ChunkObservations { + count: number; + observations: Record; +} + +interface Observation {} 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/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 diff --git a/packages/website/astro.config.ts b/packages/website/astro.config.ts index 07c0b288d..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,25 +9,20 @@ 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 }, - vite: { server: { allowedHosts: [] } }, - output: target === "web" ? "static" : "server", + vite: { + server: { allowedHosts: [] }, + esbuild: { target: "es2022" }, + }, + + 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({ @@ -50,15 +44,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 +62,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/package.json b/packages/website/package.json index a32568a55..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": { @@ -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/Compare/Body.tsx b/packages/website/src/components/Compare/Body.tsx index d6bdc5193..ee88cd4e4 100644 --- a/packages/website/src/components/Compare/Body.tsx +++ b/packages/website/src/components/Compare/Body.tsx @@ -1,4 +1,4 @@ -import { asset } from "@blitzkit/core"; +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"; @@ -58,7 +58,10 @@ export function Body({ stats }: BodyProps) { alt={unwrap(thisShell.name!)} width={16} height={16} - src={asset(`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 d3ca23c23..59d7ab555 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 { alias, 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,10 @@ export function CompareTable({ stats }: CompareTableProps) { alt={module} width={24} height={24} - src={asset(`icons/modules/${module}.webp`)} + src={alias( + "api", + `/icons/modules/${module}.webp`, + )} style={{ marginLeft: index > 0 ? -8 : 0, objectFit: "contain", @@ -323,8 +323,9 @@ export function CompareTable({ stats }: CompareTableProps) { .name!, )} key={provision} - src={asset( - `/icons/provisions/${provision}.webp`, + src={alias( + "api", + `//icons/provisions/${provision}.webp`, )} style={{ left: "50%", @@ -530,7 +531,8 @@ export function CompareTable({ stats }: CompareTableProps) { ].name!, )} key={consumable} - src={asset( + src={alias( + "api", `/icons/consumables/${consumable}.webp`, )} style={{ @@ -624,7 +626,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/components/CrewSkillManager.tsx b/packages/website/src/components/CrewSkillManager.tsx index 87e459f6b..4e4d9b7ad 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 { alias, 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={alias("api", `/icons/skills/${skill}.webp`)} onClick={(event) => { if (!onChange) return; 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/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/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/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/components/Footer.astro b/packages/website/src/components/Footer.astro index 196f62f34..3bcd19c6c 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,8 +11,8 @@ export interface Props { locale: string; } -const gameDefinitions = await fetchGameDefinitions(); -const PUBLIC_ASSET_BRANCH = assertSecret(import.meta.env.PUBLIC_BRANCH); +const gameDefinitions = await api.gameDefinitions(); +const environment = assertSecret(import.meta.env.PUBLIC_ENVIRONMENT); const { locale } = Astro.props; const strings = getStrings(locale); --- @@ -134,7 +135,7 @@ const strings = getStrings(locale); BlitzKit { - PUBLIC_ASSET_BRANCH !== "preview" && ( + environment !== "preview" && ( WoTB @@ -148,14 +149,14 @@ const strings = getStrings(locale); {packageJson.version} { - PUBLIC_ASSET_BRANCH !== "preview" && ( + environment !== "preview" && ( {gameDefinitions.version} ) } - {PUBLIC_ASSET_BRANCH} + {environment} @@ -167,7 +168,7 @@ const strings = getStrings(locale); { - PUBLIC_ASSET_BRANCH !== "preview" && ( + environment !== "preview" && ( <> WoTB @@ -181,7 +182,7 @@ const strings = getStrings(locale); CDN - {PUBLIC_ASSET_BRANCH} + {environment} diff --git a/packages/website/src/components/Gallery/Card.tsx b/packages/website/src/components/Gallery/Card.tsx index 8345b5ded..dfc762b44 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 { alias, Avatar } from "@blitzkit/core"; import { Cross1Icon, DownloadIcon } from "@radix-ui/react-icons"; import { Dialog, @@ -18,8 +18,9 @@ type GalleryCardProps = MaybeSkeletonComponentProps<{ }>; export function GalleryCard(props: GalleryCardProps) { - const src = asset( - `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/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/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..09c1a3cd0 100644 --- a/packages/website/src/components/Guesser.tsx +++ b/packages/website/src/components/Guesser.tsx @@ -1,5 +1,5 @@ import { - asset, + alias, SEARCH_KEYS, TankDefinition, TIER_ROMAN_NUMERALS, @@ -29,8 +29,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 +40,7 @@ const { go } = fuzzysort; const [tankNames, tankDefinitions] = await Promise.all([ awaitableTankNames, - awaitableTankDefinitions, + api.tankDefinitions(), ]); const ids = Object.keys(tankDefinitions.tanks); @@ -150,7 +150,10 @@ export function Guesser() { prefix={ } discriminator={ @@ -322,7 +325,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/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/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/components/ModuleButtons/ConsumableButton.tsx b/packages/website/src/components/ModuleButtons/ConsumableButton.tsx index f6ddf6749..96ab54bae 100644 --- a/packages/website/src/components/ModuleButtons/ConsumableButton.tsx +++ b/packages/website/src/components/ModuleButtons/ConsumableButton.tsx @@ -1,7 +1,7 @@ -import { asset } from "@blitzkit/core"; +import { alias } 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 +13,7 @@ interface ConsumableButtonProps extends TankComponentButtonProps { cooldown?: number; } -const consumableDefinitions = await awaitableConsumableDefinitions; +const consumableDefinitions = await api.consumableDefinitions(); export function ConsumableButton({ consumable, @@ -29,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 337937832..b796d66d7 100644 --- a/packages/website/src/components/ModuleButtons/EquipmentButton.tsx +++ b/packages/website/src/components/ModuleButtons/EquipmentButton.tsx @@ -1,5 +1,5 @@ -import { asset } from "@blitzkit/core"; -import { awaitableEquipmentDefinitions } from "../../core/awaitables/equipmentDefinitions"; +import { alias } from "@blitzkit/core"; +import { api } from "../../core/blitzkit/api"; import { useLocale } from "../../hooks/useLocale"; import { GenericTankComponentButton } from "./GenericTankComponentButton"; import type { TankComponentButtonProps } from "./TankComponentButton"; @@ -8,7 +8,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 +16,7 @@ export function EquipmentButton({ equipment, ...props }: EquipmentButtonProps) { return ( ); diff --git a/packages/website/src/components/ModuleButtons/ModuleButton.tsx b/packages/website/src/components/ModuleButtons/ModuleButton.tsx index 663293425..007dd1e28 100644 --- a/packages/website/src/components/ModuleButtons/ModuleButton.tsx +++ b/packages/website/src/components/ModuleButtons/ModuleButton.tsx @@ -1,6 +1,6 @@ -import { asset } from '@blitzkit/core'; -import { GenericTankComponentButton } from './GenericTankComponentButton'; -import type { TankComponentButtonProps } from './TankComponentButton'; +import { alias } from "@blitzkit/core"; +import { GenericTankComponentButton } from "./GenericTankComponentButton"; +import type { TankComponentButtonProps } from "./TankComponentButton"; interface ModuleProps extends TankComponentButtonProps { module: string; @@ -9,11 +9,11 @@ interface ModuleProps extends TankComponentButtonProps { export function ModuleButton({ module, ...props }: ModuleProps) { return ( diff --git a/packages/website/src/components/ModuleButtons/ProvisionButton.tsx b/packages/website/src/components/ModuleButtons/ProvisionButton.tsx index a3876df40..bdf567005 100644 --- a/packages/website/src/components/ModuleButtons/ProvisionButton.tsx +++ b/packages/website/src/components/ModuleButtons/ProvisionButton.tsx @@ -1,5 +1,5 @@ -import { asset } from "@blitzkit/core"; -import { awaitableProvisionDefinitions } from "../../core/awaitables/provisionDefinitions"; +import { alias } from "@blitzkit/core"; +import { api } from "../../core/blitzkit/api"; import { useLocale } from "../../hooks/useLocale"; import { GenericTankComponentButton } from "./GenericTankComponentButton"; import type { TankComponentButtonProps } from "./TankComponentButton"; @@ -8,7 +8,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 +16,7 @@ export function ProvisionButton({ provision, ...props }: ProvisionButtonProps) { return ( ); diff --git a/packages/website/src/components/ModuleButtons/ShellButton.tsx b/packages/website/src/components/ModuleButtons/ShellButton.tsx index 08c3fcfd8..3aeb7d9d4 100644 --- a/packages/website/src/components/ModuleButtons/ShellButton.tsx +++ b/packages/website/src/components/ModuleButtons/ShellButton.tsx @@ -1,6 +1,6 @@ -import { asset } from '@blitzkit/core'; -import { GenericTankComponentButton } from './GenericTankComponentButton'; -import type { TankComponentButtonProps } from './TankComponentButton'; +import { alias } from "@blitzkit/core"; +import { GenericTankComponentButton } from "./GenericTankComponentButton"; +import type { TankComponentButtonProps } from "./TankComponentButton"; interface ShellButtonProps extends TankComponentButtonProps { shell: string; @@ -9,7 +9,7 @@ interface ShellButtonProps extends TankComponentButtonProps { export function ShellButton({ shell, ...props }: ShellButtonProps) { return ( ); 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/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/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/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/SmallTankIcon.tsx b/packages/website/src/components/SmallTankIcon.tsx index 9bf36d757..144301eb2 100644 --- a/packages/website/src/components/SmallTankIcon.tsx +++ b/packages/website/src/components/SmallTankIcon.tsx @@ -1,4 +1,4 @@ -import { asset } from '@blitzkit/core'; +import { alias } from "@blitzkit/core"; interface SmallTankIconProps { id: number; @@ -13,16 +13,16 @@ export function SmallTankIcon({ id, size = HEIGHT }: SmallTankIconProps) {
{`Tank
diff --git a/packages/website/src/components/TankCard/index.tsx b/packages/website/src/components/TankCard/index.tsx index 64eece530..8a39e8ae1 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 { alias, 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( ( @@ -64,7 +60,10 @@ export const TankCard = forwardRef( className="tank-search-card" data-provide-link={provideLink} style={{ - backgroundImage: `url(${asset(`flags/scratched/${tank.nation}.webp`)})`, + backgroundImage: `url(${alias( + "api", + `/flags/scratched/${tank.nation}.webp`, + )})`, ...style, }} {...props} @@ -80,7 +79,7 @@ export const TankCard = forwardRef( > {unwrap(tank.name!)} 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..ee16888ae 100644 --- a/packages/website/src/components/TankSearch/components/FilterControl.tsx +++ b/packages/website/src/components/TankSearch/components/FilterControl.tsx @@ -1,5 +1,5 @@ import { - asset, + alias, GunDefinition, ShellType, TANK_CLASSES, @@ -27,10 +27,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 +40,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 +310,7 @@ function NationsFilter() { width: "1.25em", height: "1.25em", }} - src={asset(`flags/circle/${nation}.webp`)} + src={alias("api", `/flags/circle/${nation}.webp`)} /> ))} @@ -350,9 +347,7 @@ function NationsFilter() { right="0" mr="-2" style={{ - backgroundImage: `url(${asset( - `flags/scratched/${nation}.webp`, - )})`, + backgroundImage: `url(${alias("api", `/flags/scratched/${nation}.webp`)})`, backgroundSize: "100%", backgroundRepeat: "no-repeat", backgroundPosition: "5rem center", @@ -685,10 +680,9 @@ function IndividualShellFilter({ {shells[index] !== null && ( )} @@ -720,8 +714,9 @@ function IndividualShellFilter({ color={selected ? undefined : "gray"} > ))} @@ -938,7 +933,7 @@ function ConsumablesFilter() { height: "1.25em", objectFit: "contain", }} - src={asset(`icons/consumables/${consumable}.webp`)} + src={alias("api", `/icons/consumables/${consumable}.webp`)} /> {unwrap(consumableDefinition.name!)} @@ -987,7 +982,7 @@ function ProvisionsFilter() { height: "1.25em", objectFit: "contain", }} - src={asset(`icons/provisions/${provision}.webp`)} + src={alias("api", `/icons/provisions/${provision}.webp`)} /> ))} @@ -1032,7 +1027,7 @@ function ProvisionsFilter() { height: "1.25em", objectFit: "contain", }} - src={asset(`icons/provisions/${provision}.webp`)} + src={alias("api", `/icons/provisions/${provision}.webp`)} /> {unwrap(provisionDefinition.name!)} @@ -1075,8 +1070,10 @@ function GameModeAbilitiesFilter() { : rawPowers; const icons = [ - ...abilities.map((ability) => `icons/consumables/${ability}.webp`), - ...powers.map((power) => `icons/provisions/${power}.webp`), + ...abilities.map((ability) => + alias("api", `/icons/consumables/${ability}.webp`), + ), + ...powers.map((power) => alias("api", `/icons/provisions/${power}.webp`)), ]; return ( @@ -1094,7 +1091,7 @@ function GameModeAbilitiesFilter() { height: "1.25em", objectFit: "contain", }} - src={asset(icon)} + src={icon} /> ))} @@ -1161,7 +1158,10 @@ function GameModeAbilitiesFilter() { height: "1.25em", objectFit: "contain", }} - src={asset(`icons/consumables/${consumable}.webp`)} + src={alias( + "api", + `/icons/consumables/${consumable}.webp`, + )} /> {unwrap(abilityDefinition.name!)} @@ -1210,7 +1210,10 @@ function GameModeAbilitiesFilter() { height: "1.25em", objectFit: "contain", }} - src={asset(`icons/provisions/${provision}.webp`)} + src={alias( + "api", + `/icons/provisions/${provision}.webp`, + )} /> {unwrap(powerDefinition.name!)} 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/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/components/Tankopedia/CalloutsSection.tsx b/packages/website/src/components/Tankopedia/CalloutsSection.tsx index 4bc97c35c..b3e2d5b39 100644 --- a/packages/website/src/components/Tankopedia/CalloutsSection.tsx +++ b/packages/website/src/components/Tankopedia/CalloutsSection.tsx @@ -1,20 +1,21 @@ -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); 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/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..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 { asset, 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={asset(`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/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..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,4 +1,4 @@ -import { asset } from "@blitzkit/core"; +import { alias } from "@blitzkit/core"; import { Flex, IconButton, Tooltip } from "@radix-ui/themes"; import { useLocale } from "../../../../../../../../hooks/useLocale"; import { Duel } from "../../../../../../../../stores/duel"; @@ -38,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 a688de0d7..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 { asset, 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) { /> 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..5a79c3feb 100644 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Equalizer.tsx +++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Equalizer.tsx @@ -1,15 +1,5 @@ -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 { alias } from "@blitzkit/core"; +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 +34,7 @@ export function Equalizer() { style={{ cursor: "pointer", borderRadius: "var(--radius-3)", - backgroundImage: `url(${asset("icons/game_mode_banners/45.webp")})`, + backgroundImage: `url(${alias("api", `/gamemodes/45/banner.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..298966160 100644 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Miscellaneous.tsx +++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Miscellaneous.tsx @@ -1,4 +1,4 @@ -import { asset } from "@blitzkit/core"; +import { alias } from "@blitzkit/core"; import { Flex, Heading } from "@radix-ui/themes"; import { useLocale } from "../../../../hooks/useLocale"; import { Duel } from "../../../../stores/duel"; @@ -8,7 +8,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 +22,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..cd44eeb82 100644 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Modules.tsx +++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Modules.tsx @@ -1,5 +1,5 @@ import { - asset, + alias, EngineDefinition, formatCompact, GunDefinition, @@ -20,7 +20,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 +111,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..41ea483b4 100644 --- a/packages/website/src/components/Tankopedia/GameModeSection.tsx +++ b/packages/website/src/components/Tankopedia/GameModeSection.tsx @@ -1,16 +1,14 @@ -import { asset } from "@blitzkit/core"; +import { alias } 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,8 +38,9 @@ export function GameModeSection() { direction="column" style={{ borderRadius: "var(--radius-3)", - background: `url(${asset( - `icons/game_mode_banners/${id}.webp`, + background: `url(${alias( + "api", + `/gamemodes/${id}/banner.webp`, )})`, backgroundSize: "cover", backgroundPosition: "center", @@ -50,8 +49,9 @@ export function GameModeSection() { {unwrap(provisions.name!)} @@ -108,7 +108,7 @@ export function GameModeSection() { height: "1.5em", objectFit: "contain", }} - src={asset(`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 6da4bfb18..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,4 +1,4 @@ -import { asset } from "@blitzkit/core"; +import { alias } from "@blitzkit/core"; import { IconButton } from "@radix-ui/themes"; import { useArmor } from "../../../../../../hooks/useArmor"; import { Duel } from "../../../../../../stores/duel"; @@ -7,7 +7,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 +38,7 @@ export function DynamicArmorSwitcher() { > Dynamic Armor {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/components/TankSandbox/components/Lighting.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/Lighting.tsx index 75ee0f3fc..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,4 +1,6 @@ -import { useFrame } from "@react-three/fiber"; +import { blueDark, orangeDark } from "@radix-ui/colors"; +import { ContactShadows } from "@react-three/drei"; +import { invalidate, useFrame } from "@react-three/fiber"; import { clamp, times } from "lodash-es"; import { Quicklime } from "quicklime"; import { @@ -10,27 +12,35 @@ 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 { HelpingSpotLight } from "../../../../../HelpingSpotLight"; +import { TankopediaDisplay } from "../../../../../../stores/tankopediaPersistent/constants"; -const ANGLE = degToRad(12); +const ANGLE = degToRad(15); 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 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() { - const wrapper = useRef(null!); + const wrapper = useRef(null); const highGraphics = TankopediaPersistent.use((state) => state.highGraphics); const requestedDisplay = Tankopedia.use((state) => state.requestedDisplay); @@ -61,6 +71,17 @@ export function Lighting() { return ( <> + + {animate && ( - + {times(LIGHTS_COUNT, (index) => { 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; const intensity = lerp(INTENSITY_0, INTENSITY_1, x); + const light = useRef(null!); + + // useHelper(light, SpotLightHelper); + return ( - ); })} @@ -109,18 +141,31 @@ interface AnimatorProps { t0: RefObject; animationTime: RefObject; - wrapper: RefObject; + wrapper: RefObject; } 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; + + useEffect(() => { + invalidate(); + }, [requestedDisplay === display]); + const apply = useCallback((t: number) => { + if (!wrapper.current) 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; } } 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..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,7 +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 { Mesh, MeshStandardMaterial } from "three"; +import { useCallback, useEffect, useRef } from "react"; +import { Mesh, type WebGLProgramParametersWithUniforms } from "three"; const SIZE = 2 ** 4; @@ -9,7 +10,6 @@ export const screenshotReadyEvent = new Quicklime(false); export function SceneProps() { const mesh = useRef(null!); - const material = useRef(null); useEffect(() => { function handleScreenshotReady(event: QuicklimeEvent) { @@ -24,19 +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/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( state.equalize); diff --git a/packages/website/src/components/Tankopedia/MetaSection/components/Listing.tsx b/packages/website/src/components/Tankopedia/MetaSection/components/Listing.tsx index 79becdf1c..2f743a943 100644 --- a/packages/website/src/components/Tankopedia/MetaSection/components/Listing.tsx +++ b/packages/website/src/components/Tankopedia/MetaSection/components/Listing.tsx @@ -1,5 +1,5 @@ -import { Flex, Text, type TextProps } from '@radix-ui/themes'; -import { Var } from '../../../../core/radix/var'; +import { Flex, Text, type TextProps } from "@radix-ui/themes"; +import { Var } from "../../../../core/radix/var"; type ListingProps = TextProps & { label: string; @@ -11,7 +11,7 @@ export function Listing({ label, ...props }: ListingProps) { minWidth="17rem" justify="between" gap="4" - style={{ borderBottom: `1px solid ${Var('gray-a9')}` }} + style={{ borderBottom: `1px solid ${Var("gray-a9")}` }} > {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 d795d1926..ca25dbdfe 100644 --- a/packages/website/src/components/Tankopedia/MetaSection/index.tsx +++ b/packages/website/src/components/Tankopedia/MetaSection/index.tsx @@ -1,5 +1,5 @@ import { - asset, + alias, TankPriceType, TankType, TIER_ROMAN_NUMERALS, @@ -40,7 +40,7 @@ export function MetaSection() { > { strings.common.nations[ @@ -135,7 +138,7 @@ export function MetaSection() { gold @@ -152,8 +155,9 @@ export function MetaSection() { {TankPriceType[protagonist.price!.type]} diff --git a/packages/website/src/components/Tankopedia/TechTreeSection/components/Node.tsx b/packages/website/src/components/Tankopedia/TechTreeSection/components/Node.tsx index 9a6ceb7f7..5a710564e 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 { 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"; -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(${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 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/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/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; 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/equipmentDefinitions.ts b/packages/website/src/core/awaitables/equipmentDefinitions.ts deleted file mode 100644 index 82f2ae8aa..000000000 --- a/packages/website/src/core/awaitables/equipmentDefinitions.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { fetchEquipmentDefinitions } from '@blitzkit/core'; - -export const awaitableEquipmentDefinitions = fetchEquipmentDefinitions(); diff --git a/packages/website/src/core/awaitables/gallery.ts b/packages/website/src/core/awaitables/gallery.ts deleted file mode 100644 index 3571a940d..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/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/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/skillDefinitions.ts b/packages/website/src/core/awaitables/skillDefinitions.ts deleted file mode 100644 index 0b7dde41d..000000000 --- a/packages/website/src/core/awaitables/skillDefinitions.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { fetchSkillDefinitions } from '@blitzkit/core'; - -export const awaitableSkillDefinitions = fetchSkillDefinitions(); 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 d043c9705..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 { 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/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/core/blitzkit/api.ts b/packages/website/src/core/blitzkit/api.ts new file mode 100644 index 000000000..3342bd765 --- /dev/null +++ b/packages/website/src/core/blitzkit/api.ts @@ -0,0 +1,20 @@ +import type { BlitzKitAPI } from "@blitzkit/core/src/blitzkit/api/base"; + +let _api: BlitzKitAPI; + +if (import.meta.env.SSR) { + const { ServerBlitzKitAPI } = await import( + "@blitzkit/core/src/blitzkit/api/server/index" + ); + const { vfs } = await import("./vfs"); + + _api = await new ServerBlitzKitAPI(vfs).init(); +} else { + const { ClientBlitzKitAPI } = await import( + "@blitzkit/core/src/blitzkit/api/client" + ); + + _api = new ClientBlitzKitAPI(); +} + +export const api = _api; 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/core/blitzkit/chunkedStaticPaths.ts b/packages/website/src/core/blitzkit/chunkedStaticPaths.ts new file mode 100644 index 000000000..8ff960d42 --- /dev/null +++ b/packages/website/src/core/blitzkit/chunkedStaticPaths.ts @@ -0,0 +1,18 @@ +import { assertSecret, environmentConfig } from "@blitzkit/core"; +import type { GetStaticPaths } from "astro"; + +export function chunkedStaticPaths(chunk: string) { + return function () { + const environment = environmentConfig(); + const chunksEnv = assertSecret(import.meta.env.CHUNK); + const chunks = chunksEnv.split(","); + + if (chunksEnv !== "*" && !chunks.includes(chunk)) { + return []; + } + + return [ + { params: { [`chunk_${chunk}`]: environment.paths[chunk] ?? undefined } }, + ]; + } satisfies GetStaticPaths; +} 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/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/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/core/blitzkit/hasEquipment.ts b/packages/website/src/core/blitzkit/hasEquipment.ts index 54cbfbcd0..61e2aed5b 100644 --- a/packages/website/src/core/blitzkit/hasEquipment.ts +++ b/packages/website/src/core/blitzkit/hasEquipment.ts @@ -1,7 +1,7 @@ -import type { EquipmentMatrix } from '../../stores/duel'; -import { awaitableEquipmentDefinitions } from '../awaitables/equipmentDefinitions'; +import type { EquipmentMatrix } from "../../stores/duel"; +import { api } from "./api"; -const equipmentDefinitions = await awaitableEquipmentDefinitions; +const equipmentDefinitions = await api.equipmentDefinitions(); export function hasEquipment( id: number, @@ -16,7 +16,7 @@ export function hasEquipment( 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/core/blitzkit/isHalloween.ts b/packages/website/src/core/blitzkit/isHalloween.ts index d08592f25..c05deb5f5 100644 --- a/packages/website/src/core/blitzkit/isHalloween.ts +++ b/packages/website/src/core/blitzkit/isHalloween.ts @@ -1,5 +1,4 @@ export function isHalloween() { const data = new Date(); - return false; return data.getMonth() === 9 && data.getDate() === 31; } 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..19bd8e044 --- /dev/null +++ b/packages/website/src/core/blitzkit/vfs.ts @@ -0,0 +1,4 @@ +import { assertSecret } from "@blitzkit/core"; +import { LocalVFS } from "@blitzkit/core/src/blitzkit/vfs/local"; + +export const vfs = new LocalVFS(assertSecret(import.meta.env.GAME_DIR)); diff --git a/packages/website/src/hooks/useArmor.ts b/packages/website/src/hooks/useArmor.ts index d697bd0ba..94656886b 100644 --- a/packages/website/src/hooks/useArmor.ts +++ b/packages/website/src/hooks/useArmor.ts @@ -1,10 +1,10 @@ -import { asset } from "@blitzkit/core"; +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 = asset(`3d/tanks/armor/${id}.glb`); + const path = alias("api", `/tanks/${id}/collision.glb`); const gltf = useLoader(GLTFLoader, path); useDispose(gltf, path); diff --git a/packages/website/src/hooks/useEccentricity.ts b/packages/website/src/hooks/useEccentricity.ts index 4565e6099..173c60fe3 100644 --- a/packages/website/src/hooks/useEccentricity.ts +++ b/packages/website/src/hooks/useEccentricity.ts @@ -1,15 +1,15 @@ -import { TankDefinition } from '@blitzkit/core'; -import { sumBy } from 'lodash-es'; -import { useMemo } from 'react'; -import { awaitableTankDefinitions } from '../core/awaitables/tankDefinitions'; -import { Duel } from '../stores/duel'; +import { TankDefinition } from "@blitzkit/core"; +import { sumBy } from "lodash-es"; +import { useMemo } from "react"; +import { api } from "../core/blitzkit/api"; +import { Duel } from "../stores/duel"; export enum UseEccentricityMode { Tier, Class, } -const tankDefinitions = await awaitableTankDefinitions; +const tankDefinitions = await api.tankDefinitions(); export function useEccentricity(mode: UseEccentricityMode) { const tank = Duel.use((state) => state.protagonist.tank); diff --git a/packages/website/src/hooks/useEquipment.ts b/packages/website/src/hooks/useEquipment.ts index 4c24f9828..9f5ffd57a 100644 --- a/packages/website/src/hooks/useEquipment.ts +++ b/packages/website/src/hooks/useEquipment.ts @@ -1,17 +1,17 @@ -import { useMemo } from 'react'; -import { awaitableEquipmentDefinitions } from '../core/awaitables/equipmentDefinitions'; -import { Duel } from '../stores/duel'; +import { useMemo } from "react"; +import { api } from "../core/blitzkit/api"; +import { Duel } from "../stores/duel"; -const equipmentDefinitions = await awaitableEquipmentDefinitions; +const equipmentDefinitions = await api.equipmentDefinitions(); export function useEquipment(id: number, antagonist = false) { const member = Duel.use( - (state) => 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/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/hooks/useModel.ts b/packages/website/src/hooks/useModel.ts index c8d532bd5..96db9fd91 100644 --- a/packages/website/src/hooks/useModel.ts +++ b/packages/website/src/hooks/useModel.ts @@ -1,11 +1,11 @@ -import { asset } from "@blitzkit/core"; +import { alias } 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 = alias("api", `/tanks/${id}/model.glb`); const gltf = useLoader(GLTFLoader, path); useDispose(gltf, path); diff --git a/packages/website/src/hooks/useTankVotes.ts b/packages/website/src/hooks/useTankVotes.ts deleted file mode 100644 index 58a8b435e..000000000 --- a/packages/website/src/hooks/useTankVotes.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { - unwrapBlitzkitResponse, - type BlitzkitResponse, - type TankVotes, -} from '@blitzkit/core'; -import { useEffect, useState } from 'react'; -import { App } from '../stores/app'; - -const cache: Record | 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_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]/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..80b6adcf8 --- /dev/null +++ b/packages/website/src/pages/[...chunk_api]/definitions/consumables.pb.ts @@ -0,0 +1,11 @@ +import { ConsumableDefinitions } from "@blitzkit/core"; +import { api } from "../../../core/blitzkit/api"; + +export { getStaticPaths } from "../_index"; + +export async function GET() { + const definitions = await api.consumableDefinitions(); + const bytes = ConsumableDefinitions.encode(definitions).finish(); + + return new Response(bytes); +} diff --git a/packages/website/src/pages/[...chunk_api]/definitions/equipment.pb.ts b/packages/website/src/pages/[...chunk_api]/definitions/equipment.pb.ts new file mode 100644 index 000000000..3691eb377 --- /dev/null +++ b/packages/website/src/pages/[...chunk_api]/definitions/equipment.pb.ts @@ -0,0 +1,11 @@ +import { EquipmentDefinitions } from "@blitzkit/core"; +import { api } from "../../../core/blitzkit/api"; + +export { getStaticPaths } from "../_index"; + +export async function GET() { + const definitions = await api.equipmentDefinitions(); + const bytes = EquipmentDefinitions.encode(definitions).finish(); + + return new Response(bytes); +} 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]/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/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_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/skills.pb.ts b/packages/website/src/pages/[...chunk_api]/definitions/skills.pb.ts new file mode 100644 index 000000000..dd0baddec --- /dev/null +++ b/packages/website/src/pages/[...chunk_api]/definitions/skills.pb.ts @@ -0,0 +1,11 @@ +import { SkillDefinitions } from "@blitzkit/core"; +import { api } from "../../../core/blitzkit/api"; + +export { getStaticPaths } from "../_index"; + +export async function GET() { + const definitions = await api.skillDefinitions(); + const bytes = SkillDefinitions.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); +} 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); +} 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 new file mode 100644 index 000000000..9edbdf433 --- /dev/null +++ b/packages/website/src/pages/[...chunk_api]/flags/scratched/[id].webp.ts @@ -0,0 +1,36 @@ +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); +} diff --git a/packages/scripts/src/buildAssets/glossary.ts b/packages/website/src/pages/[...chunk_api]/gallery/avatars/[name].ts similarity index 60% rename from packages/scripts/src/buildAssets/glossary.ts rename to packages/website/src/pages/[...chunk_api]/gallery/avatars/[name].ts index ed3b83cfd..032e1908d 100644 --- a/packages/scripts/src/buildAssets/glossary.ts +++ b/packages/website/src/pages/[...chunk_api]/gallery/avatars/[name].ts @@ -1,13 +1,13 @@ -import { Avatar, fetchGlossary, Gallery } from "@blitzkit/core"; +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 ProgressBar from "progress"; -import { AssetUploader } from "../core/github/assetUploader"; +import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; +import { getStaticPaths as _getStaticPaths } from "../../_index"; -export async function glossary() { - console.log("Building glossary..."); +export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { + const paths: GetStaticPathsItem[] = []; - using uploader = new AssetUploader("glossary"); const avatars: Record = {}; await Promise.all( @@ -48,42 +48,32 @@ export async function glossary() { }), ); - 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 name = `${key}${avatar.extension}`; - const content = await fetch(url) - .then((response) => response.arrayBuffer()) - .then((buffer) => new Uint8Array(buffer)); - - await uploader.add({ - path: `gallery/avatars/${key}${avatar.extension}`, - content, + paths.push({ + params: { name }, + props: { url }, }); - - bar.tick(); }), ); - await uploader.add({ - path: "definitions/gallery.pb", - content: Gallery.encode(gallery).finish(), - }); + return paths; +}); - await uploader.flush(); +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); } diff --git a/packages/scripts/src/buildAssets/gameModeBanners.ts b/packages/website/src/pages/[...chunk_api]/gamemodes/[id]/banner.webp.ts similarity index 56% rename from packages/scripts/src/buildAssets/gameModeBanners.ts rename to packages/website/src/pages/[...chunk_api]/gamemodes/[id]/banner.webp.ts index df8a6a73d..e0383102d 100644 --- a/packages/scripts/src/buildAssets/gameModeBanners.ts +++ b/packages/website/src/pages/[...chunk_api]/gamemodes/[id]/banner.webp.ts @@ -1,16 +1,17 @@ +import type { SquadBattleTypeStylesYaml } from "@blitzkit/core"; +import type { APIContext, GetStaticPathsItem } from "astro"; import sharp from "sharp"; -import { AssetUploader } from "../core/github/assetUploader"; -import { vfs } from "./constants"; -import { SquadBattleTypeStyles } from "./definitions"; +import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; +import { vfs } from "../../../../core/blitzkit/vfs"; +import { getStaticPaths as _getStaticPaths } from "../../_index"; -export async function gameModeBanners() { - console.log("Building game mode banners..."); +export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { + const paths: GetStaticPathsItem[] = []; - 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 }[] = []; @@ -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); } 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/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); +} 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); +} 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); +} diff --git a/packages/scripts/src/buildAssets/equipmentIcons.ts b/packages/website/src/pages/[...chunk_api]/icons/equipment/[id].webp.ts similarity index 50% rename from packages/scripts/src/buildAssets/equipmentIcons.ts rename to packages/website/src/pages/[...chunk_api]/icons/equipment/[id].webp.ts index aebe2c847..89acb818d 100644 --- a/packages/scripts/src/buildAssets/equipmentIcons.ts +++ b/packages/website/src/pages/[...chunk_api]/icons/equipment/[id].webp.ts @@ -1,7 +1,9 @@ +import type { OptionalDevices } from "@blitzkit/core"; +import type { APIContext, GetStaticPathsItem } from "astro"; import sharp from "sharp"; -import { AssetUploader } from "../core/github/assetUploader"; -import { vfs } from "./constants"; -import { OptionalDevices } from "./definitions"; +import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; +import { vfs } from "../../../../core/blitzkit/vfs"; +import { getStaticPaths as _getStaticPaths } from "../../_index"; interface Mappings { Header: { version: number }; @@ -12,21 +14,17 @@ interface Mappings { }[]; } -export async function equipmentIcons() { +export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { + const paths: GetStaticPathsItem[] = []; + 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]; @@ -49,32 +47,51 @@ export async function equipmentIcons() { 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, + 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); - 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, + paths.push({ + params: { id: optionalDevice.id }, + props: { + sizes, + source: `Data/Gfx/UI/InventoryIcons/Big/OptionalDevices/texture0.packed.webp`, + }, }); } } - await uploader.flush(); + 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); } 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); +} 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); +} 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..917866910 --- /dev/null +++ b/packages/website/src/pages/[...chunk_api]/icons/shells/[id].webp.ts @@ -0,0 +1,59 @@ +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/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) { + 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 }, + }); + } + + console.log("Shell paths", paths); + + 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); +} 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); +} 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/scripts/src/core/blitz/extractArmor.ts b/packages/website/src/pages/[...chunk_api]/tanks/[id]/collision.glb.ts similarity index 72% rename from packages/scripts/src/core/blitz/extractArmor.ts rename to packages/website/src/pages/[...chunk_api]/tanks/[id]/collision.glb.ts index bf504d53a..7bff4a081 100644 --- a/packages/scripts/src/core/blitz/extractArmor.ts +++ b/packages/website/src/pages/[...chunk_api]/tanks/[id]/collision.glb.ts @@ -1,18 +1,49 @@ import { - Hierarchy, Sc2ReadStream, ScgReadStream, + toUniqueId, VertexAttribute, + vertexAttributeVectorSizes, + type Hierarchy, + type VehicleDefinitionList, } from "@blitzkit/core"; -import { Accessor, Document, Node, Scene } from "@gltf-transform/core"; +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 { AbstractVFS } from "../vfs/abstract"; -import { - vertexAttributeGLTFName, - vertexAttributeGltfVectorSizes, -} from "./extractModel/constants"; +import { vfs } from "../../../../core/blitzkit/vfs"; -export async function extractArmor(vfs: AbstractVFS, fileName: string) { +export { getStaticPaths } from "./_index"; + +export async function GET({ props }: APIContext<{ id: number }>) { + 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/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..c2b517a63 --- /dev/null +++ b/packages/website/src/pages/[...chunk_api]/tanks/[id]/icons/big.webp.ts @@ -0,0 +1,84 @@ +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)); + + return new Response(big); + } + } +} + +export async function extractPackedTankIcon(bytes: Uint8Array) { + const texture = sharp(bytes); + const spriteRect = parsePackedSpriteRect(bytes); + + if (!spriteRect) { + 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: ${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}`, + ); + } + + const buffer = await texture.extract({ left, top, width, height }).toBuffer(); + const array = new Uint8Array(buffer); + + return array; +} 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); + } + } +} 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 68% 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..6eae18f85 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( @@ -63,11 +105,8 @@ export 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}` - : undefined, ); material.setBaseColorTexture( document @@ -138,6 +177,19 @@ export 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 @@ -325,3 +377,85 @@ export async function extractModel(vfs: AbstractVFS, path: string) { return document; } + +async function readBaseColor(path: string) { + const raw = await readTexture(path); + const image = await sharp(raw.data, { raw }).webp().toBuffer(); + + return image; +} + +async function readOcclusion(path: string) { + const raw = await readTexture(path); + + const image = await sharp(raw.data, { raw }) + .extractChannel(3) + .webp() + .toBuffer(); + + return image; +} + +async function readRoughnessMetallic(path: string) { + const raw = await readTexture(path); + const blitz = sharp(raw.data, { 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; +} diff --git a/packages/website/src/pages/404.astro b/packages/website/src/pages/[...chunk_default]/404.astro similarity index 76% rename from packages/website/src/pages/404.astro rename to packages/website/src/pages/[...chunk_default]/404.astro index 05529036f..c252cd803 100644 --- a/packages/website/src/pages/404.astro +++ b/packages/website/src/pages/[...chunk_default]/404.astro @@ -1,9 +1,11 @@ --- 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"; + +export { getStaticPaths } from "./_index"; const { locale } = Astro.props; --- diff --git a/packages/website/src/pages/[...locale]/auth/[provider]/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/auth/[provider]/_index.tsx similarity index 96% rename from packages/website/src/pages/[...locale]/auth/[provider]/_index.tsx rename to packages/website/src/pages/[...chunk_default]/[...locale]/auth/[provider]/_index.tsx index c1bb1f1a7..311a80c44 100644 --- a/packages/website/src/pages/[...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/[...locale]/auth/[provider]/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/auth/[provider]/index.astro similarity index 56% rename from packages/website/src/pages/[...locale]/auth/[provider]/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/auth/[provider]/index.astro index b38157251..e74158384 100644 --- a/packages/website/src/pages/[...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 BaseLayout from "../../../../layouts/BaseLayout.astro"; -import { getStaticPaths as parentGetStaticPaths } from "../../index.astro"; +import { PageWrapper } from "../../../../../components/PageWrapper"; +import { mixStaticPaths } from "../../../../../core/blitzkit/mixStaticPaths"; +import BaseLayout from "../../../../../layouts/BaseLayout.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/[...locale]/compare/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/compare/_index.tsx similarity index 77% rename from packages/website/src/pages/[...locale]/compare/_index.tsx rename to packages/website/src/pages/[...chunk_default]/[...locale]/compare/_index.tsx index b658aa085..25a8649fa 100644 --- a/packages/website/src/pages/[...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/[...locale]/compare/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/compare/index.astro similarity index 57% rename from packages/website/src/pages/[...locale]/compare/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/compare/index.astro index fe6f043cb..b61354c93 100644 --- a/packages/website/src/pages/[...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"; diff --git a/packages/website/src/pages/[...locale]/docs/[directory]/[document]/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/docs/[directory]/[document]/_index.tsx similarity index 59% rename from packages/website/src/pages/[...locale]/docs/[directory]/[document]/_index.tsx rename to packages/website/src/pages/[...chunk_default]/[...locale]/docs/[directory]/[document]/_index.tsx index 5c1415000..77ecbaa6c 100644 --- a/packages/website/src/pages/[...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 new file mode 100644 index 000000000..9c00a0faa --- /dev/null +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/docs/[directory]/[document]/index.astro @@ -0,0 +1,51 @@ +--- +import { AstroSeo } from "@astrolib/seo"; +import { readFile } from "fs/promises"; +import { mixStaticPaths } from "../../../../../../core/blitzkit/mixStaticPaths"; +import BaseLayout from "../../../../../../layouts/BaseLayout.astro"; +import { getStaticPaths as _getStaticPaths } from "../../../index.astro"; +import { Page } from "./_index"; + +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 { + params: { directory: couple[0], document: couple[1] }, + props: { content }, + }; + }), + ); +}); + +const { content, locale } = Astro.props; + +const title = content.split("\n")[0].replace("# ", ""); +--- + + + + + + diff --git a/packages/website/src/pages/[...locale]/docs/patches/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/docs/patches/index.astro similarity index 83% rename from packages/website/src/pages/[...locale]/docs/patches/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/docs/patches/index.astro index 5fd4da06b..43631f651 100644 --- a/packages/website/src/pages/[...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) diff --git a/packages/website/src/pages/[...locale]/embed/[embed]/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/embed/[embed]/_index.tsx similarity index 86% rename from packages/website/src/pages/[...locale]/embed/[embed]/_index.tsx rename to packages/website/src/pages/[...chunk_default]/[...locale]/embed/[embed]/_index.tsx index d1f6ca7c7..416514749 100644 --- a/packages/website/src/pages/[...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/[...locale]/embed/[embed]/host/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/embed/[embed]/host/_index.tsx similarity index 82% rename from packages/website/src/pages/[...locale]/embed/[embed]/host/_index.tsx rename to packages/website/src/pages/[...chunk_default]/[...locale]/embed/[embed]/host/_index.tsx index 17691f545..3c8269285 100644 --- a/packages/website/src/pages/[...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; diff --git a/packages/website/src/pages/[...locale]/embed/[embed]/host/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/embed/[embed]/host/index.astro similarity index 79% rename from packages/website/src/pages/[...locale]/embed/[embed]/host/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/embed/[embed]/host/index.astro index 00fb8f464..73f575172 100644 --- a/packages/website/src/pages/[...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"; diff --git a/packages/website/src/pages/[...locale]/embed/[embed]/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/embed/[embed]/index.astro similarity index 62% rename from packages/website/src/pages/[...locale]/embed/[embed]/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/embed/[embed]/index.astro index 85c9e5c26..9bb51e3e7 100644 --- a/packages/website/src/pages/[...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 BaseLayout from "../../../../layouts/BaseLayout.astro"; -import { getStaticPaths as parentGetStaticPaths } from "../index.astro"; +import { embedConfigurations } from "../../../../../constants/embeds"; +import { mixStaticPaths } from "../../../../../core/blitzkit/mixStaticPaths"; +import BaseLayout from "../../../../../layouts/BaseLayout.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/[...locale]/embed/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/embed/_index.tsx similarity index 86% rename from packages/website/src/pages/[...locale]/embed/_index.tsx rename to packages/website/src/pages/[...chunk_default]/[...locale]/embed/_index.tsx index 73f504434..54a307810 100644 --- a/packages/website/src/pages/[...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/[...locale]/embed/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/embed/index.astro similarity index 63% rename from packages/website/src/pages/[...locale]/embed/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/embed/index.astro index 13ff94238..674665781 100644 --- a/packages/website/src/pages/[...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/pages/[...locale]/gallery/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/gallery/_index.tsx similarity index 59% rename from packages/website/src/pages/[...locale]/gallery/_index.tsx rename to packages/website/src/pages/[...chunk_default]/[...locale]/gallery/_index.tsx index e18e63bfc..f8ac2e49a 100644 --- a/packages/website/src/pages/[...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/[...locale]/gallery/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/gallery/index.astro similarity index 74% rename from packages/website/src/pages/[...locale]/gallery/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/gallery/index.astro index b0ccfcfd1..c5e321f5f 100644 --- a/packages/website/src/pages/[...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"; diff --git a/packages/website/src/pages/[...locale]/guess/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/guess/_index.tsx similarity index 79% rename from packages/website/src/pages/[...locale]/guess/_index.tsx rename to packages/website/src/pages/[...chunk_default]/[...locale]/guess/_index.tsx index ac9c88c3b..4cdcb0744 100644 --- a/packages/website/src/pages/[...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/[...locale]/guess/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/guess/index.astro similarity index 62% rename from packages/website/src/pages/[...locale]/guess/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/guess/index.astro index 4e457b286..1816e5479 100644 --- a/packages/website/src/pages/[...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"; diff --git a/packages/website/src/pages/[...locale]/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/index.astro similarity index 71% rename from packages/website/src/pages/[...locale]/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/index.astro index 4495f1d92..1f43e7990 100644 --- a/packages/website/src/pages/[...locale]/index.astro +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/index.astro @@ -4,18 +4,19 @@ 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 { 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 { 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; -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[][] = []; @@ -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; +}); --- { - - { locale !== locales.default && ( @@ -103,7 +88,7 @@ export const getStaticPaths = (() => { { - PUBLIC_ASSET_BRANCH === "preview" && ( + environment === "preview" && ( diff --git a/packages/website/src/pages/[...locale]/mixer/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/mixer/_index.tsx similarity index 88% rename from packages/website/src/pages/[...locale]/mixer/_index.tsx rename to packages/website/src/pages/[...chunk_default]/[...locale]/mixer/_index.tsx index a0d414198..2346fc2d9 100644 --- a/packages/website/src/pages/[...locale]/mixer/_index.tsx +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/mixer/_index.tsx @@ -1,5 +1,5 @@ import { - asset, + alias, GunDefinition, TankDefinition, TIER_ROMAN_NUMERALS, @@ -19,27 +19,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 +64,7 @@ type ModuleButtonProps = Omit & onSelect: ( tank: TankDefinition, turret?: TurretDefinition, - gun?: GunDefinition + gun?: GunDefinition, ) => void; turret?: number; gun?: number; @@ -112,7 +111,7 @@ function ModuleChooser({ height: "4em", objectFit: "contain", }} - src={asset(`icons/tanks/big/${tank}.webp`)} + src={alias("api", `/tanks/${tank}/icons/big.webp`)} /> )} diff --git a/packages/website/src/pages/[...locale]/mixer/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/mixer/index.astro similarity index 62% rename from packages/website/src/pages/[...locale]/mixer/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/mixer/index.astro index 70e118121..b1ea0989f 100644 --- a/packages/website/src/pages/[...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"; diff --git a/packages/website/src/pages/[...locale]/performance/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/performance/_index.tsx similarity index 54% rename from packages/website/src/pages/[...locale]/performance/_index.tsx rename to packages/website/src/pages/[...chunk_default]/[...locale]/performance/_index.tsx index e0ce43b16..78a82af19 100644 --- a/packages/website/src/pages/[...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; diff --git a/packages/website/src/pages/[...locale]/performance/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/performance/index.astro similarity index 66% rename from packages/website/src/pages/[...locale]/performance/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/performance/index.astro index fc34d8b9b..c34e55a48 100644 --- a/packages/website/src/pages/[...locale]/performance/index.astro +++ b/packages/website/src/pages/[...chunk_default]/[...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"; diff --git a/packages/website/src/pages/[...locale]/playlist/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/playlist/_index.tsx similarity index 50% rename from packages/website/src/pages/[...locale]/playlist/_index.tsx rename to packages/website/src/pages/[...chunk_default]/[...locale]/playlist/_index.tsx index 0fe0c6f44..04e090b26 100644 --- a/packages/website/src/pages/[...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/[...locale]/playlist/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/playlist/index.astro similarity index 62% rename from packages/website/src/pages/[...locale]/playlist/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/playlist/index.astro index 5d5989351..ad840e7cc 100644 --- a/packages/website/src/pages/[...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"; diff --git a/packages/website/src/pages/[...locale]/session/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/session/_index.tsx similarity index 96% rename from packages/website/src/pages/[...locale]/session/_index.tsx rename to packages/website/src/pages/[...chunk_default]/[...locale]/session/_index.tsx index 05ca5f1b3..5706a3ace 100644 --- a/packages/website/src/pages/[...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/[...locale]/session/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/session/index.astro similarity index 69% rename from packages/website/src/pages/[...locale]/session/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/session/index.astro index ff8b3df9f..3c20b8923 100644 --- a/packages/website/src/pages/[...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"; diff --git a/packages/website/src/pages/[...locale]/settings/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/settings/_index.tsx similarity index 84% rename from packages/website/src/pages/[...locale]/settings/_index.tsx rename to packages/website/src/pages/[...chunk_default]/[...locale]/settings/_index.tsx index 2279cf3af..c5054b676 100644 --- a/packages/website/src/pages/[...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/[...locale]/settings/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/settings/index.astro similarity index 75% rename from packages/website/src/pages/[...locale]/settings/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/settings/index.astro index 48671bfdb..f82a650b5 100644 --- a/packages/website/src/pages/[...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"; 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 new file mode 100644 index 000000000..4b24fe18e --- /dev/null +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/tanks/[slug]/_index.tsx @@ -0,0 +1,55 @@ +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 { 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"; + +type PageProps = MaybeSkeletonComponentProps & + LocaleAcceptorProps & { + id: number; + guide?: TankGuide; + }; + +const [tankDefinitions, modelDefinitions] = await Promise.all([ + api.tankDefinitions(), + api.modelDefinitions(), +]); + +export function Page({ id, skeleton, locale, guide }: PageProps) { + const tank = tankDefinitions.tanks[id]; + const model = modelDefinitions.models[id]; + + Tankopedia.useInitialization(model); + Duel.useInitialization({ tank, model }); + + return ( + + + + + + {tank.type === TankType.TANK_TYPE_RESEARCHABLE && !tank.deprecated && ( + + )} + + + {guide && } + + + + ); +} diff --git a/packages/website/src/pages/[...locale]/tanks/[slug]/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/tanks/[slug]/index.astro similarity index 70% rename from packages/website/src/pages/[...locale]/tanks/[slug]/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/tanks/[slug]/index.astro index 4933da71d..67727b72b 100644 --- a/packages/website/src/pages/[...locale]/tanks/[slug]/index.astro +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/tanks/[slug]/index.astro @@ -1,50 +1,41 @@ --- import { AstroSeo } from "@astrolib/seo"; import { + alias, 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 { getStaticPaths as parentGetStaticPaths } from "../index.astro"; +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 _getStaticPaths } from "../index.astro"; import { Page } from "./_index"; -export const getStaticPaths = (async () => { - const tankDefinitions = await fetchTankDefinitions(); - const parentPaths = parentGetStaticPaths(); +export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { + const tankDefinitions = await api.tankDefinitions(); - 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); -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 +52,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 = alias("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/pages/[...locale]/tanks/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/tanks/_index.tsx similarity index 56% rename from packages/website/src/pages/[...locale]/tanks/_index.tsx rename to packages/website/src/pages/[...chunk_default]/[...locale]/tanks/_index.tsx index f5b22a310..f99dbf649 100644 --- a/packages/website/src/pages/[...locale]/tanks/_index.tsx +++ b/packages/website/src/pages/[...chunk_default]/[...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/[...locale]/tanks/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/tanks/index.astro similarity index 70% rename from packages/website/src/pages/[...locale]/tanks/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/tanks/index.astro index b4dcbd295..e2d900c65 100644 --- a/packages/website/src/pages/[...locale]/tanks/index.astro +++ b/packages/website/src/pages/[...chunk_default]/[...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"; 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 new file mode 100644 index 000000000..fb136c43b --- /dev/null +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/tier-list/_index.tsx @@ -0,0 +1,30 @@ +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"; + +export function Page({ locale }: LocaleAcceptorProps) { + return ( + + + + ); +} + +function Content() { + return ( + + + + + + + + ); +} diff --git a/packages/website/src/pages/[...locale]/tier-list/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/tier-list/index.astro similarity index 65% rename from packages/website/src/pages/[...locale]/tier-list/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/tier-list/index.astro index cc9598883..cd30d4053 100644 --- a/packages/website/src/pages/[...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"; diff --git a/packages/website/src/pages/[...locale]/tools/compare/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/tools/compare/index.astro similarity index 65% rename from packages/website/src/pages/[...locale]/tools/compare/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/tools/compare/index.astro index cf0884483..9282dd96d 100644 --- a/packages/website/src/pages/[...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 new file mode 100644 index 000000000..e1b439586 --- /dev/null +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/tools/embed/[embed]/index.astro @@ -0,0 +1,17 @@ +--- +import { embedConfigurations } from "../../../../../../constants/embeds"; +import { mixStaticPaths } from "../../../../../../core/blitzkit/mixStaticPaths"; +import LegacyRedirect from "../../../../../../layouts/LegacyRedirect.astro"; +import { getStaticPaths as _getStaticPaths } from "../index.astro"; + +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/[...locale]/tools/embed/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/tools/embed/index.astro similarity index 64% rename from packages/website/src/pages/[...locale]/tools/embed/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/tools/embed/index.astro index 8096fbdd8..17fbfd9cd 100644 --- a/packages/website/src/pages/[...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/[...locale]/tools/gallery/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/tools/gallery/index.astro similarity index 65% rename from packages/website/src/pages/[...locale]/tools/gallery/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/tools/gallery/index.astro index 13a26f486..af5d8d0a2 100644 --- a/packages/website/src/pages/[...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/[...locale]/tools/performance/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/tools/performance/index.astro similarity index 65% rename from packages/website/src/pages/[...locale]/tools/performance/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/tools/performance/index.astro index 37a006ac9..d8ae8c3eb 100644 --- a/packages/website/src/pages/[...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"; diff --git a/packages/website/src/pages/[...locale]/tools/session/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/tools/session/index.astro similarity index 65% rename from packages/website/src/pages/[...locale]/tools/session/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/tools/session/index.astro index f0777caef..279220d1b 100644 --- a/packages/website/src/pages/[...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 new file mode 100644 index 000000000..68ab37b9a --- /dev/null +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/tools/tankopedia/[id]/index.astro @@ -0,0 +1,21 @@ +--- +import { api } from "../../../../../../core/blitzkit/api"; +import { mixStaticPaths } from "../../../../../../core/blitzkit/mixStaticPaths"; +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 }, + })); +}); + +const { id } = Astro.params; +const { locale } = Astro.props; +const tankDefinitions = await api.tankDefinitions(); +const tank = tankDefinitions.tanks[id]; +--- + + diff --git a/packages/website/src/pages/[...locale]/tools/tankopedia/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/tools/tankopedia/index.astro similarity index 64% rename from packages/website/src/pages/[...locale]/tools/tankopedia/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/tools/tankopedia/index.astro index ce837a8b7..d95e9d358 100644 --- a/packages/website/src/pages/[...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/[...locale]/tools/tier-list/index.astro b/packages/website/src/pages/[...chunk_default]/[...locale]/tools/tier-list/index.astro similarity index 65% rename from packages/website/src/pages/[...locale]/tools/tier-list/index.astro rename to packages/website/src/pages/[...chunk_default]/[...locale]/tools/tier-list/index.astro index 32f3c7180..bddc2c516 100644 --- a/packages/website/src/pages/[...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"; diff --git a/packages/website/src/pages/[...chunk_default]/_index.ts b/packages/website/src/pages/[...chunk_default]/_index.ts new file mode 100644 index 000000000..da6da4356 --- /dev/null +++ b/packages/website/src/pages/[...chunk_default]/_index.ts @@ -0,0 +1,3 @@ +import { chunkedStaticPaths } from "../../core/blitzkit/chunkedStaticPaths"; + +export const getStaticPaths = chunkedStaticPaths("default"); diff --git a/packages/website/src/pages/[...locale]/docs/[directory]/[document]/index.astro b/packages/website/src/pages/[...locale]/docs/[directory]/[document]/index.astro deleted file mode 100644 index d3811142b..000000000 --- a/packages/website/src/pages/[...locale]/docs/[directory]/[document]/index.astro +++ /dev/null @@ -1,58 +0,0 @@ ---- -import { AstroSeo } from "@astrolib/seo"; -import type { GetStaticPaths } from "astro"; -import { readFile } from "fs/promises"; -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", { - 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 parentPaths.map((parent) => ({ - params: { - ...parent.params, - directory: couple[0], - document: couple[1], - }, - props: { ...parent.props, content }, - })); - }) - ) - ).flat(); -}) satisfies GetStaticPaths; - -const { content, locale } = Astro.props; - -const title = content.split("\n")[0].replace("# ", ""); ---- - - - - - - diff --git a/packages/website/src/pages/[...locale]/tanks/[slug]/_index.tsx b/packages/website/src/pages/[...locale]/tanks/[slug]/_index.tsx deleted file mode 100644 index 78ed0623c..000000000 --- a/packages/website/src/pages/[...locale]/tanks/[slug]/_index.tsx +++ /dev/null @@ -1,56 +0,0 @@ -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 { - 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"; - -type PageProps = MaybeSkeletonComponentProps & - LocaleAcceptorProps & { - id: number; - guide?: TankGuide; - }; - -const [tankDefinitions, modelDefinitions] = await Promise.all([ - awaitableTankDefinitions, - awaitableModelDefinitions, -]); - -export function Page({ id, skeleton, locale, guide }: PageProps) { - const tank = tankDefinitions.tanks[id]; - const model = modelDefinitions.models[id]; - - Tankopedia.useInitialization(model); - Duel.useInitialization({ tank, model }); - - return ( - - - - - - {tank.type === TankType.TANK_TYPE_RESEARCHABLE && !tank.deprecated && ( - - )} - - - {guide && } - - - - ); -} diff --git a/packages/website/src/pages/[...locale]/tier-list/_index.tsx b/packages/website/src/pages/[...locale]/tier-list/_index.tsx deleted file mode 100644 index 37429bd3c..000000000 --- a/packages/website/src/pages/[...locale]/tier-list/_index.tsx +++ /dev/null @@ -1,30 +0,0 @@ -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"; - -export function Page({ locale }: LocaleAcceptorProps) { - return ( - - - - ); -} - -function Content() { - return ( - - - - - - - - ); -} diff --git a/packages/website/src/pages/[...locale]/tools/embed/[embed]/index.astro b/packages/website/src/pages/[...locale]/tools/embed/[embed]/index.astro deleted file mode 100644 index 4e8240656..000000000 --- a/packages/website/src/pages/[...locale]/tools/embed/[embed]/index.astro +++ /dev/null @@ -1,26 +0,0 @@ ---- -import type { GetStaticPaths } from "astro"; -import { embedConfigurations } from "../../../../../constants/embeds"; -import LegacyRedirect from "../../../../../layouts/LegacyRedirect.astro"; -import { getStaticPaths as parentGetStaticPaths } 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; - -const { embed } = Astro.params; -const { locale } = Astro.props; ---- - - diff --git a/packages/website/src/pages/[...locale]/tools/tankopedia/[id]/index.astro b/packages/website/src/pages/[...locale]/tools/tankopedia/[id]/index.astro deleted file mode 100644 index d5a571f74..000000000 --- a/packages/website/src/pages/[...locale]/tools/tankopedia/[id]/index.astro +++ /dev/null @@ -1,27 +0,0 @@ ---- -import { fetchTankDefinitions } from "@blitzkit/core"; -import type { GetStaticPaths } from "astro"; -import LegacyRedirect from "../../../../../layouts/LegacyRedirect.astro"; -import { getStaticPaths as parentGetStaticPaths } from "../index.astro"; - -export const getStaticPaths = (async () => { - const tankDefinitions = await fetchTankDefinitions(); - 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; - -const { id } = Astro.params; -const { locale } = Astro.props; -const tankDefinitions = await fetchTankDefinitions(); -const tank = tankDefinitions.tanks[id]; ---- - - 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 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"), ); } 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/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 }, 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, 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 -} 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": [] - } - ] -}