mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 10:47:04 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
87786d41a7 | ||
|
|
ee7ffe7bc8 | ||
|
|
379a33375d | ||
|
|
bd3f5dea05 | ||
|
|
146d36234e | ||
|
|
f3edcb8077 | ||
|
|
9e492015ff | ||
|
|
d2977182d9 | ||
|
|
7d2d65bab8 | ||
|
|
415c3bd066 | ||
|
|
990ab1520c | ||
|
|
81cb1afeff | ||
|
|
dba5a0a2cd | ||
|
|
04cd205fee | ||
|
|
ec93d8e9ab | ||
|
|
56321d9e33 | ||
|
|
d39ab43e74 | ||
|
|
787d3ae6ea | ||
|
|
035f751bf1 | ||
|
|
43442ac829 | ||
|
|
da02a089b9 | ||
|
|
476d8e804a | ||
|
|
43e1d53d39 | ||
|
|
5372068830 | ||
|
|
025cec3687 | ||
|
|
c47c629a83 | ||
|
|
2b7907f8f7 | ||
|
|
903d4e4d95 | ||
|
|
8857b2baae | ||
|
|
f7bbb17f66 | ||
|
|
f407ffe4bb | ||
|
|
791a09daae | ||
|
|
9592c0b55f | ||
|
|
d9a34dc60e | ||
|
|
30e4e4e9b3 | ||
|
|
bd3ea9a8ec | ||
|
|
8131b69422 | ||
|
|
340d9c2666 | ||
|
|
2a169fac34 | ||
|
|
4819100ec6 | ||
|
|
f39a94cbe0 | ||
|
|
e61598f768 | ||
|
|
04ee3a0a44 | ||
|
|
8fa613f1e2 | ||
|
|
bdc240df08 | ||
|
|
7169e0d91a | ||
|
|
889ed7e191 | ||
|
|
1fb8dac805 | ||
|
|
9f71ac68b9 | ||
|
|
c1f3517a79 | ||
|
|
adb0af0f18 | ||
|
|
f79f1ffdc3 | ||
|
|
1c9b7463fc | ||
|
|
80741d66fc | ||
|
|
f02fb00e8c | ||
|
|
3d353cc3cf | ||
|
|
c54ceb944e | ||
|
|
37a2ba3566 | ||
|
|
caff7bb6d0 | ||
|
|
01c491aee0 | ||
|
|
fd408d6777 | ||
|
|
6d1ed1e56e | ||
|
|
46f9d605f7 | ||
|
|
a65ea42b81 | ||
|
|
40f8c6a915 | ||
|
|
4678359f36 | ||
|
|
f91bd63646 | ||
|
|
c1872262f5 | ||
|
|
082e112758 | ||
|
|
4cb7d50eb5 | ||
|
|
8c050add23 | ||
|
|
2610697d86 | ||
|
|
64f1de37ad | ||
|
|
58f6862279 | ||
|
|
b47f6fdb32 | ||
|
|
ce5a53d0ff | ||
|
|
6ee1fa13d3 |
No files matched your search
@@ -2,10 +2,9 @@ name: CD Build Assets
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: dev
|
||||
workflow_dispatch:
|
||||
# schedule:
|
||||
# - cron: '0 0 * * *'
|
||||
schedule:
|
||||
- cron: '0 2 * * *'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
@@ -18,6 +17,9 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
- name: Use node
|
||||
uses: actions/setup-node@v4
|
||||
@@ -44,10 +46,19 @@ jobs:
|
||||
run: yarn setup:depotdownloader
|
||||
|
||||
- name: Run DepotDownloader
|
||||
run: ./temp/depotDownloader/DepotDownloader.exe -app 444202 -username ${{ secrets.DEPOT_DOWNLOADER_USERNAME }} -password "${{ secrets.DEPOT_DOWNLOADER_PASSWORD }}"
|
||||
run: ./temp/depotDownloader/DepotDownloader.exe -app 444200 -username ${{ secrets.DEPOT_DOWNLOADER_USERNAME }} -password "${{ secrets.DEPOT_DOWNLOADER_PASSWORD }}"
|
||||
|
||||
- name: Apply patches
|
||||
run: |
|
||||
$env:GH_TOKEN = "${{ secrets.GH_TOKEN }}"
|
||||
$env:DISCORD_TOKEN = "${{ secrets.DISCORD_TOKEN }}"
|
||||
$env:DISCORD_BLITZ_DB = "${{ secrets.DISCORD_BLITZ_DB }}"
|
||||
$env:WOTB_DLC_CDN = "${{ secrets.WOTB_DLC_CDN }}"
|
||||
yarn setup:patches --depot
|
||||
|
||||
- name: Build assets
|
||||
run: |
|
||||
$env:GH_TOKEN = "${{ secrets.GH_TOKEN }}"
|
||||
$env:DISCORD_TOKEN = "${{ secrets.DISCORD_TOKEN }}"
|
||||
$env:DISCORD_BLITZ_DB = "${{ secrets.DISCORD_BLITZ_DB }}"
|
||||
yarn build:assets --all-targets --depot ${{ github.ref == 'refs/heads/main' && '--production' || '' }}
|
||||
@@ -18,10 +18,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Use node
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: 'npm'
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
uses: actions/deploy-pages@v1
|
||||
|
||||
- name: Archive build
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dist
|
||||
path: docs/.vitepress/dist
|
||||
@@ -6,6 +6,8 @@ on:
|
||||
schedule:
|
||||
- cron: '0 */5 * * *'
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [created, published, released, edited]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
@@ -17,7 +19,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Use node
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
|
||||
@@ -14,10 +14,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Use node
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: 'npm'
|
||||
@@ -35,4 +35,4 @@ jobs:
|
||||
run: yarn
|
||||
|
||||
- name: Generate leaderboard
|
||||
run: GH_TOKEN=${{ secrets.GH_TOKEN }} DISCORD_TOKEN=${{ secrets.DISCORD_TOKEN }} yarn publish:ratingsleaderboard --region=asia --target=${{ github.event.schedule == '0 0 * * *' && 'latest' || 'midnight' }}
|
||||
run: GH_TOKEN=${{ secrets.GH_TOKEN }} DISCORD_TOKEN=${{ secrets.DISCORD_TOKEN }} DISCORD_BLITZ_DB=${{ secrets.DISCORD_BLITZ_DB }} yarn publish:ratingsleaderboard --region=asia --target=${{ github.event.schedule == '0 0 * * *' && 'latest' || 'midnight' }}
|
||||
@@ -14,10 +14,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Use node
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: 'npm'
|
||||
@@ -35,4 +35,4 @@ jobs:
|
||||
run: yarn
|
||||
|
||||
- name: Generate leaderboard
|
||||
run: GH_TOKEN=${{ secrets.GH_TOKEN }} DISCORD_TOKEN=${{ secrets.DISCORD_TOKEN }} yarn publish:ratingsleaderboard --region=eu --target=${{ github.event.schedule == '0 0 * * *' && 'latest' || 'midnight' }}
|
||||
run: GH_TOKEN=${{ secrets.GH_TOKEN }} DISCORD_TOKEN=${{ secrets.DISCORD_TOKEN }} DISCORD_BLITZ_DB=${{ secrets.DISCORD_BLITZ_DB }} yarn publish:ratingsleaderboard --region=eu --target=${{ github.event.schedule == '0 0 * * *' && 'latest' || 'midnight' }}
|
||||
@@ -14,10 +14,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Use node
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: 'npm'
|
||||
@@ -35,4 +35,4 @@ jobs:
|
||||
run: yarn
|
||||
|
||||
- name: Generate leaderboard
|
||||
run: GH_TOKEN=${{ secrets.GH_TOKEN }} DISCORD_TOKEN=${{ secrets.DISCORD_TOKEN }} yarn publish:ratingsleaderboard --region=na --target=${{ github.event.schedule == '0 0 * * *' && 'latest' || 'midnight' }}
|
||||
run: GH_TOKEN=${{ secrets.GH_TOKEN }} DISCORD_TOKEN=${{ secrets.DISCORD_TOKEN }} DISCORD_BLITZ_DB=${{ secrets.DISCORD_BLITZ_DB }} yarn publish:ratingsleaderboard --region=na --target=${{ github.event.schedule == '0 0 * * *' && 'latest' || 'midnight' }}
|
||||
@@ -13,10 +13,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Use node
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: 'npm'
|
||||
@@ -33,11 +33,14 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: yarn
|
||||
|
||||
- name: Add fixed sharp
|
||||
run: yarn add [email protected] --ignore-engines
|
||||
|
||||
- name: Build
|
||||
run: yarn build:bot
|
||||
|
||||
- name: Archive build
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: bot
|
||||
path: dist/bot
|
||||
@@ -13,10 +13,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Use node
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: 'npm'
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
run: yarn build:docs
|
||||
|
||||
- name: Archive build
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: docs
|
||||
path: dist/docs
|
||||
@@ -13,10 +13,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Use node
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: 'npm'
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
run: yarn build:website
|
||||
|
||||
- name: Archive build
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: website
|
||||
path: dist/website
|
||||
+2
-1
@@ -17,4 +17,5 @@ tsconfig.tsbuildinfo
|
||||
temp/
|
||||
/.yarn
|
||||
/.VSCodeCounter
|
||||
/.yarn
|
||||
/.yarn
|
||||
depots
|
||||
Vendored
+3
@@ -7,6 +7,7 @@
|
||||
"alphatest",
|
||||
"APAC",
|
||||
"apcr",
|
||||
"applypatches",
|
||||
"artefacts",
|
||||
"atgm",
|
||||
"AYUV",
|
||||
@@ -21,6 +22,7 @@
|
||||
"CDON",
|
||||
"classname",
|
||||
"cleandist",
|
||||
"closedkrieg",
|
||||
"cockroackdb",
|
||||
"Coef",
|
||||
"cooldown",
|
||||
@@ -73,6 +75,7 @@
|
||||
"lods",
|
||||
"metallicness",
|
||||
"minitelrose",
|
||||
"Minotauro",
|
||||
"MIPMAPCOUNT",
|
||||
"modeltransform",
|
||||
"MURICA",
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# Blitzkrieg 1.16.3
|
||||
|
||||
Further polishing and corrections for the Tankopedia.
|
||||
|
||||
## Features
|
||||
|
||||
- Added a few more stats to view for the Tankopedia
|
||||
- Added the count of shells in the magazine for auto-reloaders
|
||||
- The optimal shell index was moved below this stats
|
||||
- Shell reloads for auto-reloaders now abide crew stats
|
||||
- Intra-clip is now also shown for auto-reloaders
|
||||
- Added 1 more decimal place worth of precision when viewing and changing gun pitch and yaw
|
||||
|
||||
## Fixes
|
||||
|
||||
- "Inter-clip" is now correctly spelled as "intra-clip"
|
||||
- Tanks with initial gun pitches (like the Minotauro) are now handled correctly
|
||||
- The stats now reflect the fact that the gun is naturally pitched downwards
|
||||
- The view model now compensates for the natural pitch
|
||||
- Tanks with the improved vertical stabilizer equipment are now handled correctly
|
||||
- The view model now allows you to pitch the gun further as intended when the equipment is on
|
||||
@@ -0,0 +1,19 @@
|
||||
# Blitzkrieg 1.16.4
|
||||
|
||||
A few issues here and there addressed for good measure.
|
||||
|
||||
## Changes
|
||||
|
||||
- Armor layer penetration indices are now much more compact.
|
||||
|
||||
## Fixes
|
||||
|
||||
- Reverted splash mechanics changes from `1.16.1` as it made them less accurate.
|
||||
- Auto-reloaders now report their DPM stats correctly with crew training.
|
||||
|
||||
## Technical Changes
|
||||
|
||||
- All files in the project were reviewed and constant exports were isolated.
|
||||
- The downloaded game in the workflow now also includes the latest patches.
|
||||
- Asset building now uses sharp under the hood which should in theory be faster.
|
||||
- Fetching CDON LZ4 files now uses `no-store` instead of `no-cache`
|
||||
@@ -0,0 +1,13 @@
|
||||
# Blitzkrieg 1.17.0
|
||||
|
||||
A preview command and a touchup.
|
||||
|
||||
## Features
|
||||
|
||||
- Added `/research` in preview only accessible in the official Discord server.
|
||||
- More details will be posted when the command is made public.
|
||||
|
||||
## Changes
|
||||
|
||||
- `/owned-tanks` now returns a simple text message listing all the tanks instead of an image.
|
||||
- This is meant to improve the readability and ease of copying.
|
||||
@@ -0,0 +1,8 @@
|
||||
# Blitzkrieg 1.17.1
|
||||
|
||||
Some stability fixes.
|
||||
|
||||
## Technical Changes
|
||||
|
||||
- `sharp` is now included in the build instead of having being installed before runtime.
|
||||
- Png converter cache is now more memory efficient.
|
||||
+4
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "blitzkrieg",
|
||||
"version": "1.16.2",
|
||||
"version": "1.17.1",
|
||||
"description": "🎉 Tools for everything World of Tanks Blitz",
|
||||
"main": "src/server.ts",
|
||||
"type": "module",
|
||||
@@ -12,13 +12,14 @@
|
||||
"build:assets": "vite-node --max_old_space_size=16384 --script scripts/buildAssets/index",
|
||||
"build:test": "vite-node --script scripts/buildTest",
|
||||
"build:ratingsplot": "vite-node --script scripts/buildRatingsPlot",
|
||||
"dev:bot": "yarn build:bot && yarn start:bot",
|
||||
"dev:bot": "yarn build:bot --dev && yarn start:bot",
|
||||
"dev:docs": "vitepress dev docs",
|
||||
"dev:website": "next dev",
|
||||
"start:bot": "node --enable-source-maps dist/bot/index.cjs",
|
||||
"start:website": "next start",
|
||||
"host:docs": "vitepress preview docs",
|
||||
"publish:ratingsleaderboard": "vite-node --script scripts/buildRatingsLeaderboard --publish",
|
||||
"setup:patches": "vite-node --script scripts/applyPatches",
|
||||
"lint": "npx tsc",
|
||||
"lint:website": "next lint",
|
||||
"format": "prettier --write \"**/*.@(json|js|jsx|css|md|ts|tsx|html|xml|yml|gltf)\"",
|
||||
@@ -34,7 +35,7 @@
|
||||
"@octokit/plugin-throttling": "^8.1.3",
|
||||
"@octokit/rest": "^20.0.1",
|
||||
"@prisma/client": "^5.4.1",
|
||||
"@radix-ui/colors": "^3.0.0-rc.4",
|
||||
"@radix-ui/colors": "^3.0.0",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"@radix-ui/themes": "^2.0.1",
|
||||
"@react-three/drei": "^9.93.0",
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { mkdir, writeFile } from 'fs/promises';
|
||||
import { parse as parsePath } from 'path';
|
||||
import { parse as parseYaml } from 'yaml';
|
||||
import { readStringDVPL } from '../src/core/blitz/readStringDVPL';
|
||||
import { writeDVPL } from '../src/core/blitz/writeDVPL';
|
||||
import { secrets } from '../src/core/blitzkrieg/secrets';
|
||||
import { dvp } from '../submodules/closedkrieg/src/dvp';
|
||||
import { DATA } from './buildAssets/constants';
|
||||
|
||||
const versionTextFile = await readStringDVPL(`${DATA}/version.txt.dvpl`);
|
||||
const currentVersion = versionTextFile
|
||||
.split(' ')[0]
|
||||
.split('.')
|
||||
.slice(0, 3)
|
||||
.join('.');
|
||||
|
||||
console.log(`Installing patches for ${currentVersion}...`);
|
||||
|
||||
let patchIndex = 1;
|
||||
while (true) {
|
||||
const response = await fetch(
|
||||
`${secrets.WOTB_DLC_CDN}/dlc/s${currentVersion}_${patchIndex}.yaml`,
|
||||
);
|
||||
|
||||
if (response.status === 200) {
|
||||
console.log(`Applying patch ${patchIndex}...`);
|
||||
|
||||
const data = parseYaml(await response.text());
|
||||
const dvpm = await fetch(`${secrets.WOTB_DLC_CDN}/dlc/${data.dx11}`).then(
|
||||
(response) => response.arrayBuffer(),
|
||||
);
|
||||
const dvpd = await fetch(
|
||||
`${secrets.WOTB_DLC_CDN}/dlc/${data.dx11.replace('.dvpm', '.dvpd')}`,
|
||||
).then((response) => response.arrayBuffer());
|
||||
const files = await dvp(dvpm, dvpd);
|
||||
|
||||
await Promise.all(
|
||||
files.map(async ({ path, data }) => {
|
||||
const { dir } = parsePath(path);
|
||||
|
||||
await mkdir(`${DATA}/${dir}`, { recursive: true });
|
||||
await writeFile(`${DATA}/${path}.dvpl`, writeDVPL(Buffer.from(data)));
|
||||
}),
|
||||
);
|
||||
|
||||
patchIndex++;
|
||||
} else break;
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
import { build } from 'esbuild';
|
||||
import { cp, readdir } from 'fs/promises';
|
||||
import { argv } from 'process';
|
||||
|
||||
const isDev = argv.includes('--dev');
|
||||
|
||||
readdir('src/workers').then((workers) => {
|
||||
const files = [
|
||||
@@ -25,6 +28,7 @@ readdir('src/workers').then((workers) => {
|
||||
'.js': '.cjs',
|
||||
},
|
||||
|
||||
external: ['sharp'],
|
||||
bundle: true,
|
||||
sourcemap: true,
|
||||
minifyIdentifiers: false, // causes goofy errors
|
||||
@@ -42,3 +46,8 @@ readdir('node_modules/prisma').then((files) =>
|
||||
.filter((file) => file.endsWith('.node'))
|
||||
.map((file) => cp(`node_modules/prisma/${file}`, `dist/bot/${file}`)),
|
||||
);
|
||||
|
||||
// copy sharp as is
|
||||
if (!isDev) {
|
||||
cp('node_modules/sharp', 'dist/bot/node_modules/sharp', { recursive: true });
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
import { readdir } from 'fs/promises';
|
||||
import { argv } from 'process';
|
||||
|
||||
const isDepot = argv.includes('--depot');
|
||||
export const WOTB_WIN32_DEPOT = 444202;
|
||||
export const DATA = isDepot
|
||||
? // ? await (async () => {
|
||||
// const [installationVersion] = await readdir(`depots/${WOTB_WIN32_DEPOT}`);
|
||||
// return `depots/${WOTB_WIN32_DEPOT}/${installationVersion}/Data`;
|
||||
// })()
|
||||
`depots/${WOTB_WIN32_DEPOT}/Data`
|
||||
? await (async () => {
|
||||
const [installationVersion] = await readdir(`depots/${WOTB_WIN32_DEPOT}`);
|
||||
return `depots/${WOTB_WIN32_DEPOT}/${installationVersion}/Data`;
|
||||
})()
|
||||
: 'C:/Program Files (x86)/Steam/steamapps/common/World of Tanks Blitz/Data';
|
||||
|
||||
export const POI = {
|
||||
@@ -24,7 +24,7 @@ export const POI = {
|
||||
collisionMeshes: '3d/Tanks/CollisionMeshes',
|
||||
optionalDevices: 'XML/item_defs/vehicles/common/optional_devices.xml',
|
||||
optionalDeviceSlots:
|
||||
'XML/item_defs/vehicles/common/optional_device_slots.xml',
|
||||
'XML/item_defs/vehicles/common/optional_device_slots.xml',
|
||||
optionalDeviceImageMappings:
|
||||
'UI/Screens3/Lobby/Inventory/OptionalDevices/OptionalDevicesItemImage.style.yaml',
|
||||
optionalDevicesImage:
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import sharp from 'sharp';
|
||||
import { readDVPLFile } from '../../src/core/blitz/readDVPLFile';
|
||||
import { commitAssets } from '../../src/core/blitzkrieg/commitAssets';
|
||||
import { FileChange } from '../../src/core/blitzkrieg/commitMultipleFiles';
|
||||
import { DATA } 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(production: boolean) {
|
||||
console.log('Building currency icons...');
|
||||
|
||||
const changes = await Promise.all(
|
||||
ICONS.map(async ([file, name]) => {
|
||||
const content = (
|
||||
await sharp(
|
||||
await readDVPLFile(`${DATA}/Gfx/Lobby/currency/${file}.dvpl`),
|
||||
)
|
||||
.trim()
|
||||
.toBuffer()
|
||||
).toString('base64');
|
||||
|
||||
return {
|
||||
content,
|
||||
encoding: 'base64',
|
||||
path: `icons/currencies/${name}.webp`,
|
||||
} satisfies FileChange;
|
||||
}),
|
||||
);
|
||||
|
||||
await commitAssets('currency icons', changes, production);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { readdir } from 'fs/promises';
|
||||
import { readdir, writeFile } from 'fs/promises';
|
||||
import { parse as parsePath } from 'path';
|
||||
import { Vector3Tuple } from 'three';
|
||||
import { parse as parseYaml } from 'yaml';
|
||||
@@ -79,6 +79,16 @@ type VehicleDefinitionArmor = Record<
|
||||
string,
|
||||
number | { vehicleDamageFactor: 0; '#text': number }
|
||||
>;
|
||||
interface UnlocksInner {
|
||||
cost: number;
|
||||
'#text': number;
|
||||
}
|
||||
type Unlocks = {
|
||||
[key in 'vehicle' | 'engine' | 'chassis' | 'turret' | 'gun']:
|
||||
| UnlocksInner
|
||||
| UnlocksInner[];
|
||||
};
|
||||
type UnlocksListing = Unlocks | undefined;
|
||||
interface VehicleDefinitions {
|
||||
invisibility: {
|
||||
moving: number;
|
||||
@@ -92,7 +102,7 @@ interface VehicleDefinitions {
|
||||
};
|
||||
consumableSlots: number;
|
||||
provisionSlots: number;
|
||||
optDevicePreset: string;
|
||||
optDevicePreset: string | string[];
|
||||
hull: {
|
||||
armor: VehicleDefinitionArmor;
|
||||
turretPositions: { turret: string };
|
||||
@@ -102,6 +112,7 @@ interface VehicleDefinitions {
|
||||
};
|
||||
chassis: {
|
||||
[key: string]: {
|
||||
unlocks: UnlocksListing;
|
||||
weight: number;
|
||||
terrainResistance: string;
|
||||
rotationSpeed: number;
|
||||
@@ -130,10 +141,13 @@ interface VehicleDefinitions {
|
||||
};
|
||||
};
|
||||
engines: {
|
||||
[key: string]: unknown;
|
||||
[key: string]: {
|
||||
unlocks: UnlocksListing;
|
||||
};
|
||||
};
|
||||
turrets0: {
|
||||
[key: string]: {
|
||||
unlocks: UnlocksListing;
|
||||
rotationSpeed: number;
|
||||
weight: number;
|
||||
circularVisionRadius: number;
|
||||
@@ -149,6 +163,7 @@ interface VehicleDefinitions {
|
||||
};
|
||||
guns: {
|
||||
[key: string]: {
|
||||
unlocks: UnlocksListing;
|
||||
armor: VehicleDefinitionArmor & {
|
||||
gun?:
|
||||
| number
|
||||
@@ -353,6 +368,8 @@ export async function definitions(production: boolean) {
|
||||
`${DATA}/${POI.provisionsCommon}.dvpl`,
|
||||
);
|
||||
|
||||
const tankXps = new Map<number, number>();
|
||||
|
||||
await Promise.all(
|
||||
nations.map(async (nation) => {
|
||||
const tankList = await readXMLDVPL<{ root: VehicleDefinitionList }>(
|
||||
@@ -385,6 +402,10 @@ export async function definitions(production: boolean) {
|
||||
);
|
||||
|
||||
for (const tankKey in tankList.root) {
|
||||
const gunXps = new Map<number, number>();
|
||||
const turretXps = new Map<number, number>();
|
||||
const engineXps = new Map<number, number>();
|
||||
const trackXps = new Map<number, number>();
|
||||
const tank = tankList.root[tankKey];
|
||||
const tankPrice: TankDefinitionPrice =
|
||||
typeof tank.price === 'number'
|
||||
@@ -427,6 +448,7 @@ export async function definitions(production: boolean) {
|
||||
});
|
||||
const crew: Crew[] = [];
|
||||
const fixedCamouflage = tankTags.includes('eventCamouflage_user');
|
||||
const totalUnlocks: UnlocksListing[] = [];
|
||||
|
||||
Object.entries(tankDefinition.root.crew).forEach(([key, value]) => {
|
||||
let entry: Crew;
|
||||
@@ -473,7 +495,8 @@ export async function definitions(production: boolean) {
|
||||
forwards: tankDefinition.root.speedLimits.forward,
|
||||
backwards: tankDefinition.root.speedLimits.backward,
|
||||
},
|
||||
equipment,
|
||||
equipment:
|
||||
typeof equipment === 'string' ? equipment : equipment.at(-1)!,
|
||||
consumables: tankDefinition.root.consumableSlots,
|
||||
provisions: tankDefinition.root.provisionSlots,
|
||||
name:
|
||||
@@ -531,6 +554,7 @@ export async function definitions(production: boolean) {
|
||||
.split(' ')
|
||||
.map(Number) as Vector3Tuple;
|
||||
|
||||
totalUnlocks.push(track.unlocks);
|
||||
tankDefinitions[tankId].tracks.push({
|
||||
id: trackId,
|
||||
weight: track.weight,
|
||||
@@ -557,16 +581,18 @@ export async function definitions(production: boolean) {
|
||||
});
|
||||
|
||||
Object.keys(tankDefinition.root.engines).forEach((engineKey) => {
|
||||
const engine = enginesList.root.shared[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[tankId].engines.push({
|
||||
id: engineId,
|
||||
name: strings[engine.userString],
|
||||
fireChance: engine.fireStartingChance,
|
||||
tier: engine.level as Tier,
|
||||
weight: engine.weight,
|
||||
power: engine.power,
|
||||
name: strings[engineListEntry.userString],
|
||||
fireChance: engineListEntry.fireStartingChance,
|
||||
tier: engineListEntry.level as Tier,
|
||||
weight: engineListEntry.weight,
|
||||
power: engineListEntry.power,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -593,6 +619,7 @@ export async function definitions(production: boolean) {
|
||||
.map(Number) as Vector3Tuple;
|
||||
const turretArmor: ModelArmor = { thickness: {} };
|
||||
|
||||
totalUnlocks.push(turret.unlocks);
|
||||
Object.keys(turret.armor)
|
||||
.filter((name) => name.startsWith('armor_'))
|
||||
.forEach((name) => {
|
||||
@@ -645,11 +672,11 @@ export async function definitions(production: boolean) {
|
||||
};
|
||||
|
||||
Object.keys(turret.guns).forEach((gunKey, gunIndex) => {
|
||||
const turretGunEntry = turret.guns[gunKey];
|
||||
const gun = turret.guns[gunKey];
|
||||
const gunId = toUniqueId(nation, gunList.root.ids[gunKey]);
|
||||
const gunListEntry = gunList.root.shared[gunKey];
|
||||
const pitchLimitsRaw =
|
||||
turretGunEntry.pitchLimits ?? gunListEntry.pitchLimits;
|
||||
gun.pitchLimits ?? gunListEntry.pitchLimits;
|
||||
const gunPitch = (
|
||||
typeof pitchLimitsRaw === 'string'
|
||||
? pitchLimitsRaw
|
||||
@@ -658,43 +685,41 @@ export async function definitions(production: boolean) {
|
||||
.split(' ')
|
||||
.map(Number) as [number, number];
|
||||
const gunModel = Number(
|
||||
parsePath(turretGunEntry.models.undamaged).name.split('_')[1],
|
||||
parsePath(gun.models.undamaged).name.split('_')[1],
|
||||
);
|
||||
const gunName =
|
||||
strings[gunListEntry.userString] ?? gunKey.replaceAll('_', ' ');
|
||||
const gunType =
|
||||
'clip' in turretGunEntry
|
||||
? turretGunEntry.pumpGunMode
|
||||
'clip' in gun
|
||||
? gun.pumpGunMode
|
||||
? 'autoReloader'
|
||||
: 'autoLoader'
|
||||
: 'regular';
|
||||
const gunReload =
|
||||
gunType === 'autoReloader'
|
||||
? turretGunEntry.pumpGunReloadTimes!.split(' ').map(Number)
|
||||
: turretGunEntry.reloadTime;
|
||||
? gun.pumpGunReloadTimes!.split(' ').map(Number)
|
||||
: gun.reloadTime;
|
||||
const gunClipCount =
|
||||
gunType === 'regular' ? undefined : turretGunEntry.clip!.count;
|
||||
const gunInterClip =
|
||||
gunType === 'regular'
|
||||
? undefined
|
||||
: 60 / turretGunEntry.clip!.rate;
|
||||
const front = turretGunEntry.extraPitchLimits?.front
|
||||
? turretGunEntry.extraPitchLimits.front.split(' ').map(Number)
|
||||
gunType === 'regular' ? undefined : gun.clip!.count;
|
||||
const gunIntraClip =
|
||||
gunType === 'regular' ? undefined : 60 / gun.clip!.rate;
|
||||
const front = gun.extraPitchLimits?.front
|
||||
? gun.extraPitchLimits.front.split(' ').map(Number)
|
||||
: undefined;
|
||||
const back = turretGunEntry.extraPitchLimits?.back
|
||||
? turretGunEntry.extraPitchLimits.back.split(' ').map(Number)
|
||||
const back = gun.extraPitchLimits?.back
|
||||
? gun.extraPitchLimits.back.split(' ').map(Number)
|
||||
: undefined;
|
||||
const transition = turretGunEntry.extraPitchLimits?.transition
|
||||
? typeof turretGunEntry.extraPitchLimits.transition === 'number'
|
||||
? turretGunEntry.extraPitchLimits.transition
|
||||
: turretGunEntry.extraPitchLimits.transition.at(-1)!
|
||||
const transition = gun.extraPitchLimits?.transition
|
||||
? typeof gun.extraPitchLimits.transition === 'number'
|
||||
? gun.extraPitchLimits.transition
|
||||
: gun.extraPitchLimits.transition.at(-1)!
|
||||
: undefined;
|
||||
const gunArmor: ModelArmor = { thickness: {} };
|
||||
const shotDispersionFactors =
|
||||
turretGunEntry.shotDispersionFactors ??
|
||||
gunListEntry.shotDispersionFactors;
|
||||
gun.shotDispersionFactors ?? gunListEntry.shotDispersionFactors;
|
||||
|
||||
Object.keys(turretGunEntry.armor)
|
||||
totalUnlocks.push(gun.unlocks);
|
||||
Object.keys(gun.armor)
|
||||
.filter((name) => name.startsWith('armor_'))
|
||||
.forEach((name) => {
|
||||
const armorIdString = name.match(/armor_(\d+)/)?.[1];
|
||||
@@ -702,7 +727,7 @@ export async function definitions(production: boolean) {
|
||||
throw new SyntaxError(`Invalid armor id: ${name}`);
|
||||
}
|
||||
const armorId = parseInt(armorIdString);
|
||||
const armorRaw = turretGunEntry.armor[name];
|
||||
const armorRaw = gun.armor[name];
|
||||
if (typeof armorRaw === 'number') {
|
||||
gunArmor.thickness[armorId] = armorRaw;
|
||||
} else {
|
||||
@@ -722,15 +747,15 @@ export async function definitions(production: boolean) {
|
||||
type: gunType,
|
||||
reload: gunReload,
|
||||
count: gunClipCount,
|
||||
interClip: gunInterClip,
|
||||
intraClip: gunIntraClip,
|
||||
camouflageLoss:
|
||||
typeof turretGunEntry.invisibilityFactorAtShot === 'number'
|
||||
? turretGunEntry.invisibilityFactorAtShot
|
||||
: turretGunEntry.invisibilityFactorAtShot.at(-1)!,
|
||||
aimTime: turretGunEntry.aimingTime ?? gunListEntry.aimingTime,
|
||||
typeof gun.invisibilityFactorAtShot === 'number'
|
||||
? gun.invisibilityFactorAtShot
|
||||
: gun.invisibilityFactorAtShot.at(-1)!,
|
||||
aimTime: gun.aimingTime ?? gunListEntry.aimingTime,
|
||||
dispersion: {
|
||||
base:
|
||||
turretGunEntry.shotDispersionRadius ??
|
||||
gun.shotDispersionRadius ??
|
||||
gunListEntry.shotDispersionRadius,
|
||||
damaged: shotDispersionFactors.whileGunDamaged,
|
||||
shot: shotDispersionFactors.afterShot,
|
||||
@@ -742,11 +767,11 @@ export async function definitions(production: boolean) {
|
||||
armor: gunArmor,
|
||||
model: gunModel,
|
||||
thickness:
|
||||
turretGunEntry.armor.gun === undefined
|
||||
gun.armor.gun === undefined
|
||||
? 0
|
||||
: typeof turretGunEntry.armor.gun === 'number'
|
||||
? turretGunEntry.armor.gun
|
||||
: turretGunEntry.armor.gun['#text'],
|
||||
: typeof gun.armor.gun === 'number'
|
||||
? gun.armor.gun
|
||||
: gun.armor.gun['#text'],
|
||||
pitch: {
|
||||
min: gunPitch[0],
|
||||
max: gunPitch[1],
|
||||
@@ -808,10 +833,100 @@ export async function definitions(production: boolean) {
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
totalUnlocks.forEach((unlocks) => {
|
||||
if (unlocks === undefined) return;
|
||||
|
||||
Object.entries(unlocks).forEach(([key, value]) => {
|
||||
(Array.isArray(value) ? value : [value]).forEach((vehicle) => {
|
||||
switch (key as keyof Unlocks) {
|
||||
case 'vehicle': {
|
||||
const tankListEntry = tankList.root[vehicle['#text']];
|
||||
const currentTank = tankDefinitions[tankId];
|
||||
const successorId = toUniqueId(nation, tankListEntry.id);
|
||||
|
||||
tankXps.set(successorId, 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']]),
|
||||
vehicle.cost,
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'turret': {
|
||||
turretXps.set(
|
||||
toUniqueId(nation, turretList.root.ids[vehicle['#text']]),
|
||||
vehicle.cost,
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'engine': {
|
||||
engineXps.set(
|
||||
toUniqueId(nation, enginesList.root.ids[vehicle['#text']]),
|
||||
vehicle.cost,
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'chassis': {
|
||||
trackXps.set(
|
||||
toUniqueId(nation, chassisList.root.ids[vehicle['#text']]),
|
||||
vehicle.cost,
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Object.values(tankDefinitions[tankId].turrets).forEach((turret) => {
|
||||
turret.xp = turretXps.get(turret.id);
|
||||
|
||||
Object.values(turret.guns).forEach((gun) => {
|
||||
gun.xp = gunXps.get(gun.id);
|
||||
});
|
||||
});
|
||||
|
||||
Object.values(tankDefinitions[tankId].engines).forEach((engine) => {
|
||||
engine.xp = engineXps.get(engine.id);
|
||||
});
|
||||
|
||||
Object.values(tankDefinitions[tankId].tracks).forEach((track) => {
|
||||
track.xp = trackXps.get(track.id);
|
||||
});
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
Object.values(tankDefinitions).forEach((tank) => {
|
||||
tank.xp = tankXps.get(tank.id);
|
||||
});
|
||||
|
||||
Object.values(tankDefinitions).forEach((tank) => {
|
||||
tank.successors?.forEach((predecessorId) => {
|
||||
if (tankDefinitions[predecessorId].ancestors === undefined) {
|
||||
tankDefinitions[predecessorId].ancestors = [];
|
||||
}
|
||||
|
||||
if (!tankDefinitions[predecessorId].ancestors?.includes(tank.id)) {
|
||||
tankDefinitions[predecessorId].ancestors?.push(tank.id);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Object.entries(optionalDevices.root).forEach(
|
||||
([optionalDeviceKey, optionalDeviceEntry]) => {
|
||||
if (optionalDeviceKey === 'nextAvailableId') return;
|
||||
@@ -969,6 +1084,10 @@ export async function definitions(production: boolean) {
|
||||
}
|
||||
});
|
||||
|
||||
writeFile(
|
||||
'test.tanks.cdon.lz4',
|
||||
Buffer.from(superCompress(tankDefinitions), 'base64'),
|
||||
);
|
||||
await commitAssets(
|
||||
'definitions',
|
||||
[
|
||||
|
||||
@@ -14,6 +14,7 @@ import { boosterIcons } from './boosterIcons';
|
||||
import { camouflageIcons } from './camouflageIcons';
|
||||
import { circleFlags } from './circleFlags';
|
||||
import { consumableProvisionIcons } from './consumableProvisionIcons';
|
||||
import { currencies } from './currencies';
|
||||
import { definitions } from './definitions';
|
||||
import { equipmentIcons } from './equipmentIcons';
|
||||
import { moduleIcons } from './moduleIcons';
|
||||
@@ -38,6 +39,7 @@ const methods = [
|
||||
boosterIcons,
|
||||
tankArmors,
|
||||
tankModels,
|
||||
currencies,
|
||||
];
|
||||
|
||||
for (const method of methods) {
|
||||
|
||||
@@ -1,21 +1,15 @@
|
||||
import { NodeIO } from '@gltf-transform/core';
|
||||
import { readdir } from 'fs/promises';
|
||||
import { Vector3Tuple } from 'three';
|
||||
import { extractModel } from '../../src/core/blitz/extractModel';
|
||||
import { readXMLDVPL } from '../../src/core/blitz/readXMLDVPL';
|
||||
import { readYAMLDVPL } from '../../src/core/blitz/readYAMLDVPL';
|
||||
import { toUniqueId } from '../../src/core/blitz/toUniqueId';
|
||||
// import { AssetCommit } from '../../src/core/github/assetCommit';
|
||||
import { commitAssets } from '../../src/core/blitzkrieg/commitAssets';
|
||||
import { FileChange } from '../../src/core/blitzkrieg/commitMultipleFiles';
|
||||
import { DATA, POI } from './constants';
|
||||
import { VehicleDefinitionList } from './definitions';
|
||||
import { TankParameters } from './tankIcons';
|
||||
|
||||
interface VehicleCustomization {
|
||||
armorColor: string;
|
||||
}
|
||||
|
||||
export async function tankModels(production: boolean) {
|
||||
console.log('Building tank models...');
|
||||
|
||||
@@ -27,18 +21,9 @@ export async function tankModels(production: boolean) {
|
||||
for (const nationIndex in nations) {
|
||||
const nation = nations[nationIndex];
|
||||
const changes: FileChange[] = [];
|
||||
// const assetCommit = new AssetCommit(`tank models ${nation}`, production);
|
||||
const tanks = await readXMLDVPL<{ root: VehicleDefinitionList }>(
|
||||
`${DATA}/${POI.vehicleDefinitions}/${nation}/list.xml.dvpl`,
|
||||
);
|
||||
const customization = await readXMLDVPL<{ root: VehicleCustomization }>(
|
||||
`${DATA}/${POI.vehicleDefinitions}/${nation}/customization.xml.dvpl`,
|
||||
);
|
||||
const baseColor = customization.root.armorColor
|
||||
.split(' ')
|
||||
.slice(0, 3)
|
||||
.map(Number)
|
||||
.map((channel) => channel / 255) as Vector3Tuple;
|
||||
|
||||
console.log(`Building models for ${nation}`);
|
||||
|
||||
@@ -56,11 +41,8 @@ export async function tankModels(production: boolean) {
|
||||
const model = await extractModel(
|
||||
DATA,
|
||||
parameters.resourcesPath.blitzModelPath.replace(/\.sc2$/, ''),
|
||||
baseColor,
|
||||
);
|
||||
|
||||
// writeFile(`test.${tankKey}.glb`, await nodeIO.writeBinary(model));
|
||||
|
||||
changes.push({
|
||||
path: `3d/tanks/models/${id}.glb`,
|
||||
content: Buffer.from(await nodeIO.writeBinary(model)).toString(
|
||||
@@ -71,7 +53,6 @@ export async function tankModels(production: boolean) {
|
||||
}),
|
||||
);
|
||||
|
||||
// assetCommit.push();
|
||||
await commitAssets(`tank models ${nation}`, changes, production);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noEmit": true,
|
||||
"resolveJsonModule": true,
|
||||
"jsx": "preserve",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"incremental": true,
|
||||
"isolatedModules": true
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { useEffect, useState } from 'react';
|
||||
import * as Breakdown from '../../../components/Breakdown';
|
||||
import { WARGAMING_APPLICATION_ID } from '../../../constants/wargamingApplicationID';
|
||||
import fetchBlitz from '../../../core/blitz/fetchBlitz';
|
||||
import { resetSession } from '../../../core/blitzkrieg/resetSession';
|
||||
import {
|
||||
TankDefinition,
|
||||
tankDefinitions,
|
||||
@@ -13,10 +14,7 @@ import { tankAverages } from '../../../core/blitzstars/tankAverages';
|
||||
import calculateWN8 from '../../../core/statistics/calculateWN8';
|
||||
import { deltaTankStats } from '../../../core/statistics/deltaTankStats';
|
||||
import getWN8Percentile from '../../../core/statistics/getWN8Percentile';
|
||||
import mutateSession, {
|
||||
resetSession,
|
||||
useSession,
|
||||
} from '../../../stores/session';
|
||||
import mutateSession, { useSession } from '../../../stores/session';
|
||||
import { IndividualTankStats, TanksStats } from '../../../types/tanksStats';
|
||||
import { CustomColumnDisplay } from '../../tools/session/components/CustomColumn';
|
||||
import { Menu } from '../../tools/session/components/Menu';
|
||||
|
||||
@@ -22,7 +22,9 @@ import { UNLOCALIZED_REGION_NAMES } from '../../../constants/regions';
|
||||
import searchPlayersAcrossRegions, {
|
||||
AccountListWithServer,
|
||||
} from '../../../core/blitz/searchPlayersAcrossRegions';
|
||||
import { resetSession, setSession, useSession } from '../../../stores/session';
|
||||
import { resetSession } from '../../../core/blitzkrieg/resetSession';
|
||||
import { setSession } from '../../../core/blitzkrieg/setSession';
|
||||
import { useSession } from '../../../stores/session';
|
||||
import SessionPage from '../../embeds/session/page';
|
||||
import { Menu } from './components/Menu';
|
||||
import * as styles from './page.css';
|
||||
|
||||
@@ -19,7 +19,7 @@ import { pushTankopediaPath } from '../../../../../core/blitzkrieg/pushTankopedi
|
||||
import {
|
||||
SHELL_NAMES,
|
||||
TIER_ROMAN_NUMERALS,
|
||||
} from '../../../../../core/blitzkrieg/tankDefinitions';
|
||||
} from '../../../../../core/blitzkrieg/tankDefinitions/constants';
|
||||
import { mutateDuel, useDuel } from '../../../../../stores/duel';
|
||||
import {
|
||||
mutateTankopediaTemporary,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Flex, Heading } from '@radix-ui/themes';
|
||||
import { ModuleButton } from '../../../../../../../components/ModuleButtons/ModuleButton';
|
||||
import { TIER_ROMAN_NUMERALS } from '../../../../../../../core/blitzkrieg/tankDefinitions';
|
||||
import { TIER_ROMAN_NUMERALS } from '../../../../../../../core/blitzkrieg/tankDefinitions/constants';
|
||||
import { mutateDuel, useDuel } from '../../../../../../../stores/duel';
|
||||
import { mutateTankopediaTemporary } from '../../../../../../../stores/tankopedia';
|
||||
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
|
||||
|
||||
@@ -4,6 +4,7 @@ import { debounce } from 'lodash';
|
||||
import { use, useEffect, useRef, useState } from 'react';
|
||||
import { lerp } from 'three/src/math/MathUtils';
|
||||
import { ShellButton } from '../../../../../../components/ModuleButtons/ShellButton';
|
||||
import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits';
|
||||
import { isExplosive } from '../../../../../../core/blitz/isExplosive';
|
||||
import { resolveNearPenetration } from '../../../../../../core/blitz/resolveNearPenetration';
|
||||
import { coefficient } from '../../../../../../core/blitzkrieg/coefficient';
|
||||
@@ -17,7 +18,7 @@ import {
|
||||
CREW_MEMBER_NAMES,
|
||||
GUN_TYPE_NAMES,
|
||||
SHELL_NAMES,
|
||||
} from '../../../../../../core/blitzkrieg/tankDefinitions';
|
||||
} from '../../../../../../core/blitzkrieg/tankDefinitions/constants';
|
||||
import { unionBoundingBox } from '../../../../../../core/blitzkrieg/unionBoundingBox';
|
||||
import { useConsumable } from '../../../../../../hooks/useConsumable';
|
||||
import { useEquipment } from '../../../../../../hooks/useEquipment';
|
||||
@@ -56,6 +57,7 @@ export function Characteristics() {
|
||||
turretModelDefinition.boundingBox,
|
||||
),
|
||||
);
|
||||
const gunDefaultPitch = tankModelDefinition.turretRotation?.pitch ?? 0;
|
||||
const weight =
|
||||
tank.weight + engine.weight + track.weight + turret.weight + gun.weight;
|
||||
const weightTons = weight / 1000;
|
||||
@@ -113,7 +115,7 @@ export function Characteristics() {
|
||||
// TODO: add max rolls
|
||||
[hasTungsten, 0.15],
|
||||
);
|
||||
const interClipCoefficient = coefficient([hasShellReloadBoost, -0.3]);
|
||||
const intraClipCoefficient = coefficient([hasShellReloadBoost, -0.3]);
|
||||
const shellVelocityCoefficient = coefficient(
|
||||
[hasSupercharger, 0.3],
|
||||
[hasImprovedGunPowder, 0.3],
|
||||
@@ -210,7 +212,7 @@ export function Characteristics() {
|
||||
shell,
|
||||
damageCoefficient,
|
||||
reloadCoefficient,
|
||||
interClipCoefficient,
|
||||
intraClipCoefficient,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -218,6 +220,17 @@ export function Characteristics() {
|
||||
penetrationDistanceInput.current.value = `${penetrationDistance}`;
|
||||
}
|
||||
}, [penetrationDistance]);
|
||||
useEffect(() => {
|
||||
mutateDuel((draft) => {
|
||||
[draft.protagonist!.pitch, draft.protagonist!.yaw] = applyPitchYawLimits(
|
||||
draft.protagonist!.pitch,
|
||||
draft.protagonist!.yaw,
|
||||
gunModelDefinition.pitch,
|
||||
turretModelDefinition.yaw,
|
||||
hasImprovedVerticalStabilizer,
|
||||
);
|
||||
});
|
||||
}, [hasImprovedVerticalStabilizer]);
|
||||
|
||||
return (
|
||||
<Flex direction="column" gap="8" style={{ width: '100%' }}>
|
||||
@@ -255,9 +268,13 @@ export function Characteristics() {
|
||||
<>
|
||||
<InfoWithDelta decimals={0} indent name="Maximum" unit="hp / min">
|
||||
{gun.reload.at(-1)! < gun.reload.at(-2)!
|
||||
? (shell.damage.armor / (gun.reload.at(-1)! + gun.interClip)) *
|
||||
? ((damageCoefficient * shell.damage.armor) /
|
||||
(gun.reload.at(-1)! * reloadCoefficient +
|
||||
gun.intraClip * intraClipCoefficient)) *
|
||||
60
|
||||
: (shell.damage.armor / gun.reload[0]) * 60}
|
||||
: ((damageCoefficient * shell.damage.armor) /
|
||||
(gun.reload[0] * reloadCoefficient)) *
|
||||
60}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta
|
||||
decimals={0}
|
||||
@@ -266,13 +283,26 @@ export function Characteristics() {
|
||||
unit="hp / min"
|
||||
>
|
||||
{gun.reload.at(-1)! < gun.reload.at(-2)!
|
||||
? (shell.damage.armor / (gun.reload.at(-1)! + gun.interClip)) *
|
||||
? ((damageCoefficient * shell.damage.armor) /
|
||||
(gun.reload.at(-1)! * reloadCoefficient +
|
||||
gun.intraClip * intraClipCoefficient)) *
|
||||
(60 -
|
||||
(gun.reload.slice(0, -1).length - 1) * gun.interClip) +
|
||||
shell.damage.armor * gun.reload.slice(0, -1).length
|
||||
: (shell.damage.armor / (gun.reload[0] + gun.interClip)) *
|
||||
(60 - (gun.reload.slice(1).length - 1) * gun.interClip) +
|
||||
shell.damage.armor * gun.reload.slice(1).length}
|
||||
(gun.reload.slice(0, -1).length - 1) *
|
||||
gun.intraClip *
|
||||
intraClipCoefficient) +
|
||||
damageCoefficient *
|
||||
shell.damage.armor *
|
||||
gun.reload.slice(0, -1).length
|
||||
: ((damageCoefficient * shell.damage.armor) /
|
||||
(gun.reload[0] * reloadCoefficient +
|
||||
gun.intraClip * intraClipCoefficient)) *
|
||||
(60 -
|
||||
(gun.reload.slice(1).length - 1) *
|
||||
gun.intraClip *
|
||||
intraClipCoefficient) +
|
||||
damageCoefficient *
|
||||
shell.damage.armor *
|
||||
gun.reload.slice(1).length}
|
||||
</InfoWithDelta>
|
||||
<Info
|
||||
indent
|
||||
@@ -285,14 +315,15 @@ export function Characteristics() {
|
||||
</a>
|
||||
}
|
||||
/>
|
||||
<Info indent name="Optimal shell index">
|
||||
{gun.reload.at(-1)! < gun.reload.at(-2)! ? gun.reload.length : 1}
|
||||
</Info>
|
||||
</>
|
||||
)}
|
||||
{gun.type === 'autoReloader' ? (
|
||||
<>
|
||||
<Info name="Reload" unit="s" />
|
||||
<InfoWithDelta name="Shells">{gun.reload.length}</InfoWithDelta>
|
||||
<Info indent name="Most optimal">
|
||||
{gun.reload.at(-1)! < gun.reload.at(-2)! ? gun.reload.length : 1}
|
||||
</Info>
|
||||
<Info name="Shell reloads" unit="s" />
|
||||
{gun.reload.map((reload, index) => (
|
||||
<InfoWithDelta
|
||||
key={index}
|
||||
@@ -301,7 +332,7 @@ export function Characteristics() {
|
||||
decimals={2}
|
||||
deltaType="lowerIsBetter"
|
||||
>
|
||||
{reload}
|
||||
{reload * reloadCoefficient}
|
||||
</InfoWithDelta>
|
||||
))}
|
||||
</>
|
||||
@@ -315,17 +346,18 @@ export function Characteristics() {
|
||||
{gun.reload * reloadCoefficient}
|
||||
</InfoWithDelta>
|
||||
)}
|
||||
{gun.type === 'autoLoader' && (
|
||||
<InfoWithDelta
|
||||
indent
|
||||
decimals={2}
|
||||
name="Inter-clip reload"
|
||||
unit="s"
|
||||
deltaType="lowerIsBetter"
|
||||
>
|
||||
{gun.interClip * interClipCoefficient}
|
||||
</InfoWithDelta>
|
||||
)}
|
||||
{gun.type === 'autoLoader' ||
|
||||
(gun.type === 'autoReloader' && (
|
||||
<InfoWithDelta
|
||||
indent
|
||||
decimals={2}
|
||||
name="Intra-clip"
|
||||
unit="s"
|
||||
deltaType="lowerIsBetter"
|
||||
>
|
||||
{gun.intraClip * intraClipCoefficient}
|
||||
</InfoWithDelta>
|
||||
))}
|
||||
<InfoWithDelta name="Caliber" decimals={0} unit="mm">
|
||||
{shell.caliber}
|
||||
</InfoWithDelta>
|
||||
@@ -457,10 +489,12 @@ export function Characteristics() {
|
||||
<Info name="Gun flexibility" unit="°" />
|
||||
<InfoWithDelta decimals={1} indent name="Depression">
|
||||
{gunModelDefinition.pitch.max +
|
||||
gunDefaultPitch +
|
||||
(hasImprovedVerticalStabilizer ? 3 : 0)}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta decimals={1} indent name="Elevation">
|
||||
{-gunModelDefinition.pitch.min +
|
||||
{-gunModelDefinition.pitch.min -
|
||||
gunDefaultPitch +
|
||||
(hasImprovedVerticalStabilizer ? 3 : 0)}
|
||||
</InfoWithDelta>
|
||||
{gunModelDefinition.pitch.front && (
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useEffect, useRef } from 'react';
|
||||
import { Vector3 } from 'three';
|
||||
import { OrbitControls as OrbitControlsClass } from 'three-stdlib';
|
||||
import { applyPitchYawLimits } from '../../../../../core/blitz/applyPitchYawLimits';
|
||||
import { hasEquipment } from '../../../../../core/blitzkrieg/hasEquipment';
|
||||
import { modelDefinitions } from '../../../../../core/blitzkrieg/modelDefinitions';
|
||||
import { Pose, poseEvent } from '../../../../../core/blitzkrieg/pose';
|
||||
import { useAwait } from '../../../../../hooks/useAwait';
|
||||
@@ -67,7 +68,9 @@ export function Controls() {
|
||||
},
|
||||
);
|
||||
|
||||
function handlePoseEvent(event: Pose) {
|
||||
async function handlePoseEvent(event: Pose) {
|
||||
const hasImprovedVerticalStabilizer = await hasEquipment(122);
|
||||
|
||||
switch (event) {
|
||||
case Pose.HullDown: {
|
||||
const [pitch] = applyPitchYawLimits(
|
||||
@@ -75,6 +78,7 @@ export function Controls() {
|
||||
0,
|
||||
protagonistGunModelDefinition.pitch,
|
||||
protagonistTurretModelDefinition.yaw,
|
||||
hasImprovedVerticalStabilizer,
|
||||
);
|
||||
|
||||
camera.position
|
||||
@@ -106,6 +110,7 @@ export function Controls() {
|
||||
0,
|
||||
protagonistGunModelDefinition.pitch,
|
||||
protagonistTurretModelDefinition.yaw,
|
||||
hasImprovedVerticalStabilizer,
|
||||
);
|
||||
|
||||
camera.position
|
||||
|
||||
@@ -11,6 +11,7 @@ import { modelDefinitions } from '../../../../../../core/blitzkrieg/modelDefinit
|
||||
import { modelTransformEvent } from '../../../../../../core/blitzkrieg/modelTransform';
|
||||
import { Pose, poseEvent } from '../../../../../../core/blitzkrieg/pose';
|
||||
import { tankIcon } from '../../../../../../core/blitzkrieg/tankIcon';
|
||||
import { useEquipment } from '../../../../../../hooks/useEquipment';
|
||||
import { useWideFormat } from '../../../../../../hooks/useWideFormat';
|
||||
import { mutateDuel, useDuel } from '../../../../../../stores/duel';
|
||||
import mutateTankopediaPersistent, {
|
||||
@@ -29,6 +30,7 @@ import { TankModel } from './components/TankModel';
|
||||
|
||||
export function TankSandbox() {
|
||||
const canvas = useRef<HTMLCanvasElement>(null);
|
||||
const hasImprovedVerticalStabilizer = useEquipment(122);
|
||||
const awaitedModelDefinitions = use(modelDefinitions);
|
||||
const canvasWrapper = useRef<HTMLDivElement>(null);
|
||||
const [isFullScreen, setIsFullScreen] = useState(false);
|
||||
@@ -70,6 +72,7 @@ export function TankSandbox() {
|
||||
0,
|
||||
gunModelDefinition.pitch,
|
||||
turretModelDefinition.yaw,
|
||||
hasImprovedVerticalStabilizer,
|
||||
);
|
||||
|
||||
modelTransformEvent.emit({ pitch, yaw });
|
||||
@@ -87,6 +90,7 @@ export function TankSandbox() {
|
||||
0,
|
||||
gunModelDefinition.pitch,
|
||||
turretModelDefinition.yaw,
|
||||
hasImprovedVerticalStabilizer,
|
||||
);
|
||||
|
||||
modelTransformEvent.emit({ pitch, yaw });
|
||||
@@ -104,6 +108,7 @@ export function TankSandbox() {
|
||||
0,
|
||||
gunModelDefinition.pitch,
|
||||
turretModelDefinition.yaw,
|
||||
hasImprovedVerticalStabilizer,
|
||||
);
|
||||
|
||||
modelTransformEvent.emit({ pitch, yaw });
|
||||
@@ -131,6 +136,7 @@ export function TankSandbox() {
|
||||
draft.protagonist!.yaw,
|
||||
gunModelDefinition.pitch,
|
||||
turretModelDefinition.yaw,
|
||||
hasImprovedVerticalStabilizer,
|
||||
);
|
||||
});
|
||||
}, [protagonist.gun, protagonist.turret]);
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Euler, Group, Vector3 } from 'three';
|
||||
import { degToRad } from 'three/src/math/MathUtils';
|
||||
import { I_HAT, J_HAT, K_HAT } from '../../../../../../../constants/axis';
|
||||
import { applyPitchYawLimits } from '../../../../../../../core/blitz/applyPitchYawLimits';
|
||||
import { hasEquipment } from '../../../../../../../core/blitzkrieg/hasEquipment';
|
||||
import { jsxTree } from '../../../../../../../core/blitzkrieg/jsxTree';
|
||||
import { modelDefinitions } from '../../../../../../../core/blitzkrieg/modelDefinitions';
|
||||
import {
|
||||
@@ -106,7 +107,6 @@ export const TankModel = memo(() => {
|
||||
const turretModelDefinition =
|
||||
tankModelDefinition.turrets[protagonist.turret.id];
|
||||
const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id];
|
||||
|
||||
const { gltf } = useModel(protagonist.tank.id);
|
||||
const nodes = Object.values(gltf.nodes);
|
||||
|
||||
@@ -161,12 +161,14 @@ export const TankModel = memo(() => {
|
||||
window.addEventListener('pointerup', handlePointerUp);
|
||||
}
|
||||
}
|
||||
function handlePointerMove(event: PointerEvent) {
|
||||
async function handlePointerMove(event: PointerEvent) {
|
||||
const hasImprovedVerticalStabilizer = await hasEquipment(122);
|
||||
[pitch, yaw] = applyPitchYawLimits(
|
||||
pitch,
|
||||
yaw + event.movementX * (Math.PI / canvas.width),
|
||||
gunModelDefinition.pitch,
|
||||
turretModelDefinition.yaw,
|
||||
hasImprovedVerticalStabilizer,
|
||||
);
|
||||
modelTransformEvent.emit({ pitch, yaw });
|
||||
}
|
||||
@@ -220,12 +222,14 @@ export const TankModel = memo(() => {
|
||||
window.addEventListener('pointermove', handlePointerMove);
|
||||
window.addEventListener('pointerup', handlePointerUp);
|
||||
}
|
||||
function handlePointerMove(event: PointerEvent) {
|
||||
async function handlePointerMove(event: PointerEvent) {
|
||||
const hasImprovedVerticalStabilizer = await hasEquipment(122);
|
||||
[pitch, yaw] = applyPitchYawLimits(
|
||||
pitch - event.movementY * (Math.PI / canvas.height),
|
||||
yaw + event.movementX * (Math.PI / canvas.width),
|
||||
gunModelDefinition.pitch,
|
||||
turretModelDefinition.yaw,
|
||||
hasImprovedVerticalStabilizer,
|
||||
);
|
||||
modelTransformEvent.emit({ pitch, yaw });
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Flex, TextField } from '@radix-ui/themes';
|
||||
import { use, useEffect, useRef } from 'react';
|
||||
import { degToRad, radToDeg } from 'three/src/math/MathUtils';
|
||||
import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits';
|
||||
import { modelDefinitions } from '../../../../../../core/blitzkrieg/modelDefinitions';
|
||||
import { modelTransformEvent } from '../../../../../../core/blitzkrieg/modelTransform';
|
||||
@@ -20,22 +21,21 @@ export function RotationInputs({ isFullScreen }: RotationInputsProps) {
|
||||
const mode = useTankopediaPersistent((state) => state.mode);
|
||||
const hasCalibratedShells = useEquipment(103, true);
|
||||
const hasEnhancedArmor = useEquipment(110);
|
||||
|
||||
useEffect(() => {
|
||||
turretYawInput.current!.value = `${-Math.round(
|
||||
protagonist.yaw * (180 / Math.PI),
|
||||
)}`;
|
||||
}, [protagonist.yaw]);
|
||||
useEffect(() => {
|
||||
gunPitchInput.current!.value = `${-Math.round(
|
||||
protagonist.pitch * (180 / Math.PI),
|
||||
)}`;
|
||||
}, [protagonist.pitch]);
|
||||
|
||||
const tankModelDefinition = awaitedModelDefinitions[protagonist.tank.id];
|
||||
const turretModelDefinition =
|
||||
tankModelDefinition.turrets[protagonist.turret.id];
|
||||
const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id];
|
||||
const initialGunPitch = tankModelDefinition.turretRotation?.pitch ?? 0;
|
||||
const hasImprovedVerticalStabilizer = useEquipment(122);
|
||||
|
||||
useEffect(() => {
|
||||
turretYawInput.current!.value = radToDeg(protagonist.yaw).toFixed(1);
|
||||
}, [protagonist.yaw]);
|
||||
useEffect(() => {
|
||||
gunPitchInput.current!.value = (
|
||||
-radToDeg(protagonist.pitch) + initialGunPitch
|
||||
).toFixed(1);
|
||||
}, [protagonist.pitch]);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
@@ -68,22 +68,22 @@ export function RotationInputs({ isFullScreen }: RotationInputsProps) {
|
||||
Yaw
|
||||
</TextField.Slot>
|
||||
<TextField.Input
|
||||
defaultValue={Math.round(protagonist.yaw * (180 / Math.PI))}
|
||||
onBlur={() => {
|
||||
const [pitch, yaw] = applyPitchYawLimits(
|
||||
protagonist.pitch,
|
||||
-Number(turretYawInput.current?.value) * (Math.PI / 180),
|
||||
degToRad(Number(turretYawInput.current!.value)),
|
||||
gunModelDefinition.pitch,
|
||||
turretModelDefinition.yaw,
|
||||
hasImprovedVerticalStabilizer,
|
||||
);
|
||||
modelTransformEvent.emit({ pitch, yaw });
|
||||
mutateDuel((state) => {
|
||||
state.protagonist!.pitch = pitch;
|
||||
state.protagonist!.yaw = yaw;
|
||||
});
|
||||
turretYawInput.current!.value = `${Math.round(
|
||||
yaw * (180 / Math.PI),
|
||||
)}`;
|
||||
turretYawInput.current!.value = radToDeg(protagonist.yaw).toFixed(
|
||||
1,
|
||||
);
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter') {
|
||||
@@ -116,22 +116,24 @@ export function RotationInputs({ isFullScreen }: RotationInputsProps) {
|
||||
Pitch
|
||||
</TextField.Slot>
|
||||
<TextField.Input
|
||||
defaultValue={-Math.round(protagonist.pitch * (180 / Math.PI))}
|
||||
onBlur={() => {
|
||||
const [pitch, yaw] = applyPitchYawLimits(
|
||||
-Number(gunPitchInput.current?.value) * (Math.PI / 180),
|
||||
degToRad(
|
||||
-Number(gunPitchInput.current!.value) + initialGunPitch,
|
||||
),
|
||||
protagonist.yaw,
|
||||
gunModelDefinition.pitch,
|
||||
turretModelDefinition.yaw,
|
||||
hasImprovedVerticalStabilizer,
|
||||
);
|
||||
modelTransformEvent.emit({ pitch, yaw });
|
||||
mutateDuel((state) => {
|
||||
state.protagonist!.pitch = pitch;
|
||||
state.protagonist!.yaw = yaw;
|
||||
});
|
||||
gunPitchInput.current!.value = `${Math.round(
|
||||
pitch * (180 / Math.PI),
|
||||
)}`;
|
||||
gunPitchInput.current!.value = (
|
||||
-radToDeg(protagonist.pitch) + initialGunPitch
|
||||
).toFixed(1);
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter') {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { UpdateIcon } from '@radix-ui/react-icons';
|
||||
import { Button, Dialog, Flex, Heading } from '@radix-ui/themes';
|
||||
import { useState } from 'react';
|
||||
import { TREE_TYPE_ICONS } from '../../../../../components/Tanks';
|
||||
import { TREE_TYPE_ICONS } from '../../../../../components/Tanks/components/Item/constants';
|
||||
import { assignDuelMember } from '../../../../../core/blitzkrieg/assignDuelMember';
|
||||
import { pushTankopediaPath } from '../../../../../core/blitzkrieg/pushTankopediaPath';
|
||||
import { useDuel } from '../../../../../stores/duel';
|
||||
|
||||
+2
-4
@@ -1,9 +1,7 @@
|
||||
import { Button, Flex, Text } from '@radix-ui/themes';
|
||||
import { SmallTankIcon } from '../../../../../../components/SmallTankIcon';
|
||||
import {
|
||||
TIER_ROMAN_NUMERALS,
|
||||
TankDefinition,
|
||||
} from '../../../../../../core/blitzkrieg/tankDefinitions';
|
||||
import { TankDefinition } from '../../../../../../core/blitzkrieg/tankDefinitions';
|
||||
import { TIER_ROMAN_NUMERALS } from '../../../../../../core/blitzkrieg/tankDefinitions/constants';
|
||||
|
||||
interface CompactSearchResultRowProps {
|
||||
tanks: TankDefinition[];
|
||||
|
||||
@@ -4,17 +4,18 @@ import { CaretDownIcon } from '@radix-ui/react-icons';
|
||||
import { Button, DropdownMenu, Flex } from '@radix-ui/themes';
|
||||
import { use } from 'react';
|
||||
import { Flag } from '../../../../../../../components/Flag';
|
||||
import {} from '../../../../../../../components/Tanks';
|
||||
import {
|
||||
TANK_CLASSES,
|
||||
TREE_TYPES,
|
||||
TREE_TYPE_ICONS,
|
||||
TREE_TYPE_IMAGES,
|
||||
} from '../../../../../../../components/Tanks';
|
||||
} from '../../../../../../../components/Tanks/components/Item/constants';
|
||||
import { NATIONS } from '../../../../../../../core/blitzkrieg/tankDefinitions';
|
||||
import {
|
||||
NATIONS,
|
||||
TIERS,
|
||||
TIER_ROMAN_NUMERALS,
|
||||
} from '../../../../../../../core/blitzkrieg/tankDefinitions';
|
||||
} from '../../../../../../../core/blitzkrieg/tankDefinitions/constants';
|
||||
import mutateTankopediaPersistent, {
|
||||
TankopediaTestTankDisplay,
|
||||
useTankopediaPersistent,
|
||||
|
||||
@@ -15,7 +15,8 @@ import {
|
||||
} from '@radix-ui/themes';
|
||||
import { go } from 'fuzzysort';
|
||||
import { use, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { TANK_CLASSES, TreeType } from '../../../../../components/Tanks';
|
||||
import { TreeType } from '../../../../../components/Tanks';
|
||||
import { TANK_CLASSES } from '../../../../../components/Tanks/components/Item/constants';
|
||||
import { resolveNearPenetration } from '../../../../../core/blitz/resolveNearPenetration';
|
||||
import { asset } from '../../../../../core/blitzkrieg/asset';
|
||||
import { modelDefinitions } from '../../../../../core/blitzkrieg/modelDefinitions';
|
||||
@@ -24,13 +25,15 @@ import { resolveDpm } from '../../../../../core/blitzkrieg/resolveDpm';
|
||||
import { resolveReload } from '../../../../../core/blitzkrieg/resolveReload';
|
||||
import {
|
||||
NATIONS,
|
||||
TankDefinition,
|
||||
tanksDefinitionsArray,
|
||||
} from '../../../../../core/blitzkrieg/tankDefinitions';
|
||||
import {
|
||||
TANK_ICONS,
|
||||
TANK_ICONS_COLLECTOR,
|
||||
TANK_ICONS_PREMIUM,
|
||||
TIER_ROMAN_NUMERALS,
|
||||
TankDefinition,
|
||||
tanksDefinitionsArray,
|
||||
} from '../../../../../core/blitzkrieg/tankDefinitions';
|
||||
} from '../../../../../core/blitzkrieg/tankDefinitions/constants';
|
||||
import { tankIcon } from '../../../../../core/blitzkrieg/tankIcon';
|
||||
import { unionBoundingBox } from '../../../../../core/blitzkrieg/unionBoundingBox';
|
||||
import { theme } from '../../../../../stitches.config';
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import { getBlitzStarsLinkButton } from '../../core/blitzstars/getBlitzStarsLinkButton';
|
||||
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 { buttonRefresh } from '../../core/discord/buttonRefresh';
|
||||
import commandToURL from '../../core/discord/commandToURL';
|
||||
import { createLocalizedCommand } from '../../core/discord/createLocalizedCommand';
|
||||
import { getCustomPeriodParams } from '../../core/discord/getCustomPeriodParams';
|
||||
import { getFiltersFromButton } from '../../core/discord/getFiltersFromButton';
|
||||
import { getFiltersFromCommand } from '../../core/discord/getFiltersFromCommand';
|
||||
import resolvePeriodFromButton from '../../core/discord/resolvePeriodFromButton';
|
||||
import resolvePeriodFromCommand from '../../core/discord/resolvePeriodFromCommand';
|
||||
import resolvePlayerFromButton from '../../core/discord/resolvePlayerFromButton';
|
||||
import resolvePlayerFromCommand from '../../core/discord/resolvePlayerFromCommand';
|
||||
import { CommandRegistry } from '../../events/interactionCreate';
|
||||
import { renderBreakdown } from './render';
|
||||
|
||||
export const breakdownCommand = new Promise<CommandRegistry>(
|
||||
async (resolve) => {
|
||||
resolve({
|
||||
inProduction: true,
|
||||
inPublic: true,
|
||||
|
||||
command: await addPeriodicFilterOptions(
|
||||
createLocalizedCommand('breakdown'),
|
||||
(option) => option.addStringOption(addUsernameChoices),
|
||||
),
|
||||
|
||||
async handler(interaction) {
|
||||
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,
|
||||
);
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -1,43 +1,25 @@
|
||||
import { Locale } from 'discord.js';
|
||||
import { chunk } from 'lodash';
|
||||
import * as Breakdown from '../components/Breakdown';
|
||||
import CommandWrapper from '../components/CommandWrapper';
|
||||
import NoData from '../components/NoData';
|
||||
import TitleBar from '../components/TitleBar';
|
||||
import { AllStats, getAccountInfo } from '../core/blitz/getAccountInfo';
|
||||
import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo';
|
||||
import getTankStats from '../core/blitz/getTankStats';
|
||||
import { filtersToDescription } from '../core/blitzkrieg/filtersToDescription';
|
||||
import * as Breakdown from '../../components/Breakdown';
|
||||
import CommandWrapper from '../../components/CommandWrapper';
|
||||
import NoData from '../../components/NoData';
|
||||
import TitleBar from '../../components/TitleBar';
|
||||
import { AllStats, getAccountInfo } from '../../core/blitz/getAccountInfo';
|
||||
import { getClanAccountInfo } from '../../core/blitz/getClanAccountInfo';
|
||||
import getTankStats from '../../core/blitz/getTankStats';
|
||||
import { filtersToDescription } from '../../core/blitzkrieg/filtersToDescription';
|
||||
import {
|
||||
TankDefinition,
|
||||
tankDefinitions,
|
||||
} from '../core/blitzkrieg/tankDefinitions';
|
||||
import { getBlitzStarsLinkButton } from '../core/blitzstars/getBlitzStarsLinkButton';
|
||||
import getStatsInPeriod from '../core/blitzstars/getStatsInPeriod';
|
||||
import { tankAverages } from '../core/blitzstars/tankAverages';
|
||||
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 { buttonRefresh } from '../core/discord/buttonRefresh';
|
||||
import commandToURL from '../core/discord/commandToURL';
|
||||
import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
|
||||
import { getCustomPeriodParams } from '../core/discord/getCustomPeriodParams';
|
||||
import { getFiltersFromButton } from '../core/discord/getFiltersFromButton';
|
||||
import { getFiltersFromCommand } from '../core/discord/getFiltersFromCommand';
|
||||
import resolvePeriodFromButton from '../core/discord/resolvePeriodFromButton';
|
||||
import resolvePeriodFromCommand, {
|
||||
ResolvedPeriod,
|
||||
} from '../core/discord/resolvePeriodFromCommand';
|
||||
import resolvePlayerFromButton from '../core/discord/resolvePlayerFromButton';
|
||||
import resolvePlayerFromCommand, {
|
||||
ResolvedPlayer,
|
||||
} from '../core/discord/resolvePlayerFromCommand';
|
||||
import { translator } from '../core/localization/translator';
|
||||
import calculateWN8 from '../core/statistics/calculateWN8';
|
||||
import { StatFilters, filterStats } from '../core/statistics/filterStats';
|
||||
import getWN8Percentile from '../core/statistics/getWN8Percentile';
|
||||
import { CommandRegistry } from '../events/interactionCreate';
|
||||
} from '../../core/blitzkrieg/tankDefinitions';
|
||||
import getStatsInPeriod from '../../core/blitzstars/getStatsInPeriod';
|
||||
import { tankAverages } from '../../core/blitzstars/tankAverages';
|
||||
import { ResolvedPeriod } from '../../core/discord/resolvePeriodFromCommand';
|
||||
import { ResolvedPlayer } from '../../core/discord/resolvePlayerFromCommand';
|
||||
import { translator } from '../../core/localization/translator';
|
||||
import calculateWN8 from '../../core/statistics/calculateWN8';
|
||||
import { StatFilters, filterStats } from '../../core/statistics/filterStats';
|
||||
import getWN8Percentile from '../../core/statistics/getWN8Percentile';
|
||||
|
||||
const ROWS_PER_PAGE = 8;
|
||||
const MAX_PAGES = 9;
|
||||
@@ -296,57 +278,3 @@ export async function renderBreakdown(
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
export const breakdownCommand = new Promise<CommandRegistry>(
|
||||
async (resolve) => {
|
||||
resolve({
|
||||
inProduction: true,
|
||||
inPublic: true,
|
||||
|
||||
command: await addPeriodicFilterOptions(
|
||||
createLocalizedCommand('breakdown'),
|
||||
(option) => option.addStringOption(addUsernameChoices),
|
||||
),
|
||||
|
||||
async handler(interaction) {
|
||||
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,
|
||||
);
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -1,6 +1,7 @@
|
||||
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 { getAccountInfo } from '../core/blitz/getAccountInfo';
|
||||
@@ -93,7 +94,7 @@ async function render(
|
||||
xMaxLabel={new Date(maxTime * 1000).toLocaleDateString(locale)}
|
||||
>
|
||||
{Graph.Line({
|
||||
color: Graph.LineColor.Red,
|
||||
color: LineColor.Red,
|
||||
plot: plot,
|
||||
minY,
|
||||
maxY,
|
||||
|
||||
+31
-78
@@ -1,18 +1,16 @@
|
||||
import CommandWrapper from '../components/CommandWrapper';
|
||||
import NoData from '../components/NoData';
|
||||
import * as Tanks from '../components/Tanks';
|
||||
import TitleBar from '../components/TitleBar';
|
||||
import { chunk } from 'lodash';
|
||||
import markdownEscape from 'markdown-escape';
|
||||
import { TANK_CLASSES } from '../components/Tanks/components/Item/constants';
|
||||
import { getAccountInfo } from '../core/blitz/getAccountInfo';
|
||||
import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo';
|
||||
import getTankStats from '../core/blitz/getTankStats';
|
||||
import { emblemIdToURL } from '../core/blitzkrieg/emblemIdToURL';
|
||||
import {
|
||||
TIER_ROMAN_NUMERALS,
|
||||
NATIONS,
|
||||
TankDefinition,
|
||||
Tier,
|
||||
flags,
|
||||
tankDefinitions,
|
||||
} from '../core/blitzkrieg/tankDefinitions';
|
||||
import { tankIcon } from '../core/blitzkrieg/tankIcon';
|
||||
import { TIER_ROMAN_NUMERALS } from '../core/blitzkrieg/tankDefinitions/constants';
|
||||
import addTierChoices from '../core/discord/addTierChoices';
|
||||
import addUsernameChoices from '../core/discord/addUsernameChoices';
|
||||
import autocompleteUsername from '../core/discord/autocompleteUsername';
|
||||
@@ -21,6 +19,8 @@ import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
|
||||
import { translator } from '../core/localization/translator';
|
||||
import { CommandRegistry } from '../events/interactionCreate';
|
||||
|
||||
const TANKS_PER_MESSAGE = 64;
|
||||
|
||||
export const ownedTanksCommand = new Promise<CommandRegistry>((resolve) => {
|
||||
resolve({
|
||||
inProduction: true,
|
||||
@@ -33,10 +33,9 @@ export const ownedTanksCommand = new Promise<CommandRegistry>((resolve) => {
|
||||
async handler(interaction) {
|
||||
const { translate } = translator(interaction.locale);
|
||||
const tier = Number(interaction.options.getString('tier'));
|
||||
const account = await resolvePlayerFromCommand(interaction);
|
||||
const { id, region: server } = account;
|
||||
const accountInfo = await getAccountInfo(server, id);
|
||||
const tankStats = await getTankStats(server, id, interaction.locale);
|
||||
const { id, region } = await resolvePlayerFromCommand(interaction);
|
||||
const accountInfo = await getAccountInfo(region, id);
|
||||
const tankStats = await getTankStats(region, id, interaction.locale);
|
||||
const filteredTanks = (
|
||||
await Promise.all(
|
||||
tankStats.map(async (tankData) => ({
|
||||
@@ -45,7 +44,6 @@ export const ownedTanksCommand = new Promise<CommandRegistry>((resolve) => {
|
||||
})),
|
||||
)
|
||||
).filter((tank) => tank.tankDefinitions?.tier === tier);
|
||||
const clanAccountInfo = await getClanAccountInfo(server, id, ['clan']);
|
||||
const groupedTanks: Record<string, TankDefinition[]> = {};
|
||||
const nations: string[] = [];
|
||||
|
||||
@@ -58,73 +56,28 @@ export const ownedTanksCommand = new Promise<CommandRegistry>((resolve) => {
|
||||
}
|
||||
});
|
||||
|
||||
nations.sort();
|
||||
const awaitedTankDefinitions = await tankDefinitions;
|
||||
const awaitedNations = await NATIONS;
|
||||
const lines = tankStats
|
||||
.map(({ tank_id }) => awaitedTankDefinitions[tank_id])
|
||||
.filter((tank) => tank.tier === tier)
|
||||
.sort(
|
||||
(a, b) =>
|
||||
TANK_CLASSES.indexOf(a.class) - TANK_CLASSES.indexOf(b.class),
|
||||
)
|
||||
.sort(
|
||||
(a, b) =>
|
||||
awaitedNations.indexOf(a.nation) - awaitedNations.indexOf(b.nation),
|
||||
)
|
||||
.map((tank) => `${flags[tank.nation]} ${markdownEscape(tank.name)}`);
|
||||
|
||||
return (
|
||||
<CommandWrapper>
|
||||
<TitleBar
|
||||
title={accountInfo.nickname}
|
||||
image={
|
||||
clanAccountInfo?.clan
|
||||
? emblemIdToURL(clanAccountInfo.clan.emblem_set_id)
|
||||
: undefined
|
||||
}
|
||||
description={translate('bot.commands.owned_tanks.body.subtitle', [
|
||||
return chunk(lines, TANKS_PER_MESSAGE).map((lines, index) =>
|
||||
index === 0
|
||||
? `${translate('bot.commands.owned_tanks.body.title', [
|
||||
markdownEscape(accountInfo.nickname),
|
||||
TIER_ROMAN_NUMERALS[tier as Tier],
|
||||
])}
|
||||
/>
|
||||
|
||||
{filteredTanks.length === 0 && (
|
||||
<NoData type="tanks_found" locale={interaction.locale} />
|
||||
)}
|
||||
|
||||
{filteredTanks.length > 0 &&
|
||||
(await Promise.all(
|
||||
nations.map(async (nation) => {
|
||||
const tanks = groupedTanks[nation];
|
||||
const leftColumnSize = Math.ceil(tanks.length / 2);
|
||||
const leftColumn = tanks.slice(0, leftColumnSize);
|
||||
const rightColumn = tanks.slice(leftColumnSize);
|
||||
|
||||
return (
|
||||
<Tanks.Root key={nation}>
|
||||
<Tanks.Title>
|
||||
{translate(`common.nations.${nation}`)}
|
||||
</Tanks.Title>
|
||||
|
||||
<Tanks.Row>
|
||||
<Tanks.Column>
|
||||
{await Promise.all(
|
||||
leftColumn.map(async (tank) => (
|
||||
<Tanks.Item
|
||||
key={tank.id}
|
||||
name={tank.name}
|
||||
tankType={tank.class}
|
||||
image={tankIcon(tank.id)}
|
||||
treeType={tank.treeType}
|
||||
/>
|
||||
)),
|
||||
)}
|
||||
</Tanks.Column>
|
||||
<Tanks.Column>
|
||||
{await Promise.all(
|
||||
rightColumn.map(async (tank) => (
|
||||
<Tanks.Item
|
||||
key={tank.id}
|
||||
name={tank.name}
|
||||
tankType={tank.class}
|
||||
image={tankIcon(tank.id)}
|
||||
treeType={tank.treeType}
|
||||
/>
|
||||
)),
|
||||
)}
|
||||
</Tanks.Column>
|
||||
</Tanks.Row>
|
||||
</Tanks.Root>
|
||||
);
|
||||
}),
|
||||
))}
|
||||
</CommandWrapper>
|
||||
])}${lines.join('\n')}`
|
||||
: lines.join('\n'),
|
||||
);
|
||||
},
|
||||
|
||||
|
||||
@@ -27,6 +27,10 @@ export const permissionsCommand = new Promise<CommandRegistry>((resolve) => {
|
||||
interaction.appPermissions?.has('AttachFiles'),
|
||||
t`bot.commands.permissions.body.attach_files`,
|
||||
],
|
||||
[
|
||||
interaction.appPermissions?.has('UseExternalEmojis'),
|
||||
t`bot.commands.permissions.body.external_emojis`,
|
||||
],
|
||||
];
|
||||
const body = permissions
|
||||
.map(([hasPermission, description]) => {
|
||||
|
||||
@@ -0,0 +1,573 @@
|
||||
import { Locale, escapeMarkdown } from 'discord.js';
|
||||
import CommandWrapper from '../components/CommandWrapper';
|
||||
import TitleBar from '../components/TitleBar';
|
||||
import { equipmentPriceMatrix } from '../constants/equipmentPrice';
|
||||
import { getAccountInfo } from '../core/blitz/getAccountInfo';
|
||||
import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo';
|
||||
import getTankStats from '../core/blitz/getTankStats';
|
||||
import resolveTankId from '../core/blitz/resolveTankId';
|
||||
import { asset } from '../core/blitzkrieg/asset';
|
||||
import { buildTechTreeLine } from '../core/blitzkrieg/buildTechTreeLine';
|
||||
import { emblemIdToURL } from '../core/blitzkrieg/emblemIdToURL';
|
||||
import { iconPng } from '../core/blitzkrieg/iconPng';
|
||||
import { resolveAncestry } from '../core/blitzkrieg/resolveAncestry';
|
||||
import { tankDefinitions } from '../core/blitzkrieg/tankDefinitions';
|
||||
import {
|
||||
TANK_ICONS,
|
||||
TIER_ROMAN_NUMERALS,
|
||||
} from '../core/blitzkrieg/tankDefinitions/constants';
|
||||
import { tankIconPng } from '../core/blitzkrieg/tankIconPng';
|
||||
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 { CommandRegistry } from '../events/interactionCreate';
|
||||
import { theme } from '../stitches.config';
|
||||
|
||||
export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
||||
const { t } = translator(Locale.EnglishUS);
|
||||
|
||||
resolve({
|
||||
inProduction: true,
|
||||
inPublic: true,
|
||||
inPreview: true,
|
||||
|
||||
command: createLocalizedCommand('research')
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName(t`bot.commands.research.options.target_tank`)
|
||||
.setNameLocalizations(
|
||||
localizationObject('bot.commands.research.options.target_tank'),
|
||||
)
|
||||
.setDescription(
|
||||
t`bot.commands.research.options.target_tank.description`,
|
||||
)
|
||||
.setDescriptionLocalizations(
|
||||
localizationObject(
|
||||
'bot.commands.research.options.target_tank.description',
|
||||
),
|
||||
)
|
||||
.setAutocomplete(true)
|
||||
.setRequired(true),
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName(t`bot.commands.research.options.starting_tank`)
|
||||
.setNameLocalizations(
|
||||
localizationObject('bot.commands.research.options.starting_tank'),
|
||||
)
|
||||
.setDescription(
|
||||
t`bot.commands.research.options.starting_tank.description`,
|
||||
)
|
||||
.setDescriptionLocalizations(
|
||||
localizationObject(
|
||||
'bot.commands.research.options.starting_tank.description',
|
||||
),
|
||||
)
|
||||
.setAutocomplete(true)
|
||||
.setRequired(false),
|
||||
),
|
||||
|
||||
async handler(interaction) {
|
||||
const { t, translate } = 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[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 translate('bot.commands.research.errors.start_end_equal', [
|
||||
escapeMarkdown(targetTank.name),
|
||||
]);
|
||||
}
|
||||
|
||||
const startingTank = awaitedTankDefinitions[startingTankId];
|
||||
|
||||
if (startingTank.tier > targetTank.tier) {
|
||||
return translate('bot.commands.research.errors.unordered', [
|
||||
escapeMarkdown(startingTank.name),
|
||||
escapeMarkdown(targetTank.name),
|
||||
escapeMarkdown(targetTank.name),
|
||||
]);
|
||||
}
|
||||
|
||||
if (!targetTankAncestry.includes(startingTankId)) {
|
||||
return translate('bot.commands.research.errors.tanks_not_on_line', [
|
||||
escapeMarkdown(targetTank.name),
|
||||
escapeMarkdown(startingTank.name),
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
if (targetTank.treeType !== 'researchable') {
|
||||
return translate('bot.commands.research.errors.non_tech_tree', [
|
||||
escapeMarkdown(targetTank.name),
|
||||
]);
|
||||
}
|
||||
|
||||
if (targetTank.tier === 1) {
|
||||
return translate('bot.commands.research.errors.tier_1', [
|
||||
escapeMarkdown(targetTank.name),
|
||||
]);
|
||||
}
|
||||
|
||||
const tankStats = await getTankStats(region, id, interaction.locale);
|
||||
|
||||
if (tankStats.some(({ tank_id }) => tank_id === targetTankId)) {
|
||||
return translate('bot.commands.research.errors.already_researched', [
|
||||
escapeMarkdown(targetTank.name),
|
||||
]);
|
||||
}
|
||||
|
||||
const foundAncestor = targetTankAncestry.find(
|
||||
(id) =>
|
||||
awaitedTankDefinitions[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 ? emblemIdToURL(clan.emblem_set_id) : undefined;
|
||||
const costs = await Promise.all(
|
||||
line.map(async (id) => {
|
||||
const tank = awaitedTankDefinitions[id];
|
||||
const turretXps = new Map<number, number>();
|
||||
const gunXps = new Map<number, number>();
|
||||
const engineXps = new Map<number, number>();
|
||||
const trackXps = new Map<number, number>();
|
||||
|
||||
tank.turrets.forEach((turret) => {
|
||||
if (turret.xp !== undefined) {
|
||||
turretXps.set(turret.id, turret.xp);
|
||||
}
|
||||
|
||||
turret.guns.forEach((gun) => {
|
||||
if (gun.xp !== undefined) {
|
||||
gunXps.set(gun.id, gun.xp);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
tank.engines.forEach((engine) => {
|
||||
if (engine.xp !== undefined) {
|
||||
engineXps.set(engine.id, engine.xp);
|
||||
}
|
||||
});
|
||||
|
||||
tank.tracks.forEach((track) => {
|
||||
if (track.xp !== undefined) {
|
||||
trackXps.set(track.id, track.xp);
|
||||
}
|
||||
});
|
||||
|
||||
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.xp,
|
||||
upgrades: moduleXp,
|
||||
purchase: tank.price.value,
|
||||
equipment: equipmentPriceMatrix[tank.tier].reduce(
|
||||
(a, b) => a + 3 * b,
|
||||
0,
|
||||
),
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
const image = (
|
||||
<CommandWrapper>
|
||||
<TitleBar
|
||||
title={nickname}
|
||||
image={clanImage}
|
||||
description={`${t`bot.commands.research.body.subtitle`} • ${targetTank.name}`}
|
||||
/>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 8,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
borderRadius: 4,
|
||||
backgroundColor: theme.colors.appBackground2,
|
||||
border: theme.borderStyles.subtle,
|
||||
}}
|
||||
>
|
||||
<div style={{ width: 48 }}></div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
position: 'relative',
|
||||
right: 24,
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
color: theme.colors.textHighContrast,
|
||||
}}
|
||||
>
|
||||
{t`bot.commands.research.body.total`}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
gap: 8,
|
||||
padding: '16px 0',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
gap: 4,
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={await iconPng(asset('icons/currencies/xp.webp'))}
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
<span
|
||||
style={{
|
||||
fontSize: 16,
|
||||
color: theme.colors.textHighContrast,
|
||||
}}
|
||||
>
|
||||
Experience:{' '}
|
||||
{costs
|
||||
.reduce((a, b) => a + (b.research ?? 0) + b.upgrades, 0)
|
||||
.toLocaleString(interaction.locale)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
gap: 4,
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={await iconPng(asset('icons/currencies/silver.webp'))}
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
<span
|
||||
style={{
|
||||
fontSize: 16,
|
||||
color: theme.colors.textHighContrast,
|
||||
}}
|
||||
>
|
||||
Purchase:{' '}
|
||||
{costs
|
||||
.reduce((a, b) => a + b.purchase, 0)
|
||||
.toLocaleString(interaction.locale)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
gap: 4,
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={await iconPng(asset('icons/currencies/silver.webp'))}
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
<span
|
||||
style={{
|
||||
fontSize: 16,
|
||||
color: theme.colors.textHighContrast,
|
||||
}}
|
||||
>
|
||||
Equipment:{' '}
|
||||
{costs
|
||||
.reduce((a, b) => a + b.equipment, 0)
|
||||
.toLocaleString(interaction.locale)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{await Promise.all(
|
||||
line.map(async (id, index) => {
|
||||
const tank = awaitedTankDefinitions[id];
|
||||
|
||||
return (
|
||||
<div
|
||||
key={id}
|
||||
style={{
|
||||
display: 'flex',
|
||||
borderRadius: 4,
|
||||
backgroundColor: theme.colors.appBackground2,
|
||||
border: theme.borderStyles.subtle,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: 48,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
color: theme.colors.textLowContrast,
|
||||
fontSize: 16,
|
||||
}}
|
||||
>
|
||||
{TIER_ROMAN_NUMERALS[tank.tier]}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
position: 'relative',
|
||||
right: '5%',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
height: 120,
|
||||
width: 240,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-start',
|
||||
left: '25%',
|
||||
top: '5%',
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={await tankIconPng(id)}
|
||||
style={{
|
||||
height: '100%',
|
||||
position: 'absolute',
|
||||
objectFit: 'contain',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
gap: 4,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
position: 'absolute',
|
||||
bottom: 8,
|
||||
left: '50%',
|
||||
transform: 'translateX(-50%)',
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={TANK_ICONS[tank.class]}
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
|
||||
<span
|
||||
style={{
|
||||
color: theme.colors.textHighContrast,
|
||||
}}
|
||||
>
|
||||
{tank.name}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
gap: 8,
|
||||
padding: '16px 0',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
gap: 4,
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={await iconPng(asset('icons/currencies/xp.webp'))}
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
<span
|
||||
style={{
|
||||
fontSize: 16,
|
||||
color: theme.colors.textHighContrast,
|
||||
}}
|
||||
>
|
||||
{translate('bot.commands.research.body.research', [
|
||||
`${costs[index].research?.toLocaleString(
|
||||
interaction.locale,
|
||||
)}`,
|
||||
])}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{costs[index].upgrades > 0 && (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
gap: 4,
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={await iconPng(
|
||||
asset('icons/currencies/free-xp.webp'),
|
||||
)}
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
<span
|
||||
style={{
|
||||
fontSize: 16,
|
||||
color: theme.colors.textHighContrast,
|
||||
}}
|
||||
>
|
||||
{translate('bot.commands.research.body.upgrades', [
|
||||
costs[index].upgrades.toLocaleString(),
|
||||
])}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
gap: 4,
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={await iconPng(
|
||||
asset('icons/currencies/silver.webp'),
|
||||
)}
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
<span
|
||||
style={{
|
||||
fontSize: 16,
|
||||
color: theme.colors.textHighContrast,
|
||||
}}
|
||||
>
|
||||
{translate('bot.commands.research.body.purchase', [
|
||||
costs[index].purchase.toLocaleString(
|
||||
interaction.locale,
|
||||
),
|
||||
])}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
gap: 4,
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={await iconPng(
|
||||
asset('icons/currencies/silver.webp'),
|
||||
)}
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
<span
|
||||
style={{
|
||||
fontSize: 16,
|
||||
color: theme.colors.textHighContrast,
|
||||
}}
|
||||
>
|
||||
{translate('bot.commands.research.body.equipment', [
|
||||
costs[index].equipment.toLocaleString(
|
||||
interaction.locale,
|
||||
),
|
||||
])}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}),
|
||||
)}
|
||||
</div>
|
||||
</CommandWrapper>
|
||||
);
|
||||
|
||||
return startingTankRaw
|
||||
? image
|
||||
: [t`bot.commands.research.body.estimation`, image];
|
||||
},
|
||||
|
||||
autocomplete(interaction) {
|
||||
autocompleteTanks(interaction, true, ['target-tank', 'starting-tank']);
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -3,10 +3,8 @@ import markdownEscape from 'markdown-escape';
|
||||
import { Region } from '../constants/regions';
|
||||
import { WARGAMING_APPLICATION_ID } from '../constants/wargamingApplicationID';
|
||||
import fetchBlitz from '../core/blitz/fetchBlitz';
|
||||
import {
|
||||
AccountList,
|
||||
usernamePattern,
|
||||
} from '../core/blitz/searchPlayersAcrossRegions';
|
||||
import { AccountList } from '../core/blitz/searchPlayersAcrossRegions';
|
||||
import { usernamePattern } from '../core/blitz/searchPlayersAcrossRegions/constants';
|
||||
import addRegionChoices from '../core/discord/addRegionChoices';
|
||||
import addUsernameChoices from '../core/discord/addUsernameChoices';
|
||||
import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
|
||||
|
||||
@@ -15,7 +15,7 @@ import resolvePeriodFromCommand from '../core/discord/resolvePeriodFromCommand';
|
||||
import resolvePlayerFromButton from '../core/discord/resolvePlayerFromButton';
|
||||
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
|
||||
import { CommandRegistry } from '../events/interactionCreate';
|
||||
import { renderBreakdown } from './breakdown';
|
||||
import { renderBreakdown } from './breakdown/render';
|
||||
|
||||
export const todayCommand = new Promise<CommandRegistry>(async (resolve) => {
|
||||
const nations = await NATIONS;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Percentile } from '../../../../constants/percentiles';
|
||||
import { translator } from '../../../../core/localization/translator';
|
||||
import getWN8Percentile from '../../../../core/statistics/getWN8Percentile';
|
||||
import { theme } from '../../../../stitches.config';
|
||||
import { PERCENTILE_COLORS } from '../../../PercentileIndicator';
|
||||
import { PERCENTILE_COLORS } from '../../../PercentileIndicator/constants';
|
||||
import { Glow } from './components/Glow';
|
||||
|
||||
export interface WN8DisplayProps {
|
||||
|
||||
@@ -34,7 +34,7 @@ export function WN8SurroundingStat({
|
||||
style={{
|
||||
color: theme.colors.textHighContrast,
|
||||
fontSize: 24,
|
||||
fontWeight: 'bold',
|
||||
fontWeight: 700,
|
||||
}}
|
||||
>
|
||||
{value}
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
import { useFrame } from '@react-three/fiber';
|
||||
import { useEffect } from 'react';
|
||||
import {
|
||||
MeshBasicMaterial,
|
||||
Object3D,
|
||||
ShaderMaterial,
|
||||
Vector2,
|
||||
WebGLRenderTarget,
|
||||
} from 'three';
|
||||
import { MeshBasicMaterial, Object3D, ShaderMaterial, Vector2 } from 'three';
|
||||
import { degToRad } from 'three/src/math/MathUtils';
|
||||
import { canSplash } from '../../../../core/blitz/canSplash';
|
||||
import { isExplosive } from '../../../../core/blitz/isExplosive';
|
||||
@@ -21,8 +15,7 @@ import {
|
||||
} from '../../../../stores/tankopedia';
|
||||
import fragmentShader from './shaders/fragment.glsl';
|
||||
import vertexShader from './shaders/vertex.glsl';
|
||||
|
||||
export const spacedArmorRenderTarget = new WebGLRenderTarget();
|
||||
import { spacedArmorRenderTarget } from './target';
|
||||
|
||||
interface CoreArmorSceneComponentProps {
|
||||
thickness: number;
|
||||
|
||||
@@ -53,7 +53,7 @@ void main() {
|
||||
float spacedArmorThickness = spacedArmorBufferFragment.r * penetration;
|
||||
remainingPenetration -= spacedArmorThickness;
|
||||
|
||||
if (isExplosive && !canSplash && remainingPenetration > 0.0) {
|
||||
if (isExplosive && remainingPenetration > 0.0) {
|
||||
// there is a 50% penetration loss per meter for HEAT shells
|
||||
remainingPenetration -= 0.5 * remainingPenetration * distanceFromSpacedArmor;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import { WebGLRenderTarget } from 'three';
|
||||
|
||||
export const spacedArmorRenderTarget = new WebGLRenderTarget();
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Card, Flex, Inset, Text } from '@radix-ui/themes';
|
||||
import { Card, Flex, Text } from '@radix-ui/themes';
|
||||
import { Html } from '@react-three/drei';
|
||||
import { useFrame } from '@react-three/fiber';
|
||||
import { useEffect, useRef } from 'react';
|
||||
@@ -182,33 +182,38 @@ export function ShotDisplay() {
|
||||
pointerEvents: 'none',
|
||||
}}
|
||||
>
|
||||
<Card
|
||||
<div
|
||||
style={{
|
||||
backgroundColor:
|
||||
trueStatus === 'penetration'
|
||||
? '#00ff00c0'
|
||||
? '#00ff0080'
|
||||
: trueStatus === 'blocked'
|
||||
? '#ff0000c0'
|
||||
: '#ffff00c0',
|
||||
? '#ff000080'
|
||||
: '#ffff0080',
|
||||
borderRadius: 8,
|
||||
border: 'none',
|
||||
padding: 4,
|
||||
width: hasMultipleLayers ? undefined : 8,
|
||||
height: hasMultipleLayers ? undefined : 8,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<Inset>
|
||||
{hasMultipleLayers && (
|
||||
<Text
|
||||
style={{
|
||||
width: '100%',
|
||||
display: 'block',
|
||||
textAlign: 'center',
|
||||
color:
|
||||
trueStatus === 'ricochet' ? 'black' : undefined,
|
||||
}}
|
||||
>
|
||||
<b>{layer.index + 1}</b>
|
||||
</Text>
|
||||
)}
|
||||
</Inset>
|
||||
</Card>
|
||||
{hasMultipleLayers && (
|
||||
<Text
|
||||
size="2"
|
||||
style={{
|
||||
width: '100%',
|
||||
display: 'block',
|
||||
textAlign: 'center',
|
||||
color: trueStatus === 'ricochet' ? 'black' : undefined,
|
||||
}}
|
||||
>
|
||||
<b>{layer.index + 1}</b>
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
</Html>
|
||||
</group>
|
||||
</>
|
||||
|
||||
@@ -170,7 +170,7 @@ export function SpacedArmorSceneComponent({
|
||||
const distance = (
|
||||
shot.layers.at(-1) as ShotLayerBase
|
||||
).point.distanceTo(intersections[0].point);
|
||||
if (explosive && !splashing)
|
||||
if (explosive)
|
||||
remainingPenetration -= 0.5 * remainingPenetration * distance;
|
||||
const blocked = remainingPenetration < 0;
|
||||
|
||||
@@ -196,7 +196,7 @@ export function SpacedArmorSceneComponent({
|
||||
const previousIntersection = intersections[layerIndex - 1];
|
||||
const distance =
|
||||
intersection.distance - previousIntersection.distance;
|
||||
if (explosive && !splashing)
|
||||
if (explosive)
|
||||
remainingPenetration -=
|
||||
0.5 * remainingPenetration * distance;
|
||||
const wasted = remainingPenetration < 0;
|
||||
|
||||
@@ -2,7 +2,7 @@ import { createPortal, useFrame } from '@react-three/fiber';
|
||||
import { memo, useState } from 'react';
|
||||
import { DepthTexture, Scene, Vector2 } from 'three';
|
||||
import { CoreArmorScene } from './components/CoreArmorScene';
|
||||
import { spacedArmorRenderTarget } from './components/CoreArmorSceneComponent';
|
||||
import { spacedArmorRenderTarget } from './components/CoreArmorSceneComponent/target';
|
||||
import { SpacedArmorScene } from './components/SpacedArmorScene';
|
||||
|
||||
export const Armor = memo(() => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
export interface RootProps {
|
||||
interface RootProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,8 @@ import {
|
||||
GrayColor,
|
||||
PALETTES,
|
||||
} from '../../../constants/radixColors';
|
||||
import { TREE_TYPE_ICONS, TankClass, TreeType } from '../../Tanks';
|
||||
import { TankClass, TreeType } from '../../Tanks';
|
||||
import { TREE_TYPE_ICONS } from '../../Tanks/components/Item/constants';
|
||||
import { RowStat } from './RowStat';
|
||||
|
||||
export interface RowStatItem {
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from '../../../constants/radixColors';
|
||||
import PercentileIndicator from '../../PercentileIndicator';
|
||||
|
||||
export interface RowStatProps {
|
||||
interface RowStatProps {
|
||||
name: string;
|
||||
value?: string | number;
|
||||
delta?: number;
|
||||
@@ -63,7 +63,7 @@ export function RowStat({
|
||||
<span
|
||||
style={{
|
||||
color: theme.textHighContrast,
|
||||
fontWeight: 'bold',
|
||||
fontWeight: 700,
|
||||
fontSize: 16,
|
||||
textAlign: 'center',
|
||||
}}
|
||||
|
||||
@@ -16,7 +16,7 @@ export default function CommandWrapper({ children, fat }: CommandWrapperProps) {
|
||||
gap: 16,
|
||||
width: 480,
|
||||
color: theme.colors.textHighContrast,
|
||||
background: 'url(https://i.imgur.com/PhS06NJ.png)',
|
||||
background: theme.colors.appBackground1,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -2,13 +2,13 @@ import { Percentile } from '../../../constants/percentiles';
|
||||
import { theme } from '../../../stitches.config';
|
||||
import PercentileIndicator from '../../PercentileIndicator';
|
||||
|
||||
export interface CardItem {
|
||||
interface CardItem {
|
||||
label: string;
|
||||
value: string | number | undefined;
|
||||
percentile?: Percentile;
|
||||
}
|
||||
|
||||
export interface CardProps {
|
||||
interface CardProps {
|
||||
items: CardItem[];
|
||||
title: string;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export interface RootProps {
|
||||
interface RootProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export interface RowProps {
|
||||
interface RowProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Card } from './components/Card';
|
||||
import { Root } from './components/Root';
|
||||
import { Row } from './components/Row';
|
||||
|
||||
export interface GenericAllStatsProps {
|
||||
interface GenericAllStatsProps {
|
||||
stats: AllStats;
|
||||
supplementaryStats?: SupplementaryStats;
|
||||
locale: Locale;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Stat } from '..';
|
||||
import { GenericStat } from '..';
|
||||
import GenericStatsRow from './GenericStatsRow';
|
||||
|
||||
export interface GenericStatsColumnProps {
|
||||
stats: Stat[];
|
||||
interface GenericStatsColumnProps {
|
||||
stats: GenericStat[];
|
||||
}
|
||||
|
||||
export default function GenericStatsColumn({ stats }: GenericStatsColumnProps) {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Stat } from '..';
|
||||
import { GenericStat } from '..';
|
||||
import { theme } from '../../../stitches.config';
|
||||
import PercentileIndicator from '../../PercentileIndicator';
|
||||
|
||||
export interface GenericStatsRowProps {
|
||||
stat: Stat;
|
||||
interface GenericStatsRowProps {
|
||||
stat: GenericStat;
|
||||
}
|
||||
|
||||
export default function GenericStatsRow({ stat }: GenericStatsRowProps) {
|
||||
@@ -30,7 +30,7 @@ export default function GenericStatsRow({ stat }: GenericStatsRowProps) {
|
||||
<span
|
||||
style={{
|
||||
color: theme.colors.textHighContrast,
|
||||
fontWeight: 'bold',
|
||||
fontWeight: 700,
|
||||
fontSize: 16,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Percentile } from '../../constants/percentiles';
|
||||
import GenericStatsColumn from './components/GenericStatsColumn';
|
||||
|
||||
export type Stat =
|
||||
export type GenericStat =
|
||||
| [string, string | number | undefined]
|
||||
| [string, string | number | undefined, Percentile | undefined];
|
||||
|
||||
export interface GenericStatsProps {
|
||||
stats: Stat[];
|
||||
stats: GenericStat[];
|
||||
columnCount?: number;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ export default function GenericStats({
|
||||
}: GenericStatsProps) {
|
||||
const filteredStats = stats.filter(([, value]) => value !== undefined);
|
||||
const itemsPerRow = Math.ceil(filteredStats.length / columnCount);
|
||||
const columns: Stat[][] = [];
|
||||
const columns: GenericStat[][] = [];
|
||||
|
||||
filteredStats.forEach((stat, index) => {
|
||||
const column = Math.floor(index / itemsPerRow);
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
export enum LineColor {
|
||||
Red,
|
||||
Green,
|
||||
Blue,
|
||||
White,
|
||||
}
|
||||
export enum LineStyle {
|
||||
Solid,
|
||||
Dashes,
|
||||
DashesAndDots,
|
||||
}
|
||||
+4
-16
@@ -1,12 +1,6 @@
|
||||
import { blue, green, red, slateDark } from '@radix-ui/colors';
|
||||
import { GRAPH_HEIGHT, GRAPH_WIDTH } from './Root';
|
||||
|
||||
export enum LineColor {
|
||||
Red,
|
||||
Green,
|
||||
Blue,
|
||||
White,
|
||||
}
|
||||
import { GRAPH_HEIGHT, GRAPH_WIDTH } from '../Root/constants';
|
||||
import { LineColor, LineStyle } from './constants';
|
||||
|
||||
const LINE_COLORS = {
|
||||
[LineColor.Red]: red.red9,
|
||||
@@ -15,12 +9,6 @@ const LINE_COLORS = {
|
||||
[LineColor.White]: slateDark.slate11,
|
||||
};
|
||||
|
||||
export enum LineStyle {
|
||||
Solid,
|
||||
Dashes,
|
||||
DashesAndDots,
|
||||
}
|
||||
|
||||
const LINE_STYLES = {
|
||||
[LineStyle.Solid]: undefined,
|
||||
[LineStyle.Dashes]: '8, 8',
|
||||
@@ -28,9 +16,9 @@ const LINE_STYLES = {
|
||||
};
|
||||
|
||||
export type PlotItem = [number, number];
|
||||
export type Plot = PlotItem[];
|
||||
type Plot = PlotItem[];
|
||||
|
||||
export interface LineProps {
|
||||
interface LineProps {
|
||||
plot: Plot;
|
||||
minY?: number;
|
||||
maxY?: number;
|
||||
@@ -0,0 +1,4 @@
|
||||
export enum MarginOrientation {
|
||||
Vertical,
|
||||
Horizontal,
|
||||
}
|
||||
+4
-8
@@ -1,13 +1,9 @@
|
||||
import { times } from 'lodash';
|
||||
import { theme } from '../../../stitches.config';
|
||||
import { MarginInputProps } from './Root';
|
||||
import { theme } from '../../../../stitches.config';
|
||||
import { MarginInputProps } from '../Root';
|
||||
import { MarginOrientation } from './constants';
|
||||
|
||||
export enum MarginOrientation {
|
||||
Vertical,
|
||||
Horizontal,
|
||||
}
|
||||
|
||||
export interface MarginProps extends MarginInputProps {
|
||||
interface MarginProps extends MarginInputProps {
|
||||
verticalSeparations: number;
|
||||
orientation?: MarginOrientation;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export const GRAPH_WIDTH = 384;
|
||||
export const GRAPH_HEIGHT = 320;
|
||||
+5
-6
@@ -1,10 +1,9 @@
|
||||
import { times } from 'lodash';
|
||||
import { ReactNode } from 'react';
|
||||
import { theme } from '../../../stitches.config';
|
||||
import { Margin, MarginOrientation } from './Margin';
|
||||
|
||||
export const GRAPH_WIDTH = 384;
|
||||
export const GRAPH_HEIGHT = 320;
|
||||
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;
|
||||
@@ -13,7 +12,7 @@ export interface MarginInputProps {
|
||||
precision?: number;
|
||||
}
|
||||
|
||||
export interface RootProps {
|
||||
interface RootProps {
|
||||
children: ReactNode;
|
||||
verticalMargin?: MarginInputProps;
|
||||
horizontalMargin?: MarginInputProps;
|
||||
@@ -1,6 +1,6 @@
|
||||
import { theme } from '../../../stitches.config';
|
||||
|
||||
export interface GapProps {
|
||||
interface GapProps {
|
||||
message: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { ComponentProps } from 'react';
|
||||
import { RatingsReward } from '../../../commands/ratings';
|
||||
import { theme } from '../../../stitches.config';
|
||||
|
||||
export interface ItemProps extends ComponentProps<'div'> {
|
||||
interface ItemProps extends ComponentProps<'div'> {
|
||||
position: number;
|
||||
deltaPosition?: number;
|
||||
score?: number;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
export interface RootProps {
|
||||
interface RootProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { DiscordLogoIcon } from '@radix-ui/react-icons';
|
||||
import { Flex, Text, Theme } from '@radix-ui/themes';
|
||||
import Link from 'next/link';
|
||||
import { theme } from '../../stitches.config';
|
||||
import { BlitzkriegWormWide } from '../../icons/BlitzkriegWormWide';
|
||||
import { theme } from '../../stitches.config';
|
||||
|
||||
export default function Navbar() {
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export enum NoDataType {
|
||||
BattlesInPeriod,
|
||||
PlayersInPeriod,
|
||||
TanksFound,
|
||||
}
|
||||
@@ -1,14 +1,8 @@
|
||||
import { Locale } from 'discord.js';
|
||||
import { translator } from '../core/localization/translator';
|
||||
import { theme } from '../stitches.config';
|
||||
import { translator } from '../../core/localization/translator';
|
||||
import { theme } from '../../stitches.config';
|
||||
|
||||
export enum NoDataType {
|
||||
BattlesInPeriod,
|
||||
PlayersInPeriod,
|
||||
TanksFound,
|
||||
}
|
||||
|
||||
export interface NoDataProps {
|
||||
interface NoDataProps {
|
||||
type: 'battles_in_period' | 'players_in_period' | 'tanks_found';
|
||||
locale: Locale;
|
||||
}
|
||||
+1
-20
@@ -10,11 +10,7 @@ import {
|
||||
slate,
|
||||
teal,
|
||||
} from '@radix-ui/colors';
|
||||
import { Percentile } from '../constants/percentiles';
|
||||
|
||||
export interface PercentileIndicatorProps {
|
||||
percentile: Percentile;
|
||||
}
|
||||
import { Percentile } from '../../constants/percentiles';
|
||||
|
||||
export const PERCENTILE_COLORS = {
|
||||
[Percentile.VeryBad]: slate.slate9,
|
||||
@@ -28,18 +24,3 @@ export const PERCENTILE_COLORS = {
|
||||
[Percentile.Unicum]: pink.pink9,
|
||||
[Percentile.SuperUnicum]: purple.purple9,
|
||||
};
|
||||
|
||||
export default function PercentileIndicator({
|
||||
percentile,
|
||||
}: PercentileIndicatorProps) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: 4,
|
||||
backgroundColor: PERCENTILE_COLORS[percentile],
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Percentile } from '../../constants/percentiles';
|
||||
import { PERCENTILE_COLORS } from './constants';
|
||||
|
||||
interface PercentileIndicatorProps {
|
||||
percentile: Percentile;
|
||||
}
|
||||
|
||||
export default function PercentileIndicator({
|
||||
percentile,
|
||||
}: PercentileIndicatorProps) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: 4,
|
||||
backgroundColor: PERCENTILE_COLORS[percentile],
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
export interface ColumnProps {
|
||||
interface ColumnProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { TankClass, TreeType } from '.';
|
||||
import {
|
||||
TANK_ICONS,
|
||||
TANK_ICONS_COLLECTOR,
|
||||
TANK_ICONS_PREMIUM,
|
||||
} from '../../../../core/blitzkrieg/tankDefinitions/constants';
|
||||
|
||||
export const TREE_TYPES = ['researchable', 'premium', 'collector'] as const; // BIG TODO: REMOVE THIS AFTER LOCALIZATION
|
||||
|
||||
export const TANK_CLASSES = [
|
||||
'lightTank',
|
||||
'mediumTank',
|
||||
'heavyTank',
|
||||
'AT-SPG',
|
||||
] as const;
|
||||
|
||||
export const TREE_TYPE_NAMES: Record<TreeType, string> = {
|
||||
researchable: 'Tech tree',
|
||||
premium: 'Premium',
|
||||
collector: 'Collector',
|
||||
};
|
||||
export const TREE_TYPE_IMAGES: Record<TreeType, string> = {
|
||||
researchable: 'https://i.imgur.com/pJxO2XY.png',
|
||||
premium: 'https://i.imgur.com/mZzSwOU.png',
|
||||
collector: 'https://i.imgur.com/7A0RsG5.png',
|
||||
};
|
||||
export const TREE_TYPE_ICONS: Record<TreeType, Record<TankClass, string>> = {
|
||||
researchable: TANK_ICONS,
|
||||
premium: TANK_ICONS_PREMIUM,
|
||||
collector: TANK_ICONS_COLLECTOR,
|
||||
};
|
||||
+3
-36
@@ -1,37 +1,10 @@
|
||||
import {
|
||||
TANK_ICONS,
|
||||
TANK_ICONS_COLLECTOR,
|
||||
TANK_ICONS_PREMIUM,
|
||||
} from '../../../core/blitzkrieg/tankDefinitions';
|
||||
import { theme } from '../../../stitches.config';
|
||||
|
||||
export const TANK_CLASSES = [
|
||||
'lightTank',
|
||||
'mediumTank',
|
||||
'heavyTank',
|
||||
'AT-SPG',
|
||||
] as const;
|
||||
|
||||
export const TREE_TYPES = ['researchable', 'premium', 'collector'] as const;
|
||||
import { theme } from '../../../../stitches.config';
|
||||
import { TANK_CLASSES, TREE_TYPES, TREE_TYPE_ICONS } from './constants';
|
||||
|
||||
export type TankClass = (typeof TANK_CLASSES)[number];
|
||||
export type TreeType = (typeof TREE_TYPES)[number];
|
||||
|
||||
// BIG TODO: REMOVE THIS AFTER LOCALIZATION
|
||||
export const TREE_TYPE_NAMES: Record<TreeType, string> = {
|
||||
researchable: 'Tech tree',
|
||||
premium: 'Premium',
|
||||
collector: 'Collector',
|
||||
};
|
||||
|
||||
export const TREE_TYPE_IMAGES: Record<TreeType, string> = {
|
||||
researchable: 'https://i.imgur.com/pJxO2XY.png',
|
||||
premium: 'https://i.imgur.com/mZzSwOU.png',
|
||||
collector: 'https://i.imgur.com/7A0RsG5.png',
|
||||
};
|
||||
|
||||
export interface ItemProps {
|
||||
image?: string;
|
||||
tankType?: TankClass;
|
||||
name: string;
|
||||
treeType: TreeType;
|
||||
@@ -43,13 +16,7 @@ const TREE_TYPE_COLOR = {
|
||||
collector: '_blue',
|
||||
} as const;
|
||||
|
||||
export const TREE_TYPE_ICONS: Record<TreeType, Record<TankClass, string>> = {
|
||||
researchable: TANK_ICONS,
|
||||
premium: TANK_ICONS_PREMIUM,
|
||||
collector: TANK_ICONS_COLLECTOR,
|
||||
};
|
||||
|
||||
export function Item({ image, tankType, name, treeType }: ItemProps) {
|
||||
export function Item({ tankType, name, treeType }: ItemProps) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
export interface RootProps {
|
||||
interface RootProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
export interface RowProps {
|
||||
interface RowProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { theme } from '../../../stitches.config';
|
||||
|
||||
export interface TitleProps {
|
||||
interface TitleProps {
|
||||
children: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { theme } from '../stitches.config';
|
||||
|
||||
export type TierWeightsRecord = Partial<Record<Tier, number>>;
|
||||
|
||||
export interface TierWeightsProps {
|
||||
interface TierWeightsProps {
|
||||
weights: TierWeightsRecord;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { theme } from '../stitches.config';
|
||||
|
||||
export interface TitleBarProps {
|
||||
interface TitleBarProps {
|
||||
title: string;
|
||||
image?: string;
|
||||
description?: string;
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Vector3Tuple } from 'three';
|
||||
import { Tier } from '../core/blitzkrieg/tankDefinitions';
|
||||
|
||||
export const equipmentPriceMatrix: Record<Tier, Vector3Tuple> = {
|
||||
1: [100, 200, 300],
|
||||
2: [200, 300, 400],
|
||||
3: [400, 500, 600],
|
||||
4: [4000, 5000, 6000],
|
||||
5: [40_000, 50_000, 60_000],
|
||||
6: [100_000, 120_000, 140_000],
|
||||
7: [150_000, 170_000, 190_000],
|
||||
8: [200_000, 225_000, 250_000],
|
||||
9: [250_000, 275_000, 300_000],
|
||||
10: [300_000, 350_000, 400_000],
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
import { clamp } from 'lodash';
|
||||
import { degToRad } from 'three/src/math/MathUtils';
|
||||
import { PitchLimits, YawLimits } from '../blitzkrieg/modelDefinitions';
|
||||
import { normalizeAngleRad } from '../math/normalizeAngleRad';
|
||||
|
||||
@@ -7,33 +8,31 @@ export function applyPitchYawLimits(
|
||||
yaw: number, // rad
|
||||
pitchLimits: PitchLimits, // deg
|
||||
yawLimits?: YawLimits, // deg
|
||||
verticalStabilizer = false,
|
||||
) {
|
||||
let newPitch = pitch;
|
||||
let newYaw = yaw;
|
||||
|
||||
if (yawLimits) {
|
||||
newYaw = normalizeAngleRad(
|
||||
clamp(
|
||||
newYaw,
|
||||
-yawLimits.max * (Math.PI / 180),
|
||||
-yawLimits.min * (Math.PI / 180),
|
||||
),
|
||||
clamp(newYaw, -degToRad(yawLimits.max), -degToRad(yawLimits.min)),
|
||||
);
|
||||
}
|
||||
|
||||
let lowerPitch = -pitchLimits.max * (Math.PI / 180);
|
||||
let upperPitch = -pitchLimits.min * (Math.PI / 180);
|
||||
const transition = (pitchLimits.transition ?? 10) * (Math.PI / 180);
|
||||
const pitchBuff = verticalStabilizer ? degToRad(3) : 0;
|
||||
let lowerPitch = -degToRad(pitchLimits.max) - pitchBuff;
|
||||
let upperPitch = -degToRad(pitchLimits.min) + pitchBuff;
|
||||
const transition = degToRad(pitchLimits.transition ?? 10);
|
||||
|
||||
if (pitchLimits.back) {
|
||||
const range = pitchLimits.back.range * (Math.PI / 180);
|
||||
const range = degToRad(pitchLimits.back.range);
|
||||
const yawRotatedAbs = Math.abs(normalizeAngleRad(newYaw - Math.PI));
|
||||
|
||||
if (yawRotatedAbs <= range + transition) {
|
||||
if (yawRotatedAbs <= range) {
|
||||
// inside range
|
||||
lowerPitch = -pitchLimits.back.max * (Math.PI / 180);
|
||||
upperPitch = -pitchLimits.back.min * (Math.PI / 180);
|
||||
lowerPitch = -degToRad(pitchLimits.back.max);
|
||||
upperPitch = -degToRad(pitchLimits.back.min);
|
||||
} else {
|
||||
// inside transition
|
||||
const transitionProgress = (yawRotatedAbs - range) / transition;
|
||||
@@ -44,21 +43,21 @@ export function applyPitchYawLimits(
|
||||
(1 - transitionProgress) * pitchLimits.back.min +
|
||||
transitionProgress * pitchLimits.min;
|
||||
|
||||
lowerPitch = -maxInterpolatedPitch * (Math.PI / 180);
|
||||
upperPitch = -minInterpolatedPitch * (Math.PI / 180);
|
||||
lowerPitch = -degToRad(maxInterpolatedPitch);
|
||||
upperPitch = -degToRad(minInterpolatedPitch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pitchLimits.front) {
|
||||
const range = pitchLimits.front.range * (Math.PI / 180);
|
||||
const range = degToRad(pitchLimits.front.range);
|
||||
const yawAbs = Math.abs(normalizeAngleRad(newYaw));
|
||||
|
||||
if (yawAbs <= range + transition) {
|
||||
if (yawAbs <= range) {
|
||||
// inside range
|
||||
lowerPitch = -pitchLimits.front.max * (Math.PI / 180);
|
||||
upperPitch = -pitchLimits.front.min * (Math.PI / 180);
|
||||
lowerPitch = -degToRad(pitchLimits.front.max);
|
||||
upperPitch = -degToRad(pitchLimits.front.min);
|
||||
} else {
|
||||
// inside transition
|
||||
const transitionProgress = (yawAbs - range) / transition;
|
||||
@@ -69,8 +68,8 @@ export function applyPitchYawLimits(
|
||||
(1 - transitionProgress) * pitchLimits.front.min +
|
||||
transitionProgress * pitchLimits.min;
|
||||
|
||||
lowerPitch = -maxInterpolatedPitch * (Math.PI / 180);
|
||||
upperPitch = -minInterpolatedPitch * (Math.PI / 180);
|
||||
lowerPitch = -degToRad(maxInterpolatedPitch);
|
||||
upperPitch = -degToRad(minInterpolatedPitch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
|
||||
import fetchBlitz from './fetchBlitz';
|
||||
|
||||
export interface EncyclopediaInfo {
|
||||
achievement_sections: Record<string, { name: string; order: number }>;
|
||||
tanks_updated_at: number;
|
||||
languages: Record<string, string>;
|
||||
vehicle_types: Record<string, string>;
|
||||
vehicle_nations: Record<string, string>;
|
||||
game_version: string;
|
||||
}
|
||||
|
||||
export const encyclopediaInfo = fetchBlitz<EncyclopediaInfo>(
|
||||
`https://api.wotblitz.com/wotb/encyclopedia/info/?application_id=${WARGAMING_APPLICATION_ID}`,
|
||||
);
|
||||
@@ -6,13 +6,9 @@ import { VertexAttribute } from '../streams/scpg';
|
||||
import {
|
||||
vertexAttributeGLTFName,
|
||||
vertexAttributeGltfVectorSizes,
|
||||
} from './extractModel';
|
||||
} from './extractModel/constants';
|
||||
import { readDVPLFile } from './readDVPLFile';
|
||||
|
||||
Buffer.prototype.toJSON = function () {
|
||||
return [...(this as Buffer)].map((int) => int.toString(16)).join('');
|
||||
};
|
||||
|
||||
export async function extractArmor(data: string, fileName: string) {
|
||||
const sc2Path = `${data}/3d/Tanks/CollisionMeshes/${fileName}.sc2.dvpl`;
|
||||
const scgPath = `${data}/3d/Tanks/CollisionMeshes/${fileName}.scg.dvpl`;
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { vertexAttributeVectorSizes } from '../../streams/scg';
|
||||
import { VertexAttribute } from '../../streams/scpg';
|
||||
|
||||
export const vertexAttributeGLTFName: Partial<Record<VertexAttribute, string>> =
|
||||
{
|
||||
[VertexAttribute.VERTEX]: 'POSITION',
|
||||
[VertexAttribute.NORMAL]: 'NORMAL',
|
||||
[VertexAttribute.TEXCOORD0]: 'TEXCOORD_0',
|
||||
[VertexAttribute.TEXCOORD1]: 'TEXCOORD_0',
|
||||
[VertexAttribute.TEXCOORD2]: 'TEXCOORD_0',
|
||||
[VertexAttribute.TEXCOORD3]: 'TEXCOORD_0',
|
||||
[VertexAttribute.TANGENT]: 'TANGENT',
|
||||
[VertexAttribute.JOINTINDEX]: 'JOINT_0',
|
||||
[VertexAttribute.JOINTWEIGHT]: 'WEIGHT_0',
|
||||
};
|
||||
export const vertexAttributeGltfVectorSizes = {
|
||||
...vertexAttributeVectorSizes,
|
||||
[VertexAttribute.TANGENT]: 4,
|
||||
} as const;
|
||||
@@ -1,51 +1,26 @@
|
||||
import { Document, Material, Node, Scene } from '@gltf-transform/core';
|
||||
import { times } from 'lodash';
|
||||
import { dirname } from 'path';
|
||||
import { Matrix4, Quaternion, Vector3, Vector4Tuple } from 'three';
|
||||
import { readTexture } from '../../blitzkrieg/readTexture';
|
||||
import { TextureMutation } from '../../blitzkrieg/readTexture/constants';
|
||||
import { Hierarchy, Sc2ReadStream, Textures } from '../../streams/sc2';
|
||||
import { ScgReadStream } from '../../streams/scg';
|
||||
import { VertexAttribute } from '../../streams/scpg';
|
||||
import { readDVPLFile } from '../readDVPLFile';
|
||||
import {
|
||||
Matrix4,
|
||||
Quaternion,
|
||||
Vector3,
|
||||
Vector3Tuple,
|
||||
Vector4Tuple,
|
||||
} from 'three';
|
||||
import { TextureMutation, readTexture } from '../blitzkrieg/readTexture';
|
||||
import { Hierarchy, Sc2ReadStream, Textures } from '../streams/sc2';
|
||||
import { ScgReadStream, vertexAttributeVectorSizes } from '../streams/scg';
|
||||
import { VertexAttribute } from '../streams/scpg';
|
||||
import { readDVPLFile } from './readDVPLFile';
|
||||
vertexAttributeGLTFName,
|
||||
vertexAttributeGltfVectorSizes,
|
||||
} from './constants';
|
||||
|
||||
const ERROR_ON_UNKNOWN_COMPONENT = false;
|
||||
const MAX_FLOAT32 = 2 ** 127 * (2 - 2 ** -23);
|
||||
|
||||
export const vertexAttributeGLTFName: Partial<Record<VertexAttribute, string>> =
|
||||
{
|
||||
[VertexAttribute.VERTEX]: 'POSITION',
|
||||
[VertexAttribute.NORMAL]: 'NORMAL',
|
||||
[VertexAttribute.TEXCOORD0]: 'TEXCOORD_0',
|
||||
[VertexAttribute.TEXCOORD1]: 'TEXCOORD_0',
|
||||
[VertexAttribute.TEXCOORD2]: 'TEXCOORD_0',
|
||||
[VertexAttribute.TEXCOORD3]: 'TEXCOORD_0',
|
||||
[VertexAttribute.TANGENT]: 'TANGENT',
|
||||
[VertexAttribute.JOINTINDEX]: 'JOINT_0',
|
||||
[VertexAttribute.JOINTWEIGHT]: 'WEIGHT_0',
|
||||
};
|
||||
|
||||
export const vertexAttributeGltfVectorSizes = {
|
||||
...vertexAttributeVectorSizes,
|
||||
|
||||
[VertexAttribute.TANGENT]: 4,
|
||||
} as const;
|
||||
|
||||
const omitMeshNames = {
|
||||
start: ['chassis_chassis_', 'chassis_track_crash_', 'HP_'],
|
||||
end: ['_POINT'],
|
||||
};
|
||||
|
||||
export async function extractModel(
|
||||
data: string,
|
||||
path: string,
|
||||
baseColor?: Vector3Tuple,
|
||||
) {
|
||||
export async function extractModel(data: string, path: string) {
|
||||
const sc2Path = `${data}/3d/${path}.sc2.dvpl`;
|
||||
const scgPath = `${data}/3d/${path}.scg.dvpl`;
|
||||
const sc2 = new Sc2ReadStream((await readDVPLFile(sc2Path)).buffer).sc2();
|
||||
@@ -82,13 +57,11 @@ export async function extractModel(
|
||||
material.setBaseColorTexture(
|
||||
document
|
||||
.createTexture(node.materialName)
|
||||
.setMimeType('image/png')
|
||||
.setMimeType('image/jpeg')
|
||||
.setImage(
|
||||
await readTexture(
|
||||
`${data}/3d/${dirname(path)}/${textures.albedo}`,
|
||||
baseColor
|
||||
? { mutation: TextureMutation.BaseColor, baseColor }
|
||||
: undefined,
|
||||
TextureMutation.Albedo,
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -97,11 +70,11 @@ export async function extractModel(
|
||||
material.setMetallicRoughnessTexture(
|
||||
document
|
||||
.createTexture(node.materialName)
|
||||
.setMimeType('image/png')
|
||||
.setMimeType('image/jpeg')
|
||||
.setImage(
|
||||
await readTexture(
|
||||
`${data}/3d/${dirname(path)}/${textures.baseRMMap}`,
|
||||
{ mutation: TextureMutation.RoughnessMetallicness },
|
||||
TextureMutation.RoughnessMetallicness,
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -113,13 +86,13 @@ export async function extractModel(
|
||||
material.setNormalTexture(
|
||||
document
|
||||
.createTexture(node.materialName)
|
||||
.setMimeType('image/png')
|
||||
.setMimeType('image/jpeg')
|
||||
.setImage(
|
||||
await readTexture(
|
||||
`${data}/3d/${dirname(path)}/${
|
||||
textures.baseNormalMap ?? textures.normalmap
|
||||
}`,
|
||||
isBase ? { mutation: TextureMutation.Normal } : undefined,
|
||||
isBase ? TextureMutation.Normal : undefined,
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -129,11 +102,11 @@ export async function extractModel(
|
||||
material.setOcclusionTexture(
|
||||
document
|
||||
.createTexture(node.materialName)
|
||||
.setMimeType('image/png')
|
||||
.setMimeType('image/jpeg')
|
||||
.setImage(
|
||||
await readTexture(
|
||||
`${data}/3d/${dirname(path)}/${textures.miscMap}`,
|
||||
{ mutation: TextureMutation.Miscellaneous },
|
||||
TextureMutation.Miscellaneous,
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -1,4 +1,4 @@
|
||||
export type BlitzResponse<Data extends object> =
|
||||
type BlitzResponse<Data extends object> =
|
||||
| {
|
||||
status: 'error';
|
||||
error: { field: string; message: string; code: 402; value: null };
|
||||
|
||||
@@ -4,7 +4,7 @@ import fetchBlitz from './fetchBlitz';
|
||||
import { normalizeExtra } from './normalizeExtra';
|
||||
import { normalizeIds } from './normalizeIds';
|
||||
|
||||
export type IndividualClanAccountInfo = {
|
||||
type IndividualClanAccountInfo = {
|
||||
account_id: number;
|
||||
joined_at: number;
|
||||
clan_id: number;
|
||||
@@ -21,7 +21,7 @@ export type IndividualClanAccountInfo = {
|
||||
};
|
||||
} | null;
|
||||
|
||||
export interface ClanAccountInfo {
|
||||
interface ClanAccountInfo {
|
||||
[accountId: number]: IndividualClanAccountInfo;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID
|
||||
import fetchBlitz from './fetchBlitz';
|
||||
import { normalizeIds } from './normalizeIds';
|
||||
|
||||
export interface IndividualClanInfo {
|
||||
interface IndividualClanInfo {
|
||||
recruiting_options: {
|
||||
vehicles_level: number;
|
||||
wins_ratio: number;
|
||||
@@ -32,7 +32,7 @@ export interface IndividualClanInfo {
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface ClanInfo {
|
||||
interface ClanInfo {
|
||||
[clanId: number]: IndividualClanInfo;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,24 +2,27 @@ import { Locale } from 'discord.js';
|
||||
import { go } from 'fuzzysort';
|
||||
import markdownEscape from 'markdown-escape';
|
||||
import { UserError } from '../../hooks/userError';
|
||||
import {
|
||||
tankDefinitions,
|
||||
tanksDefinitionsArray,
|
||||
} from '../blitzkrieg/tankDefinitions';
|
||||
import { tankDefinitions, tankNames } from '../blitzkrieg/tankDefinitions';
|
||||
import { tankNamesTechTreeOnly } from '../discord/autocompleteTanks';
|
||||
import { translator } from '../localization/translator';
|
||||
|
||||
export default async function resolveTankId(
|
||||
tank: string | number,
|
||||
locale: Locale,
|
||||
techTreeOnly = false,
|
||||
) {
|
||||
const { translate } = translator(locale);
|
||||
const number = typeof tank === 'string' ? Number(tank) : tank;
|
||||
|
||||
if (Number.isNaN(number)) {
|
||||
const searchResult = go(`${tank}`, await tanksDefinitionsArray, {
|
||||
keys: ['name'],
|
||||
limit: 1,
|
||||
});
|
||||
const searchResult = go(
|
||||
`${tank}`,
|
||||
await (techTreeOnly ? tankNamesTechTreeOnly : tankNames),
|
||||
{
|
||||
keys: ['combined'],
|
||||
limit: 1,
|
||||
},
|
||||
);
|
||||
|
||||
if (searchResult.length === 0) {
|
||||
throw new UserError(
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import { Region } from '../../constants/regions';
|
||||
import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
|
||||
import { Clan, ClanList } from '../../types/clanList';
|
||||
import { ClanList } from '../../types/clanList';
|
||||
import fetchBlitz from './fetchBlitz';
|
||||
|
||||
export type ClanListWithRegion = (Clan & {
|
||||
region: 'com' | 'eu' | 'asia';
|
||||
})[];
|
||||
|
||||
export default async function searchClansAcrossRegions(
|
||||
search: string,
|
||||
limit = 25,
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export const usernamePattern = /^[a-zA-Z0-9_]{3,24}$/;
|
||||
export const usernamePatternWithoutPosition = /[a-zA-Z0-9_]{3,24}/;
|
||||
+4
-6
@@ -1,6 +1,7 @@
|
||||
import { Region } from '../../constants/regions';
|
||||
import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
|
||||
import fetchBlitz from './fetchBlitz';
|
||||
import { Region } from '../../../constants/regions';
|
||||
import { WARGAMING_APPLICATION_ID } from '../../../constants/wargamingApplicationID';
|
||||
import fetchBlitz from '../fetchBlitz';
|
||||
import { usernamePattern } from './constants';
|
||||
|
||||
export interface AccountListItem {
|
||||
nickname: string;
|
||||
@@ -11,9 +12,6 @@ export type AccountListWithServer = (AccountListItem & {
|
||||
region: Region;
|
||||
})[];
|
||||
|
||||
export const usernamePattern = /^[a-zA-Z0-9_]{3,24}$/;
|
||||
export const usernamePatternWithoutPosition = /[a-zA-Z0-9_]{3,24}/;
|
||||
|
||||
export default async function searchPlayersAcrossRegions(
|
||||
search: string,
|
||||
limit = 25,
|
||||
@@ -0,0 +1,50 @@
|
||||
import { crc32 } from 'crc';
|
||||
import { encodeBlockHC } from 'lz4';
|
||||
|
||||
/**
|
||||
* Thanks Maddoxkkm! Modernized heavily.
|
||||
*
|
||||
* https://github.com/Maddoxkkm/dvpl_converter/
|
||||
*/
|
||||
export function writeDVPL(buffer: Buffer) {
|
||||
let output = Buffer.alloc(buffer.length);
|
||||
const compressedBlockSize = encodeBlockHC(buffer, output);
|
||||
|
||||
if (compressedBlockSize === 0 || compressedBlockSize >= buffer.length) {
|
||||
const footerBuffer = toDVPLFooter(
|
||||
buffer.length,
|
||||
buffer.length,
|
||||
crc32(buffer),
|
||||
0,
|
||||
);
|
||||
|
||||
return Buffer.concat([buffer, footerBuffer], buffer.length + 20);
|
||||
} else {
|
||||
output = output.subarray(0, compressedBlockSize);
|
||||
const footerBuffer = toDVPLFooter(
|
||||
buffer.length,
|
||||
compressedBlockSize,
|
||||
crc32(output),
|
||||
2,
|
||||
);
|
||||
|
||||
return Buffer.concat([output, footerBuffer], compressedBlockSize + 20);
|
||||
}
|
||||
}
|
||||
|
||||
function toDVPLFooter(
|
||||
inputSize: number,
|
||||
compressedSize: number,
|
||||
crc32: number,
|
||||
type: number,
|
||||
) {
|
||||
const result = Buffer.alloc(20);
|
||||
|
||||
result.writeUInt32LE(inputSize, 0);
|
||||
result.writeUInt32LE(compressedSize, 4);
|
||||
result.writeUInt32LE(crc32, 8);
|
||||
result.writeUInt32LE(type, 12);
|
||||
result.write('DVPL', 16, 4, 'utf8');
|
||||
|
||||
return result;
|
||||
}
|
||||
Loaded 100 of 163 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user