Compare commits

...
20 Commits
Author SHA1 Message Date
TrèsAbhi 191843f972 Merge pull request #220 from blitzkit/main-i18n
New Crowdin updates
2026-03-24 09:39:43 -05:00
TrèsAbhi c1fde44809 Merge pull request #223 from blitzkit/fixes
fixes
2026-03-24 09:39:28 -05:00
TrèsAbhi 1a3517e035 feat: logs 2026-03-23 22:50:34 -05:00
TrèsAbhi afec6d9851 feat: enable all tiers 2026-03-23 22:41:34 -05:00
TrèsAbhi e3f2fd70ad fix: use turret mantlet 2026-03-23 22:36:10 -05:00
TrèsAbhi 5adbec25c4 fix: compact compare headers 2026-03-23 22:20:29 -05:00
TrèsAbhi 4ff8d77460 fix: object review videos 2026-03-23 22:13:29 -05:00
TrèsAbhi 22faa6d294 fix: stale compare indices 2026-03-23 22:07:20 -05:00
TrèsAbhi a2abfa7f85 fix: v-stabs now give 12% 2026-03-23 21:56:33 -05:00
TrèsAbhi 7b93b12fcb Merge pull request #222 from karelpak1/Position-fix 2026-03-23 19:56:17 -05:00
GonnaHetzMe ed252d5dfe Reset localTranslation for model extraction 2026-03-23 13:17:42 +01:00
TrèsAbhi c365993541 feat: remove discord bot
.
2026-03-19 09:21:58 -05:00
TrèsAbhi 30f9475208 feat: disable bot 2026-03-19 09:19:28 -05:00
TrèsAbhi 454c526666 New translations en.json (Polish) 2026-03-16 18:24:41 -05:00
TrèsAbhi e67970d2b9 New translations en.json (Polish) 2026-03-16 16:55:46 -05:00
TrèsAbhi 0f1c03d1e5 New translations en.json (Chinese Simplified) 2026-03-12 22:52:18 -05:00
tresabhi 8394f2ec1e feat: logs 2026-03-11 16:51:32 -05:00
tresabhi f14e73d811 fix: alpha clipping 2026-03-11 16:50:18 -05:00
TrèsAbhi 23e00b4199 New translations en.json (Polish) 2026-03-10 14:55:15 -05:00
TrèsAbhi 427b189a34 New Crowdin updates (#217) 2026-03-04 17:42:01 -06:00
28 changed files with 384 additions and 244 deletions

No files matched your search

-6
View File
@@ -1,13 +1,7 @@
name: Host name: Host
on: on:
push:
branches: main
schedule:
- cron: "0 */5 * * *"
workflow_dispatch: workflow_dispatch:
release:
types: [created, published, released, edited]
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
+5
View File
@@ -0,0 +1,5 @@
# BlitzKit 1.29.15
## Fixes
- Tanks now use alpha clipping to render translucency, fixing a lot of "ghostly" looking tanks
+18
View File
@@ -0,0 +1,18 @@
# BlitzKit 1.29.16
Thank you to [GonnaHetzMe](https://github.com/karelpak1) for your contributions this update.
## Fixes
- [GonnaHetzMe] The turret for Centurion I is no longer offset from the body
- Please allow up to 24 hours for the model to update
- Vertical Stabilizers now give the correct 12% buff instead of 15%
- Re-ordering tanks in Compare now works once again
- Fixed review videos from being about the Object 452K across all tanks
- Please allow up to 2 weeks for all tanks to refresh their videos
- Tanks in Mixer with oscillating turrets are not handled properly
## Features
- Headings in the Compare tool are now even more compact for mobile devices
- Enabled all tiers for the randomizer in Mixer
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "blitzkit", "name": "blitzkit",
"version": "1.29.14", "version": "1.29.16",
"license": "BlitzKit License", "license": "BlitzKit License",
"private": true, "private": true,
"scripts": { "scripts": {
@@ -0,0 +1,3 @@
export function bufferToBigInt(buffer: ArrayBuffer) {
return new DataView(buffer).getBigUint64(0, true);
}
+2
View File
@@ -1,4 +1,5 @@
export * from './buffer'; export * from './buffer';
export * from './bufferToBigInt';
export * from './dds'; export * from './dds';
export * from './dids'; export * from './dids';
export * from './pvr'; export * from './pvr';
@@ -7,3 +8,4 @@ export * from './sc2';
export * from './scg'; export * from './scg';
export * from './scpg'; export * from './scpg';
export * from './windows'; export * from './windows';
+10 -3
View File
@@ -320,9 +320,16 @@
} }
}, },
"commands": { "commands": {
"dead": "# Stat Commands Are Dead\n\nThe BlitzKit Discord bot used to use BlitzStars to fetch periodic statistics. BlitzStars has enabled aggressive Cloudflare protections; hence, disabling BlitzKit from using BlitzStar's API. This has impacted the following commands:\n\n- `/stats`\n- `/today`\n- `/breakdown`\n- `/evolution`\n- `/full-stats`\n\nPlease use [Aftermath](<https://amth.one/invite>) as an alternative which is completely independent of BlitzStars. BlitzKit-hosted stat commands will return in the near future.",
"replay": { "replay": {
"name": "repetición", "name": "repetición",
"description": "Mostrar resultados de la repetición" "description": "Mostrar resultados de la repetición",
"options": {
"file": {
"name": "file",
"description": "Replay file"
}
}
}, },
"research": { "research": {
"name": "desarollar", "name": "desarollar",
@@ -1005,8 +1012,8 @@
"title": "Características", "title": "Características",
"compare": { "compare": {
"label": "Comparar con", "label": "Comparar con",
"class": "Tier {tier} {class}", "class": "{tier} {class}",
"tier": "All of tier {tier}", "tier": "All tier {tier}",
"all": "Todos los tanques" "all": "Todos los tanques"
}, },
"values": { "values": {
+10 -3
View File
@@ -320,9 +320,16 @@
} }
}, },
"commands": { "commands": {
"dead": "# Stat Commands Are Dead\n\nThe BlitzKit Discord bot used to use BlitzStars to fetch periodic statistics. BlitzStars has enabled aggressive Cloudflare protections; hence, disabling BlitzKit from using BlitzStar's API. This has impacted the following commands:\n\n- `/stats`\n- `/today`\n- `/breakdown`\n- `/evolution`\n- `/full-stats`\n\nPlease use [Aftermath](<https://amth.one/invite>) as an alternative which is completely independent of BlitzStars. BlitzKit-hosted stat commands will return in the near future.",
"replay": { "replay": {
"name": "rejouer", "name": "rejouer",
"description": "Afficher les résultats de la relecture" "description": "Afficher les résultats de la relecture",
"options": {
"file": {
"name": "file",
"description": "Replay file"
}
}
}, },
"research": { "research": {
"name": "recherche", "name": "recherche",
@@ -1005,8 +1012,8 @@
"title": "Characteristics", "title": "Characteristics",
"compare": { "compare": {
"label": "Compare against", "label": "Compare against",
"class": "Tier {tier} {class}", "class": "{tier} {class}",
"tier": "All of tier {tier}", "tier": "All tier {tier}",
"all": "All tanks" "all": "All tanks"
}, },
"values": { "values": {
+10 -3
View File
@@ -320,9 +320,16 @@
} }
}, },
"commands": { "commands": {
"dead": "# Stat Commands Are Dead\n\nThe BlitzKit Discord bot used to use BlitzStars to fetch periodic statistics. BlitzStars has enabled aggressive Cloudflare protections; hence, disabling BlitzKit from using BlitzStar's API. This has impacted the following commands:\n\n- `/stats`\n- `/today`\n- `/breakdown`\n- `/evolution`\n- `/full-stats`\n\nPlease use [Aftermath](<https://amth.one/invite>) as an alternative which is completely independent of BlitzStars. BlitzKit-hosted stat commands will return in the near future.",
"replay": { "replay": {
"name": "replay", "name": "replay",
"description": "Display replay results" "description": "Display replay results",
"options": {
"file": {
"name": "file",
"description": "Replay file"
}
}
}, },
"research": { "research": {
"name": "研究", "name": "研究",
@@ -1005,8 +1012,8 @@
"title": "性能値", "title": "性能値",
"compare": { "compare": {
"label": "比較対象", "label": "比較対象",
"class": "Tier {tier} {class}", "class": "{tier} {class}",
"tier": "All of tier {tier}", "tier": "All tier {tier}",
"all": "全車輌" "all": "全車輌"
}, },
"values": { "values": {
+45 -38
View File
@@ -1,6 +1,6 @@
{ {
"test": { "test": {
"default": "", "default": "test",
"long": "This is a long test string. You MUST NEVER see this in the UI. My favorite fruit is {fruit} because it has the color of {color}. I like it so much that I will say its name 3 times! {fruit}! {fruit}! {fruit}! Once again, it's {color}, not {not_color}.", "long": "This is a long test string. You MUST NEVER see this in the UI. My favorite fruit is {fruit} because it has the color of {color}. I like it so much that I will say its name 3 times! {fruit}! {fruit}! {fruit}! Once again, it's {color}, not {not_color}.",
"fox": "Szybki brązowy lis przeskoczył nad leniwym psem." "fox": "Szybki brązowy lis przeskoczył nad leniwym psem."
}, },
@@ -17,9 +17,9 @@
"plus": "+{value}" "plus": "+{value}"
}, },
"gun_types": { "gun_types": {
"auto_loader": "Auto loader", "auto_loader": "Samo ładowacz",
"auto_reloader": "Automatyczne przeładowanie", "auto_reloader": "Automatyczne przeładowanie",
"regular": "" "regular": "Regular"
}, },
"shells": { "shells": {
"ap": "AP", "ap": "AP",
@@ -28,18 +28,18 @@
"he": "OB" "he": "OB"
}, },
"player_count_period": { "player_count_period": {
"total": "Total", "total": "Łącznie",
"d_120": "Past 120 days", "d_120": "Powyżej 120 dni",
"d_90": "Past 90 days", "d_90": "Powyżej 90 dni",
"d_60": "Past 60 days", "d_60": "Powyżej 60 dni",
"d_30": "Past month", "d_30": "W ciągu miesiąca",
"d_7": "W minionym tygodniu", "d_7": "W minionym tygodniu",
"d_1": "Yesterday" "d_1": "Wczoraj"
}, },
"branches": { "branches": {
"local": "dev", "local": "dev",
"dev": "beta", "dev": "beta",
"opentest": "opentest", "opentest": "otwarty test",
"preview": "podgląd" "preview": "podgląd"
}, },
"locales": { "locales": {
@@ -80,45 +80,45 @@
}, },
"nations": { "nations": {
"ussr": "ZSRR", "ussr": "ZSRR",
"germany": "Germany", "germany": "Niemcy",
"usa": "U.S.A.", "usa": "Stany Zjednoczone.",
"china": "Chiny", "china": "Chiny",
"uk": "Wielka Brytania", "uk": "Wielka Brytania",
"france": "France", "france": "Francja",
"japan": "Japan", "japan": "Japonia",
"european": "Nacja Europejska", "european": "Nacja Europejska",
"other": "Nacja Hybrydowa" "other": "Nacja Hybrydowa"
}, },
"nations_adjectives": { "nations_adjectives": {
"ussr": "ZSRR", "ussr": "ZSRR",
"germany": "German", "germany": "Niemcy",
"usa": "Amerykański", "usa": "Amerykański",
"china": "Chiński", "china": "Chiński",
"uk": "Wielkiej Brytanii", "uk": "Wielkiej Brytanii",
"france": "Francuski", "france": "Francuski",
"japan": "Japoński", "japan": "Japoński",
"european": "European", "european": "Europa",
"other": "Hybrid" "other": "Hybryda"
}, },
"tank_class_short": { "tank_class_short": {
"-1": "unrecognized", "-1": "unrecognized",
"0": "Light", "0": "Lekki",
"1": "Medium", "1": "Średni",
"2": "Heavy", "2": "Ciężki",
"3": "TD" "3": "TD"
}, },
"tank_class_short_plural_lowercase": { "tank_class_short_plural_lowercase": {
"0": "lights", "0": "lekkie",
"1": "mediums", "1": "średnie",
"2": "heavies", "2": "ciężkie",
"3": "TDs" "3": "TDs"
}, },
"tank_class_medium": { "tank_class_medium": {
"-1": "unrecognized", "-1": "unrecognized",
"0": "Lekki", "0": "Lekki",
"1": "Medium", "1": "Średni",
"2": "Heavy", "2": "Ciężki",
"3": "Tank destroyer" "3": "Niszczyciel czołgów"
}, },
"tree_type": { "tree_type": {
"-1": "unrecognized", "-1": "unrecognized",
@@ -210,7 +210,7 @@
"good": "Dobrze", "good": "Dobrze",
"very_good": "Bardzo dobrze", "very_good": "Bardzo dobrze",
"great": "Świetne", "great": "Świetne",
"unicum": "Unikalny ", "unicum": "Unikalny",
"super_unicum": "Bardzo unikalny" "super_unicum": "Bardzo unikalny"
} }
}, },
@@ -236,7 +236,7 @@
"description": "Kariera" "description": "Kariera"
}, },
"custom": { "custom": {
"name": "Niestandardowy", "name": "custom",
"description": "Zakres niestandardowy", "description": "Zakres niestandardowy",
"options": { "options": {
"start": { "start": {
@@ -320,9 +320,16 @@
} }
}, },
"commands": { "commands": {
"dead": "# Stat Commands Are Dead\n\nThe BlitzKit Discord bot used to use BlitzStars to fetch periodic statistics. BlitzStars has enabled aggressive Cloudflare protections; hence, disabling BlitzKit from using BlitzStar's API. This has impacted the following commands:\n\n- `/stats`\n- `/today`\n- `/breakdown`\n- `/evolution`\n- `/full-stats`\n\nPlease use [Aftermath](<https://amth.one/invite>) as an alternative which is completely independent of BlitzStars. BlitzKit-hosted stat commands will return in the near future.",
"replay": { "replay": {
"name": "powtórz", "name": "powtórz",
"description": "Wyświetl wyniki powtórki" "description": "Wyświetl wyniki powtórki",
"options": {
"file": {
"name": "file",
"description": "Replay file"
}
}
}, },
"research": { "research": {
"name": "badanie", "name": "badanie",
@@ -849,7 +856,7 @@
"hull": "Hull", "hull": "Hull",
"turret": "Turret", "turret": "Turret",
"hard": "Hard", "hard": "Hard",
"medium": "Medium", "medium": "Średni",
"soft": "Soft", "soft": "Soft",
"effective": "Effective" "effective": "Effective"
}, },
@@ -1005,8 +1012,8 @@
"title": "Characteristics", "title": "Characteristics",
"compare": { "compare": {
"label": "Compare against", "label": "Compare against",
"class": "Tier {tier} {class}", "class": "{tier} {class}",
"tier": "All of tier {tier}", "tier": "All tier {tier}",
"all": "All tanks" "all": "All tanks"
}, },
"values": { "values": {
@@ -1059,19 +1066,19 @@
"speedBackwards": "Backwards", "speedBackwards": "Backwards",
"hardTerrainCoefficient": "Hard", "hardTerrainCoefficient": "Hard",
"hardTerrainCoefficientRaw": "Hard", "hardTerrainCoefficientRaw": "Hard",
"mediumTerrainCoefficient": "Medium", "mediumTerrainCoefficient": "Średni",
"mediumTerrainCoefficientRaw": "Medium", "mediumTerrainCoefficientRaw": "Średni",
"softTerrainCoefficient": "Soft", "softTerrainCoefficient": "Soft",
"softTerrainCoefficientRaw": "Soft", "softTerrainCoefficientRaw": "Soft",
"enginePower": "Engine power", "enginePower": "Engine power",
"powerToWeightRatioHardTerrain": "Hard", "powerToWeightRatioHardTerrain": "Hard",
"powerToWeightRatioMediumTerrain": "Medium", "powerToWeightRatioMediumTerrain": "Średni",
"powerToWeightRatioSoftTerrain": "Soft", "powerToWeightRatioSoftTerrain": "Soft",
"weight": "Weight", "weight": "Weight",
"turretTraverseSpeed": "Turret traverse speed", "turretTraverseSpeed": "Turret traverse speed",
"gunTraverseSpeed": "Gun traverse speed", "gunTraverseSpeed": "Gun traverse speed",
"hullTraverseHardTerrain": "Hard", "hullTraverseHardTerrain": "Hard",
"hullTraverseMediumTerrain": "Medium", "hullTraverseMediumTerrain": "Średni",
"hullTraverseSoftTerrain": "Soft", "hullTraverseSoftTerrain": "Soft",
"health": "Health", "health": "Health",
"fireChance": "Fire chance", "fireChance": "Fire chance",
@@ -1174,7 +1181,7 @@
"table": { "table": {
"tanks": { "tanks": {
"header": "Tank", "header": "Tank",
"total": "Total" "total": "Łącznie"
}, },
"stats": { "stats": {
"winrate": "Śr. wygranych", "winrate": "Śr. wygranych",
@@ -1232,7 +1239,7 @@
}, },
"table": { "table": {
"tank": "Tank", "tank": "Tank",
"total": "Total", "total": "Łącznie",
"remove": "Remove" "remove": "Remove"
} }
}, },
+10 -3
View File
@@ -320,9 +320,16 @@
} }
}, },
"commands": { "commands": {
"dead": "# Stat Commands Are Dead\n\nThe BlitzKit Discord bot used to use BlitzStars to fetch periodic statistics. BlitzStars has enabled aggressive Cloudflare protections; hence, disabling BlitzKit from using BlitzStar's API. This has impacted the following commands:\n\n- `/stats`\n- `/today`\n- `/breakdown`\n- `/evolution`\n- `/full-stats`\n\nPlease use [Aftermath](<https://amth.one/invite>) as an alternative which is completely independent of BlitzStars. BlitzKit-hosted stat commands will return in the near future.",
"replay": { "replay": {
"name": "replay", "name": "replay",
"description": "Exibir resultados de replays" "description": "Exibir resultados de replays",
"options": {
"file": {
"name": "file",
"description": "Replay file"
}
}
}, },
"research": { "research": {
"name": "pesquisa", "name": "pesquisa",
@@ -1005,8 +1012,8 @@
"title": "Características", "title": "Características",
"compare": { "compare": {
"label": "Comparar com", "label": "Comparar com",
"class": "Tier {tier} {class}", "class": "{tier} {class}",
"tier": "All of tier {tier}", "tier": "All tier {tier}",
"all": "Todos os tanques" "all": "Todos os tanques"
}, },
"values": { "values": {
+10 -3
View File
@@ -320,9 +320,16 @@
} }
}, },
"commands": { "commands": {
"dead": ".",
"replay": { "replay": {
"name": "повтор", "name": "повтор",
"description": "Показать результаты повтора" "description": "Показать результаты повтора",
"options": {
"file": {
"name": "Файл",
"description": "Запись повтора"
}
}
}, },
"research": { "research": {
"name": "исследование", "name": "исследование",
@@ -1005,8 +1012,8 @@
"title": "Характеристики", "title": "Характеристики",
"compare": { "compare": {
"label": "Сравнивать с", "label": "Сравнивать с",
"class": "{class} {tier} уровня", "class": "Уровень Тип",
"tier": "Все {tier} уровня", "tier": "Все уровни",
"all": "Все танки" "all": "Все танки"
}, },
"values": { "values": {
+21 -14
View File
@@ -1,7 +1,7 @@
{ {
"test": { "test": {
"default": "тест", "default": "тест",
"long": "{fruit}", "long": "{color}{fruit}",
"fox": "Швидка бура лисиця перестрибує через лінивого собаку." "fox": "Швидка бура лисиця перестрибує через лінивого собаку."
}, },
"common": { "common": {
@@ -23,7 +23,7 @@
}, },
"shells": { "shells": {
"ap": "ББ", "ap": "ББ",
"ap_cr": "APCR", "ap_cr": "",
"hc": "HEAT", "hc": "HEAT",
"he": "ОФ" "he": "ОФ"
}, },
@@ -320,9 +320,16 @@
} }
}, },
"commands": { "commands": {
"dead": ".",
"replay": { "replay": {
"name": "реплей", "name": "реплей",
"description": "Показати результати реплею" "description": "Показати результати реплею",
"options": {
"file": {
"name": "",
"description": "Replay filehttps://crowdin.com/editor/blitzkrieg/80/en-uk?view=side-by-side#9706"
}
}
}, },
"research": { "research": {
"name": "дослідження", "name": "дослідження",
@@ -660,7 +667,7 @@
}, },
"tank_search": { "tank_search": {
"select_dialog_title": "Знайти танк", "select_dialog_title": "Знайти танк",
"swap_dialog_title": "Swap tank", "swap_dialog_title": "Резервуар підміни",
"clear_filters": "Очистити", "clear_filters": "Очистити",
"login": "Увійдіть, щоб скористатися", "login": "Увійдіть, щоб скористатися",
"search_bar_hint": "Шукайте техніку...", "search_bar_hint": "Шукайте техніку...",
@@ -671,8 +678,8 @@
"sorting_by": "Відсортувати за {name}", "sorting_by": "Відсортувати за {name}",
"clear": "Clear", "clear": "Clear",
"tier": "Рівень {tier}", "tier": "Рівень {tier}",
"passive": "{name} (Passive)", "passive": "",
"active": "{name} (Active)", "active": "{name}{name}",
"no_tanks_found": { "no_tanks_found": {
"body": "Нічого не знайдено.", "body": "Нічого не знайдено.",
"search_again": "Спробуйте шукати заново.", "search_again": "Спробуйте шукати заново.",
@@ -735,11 +742,11 @@
}, },
"home": { "home": {
"pwa": { "pwa": {
"title": "Install the BlitzKit App", "title": "",
"new": "", "new": "",
"checking": "Checking compatibility...", "checking": "",
"instructions": "Instructions for Apple devices.", "instructions": ".",
"install": "Install", "install": "",
"dismiss": "Dismiss" "dismiss": "Dismiss"
}, },
"discord": { "discord": {
@@ -823,7 +830,7 @@
"help": { "help": {
"title": "Partially reveal tank?", "title": "Partially reveal tank?",
"description": "Reveals the tank without showing the name. This round will be considered incorrect and break your streak!", "description": "Reveals the tank without showing the name. This round will be considered incorrect and break your streak!",
"cancel": "Cancel", "cancel": "",
"reveal": "Reveal" "reveal": "Reveal"
}, },
"clear": "Clear", "clear": "Clear",
@@ -1005,8 +1012,8 @@
"title": "Characteristics", "title": "Characteristics",
"compare": { "compare": {
"label": "Compare against", "label": "Compare against",
"class": "Tier {tier} {class}", "class": "{tier} {class}",
"tier": "All of tier {tier}", "tier": "All tier {tier}",
"all": "Усі танки" "all": "Усі танки"
}, },
"values": { "values": {
@@ -1274,7 +1281,7 @@
"button": "Generate URL", "button": "Generate URL",
"title": "Generate URL", "title": "Generate URL",
"subtitle": "Search an account to track", "subtitle": "Search an account to track",
"cancel": "Cancel", "cancel": "",
"success": { "success": {
"title": "Copy URL", "title": "Copy URL",
"info": "Generated the URL for {name}. The embed will start tracking as soon as the URL is pasted. Reset at any time by right-clicking on PC or long-pressing on mobile and then selecting \"Reset\".", "info": "Generated the URL for {name}. The embed will start tracking as soon as the URL is pasted. Reset at any time by right-clicking on PC or long-pressing on mobile and then selecting \"Reset\".",
+52 -45
View File
@@ -86,8 +86,8 @@
"uk": "英国", "uk": "英国",
"france": "法国", "france": "法国",
"japan": "日本", "japan": "日本",
"european": "欧洲国家", "european": "欧盟 (E系)",
"other": "混合型国家" "other": "X系"
}, },
"nations_adjectives": { "nations_adjectives": {
"ussr": "苏联", "ussr": "苏联",
@@ -97,34 +97,34 @@
"uk": "英国", "uk": "英国",
"france": "法国", "france": "法国",
"japan": "日本", "japan": "日本",
"european": "欧洲国家", "european": "欧盟 (E系)",
"other": "混合型国家" "other": "X系"
}, },
"tank_class_short": { "tank_class_short": {
"-1": "未识别", "-1": "未识别",
"0": "轻型坦克", "0": "轻型坦克",
"1": "中型坦克", "1": "中型坦克",
"2": "重型战车", "2": "重型战车",
"3": "驱逐战车" "3": "坦克歼击车"
}, },
"tank_class_short_plural_lowercase": { "tank_class_short_plural_lowercase": {
"0": "轻型战车", "0": "轻型坦克",
"1": "中型战车", "1": "中型坦克",
"2": "重型战车", "2": "重型坦克",
"3": "驱逐战车" "3": "坦克歼击车"
}, },
"tank_class_medium": { "tank_class_medium": {
"-1": "未识别", "-1": "未识别",
"0": "轻型战车", "0": "轻型坦克",
"1": "中型战车", "1": "中型坦克",
"2": "重型战车", "2": "重型坦克",
"3": "驱逐战车" "3": "坦克歼击车"
}, },
"tree_type": { "tree_type": {
"-1": "未识别", "-1": "未识别",
"0": "科技树", "0": "科技树",
"1": "加值战车", "1": "金币车",
"2": "收藏家战车" "2": "收藏车"
}, },
"regions": { "regions": {
"normal": { "normal": {
@@ -140,8 +140,8 @@
}, },
"leagues": { "leagues": {
"diamond": "钻石", "diamond": "钻石",
"platinum": "白金", "platinum": "铂金",
"gold": "黃金", "gold": "黄金",
"silver": "白银", "silver": "白银",
"bronze": "青铜" "bronze": "青铜"
}, },
@@ -210,7 +210,7 @@
"good": "好", "good": "好",
"very_good": "非常好", "very_good": "非常好",
"great": "很好", "great": "很好",
"unicum": "極品", "unicum": "极品",
"super_unicum": "超强势玩家" "super_unicum": "超强势玩家"
} }
}, },
@@ -256,7 +256,7 @@
"description": "公会名称或标签" "description": "公会名称或标签"
}, },
"tank": { "tank": {
"name": "战车", "name": "坦克",
"description": "要获取战绩的坦克" "description": "要获取战绩的坦克"
}, },
"username": { "username": {
@@ -269,8 +269,8 @@
"description": "国别" "description": "国别"
}, },
"class": { "class": {
"name": "战车车型", "name": "坦克车型",
"description": "战车车型" "description": "坦克车型"
}, },
"type": { "type": {
"name": "战车类型", "name": "战车类型",
@@ -320,9 +320,16 @@
} }
}, },
"commands": { "commands": {
"dead": "# 统计指令现已停用\nBlitzKit Discord 机器人此前依赖 BlitzStars 获取周期性的统计数据。由于 BlitzStars 已启用严格的 Cloudflare 防护,导致 BlitzKit 无法继续使用其 API。这已导致以下指令失效:\n/统计\n/今日\n/损坏\n/演变\n/完整统计\n请使用 [Aftermath](<https://amth.one/invite>) \n作为完全独立于 BlitzStars 的替代方案。由 BlitzKit 托管的统计指令将在近期内恢复。",
"replay": { "replay": {
"name": "回放", "name": "回放",
"description": "显示重播结果" "description": "显示重播结果",
"options": {
"file": {
"name": "file",
"description": "Replay file"
}
}
}, },
"research": { "research": {
"name": "研发", "name": "研发",
@@ -420,8 +427,8 @@
"description": "玩家数据统计" "description": "玩家数据统计"
}, },
"tank": { "tank": {
"name": "战车", "name": "坦克",
"description": "战车数据统计" "description": "坦克数据统计"
} }
} }
}, },
@@ -557,15 +564,15 @@
}, },
"today": { "today": {
"name": "今日", "name": "今日",
"description": "今天玩过的战车和统计资料" "description": "今天玩过的坦克和统计数据"
}, },
"stats": { "stats": {
"name": "统计资料", "name": "统计资料",
"description": "一段时间内的快速统计" "description": "一段时间内的快速统计"
}, },
"owned_tanks": { "owned_tanks": {
"name": "已拥有的战车", "name": "已拥有的坦克",
"description": "显示该阶级中所有您已拥有的战车", "description": "在一个等级中显示所有已拥有的坦克",
"body": { "body": {
"title": "# {name} 所拥有的 {tier} 阶坦克\n\n" "title": "# {name} 所拥有的 {tier} 阶坦克\n\n"
} }
@@ -578,7 +585,7 @@
"view_channels": "查看频道:需要找到重新整理按钮使用的讯息", "view_channels": "查看频道:需要找到重新整理按钮使用的讯息",
"read_message_history": "读取消息历史: 需要找到消息以刷新", "read_message_history": "读取消息历史: 需要找到消息以刷新",
"attach_files": "附加文件:需要将图像添加到基于图像的命令", "attach_files": "附加文件:需要将图像添加到基于图像的命令",
"external_emojis": "外部表情符号:需要添加国家旗帜和战车类别" "external_emojis": "外部表情:需要添加国家旗帜和坦克等级表情"
} }
}, },
"ping": { "ping": {
@@ -595,7 +602,7 @@
}, },
"wotb": { "wotb": {
"name": "战车世界闪击战", "name": "战车世界闪击战",
"description": "测试战车世界闪击战的ping值(延迟值)" "description": "测试坦克世界闪击战的ping值(延迟值)"
} }
} }
}, },
@@ -666,7 +673,7 @@
"search_bar_hint": "搜寻战车……", "search_bar_hint": "搜寻战车……",
"recent": "最近查看的", "recent": "最近查看的",
"select_all": "全选", "select_all": "全选",
"count_singular": "1辆战车", "count_singular": "1 辆坦克",
"count_plural": "{count} 辆坦克", "count_plural": "{count} 辆坦克",
"sorting_by": "按 {name} 排序", "sorting_by": "按 {name} 排序",
"clear": "清除", "clear": "清除",
@@ -674,7 +681,7 @@
"passive": "{name} (被动)", "passive": "{name} (被动)",
"active": "{name} (主动)", "active": "{name} (主动)",
"no_tanks_found": { "no_tanks_found": {
"body": "找不到战车。", "body": "未找到坦克。",
"search_again": "请尝试重新搜索。", "search_again": "请尝试重新搜索。",
"clear_filters": "请尝试清除筛选。" "clear_filters": "请尝试清除筛选。"
}, },
@@ -688,11 +695,11 @@
"survivability.volume": "体积", "survivability.volume": "体积",
"survivability.length": "长度", "survivability.length": "长度",
"fire.dpm": "标准炮弹 每分钟伤害血量", "fire.dpm": "标准炮弹 每分钟伤害血量",
"fire.dpmPremium": "加值炮弹 每分钟伤害血量", "fire.dpmPremium": "金币炮弹 每分钟伤害值",
"fire.reload": "装填时间", "fire.reload": "装填时间",
"fire.caliber": "口径", "fire.caliber": "口径",
"fire.standardPenetration": "标准炮弹穿透力", "fire.standardPenetration": "标准炮弹穿透力",
"fire.premiumPenetration": "加值炮弹穿透力", "fire.premiumPenetration": "金币炮弹穿透力",
"fire.tertiaryPenetration": "第三种炮弹穿透力", "fire.tertiaryPenetration": "第三种炮弹穿透力",
"fire.damage": "炮弹伤害值", "fire.damage": "炮弹伤害值",
"fire.moduleDamage": "模组伤害值", "fire.moduleDamage": "模组伤害值",
@@ -743,8 +750,8 @@
"dismiss": "取消" "dismiss": "取消"
}, },
"discord": { "discord": {
"join_short": "加入超級酷的人群!", "join_short": "加入超级酷的人群!",
"join_long": "加入我們的Discord社群,和超酷的人一起聊天!", "join_long": "加入我们的Discord社群,和超酷的人一起聊天!",
"action": "Discord" "action": "Discord"
}, },
"hero": { "hero": {
@@ -758,7 +765,7 @@
"title": "其他酷炫的项目" "title": "其他酷炫的项目"
}, },
"help_translate": "请帮忙将 {name} 翻译成 {language}!", "help_translate": "请帮忙将 {name} 翻译成 {language}!",
"seo_description": "战车世界闪击战 全方位工具" "seo_description": "坦克世界闪击战 全方位工具"
}, },
"docs": { "docs": {
"patches": { "patches": {
@@ -793,7 +800,7 @@
"proceed": "继续操作" "proceed": "继续操作"
}, },
"generate": "创建游玩列表", "generate": "创建游玩列表",
"name": "遊玩列表", "name": "游玩列表",
"description": "坦克游玩列表生成器", "description": "坦克游玩列表生成器",
"seo_description": "想不到在坦克世界闪击战玩什么? 使用BlitzKit 游玩列表来获得建议!", "seo_description": "想不到在坦克世界闪击战玩什么? 使用BlitzKit 游玩列表来获得建议!",
"button": "生成", "button": "生成",
@@ -954,7 +961,7 @@
"type": "战车类型", "type": "战车类型",
"dev_id": "开发参数:ID", "dev_id": "开发参数:ID",
"purchase": "购买价格", "purchase": "购买价格",
"restoration": "恢复战车价格", "restoration": "恢复坦克价格",
"sale": "出售价格", "sale": "出售价格",
"research": "研发所需经验" "research": "研发所需经验"
}, },
@@ -1005,8 +1012,8 @@
"title": "属性", "title": "属性",
"compare": { "compare": {
"label": "比较", "label": "比较",
"class": "{tier}阶 {class}", "class": "{tier} {class}",
"tier": "所有 {tier} 阶", "tier": "All tier {tier}",
"all": "所有坦克" "all": "所有坦克"
}, },
"values": { "values": {
@@ -1049,10 +1056,10 @@
"dispersionGunDamaged": "主炮损坏时", "dispersionGunDamaged": "主炮损坏时",
"gunDepression": "俯角", "gunDepression": "俯角",
"gunElevation": "仰角", "gunElevation": "仰角",
"gunFrontalDepression": "车辆前部俯角", "gunFrontalDepression": "车体前部俯角",
"gunFrontalElevation": "车辆前部仰角", "gunFrontalElevation": "车体前部仰角",
"gunRearDepression": "车辆後部俯角", "gunRearDepression": "车体后部俯角",
"gunRearElevation": "车辆後部仰角", "gunRearElevation": "车体后部仰角",
"azimuthLeft": "左射界", "azimuthLeft": "左射界",
"azimuthRight": "右射界", "azimuthRight": "右射界",
"speedForwards": "最高前进速度", "speedForwards": "最高前进速度",
@@ -1252,7 +1259,7 @@
"description": "用于串流的嵌入", "description": "用于串流的嵌入",
"seo_description": "创建具有极高自订化的《坦克世界:闪击战》串流嵌入功能", "seo_description": "创建具有极高自订化的《坦克世界:闪击战》串流嵌入功能",
"coming_soon": "更多嵌入功能即将推出。", "coming_soon": "更多嵌入功能即将推出。",
"suggest": "建议与想法", "suggest": "建议与想法。",
"preview": { "preview": {
"title": "示例预览", "title": "示例预览",
"upload": "上传测试背景" "upload": "上传测试背景"
@@ -12,12 +12,14 @@ export async function tankModels() {
using uploader = new AssetUploader("tank models"); using uploader = new AssetUploader("tank models");
const nodeIO = new NodeIO().registerExtensions(ALL_EXTENSIONS); const nodeIO = new NodeIO().registerExtensions(ALL_EXTENSIONS);
const nations = vfs.dir(`Data/XML/item_defs/vehicles`).filter((nation) => nation !== "common") const nations = vfs
.dir(`Data/XML/item_defs/vehicles`)
.filter((nation) => nation !== "common");
for (const nationIndex in nations) { for (const nationIndex in nations) {
const nation = nations[nationIndex]; const nation = nations[nationIndex];
const tanks = await vfs.xml<{ root: VehicleDefinitionList }>( const tanks = await vfs.xml<{ root: VehicleDefinitionList }>(
`Data/XML/item_defs/vehicles/${nation}/list.xml` `Data/XML/item_defs/vehicles/${nation}/list.xml`,
); );
const entries = Object.entries(tanks.root); const entries = Object.entries(tanks.root);
@@ -26,11 +28,11 @@ export async function tankModels() {
const id = toUniqueId(nation, tank.id); const id = toUniqueId(nation, tank.id);
const parameters = await vfs.yaml<TankParameters>( const parameters = await vfs.yaml<TankParameters>(
`Data/3d/Tanks/Parameters/${nation}/${tankKey}.yaml` `Data/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`,
); );
const model = await extractModel( const model = await extractModel(
vfs, vfs,
parameters.resourcesPath.blitzModelPath.replace(/\.sc2$/, "") parameters.resourcesPath.blitzModelPath.replace(/\.sc2$/, ""),
); );
const content = Buffer.from(await nodeIO.writeBinary(model)); const content = Buffer.from(await nodeIO.writeBinary(model));
@@ -6,13 +6,13 @@ import {
} from "@blitzkit/core"; } from "@blitzkit/core";
import { Accessor, Document, Node, Scene } from "@gltf-transform/core"; import { Accessor, Document, Node, Scene } from "@gltf-transform/core";
import { times } from "lodash-es"; import { times } from "lodash-es";
import { SteamVFS } from "../vfs/steam"; import { AbstractVFS } from "../vfs/abstract";
import { import {
vertexAttributeGLTFName, vertexAttributeGLTFName,
vertexAttributeGltfVectorSizes, vertexAttributeGltfVectorSizes,
} from "./extractModel/constants"; } from "./extractModel/constants";
export async function extractArmor(vfs: SteamVFS, fileName: string) { export async function extractArmor(vfs: AbstractVFS, fileName: string) {
const sc2Path = `Data/3d/Tanks/CollisionMeshes/${fileName}.sc2`; const sc2Path = `Data/3d/Tanks/CollisionMeshes/${fileName}.sc2`;
const scgPath = `Data/3d/Tanks/CollisionMeshes/${fileName}.scg`; const scgPath = `Data/3d/Tanks/CollisionMeshes/${fileName}.scg`;
const sc2 = new Sc2ReadStream( const sc2 = new Sc2ReadStream(
@@ -1,4 +1,5 @@
import { import {
bufferToBigInt,
Hierarchy, Hierarchy,
Sc2ReadStream, Sc2ReadStream,
ScgReadStream, ScgReadStream,
@@ -9,7 +10,7 @@ import { Document, Material, Node, Scene } from "@gltf-transform/core";
import { dedup, prune } from "@gltf-transform/functions"; import { dedup, prune } from "@gltf-transform/functions";
import { times } from "lodash-es"; import { times } from "lodash-es";
import { dirname } from "path"; import { dirname } from "path";
import { SteamVFS } from "../../vfs/steam"; import { AbstractVFS } from "../../vfs/abstract";
import { readBaseColor } from "../readBaseColor"; import { readBaseColor } from "../readBaseColor";
import { readNormal } from "../readNormal"; import { readNormal } from "../readNormal";
import { readRoughnessMetallic } from "../readRoughnessMetallic"; import { readRoughnessMetallic } from "../readRoughnessMetallic";
@@ -25,7 +26,7 @@ const omitMeshNames = {
end: ["_POINT"], end: ["_POINT"],
}; };
export async function extractModel(vfs: SteamVFS, path: string) { export async function extractModel(vfs: AbstractVFS, path: string) {
const sc2Path = `Data/3d/${path}.sc2`; const sc2Path = `Data/3d/${path}.sc2`;
const scgPath = `Data/3d/${path}.scg`; const scgPath = `Data/3d/${path}.scg`;
const sc2 = new Sc2ReadStream( const sc2 = new Sc2ReadStream(
@@ -40,7 +41,7 @@ export async function extractModel(vfs: SteamVFS, path: string) {
const materials = new Map<bigint, Material | bigint | undefined>(); const materials = new Map<bigint, Material | bigint | undefined>();
for (const node of sc2["#dataNodes"]) { for (const node of sc2["#dataNodes"]) {
const id = new DataView(node["#id"]).getBigUint64(0, true); const id = bufferToBigInt(node["#id"]);
if (node.parentMaterialKey !== undefined) { if (node.parentMaterialKey !== undefined) {
/** /**
@@ -80,8 +81,20 @@ export async function extractModel(vfs: SteamVFS, path: string) {
configIndex < node.configCount; configIndex < node.configCount;
configIndex++ configIndex++
) { ) {
if (!hasAlpha) continue; const archive = node[`configArchive_${configIndex}`];
material.setAlphaMode("BLEND").setDoubleSided(true);
if (
archive.configName !== "Default" ||
!archive.enabledPresets?.AlphaTest
) {
continue;
}
const view = new DataView(archive.properties.alphatestThreshold);
const _ = view.getUint8(0); // only god knows what these flags are
const alphaCutoff = view.getFloat32(1, true);
material.setAlphaMode("MASK").setAlphaCutoff(alphaCutoff);
} }
} }
@@ -116,7 +129,7 @@ export async function extractModel(vfs: SteamVFS, path: string) {
); );
} }
materials.set(new DataView(node["#id"]).getBigUint64(0, true), material); materials.set(id, material);
} }
} }
@@ -157,6 +170,7 @@ export async function extractModel(vfs: SteamVFS, path: string) {
break; break;
case "TransformComponent": { case "TransformComponent": {
component["tc.localTranslation"] = [0, 0, 0]; // Game when is loading model resets position to [0, 0, 0], so any set value is ignored.
node.setTranslation(component["tc.localTranslation"]); node.setTranslation(component["tc.localTranslation"]);
node.setRotation(component["tc.localRotation"]); node.setRotation(component["tc.localRotation"]);
node.setScale(component["tc.localScale"]); node.setScale(component["tc.localScale"]);
+10 -5
View File
@@ -5,6 +5,7 @@ import {
Video, Video,
youtubers, youtubers,
} from "@blitzkit/core"; } from "@blitzkit/core";
import locales from "@blitzkit/i18n/locales.json";
import { google } from "googleapis"; import { google } from "googleapis";
import { cloneDeep, uniqBy } from "lodash-es"; import { cloneDeep, uniqBy } from "lodash-es";
import { AssetUploader } from "./core/github/assetUploader"; import { AssetUploader } from "./core/github/assetUploader";
@@ -42,9 +43,11 @@ for (const tank of tanksSanitized) {
} }
try { try {
const q = `World of Tanks Blitz ${tank.name.locales[locales.default]}`;
const results = await youtube.search.list({ const results = await youtube.search.list({
part: ["snippet"], part: ["snippet"],
q: `World of Tanks Blitz ${tank.name}`, q,
}); });
if (!results.data.items || results.data.items.length === 0) continue; if (!results.data.items || results.data.items.length === 0) continue;
@@ -54,9 +57,9 @@ for (const tank of tanksSanitized) {
(item) => (item) =>
item.snippet?.channelId && item.snippet?.channelId &&
item.id?.videoId && item.id?.videoId &&
youtubers.some(({ id }) => id === item.snippet?.channelId) youtubers.some(({ id }) => id === item.snippet?.channelId),
), ),
(item) => item.snippet!.channelId (item) => item.snippet!.channelId,
); );
reviews.reviews[tank.id] = { reviews.reviews[tank.id] = {
@@ -66,11 +69,13 @@ for (const tank of tanksSanitized) {
({ ({
id: item.id!.videoId!, id: item.id!.videoId!,
author: item.snippet!.channelId!, author: item.snippet!.channelId!,
} satisfies Video) }) satisfies Video,
), ),
}; };
console.log(`${++done} done; found ${items.length} for ${tank.name.locales.en}`); console.log(
`${++done} done; found ${items.length} for ${tank.name.locales.en}`,
);
await new Promise((resolve) => setTimeout(resolve, 1000 / 5)); await new Promise((resolve) => setTimeout(resolve, 1000 / 5));
} catch (error) { } catch (error) {
@@ -14,7 +14,7 @@ import { StickyRowHeaderCell } from "../StickyRowHeaderCell";
type CompareRowProps = { type CompareRowProps = {
display?: ( display?: (
member: Awaited<TankCharacteristics> member: Awaited<TankCharacteristics>,
) => number | string | undefined; ) => number | string | undefined;
deltaType?: CompareCellDirection; deltaType?: CompareCellDirection;
decimals?: number; decimals?: number;
@@ -46,7 +46,7 @@ export function CompareRow({
const values = stats.map((stat) => const values = stats.map((stat) =>
typeof props.value === "function" typeof props.value === "function"
? props.value(stat)! ? props.value(stat)!
: (stat[props.value] as number) : (stat[props.value] as number),
); );
const id = useRef(Math.random()); const id = useRef(Math.random());
const name = const name =
@@ -56,10 +56,10 @@ export function CompareRow({
return ( return (
<Table.Row> <Table.Row>
<StickyRowHeaderCell> <StickyRowHeaderCell left="-1.25rem">
<Flex <Flex
align="center" align="center"
style={{ whiteSpace: "nowrap" }} maxWidth={{ initial: "6rem", sm: "unset" }}
gap={indent ? { initial: "4", sm: "6" } : { initial: "1", sm: "2" }} gap={indent ? { initial: "4", sm: "6" } : { initial: "1", sm: "2" }}
> >
<IconButton <IconButton
@@ -111,8 +111,8 @@ export function CompareRow({
const resolvedDisplayValue = display const resolvedDisplayValue = display
? display(stats[index]) ? display(stats[index])
: decimals === undefined : decimals === undefined
? value ? value
: value?.toFixed(decimals); : value?.toFixed(decimals);
return ( return (
<CompareCell <CompareCell
@@ -145,8 +145,8 @@ export function CompareRow({
deltaNominalDisplay deltaNominalDisplay
? deltaNominalDisplay(delta) ? deltaNominalDisplay(delta)
: decimals === undefined : decimals === undefined
? delta ? delta
: delta.toFixed(decimals) : delta.toFixed(decimals)
}`} }`}
) )
</Text> </Text>
@@ -156,7 +156,7 @@ export function CompareRow({
<Text color="gray"> <Text color="gray">
( (
{`${deltaPercentage > 0 ? "+" : ""}${Math.round( {`${deltaPercentage > 0 ? "+" : ""}${Math.round(
deltaPercentage * 100 deltaPercentage * 100,
)}%`} )}%`}
) )
</Text> </Text>
@@ -1,7 +1,7 @@
import { Heading, Table } from '@radix-ui/themes'; import { Heading, Table } from "@radix-ui/themes";
import { times } from 'lodash-es'; import { times } from "lodash-es";
import { CompareEphemeral } from '../../stores/compareEphemeral'; import { CompareEphemeral } from "../../stores/compareEphemeral";
import { StickyColumnHeaderCell } from '../StickyColumnHeaderCell'; import { StickyColumnHeaderCell } from "../StickyColumnHeaderCell";
export function CompareSectionTitle({ children }: { children: string }) { export function CompareSectionTitle({ children }: { children: string }) {
const memberCount = CompareEphemeral.use((state) => state.members.length); const memberCount = CompareEphemeral.use((state) => state.members.length);
@@ -105,13 +105,13 @@ export function CompareTable({ stats }: CompareTableProps) {
crewSkills[skill] === 0 crewSkills[skill] === 0
? Var("gray-11") ? Var("gray-11")
: permanentSkills.includes(skill) : permanentSkills.includes(skill)
? Var("crimson-11") ? Var("crimson-11")
: Var("amber-11"), : Var("amber-11"),
}} }}
/> />
))} ))}
</Flex> </Flex>
) ),
)} )}
</Flex> </Flex>
</Button> </Button>
@@ -173,19 +173,19 @@ export function CompareTable({ stats }: CompareTableProps) {
consumables, consumables,
camouflage, camouflage,
}, },
index index,
) => { ) => {
const equipmentPreset = const equipmentPreset =
equipmentDefinitions.presets[tank.equipment_preset]; equipmentDefinitions.presets[tank.equipment_preset];
const provisionsList = availableProvisions( const provisionsList = availableProvisions(
tank, tank,
gun, gun,
provisionDefinitions provisionDefinitions,
); );
const consumablesList = Object.values( const consumablesList = Object.values(
consumableDefinitions.consumables consumableDefinitions.consumables,
).filter((consumable) => ).filter((consumable) =>
checkConsumableProvisionInclusivity(consumable, tank, gun) checkConsumableProvisionInclusivity(consumable, tank, gun),
); );
return ( return (
@@ -294,11 +294,11 @@ export function CompareTable({ stats }: CompareTableProps) {
<img <img
alt={unwrap( alt={unwrap(
provisionDefinitions.provisions[provision] provisionDefinitions.provisions[provision]
.name .name,
)} )}
key={provision} key={provision}
src={asset( src={asset(
`/icons/provisions/${provision}.webp` `/icons/provisions/${provision}.webp`,
)} )}
style={{ style={{
left: "50%", left: "50%",
@@ -335,7 +335,7 @@ export function CompareTable({ stats }: CompareTableProps) {
<Popover.Content> <Popover.Content>
<ProvisionsManager <ProvisionsManager
provisions={provisionsList.map( provisions={provisionsList.map(
(provision) => provision.id (provision) => provision.id,
)} )}
selected={provisions} selected={provisions}
disabled={tank.max_provisions === provisions.length} disabled={tank.max_provisions === provisions.length}
@@ -452,7 +452,7 @@ export function CompareTable({ stats }: CompareTableProps) {
CompareEphemeral.mutate((draft) => { CompareEphemeral.mutate((draft) => {
draft.members[index].equipmentMatrix = times( draft.members[index].equipmentMatrix = times(
3, 3,
() => times(3, () => 0) () => times(3, () => 0),
) as EquipmentMatrix; ) as EquipmentMatrix;
draft.sorting = undefined; draft.sorting = undefined;
}); });
@@ -501,11 +501,11 @@ export function CompareTable({ stats }: CompareTableProps) {
alt={unwrap( alt={unwrap(
consumableDefinitions.consumables[ consumableDefinitions.consumables[
consumable consumable
].name ].name,
)} )}
key={consumable} key={consumable}
src={asset( src={asset(
`/icons/consumables/${consumable}.webp` `/icons/consumables/${consumable}.webp`,
)} )}
style={{ style={{
left: "50%", left: "50%",
@@ -610,7 +610,7 @@ export function CompareTable({ stats }: CompareTableProps) {
</Flex> </Flex>
</Table.Cell> </Table.Cell>
); );
} },
)} )}
</Table.Row> </Table.Row>
</Table.Body> </Table.Body>
@@ -55,7 +55,7 @@ export function TankCard({ index, tank }: TankCardProps) {
}; };
}); });
const closest = distances.reduce((a, b) => const closest = distances.reduce((a, b) =>
a.distance < b.distance ? a : b a.distance < b.distance ? a : b,
); );
dropIndex = closest.index; dropIndex = closest.index;
@@ -85,8 +85,8 @@ export function TankCard({ index, tank }: TankCardProps) {
CompareEphemeral.mutate((draft) => { CompareEphemeral.mutate((draft) => {
const fromIndex = index; const fromIndex = index;
const toIndex = dropIndex > index ? dropIndex - 1 : dropIndex; const toIndex = dropIndex > index ? dropIndex - 1 : dropIndex;
const member = draft.members[fromIndex]; const member = draft.members[fromIndex];
draft.members.splice(fromIndex, 1); draft.members.splice(fromIndex, 1);
draft.members.splice(toIndex, 0, member); draft.members.splice(toIndex, 0, member);
}); });
@@ -99,7 +99,7 @@ export function TankCard({ index, tank }: TankCardProps) {
return () => { return () => {
draggable.current?.removeEventListener("pointerdown", handlePointerDown); draggable.current?.removeEventListener("pointerdown", handlePointerDown);
}; };
}, []); }, [index]);
return ( return (
<StickyColumnHeaderCell width="0"> <StickyColumnHeaderCell width="0">
@@ -141,7 +141,6 @@ export function TankCard({ index, tank }: TankCardProps) {
</Text> </Text>
</Flex> </Flex>
</Flex> </Flex>
<InsertionMarker index={index + 1} /> <InsertionMarker index={index + 1} />
</StickyColumnHeaderCell> </StickyColumnHeaderCell>
); );
@@ -84,7 +84,7 @@ export function Model() {
useEffect(() => { useEffect(() => {
function handleModelTransform( function handleModelTransform(
event: QuicklimeEvent<ModelTransformEventData> event: QuicklimeEvent<ModelTransformEventData>,
) { ) {
turretGroup.current.rotation.z = event.data.yaw; turretGroup.current.rotation.z = event.data.yaw;
gunGroup.current.rotation.x = event.data.pitch; gunGroup.current.rotation.x = event.data.pitch;
@@ -171,6 +171,49 @@ export function Model() {
turretModel.gun_origin.y, turretModel.gun_origin.y,
]} ]}
> >
<group
position={[
-turretTankModel.turret_origin.x -
turretTrackModel.origin.x -
turretModel.gun_origin.x,
-turretTankModel.turret_origin.z -
turretTrackModel.origin.z -
turretModel.gun_origin.z,
-turretTankModel.turret_origin.y -
turretTrackModel.origin.y -
turretModel.gun_origin.y,
]}
>
{turretNodes.map((node) => {
const isCurrentMantlet =
node.name ===
`gun_${turretModel.guns[turret.turret.guns.at(-1)!.id].model_id.toString().padStart(2, "0")}_mask`;
// const isCurrentGun =
// node.name ===
// `gun_${gunModel.model_id.toString().padStart(2, "0")}`;
const isVisible = isCurrentMantlet;
if (!isVisible) return null;
return jsxTree(node, {
mesh(_, props, key) {
return (
<mesh
{...props}
key={key}
castShadow
receiveShadow
onPointerDown={(event) => {
enablePitchRotation.current = true;
handlePointerDown(event);
}}
/>
);
},
});
})}
</group>
<group <group
position={[ position={[
-gunTankModel.turret_origin.x - -gunTankModel.turret_origin.x -
@@ -185,13 +228,13 @@ export function Model() {
]} ]}
> >
{gunNodes.map((node) => { {gunNodes.map((node) => {
const isCurrentMantlet = // const isCurrentMantlet =
node.name === // node.name ===
`gun_${gunModel.model_id.toString().padStart(2, "0")}_mask`; // `gun_${gunModel.model_id.toString().padStart(2, "0")}_mask`;
const isCurrentGun = const isCurrentGun =
node.name === node.name ===
`gun_${gunModel.model_id.toString().padStart(2, "0")}`; `gun_${gunModel.model_id.toString().padStart(2, "0")}`;
const isVisible = isCurrentGun || isCurrentMantlet; const isVisible = isCurrentGun;
if (!isVisible) return null; if (!isVisible) return null;
@@ -1,12 +1,16 @@
import { Table } from "@radix-ui/themes"; import { Table } from "@radix-ui/themes";
import type { RowHeaderCellProps } from "@radix-ui/themes/src/components/table.js"; import type { RowHeaderCellProps } from "@radix-ui/themes/src/components/table.js";
export function StickyRowHeaderCell({ style, ...props }: RowHeaderCellProps) { interface Props extends RowHeaderCellProps {
left?: string | number;
}
export function StickyRowHeaderCell({ style, left = 0, ...props }: Props) {
return ( return (
<Table.RowHeaderCell <Table.RowHeaderCell
style={{ style={{
position: "sticky", position: "sticky",
left: 0, left,
backgroundColor: "var(--color-panel)", backgroundColor: "var(--color-panel)",
...style, ...style,
}} }}
@@ -1,12 +1,12 @@
import { youtubers } from '@blitzkit/core'; import { youtubers } from "@blitzkit/core";
import { Box, Flex, Heading, Link, Skeleton, Text } from '@radix-ui/themes'; import { Box, Flex, Heading, Link, Skeleton, Text } from "@radix-ui/themes";
import { awaitableReviews } from '../../core/awaitables/reviews'; import { awaitableReviews } from "../../core/awaitables/reviews";
import { Var } from '../../core/radix/var'; import { Var } from "../../core/radix/var";
import { useLocale } from '../../hooks/useLocale'; import { useLocale } from "../../hooks/useLocale";
import { Duel } from '../../stores/duel'; import { Duel } from "../../stores/duel";
import type { MaybeSkeletonComponentProps } from '../../types/maybeSkeletonComponentProps'; import type { MaybeSkeletonComponentProps } from "../../types/maybeSkeletonComponentProps";
import { PageWrapper } from '../PageWrapper'; import { PageWrapper } from "../PageWrapper";
import { VerifiedIcon } from '../VerifiedIcon'; import { VerifiedIcon } from "../VerifiedIcon";
const reviews = await awaitableReviews; const reviews = await awaitableReviews;
@@ -47,22 +47,22 @@ export function VideoSection({ skeleton }: MaybeSkeletonComponentProps) {
<Flex <Flex
direction="column" direction="column"
style={{ style={{
borderRadius: 'var(--radius-3)', borderRadius: "var(--radius-3)",
overflow: 'hidden', overflow: "hidden",
background: skeleton background: skeleton
? Var('color-panel-solid') ? Var("color-panel-solid")
: `url(${image})`, : `url(${image})`,
backgroundPosition: '0 18px', backgroundPosition: "0 18px",
backgroundSize: 'cover', backgroundSize: "cover",
}} }}
> >
<Box <Box
style={{ style={{
background: skeleton ? 'transparent' : `url(${image})`, background: skeleton ? "transparent" : `url(${image})`,
backgroundSize: 'cover', backgroundSize: "cover",
backgroundPosition: 'center', backgroundPosition: "center",
height: 128, height: 128,
aspectRatio: '16 / 9', aspectRatio: "16 / 9",
}} }}
/> />
<Flex <Flex
@@ -70,8 +70,8 @@ export function VideoSection({ skeleton }: MaybeSkeletonComponentProps) {
width="100%" width="100%"
justify="center" justify="center"
style={{ style={{
backdropFilter: 'blur(4rem)', backdropFilter: "blur(4rem)",
WebkitBackdropFilter: 'blur(4rem)', WebkitBackdropFilter: "blur(4rem)",
}} }}
align="center" align="center"
gap="1" gap="1"
-5
View File
@@ -55,11 +55,6 @@ export const tools: Record<string, Tool> = {
id: "session", id: "session",
button: "blue", button: "blue",
}, },
discord: {
id: "discord",
href: "https://discord.com/application-directory/1097673957865443370",
button: "indigo",
},
tier_list: { tier_list: {
id: "tier-list", id: "tier-list",
strings: "tier_list", strings: "tier_list",
@@ -8,7 +8,7 @@ const tanks = Object.values(tankDefinitions.tanks).filter((tank) => {
const turret = tank.turrets.at(-1)!; const turret = tank.turrets.at(-1)!;
const turretModel = tankModel.turrets[turret.id]; const turretModel = tankModel.turrets[turret.id];
return tank.tier >= 10 && !turretModel.yaw; return !turretModel.yaw;
}); });
export function curateMixer() { export function curateMixer() {
@@ -76,7 +76,7 @@ export function tankCharacteristics(
tankModelDefinition: ModelDefinition; tankModelDefinition: ModelDefinition;
equipmentDefinitions: EquipmentDefinitions; equipmentDefinitions: EquipmentDefinitions;
provisionDefinitions: ProvisionDefinitions; provisionDefinitions: ProvisionDefinitions;
} },
) { ) {
const preset = equipmentDefinitions.presets[tank.equipment_preset]; const preset = equipmentDefinitions.presets[tank.equipment_preset];
const turretModelDefinition = tankModelDefinition.turrets[turret.id]; const turretModelDefinition = tankModelDefinition.turrets[turret.id];
@@ -138,7 +138,7 @@ export function tankCharacteristics(
provisionDefinitions.provisions[provision].crew provisionDefinitions.provisions[provision].crew
? total + provisionDefinitions.provisions[provision].crew! / 100 ? total + provisionDefinitions.provisions[provision].crew! / 100
: total, : total,
0 0,
) + (hasImprovedVentilation ? 0.08 : 0); ) + (hasImprovedVentilation ? 0.08 : 0);
const commanderMastery = 1 + provisionCrewBonus; const commanderMastery = 1 + provisionCrewBonus;
const loaderMastery = const loaderMastery =
@@ -158,13 +158,13 @@ export function tankCharacteristics(
coefficient( coefficient(
[applyReactiveArmor && shell.type !== ShellType.HE, -0.27], [applyReactiveArmor && shell.type !== ShellType.HE, -0.27],
[applyDynamicArmor, -0.1], [applyDynamicArmor, -0.1],
[applySpallLiner && shell.type === ShellType.HE, -0.2] [applySpallLiner && shell.type === ShellType.HE, -0.2],
); );
const assaultDamageCoefficient = const assaultDamageCoefficient =
gun.assault_ranges && gun.assault_ranges.types.includes(shell.type) gun.assault_ranges && gun.assault_ranges.types.includes(shell.type)
? gun.assault_ranges.ranges.find( ? (gun.assault_ranges.ranges.find(
({ distance }) => distance >= assaultDistance ({ distance }) => distance >= assaultDistance,
)?.factor ?? 0 )?.factor ?? 0)
: 1; : 1;
const moduleDamageCoefficient = coefficient([hasTungsten, 0.15]); const moduleDamageCoefficient = coefficient([hasTungsten, 0.15]);
const reloadCoefficient = const reloadCoefficient =
@@ -178,16 +178,16 @@ export function tankCharacteristics(
const healthCoefficient = coefficient( const healthCoefficient = coefficient(
[hasSandbagArmor, 0.03], [hasSandbagArmor, 0.03],
[hasEnhancedSandbagArmor, 0.06], [hasEnhancedSandbagArmor, 0.06],
[hasImprovedAssembly, 0.04] [hasImprovedAssembly, 0.04],
); );
const shellVelocityCoefficient = coefficient( const shellVelocityCoefficient = coefficient(
[hasSupercharger, 0.35], [hasSupercharger, 0.35],
[hasImprovedGunPowder, 0.3] [hasImprovedGunPowder, 0.3],
); );
const aimTimeCoefficient = const aimTimeCoefficient =
coefficient( coefficient(
[hasEnhancedGunLayingDrive, -0.15], [hasEnhancedGunLayingDrive, -0.15],
[hasReticleCalibration, -0.3] [hasReticleCalibration, -0.3],
) * coefficient([true, degressiveStat(gunnerMastery)]); ) * coefficient([true, degressiveStat(gunnerMastery)]);
const dispersionStillCoefficient = const dispersionStillCoefficient =
coefficient([hasRefinedGun, -0.15], [hasReticleCalibration, -0.3]) * coefficient([hasRefinedGun, -0.15], [hasReticleCalibration, -0.3]) *
@@ -197,20 +197,20 @@ export function tankCharacteristics(
-0.12, -0.12,
]); ]);
const dispersionHullTraverseCoefficient = coefficient( const dispersionHullTraverseCoefficient = coefficient(
[hasVerticalStabilizer, -0.15], [hasVerticalStabilizer, -0.12],
[true, -crewSkills.smooth_turn / 100] [true, -crewSkills.smooth_turn / 100],
); );
const dispersionAfterShotCoefficient = coefficient( const dispersionAfterShotCoefficient = coefficient(
[hasVerticalStabilizer, -0.15], [hasVerticalStabilizer, -0.12],
[true, -crewSkills.soft_recoil / 100] [true, -crewSkills.soft_recoil / 100],
); );
const dispersionMovingCoefficient = coefficient( const dispersionMovingCoefficient = coefficient(
[hasVerticalStabilizer, -0.15], [hasVerticalStabilizer, -0.12],
[true, -crewSkills.smooth_driving / 100] [true, -crewSkills.smooth_driving / 100],
); );
const dispersionTurretTraverseCoefficient = coefficient( const dispersionTurretTraverseCoefficient = coefficient(
[hasVerticalStabilizer, -0.15], [hasVerticalStabilizer, -0.12],
[true, -crewSkills.smooth_turret * (2 / 100)] [true, -crewSkills.smooth_turret * (2 / 100)],
); );
const enginePowerCoefficient = coefficient( const enginePowerCoefficient = coefficient(
[ [
@@ -225,7 +225,7 @@ export function tankCharacteristics(
[hasStandardFuel, 0.03], [hasStandardFuel, 0.03],
[hasImprovedFuel, 0.1], [hasImprovedFuel, 0.1],
[hasGearOil, 0.03], [hasGearOil, 0.03],
[hasImprovedGearOil, 0.06] [hasImprovedGearOil, 0.06],
); );
const turretTraverseCoefficient = const turretTraverseCoefficient =
coefficient([hasStandardFuel, 0.03], [hasImprovedFuel, 0.1]) * coefficient([hasStandardFuel, 0.03], [hasImprovedFuel, 0.1]) *
@@ -234,7 +234,7 @@ export function tankCharacteristics(
coefficient( coefficient(
[hasImprovedControl, 0.11], [hasImprovedControl, 0.11],
[hasImprovedEnginePowerBoost, 0.01], [hasImprovedEnginePowerBoost, 0.01],
[true, crewSkills.virtuoso / 100] [true, crewSkills.virtuoso / 100],
) * coefficient([true, progressiveStat(driverMastery)]); ) * coefficient([true, progressiveStat(driverMastery)]);
const resistanceHardCoefficient = const resistanceHardCoefficient =
@@ -253,22 +253,22 @@ export function tankCharacteristics(
tank.class === TankClass.HEAVY tank.class === TankClass.HEAVY
? 0.06 ? 0.06
: tank.class === TankClass.MEDIUM : tank.class === TankClass.MEDIUM
? 0.08 ? 0.08
: tank.class === TankClass.LIGHT : tank.class === TankClass.LIGHT
? 0.11 ? 0.11
: 0.04, : 0.04,
]) * coefficient([true, progressiveStat(commanderMastery)]); ]) * coefficient([true, progressiveStat(commanderMastery)]);
const fireChanceCoefficient = coefficient([hasProtectiveKit, -0.2]); const fireChanceCoefficient = coefficient([hasProtectiveKit, -0.2]);
const speedForwardsSum = sum( const speedForwardsSum = sum(
[hasImprovedEnginePowerBoost, 4], [hasImprovedEnginePowerBoost, 4],
[hasGearOil, 2], [hasGearOil, 2],
[hasImprovedGearOil, 4] [hasImprovedGearOil, 4],
); );
const speedBackwardsSum = sum( const speedBackwardsSum = sum(
[hasImprovedEnginePowerBoost, 8], [hasImprovedEnginePowerBoost, 8],
[hasGearOil, 2], [hasGearOil, 2],
[hasImprovedGearOil, 4] [hasImprovedGearOil, 4],
); );
const camouflageCoefficientMoving = sum( const camouflageCoefficientMoving = sum(
[ [
@@ -278,8 +278,8 @@ export function tankCharacteristics(
tank.class === TankClass.TANK_DESTROYER tank.class === TankClass.TANK_DESTROYER
? 0.04 ? 0.04
: tank.class === TankClass.HEAVY : tank.class === TankClass.HEAVY
? 0.03 ? 0.03
: 0.02, : 0.02,
]), ]),
], ],
[ [
@@ -287,11 +287,11 @@ export function tankCharacteristics(
tank.class === TankClass.HEAVY tank.class === TankClass.HEAVY
? 0.02 ? 0.02
: tank.class === TankClass.MEDIUM : tank.class === TankClass.MEDIUM
? 0.04 ? 0.04
: tank.class === TankClass.LIGHT : tank.class === TankClass.LIGHT
? 0.07 ? 0.07
: 0.1, : 0.1,
] ],
); );
const camouflageCoefficientStill = sum( const camouflageCoefficientStill = sum(
[ [
@@ -302,10 +302,10 @@ export function tankCharacteristics(
tank.class === TankClass.TANK_DESTROYER tank.class === TankClass.TANK_DESTROYER
? 0.04 ? 0.04
: tank.class === TankClass.HEAVY : tank.class === TankClass.HEAVY
? 0.03 ? 0.03
: 0.02, : 0.02,
], ],
[true, crewSkills.camouflage * (3 / 100)] [true, crewSkills.camouflage * (3 / 100)],
), ),
], ],
[ [
@@ -313,18 +313,18 @@ export function tankCharacteristics(
tank.class === TankClass.HEAVY tank.class === TankClass.HEAVY
? 0.04 ? 0.04
: tank.class === TankClass.MEDIUM : tank.class === TankClass.MEDIUM
? 0.08 ? 0.08
: tank.class === TankClass.LIGHT : tank.class === TankClass.LIGHT
? 0.14 ? 0.14
: 0.2, : 0.2,
] ],
); );
const size = normalizeBoundingBox( const size = normalizeBoundingBox(
unionBoundingBox( unionBoundingBox(
tankModelDefinition.bounding_box, tankModelDefinition.bounding_box,
turretModelDefinition.bounding_box turretModelDefinition.bounding_box,
) ),
); );
const weightKg = const weightKg =
tank.weight + engine.weight + track.weight + turret.weight + gun.weight; tank.weight + engine.weight + track.weight + turret.weight + gun.weight;
@@ -344,7 +344,7 @@ export function tankCharacteristics(
shell, shell,
damageCoefficient, damageCoefficient,
reloadCoefficient, reloadCoefficient,
intraClipCoefficient intraClipCoefficient,
); );
const intraClip = const intraClip =
gun.gun_type?.$case === "regular" gun.gun_type?.$case === "regular"
@@ -398,7 +398,7 @@ export function tankCharacteristics(
const shellReloads = const shellReloads =
gun.gun_type!.$case === "auto_reloader" gun.gun_type!.$case === "auto_reloader"
? gun.gun_type!.value.shell_reloads.map( ? gun.gun_type!.value.shell_reloads.map(
(reload) => reload * reloadCoefficient (reload) => reload * reloadCoefficient,
) )
: undefined; : undefined;
const shellReload = const shellReload =