Compare commits

...
27 Commits
Author SHA1 Message Date
TrèsAbhi 5dba69f30a New Crowdin updates (#210) 2026-02-03 15:00:55 -06:00
TrèsAbhi 023c348b0d New Crowdin updates (#209)
* New translations en.json (French)

* New translations en.json (French)

* New translations en.json (Russian)
2026-01-21 11:00:30 -06:00
TrèsAbhi 8313dd811d feat: logs 2026-01-16 19:31:53 -06:00
TrèsAbhi 3f7d10c2c6 fix: guesser sizing 2026-01-16 19:31:09 -06:00
TrèsAbhi 98e83d120a New translations en.json (Russian) (#208) 2026-01-16 18:46:33 -06:00
TrèsAbhi dd845d2784 New translations en.json (Chinese Simplified) (#207) 2026-01-12 22:06:59 -06:00
TrèsAbhi dc15df2a40 New Crowdin updates (#206) 2026-01-09 14:06:11 -06:00
TrèsAbhi 7c838aaf06 New Crowdin updates (#205)
* New translations en.json (French)

* New translations en.json (Spanish)

* New translations en.json (Japanese)

* New translations en.json (Polish)

* New translations en.json (Portuguese)

* New translations en.json (Russian)

* New translations en.json (Ukrainian)

* New translations en.json (Chinese Simplified)
2026-01-08 10:13:55 -06:00
TrèsAbhi f181c96518 Merge pull request #204 from blitzkit/dev
dev
2026-01-08 03:48:00 +00:00
TrèsAbhi 6fbfa2c70b Merge branch 'main' into dev 2026-01-07 20:16:06 -06:00
TrèsAbhi 17dd50b902 feat: logs 2026-01-07 20:15:45 -06:00
TrèsAbhi 9061335292 feat: deduplicate game mode consumales and powers 2026-01-07 20:11:54 -06:00
TrèsAbhi c27d3a3b6a Merge pull request #203 from Hitjack007/filter-by-module-damage
Fixed Module dmage filter showing alpha damage
2026-01-07 03:22:49 +00:00
Mark Greene c37acf4a93 Fixed Module dmage filter showing alpha damage 2026-01-07 15:41:55 +13:00
TrèsAbhi 0c1cef87f6 Merge pull request #200 from blitzkit/tresabhi-patch-1
Rename build job to 'Build website'
2026-01-05 05:18:30 +00:00
TrèsAbhi e8b777be8a Rename build job to 'Build bot' 2026-01-04 19:08:35 -06:00
TrèsAbhi 2941348412 Rename build job to 'Build website' 2026-01-04 19:08:04 -06:00
TrèsAbhi 68b2ecfb6d Merge branch 'main' into dev 2026-01-04 15:19:35 -06:00
TrèsAbhi d5526da1fb Merge pull request #198 from Hitjack007/dev 2026-01-04 21:18:31 +00:00
TrèsAbhi 091305a20f feat: break streak on tier change 2026-01-04 15:18:00 -06:00
TrèsAbhi 85cd70b2d5 feat: disable selecting 0 tiers 2026-01-04 15:16:57 -06:00
TrèsAbhi 7997f490fc feat: clean up tiers ui 2026-01-04 15:11:20 -06:00
TrèsAbhi 5a0b309b0b feat: combine tabs 2026-01-04 14:58:59 -06:00
Mark Greene 43c14665de Fix 2026-01-04 14:50:11 +13:00
Mark Greene e249408360 Fixed Filters not centering 2026-01-04 14:22:20 +13:00
Mark Greene 24e57d7125 Added Tier Filter to Guesser 2026-01-04 10:18:48 +13:00
Mark Greene 636a2934a2 Reworked Game Mode Ability Filters 2026-01-03 22:07:01 +13:00
19 changed files with 526 additions and 363 deletions

No files matched your search

+1 -1
View File
@@ -10,7 +10,7 @@ concurrency:
jobs:
build:
name: Build
name: Build bot
runs-on: ubuntu-latest
steps:
+1 -1
View File
@@ -35,7 +35,7 @@ env:
jobs:
build:
name: Build
name: Build website
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
+14
View File
@@ -0,0 +1,14 @@
# BlitzKit 1.29.10
Thank you to [Mark Greene](https://github.com/Hitjack007) for your contributions this update.
## Features
- Combined game mode passive and active abilities into a single dropdown
- [Mark Greene] Added tier filters to the Guess game
- Changing tiers will break your streak
## Fixes
- [Mark Greene] module damage is now properly shown when sorting with it in the tank search
- Duplicate abilities in the tank filters are now removed
+5
View File
@@ -0,0 +1,5 @@
# BlitzKit 1.29.11
## Fixes
- The Guesser game's toolbar now scales properly with screen size
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "blitzkit",
"version": "1.29.9",
"version": "1.29.11",
"license": "BlitzKit License",
"private": true,
"scripts": {
+6 -1
View File
@@ -762,6 +762,8 @@
"sorting_by": "Sorting by {name}",
"clear": "Clear",
"tier": "Tier {tier}",
"passive": "{name} (Passive)",
"active": "{name} (Active)",
"no_tanks_found": {
"body": "No tanks found.",
@@ -928,7 +930,10 @@
"description": "Reveals the tank without showing the name. This round will be considered incorrect and break your streak!",
"cancel": "Cancel",
"reveal": "Reveal"
}
},
"clear": "Clear",
"select_all": "Select all",
"tier": "Tier {tier}"
},
"tankopedia": {
+6 -1
View File
@@ -671,6 +671,8 @@
"sorting_by": "Sorting by {name}",
"clear": "Limpiar",
"tier": "Tier {tier}",
"passive": "{name} (Passive)",
"active": "{name} (Active)",
"no_tanks_found": {
"body": "No se encontraron tanques.",
"search_again": "Intenta buscar de nuevo.",
@@ -823,7 +825,10 @@
"description": "Reveals the tank without showing the name. This round will be considered incorrect and break your streak!",
"cancel": "Cancelar",
"reveal": "Reveal"
}
},
"clear": "Limpiar",
"select_all": "Seleccionar todo",
"tier": "Tier {tier}"
},
"tankopedia": {
"search": "Search a stat...",
+94 -89
View File
@@ -1,7 +1,7 @@
{
"test": {
"default": "teste",
"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": "Ceci est une longue chaîne de test. Vous ne DEVEZ JAMAIS le voir dans l'interface utilisateur. Mon fruit préféré est {fruit}, car il a la couleur {color}. Je l'aime tellement que je vais dire son nom 3 fois ! {fruit}! {fruit}! {fruit} ! Encore une fois, c'est {color}, pas {not_color}.",
"fox": "Le renard brun et rapide saute par-dessus le chien paresseux."
},
"common": {
@@ -10,7 +10,7 @@
"m": "{value}m",
"mm": "{value}mm",
"deg": "{value}°",
"hp": "{value}hp",
"hp": "{value}pv",
"x": "x{value}",
"percentage": "{value}%",
"deg_s": "{value}°/s",
@@ -187,11 +187,11 @@
"bot": {
"common": {
"errors": {
"clan_not_found": "# Could not find clan\nCouldn't find \"{search}\". Try selecting a clan from the search result.",
"player_not_found": "# Could not find user\nI couldn't find user \"{search}\". Try picking an user from the search result, typing in a valid username, or using the `/link` command.",
"clan_not_found": "# Impossible de trouver le clan\nImpossible de trouver «{search} ». Essayez de sélectionner un clan dans le résultat de la recherche.",
"player_not_found": "# Impossible de trouver l'utilisateur\nJe n'ai pas pu trouver l'utilisateur \"{search}\". Essayez de choisir un utilisateur dans le résultat de la recherche, en tapant un nom d'utilisateur valide, ou en utilisant la commande `/link`.",
"player_not_linked": "# Utilisez `/link` pour commencer\nJe ne sais pas qui vous êtes, mais vous pouvez me le dire aujourd'hui !",
"tank_name_not_found": "# Tank not found\nCould not find tank by the name \"{search}\".",
"tank_id_not_found": "# Tank not found\nCould not find tank by the ID \"{search}\". Try typing the name of the tank instead of a number.",
"tank_name_not_found": "# Char introuvable\nImpossible de trouver le char sous le nom \"{search}\".",
"tank_id_not_found": "# Char introuvable\nImpossible de trouver le char avec l'ID \"{search}\". Essayez de taper le nom du char au lieu d'un nombre.",
"no_tank_stats": "# Aucune statistique de tank disponible\nCe joueur n'a pas de statistiques disponibles. Ce n'est peut-être pas le joueur que vous cherchez.",
"get_help": "Aidez le serveur Discord",
"uncaught_error": "# Blitzkit a rencontré une erreur !\nNous sommes désolés de cela. N'hésitez pas à [rejoindre le serveur officiel Discord](https://discord.gg/nDt7AjGJQH) pour obtenir de l'aide.",
@@ -285,19 +285,19 @@
"name": "rang",
"description": "Rang",
"choices": {
"tier": "Tier {tier_1} - {tier_2}"
"tier": "Rang {tier_}1 - {tier_}2"
}
}
},
"periods": {
"career": "Carrière",
"today": "Aujourd'hui",
"days": "{days} days",
"custom": "{day_1} - {day_2} days"
"days": "{days} jours",
"custom": "{day_1} - {day_2} jours"
},
"filters": {
"none": "Aucun filtre",
"tier": "Tier {tier}"
"tier": "Rang {tier}"
},
"no_data": {
"battles_in_period": "",
@@ -340,19 +340,19 @@
"body": {
"subtitle": "Recherche",
"total": "Total",
"research": "Research: {cost}",
"upgrades": "Upgrades: {cost}",
"purchase": "Purchase: {cost}",
"equipment": "Equipment: {cost}",
"research": "Recherche : {cost}",
"upgrades": "Améliorations : {cost}",
"purchase": "Achat : {cost}",
"equipment": "Équipement : {cost}",
"estimation": "# Ceci est une estimation !\n\nCette liste peut inclure des chars déjà recherchés en raison de l'information inexacte fournie par Wargaming. Essayez de fournir un `char de départ` depuis la ligne lorsque vous utilisez la commande si vous le pouvez."
},
"errors": {
"non_tech_tree": "# {tank} is not on the tech tree\n\nYou cannot research a premium/collector tank.",
"tier_1": "# {tank} is a tier I\n\nTier I tanks are always unlocked for free for all players.",
"already_researched": "# {tank} is already researched\n\nTry providing a `starting-tank` when using the command to view the totals for a line you have already researched.",
"start_end_equal": "# {tank} was provided twice\n\nMake sure `starting-tank` and `target-tank` are different.",
"tanks_not_on_line": "# {tank_1} and {tank_2} are not on the same line\n\nMake sure `starting-tank` and `target-tank` are on the same tech-tree line.",
"unordered": "# {tank_1} higher tiered to {tank_2}\n\nMake sure to provide {tank_2} as the `starting-tank` instead as it is the lower tiered tank."
"non_tech_tree": "# {tank} n'est pas dans l'arbre technologique\n\nVous ne pouvez pas rechercher un char premium/de collection.",
"tier_1": "# {tank} est un char de rang I\n\nLe char de rang 1 sont déverrouillé gratuitement pour tous les joueurs.",
"already_researched": "# {tank} est déjà recherché\n\nEssayez de fournir un `char de départ` en utilisant la commande pour voir la progression d'une lignée.",
"start_end_equal": "# {tank} a été fourni deux fois\n\nAssurez-vous que `starting-tank` et `target-tank` sont différents.",
"tanks_not_on_line": "{tank_}1 et {tank_}2 ne sont pas sur la même ligne \n\nAssurez-vous que `starting-tank` et `target-tank` sont sur la même ligne d'arbre technologique.",
"unordered": "# {tank_1} niveau supérieur à {tank_2}\n\nAssurez-vous de fournir {tank_2} comme le `starting-tank` à la place car c'est le char de niveau inférieur."
}
},
"about": {
@@ -395,7 +395,7 @@
"wn8": "WN8",
"winrate": "Taux de victoires",
"damage": "Dégâts",
"unknown_tank": "Unknown tank {id}"
"unknown_tank": "Char {id} inconnu"
}
},
"debug": {
@@ -431,12 +431,12 @@
"options": {
"threshold": {
"name": "seuil",
"description": "The number of days inactive (default: {days} days)"
"description": "Le nombre de jours inactifs (par défaut: {days} jours)"
}
},
"body": {
"subtitle": "Inactive for {days}+ days",
"listing": "{days} days"
"subtitle": "Inactif depuis plus de {days} jours",
"listing": "{days} jours"
}
},
"full_stats": {
@@ -479,7 +479,7 @@
"body": {
"accounts_linked": "Comptes liés",
"no_role_permissions": {
"title": "{name} failed to verify",
"title": "La vérification de {name} a échouée",
"description": "Je n'ai pas l'autorisation de gérer les rôles."
}
},
@@ -494,11 +494,11 @@
"options": {
"limit": {
"name": "limite",
"description": "The max number of clan results (default: {limit})"
"description": "Nombre maximum de résultats du clan (par défaut: {limit})"
}
},
"body": {
"title": "Clan search for {search} in region {region}",
"title": "Recherche de clan {search} dans la région {region}",
"no_results": "Pas de clan trouvé"
}
},
@@ -508,11 +508,11 @@
"options": {
"limit": {
"name": "limite",
"description": "The max number of player results (default: {limit})"
"description": "Le nombre maximum de résultats de joueur (par défaut: {limit})"
}
},
"body": {
"title": "Player search for {search} in region {region}",
"title": "Recherche de {search} dans la région {region}",
"no_results": "Pas de joueur de trouvé"
}
},
@@ -532,13 +532,13 @@
"options": {
"limit": {
"name": "limite",
"description": "How many neighbors to display (default: {count})"
"description": "Combien de voisins afficher (par défaut : {count})"
},
"season": {
"name": "saison",
"description": "Numéro de saison (défaut : courant)",
"choices": {
"current": "{season} (current)"
"current": "{season} (actuel)"
}
},
"league": {
@@ -547,12 +547,12 @@
}
},
"body": {
"top_players": "Top {count} players",
"no_participation": "# {player} didn't play rating in season {season}\n\nThis player did not participate in this season or did not get past calibration.",
"top_players": "Top {count} joueurs",
"no_participation": "# {player} n'a pas joué de cotées dans la saison {season}\n\nCe joueur n'a pas participé à cette saison ou n'a pas dépassé l'étalonnage.",
"no_ongoing_season": "Pas de saison en cours\n\nIl n'y a pas de saison de notation en cours actuellement.",
"deleted_player": "Deleted {id}",
"deleted_player": "{id} supprimé",
"neighbors": "évaluer les voisins",
"more": "+ {count} more"
"more": "+ {count} plus"
}
},
"today": {
@@ -567,7 +567,7 @@
"name": "chars possédés",
"description": "Affiche tous les chars d'un niveau",
"body": {
"title": "# {name}'s owned tier {tier} tanks\n\n"
"title": "# Les chars du palier {tier} possédés par {name}"
}
},
"permissions": {
@@ -603,94 +603,96 @@
"name": "info-joueur",
"description": "Informations de base sur un joueur",
"body": {
"title": "{name}'s information",
"nickname": "Nickname",
"title": "Informations de {name}",
"nickname": "Pseudo",
"battles": "Batailles",
"winrate": "Taux de victoires",
"account_id": "Account ID",
"created": "Created",
"last_battle": "Last battle"
"created": "Créé",
"last_battle": "Dernière bataille"
}
},
"rating": {
"name": "côtée",
"description": "Rating player's statistics",
"description": "Statistiques des batailles cotées du joueur",
"subcommands": {
"today": {
"name": "aujourd'hui",
"description": "Today's rating statistics"
"description": "Statistiques des batailles cotées d'aujourd'hui"
},
"season": {
"name": "saison",
"description": "This season's rating statistics"
"description": "Statistiques des batailles cotées de cette saison"
}
},
"body": {
"subtitle": {
"today": "Today's rating statistics",
"season": "This season's rating statistics"
"today": "Statistiques de batailles cotées d'aujourd'hui",
"season": "Statistiques de batailles cotées cette saison"
}
},
"errors": {
"no_participation": "# {name} hasn't played rating\n\nThis player hasn't participated in the rating gamemode this season.",
"unrecorded_stats": "# Statistics currently unavailable\n\nWargaming may be currently be facing server downtime. If there is an on-going season, statistics should be available withing 24 hours."
"no_participation": "# {name} n’a pas joué en cotée\n\nCe joueur n’a pas participé au mode cotée cette saison.",
"unrecorded_stats": "# Statistiques actuellement indisponibles\n\nWargaming rencontre peut-être une interruption de service.\nSi une saison est en cours, les statistiques devraient être disponibles sous 24 heures."
}
}
}
},
"website": {
"redirect": {
"title": "301: moved permanently",
"body": "This page has moved. Redirecting..."
"title": "301 : déplacé définitivement",
"body": "Cette page a été déplacée. Redirection en cours…"
},
"settings": {
"title": "Settings",
"description": "Change BlitzKit settings",
"dev_mode": "Development mode",
"language": "Language"
"title": "Paramètres",
"description": "Modifier les paramètres de BlitzKit",
"dev_mode": "Mode développement",
"language": "Langue"
},
"common": {
"player_search": {
"hint": "Search players...",
"no_results": "No results",
"searching": "Searching..."
"hint": "Recherche de joueurs...",
"no_results": "Aucun résultat",
"searching": "Recherche..."
},
"copy_button": {
"copied": "Copied!"
"copied": "Copié !"
},
"tank_search": {
"select_dialog_title": "Search for tanks",
"swap_dialog_title": "Swap tank",
"clear_filters": "Clear filters",
"login": "Login to use this",
"search_bar_hint": "Search tanks...",
"recent": "Recently viewed",
"select_all": "Select all",
"count_singular": "1 tank",
"count_plural": "{count} tanks",
"sorting_by": "Sorting by {name}",
"clear": "Clear",
"tier": "Tier {tier}",
"select_dialog_title": "Recherche de chars",
"swap_dialog_title": "Échanger le char",
"clear_filters": "Supprimer les filtres",
"login": "Connectez-vous pour utiliser ceci",
"search_bar_hint": "Recherche des tanks...",
"recent": "Vu récemment",
"select_all": "Sélectionner tout",
"count_singular": "1 char",
"count_plural": "{count} chars",
"sorting_by": "Trier par {name}",
"clear": "Effacer",
"tier": "Rang {tier}",
"passive": "{name} (Passif)",
"active": "{name} (actif)",
"no_tanks_found": {
"body": "No tanks found.",
"search_again": "Try searching again.",
"clear_filters": "Try clearing filters."
"body": "Aucun tank trouvé.",
"search_again": "Essayez de chercher à nouveau.",
"clear_filters": "Essayez de supprimer les filtres."
},
"sort": {
"meta.none": "none",
"survivability.health": "health",
"survivability.viewRange": "view range",
"survivability.camouflageStill": "camouflage still",
"survivability.camouflageMoving": "camouflage moving",
"survivability.camouflageShooting": "camouflage shooting",
"meta.none": "aucun",
"survivability.health": "santé",
"survivability.viewRange": "portée de vue",
"survivability.camouflageStill": "camouflage à l’arrêt",
"survivability.camouflageMoving": "camouflage en movement",
"survivability.camouflageShooting": "camouflage en tirant",
"survivability.volume": "volume",
"survivability.length": "length",
"fire.dpm": "standard DPM",
"fire.dpmPremium": "premium DPM",
"fire.reload": "reload",
"fire.caliber": "caliber",
"fire.standardPenetration": "standard penetration",
"fire.premiumPenetration": "premium penetration",
"survivability.length": "longueur",
"fire.dpm": "DPM standard",
"fire.dpmPremium": "DPM premium",
"fire.reload": "rechargement",
"fire.caliber": "calibration",
"fire.standardPenetration": "pénétration standard",
"fire.premiumPenetration": "pénétration premium",
"fire.tertiaryPenetration": "tertiary penetration",
"fire.damage": "damage",
"fire.moduleDamage": "module damage",
@@ -815,7 +817,7 @@
"skip": "Skip",
"guess": "Guess",
"next": "Next",
"no_results": "No results"
"no_results": "Aucun résultat"
},
"stats": "{correct} / {total} guessed correctly",
"help": {
@@ -823,7 +825,10 @@
"description": "Reveals the tank without showing the name. This round will be considered incorrect and break your streak!",
"cancel": "Cancel",
"reveal": "Reveal"
}
},
"clear": "Clear",
"select_all": "Sélectionner tout",
"tier": "Rang {tier}"
},
"tankopedia": {
"search": "Search a stat...",
@@ -1206,8 +1211,8 @@
"seo_description": "Track your stats in real time as you play World of Tanks Blitz",
"search": {
"lookup": "Look up a player to get started",
"hint": "Search players...",
"searching": "Searching...",
"hint": "Recherche de joueurs...",
"searching": "Recherche...",
"no_results": "Pas de joueur de trouvé"
},
"controls": {
+12 -7
View File
@@ -187,11 +187,11 @@
"bot": {
"common": {
"errors": {
"clan_not_found": "# Could not find clan\nCouldn't find \"{search}\". Try selecting a clan from the search result.",
"player_not_found": "# Could not find user\nI couldn't find user \"{search}\". Try picking an user from the search result, typing in a valid username, or using the `/link` command.",
"clan_not_found": "",
"player_not_found": "# ユーザーが見つかりませんでした\nユーザー「{search}」が見つかりませんでした。検索結果からユーザーを選択するか、有効なユーザー名を入力するか、/link コマンドを使用してください。",
"player_not_linked": "# `/link`を使用して始めましょう\nはじめまして!あなたが誰か、私に教えてください。",
"tank_name_not_found": "# Tank not found\nCould not find tank by the name \"{search}\".",
"tank_id_not_found": "# Tank not found\nCould not find tank by the ID \"{search}\". Try typing the name of the tank instead of a number.",
"tank_id_not_found": "# 車両が見つかりません\nID「{search}」の戦車が見つかりませんでした。数字ではなく戦車の名前を入力してみてください。",
"no_tank_stats": "# 利用可能な車輌統計がありません\nこのプレイヤーには利用可能な統計がありません。あなたが探しているプレイヤーではない可能性があります。",
"get_help": "Discordサーバーに参加する",
"uncaught_error": "# BlitzKitがエラーに遭遇しました!\n申し訳ありません。お気軽に[公式Discordサーバー](https://discord.gg/nDt7AjGJQH)にお問い合わせください。",
@@ -395,7 +395,7 @@
"wn8": "WN8",
"winrate": "勝率",
"damage": "ダメージ",
"unknown_tank": "Unknown tank {id}"
"unknown_tank": "不明な車両{id}"
}
},
"debug": {
@@ -560,7 +560,7 @@
"description": "Today's played tanks and statistics"
},
"stats": {
"name": "stats",
"name": "",
"description": "Quick statistics for a period"
},
"owned_tanks": {
@@ -671,6 +671,8 @@
"sorting_by": "Sorting by {name}",
"clear": "クリア",
"tier": "Tier {tier}",
"passive": "{name} (Passive)",
"active": "{name} (Active)",
"no_tanks_found": {
"body": "該当する車輌が存在しません。",
"search_again": "もう一度検索してください。",
@@ -781,7 +783,7 @@
"tank": "車輌名",
"last_played": "Last played",
"battles": "Career battles",
"unrecorded": "--",
"unrecorded": "",
"wins": "勝利"
},
"large_warning": {
@@ -823,7 +825,10 @@
"description": "Reveals the tank without showing the name. This round will be considered incorrect and break your streak!",
"cancel": "Cancel",
"reveal": "Reveal"
}
},
"clear": "クリア",
"select_all": "全て選択",
"tier": "Tier {tier}"
},
"tankopedia": {
"search": "Search a stat...",
+6 -1
View File
@@ -671,6 +671,8 @@
"sorting_by": "Sorting by {name}",
"clear": "Clear",
"tier": "Tier {tier}",
"passive": "{name} (Passive)",
"active": "{name} (Active)",
"no_tanks_found": {
"body": "No tanks found.",
"search_again": "Try searching again.",
@@ -823,7 +825,10 @@
"description": "Reveals the tank without showing the name. This round will be considered incorrect and break your streak!",
"cancel": "Cancel",
"reveal": "Reveal"
}
},
"clear": "Clear",
"select_all": "Select all",
"tier": "Tier {tier}"
},
"tankopedia": {
"search": "Search a stat...",
+6 -1
View File
@@ -671,6 +671,8 @@
"sorting_by": "Sorting by {name}",
"clear": "Limpar",
"tier": "Tier {tier}",
"passive": "{name} (Passive)",
"active": "{name} (Active)",
"no_tanks_found": {
"body": "Nenhum tanque encontrado.",
"search_again": "Tente pesquisar novamente.",
@@ -823,7 +825,10 @@
"description": "Reveals the tank without showing the name. This round will be considered incorrect and break your streak!",
"cancel": "Cancelar",
"reveal": "Reveal"
}
},
"clear": "Limpar",
"select_all": "Selecionar todos",
"tier": "Tier {tier}"
},
"tankopedia": {
"search": "Search a stat...",
+8 -3
View File
@@ -297,7 +297,7 @@
},
"filters": {
"none": "Без фильтров",
"tier": " {tier} уровня"
"tier": "{tier} уровень"
},
"no_data": {
"battles_in_period": "За этот период боёв не было",
@@ -670,7 +670,9 @@
"count_plural": "Танков: {count}",
"sorting_by": "Сортировать по {name}",
"clear": "Очистить",
"tier": " {tier} уровня",
"tier": "{tier} уровень",
"passive": "{name} (Пассивный)",
"active": "{name} (Активный)",
"no_tanks_found": {
"body": "Танков не найдено.",
"search_again": "Попробуйте повторить поиск.",
@@ -823,7 +825,10 @@
"description": "Показывает танк не показывая название. Этот раунд будет считаться проигранным и прекратит серию!",
"cancel": "Отмена",
"reveal": "Раскрыть"
}
},
"clear": "Очистить",
"select_all": "Выбрать всё",
"tier": "{tier} уровень"
},
"tankopedia": {
"search": "Поиск характеристик...",
+72 -67
View File
@@ -79,11 +79,11 @@
"10": "Супер винятковий"
},
"nations": {
"ussr": "СРСР",
"ussr": "С.Р.С.Р.",
"germany": "Німеччина",
"usa": "ЗША",
"usa": "С.Ш.А.",
"china": "Китай",
"uk": "Велика Британія",
"uk": "Сполучене Королівство ",
"france": "Франція",
"japan": "Японія",
"european": "Європейська нація",
@@ -121,7 +121,7 @@
"3": "ПТ-САУ"
},
"tree_type": {
"-1": "не розпізнано",
"-1": "не розпізнаний",
"0": "Звичайний",
"1": "Преміум",
"2": "Колекційний"
@@ -341,18 +341,18 @@
"subtitle": "Дослідження",
"total": "Загалом",
"research": "Дослідження: {cost}",
"upgrades": "Upgrades: {cost}",
"upgrades": "Прокачування: {cost}",
"purchase": "Вартість: {cost}",
"equipment": "Спорядження: {cost}",
"estimation": "# Це оцінка!\n\nЦей список може включати вже досліджені танки через неточну інформацію для вантажу. Спробуйте використати \"початковий танк\" з рядка за допомогою команди."
},
"errors": {
"non_tech_tree": "# {tank} is not on the tech tree\n\nYou cannot research a premium/collector tank.",
"tier_1": "# {tank} is a tier I\n\nTier I tanks are always unlocked for free for all players.",
"already_researched": "# {tank} is already researched\n\nTry providing a `starting-tank` when using the command to view the totals for a line you have already researched.",
"start_end_equal": "# {tank} was provided twice\n\nMake sure `starting-tank` and `target-tank` are different.",
"tanks_not_on_line": "# {tank_1} and {tank_2} are not on the same line\n\nMake sure `starting-tank` and `target-tank` are on the same tech-tree line.",
"unordered": "# {tank_1} higher tiered to {tank_2}\n\nMake sure to provide {tank_2} as the `starting-tank` instead as it is the lower tiered tank."
"non_tech_tree": "#{tank} відсутній в дереві дослідження\n\nВи не можете дослідити преміум/колеційний танк.",
"tier_1": "#{tank} І рівня \n\nТанки І рівня завжди розблоковані для всіх гравців безкоштовно",
"already_researched": "# {tank} вже було досліджено\n\nСпробуйте використати `початковий-танк` коли використовуєте команду для перегляду загальної гілки, яку ви вже дослідили",
"start_end_equal": "#{tank} було введено двічі \n\nПереконайтесь що `початковий-танк` та `ціль-танк` різні",
"tanks_not_on_line": "# {tank_1} та {tank_2} не в одній гілці\n\nПереконайтесь що `початковий-танк` та `ціль-танк` знаходяться в єдиній гілці дерева дослідження",
"unordered": "# {tank_1} вище рівнем відносно {tank_2}\n\nОбов’язково вкажіть {tank_2} як `початковий-танк`, оскільки це танк нижчого рівня."
}
},
"about": {
@@ -395,7 +395,7 @@
"wn8": "WN8",
"winrate": "Відсоток перемог",
"damage": "Шкода",
"unknown_tank": "Unknown tank {id}"
"unknown_tank": "Невідомий танк {id}"
}
},
"debug": {
@@ -431,11 +431,11 @@
"options": {
"threshold": {
"name": "поріг",
"description": "The number of days inactive (default: {days} days)"
"description": "Кількість днів неактивності (за замовчуванням: {days} днів)"
}
},
"body": {
"subtitle": "Inactive for {days}+ days",
"subtitle": "Неактивний {days}+ днів",
"listing": "{days} днів"
}
},
@@ -479,7 +479,7 @@
"body": {
"accounts_linked": "Прив'язані акаунти",
"no_role_permissions": {
"title": "{name} failed to verify",
"title": "{name} не вдалося перевірити",
"description": "У мене немає дозволу керувати ролями."
}
},
@@ -494,11 +494,11 @@
"options": {
"limit": {
"name": "ліміт",
"description": "The max number of clan results (default: {limit})"
"description": "Максимальна кількість результатів клану (за замовчуванням: {limit})"
}
},
"body": {
"title": "Clan search for {search} in region {region}",
"title": "Пошук клану для {search} в регіоні {region}",
"no_results": "Жодного клану не знайдено"
}
},
@@ -508,11 +508,11 @@
"options": {
"limit": {
"name": "ліміт",
"description": "The max number of player results (default: {limit})"
"description": "Максимальна кількість результатів гравця (за замовчуванням: {limit})"
}
},
"body": {
"title": "Player search for {search} in region {region}",
"title": "Пошук гравця для {search} у регіоні {region}",
"no_results": "Гравця не знайдено"
}
},
@@ -521,8 +521,8 @@
"description": "Статистика рейтингових боїв",
"subcommands": {
"neighbors": {
"name": "neighbors",
"description": "Neighboring players on the leaderboard"
"name": "сусіди",
"description": "Сусідуючі гравці в лідерборді"
},
"league": {
"name": "ліга",
@@ -532,32 +532,32 @@
"options": {
"limit": {
"name": "ліміт",
"description": "How many neighbors to display (default: {count})"
"description": "Скільки сусідів відобразити (за замовчуванням: {count})"
},
"season": {
"name": "сезон",
"description": "Базова інформація про гравця",
"choices": {
"current": "{season} (current)"
"current": "{season} (поточний)"
}
},
"league": {
"name": "ліга",
"description": "The league to display"
"description": "Ліга для відображення"
}
},
"body": {
"top_players": "Top {count} players",
"no_participation": "# {player} didn't play rating in season {season}\n\nThis player did not participate in this season or did not get past calibration.",
"no_ongoing_season": "# No ongoing season\n\nThere isn't a currently running rating season.",
"top_players": "Топ {count} гравців",
"no_participation": "# {player} не грав у рейтингу в сезоні {season}\n\nЦей гравець не брав участь у цьому сезоні або не проходив калібрування",
"no_ongoing_season": "# Немає поточного сезону\n\nВ даний час немає сезону рейтингу.",
"deleted_player": "",
"neighbors": "Rating neighbors",
"more": "+ {count} more"
"neighbors": "Сусіди по рейтингу",
"more": "+ {count} ще"
}
},
"today": {
"name": "сьогодні",
"description": "Today's played tanks and statistics"
"description": "Сьогодні зіграні танки та статистика"
},
"stats": {
"name": "статистика",
@@ -567,43 +567,43 @@
"name": "отримані танки",
"description": "Показує всі ваші танки даного рівня",
"body": {
"title": "# {name}'s owned tier {tier} tanks\n\n"
"title": "# {name} отриманих рівнів {tier} танків\n\n"
}
},
"permissions": {
"name": "дозволи",
"description": "Checks wether the bot has the required permissions to function",
"description": "Перевіряє чи бот має необхідні дозволи для роботи",
"body": {
"title": "# Permissions\n\n{body}",
"view_channels": "View channels: needed to find the message the refresh button was used on",
"read_message_history": "Read message history: needed to find the message to refresh",
"attach_files": "Attach files: needed to add images to image-based commands",
"external_emojis": "External emojis: needed to add national flags and tank class emojis"
"title": "# Дозволи\n\n{body}",
"view_channels": "Перегляньте канали: необхідне для знаходження повідомлення, на якому використовувалася кнопка оновлення",
"read_message_history": "Прочитайте історію повідомлень: потрібно знайти повідомлення для оновлення",
"attach_files": "Закріпіть файли: потрібно для додавання зображень до команд на основі зображень",
"external_emojis": "Зовнішні емодзі: потрібні для додавання емодзі національних прапорів та класів техніки"
}
},
"ping": {
"name": "",
"description": "Check wether the bot is alive.",
"description": "Перевірити чи бот живий",
"body": {
"pong": "Pong 🏓",
"pong_with_time": "Pong 🏓 - {time}ms"
"pong": "Понг🏓",
"pong_with_time": "Понг🏓 - {time}мс"
},
"subcommands": {
"blitzkit": {
"name": "blitzkit",
"name": "BlitzKit",
"description": "Ping BlitzKit"
},
"wotb": {
"name": "wotb",
"name": "WoTB",
"description": "Ping World of Tanks Blitz"
}
}
},
"player_info": {
"name": "player-info",
"name": "інформація про гравця",
"description": "Базова інформація про гравця",
"body": {
"title": "{name}'s information",
"title": "інформація про {name}",
"nickname": "Нік",
"battles": "Бої",
"winrate": "Відсоток перемог",
@@ -614,33 +614,33 @@
},
"rating": {
"name": "рейтинг",
"description": "Rating player's statistics",
"description": "Статистика рейтингових боїв гравця ",
"subcommands": {
"today": {
"name": "сьогодні",
"description": "Today's rating statistics"
"description": "Сьогоднішня статистика рейтингових боїв"
},
"season": {
"name": "сезон",
"description": "This season's rating statistics"
"description": "Статистика рейтингових боїв поточного сезону"
}
},
"body": {
"subtitle": {
"today": "Today's rating statistics",
"season": "This season's rating statistics"
"today": "Сьогоднішня статистика рейтингових боїв",
"season": "Статистика рейтингових боїв даного сезону"
}
},
"errors": {
"no_participation": "# {name} hasn't played rating\n\nThis player hasn't participated in the rating gamemode this season.",
"unrecorded_stats": "# Statistics currently unavailable\n\nWargaming may be currently be facing server downtime. If there is an on-going season, statistics should be available withing 24 hours."
"no_participation": "# {name} не грав рейтингові бої\n\nЦей гравець не брав участь у рейтинговому режимі цього сезону.",
"unrecorded_stats": "# Статистика на цей момент недоступна\n\nWargaming наразі зіткнулась з технічними несправностями. Якщо зараз проходить сезон, статистика буде доступною в найближчі 24 години."
}
}
}
},
"website": {
"redirect": {
"title": "301: moved permanently",
"title": "301: переміщено назавжди",
"body": "This page has moved. Redirecting..."
},
"settings": {
@@ -671,6 +671,8 @@
"sorting_by": "Відсортувати за {name}",
"clear": "Clear",
"tier": "Рівень {tier}",
"passive": "{name} (Passive)",
"active": "{name} (Active)",
"no_tanks_found": {
"body": "Нічого не знайдено.",
"search_again": "Спробуйте шукати заново.",
@@ -823,7 +825,10 @@
"description": "Reveals the tank without showing the name. This round will be considered incorrect and break your streak!",
"cancel": "Cancel",
"reveal": "Reveal"
}
},
"clear": "Clear",
"select_all": "Вибрати все",
"tier": "Рівень {tier}"
},
"tankopedia": {
"search": "Search a stat...",
@@ -832,33 +837,33 @@
"description": "Танкова енциклопедія",
"seo_description": "Характеристики, броня та інше для всіх танків WoT Blitz",
"tank_seo_title": "{name} - Tier {tier} {nationality} {class} | {website} Tankopedia | World of Tanks Blitz (WoTB)",
"tank_seo_description": "{name} armor, statistics, equipment, provisions, and more. {dpm} DPM, {alpha}hp alpha, {penetration}mm penetration, {speed}kph top speed, {health}hp health.",
"tank_seo_description": "{name} броня, характеристики, обладнання, амуніція і т.д. {dpm} шкода на хвилину, {alpha} разова шкода, {penetration}мм пробиття, {speed} км/год максимальна швидкість, {health} міцність.",
"visualizers": {
"reload": {
"skip": "Skip"
"skip": "Пропустити"
},
"assault": {
"title": "Assault shells"
"title": "Штурмові снаряди"
},
"rotator": {
"hull": "Hull",
"turret": "Turret",
"hard": "Hard",
"hull": "Корпус",
"turret": "Башта",
"hard": "Жорстко",
"medium": "Середній",
"soft": "Soft",
"effective": "Effective"
"soft": "Ніжно",
"effective": "Ефективний"
},
"ricochet": {
"angle": "Angle",
"angle": "Кут",
"normalization": "Нормалізація",
"effective_angle": "Effective angle",
"effective_angle": "Ефективний кут",
"armor": "Броня",
"no_ricochet": "Explosive shells don't ricochet"
"no_ricochet": "Фугасні снаряди не рикошетять"
},
"view_range": {
"camo": "Camo",
"ghost_factory": "Ghost Factory",
"dead_rail": "Dead Rail"
"camo": "Камуфляж",
"ghost_factory": "Промзона",
"dead_rail": "Ешелон"
},
"flexibility": {
"yaw": "Yaw",
+13 -8
View File
@@ -23,8 +23,8 @@
},
"shells": {
"ap": "穿甲弹",
"ap_cr": "APCR",
"hc": "HEAT",
"ap_cr": "硬式复合穿甲弹",
"hc": "反战车高爆弹",
"he": "高爆弹"
},
"player_count_period": {
@@ -671,6 +671,8 @@
"sorting_by": "按 {name} 排序",
"clear": "清除",
"tier": "{tier} 阶",
"passive": "{name} (被动)",
"active": "{name} (主动)",
"no_tanks_found": {
"body": "找不到战车。",
"search_again": "请尝试重新搜索。",
@@ -819,11 +821,14 @@
},
"stats": "猜对了 {correct} / {total}",
"help": {
"title": "Partially reveal tank?",
"description": "Reveals the tank without showing the name. This round will be considered incorrect and break your streak!",
"title": "显示坦克?",
"description": "显示坦克而不显示名字。这轮会被视为不正确,并打破你的连续记录!",
"cancel": "取消",
"reveal": "Reveal"
}
"reveal": "显示"
},
"clear": "清除",
"select_all": "全选",
"tier": "{tier} 阶"
},
"tankopedia": {
"search": "搜索属性……",
@@ -1145,11 +1150,11 @@
},
"provisions": {
"clear": "清除",
"clear_all": "Clear all tanks"
"clear_all": "清除所有坦克"
},
"consumables": {
"clear": "清除",
"clear_all": "Clear all tanks"
"clear_all": "清除所有坦克"
}
}
},
+139 -55
View File
@@ -9,13 +9,17 @@ import {
ArrowRightIcon,
EyeOpenIcon,
MagnifyingGlassIcon,
MixerVerticalIcon,
PaperPlaneIcon,
StarFilledIcon,
TrashIcon,
} from "@radix-ui/react-icons";
import {
AlertDialog,
Box,
Button,
Card,
DropdownMenu,
Flex,
IconButton,
Spinner,
@@ -23,7 +27,7 @@ import {
TextField,
} from "@radix-ui/themes";
import fuzzysort from "fuzzysort";
import { debounce } from "lodash-es";
import { debounce, times } from "lodash-es";
import { useCallback, useEffect, useRef, useState } from "react";
import { awaitableTankDefinitions } from "../core/awaitables/tankDefinitions";
import { awaitableTankNames } from "../core/awaitables/tankNames";
@@ -53,6 +57,7 @@ export function Guesser() {
const [searching, setSearching] = useState(false);
const [results, setResults] = useState<TankDefinition[] | null>(null);
const [selected, setSelected] = useState<TankDefinition | null>(null);
const tiers = Guess.use((state) => state.tiers);
const requestSearch = useCallback(() => {
setSelected(null);
@@ -97,58 +102,60 @@ export function Guesser() {
width="100%"
p="4"
maxWidth="25rem"
gap="4"
gap="3"
>
{results !== null && (
<Card variant="classic">
<Box py="2" px="3">
{results.length === 0 && (
<Flex justify="center">
<Text color="gray">
{strings.website.tools.guess.search.no_results}
</Text>
</Flex>
)}
<Box position="relative">
{results !== null && (
<Card variant="classic">
<Box py="2" px="3">
{results.length === 0 && (
<Flex justify="center">
<Text color="gray">
{strings.website.tools.guess.search.no_results}
</Text>
</Flex>
)}
<SearchResults.Root>
{results.map((result) => {
const Icon = classIcons[result.class];
<SearchResults.Root>
{results.map((result) => {
const Icon = classIcons[result.class];
return (
<SearchResults.Item
key={result.id}
onClick={() => {
if (!input.current) return;
return (
<SearchResults.Item
key={result.id}
onClick={() => {
if (!input.current) return;
input.current.value = unwrap(result.name);
setSelected(result);
setResults(null);
}}
text={unwrap(result.name)}
prefix={
<img
style={{ width: "1em", height: "1em" }}
src={asset(`flags/circle/${result.nation}.webp`)}
/>
}
discriminator={
<Flex
align="center"
gap="1"
width="38px"
justify="center"
>
<Icon width="1em" height="1em" />
{TIER_ROMAN_NUMERALS[result.tier]}
</Flex>
}
/>
);
})}
</SearchResults.Root>
</Box>
</Card>
)}
input.current.value = unwrap(result.name);
setSelected(result);
setResults(null);
}}
text={unwrap(result.name)}
prefix={
<img
style={{ width: "1em", height: "1em" }}
src={asset(`flags/circle/${result.nation}.webp`)}
/>
}
discriminator={
<Flex
align="center"
gap="1"
width="38px"
justify="center"
>
<Icon width="1em" height="1em" />
{TIER_ROMAN_NUMERALS[result.tier]}
</Flex>
}
/>
);
})}
</SearchResults.Root>
</Box>
</Card>
)}
</Box>
<Flex justify="center" style={{ userSelect: "none" }}>
<Text>
@@ -160,14 +167,85 @@ export function Guesser() {
</Text>
</Flex>
<Flex gap="3">
<Flex gap={{ initial: "2", sm: "3" }}>
<DropdownMenu.Root modal={false}>
<DropdownMenu.Trigger>
<IconButton
size={{ initial: "2", sm: "3" }}
variant="surface"
color="gray"
>
<MixerVerticalIcon />
</IconButton>
</DropdownMenu.Trigger>
<DropdownMenu.Content>
{times(10, (index) => {
const tier = 10 - index;
const isSelected = tiers.includes(tier);
return (
<DropdownMenu.CheckboxItem
checked={isSelected}
key={tier}
onClick={(event) => {
event.preventDefault();
Guess.mutate((draft) => {
if (isSelected) {
if (draft.tiers.length === 1) return;
draft.tiers = draft.tiers.filter((t) => t !== tier);
} else {
draft.tiers = [...draft.tiers, tier];
}
draft.streak = 0;
});
}}
>
{literals(strings.website.tools.guess.tier, {
tier: TIER_ROMAN_NUMERALS[tier],
})}
</DropdownMenu.CheckboxItem>
);
})}
<DropdownMenu.Separator />
<DropdownMenu.Item
onClick={(event) => {
event.preventDefault();
Guess.mutate((draft) => {
draft.tiers = Guess.initial.tiers;
});
}}
>
<StarFilledIcon /> {strings.website.tools.guess.select_all}
</DropdownMenu.Item>
<DropdownMenu.Item
color="red"
onClick={(event) => {
event.preventDefault();
Guess.mutate((draft) => {
draft.tiers = [];
});
}}
>
<TrashIcon /> {strings.website.tools.guess.clear}
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
<TextField.Root
disabled={guessState !== GuessState.NotGuessed}
ref={input}
onChange={requestSearch}
style={{ flex: 1 }}
style={{ flex: 1, maxWidth: "14rem" }}
placeholder={strings.website.tools.guess.search.placeholder}
size="3"
size={{ initial: "2", sm: "3" }}
variant="classic"
>
<TextField.Slot>
@@ -178,7 +256,7 @@ export function Guesser() {
<AlertDialog.Root>
<AlertDialog.Trigger>
<IconButton
size="3"
size={{ initial: "2", sm: "3" }}
disabled={guessState !== GuessState.NotGuessed}
>
<EyeOpenIcon />
@@ -218,7 +296,7 @@ export function Guesser() {
</AlertDialog.Root>
<Button
size="3"
size={{ initial: "2", sm: "3" }}
color={
guessState === GuessState.NotGuessed && selected === null
? "red"
@@ -238,7 +316,13 @@ export function Guesser() {
draft.streak = correct ? draft.streak + 1 : 0;
});
} else {
const id = Number(ids[Math.floor(Math.random() * ids.length)]);
const filteredIds = ids.filter((id) => {
const tank = tankDefinitions.tanks[Number(id)];
return tiers.includes(tank.tier);
});
const id = Number(
filteredIds[Math.floor(Math.random() * filteredIds.length)]
);
const tank = tankDefinitions.tanks[id];
Guess.mutate((draft) => {
@@ -41,7 +41,7 @@ export function TankSearchCard({ tank, onSelect }: TankSearchCardProps) {
case 'fire.damage':
return shell0.armor_damage.toFixed(0);
case 'fire.moduleDamage':
return shell0.armor_damage.toFixed(0);
return shell0.module_damage.toFixed(0);
case 'fire.dispersionMoving':
return tracks.dispersion_move.toFixed(3);
case 'fire.dispersionStill':
@@ -83,11 +83,36 @@ for (const gameModeIdString in gameModeRoleSets) {
for (const roleId of roles) {
const role = gameDefinitions.roles[roleId];
for (const id of role.consumables) {
outerLoop: for (const id of role.consumables) {
const name =
consumableDefinitions.consumables[id].name.locales[locales.default];
for (const otherConsumable of consumables.values()) {
const otherName =
consumableDefinitions.consumables[otherConsumable].name.locales[
locales.default
];
if (name === otherName) continue outerLoop;
}
consumables.add(id);
allGameModeConsumables.add(id);
}
for (const id of role.provisions) {
outerLoop: for (const id of role.provisions) {
const name =
provisionDefinitions.provisions[id].name.locales[locales.default];
for (const otherProvision of provisions.values()) {
const otherName =
provisionDefinitions.provisions[otherProvision].name.locales[
locales.default
];
if (name === otherName) continue outerLoop;
}
provisions.add(id);
allGameModeProvisions.add(id);
}
@@ -155,7 +180,7 @@ const TIERS = times(10, (i) => 10 - i);
export function FilterControl() {
return (
<Flex align="center" gap="2" wrap="wrap">
<Flex align="center" justify="center" gap="2" wrap="wrap">
<TiersFilter />
<ClassFilter />
<TypeFilter />
@@ -168,8 +193,7 @@ export function FilterControl() {
<ShellFilter />
<ConsumablesFilter />
<ProvisionsFilter />
<AbilitiesFilter />
<PowersFilter />
<GameModeAbilitiesFilter />
<ResetButton />
</Flex>
@@ -1031,22 +1055,33 @@ function ProvisionsFilter() {
);
}
function PowersFilter() {
function GameModeAbilitiesFilter() {
const { unwrap, strings } = useLocale();
const rawAbilities = TankFilters.use((state) => state.abilities);
const rawPowers = TankFilters.use((state) => state.powers);
const abilities =
rawAbilities.length === 0 && rawPowers.length === 0
? Array.from(allGameModeConsumables.values())
: rawAbilities;
const powers =
rawPowers.length === 0
rawPowers.length === 0 && rawAbilities.length === 0
? Array.from(allGameModeProvisions.values())
: rawPowers;
const icons = [
...abilities.map((ability) => `icons/consumables/${ability}.webp`),
...powers.map((power) => `icons/provisions/${power}.webp`),
];
return (
<DropdownMenu.Root modal={false}>
<DropdownMenu.Trigger>
<Button color="gray" variant="surface">
<Flex>
{powers.slice(0, MAX_ICONS).map((power, index) => (
{icons.slice(0, MAX_ICONS).map((icon, index) => (
<img
key={power}
key={icon}
style={{
filter: "drop-shadow(0 0 var(--space-1) var(--black-a11))",
marginLeft: index > 0 ? "-0.5em" : undefined,
@@ -1054,14 +1089,14 @@ function PowersFilter() {
height: "1.25em",
objectFit: "contain",
}}
src={asset(`icons/provisions/${power}.webp`)}
src={asset(icon)}
/>
))}
{powers.length > MAX_ICONS && (
{icons.length > MAX_ICONS && (
<Text size="1" ml="1">
{literals(strings.common.units.plus, {
value: powers.length - MAX_ICONS,
value: icons.length - MAX_ICONS,
})}
</Text>
)}
@@ -1070,14 +1105,75 @@ function PowersFilter() {
</DropdownMenu.Trigger>
<DropdownMenu.Content>
{gameModeRoles.map(({ gameModeId, provisions }) => {
{gameModeRoles.map(({ gameModeId, consumables, provisions }) => {
const gameMode = gameDefinitions.gameModes[gameModeId];
const hasNeither =
consumables.length === 0 && provisions.length === 0;
if (provisions.length === 0) return null;
if (hasNeither) return null;
const hasBoth = consumables.length > 0 && provisions.length > 0;
return (
<Fragment key={gameModeId}>
<DropdownMenu.Label>{unwrap(gameMode.name)}</DropdownMenu.Label>
{consumables.length > 0 && (
<DropdownMenu.Label>
{hasBoth
? literals(strings.website.common.tank_search.active, {
name: unwrap(gameMode.name),
})
: unwrap(gameMode.name)}
</DropdownMenu.Label>
)}
{consumables.map((consumable) => {
{
const selected = rawAbilities.includes(consumable);
const abilityDefinition =
consumableDefinitions.consumables[consumable];
return (
<DropdownMenu.CheckboxItem
onClick={(event) => {
event.preventDefault();
TankFilters.mutate((draft) => {
if (selected) {
draft.abilities = draft.abilities.filter(
(n) => n !== consumable
);
} else {
draft.abilities = [...draft.abilities, consumable];
}
});
}}
checked={selected}
key={consumable}
>
<img
style={{
width: "1.25em",
height: "1.25em",
objectFit: "contain",
}}
src={asset(`icons/consumables/${consumable}.webp`)}
/>
{unwrap(abilityDefinition.name)}
</DropdownMenu.CheckboxItem>
);
}
})}
{provisions.length > 0 && (
<DropdownMenu.Label>
{hasBoth
? literals(strings.website.common.tank_search.passive, {
name: unwrap(gameMode.name),
})
: unwrap(gameMode.name)}
</DropdownMenu.Label>
)}
{provisions.map((provision) => {
{
@@ -1129,6 +1225,7 @@ function PowersFilter() {
event.preventDefault();
TankFilters.mutate((draft) => {
draft.abilities = [];
draft.powers = [];
});
}}
@@ -1140,113 +1237,3 @@ function PowersFilter() {
</DropdownMenu.Root>
);
}
function AbilitiesFilter() {
const { unwrap, strings } = useLocale();
const rawAbilities = TankFilters.use((state) => state.abilities);
const abilities =
rawAbilities.length === 0
? Array.from(allGameModeConsumables.values())
: rawAbilities;
return (
<DropdownMenu.Root modal={false}>
<DropdownMenu.Trigger>
<Button color="gray" variant="surface">
<Flex>
{abilities.slice(0, MAX_ICONS).map((ability, index) => (
<img
key={ability}
style={{
filter: "drop-shadow(0 0 var(--space-1) var(--black-a11))",
marginLeft: index > 0 ? "-0.5em" : undefined,
width: "1.25em",
height: "1.25em",
objectFit: "contain",
}}
src={asset(`icons/consumables/${ability}.webp`)}
/>
))}
{abilities.length > MAX_ICONS && (
<Text size="1" ml="1">
{literals(strings.common.units.plus, {
value: abilities.length - MAX_ICONS,
})}
</Text>
)}
</Flex>
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content>
{gameModeRoles.map(({ gameModeId, consumables }) => {
const gameMode = gameDefinitions.gameModes[gameModeId];
if (consumables.length === 0) return null;
return (
<Fragment key={gameModeId}>
<DropdownMenu.Label>{unwrap(gameMode.name)}</DropdownMenu.Label>
{consumables.map((consumable) => {
{
const selected = rawAbilities.includes(consumable);
const abilityDefinition =
consumableDefinitions.consumables[consumable];
return (
<DropdownMenu.CheckboxItem
onClick={(event) => {
event.preventDefault();
TankFilters.mutate((draft) => {
if (selected) {
draft.abilities = draft.abilities.filter(
(n) => n !== consumable
);
} else {
draft.abilities = [...draft.abilities, consumable];
}
});
}}
checked={selected}
key={consumable}
>
<img
style={{
width: "1.25em",
height: "1.25em",
objectFit: "contain",
}}
src={asset(`icons/consumables/${consumable}.webp`)}
/>
{unwrap(abilityDefinition.name)}
</DropdownMenu.CheckboxItem>
);
}
})}
</Fragment>
);
})}
<DropdownMenu.Separator />
<DropdownMenu.Item
color="red"
onClick={(event) => {
event.preventDefault();
TankFilters.mutate((draft) => {
draft.abilities = [];
});
}}
>
<TrashIcon />
{strings.website.common.tank_search.clear}
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
);
}
@@ -1,5 +1,5 @@
import { Box, Flex, Heading } from "@radix-ui/themes";
import { Suspense } from "react";
import { Suspense, useEffect } from "react";
import { GuessBackground } from "../../../components/GuessBackground";
import { Guesser } from "../../../components/Guesser";
import { GuessRenderer } from "../../../components/GuessRenderer";
@@ -62,6 +62,31 @@ function Content({ skeleton }: MaybeSkeletonComponentProps) {
const name = unwrap(tank.name);
const fontSize = `min(48vh, ${55 / name.length}vw)`;
const transitionDuration = isRevealed ? "2s" : undefined;
const tiers = Guess.use((state) => state.tiers);
useEffect(() => {
const isTankAlreadyCompliant = tiers.includes(tank.tier);
if (isTankAlreadyCompliant) return;
const filteredIds = ids.filter((id) => {
const tank = tankDefinitions.tanks[Number(id)];
return tiers.includes(tank.tier);
});
if (filteredIds.length === 0) return;
const id = Number(
filteredIds[Math.floor(Math.random() * filteredIds.length)]
);
const newTank = tankDefinitions.tanks[id];
Guess.mutate((draft) => {
draft.tank = newTank;
draft.guessState = GuessState.NotGuessed;
draft.helpingReveal = false;
});
}, [tiers]);
return (
<Flex flexGrow="1" position="relative" overflow="hidden">
+3
View File
@@ -1,4 +1,5 @@
import type { TankDefinition } from "@blitzkit/core";
import { times } from "lodash-es";
import { Varuna } from "varuna";
export enum GuessState {
@@ -14,6 +15,7 @@ export interface Guess {
correctGuesses: number;
streak: number;
helpingReveal: boolean;
tiers: number[];
}
export const Guess = new Varuna<Guess, TankDefinition>((tank) => ({
@@ -23,4 +25,5 @@ export const Guess = new Varuna<Guess, TankDefinition>((tank) => ({
correctGuesses: 0,
streak: 0,
helpingReveal: false,
tiers: times(10, (index) => index + 1),
}));