Compare commits

..
8 Commits
32 changed files with 1122 additions and 2038 deletions

No files matched your search

+1 -1
View File
@@ -10,7 +10,7 @@ concurrency:
jobs:
build:
name: Build bot
name: Build
runs-on: ubuntu-latest
steps:
+1 -1
View File
@@ -35,7 +35,7 @@ env:
jobs:
build:
name: Build website
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
-2
View File
@@ -75,7 +75,6 @@
"DIDS",
"donotremove",
"drei",
"Droodles",
"dvpd",
"dvpl",
"DVPLCRC",
@@ -102,7 +101,6 @@
"gtag",
"haha",
"Herdbreaker",
"Hetz",
"highp",
"iife",
"imgur",
+16 -44
View File
@@ -1,47 +1,19 @@
# BlitzKit Credits
BlitzKit is a one man band **TrèsAbhi** but I am far from solo. Helpers, in the order of contribution over time:
BlitzKit is a one man band **TrèsAbhi** but I am far from solo.
### S0AP
BlitzKit Discord bot verification, testing, advertisement, and the initial encouragement to create BlitzKit.
### Prince_NA
BlitzKit Discord bot and website testing and general advise.
### Vovko
BlitzKit Discord bot and website rendering pipeline.
### Maddox
Game file decompression.
### Pyogenics
Game 3D model parsing.
### Aeson
Game file layout, general advise, advertisement, and Tankopedia pre-release testing.
### Minitelrose, ChickenMan7777
3-caliber rule edge cases.
### HisRoyalFatness, Synx
Embed UX.
### [OPTML] clan, \_34, xkdkw, Droodles Blitz
Tankopedia pre-release testing.
### GonnaHetzMe
Redacted by request.
### Mark Greene
GitHub contributor on bug fixes and feature additions.
| Role | People |
| ------------------------------ | ------------------------------ |
| Bot verification | S0AP |
| General testing | Prince_NA |
| Bot advertisement platform | S0AP |
| Bot rendering pipeline | Vovko |
| Game asset decompression | Maddox |
| Game 3D model parsing | Pyogenics |
| Game file layout | Aeson |
| 3-caliber rule edge cases | Minitelrose, ChickenMan7777 |
| Embed UX | \_34, HisRoyalFatness, Synx |
| Tankopedia pre-release testing | [OPTML], Aeson, \_34, xkdkw |
| Website advertisement platform | Aeson |
| Compare pre-release testing | [OPTML] |
| Redacted | GonnaHetzMe |
-14
View File
@@ -1,14 +0,0 @@
# 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
@@ -1,5 +0,0 @@
# BlitzKit 1.29.11
## Fixes
- The Guesser game's toolbar now scales properly with screen size
-14
View File
@@ -1,14 +0,0 @@
# BlitzKit 1.29.9
Thank you to [Mark Greene](https://github.com/Hitjack007) for your contributions this update.
## Features
- Reworked tank search filters to be far more compact to make way for new filters
- [Mark Greene] Added the ability to filter tanks by consumables
- Added the ability to filter tanks by provisions and game-mode abilities and powers
## Fixes
- Consumable and provision icons in Compare are now properly sized
- [Mark Greene] Provisions now properly affect terrain resistances
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "blitzkit",
"version": "1.29.11",
"version": "1.29.8",
"license": "BlitzKit License",
"private": true,
"scripts": {
@@ -4,54 +4,63 @@ import {
GunDefinition,
Provision,
TankDefinition,
} from "../protos";
} from '../protos';
export function checkConsumableProvisionInclusivity(
consumableProvision: Consumable | Provision,
tank: TankDefinition,
gun: GunDefinition
gun: GunDefinition,
) {
const included = consumableProvision.include?.every((rule) => {
switch (rule.filter_type!.$case) {
case "tiers":
return (
rule.filter_type!.value.min <= tank.tier &&
tank.tier <= rule.filter_type!.value.max
);
const included =
!consumableProvision.game_mode_exclusive &&
consumableProvision.include?.every((rule) => {
switch (rule.filter_type!.$case) {
case 'tiers':
return (
rule.filter_type!.value.min <= tank.tier &&
tank.tier <= rule.filter_type!.value.max
);
case "ids":
return rule.filter_type!.value.ids.includes(tank.id);
case 'ids':
return rule.filter_type!.value.ids.includes(tank.id);
case "nations":
return rule.filter_type!.value.nations.includes(tank.nation);
case 'nations':
return rule.filter_type!.value.nations.includes(tank.nation);
case "categories":
throw new SyntaxError("Category filtering found in include rule");
}
});
const excluded = consumableProvision.exclude?.some((rule) => {
switch (rule.filter_type!.$case) {
case "tiers":
return (
rule.filter_type!.value.min <= tank.tier &&
tank.tier <= rule.filter_type!.value.max
);
case 'categories':
throw new SyntaxError('Category filtering found in include rule');
}
});
const excluded =
consumableProvision.game_mode_exclusive ||
consumableProvision.exclude?.some((rule) => {
switch (rule.filter_type!.$case) {
case 'tiers':
return (
rule.filter_type!.value.min <= tank.tier &&
tank.tier <= rule.filter_type!.value.max
);
case "ids":
return rule.filter_type!.value.ids.includes(tank.id);
case 'ids':
return rule.filter_type!.value.ids.includes(tank.id);
case "nations":
return rule.filter_type!.value.nations.includes(tank.nation);
case 'nations':
return rule.filter_type!.value.nations.includes(tank.nation);
case "categories":
return rule.filter_type!.value.categories.some((category) => {
switch (category) {
case ConsumableTankCategoryFilterCategory.CLIP:
return gun.gun_type!.$case !== "regular";
}
});
}
});
case 'categories':
return rule.filter_type!.value.categories.some((category) => {
switch (category) {
case ConsumableTankCategoryFilterCategory.CLIP:
return gun.gun_type!.$case !== 'regular';
}
});
}
});
return consumableProvision.include?.length > 0 && included && !excluded;
return (
!consumableProvision.game_mode_exclusive &&
consumableProvision.include?.length > 0 &&
included &&
!excluded
);
}
+4 -14
View File
@@ -15,8 +15,7 @@
"hp": "{value}hp",
"x": "x{value}",
"percentage": "{value}%",
"deg_s": "{value}°/s",
"plus": "+{value}"
"deg_s": "{value}°/s"
},
"gun_types": {
@@ -27,8 +26,8 @@
"shells": {
"ap": "AP",
"ap_cr": "APCR",
"hc": "HEAT",
"apcr": "APCR",
"heat": "HEAT",
"he": "HE"
},
@@ -760,17 +759,11 @@
"count_singular": "1 tank",
"count_plural": "{count} tanks",
"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.",
"clear_filters": "Try clearing filters."
},
"sort": {
"meta.none": "none",
"survivability.health": "health",
@@ -930,10 +923,7 @@
"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": {
+4 -12
View File
@@ -13,8 +13,7 @@
"hp": "{value}hp",
"x": "x{value}",
"percentage": "{value}%",
"deg_s": "{value}°/s",
"plus": "+{value}"
"deg_s": "{value}°/s"
},
"gun_types": {
"auto_loader": "Auto cargador",
@@ -23,8 +22,8 @@
},
"shells": {
"ap": "AP",
"ap_cr": "APCR",
"hc": "HEAT",
"apcr": "APCR",
"heat": "AEAC",
"he": "AE"
},
"player_count_period": {
@@ -669,10 +668,6 @@
"count_singular": "1 tanque",
"count_plural": "{count} tanks",
"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.",
@@ -825,10 +820,7 @@
"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...",
+90 -98
View File
@@ -1,7 +1,7 @@
{
"test": {
"default": "teste",
"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}.",
"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": "Le renard brun et rapide saute par-dessus le chien paresseux."
},
"common": {
@@ -10,11 +10,10 @@
"m": "{value}m",
"mm": "{value}mm",
"deg": "{value}°",
"hp": "{value}pv",
"hp": "{value}hp",
"x": "x{value}",
"percentage": "{value}%",
"deg_s": "{value}°/s",
"plus": "+{value}"
"deg_s": "{value}°/s"
},
"gun_types": {
"auto_loader": "Chargement auto",
@@ -23,8 +22,8 @@
},
"shells": {
"ap": "AP",
"ap_cr": "APCR",
"hc": "HEAT",
"apcr": "APCR",
"heat": "HEAT",
"he": "HE"
},
"player_count_period": {
@@ -187,11 +186,11 @@
"bot": {
"common": {
"errors": {
"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`.",
"clan_not_found": "# Could not find clan\nCouldn't find \"{search}\". Try selecting a clan from the search result.",
"player_not_found": "# Could not find user\nI couldn't find user \"{search}\". Try picking an user from the search result, typing in a valid username, or using the `/link` command.",
"player_not_linked": "# Utilisez `/link` pour commencer\nJe ne sais pas qui vous êtes, mais vous pouvez me le dire aujourd'hui !",
"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.",
"tank_name_not_found": "# Tank not found\nCould not find tank by the name \"{search}\".",
"tank_id_not_found": "# Tank not found\nCould not find tank by the ID \"{search}\". Try typing the name of the tank instead of a number.",
"no_tank_stats": "# 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 +284,19 @@
"name": "rang",
"description": "Rang",
"choices": {
"tier": "Rang {tier_}1 - {tier_}2"
"tier": "Tier {tier_1} - {tier_2}"
}
}
},
"periods": {
"career": "Carrière",
"today": "Aujourd'hui",
"days": "{days} jours",
"custom": "{day_1} - {day_2} jours"
"days": "{days} days",
"custom": "{day_1} - {day_2} days"
},
"filters": {
"none": "Aucun filtre",
"tier": "Rang {tier}"
"tier": "Tier {tier}"
},
"no_data": {
"battles_in_period": "",
@@ -340,19 +339,19 @@
"body": {
"subtitle": "Recherche",
"total": "Total",
"research": "Recherche : {cost}",
"upgrades": "Améliorations : {cost}",
"purchase": "Achat : {cost}",
"equipment": "Équipement : {cost}",
"research": "Research: {cost}",
"upgrades": "Upgrades: {cost}",
"purchase": "Purchase: {cost}",
"equipment": "Equipment: {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} 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."
"non_tech_tree": "# {tank} is not on the tech tree\n\nYou cannot research a premium/collector tank.",
"tier_1": "# {tank} is a tier I\n\nTier I tanks are always unlocked for free for all players.",
"already_researched": "# {tank} is already researched\n\nTry providing a `starting-tank` when using the command to view the totals for a line you have already researched.",
"start_end_equal": "# {tank} was provided twice\n\nMake sure `starting-tank` and `target-tank` are different.",
"tanks_not_on_line": "# {tank_1} and {tank_2} are not on the same line\n\nMake sure `starting-tank` and `target-tank` are on the same tech-tree line.",
"unordered": "# {tank_1} higher tiered to {tank_2}\n\nMake sure to provide {tank_2} as the `starting-tank` instead as it is the lower tiered tank."
}
},
"about": {
@@ -395,7 +394,7 @@
"wn8": "WN8",
"winrate": "Taux de victoires",
"damage": "Dégâts",
"unknown_tank": "Char {id} inconnu"
"unknown_tank": "Unknown tank {id}"
}
},
"debug": {
@@ -431,12 +430,12 @@
"options": {
"threshold": {
"name": "seuil",
"description": "Le nombre de jours inactifs (par défaut: {days} jours)"
"description": "The number of days inactive (default: {days} days)"
}
},
"body": {
"subtitle": "Inactif depuis plus de {days} jours",
"listing": "{days} jours"
"subtitle": "Inactive for {days}+ days",
"listing": "{days} days"
}
},
"full_stats": {
@@ -479,7 +478,7 @@
"body": {
"accounts_linked": "Comptes liés",
"no_role_permissions": {
"title": "La vérification de {name} a échouée",
"title": "{name} failed to verify",
"description": "Je n'ai pas l'autorisation de gérer les rôles."
}
},
@@ -494,11 +493,11 @@
"options": {
"limit": {
"name": "limite",
"description": "Nombre maximum de résultats du clan (par défaut: {limit})"
"description": "The max number of clan results (default: {limit})"
}
},
"body": {
"title": "Recherche de clan {search} dans la région {region}",
"title": "Clan search for {search} in region {region}",
"no_results": "Pas de clan trouvé"
}
},
@@ -508,11 +507,11 @@
"options": {
"limit": {
"name": "limite",
"description": "Le nombre maximum de résultats de joueur (par défaut: {limit})"
"description": "The max number of player results (default: {limit})"
}
},
"body": {
"title": "Recherche de {search} dans la région {region}",
"title": "Player search for {search} in region {region}",
"no_results": "Pas de joueur de trouvé"
}
},
@@ -532,13 +531,13 @@
"options": {
"limit": {
"name": "limite",
"description": "Combien de voisins afficher (par défaut : {count})"
"description": "How many neighbors to display (default: {count})"
},
"season": {
"name": "saison",
"description": "Numéro de saison (défaut : courant)",
"choices": {
"current": "{season} (actuel)"
"current": "{season} (current)"
}
},
"league": {
@@ -547,12 +546,12 @@
}
},
"body": {
"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.",
"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": "Pas de saison en cours\n\nIl n'y a pas de saison de notation en cours actuellement.",
"deleted_player": "{id} supprimé",
"deleted_player": "Deleted {id}",
"neighbors": "évaluer les voisins",
"more": "+ {count} plus"
"more": "+ {count} more"
}
},
"today": {
@@ -567,7 +566,7 @@
"name": "chars possédés",
"description": "Affiche tous les chars d'un niveau",
"body": {
"title": "# Les chars du palier {tier} possédés par {name}"
"title": "# {name}'s owned tier {tier} tanks\n\n"
}
},
"permissions": {
@@ -603,96 +602,92 @@
"name": "info-joueur",
"description": "Informations de base sur un joueur",
"body": {
"title": "Informations de {name}",
"nickname": "Pseudo",
"title": "{name}'s information",
"nickname": "Nickname",
"battles": "Batailles",
"winrate": "Taux de victoires",
"account_id": "Account ID",
"created": "Créé",
"last_battle": "Dernière bataille"
"created": "Created",
"last_battle": "Last battle"
}
},
"rating": {
"name": "côtée",
"description": "Statistiques des batailles cotées du joueur",
"description": "Rating player's statistics",
"subcommands": {
"today": {
"name": "aujourd'hui",
"description": "Statistiques des batailles cotées d'aujourd'hui"
"description": "Today's rating statistics"
},
"season": {
"name": "saison",
"description": "Statistiques des batailles cotées de cette saison"
"description": "This season's rating statistics"
}
},
"body": {
"subtitle": {
"today": "Statistiques de batailles cotées d'aujourd'hui",
"season": "Statistiques de batailles cotées cette saison"
"today": "Today's rating statistics",
"season": "This season's rating statistics"
}
},
"errors": {
"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."
"no_participation": "# {name} hasn't played rating\n\nThis player hasn't participated in the rating gamemode this season.",
"unrecorded_stats": "# Statistics currently unavailable\n\nWargaming may be currently be facing server downtime. If there is an on-going season, statistics should be available withing 24 hours."
}
}
}
},
"website": {
"redirect": {
"title": "301 : déplacé définitivement",
"body": "Cette page a été déplacée. Redirection en cours…"
"title": "301: moved permanently",
"body": "This page has moved. Redirecting..."
},
"settings": {
"title": "Paramètres",
"description": "Modifier les paramètres de BlitzKit",
"dev_mode": "Mode développement",
"language": "Langue"
"title": "Settings",
"description": "Change BlitzKit settings",
"dev_mode": "Development mode",
"language": "Language"
},
"common": {
"player_search": {
"hint": "Recherche de joueurs...",
"no_results": "Aucun résultat",
"searching": "Recherche..."
"hint": "Search players...",
"no_results": "No results",
"searching": "Searching..."
},
"copy_button": {
"copied": "Copié !"
"copied": "Copied!"
},
"tank_search": {
"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)",
"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}",
"no_tanks_found": {
"body": "Aucun tank trouvé.",
"search_again": "Essayez de chercher à nouveau.",
"clear_filters": "Essayez de supprimer les filtres."
"body": "No tanks found.",
"search_again": "Try searching again.",
"clear_filters": "Try clearing filters."
},
"sort": {
"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",
"meta.none": "none",
"survivability.health": "health",
"survivability.viewRange": "view range",
"survivability.camouflageStill": "camouflage still",
"survivability.camouflageMoving": "camouflage moving",
"survivability.camouflageShooting": "camouflage shooting",
"survivability.volume": "volume",
"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",
"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",
"fire.tertiaryPenetration": "tertiary penetration",
"fire.damage": "damage",
"fire.moduleDamage": "module damage",
@@ -817,7 +812,7 @@
"skip": "Skip",
"guess": "Guess",
"next": "Next",
"no_results": "Aucun résultat"
"no_results": "No results"
},
"stats": "{correct} / {total} guessed correctly",
"help": {
@@ -825,10 +820,7 @@
"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...",
@@ -1211,8 +1203,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": "Recherche de joueurs...",
"searching": "Recherche...",
"hint": "Search players...",
"searching": "Searching...",
"no_results": "Pas de joueur de trouvé"
},
"controls": {
+10 -18
View File
@@ -13,8 +13,7 @@
"hp": "{value}hp",
"x": "x{value}",
"percentage": "{value}%",
"deg_s": "{value}°/s",
"plus": "+{value}"
"deg_s": "{value}°/s"
},
"gun_types": {
"auto_loader": "自動装填砲",
@@ -23,8 +22,8 @@
},
"shells": {
"ap": "AP",
"ap_cr": "APCR",
"hc": "HEAT",
"apcr": "APCR",
"heat": "HEAT",
"he": "HE"
},
"player_count_period": {
@@ -187,11 +186,11 @@
"bot": {
"common": {
"errors": {
"clan_not_found": "",
"player_not_found": "# ユーザーが見つかりませんでした\nユーザー「{search}」が見つかりませんでした。検索結果からユーザーを選択するか、有効なユーザー名を入力するか、/link コマンドを使用してください。",
"clan_not_found": "# Could not find clan\nCouldn't find \"{search}\". Try selecting a clan from the search result.",
"player_not_found": "# Could not find user\nI couldn't find user \"{search}\". Try picking an user from the search result, typing in a valid username, or using the `/link` command.",
"player_not_linked": "# `/link`を使用して始めましょう\nはじめまして!あなたが誰か、私に教えてください。",
"tank_name_not_found": "# Tank not found\nCould not find tank by the name \"{search}\".",
"tank_id_not_found": "# 車両が見つかりません\nID「{search}」の戦車が見つかりませんでした。数字ではなく戦車の名前を入力してみてください。",
"tank_id_not_found": "# Tank not found\nCould not find tank by the ID \"{search}\". Try typing the name of the tank instead of a number.",
"no_tank_stats": "# 利用可能な車輌統計がありません\nこのプレイヤーには利用可能な統計がありません。あなたが探しているプレイヤーではない可能性があります。",
"get_help": "Discordサーバーに参加する",
"uncaught_error": "# BlitzKitがエラーに遭遇しました!\n申し訳ありません。お気軽に[公式Discordサーバー](https://discord.gg/nDt7AjGJQH)にお問い合わせください。",
@@ -395,7 +394,7 @@
"wn8": "WN8",
"winrate": "勝率",
"damage": "ダメージ",
"unknown_tank": "不明な車両{id}"
"unknown_tank": "Unknown tank {id}"
}
},
"debug": {
@@ -560,7 +559,7 @@
"description": "Today's played tanks and statistics"
},
"stats": {
"name": "",
"name": "stats",
"description": "Quick statistics for a period"
},
"owned_tanks": {
@@ -669,10 +668,6 @@
"count_singular": "1 輌",
"count_plural": "{count} tanks",
"sorting_by": "Sorting by {name}",
"clear": "クリア",
"tier": "Tier {tier}",
"passive": "{name} (Passive)",
"active": "{name} (Active)",
"no_tanks_found": {
"body": "該当する車輌が存在しません。",
"search_again": "もう一度検索してください。",
@@ -783,7 +778,7 @@
"tank": "車輌名",
"last_played": "Last played",
"battles": "Career battles",
"unrecorded": "",
"unrecorded": "--",
"wins": "勝利"
},
"large_warning": {
@@ -825,10 +820,7 @@
"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...",
+4 -12
View File
@@ -13,8 +13,7 @@
"hp": "{value}hp",
"x": "x{value}",
"percentage": "{value}%",
"deg_s": "{value}°/s",
"plus": "+{value}"
"deg_s": "{value}°/s"
},
"gun_types": {
"auto_loader": "Auto loader",
@@ -23,8 +22,8 @@
},
"shells": {
"ap": "AP",
"ap_cr": "APCR",
"hc": "HEAT",
"apcr": "APCR",
"heat": "HEAT",
"he": "OB"
},
"player_count_period": {
@@ -669,10 +668,6 @@
"count_singular": "1 tank",
"count_plural": "{count} tanks",
"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.",
@@ -825,10 +820,7 @@
"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...",
+4 -12
View File
@@ -13,8 +13,7 @@
"hp": "{value}hp",
"x": "x{value}",
"percentage": "{value}%",
"deg_s": "{value}°/s",
"plus": "+{value}"
"deg_s": "{value}°/s"
},
"gun_types": {
"auto_loader": "Autocarregador",
@@ -23,8 +22,8 @@
},
"shells": {
"ap": "AP",
"ap_cr": "APCR",
"hc": "HEAT",
"apcr": "APCR",
"heat": "HEAT",
"he": "HE"
},
"player_count_period": {
@@ -669,10 +668,6 @@
"count_singular": "1 tanque",
"count_plural": "{count} tanks",
"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.",
@@ -825,10 +820,7 @@
"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...",
+13 -21
View File
@@ -13,8 +13,7 @@
"hp": "{value}оп",
"x": "x{value}",
"percentage": "{value}%",
"deg_s": "{value}°/с",
"plus": "+{value}"
"deg_s": "{value}°/с"
},
"gun_types": {
"auto_loader": "Барабан",
@@ -23,8 +22,8 @@
},
"shells": {
"ap": "ББ",
"ap_cr": "БП",
"hc": "ОФ",
"apcr": "БП",
"heat": "КС",
"he": "ОФ"
},
"player_count_period": {
@@ -297,7 +296,7 @@
},
"filters": {
"none": "Без фильтров",
"tier": "{tier} уровень"
"tier": " {tier} уровня"
},
"no_data": {
"battles_in_period": "За этот период боёв не было",
@@ -669,10 +668,6 @@
"count_singular": "1 танк",
"count_plural": "Танков: {count}",
"sorting_by": "Сортировать по {name}",
"clear": "Очистить",
"tier": "{tier} уровень",
"passive": "{name} (Пассивный)",
"active": "{name} (Активный)",
"no_tanks_found": {
"body": "Танков не найдено.",
"search_again": "Попробуйте повторить поиск.",
@@ -819,16 +814,13 @@
"next": "Следующий",
"no_results": "Ничего не найденo"
},
"stats": "{correct} / {total} правильно, серия: {streak}",
"stats": "{correct} / {total} correct, {streak} streak",
"help": {
"title": "Частично раскрыть танк?",
"description": "Показывает танк не показывая название. Этот раунд будет считаться проигранным и прекратит серию!",
"title": "Partially reveal tank?",
"description": "Reveals the tank without showing the name. This round will be considered incorrect and break your streak!",
"cancel": "Отмена",
"reveal": "Раскрыть"
},
"clear": "Очистить",
"select_all": "Выбрать всё",
"tier": "{tier} уровень"
"reveal": "Reveal"
}
},
"tankopedia": {
"search": "Поиск характеристик...",
@@ -1149,12 +1141,12 @@
"upgrade": "Топ"
},
"provisions": {
"clear": "Убрать",
"clear_all": "Убрать со всех"
"clear": "Clear this tank",
"clear_all": "Clear all tanks"
},
"consumables": {
"clear": "Убрать",
"clear_all": "Убрать со всех"
"clear": "Clear this tank",
"clear_all": "Clear all tanks"
}
}
},
+70 -78
View File
@@ -13,8 +13,7 @@
"hp": "{value}од",
"x": "x{value}",
"percentage": "{value}%",
"deg_s": "{value}град/c",
"plus": "+{value}"
"deg_s": "{value}град/c"
},
"gun_types": {
"auto_loader": "Магазинна система заряджання",
@@ -23,8 +22,8 @@
},
"shells": {
"ap": "ББ",
"ap_cr": "APCR",
"hc": "HEAT",
"apcr": "БП",
"heat": "КС",
"he": "ОФ"
},
"player_count_period": {
@@ -79,11 +78,11 @@
"10": "Супер винятковий"
},
"nations": {
"ussr": "С.Р.С.Р.",
"ussr": "СРСР",
"germany": "Німеччина",
"usa": "С.Ш.А.",
"usa": "ЗША",
"china": "Китай",
"uk": "Сполучене Королівство ",
"uk": "Велика Британія",
"france": "Франція",
"japan": "Японія",
"european": "Європейська нація",
@@ -121,7 +120,7 @@
"3": "ПТ-САУ"
},
"tree_type": {
"-1": "не розпізнаний",
"-1": "не розпізнано",
"0": "Звичайний",
"1": "Преміум",
"2": "Колекційний"
@@ -341,18 +340,18 @@
"subtitle": "Дослідження",
"total": "Загалом",
"research": "Дослідження: {cost}",
"upgrades": "Прокачування: {cost}",
"upgrades": "Upgrades: {cost}",
"purchase": "Вартість: {cost}",
"equipment": "Спорядження: {cost}",
"estimation": "# Це оцінка!\n\nЦей список може включати вже досліджені танки через неточну інформацію для вантажу. Спробуйте використати \"початковий танк\" з рядка за допомогою команди."
},
"errors": {
"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} як `початковий-танк`, оскільки це танк нижчого рівня."
"non_tech_tree": "# {tank} is not on the tech tree\n\nYou cannot research a premium/collector tank.",
"tier_1": "# {tank} is a tier I\n\nTier I tanks are always unlocked for free for all players.",
"already_researched": "# {tank} is already researched\n\nTry providing a `starting-tank` when using the command to view the totals for a line you have already researched.",
"start_end_equal": "# {tank} was provided twice\n\nMake sure `starting-tank` and `target-tank` are different.",
"tanks_not_on_line": "# {tank_1} and {tank_2} are not on the same line\n\nMake sure `starting-tank` and `target-tank` are on the same tech-tree line.",
"unordered": "# {tank_1} higher tiered to {tank_2}\n\nMake sure to provide {tank_2} as the `starting-tank` instead as it is the lower tiered tank."
}
},
"about": {
@@ -395,7 +394,7 @@
"wn8": "WN8",
"winrate": "Відсоток перемог",
"damage": "Шкода",
"unknown_tank": "Невідомий танк {id}"
"unknown_tank": "Unknown tank {id}"
}
},
"debug": {
@@ -431,11 +430,11 @@
"options": {
"threshold": {
"name": "поріг",
"description": "Кількість днів неактивності (за замовчуванням: {days} днів)"
"description": "The number of days inactive (default: {days} days)"
}
},
"body": {
"subtitle": "Неактивний {days}+ днів",
"subtitle": "Inactive for {days}+ days",
"listing": "{days} днів"
}
},
@@ -479,7 +478,7 @@
"body": {
"accounts_linked": "Прив'язані акаунти",
"no_role_permissions": {
"title": "{name} не вдалося перевірити",
"title": "{name} failed to verify",
"description": "У мене немає дозволу керувати ролями."
}
},
@@ -494,11 +493,11 @@
"options": {
"limit": {
"name": "ліміт",
"description": "Максимальна кількість результатів клану (за замовчуванням: {limit})"
"description": "The max number of clan results (default: {limit})"
}
},
"body": {
"title": "Пошук клану для {search} в регіоні {region}",
"title": "Clan search for {search} in region {region}",
"no_results": "Жодного клану не знайдено"
}
},
@@ -508,11 +507,11 @@
"options": {
"limit": {
"name": "ліміт",
"description": "Максимальна кількість результатів гравця (за замовчуванням: {limit})"
"description": "The max number of player results (default: {limit})"
}
},
"body": {
"title": "Пошук гравця для {search} у регіоні {region}",
"title": "Player search for {search} in region {region}",
"no_results": "Гравця не знайдено"
}
},
@@ -521,8 +520,8 @@
"description": "Статистика рейтингових боїв",
"subcommands": {
"neighbors": {
"name": "сусіди",
"description": "Сусідуючі гравці в лідерборді"
"name": "neighbors",
"description": "Neighboring players on the leaderboard"
},
"league": {
"name": "ліга",
@@ -532,32 +531,32 @@
"options": {
"limit": {
"name": "ліміт",
"description": "Скільки сусідів відобразити (за замовчуванням: {count})"
"description": "How many neighbors to display (default: {count})"
},
"season": {
"name": "сезон",
"description": "Базова інформація про гравця",
"choices": {
"current": "{season} (поточний)"
"current": "{season} (current)"
}
},
"league": {
"name": "ліга",
"description": "Ліга для відображення"
"description": "The league to display"
}
},
"body": {
"top_players": "Топ {count} гравців",
"no_participation": "# {player} не грав у рейтингу в сезоні {season}\n\nЦей гравець не брав участь у цьому сезоні або не проходив калібрування",
"no_ongoing_season": "# Немає поточного сезону\n\nВ даний час немає сезону рейтингу.",
"top_players": "Top {count} players",
"no_participation": "# {player} didn't play rating in season {season}\n\nThis player did not participate in this season or did not get past calibration.",
"no_ongoing_season": "# No ongoing season\n\nThere isn't a currently running rating season.",
"deleted_player": "",
"neighbors": "Сусіди по рейтингу",
"more": "+ {count} ще"
"neighbors": "Rating neighbors",
"more": "+ {count} more"
}
},
"today": {
"name": "сьогодні",
"description": "Сьогодні зіграні танки та статистика"
"description": "Today's played tanks and statistics"
},
"stats": {
"name": "статистика",
@@ -567,43 +566,43 @@
"name": "отримані танки",
"description": "Показує всі ваші танки даного рівня",
"body": {
"title": "# {name} отриманих рівнів {tier} танків\n\n"
"title": "# {name}'s owned tier {tier} tanks\n\n"
}
},
"permissions": {
"name": "дозволи",
"description": "Перевіряє чи бот має необхідні дозволи для роботи",
"description": "Checks wether the bot has the required permissions to function",
"body": {
"title": "# Дозволи\n\n{body}",
"view_channels": "Перегляньте канали: необхідне для знаходження повідомлення, на якому використовувалася кнопка оновлення",
"read_message_history": "Прочитайте історію повідомлень: потрібно знайти повідомлення для оновлення",
"attach_files": "Закріпіть файли: потрібно для додавання зображень до команд на основі зображень",
"external_emojis": "Зовнішні емодзі: потрібні для додавання емодзі національних прапорів та класів техніки"
"title": "# Permissions\n\n{body}",
"view_channels": "View channels: needed to find the message the refresh button was used on",
"read_message_history": "Read message history: needed to find the message to refresh",
"attach_files": "Attach files: needed to add images to image-based commands",
"external_emojis": "External emojis: needed to add national flags and tank class emojis"
}
},
"ping": {
"name": "",
"description": "Перевірити чи бот живий",
"description": "Check wether the bot is alive.",
"body": {
"pong": "Понг🏓",
"pong_with_time": "Понг🏓 - {time}мс"
"pong": "Pong 🏓",
"pong_with_time": "Pong 🏓 - {time}ms"
},
"subcommands": {
"blitzkit": {
"name": "BlitzKit",
"name": "blitzkit",
"description": "Ping BlitzKit"
},
"wotb": {
"name": "WoTB",
"name": "wotb",
"description": "Ping World of Tanks Blitz"
}
}
},
"player_info": {
"name": "інформація про гравця",
"name": "player-info",
"description": "Базова інформація про гравця",
"body": {
"title": "інформація про {name}",
"title": "{name}'s information",
"nickname": "Нік",
"battles": "Бої",
"winrate": "Відсоток перемог",
@@ -614,33 +613,33 @@
},
"rating": {
"name": "рейтинг",
"description": "Статистика рейтингових боїв гравця ",
"description": "Rating player's statistics",
"subcommands": {
"today": {
"name": "сьогодні",
"description": "Сьогоднішня статистика рейтингових боїв"
"description": "Today's rating statistics"
},
"season": {
"name": "сезон",
"description": "Статистика рейтингових боїв поточного сезону"
"description": "This season's rating statistics"
}
},
"body": {
"subtitle": {
"today": "Сьогоднішня статистика рейтингових боїв",
"season": "Статистика рейтингових боїв даного сезону"
"today": "Today's rating statistics",
"season": "This season's rating statistics"
}
},
"errors": {
"no_participation": "# {name} не грав рейтингові бої\n\nЦей гравець не брав участь у рейтинговому режимі цього сезону.",
"unrecorded_stats": "# Статистика на цей момент недоступна\n\nWargaming наразі зіткнулась з технічними несправностями. Якщо зараз проходить сезон, статистика буде доступною в найближчі 24 години."
"no_participation": "# {name} hasn't played rating\n\nThis player hasn't participated in the rating gamemode this season.",
"unrecorded_stats": "# Statistics currently unavailable\n\nWargaming may be currently be facing server downtime. If there is an on-going season, statistics should be available withing 24 hours."
}
}
}
},
"website": {
"redirect": {
"title": "301: переміщено назавжди",
"title": "301: moved permanently",
"body": "This page has moved. Redirecting..."
},
"settings": {
@@ -669,10 +668,6 @@
"count_singular": "1 танк",
"count_plural": "Танків: {count} ",
"sorting_by": "Відсортувати за {name}",
"clear": "Clear",
"tier": "Рівень {tier}",
"passive": "{name} (Passive)",
"active": "{name} (Active)",
"no_tanks_found": {
"body": "Нічого не знайдено.",
"search_again": "Спробуйте шукати заново.",
@@ -825,10 +820,7 @@
"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...",
@@ -837,33 +829,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} броня, характеристики, обладнання, амуніція і т.д. {dpm} шкода на хвилину, {alpha} разова шкода, {penetration}мм пробиття, {speed} км/год максимальна швидкість, {health} міцність.",
"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.",
"visualizers": {
"reload": {
"skip": "Пропустити"
"skip": "Skip"
},
"assault": {
"title": "Штурмові снаряди"
"title": "Assault shells"
},
"rotator": {
"hull": "Корпус",
"turret": "Башта",
"hard": "Жорстко",
"hull": "Hull",
"turret": "Turret",
"hard": "Hard",
"medium": "Середній",
"soft": "Ніжно",
"effective": "Ефективний"
"soft": "Soft",
"effective": "Effective"
},
"ricochet": {
"angle": "Кут",
"angle": "Angle",
"normalization": "Нормалізація",
"effective_angle": "Ефективний кут",
"effective_angle": "Effective angle",
"armor": "Броня",
"no_ricochet": "Фугасні снаряди не рикошетять"
"no_ricochet": "Explosive shells don't ricochet"
},
"view_range": {
"camo": "Камуфляж",
"ghost_factory": "Промзона",
"dead_rail": "Ешелон"
"camo": "Camo",
"ghost_factory": "Ghost Factory",
"dead_rail": "Dead Rail"
},
"flexibility": {
"yaw": "Yaw",
+9 -17
View File
@@ -13,8 +13,7 @@
"hp": "{value} 生命值",
"x": "x{value}",
"percentage": "{value}%",
"deg_s": "{value}度°/秒",
"plus": "+{value}"
"deg_s": "{value}度°/秒"
},
"gun_types": {
"auto_loader": "弹夹型坦克",
@@ -23,8 +22,8 @@
},
"shells": {
"ap": "穿甲弹",
"ap_cr": "硬式复合穿甲弹",
"hc": "反战车高爆弹",
"apcr": "硬式复合穿甲弹",
"heat": "反战车高爆弹",
"he": "高爆弹"
},
"player_count_period": {
@@ -669,10 +668,6 @@
"count_singular": "1辆战车",
"count_plural": "{count} 辆坦克",
"sorting_by": "按 {name} 排序",
"clear": "清除",
"tier": "{tier} 阶",
"passive": "{name} (被动)",
"active": "{name} (主动)",
"no_tanks_found": {
"body": "找不到战车。",
"search_again": "请尝试重新搜索。",
@@ -821,14 +816,11 @@
},
"stats": "猜对了 {correct} / {total}",
"help": {
"title": "显示坦克?",
"description": "显示坦克而不显示名字。这轮会被视为不正确,并打破你的连续记录!",
"title": "Partially reveal tank?",
"description": "Reveals the tank without showing the name. This round will be considered incorrect and break your streak!",
"cancel": "取消",
"reveal": "显示"
},
"clear": "清除",
"select_all": "全选",
"tier": "{tier} 阶"
"reveal": "Reveal"
}
},
"tankopedia": {
"search": "搜索属性……",
@@ -1150,11 +1142,11 @@
},
"provisions": {
"clear": "清除",
"clear_all": "清除所有坦克"
"clear_all": "Clear all tanks"
},
"consumables": {
"clear": "清除",
"clear_all": "清除所有坦克"
"clear_all": "Clear all tanks"
}
}
},
+138 -134
View File
@@ -1557,85 +1557,87 @@ export async function definitions() {
const includeRaw = consumable.vehicleFilter?.include.vehicle;
const excludeRaw = consumable.vehicleFilter?.exclude?.vehicle;
if (includeRaw) {
entry.include = [];
if (!entry.game_mode_exclusive) {
if (includeRaw) {
entry.include = [];
if ("minLevel" in includeRaw) {
entry.include.push({
filter_type: {
$case: "tiers",
value: {
min: includeRaw.minLevel,
max: includeRaw.maxLevel,
if ("minLevel" in includeRaw) {
entry.include.push({
filter_type: {
$case: "tiers",
value: {
min: includeRaw.minLevel,
max: includeRaw.maxLevel,
},
},
},
});
} else if ("name" in includeRaw) {
entry.include.push({
filter_type: {
$case: "ids",
value: {
ids: includeRaw.name.split(/ +/).map((key) => {
return tankStringIdMap[key];
}),
});
} else if ("name" in includeRaw) {
entry.include.push({
filter_type: {
$case: "ids",
value: {
ids: includeRaw.name.split(/ +/).map((key) => {
return tankStringIdMap[key];
}),
},
},
},
});
} else throw new SyntaxError("Unhandled include type");
});
} else throw new SyntaxError("Unhandled include type");
if (consumable.vehicleFilter?.include.nations) {
entry.include.push({
filter_type: {
$case: "nations",
value: {
nations: consumable.vehicleFilter.include.nations.split(" "),
if (consumable.vehicleFilter?.include.nations) {
entry.include.push({
filter_type: {
$case: "nations",
value: {
nations: consumable.vehicleFilter.include.nations.split(" "),
},
},
},
});
});
}
}
}
if (excludeRaw) {
entry.exclude = [];
if (excludeRaw) {
entry.exclude = [];
if ("name" in excludeRaw) {
entry.exclude!.push({
filter_type: {
$case: "ids",
value: {
ids: excludeRaw.name.split(/ +/).map((key) => {
return tankStringIdMap[key];
}),
if ("name" in excludeRaw) {
entry.exclude!.push({
filter_type: {
$case: "ids",
value: {
ids: excludeRaw.name.split(/ +/).map((key) => {
return tankStringIdMap[key];
}),
},
},
},
});
} else if ("extendedTags" in excludeRaw) {
entry.exclude!.push({
filter_type: {
$case: "categories",
value: {
categories: excludeRaw.extendedTags
.split(" ")
.map(
(item) =>
blitzTankFilterDefinitionCategoryToBlitzkit[
item as BlitzTankFilterDefinitionCategory
]
),
});
} else if ("extendedTags" in excludeRaw) {
entry.exclude!.push({
filter_type: {
$case: "categories",
value: {
categories: excludeRaw.extendedTags
.split(" ")
.map(
(item) =>
blitzTankFilterDefinitionCategoryToBlitzkit[
item as BlitzTankFilterDefinitionCategory
]
),
},
},
},
});
} else throw new SyntaxError("Unhandled exclude type");
});
} else throw new SyntaxError("Unhandled exclude type");
if (consumable.vehicleFilter?.exclude?.nations) {
entry.exclude!.push({
filter_type: {
$case: "nations",
value: {
nations: consumable.vehicleFilter.exclude.nations.split(" "),
if (consumable.vehicleFilter?.exclude?.nations) {
entry.exclude!.push({
filter_type: {
$case: "nations",
value: {
nations: consumable.vehicleFilter.exclude.nations.split(" "),
},
},
},
});
});
}
}
}
});
@@ -1655,85 +1657,87 @@ export async function definitions() {
const includeRaw = provision.vehicleFilter?.include.vehicle;
const excludeRaw = provision.vehicleFilter?.exclude?.vehicle;
if (includeRaw) {
entry.include = [];
if (!entry.game_mode_exclusive) {
if (includeRaw) {
entry.include = [];
if ("minLevel" in includeRaw) {
entry.include.push({
filter_type: {
$case: "tiers",
value: {
min: includeRaw.minLevel,
max: includeRaw.maxLevel,
if ("minLevel" in includeRaw) {
entry.include.push({
filter_type: {
$case: "tiers",
value: {
min: includeRaw.minLevel,
max: includeRaw.maxLevel,
},
},
},
});
} else if ("name" in includeRaw) {
entry.include.push({
filter_type: {
$case: "ids",
value: {
ids: includeRaw.name.split(/ +/).map((key) => {
return tankStringIdMap[key];
}),
});
} else if ("name" in includeRaw) {
entry.include.push({
filter_type: {
$case: "ids",
value: {
ids: includeRaw.name.split(/ +/).map((key) => {
return tankStringIdMap[key];
}),
},
},
},
});
} else throw new SyntaxError("Unhandled include type");
});
} else throw new SyntaxError("Unhandled include type");
if (provision.vehicleFilter?.include.nations) {
entry.include.push({
filter_type: {
$case: "nations",
value: {
nations: provision.vehicleFilter.include.nations.split(" "),
if (provision.vehicleFilter?.include.nations) {
entry.include.push({
filter_type: {
$case: "nations",
value: {
nations: provision.vehicleFilter.include.nations.split(" "),
},
},
},
});
});
}
}
}
if (excludeRaw) {
entry.exclude = [];
if (excludeRaw) {
entry.exclude = [];
if ("name" in excludeRaw) {
entry.exclude!.push({
filter_type: {
$case: "ids",
value: {
ids: excludeRaw.name
.split(/ +/)
.map((key) => tankStringIdMap[key]),
if ("name" in excludeRaw) {
entry.exclude!.push({
filter_type: {
$case: "ids",
value: {
ids: excludeRaw.name
.split(/ +/)
.map((key) => tankStringIdMap[key]),
},
},
},
});
} else if ("extendedTags" in excludeRaw) {
entry.exclude!.push({
filter_type: {
$case: "categories",
value: {
categories: excludeRaw.extendedTags
.split(" ")
.map(
(item) =>
blitzTankFilterDefinitionCategoryToBlitzkit[
item as BlitzTankFilterDefinitionCategory
]
),
});
} else if ("extendedTags" in excludeRaw) {
entry.exclude!.push({
filter_type: {
$case: "categories",
value: {
categories: excludeRaw.extendedTags
.split(" ")
.map(
(item) =>
blitzTankFilterDefinitionCategoryToBlitzkit[
item as BlitzTankFilterDefinitionCategory
]
),
},
},
},
});
} else throw new SyntaxError("Unhandled exclude type");
});
} else throw new SyntaxError("Unhandled exclude type");
if (provision.vehicleFilter?.exclude?.nations) {
entry.exclude!.push({
filter_type: {
$case: "nations",
value: {
nations: provision.vehicleFilter.exclude.nations.split(" "),
if (provision.vehicleFilter?.exclude?.nations) {
entry.exclude!.push({
filter_type: {
$case: "nations",
value: {
nations: provision.vehicleFilter.exclude.nations.split(" "),
},
},
},
});
});
}
}
}
@@ -1,6 +1,6 @@
import type { Consumable } from "@blitzkit/core";
import { Flex, type FlexProps } from "@radix-ui/themes";
import { ConsumableButton } from "./ModuleButtons/ConsumableButton";
import type { Consumable } from '@blitzkit/core';
import { Flex, type FlexProps } from '@radix-ui/themes';
import { ConsumableButton } from './ModuleButtons/ConsumableButton';
type ConsumablesManagerProps = FlexProps & {
consumables: Consumable[];
@@ -27,8 +27,6 @@ export function ConsumablesManager({
return (
<Flex wrap="wrap" gap="2" {...props}>
{consumables.map((consumable) => {
if (consumable.game_mode_exclusive) return null;
const isSelected = selected.some((id) => id === consumable.id);
return (
+55 -139
View File
@@ -9,17 +9,13 @@ import {
ArrowRightIcon,
EyeOpenIcon,
MagnifyingGlassIcon,
MixerVerticalIcon,
PaperPlaneIcon,
StarFilledIcon,
TrashIcon,
} from "@radix-ui/react-icons";
import {
AlertDialog,
Box,
Button,
Card,
DropdownMenu,
Flex,
IconButton,
Spinner,
@@ -27,7 +23,7 @@ import {
TextField,
} from "@radix-ui/themes";
import fuzzysort from "fuzzysort";
import { debounce, times } from "lodash-es";
import { debounce } from "lodash-es";
import { useCallback, useEffect, useRef, useState } from "react";
import { awaitableTankDefinitions } from "../core/awaitables/tankDefinitions";
import { awaitableTankNames } from "../core/awaitables/tankNames";
@@ -57,7 +53,6 @@ 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);
@@ -102,60 +97,58 @@ export function Guesser() {
width="100%"
p="4"
maxWidth="25rem"
gap="3"
gap="4"
>
<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>
)}
{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>
)}
</Box>
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>
)}
<Flex justify="center" style={{ userSelect: "none" }}>
<Text>
@@ -167,85 +160,14 @@ export function Guesser() {
</Text>
</Flex>
<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>
<Flex gap="3">
<TextField.Root
disabled={guessState !== GuessState.NotGuessed}
ref={input}
onChange={requestSearch}
style={{ flex: 1, maxWidth: "14rem" }}
style={{ flex: 1 }}
placeholder={strings.website.tools.guess.search.placeholder}
size={{ initial: "2", sm: "3" }}
size="3"
variant="classic"
>
<TextField.Slot>
@@ -256,7 +178,7 @@ export function Guesser() {
<AlertDialog.Root>
<AlertDialog.Trigger>
<IconButton
size={{ initial: "2", sm: "3" }}
size="3"
disabled={guessState !== GuessState.NotGuessed}
>
<EyeOpenIcon />
@@ -296,7 +218,7 @@ export function Guesser() {
</AlertDialog.Root>
<Button
size={{ initial: "2", sm: "3" }}
size="3"
color={
guessState === GuessState.NotGuessed && selected === null
? "red"
@@ -316,13 +238,7 @@ export function Guesser() {
draft.streak = correct ? draft.streak + 1 : 0;
});
} else {
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 id = Number(ids[Math.floor(Math.random() * ids.length)]);
const tank = tankDefinitions.tanks[id];
Guess.mutate((draft) => {
@@ -1,6 +1,5 @@
import { Flex } from "@radix-ui/themes";
import { awaitableProvisionDefinitions } from "../core/awaitables/provisionDefinitions";
import { ProvisionButton } from "./ModuleButtons/ProvisionButton";
import { Flex } from '@radix-ui/themes';
import { ProvisionButton } from './ModuleButtons/ProvisionButton';
interface ProvisionsManagerProps {
provisions: number[];
@@ -9,8 +8,6 @@ interface ProvisionsManagerProps {
onChange?: (provisions: number[]) => void;
}
const provisionDefinitions = await awaitableProvisionDefinitions;
export function ProvisionsManager({
provisions,
selected,
@@ -18,18 +15,15 @@ export function ProvisionsManager({
disabled,
}: ProvisionsManagerProps) {
return (
<Flex wrap="wrap" gap="2" justify={{ initial: "center", sm: "start" }}>
{provisions.map((id) => {
const isSelected = selected.includes(id);
const provision = provisionDefinitions.provisions[id];
if (provision.game_mode_exclusive) return null;
<Flex wrap="wrap" gap="2" justify={{ initial: 'center', sm: 'start' }}>
{provisions.map((provision) => {
const isSelected = selected.includes(provision);
return (
<ProvisionButton
key={id}
key={provision}
disabled={disabled && !isSelected}
provision={id}
provision={provision}
selected={isSelected}
onClick={() => {
if (!onChange) return;
@@ -37,9 +31,9 @@ export function ProvisionsManager({
const draft = [...selected];
if (isSelected) {
draft.splice(draft.indexOf(id), 1);
draft.splice(draft.indexOf(provision), 1);
} else {
draft.push(id);
draft.push(provision);
}
onChange(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.module_damage.toFixed(0);
return shell0.armor_damage.toFixed(0);
case 'fire.dispersionMoving':
return tracks.dispersion_move.toFixed(3);
case 'fire.dispersionStill':
File diff suppressed because it is too large. Load diff
@@ -429,7 +429,7 @@ export const TankSearch = memo<TankSearchProps>(
}, [tankFilters, tankopediaSort]);
return (
<Flex direction="column" gap="2" flexGrow="1" {...props}>
<Flex direction="column" gap="4" flexGrow="1" {...props}>
<SearchBar
skeleton={skeleton}
topResult={tanks?.[0]}
@@ -1,10 +1,10 @@
import { asset } from "@blitzkit/core";
import { Box, Flex, Heading, Text } from "@radix-ui/themes";
import { awaitableConsumableDefinitions } from "../../core/awaitables/consumableDefinitions";
import { awaitableGameDefinitions } from "../../core/awaitables/gameDefinitions";
import { awaitableProvisionDefinitions } from "../../core/awaitables/provisionDefinitions";
import { useLocale } from "../../hooks/useLocale";
import { Duel } from "../../stores/duel";
import { asset } from '@blitzkit/core';
import { Box, Flex, Heading, Text } from '@radix-ui/themes';
import { awaitableConsumableDefinitions } from '../../core/awaitables/consumableDefinitions';
import { awaitableGameDefinitions } from '../../core/awaitables/gameDefinitions';
import { awaitableProvisionDefinitions } from '../../core/awaitables/provisionDefinitions';
import { useLocale } from '../../hooks/useLocale';
import { Duel } from '../../stores/duel';
const [gameDefinitions, consumableDefinitions, provisionDefinitions] =
await Promise.all([
@@ -39,22 +39,22 @@ export function GameModeSection() {
overflow="hidden"
direction="column"
style={{
borderRadius: "var(--radius-3)",
borderRadius: 'var(--radius-3)',
background: `url(${asset(
`icons/game_mode_banners/${id}.webp`
`icons/game_mode_banners/${id}.webp`,
)})`,
backgroundSize: "cover",
backgroundPosition: "center",
backgroundSize: 'cover',
backgroundPosition: 'center',
}}
>
<Box
flexGrow="1"
style={{
background: `url(${asset(
`icons/game_mode_banners/${id}.webp`
`icons/game_mode_banners/${id}.webp`,
)})`,
backgroundSize: "cover",
backgroundPosition: "center",
backgroundSize: 'cover',
backgroundPosition: 'center',
}}
/>
@@ -64,8 +64,8 @@ export function GameModeSection() {
height="98px"
flexShrink="0"
style={{
backdropFilter: "blur(4rem)",
WebkitBackdropFilter: "blur(4rem)",
backdropFilter: 'blur(4rem)',
WebkitBackdropFilter: 'blur(4rem)',
}}
align="center"
justify="center"
@@ -83,9 +83,9 @@ export function GameModeSection() {
<Flex align="center" gap="1">
<img
style={{
width: "1.5em",
height: "1.5em",
objectFit: "contain",
width: '1.5em',
height: '1.5em',
objectFit: 'contain',
}}
src={asset(`icons/provisions/${id}.webp`)}
alt={unwrap(provisions.name)}
@@ -95,7 +95,6 @@ export function GameModeSection() {
</Text>
);
})}
{gameDefinitions.roles[value].consumables.map((id) => {
const consumable = consumableDefinitions.consumables[id];
@@ -104,9 +103,9 @@ export function GameModeSection() {
<Flex align="center" gap="1">
<img
style={{
width: "1.5em",
height: "1.5em",
objectFit: "contain",
width: '1.5em',
height: '1.5em',
objectFit: 'contain',
}}
src={asset(`icons/consumables/${id}.webp`)}
alt={unwrap(consumable.name)}
@@ -108,10 +108,10 @@ export function CustomShellButton() {
{strings.common.shells.ap}
</Select.Item>
<Select.Item value={`${ShellType.APCR}`}>
{strings.common.shells.ap_cr}
{strings.common.shells.apcr}
</Select.Item>
<Select.Item value={`${ShellType.HEAT}`}>
{strings.common.shells.hc}
{strings.common.shells.heat}
</Select.Item>
<Select.Item value={`${ShellType.HE}`}>
{strings.common.shells.he}
@@ -1,17 +1,9 @@
import type { TankDefinition } from "@blitzkit/core";
import { checkConsumableProvisionInclusivity } from "@blitzkit/core/src/blitzkit/checkConsumableProvisionInclusivity";
import { times } from "lodash-es";
import { awaitableConsumableDefinitions } from "../../core/awaitables/consumableDefinitions";
import type { TankFilters } from "../../stores/tankFilters";
import { awaitableGameDefinitions } from "../awaitables/gameDefinitions";
import { awaitableProvisionDefinitions } from "../awaitables/provisionDefinitions";
const SHELLS = times(3, (index) => index);
const consumableDefinitions = await awaitableConsumableDefinitions;
const provisionDefinitions = await awaitableProvisionDefinitions;
const gameDefinitions = await awaitableGameDefinitions;
export async function filterTank(
filters: TankFilters,
tank: TankDefinition,
@@ -48,42 +40,6 @@ export async function filterTank(
gun.shells[index].type === filters.shells[index]
)
)
) &&
(filters.consumables.length === 0 ||
filters.consumables.every((consumable) =>
tank.turrets.some((turret) =>
turret.guns.some((gun) =>
checkConsumableProvisionInclusivity(
consumableDefinitions.consumables[consumable],
tank,
gun
)
)
)
)) &&
(filters.provisions.length === 0 ||
filters.provisions.every((provision) =>
tank.turrets.some((turret) =>
turret.guns.some((gun) =>
checkConsumableProvisionInclusivity(
provisionDefinitions.provisions[provision],
tank,
gun
)
)
)
)) &&
(filters.abilities.length === 0 ||
Object.values(tank.roles).some((id) =>
gameDefinitions.roles[id as unknown as number].consumables.some(
(consumable) => filters.abilities.includes(consumable)
)
)) &&
(filters.powers.length === 0 ||
Object.values(tank.roles).some((id) =>
gameDefinitions.roles[id as unknown as number].provisions.some(
(consumable) => filters.powers.includes(consumable)
)
))
)
);
}
@@ -19,9 +19,9 @@ import {
type TankDefinition,
type TrackDefinition,
type TurretDefinition,
} from "@blitzkit/core";
import { coefficient } from "@blitzkit/core/src/blitzkit/coefficient";
import type { EquipmentMatrix } from "../../stores/duel";
} from '@blitzkit/core';
import { coefficient } from '@blitzkit/core/src/blitzkit/coefficient';
import type { EquipmentMatrix } from '../../stores/duel';
export type TankCharacteristics = ReturnType<typeof tankCharacteristics>;
export type TankCharacteristicsKey = keyof TankCharacteristics;
@@ -76,7 +76,7 @@ export function tankCharacteristics(
tankModelDefinition: ModelDefinition;
equipmentDefinitions: EquipmentDefinitions;
provisionDefinitions: ProvisionDefinitions;
}
},
) {
const preset = equipmentDefinitions.presets[tank.equipment_preset];
const turretModelDefinition = tankModelDefinition.turrets[turret.id];
@@ -86,7 +86,7 @@ export function tankCharacteristics(
return preset.slots.some((slot, index) => {
const choice = equipmentMatrix[Math.floor(index / 3)][index % 3];
if (choice === 0) return false;
const equipped = slot[choice === -1 ? "left" : "right"];
const equipped = slot[choice === -1 ? 'left' : 'right'];
return equipped === id;
});
}
@@ -138,7 +138,7 @@ export function tankCharacteristics(
provisionDefinitions.provisions[provision].crew
? total + provisionDefinitions.provisions[provision].crew! / 100
: total,
0
0,
) + (hasImprovedVentilation ? 0.08 : 0);
const commanderMastery = 1 + provisionCrewBonus;
const loaderMastery =
@@ -158,19 +158,19 @@ export function tankCharacteristics(
coefficient(
[applyReactiveArmor && shell.type !== ShellType.HE, -0.27],
[applyDynamicArmor, -0.1],
[applySpallLiner && shell.type === ShellType.HE, -0.2]
[applySpallLiner && shell.type === ShellType.HE, -0.2],
);
const assaultDamageCoefficient =
gun.assault_ranges && gun.assault_ranges.types.includes(shell.type)
? gun.assault_ranges.ranges.find(
({ distance }) => distance >= assaultDistance
)?.factor ?? 0
? (gun.assault_ranges.ranges.find(
({ distance }) => distance >= assaultDistance,
)?.factor ?? 0)
: 1;
const moduleDamageCoefficient = coefficient([hasTungsten, 0.15]);
const reloadCoefficient =
(coefficient([hasGunRammer, -0.05]) *
coefficient([true, degressiveStat(loaderMastery)])) /
coefficient([hasAdrenaline && gun.gun_type!.$case === "regular", 0.17]);
coefficient([hasAdrenaline && gun.gun_type!.$case === 'regular', 0.17]);
const penetrationCoefficient = coefficient([
hasCalibratedShells,
resolvePenetrationCoefficient(true, shell.type) - 1,
@@ -178,16 +178,16 @@ export function tankCharacteristics(
const healthCoefficient = coefficient(
[hasSandbagArmor, 0.03],
[hasEnhancedSandbagArmor, 0.06],
[hasImprovedAssembly, 0.04]
[hasImprovedAssembly, 0.04],
);
const shellVelocityCoefficient = coefficient(
[hasSupercharger, 0.35],
[hasImprovedGunPowder, 0.3]
[hasImprovedGunPowder, 0.3],
);
const aimTimeCoefficient =
coefficient(
[hasEnhancedGunLayingDrive, -0.15],
[hasReticleCalibration, -0.3]
[hasReticleCalibration, -0.3],
) * coefficient([true, degressiveStat(gunnerMastery)]);
const dispersionStillCoefficient =
coefficient([hasRefinedGun, -0.15], [hasReticleCalibration, -0.3]) *
@@ -198,19 +198,19 @@ export function tankCharacteristics(
]);
const dispersionHullTraverseCoefficient = coefficient(
[hasVerticalStabilizer, -0.15],
[true, -crewSkills.smooth_turn / 100]
[true, -crewSkills.smooth_turn / 100],
);
const dispersionAfterShotCoefficient = coefficient(
[hasVerticalStabilizer, -0.15],
[true, -crewSkills.soft_recoil / 100]
[true, -crewSkills.soft_recoil / 100],
);
const dispersionMovingCoefficient = coefficient(
[hasVerticalStabilizer, -0.15],
[true, -crewSkills.smooth_driving / 100]
[true, -crewSkills.smooth_driving / 100],
);
const dispersionTurretTraverseCoefficient = coefficient(
[hasVerticalStabilizer, -0.15],
[true, -crewSkills.smooth_turret * (2 / 100)]
[true, -crewSkills.smooth_turret * (2 / 100)],
);
const enginePowerCoefficient = coefficient(
[
@@ -225,7 +225,7 @@ export function tankCharacteristics(
[hasStandardFuel, 0.03],
[hasImprovedFuel, 0.1],
[hasGearOil, 0.03],
[hasImprovedGearOil, 0.06]
[hasImprovedGearOil, 0.06],
);
const turretTraverseCoefficient =
coefficient([hasStandardFuel, 0.03], [hasImprovedFuel, 0.1]) *
@@ -234,41 +234,36 @@ export function tankCharacteristics(
coefficient(
[hasImprovedControl, 0.11],
[hasImprovedEnginePowerBoost, 0.01],
[true, crewSkills.virtuoso / 100]
[true, crewSkills.virtuoso / 100],
) * coefficient([true, progressiveStat(driverMastery)]);
const resistanceHardCoefficient =
coefficient([hasImprovedSuspension, -0.2]) *
coefficient([true, degressiveStat(driverMastery)]);
const resistanceMediumCoefficient =
coefficient([hasImprovedSuspension, -0.15]) *
coefficient([true, degressiveStat(driverMastery)]);
const resistanceSoftCoefficient =
coefficient([hasImprovedSuspension, -0.3]) *
coefficient([true, degressiveStat(driverMastery)]);
const resistanceHardCoefficient = coefficient([hasImprovedSuspension, -0.2]);
const resistanceMediumCoefficient = coefficient([
hasImprovedSuspension,
-0.15,
]);
const resistanceSoftCoefficient = coefficient([hasImprovedSuspension, -0.3]);
const viewRangeCoefficient =
coefficient([
hasImprovedOptics,
tank.class === TankClass.HEAVY
? 0.06
: tank.class === TankClass.MEDIUM
? 0.08
: tank.class === TankClass.LIGHT
? 0.11
: 0.04,
? 0.08
: tank.class === TankClass.LIGHT
? 0.11
: 0.04,
]) * coefficient([true, progressiveStat(commanderMastery)]);
const fireChanceCoefficient = coefficient([hasProtectiveKit, -0.2]);
const speedForwardsSum = sum(
[hasImprovedEnginePowerBoost, 4],
[hasGearOil, 2],
[hasImprovedGearOil, 4]
[hasImprovedGearOil, 4],
);
const speedBackwardsSum = sum(
[hasImprovedEnginePowerBoost, 8],
[hasGearOil, 2],
[hasImprovedGearOil, 4]
[hasImprovedGearOil, 4],
);
const camouflageCoefficientMoving = sum(
[
@@ -278,8 +273,8 @@ export function tankCharacteristics(
tank.class === TankClass.TANK_DESTROYER
? 0.04
: tank.class === TankClass.HEAVY
? 0.03
: 0.02,
? 0.03
: 0.02,
]),
],
[
@@ -287,11 +282,11 @@ export function tankCharacteristics(
tank.class === TankClass.HEAVY
? 0.02
: tank.class === TankClass.MEDIUM
? 0.04
: tank.class === TankClass.LIGHT
? 0.07
: 0.1,
]
? 0.04
: tank.class === TankClass.LIGHT
? 0.07
: 0.1,
],
);
const camouflageCoefficientStill = sum(
[
@@ -302,10 +297,10 @@ export function tankCharacteristics(
tank.class === TankClass.TANK_DESTROYER
? 0.04
: tank.class === TankClass.HEAVY
? 0.03
: 0.02,
? 0.03
: 0.02,
],
[true, crewSkills.camouflage * (3 / 100)]
[true, crewSkills.camouflage * (3 / 100)],
),
],
[
@@ -313,18 +308,18 @@ export function tankCharacteristics(
tank.class === TankClass.HEAVY
? 0.04
: tank.class === TankClass.MEDIUM
? 0.08
: tank.class === TankClass.LIGHT
? 0.14
: 0.2,
]
? 0.08
: tank.class === TankClass.LIGHT
? 0.14
: 0.2,
],
);
const size = normalizeBoundingBox(
unionBoundingBox(
tankModelDefinition.bounding_box,
turretModelDefinition.bounding_box
)
turretModelDefinition.bounding_box,
),
);
const weightKg =
tank.weight + engine.weight + track.weight + turret.weight + gun.weight;
@@ -344,14 +339,14 @@ export function tankCharacteristics(
shell,
damageCoefficient,
reloadCoefficient,
intraClipCoefficient
intraClipCoefficient,
);
const intraClip =
gun.gun_type?.$case === "regular"
gun.gun_type?.$case === 'regular'
? undefined
: gun.gun_type!.value.intra_clip * intraClipCoefficient;
const mostOptimalShellIndex =
gun.gun_type!.$case === "auto_reloader"
gun.gun_type!.$case === 'auto_reloader'
? gun.gun_type!.value.shell_reloads.reduce<null | {
index: number;
reload: number;
@@ -369,7 +364,7 @@ export function tankCharacteristics(
shell.armor_damage * damageCoefficientWithoutAssault;
const damage = shell.armor_damage * damageCoefficient;
const dpmEffective =
gun.gun_type!.$case === "auto_reloader"
gun.gun_type!.$case === 'auto_reloader'
? gun.gun_type!.value.shell_reloads[0] >
gun.gun_type!.value.shell_reloads[1] + gun.gun_type!.value.intra_clip
? ((armorDamageCoefficient * shell.armor_damage) /
@@ -394,23 +389,23 @@ export function tankCharacteristics(
gun.gun_type!.value.shell_reloads.slice(1).length
: undefined;
const shells =
gun.gun_type!.$case === "regular" ? 1 : gun.gun_type!.value.shell_count;
gun.gun_type!.$case === 'regular' ? 1 : gun.gun_type!.value.shell_count;
const shellReloads =
gun.gun_type!.$case === "auto_reloader"
gun.gun_type!.$case === 'auto_reloader'
? gun.gun_type!.value.shell_reloads.map(
(reload) => reload * reloadCoefficient
(reload) => reload * reloadCoefficient,
)
: undefined;
const shellReload =
gun.gun_type!.$case === "auto_reloader"
gun.gun_type!.$case === 'auto_reloader'
? undefined
: (gun.gun_type!.$case === "regular"
: (gun.gun_type!.$case === 'regular'
? gun.gun_type!.value.reload
: gun.gun_type!.value.clip_reload) * reloadCoefficient;
const caliber = shell.caliber;
const penetration = shell.penetration.near * penetrationCoefficient;
const clipDamage =
gun.gun_type!.$case === "regular"
gun.gun_type!.$case === 'regular'
? undefined
: gun.gun_type!.value.shell_count * damage;
const moduleDamage = shell.module_damage * moduleDamageCoefficient;
@@ -604,45 +599,45 @@ export const characteristicsOrder: {
items: TankCharacteristicsKey[];
}[] = [
{
name: "Firepower",
name: 'Firepower',
items: [
"gunType",
"dpm",
"shells",
"shellReload",
"shellReloads",
"intraClip",
"penetration",
"penetrationAt250m",
"damage",
"clipDamage",
"moduleDamage",
"caliber",
"shellNormalization",
"shellRicochet",
"shellVelocity",
"shellRange",
"shellCapacity",
"aimTime",
"dispersion",
"dispersionMoving",
"dispersionHullTraversing",
"dispersionTurretTraversing",
"dispersionShooting",
"dispersionGunDamaged",
"gunDepression",
"gunElevation",
"gunFrontalDepression",
"gunFrontalElevation",
"gunRearDepression",
"gunRearElevation",
"azimuthLeft",
"azimuthRight",
'gunType',
'dpm',
'shells',
'shellReload',
'shellReloads',
'intraClip',
'penetration',
'penetrationAt250m',
'damage',
'clipDamage',
'moduleDamage',
'caliber',
'shellNormalization',
'shellRicochet',
'shellVelocity',
'shellRange',
'shellCapacity',
'aimTime',
'dispersion',
'dispersionMoving',
'dispersionHullTraversing',
'dispersionTurretTraversing',
'dispersionShooting',
'dispersionGunDamaged',
'gunDepression',
'gunElevation',
'gunFrontalDepression',
'gunFrontalElevation',
'gunRearDepression',
'gunRearElevation',
'azimuthLeft',
'azimuthRight',
],
},
{
name: "Crew",
items: ["crewCount"],
name: 'Crew',
items: ['crewCount'],
},
];
@@ -1,5 +1,5 @@
import { Box, Flex, Heading } from "@radix-ui/themes";
import { Suspense, useEffect } from "react";
import { Suspense } from "react";
import { GuessBackground } from "../../../components/GuessBackground";
import { Guesser } from "../../../components/Guesser";
import { GuessRenderer } from "../../../components/GuessRenderer";
@@ -62,31 +62,6 @@ 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,5 +1,4 @@
import type { TankDefinition } from "@blitzkit/core";
import { times } from "lodash-es";
import { Varuna } from "varuna";
export enum GuessState {
@@ -15,7 +14,6 @@ export interface Guess {
correctGuesses: number;
streak: number;
helpingReveal: boolean;
tiers: number[];
}
export const Guess = new Varuna<Guess, TankDefinition>((tank) => ({
@@ -25,5 +23,4 @@ export const Guess = new Varuna<Guess, TankDefinition>((tank) => ({
correctGuesses: 0,
streak: 0,
helpingReveal: false,
tiers: times(10, (index) => index + 1),
}));
@@ -21,10 +21,6 @@ export interface TankFilters {
searching: boolean;
gunType: CaseType<GunDefinition>[];
shells: [ShellType | null, ShellType | null, ShellType | null];
consumables: number[];
provisions: number[];
abilities: number[];
powers: number[];
showTesting: boolean;
showNonTesting: boolean;
@@ -41,10 +37,6 @@ export const TankFilters = new Varuna<TankFilters>({
searching: false,
gunType: [],
shells: [null, null, null],
consumables: [],
provisions: [],
abilities: [],
powers: [],
showTesting: true,
showNonTesting: true,