Merge branch 'main' into ue

This commit is contained in:
TrèsAbhi committed 2026-01-02 12:51:20 -06:00
commit 1551dc50fd
21 files changed
+1676 -889

No files matched your search

+14
View File
@@ -0,0 +1,14 @@
# 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
@@ -4,63 +4,54 @@ import {
GunDefinition, GunDefinition,
Provision, Provision,
TankDefinition, TankDefinition,
} from '../protos'; } from "../protos";
export function checkConsumableProvisionInclusivity( export function checkConsumableProvisionInclusivity(
consumableProvision: Consumable | Provision, consumableProvision: Consumable | Provision,
tank: TankDefinition, tank: TankDefinition,
gun: GunDefinition, gun: GunDefinition
) { ) {
const included = const included = consumableProvision.include?.every((rule) => {
!consumableProvision.game_mode_exclusive && switch (rule.filter_type!.$case) {
consumableProvision.include?.every((rule) => { case "tiers":
switch (rule.filter_type!.$case) { return (
case 'tiers': rule.filter_type!.value.min <= tank.tier &&
return ( tank.tier <= rule.filter_type!.value.max
rule.filter_type!.value.min <= tank.tier && );
tank.tier <= rule.filter_type!.value.max
);
case 'ids': case "ids":
return rule.filter_type!.value.ids.includes(tank.id); return rule.filter_type!.value.ids.includes(tank.id);
case 'nations': case "nations":
return rule.filter_type!.value.nations.includes(tank.nation); return rule.filter_type!.value.nations.includes(tank.nation);
case 'categories': case "categories":
throw new SyntaxError('Category filtering found in include rule'); throw new SyntaxError("Category filtering found in include rule");
} }
}); });
const excluded = const excluded = consumableProvision.exclude?.some((rule) => {
consumableProvision.game_mode_exclusive || switch (rule.filter_type!.$case) {
consumableProvision.exclude?.some((rule) => { case "tiers":
switch (rule.filter_type!.$case) { return (
case 'tiers': rule.filter_type!.value.min <= tank.tier &&
return ( tank.tier <= rule.filter_type!.value.max
rule.filter_type!.value.min <= tank.tier && );
tank.tier <= rule.filter_type!.value.max
);
case 'ids': case "ids":
return rule.filter_type!.value.ids.includes(tank.id); return rule.filter_type!.value.ids.includes(tank.id);
case 'nations': case "nations":
return rule.filter_type!.value.nations.includes(tank.nation); return rule.filter_type!.value.nations.includes(tank.nation);
case 'categories': case "categories":
return rule.filter_type!.value.categories.some((category) => { return rule.filter_type!.value.categories.some((category) => {
switch (category) { switch (category) {
case ConsumableTankCategoryFilterCategory.CLIP: case ConsumableTankCategoryFilterCategory.CLIP:
return gun.gun_type!.$case !== 'regular'; return gun.gun_type!.$case !== "regular";
} }
}); });
} }
}); });
return ( return consumableProvision.include?.length > 0 && included && !excluded;
!consumableProvision.game_mode_exclusive &&
consumableProvision.include?.length > 0 &&
included &&
!excluded
);
} }
+8 -3
View File
@@ -15,7 +15,8 @@
"hp": "{value}hp", "hp": "{value}hp",
"x": "x{value}", "x": "x{value}",
"percentage": "{value}%", "percentage": "{value}%",
"deg_s": "{value}°/s" "deg_s": "{value}°/s",
"plus": "+{value}"
}, },
"gun_types": { "gun_types": {
@@ -26,8 +27,8 @@
"shells": { "shells": {
"ap": "AP", "ap": "AP",
"apcr": "APCR", "ap_cr": "APCR",
"heat": "HEAT", "hc": "HEAT",
"he": "HE" "he": "HE"
}, },
@@ -759,11 +760,15 @@
"count_singular": "1 tank", "count_singular": "1 tank",
"count_plural": "{count} tanks", "count_plural": "{count} tanks",
"sorting_by": "Sorting by {name}", "sorting_by": "Sorting by {name}",
"clear": "Clear",
"tier": "Tier {tier}",
"no_tanks_found": { "no_tanks_found": {
"body": "No tanks found.", "body": "No tanks found.",
"search_again": "Try searching again.", "search_again": "Try searching again.",
"clear_filters": "Try clearing filters." "clear_filters": "Try clearing filters."
}, },
"sort": { "sort": {
"meta.none": "none", "meta.none": "none",
"survivability.health": "health", "survivability.health": "health",
+17 -6
View File
@@ -13,7 +13,8 @@
"hp": "{value}hp", "hp": "{value}hp",
"x": "x{value}", "x": "x{value}",
"percentage": "{value}%", "percentage": "{value}%",
"deg_s": "{value}°/s" "deg_s": "{value}°/s",
"plus": "+{value}"
}, },
"gun_types": { "gun_types": {
"auto_loader": "Auto cargador", "auto_loader": "Auto cargador",
@@ -22,8 +23,8 @@
}, },
"shells": { "shells": {
"ap": "AP", "ap": "AP",
"apcr": "APCR", "ap_cr": "APCR",
"heat": "AEAC", "hc": "HEAT",
"he": "AE" "he": "AE"
}, },
"player_count_period": { "player_count_period": {
@@ -668,6 +669,8 @@
"count_singular": "1 tanque", "count_singular": "1 tanque",
"count_plural": "{count} tanks", "count_plural": "{count} tanks",
"sorting_by": "Sorting by {name}", "sorting_by": "Sorting by {name}",
"clear": "Limpiar",
"tier": "Tier {tier}",
"no_tanks_found": { "no_tanks_found": {
"body": "No se encontraron tanques.", "body": "No se encontraron tanques.",
"search_again": "Intenta buscar de nuevo.", "search_again": "Intenta buscar de nuevo.",
@@ -814,7 +817,13 @@
"next": "Next", "next": "Next",
"no_results": "Sin resultados" "no_results": "Sin resultados"
}, },
"stats": "{correct} / {total} guessed correctly" "stats": "{correct} / {total} correct, {streak} streak",
"help": {
"title": "Partially reveal tank?",
"description": "Reveals the tank without showing the name. This round will be considered incorrect and break your streak!",
"cancel": "Cancelar",
"reveal": "Reveal"
}
}, },
"tankopedia": { "tankopedia": {
"search": "Search a stat...", "search": "Search a stat...",
@@ -1135,10 +1144,12 @@
"upgrade": "Mejorar" "upgrade": "Mejorar"
}, },
"provisions": { "provisions": {
"clear": "Limpiar" "clear": "Clear this tank",
"clear_all": "Clear all tanks"
}, },
"consumables": { "consumables": {
"clear": "Limpiar" "clear": "Clear this tank",
"clear_all": "Clear all tanks"
} }
} }
}, },
+17 -6
View File
@@ -13,7 +13,8 @@
"hp": "{value}hp", "hp": "{value}hp",
"x": "x{value}", "x": "x{value}",
"percentage": "{value}%", "percentage": "{value}%",
"deg_s": "{value}°/s" "deg_s": "{value}°/s",
"plus": "+{value}"
}, },
"gun_types": { "gun_types": {
"auto_loader": "Chargement auto", "auto_loader": "Chargement auto",
@@ -22,8 +23,8 @@
}, },
"shells": { "shells": {
"ap": "AP", "ap": "AP",
"apcr": "APCR", "ap_cr": "APCR",
"heat": "HEAT", "hc": "HEAT",
"he": "HE" "he": "HE"
}, },
"player_count_period": { "player_count_period": {
@@ -668,6 +669,8 @@
"count_singular": "1 tank", "count_singular": "1 tank",
"count_plural": "{count} tanks", "count_plural": "{count} tanks",
"sorting_by": "Sorting by {name}", "sorting_by": "Sorting by {name}",
"clear": "Clear",
"tier": "Tier {tier}",
"no_tanks_found": { "no_tanks_found": {
"body": "No tanks found.", "body": "No tanks found.",
"search_again": "Try searching again.", "search_again": "Try searching again.",
@@ -814,7 +817,13 @@
"next": "Next", "next": "Next",
"no_results": "No results" "no_results": "No results"
}, },
"stats": "{correct} / {total} guessed correctly" "stats": "{correct} / {total} guessed correctly",
"help": {
"title": "Partially reveal tank?",
"description": "Reveals the tank without showing the name. This round will be considered incorrect and break your streak!",
"cancel": "Cancel",
"reveal": "Reveal"
}
}, },
"tankopedia": { "tankopedia": {
"search": "Search a stat...", "search": "Search a stat...",
@@ -1135,10 +1144,12 @@
"upgrade": "Upgrade" "upgrade": "Upgrade"
}, },
"provisions": { "provisions": {
"clear": "Clear" "clear": "Clear",
"clear_all": "Clear all tanks"
}, },
"consumables": { "consumables": {
"clear": "Clear" "clear": "Clear",
"clear_all": "Clear all tanks"
} }
} }
}, },
+17 -6
View File
@@ -13,7 +13,8 @@
"hp": "{value}hp", "hp": "{value}hp",
"x": "x{value}", "x": "x{value}",
"percentage": "{value}%", "percentage": "{value}%",
"deg_s": "{value}°/s" "deg_s": "{value}°/s",
"plus": "+{value}"
}, },
"gun_types": { "gun_types": {
"auto_loader": "自動装填砲", "auto_loader": "自動装填砲",
@@ -22,8 +23,8 @@
}, },
"shells": { "shells": {
"ap": "AP", "ap": "AP",
"apcr": "APCR", "ap_cr": "APCR",
"heat": "HEAT", "hc": "HEAT",
"he": "HE" "he": "HE"
}, },
"player_count_period": { "player_count_period": {
@@ -668,6 +669,8 @@
"count_singular": "1 輌", "count_singular": "1 輌",
"count_plural": "{count} tanks", "count_plural": "{count} tanks",
"sorting_by": "Sorting by {name}", "sorting_by": "Sorting by {name}",
"clear": "クリア",
"tier": "Tier {tier}",
"no_tanks_found": { "no_tanks_found": {
"body": "該当する車輌が存在しません。", "body": "該当する車輌が存在しません。",
"search_again": "もう一度検索してください。", "search_again": "もう一度検索してください。",
@@ -814,7 +817,13 @@
"next": "Next", "next": "Next",
"no_results": "該当なし" "no_results": "該当なし"
}, },
"stats": "{correct} / {total} guessed correctly" "stats": "{correct} / {total} correct, {streak} streak",
"help": {
"title": "Partially reveal tank?",
"description": "Reveals the tank without showing the name. This round will be considered incorrect and break your streak!",
"cancel": "Cancel",
"reveal": "Reveal"
}
}, },
"tankopedia": { "tankopedia": {
"search": "Search a stat...", "search": "Search a stat...",
@@ -1135,10 +1144,12 @@
"upgrade": "最終装備" "upgrade": "最終装備"
}, },
"provisions": { "provisions": {
"clear": "クリア" "clear": "Clear this tank",
"clear_all": "Clear all tanks"
}, },
"consumables": { "consumables": {
"clear": "クリア" "clear": "Clear this tank",
"clear_all": "Clear all tanks"
} }
} }
}, },
+17 -6
View File
@@ -13,7 +13,8 @@
"hp": "{value}hp", "hp": "{value}hp",
"x": "x{value}", "x": "x{value}",
"percentage": "{value}%", "percentage": "{value}%",
"deg_s": "{value}°/s" "deg_s": "{value}°/s",
"plus": "+{value}"
}, },
"gun_types": { "gun_types": {
"auto_loader": "Auto loader", "auto_loader": "Auto loader",
@@ -22,8 +23,8 @@
}, },
"shells": { "shells": {
"ap": "AP", "ap": "AP",
"apcr": "APCR", "ap_cr": "APCR",
"heat": "HEAT", "hc": "HEAT",
"he": "OB" "he": "OB"
}, },
"player_count_period": { "player_count_period": {
@@ -668,6 +669,8 @@
"count_singular": "1 tank", "count_singular": "1 tank",
"count_plural": "{count} tanks", "count_plural": "{count} tanks",
"sorting_by": "Sorting by {name}", "sorting_by": "Sorting by {name}",
"clear": "Clear",
"tier": "Tier {tier}",
"no_tanks_found": { "no_tanks_found": {
"body": "No tanks found.", "body": "No tanks found.",
"search_again": "Try searching again.", "search_again": "Try searching again.",
@@ -814,7 +817,13 @@
"next": "Next", "next": "Next",
"no_results": "Brak wyników" "no_results": "Brak wyników"
}, },
"stats": "{correct} / {total} guessed correctly" "stats": "{correct} / {total} correct, {streak} streak",
"help": {
"title": "Partially reveal tank?",
"description": "Reveals the tank without showing the name. This round will be considered incorrect and break your streak!",
"cancel": "Cancel",
"reveal": "Reveal"
}
}, },
"tankopedia": { "tankopedia": {
"search": "Search a stat...", "search": "Search a stat...",
@@ -1135,10 +1144,12 @@
"upgrade": "Upgrade" "upgrade": "Upgrade"
}, },
"provisions": { "provisions": {
"clear": "Clear" "clear": "Clear this tank",
"clear_all": "Clear all tanks"
}, },
"consumables": { "consumables": {
"clear": "Clear" "clear": "Clear this tank",
"clear_all": "Clear all tanks"
} }
} }
}, },
+17 -6
View File
@@ -13,7 +13,8 @@
"hp": "{value}hp", "hp": "{value}hp",
"x": "x{value}", "x": "x{value}",
"percentage": "{value}%", "percentage": "{value}%",
"deg_s": "{value}°/s" "deg_s": "{value}°/s",
"plus": "+{value}"
}, },
"gun_types": { "gun_types": {
"auto_loader": "Autocarregador", "auto_loader": "Autocarregador",
@@ -22,8 +23,8 @@
}, },
"shells": { "shells": {
"ap": "AP", "ap": "AP",
"apcr": "APCR", "ap_cr": "APCR",
"heat": "HEAT", "hc": "HEAT",
"he": "HE" "he": "HE"
}, },
"player_count_period": { "player_count_period": {
@@ -668,6 +669,8 @@
"count_singular": "1 tanque", "count_singular": "1 tanque",
"count_plural": "{count} tanks", "count_plural": "{count} tanks",
"sorting_by": "Sorting by {name}", "sorting_by": "Sorting by {name}",
"clear": "Limpar",
"tier": "Tier {tier}",
"no_tanks_found": { "no_tanks_found": {
"body": "Nenhum tanque encontrado.", "body": "Nenhum tanque encontrado.",
"search_again": "Tente pesquisar novamente.", "search_again": "Tente pesquisar novamente.",
@@ -814,7 +817,13 @@
"next": "Next", "next": "Next",
"no_results": "Sem resultados" "no_results": "Sem resultados"
}, },
"stats": "{correct} / {total} guessed correctly" "stats": "{correct} / {total} correct, {streak} streak",
"help": {
"title": "Partially reveal tank?",
"description": "Reveals the tank without showing the name. This round will be considered incorrect and break your streak!",
"cancel": "Cancelar",
"reveal": "Reveal"
}
}, },
"tankopedia": { "tankopedia": {
"search": "Search a stat...", "search": "Search a stat...",
@@ -1135,10 +1144,12 @@
"upgrade": "Melhorar" "upgrade": "Melhorar"
}, },
"provisions": { "provisions": {
"clear": "Limpar" "clear": "Clear this tank",
"clear_all": "Clear all tanks"
}, },
"consumables": { "consumables": {
"clear": "Limpar" "clear": "Clear this tank",
"clear_all": "Clear all tanks"
} }
} }
}, },
+17 -6
View File
@@ -13,7 +13,8 @@
"hp": "{value}оп", "hp": "{value}оп",
"x": "x{value}", "x": "x{value}",
"percentage": "{value}%", "percentage": "{value}%",
"deg_s": "{value}°/с" "deg_s": "{value}°/с",
"plus": "+{value}"
}, },
"gun_types": { "gun_types": {
"auto_loader": "Барабан", "auto_loader": "Барабан",
@@ -22,8 +23,8 @@
}, },
"shells": { "shells": {
"ap": "ББ", "ap": "ББ",
"apcr": "БП", "ap_cr": "APCR",
"heat": "КС", "hc": "HEAT",
"he": "ОФ" "he": "ОФ"
}, },
"player_count_period": { "player_count_period": {
@@ -668,6 +669,8 @@
"count_singular": "1 танк", "count_singular": "1 танк",
"count_plural": "Танков: {count}", "count_plural": "Танков: {count}",
"sorting_by": "Сортировать по {name}", "sorting_by": "Сортировать по {name}",
"clear": "Очистить",
"tier": " {tier} уровня",
"no_tanks_found": { "no_tanks_found": {
"body": "Танков не найдено.", "body": "Танков не найдено.",
"search_again": "Попробуйте повторить поиск.", "search_again": "Попробуйте повторить поиск.",
@@ -814,7 +817,13 @@
"next": "Следующий", "next": "Следующий",
"no_results": "Ничего не найденo" "no_results": "Ничего не найденo"
}, },
"stats": "{correct} / {total} угадано верно" "stats": "{correct} / {total} правильно, серия: {streak}",
"help": {
"title": "Частично раскрыть танк?",
"description": "Показывает танк не показывая название. Этот раунд будет считаться проигранным и прекратит серию!",
"cancel": "Отмена",
"reveal": "Раскрыть"
}
}, },
"tankopedia": { "tankopedia": {
"search": "Поиск характеристик...", "search": "Поиск характеристик...",
@@ -1135,10 +1144,12 @@
"upgrade": "Топ" "upgrade": "Топ"
}, },
"provisions": { "provisions": {
"clear": "Очистить" "clear": "Убрать",
"clear_all": "Убрать со всех"
}, },
"consumables": { "consumables": {
"clear": "Очистить" "clear": "Убрать",
"clear_all": "Убрать со всех"
} }
} }
}, },
+17 -6
View File
@@ -13,7 +13,8 @@
"hp": "{value}од", "hp": "{value}од",
"x": "x{value}", "x": "x{value}",
"percentage": "{value}%", "percentage": "{value}%",
"deg_s": "{value}град/c" "deg_s": "{value}град/c",
"plus": "+{value}"
}, },
"gun_types": { "gun_types": {
"auto_loader": "Магазинна система заряджання", "auto_loader": "Магазинна система заряджання",
@@ -22,8 +23,8 @@
}, },
"shells": { "shells": {
"ap": "ББ", "ap": "ББ",
"apcr": "БП", "ap_cr": "APCR",
"heat": "КС", "hc": "HEAT",
"he": "ОФ" "he": "ОФ"
}, },
"player_count_period": { "player_count_period": {
@@ -668,6 +669,8 @@
"count_singular": "1 танк", "count_singular": "1 танк",
"count_plural": "Танків: {count} ", "count_plural": "Танків: {count} ",
"sorting_by": "Відсортувати за {name}", "sorting_by": "Відсортувати за {name}",
"clear": "Clear",
"tier": "Рівень {tier}",
"no_tanks_found": { "no_tanks_found": {
"body": "Нічого не знайдено.", "body": "Нічого не знайдено.",
"search_again": "Спробуйте шукати заново.", "search_again": "Спробуйте шукати заново.",
@@ -814,7 +817,13 @@
"next": "Next", "next": "Next",
"no_results": "Немає результатів" "no_results": "Немає результатів"
}, },
"stats": "{correct} / {total} guessed correctly" "stats": "{correct} / {total} correct, {streak} streak",
"help": {
"title": "Partially reveal tank?",
"description": "Reveals the tank without showing the name. This round will be considered incorrect and break your streak!",
"cancel": "Cancel",
"reveal": "Reveal"
}
}, },
"tankopedia": { "tankopedia": {
"search": "Search a stat...", "search": "Search a stat...",
@@ -1135,10 +1144,12 @@
"upgrade": "Upgrade" "upgrade": "Upgrade"
}, },
"provisions": { "provisions": {
"clear": "Clear" "clear": "Clear this tank",
"clear_all": "Clear all tanks"
}, },
"consumables": { "consumables": {
"clear": "Clear" "clear": "Clear this tank",
"clear_all": "Clear all tanks"
} }
} }
}, },
+17 -6
View File
@@ -13,7 +13,8 @@
"hp": "{value} 生命值", "hp": "{value} 生命值",
"x": "x{value}", "x": "x{value}",
"percentage": "{value}%", "percentage": "{value}%",
"deg_s": "{value}度°/秒" "deg_s": "{value}度°/秒",
"plus": "+{value}"
}, },
"gun_types": { "gun_types": {
"auto_loader": "弹夹型坦克", "auto_loader": "弹夹型坦克",
@@ -22,8 +23,8 @@
}, },
"shells": { "shells": {
"ap": "穿甲弹", "ap": "穿甲弹",
"apcr": "硬式复合穿甲弹", "ap_cr": "APCR",
"heat": "反战车高爆弹", "hc": "HEAT",
"he": "高爆弹" "he": "高爆弹"
}, },
"player_count_period": { "player_count_period": {
@@ -668,6 +669,8 @@
"count_singular": "1辆战车", "count_singular": "1辆战车",
"count_plural": "{count} 辆坦克", "count_plural": "{count} 辆坦克",
"sorting_by": "按 {name} 排序", "sorting_by": "按 {name} 排序",
"clear": "清除",
"tier": "{tier} 阶",
"no_tanks_found": { "no_tanks_found": {
"body": "找不到战车。", "body": "找不到战车。",
"search_again": "请尝试重新搜索。", "search_again": "请尝试重新搜索。",
@@ -814,7 +817,13 @@
"next": "下一个", "next": "下一个",
"no_results": "沒有結果" "no_results": "沒有結果"
}, },
"stats": "猜对了 {correct} / {total}" "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!",
"cancel": "取消",
"reveal": "Reveal"
}
}, },
"tankopedia": { "tankopedia": {
"search": "搜索属性……", "search": "搜索属性……",
@@ -1135,10 +1144,12 @@
"upgrade": "顶级配置" "upgrade": "顶级配置"
}, },
"provisions": { "provisions": {
"clear": "清除" "clear": "清除",
"clear_all": "Clear all tanks"
}, },
"consumables": { "consumables": {
"clear": "清除" "clear": "清除",
"clear_all": "Clear all tanks"
} }
} }
}, },
+134 -138
View File
@@ -1557,87 +1557,85 @@ export async function definitions() {
const includeRaw = consumable.vehicleFilter?.include.vehicle; const includeRaw = consumable.vehicleFilter?.include.vehicle;
const excludeRaw = consumable.vehicleFilter?.exclude?.vehicle; const excludeRaw = consumable.vehicleFilter?.exclude?.vehicle;
if (!entry.game_mode_exclusive) { if (includeRaw) {
if (includeRaw) { entry.include = [];
entry.include = [];
if ("minLevel" in includeRaw) { if ("minLevel" in includeRaw) {
entry.include.push({ entry.include.push({
filter_type: { filter_type: {
$case: "tiers", $case: "tiers",
value: { value: {
min: includeRaw.minLevel, min: includeRaw.minLevel,
max: includeRaw.maxLevel, max: includeRaw.maxLevel,
},
}, },
}); },
} else if ("name" in includeRaw) { });
entry.include.push({ } else if ("name" in includeRaw) {
filter_type: { entry.include.push({
$case: "ids", filter_type: {
value: { $case: "ids",
ids: includeRaw.name.split(/ +/).map((key) => { value: {
return tankStringIdMap[key]; 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) { if (consumable.vehicleFilter?.include.nations) {
entry.include.push({ entry.include.push({
filter_type: { filter_type: {
$case: "nations", $case: "nations",
value: { value: {
nations: consumable.vehicleFilter.include.nations.split(" "), nations: consumable.vehicleFilter.include.nations.split(" "),
},
}, },
}); },
} });
} }
}
if (excludeRaw) { if (excludeRaw) {
entry.exclude = []; entry.exclude = [];
if ("name" in excludeRaw) { if ("name" in excludeRaw) {
entry.exclude!.push({ entry.exclude!.push({
filter_type: { filter_type: {
$case: "ids", $case: "ids",
value: { value: {
ids: excludeRaw.name.split(/ +/).map((key) => { ids: excludeRaw.name.split(/ +/).map((key) => {
return tankStringIdMap[key]; return tankStringIdMap[key];
}), }),
},
}, },
}); },
} else if ("extendedTags" in excludeRaw) { });
entry.exclude!.push({ } else if ("extendedTags" in excludeRaw) {
filter_type: { entry.exclude!.push({
$case: "categories", filter_type: {
value: { $case: "categories",
categories: excludeRaw.extendedTags value: {
.split(" ") categories: excludeRaw.extendedTags
.map( .split(" ")
(item) => .map(
blitzTankFilterDefinitionCategoryToBlitzkit[ (item) =>
item as BlitzTankFilterDefinitionCategory blitzTankFilterDefinitionCategoryToBlitzkit[
] item as BlitzTankFilterDefinitionCategory
), ]
}, ),
}, },
}); },
} else throw new SyntaxError("Unhandled exclude type"); });
} else throw new SyntaxError("Unhandled exclude type");
if (consumable.vehicleFilter?.exclude?.nations) { if (consumable.vehicleFilter?.exclude?.nations) {
entry.exclude!.push({ entry.exclude!.push({
filter_type: { filter_type: {
$case: "nations", $case: "nations",
value: { value: {
nations: consumable.vehicleFilter.exclude.nations.split(" "), nations: consumable.vehicleFilter.exclude.nations.split(" "),
},
}, },
}); },
} });
} }
} }
}); });
@@ -1657,87 +1655,85 @@ export async function definitions() {
const includeRaw = provision.vehicleFilter?.include.vehicle; const includeRaw = provision.vehicleFilter?.include.vehicle;
const excludeRaw = provision.vehicleFilter?.exclude?.vehicle; const excludeRaw = provision.vehicleFilter?.exclude?.vehicle;
if (!entry.game_mode_exclusive) { if (includeRaw) {
if (includeRaw) { entry.include = [];
entry.include = [];
if ("minLevel" in includeRaw) { if ("minLevel" in includeRaw) {
entry.include.push({ entry.include.push({
filter_type: { filter_type: {
$case: "tiers", $case: "tiers",
value: { value: {
min: includeRaw.minLevel, min: includeRaw.minLevel,
max: includeRaw.maxLevel, max: includeRaw.maxLevel,
},
}, },
}); },
} else if ("name" in includeRaw) { });
entry.include.push({ } else if ("name" in includeRaw) {
filter_type: { entry.include.push({
$case: "ids", filter_type: {
value: { $case: "ids",
ids: includeRaw.name.split(/ +/).map((key) => { value: {
return tankStringIdMap[key]; 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) { if (provision.vehicleFilter?.include.nations) {
entry.include.push({ entry.include.push({
filter_type: { filter_type: {
$case: "nations", $case: "nations",
value: { value: {
nations: provision.vehicleFilter.include.nations.split(" "), nations: provision.vehicleFilter.include.nations.split(" "),
},
}, },
}); },
} });
} }
}
if (excludeRaw) { if (excludeRaw) {
entry.exclude = []; entry.exclude = [];
if ("name" in excludeRaw) { if ("name" in excludeRaw) {
entry.exclude!.push({ entry.exclude!.push({
filter_type: { filter_type: {
$case: "ids", $case: "ids",
value: { value: {
ids: excludeRaw.name ids: excludeRaw.name
.split(/ +/) .split(/ +/)
.map((key) => tankStringIdMap[key]), .map((key) => tankStringIdMap[key]),
},
}, },
}); },
} else if ("extendedTags" in excludeRaw) { });
entry.exclude!.push({ } else if ("extendedTags" in excludeRaw) {
filter_type: { entry.exclude!.push({
$case: "categories", filter_type: {
value: { $case: "categories",
categories: excludeRaw.extendedTags value: {
.split(" ") categories: excludeRaw.extendedTags
.map( .split(" ")
(item) => .map(
blitzTankFilterDefinitionCategoryToBlitzkit[ (item) =>
item as BlitzTankFilterDefinitionCategory blitzTankFilterDefinitionCategoryToBlitzkit[
] item as BlitzTankFilterDefinitionCategory
), ]
}, ),
}, },
}); },
} else throw new SyntaxError("Unhandled exclude type"); });
} else throw new SyntaxError("Unhandled exclude type");
if (provision.vehicleFilter?.exclude?.nations) { if (provision.vehicleFilter?.exclude?.nations) {
entry.exclude!.push({ entry.exclude!.push({
filter_type: { filter_type: {
$case: "nations", $case: "nations",
value: { value: {
nations: provision.vehicleFilter.exclude.nations.split(" "), nations: provision.vehicleFilter.exclude.nations.split(" "),
},
}, },
}); },
} });
} }
} }
@@ -1,6 +1,6 @@
import type { Consumable } from '@blitzkit/core'; import type { Consumable } from "@blitzkit/core";
import { Flex, type FlexProps } from '@radix-ui/themes'; import { Flex, type FlexProps } from "@radix-ui/themes";
import { ConsumableButton } from './ModuleButtons/ConsumableButton'; import { ConsumableButton } from "./ModuleButtons/ConsumableButton";
type ConsumablesManagerProps = FlexProps & { type ConsumablesManagerProps = FlexProps & {
consumables: Consumable[]; consumables: Consumable[];
@@ -27,6 +27,8 @@ export function ConsumablesManager({
return ( return (
<Flex wrap="wrap" gap="2" {...props}> <Flex wrap="wrap" gap="2" {...props}>
{consumables.map((consumable) => { {consumables.map((consumable) => {
if (consumable.game_mode_exclusive) return null;
const isSelected = selected.some((id) => id === consumable.id); const isSelected = selected.some((id) => id === consumable.id);
return ( return (
@@ -1,5 +1,6 @@
import { Flex } from '@radix-ui/themes'; import { Flex } from "@radix-ui/themes";
import { ProvisionButton } from './ModuleButtons/ProvisionButton'; import { awaitableProvisionDefinitions } from "../core/awaitables/provisionDefinitions";
import { ProvisionButton } from "./ModuleButtons/ProvisionButton";
interface ProvisionsManagerProps { interface ProvisionsManagerProps {
provisions: number[]; provisions: number[];
@@ -8,6 +9,8 @@ interface ProvisionsManagerProps {
onChange?: (provisions: number[]) => void; onChange?: (provisions: number[]) => void;
} }
const provisionDefinitions = await awaitableProvisionDefinitions;
export function ProvisionsManager({ export function ProvisionsManager({
provisions, provisions,
selected, selected,
@@ -15,15 +18,18 @@ export function ProvisionsManager({
disabled, disabled,
}: ProvisionsManagerProps) { }: ProvisionsManagerProps) {
return ( return (
<Flex wrap="wrap" gap="2" justify={{ initial: 'center', sm: 'start' }}> <Flex wrap="wrap" gap="2" justify={{ initial: "center", sm: "start" }}>
{provisions.map((provision) => { {provisions.map((id) => {
const isSelected = selected.includes(provision); const isSelected = selected.includes(id);
const provision = provisionDefinitions.provisions[id];
if (provision.game_mode_exclusive) return null;
return ( return (
<ProvisionButton <ProvisionButton
key={provision} key={id}
disabled={disabled && !isSelected} disabled={disabled && !isSelected}
provision={provision} provision={id}
selected={isSelected} selected={isSelected}
onClick={() => { onClick={() => {
if (!onChange) return; if (!onChange) return;
@@ -31,9 +37,9 @@ export function ProvisionsManager({
const draft = [...selected]; const draft = [...selected];
if (isSelected) { if (isSelected) {
draft.splice(draft.indexOf(provision), 1); draft.splice(draft.indexOf(id), 1);
} else { } else {
draft.push(provision); draft.push(id);
} }
onChange(draft); onChange(draft);
File diff suppressed because it is too large. Load diff
@@ -429,7 +429,7 @@ export const TankSearch = memo<TankSearchProps>(
}, [tankFilters, tankopediaSort]); }, [tankFilters, tankopediaSort]);
return ( return (
<Flex direction="column" gap="4" flexGrow="1" {...props}> <Flex direction="column" gap="2" flexGrow="1" {...props}>
<SearchBar <SearchBar
skeleton={skeleton} skeleton={skeleton}
topResult={tanks?.[0]} topResult={tanks?.[0]}
@@ -1,10 +1,10 @@
import { asset } from '@blitzkit/core'; import { asset } from "@blitzkit/core";
import { Box, Flex, Heading, Text } from '@radix-ui/themes'; import { Box, Flex, Heading, Text } from "@radix-ui/themes";
import { awaitableConsumableDefinitions } from '../../core/awaitables/consumableDefinitions'; import { awaitableConsumableDefinitions } from "../../core/awaitables/consumableDefinitions";
import { awaitableGameDefinitions } from '../../core/awaitables/gameDefinitions'; import { awaitableGameDefinitions } from "../../core/awaitables/gameDefinitions";
import { awaitableProvisionDefinitions } from '../../core/awaitables/provisionDefinitions'; import { awaitableProvisionDefinitions } from "../../core/awaitables/provisionDefinitions";
import { useLocale } from '../../hooks/useLocale'; import { useLocale } from "../../hooks/useLocale";
import { Duel } from '../../stores/duel'; import { Duel } from "../../stores/duel";
const [gameDefinitions, consumableDefinitions, provisionDefinitions] = const [gameDefinitions, consumableDefinitions, provisionDefinitions] =
await Promise.all([ await Promise.all([
@@ -39,22 +39,22 @@ export function GameModeSection() {
overflow="hidden" overflow="hidden"
direction="column" direction="column"
style={{ style={{
borderRadius: 'var(--radius-3)', borderRadius: "var(--radius-3)",
background: `url(${asset( background: `url(${asset(
`icons/game_mode_banners/${id}.webp`, `icons/game_mode_banners/${id}.webp`
)})`, )})`,
backgroundSize: 'cover', backgroundSize: "cover",
backgroundPosition: 'center', backgroundPosition: "center",
}} }}
> >
<Box <Box
flexGrow="1" flexGrow="1"
style={{ style={{
background: `url(${asset( background: `url(${asset(
`icons/game_mode_banners/${id}.webp`, `icons/game_mode_banners/${id}.webp`
)})`, )})`,
backgroundSize: 'cover', backgroundSize: "cover",
backgroundPosition: 'center', backgroundPosition: "center",
}} }}
/> />
@@ -64,8 +64,8 @@ export function GameModeSection() {
height="98px" height="98px"
flexShrink="0" flexShrink="0"
style={{ style={{
backdropFilter: 'blur(4rem)', backdropFilter: "blur(4rem)",
WebkitBackdropFilter: 'blur(4rem)', WebkitBackdropFilter: "blur(4rem)",
}} }}
align="center" align="center"
justify="center" justify="center"
@@ -83,9 +83,9 @@ export function GameModeSection() {
<Flex align="center" gap="1"> <Flex align="center" gap="1">
<img <img
style={{ style={{
width: '1.5em', width: "1.5em",
height: '1.5em', height: "1.5em",
objectFit: 'contain', objectFit: "contain",
}} }}
src={asset(`icons/provisions/${id}.webp`)} src={asset(`icons/provisions/${id}.webp`)}
alt={unwrap(provisions.name)} alt={unwrap(provisions.name)}
@@ -95,6 +95,7 @@ export function GameModeSection() {
</Text> </Text>
); );
})} })}
{gameDefinitions.roles[value].consumables.map((id) => { {gameDefinitions.roles[value].consumables.map((id) => {
const consumable = consumableDefinitions.consumables[id]; const consumable = consumableDefinitions.consumables[id];
@@ -103,9 +104,9 @@ export function GameModeSection() {
<Flex align="center" gap="1"> <Flex align="center" gap="1">
<img <img
style={{ style={{
width: '1.5em', width: "1.5em",
height: '1.5em', height: "1.5em",
objectFit: 'contain', objectFit: "contain",
}} }}
src={asset(`icons/consumables/${id}.webp`)} src={asset(`icons/consumables/${id}.webp`)}
alt={unwrap(consumable.name)} alt={unwrap(consumable.name)}
@@ -108,10 +108,10 @@ export function CustomShellButton() {
{strings.common.shells.ap} {strings.common.shells.ap}
</Select.Item> </Select.Item>
<Select.Item value={`${ShellType.APCR}`}> <Select.Item value={`${ShellType.APCR}`}>
{strings.common.shells.apcr} {strings.common.shells.ap_cr}
</Select.Item> </Select.Item>
<Select.Item value={`${ShellType.HEAT}`}> <Select.Item value={`${ShellType.HEAT}`}>
{strings.common.shells.heat} {strings.common.shells.hc}
</Select.Item> </Select.Item>
<Select.Item value={`${ShellType.HE}`}> <Select.Item value={`${ShellType.HE}`}>
{strings.common.shells.he} {strings.common.shells.he}
@@ -1,9 +1,17 @@
import type { TankDefinition } from "@blitzkit/core"; import type { TankDefinition } from "@blitzkit/core";
import { checkConsumableProvisionInclusivity } from "@blitzkit/core/src/blitzkit/checkConsumableProvisionInclusivity";
import { times } from "lodash-es"; import { times } from "lodash-es";
import { awaitableConsumableDefinitions } from "../../core/awaitables/consumableDefinitions";
import type { TankFilters } from "../../stores/tankFilters"; import type { TankFilters } from "../../stores/tankFilters";
import { awaitableGameDefinitions } from "../awaitables/gameDefinitions";
import { awaitableProvisionDefinitions } from "../awaitables/provisionDefinitions";
const SHELLS = times(3, (index) => index); const SHELLS = times(3, (index) => index);
const consumableDefinitions = await awaitableConsumableDefinitions;
const provisionDefinitions = await awaitableProvisionDefinitions;
const gameDefinitions = await awaitableGameDefinitions;
export async function filterTank( export async function filterTank(
filters: TankFilters, filters: TankFilters,
tank: TankDefinition, tank: TankDefinition,
@@ -40,6 +48,42 @@ export async function filterTank(
gun.shells[index].type === filters.shells[index] 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 TankDefinition,
type TrackDefinition, type TrackDefinition,
type TurretDefinition, type TurretDefinition,
} from '@blitzkit/core'; } from "@blitzkit/core";
import { coefficient } from '@blitzkit/core/src/blitzkit/coefficient'; import { coefficient } from "@blitzkit/core/src/blitzkit/coefficient";
import type { EquipmentMatrix } from '../../stores/duel'; import type { EquipmentMatrix } from "../../stores/duel";
export type TankCharacteristics = ReturnType<typeof tankCharacteristics>; export type TankCharacteristics = ReturnType<typeof tankCharacteristics>;
export type TankCharacteristicsKey = keyof TankCharacteristics; export type TankCharacteristicsKey = keyof TankCharacteristics;
@@ -76,7 +76,7 @@ export function tankCharacteristics(
tankModelDefinition: ModelDefinition; tankModelDefinition: ModelDefinition;
equipmentDefinitions: EquipmentDefinitions; equipmentDefinitions: EquipmentDefinitions;
provisionDefinitions: ProvisionDefinitions; provisionDefinitions: ProvisionDefinitions;
}, }
) { ) {
const preset = equipmentDefinitions.presets[tank.equipment_preset]; const preset = equipmentDefinitions.presets[tank.equipment_preset];
const turretModelDefinition = tankModelDefinition.turrets[turret.id]; const turretModelDefinition = tankModelDefinition.turrets[turret.id];
@@ -86,7 +86,7 @@ export function tankCharacteristics(
return preset.slots.some((slot, index) => { return preset.slots.some((slot, index) => {
const choice = equipmentMatrix[Math.floor(index / 3)][index % 3]; const choice = equipmentMatrix[Math.floor(index / 3)][index % 3];
if (choice === 0) return false; if (choice === 0) return false;
const equipped = slot[choice === -1 ? 'left' : 'right']; const equipped = slot[choice === -1 ? "left" : "right"];
return equipped === id; return equipped === id;
}); });
} }
@@ -138,7 +138,7 @@ export function tankCharacteristics(
provisionDefinitions.provisions[provision].crew provisionDefinitions.provisions[provision].crew
? total + provisionDefinitions.provisions[provision].crew! / 100 ? total + provisionDefinitions.provisions[provision].crew! / 100
: total, : total,
0, 0
) + (hasImprovedVentilation ? 0.08 : 0); ) + (hasImprovedVentilation ? 0.08 : 0);
const commanderMastery = 1 + provisionCrewBonus; const commanderMastery = 1 + provisionCrewBonus;
const loaderMastery = const loaderMastery =
@@ -158,19 +158,19 @@ export function tankCharacteristics(
coefficient( coefficient(
[applyReactiveArmor && shell.type !== ShellType.HE, -0.27], [applyReactiveArmor && shell.type !== ShellType.HE, -0.27],
[applyDynamicArmor, -0.1], [applyDynamicArmor, -0.1],
[applySpallLiner && shell.type === ShellType.HE, -0.2], [applySpallLiner && shell.type === ShellType.HE, -0.2]
); );
const assaultDamageCoefficient = const assaultDamageCoefficient =
gun.assault_ranges && gun.assault_ranges.types.includes(shell.type) gun.assault_ranges && gun.assault_ranges.types.includes(shell.type)
? (gun.assault_ranges.ranges.find( ? gun.assault_ranges.ranges.find(
({ distance }) => distance >= assaultDistance, ({ distance }) => distance >= assaultDistance
)?.factor ?? 0) )?.factor ?? 0
: 1; : 1;
const moduleDamageCoefficient = coefficient([hasTungsten, 0.15]); const moduleDamageCoefficient = coefficient([hasTungsten, 0.15]);
const reloadCoefficient = const reloadCoefficient =
(coefficient([hasGunRammer, -0.05]) * (coefficient([hasGunRammer, -0.05]) *
coefficient([true, degressiveStat(loaderMastery)])) / 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([ const penetrationCoefficient = coefficient([
hasCalibratedShells, hasCalibratedShells,
resolvePenetrationCoefficient(true, shell.type) - 1, resolvePenetrationCoefficient(true, shell.type) - 1,
@@ -178,16 +178,16 @@ export function tankCharacteristics(
const healthCoefficient = coefficient( const healthCoefficient = coefficient(
[hasSandbagArmor, 0.03], [hasSandbagArmor, 0.03],
[hasEnhancedSandbagArmor, 0.06], [hasEnhancedSandbagArmor, 0.06],
[hasImprovedAssembly, 0.04], [hasImprovedAssembly, 0.04]
); );
const shellVelocityCoefficient = coefficient( const shellVelocityCoefficient = coefficient(
[hasSupercharger, 0.35], [hasSupercharger, 0.35],
[hasImprovedGunPowder, 0.3], [hasImprovedGunPowder, 0.3]
); );
const aimTimeCoefficient = const aimTimeCoefficient =
coefficient( coefficient(
[hasEnhancedGunLayingDrive, -0.15], [hasEnhancedGunLayingDrive, -0.15],
[hasReticleCalibration, -0.3], [hasReticleCalibration, -0.3]
) * coefficient([true, degressiveStat(gunnerMastery)]); ) * coefficient([true, degressiveStat(gunnerMastery)]);
const dispersionStillCoefficient = const dispersionStillCoefficient =
coefficient([hasRefinedGun, -0.15], [hasReticleCalibration, -0.3]) * coefficient([hasRefinedGun, -0.15], [hasReticleCalibration, -0.3]) *
@@ -198,19 +198,19 @@ export function tankCharacteristics(
]); ]);
const dispersionHullTraverseCoefficient = coefficient( const dispersionHullTraverseCoefficient = coefficient(
[hasVerticalStabilizer, -0.15], [hasVerticalStabilizer, -0.15],
[true, -crewSkills.smooth_turn / 100], [true, -crewSkills.smooth_turn / 100]
); );
const dispersionAfterShotCoefficient = coefficient( const dispersionAfterShotCoefficient = coefficient(
[hasVerticalStabilizer, -0.15], [hasVerticalStabilizer, -0.15],
[true, -crewSkills.soft_recoil / 100], [true, -crewSkills.soft_recoil / 100]
); );
const dispersionMovingCoefficient = coefficient( const dispersionMovingCoefficient = coefficient(
[hasVerticalStabilizer, -0.15], [hasVerticalStabilizer, -0.15],
[true, -crewSkills.smooth_driving / 100], [true, -crewSkills.smooth_driving / 100]
); );
const dispersionTurretTraverseCoefficient = coefficient( const dispersionTurretTraverseCoefficient = coefficient(
[hasVerticalStabilizer, -0.15], [hasVerticalStabilizer, -0.15],
[true, -crewSkills.smooth_turret * (2 / 100)], [true, -crewSkills.smooth_turret * (2 / 100)]
); );
const enginePowerCoefficient = coefficient( const enginePowerCoefficient = coefficient(
[ [
@@ -225,7 +225,7 @@ export function tankCharacteristics(
[hasStandardFuel, 0.03], [hasStandardFuel, 0.03],
[hasImprovedFuel, 0.1], [hasImprovedFuel, 0.1],
[hasGearOil, 0.03], [hasGearOil, 0.03],
[hasImprovedGearOil, 0.06], [hasImprovedGearOil, 0.06]
); );
const turretTraverseCoefficient = const turretTraverseCoefficient =
coefficient([hasStandardFuel, 0.03], [hasImprovedFuel, 0.1]) * coefficient([hasStandardFuel, 0.03], [hasImprovedFuel, 0.1]) *
@@ -234,36 +234,41 @@ export function tankCharacteristics(
coefficient( coefficient(
[hasImprovedControl, 0.11], [hasImprovedControl, 0.11],
[hasImprovedEnginePowerBoost, 0.01], [hasImprovedEnginePowerBoost, 0.01],
[true, crewSkills.virtuoso / 100], [true, crewSkills.virtuoso / 100]
) * coefficient([true, progressiveStat(driverMastery)]); ) * coefficient([true, progressiveStat(driverMastery)]);
const resistanceHardCoefficient = coefficient([hasImprovedSuspension, -0.2]);
const resistanceMediumCoefficient = coefficient([ const resistanceHardCoefficient =
hasImprovedSuspension, coefficient([hasImprovedSuspension, -0.2]) *
-0.15, coefficient([true, degressiveStat(driverMastery)]);
]); const resistanceMediumCoefficient =
const resistanceSoftCoefficient = coefficient([hasImprovedSuspension, -0.3]); coefficient([hasImprovedSuspension, -0.15]) *
coefficient([true, degressiveStat(driverMastery)]);
const resistanceSoftCoefficient =
coefficient([hasImprovedSuspension, -0.3]) *
coefficient([true, degressiveStat(driverMastery)]);
const viewRangeCoefficient = const viewRangeCoefficient =
coefficient([ coefficient([
hasImprovedOptics, hasImprovedOptics,
tank.class === TankClass.HEAVY tank.class === TankClass.HEAVY
? 0.06 ? 0.06
: tank.class === TankClass.MEDIUM : tank.class === TankClass.MEDIUM
? 0.08 ? 0.08
: tank.class === TankClass.LIGHT : tank.class === TankClass.LIGHT
? 0.11 ? 0.11
: 0.04, : 0.04,
]) * coefficient([true, progressiveStat(commanderMastery)]); ]) * coefficient([true, progressiveStat(commanderMastery)]);
const fireChanceCoefficient = coefficient([hasProtectiveKit, -0.2]); const fireChanceCoefficient = coefficient([hasProtectiveKit, -0.2]);
const speedForwardsSum = sum( const speedForwardsSum = sum(
[hasImprovedEnginePowerBoost, 4], [hasImprovedEnginePowerBoost, 4],
[hasGearOil, 2], [hasGearOil, 2],
[hasImprovedGearOil, 4], [hasImprovedGearOil, 4]
); );
const speedBackwardsSum = sum( const speedBackwardsSum = sum(
[hasImprovedEnginePowerBoost, 8], [hasImprovedEnginePowerBoost, 8],
[hasGearOil, 2], [hasGearOil, 2],
[hasImprovedGearOil, 4], [hasImprovedGearOil, 4]
); );
const camouflageCoefficientMoving = sum( const camouflageCoefficientMoving = sum(
[ [
@@ -273,8 +278,8 @@ export function tankCharacteristics(
tank.class === TankClass.TANK_DESTROYER tank.class === TankClass.TANK_DESTROYER
? 0.04 ? 0.04
: tank.class === TankClass.HEAVY : tank.class === TankClass.HEAVY
? 0.03 ? 0.03
: 0.02, : 0.02,
]), ]),
], ],
[ [
@@ -282,11 +287,11 @@ export function tankCharacteristics(
tank.class === TankClass.HEAVY tank.class === TankClass.HEAVY
? 0.02 ? 0.02
: tank.class === TankClass.MEDIUM : tank.class === TankClass.MEDIUM
? 0.04 ? 0.04
: tank.class === TankClass.LIGHT : tank.class === TankClass.LIGHT
? 0.07 ? 0.07
: 0.1, : 0.1,
], ]
); );
const camouflageCoefficientStill = sum( const camouflageCoefficientStill = sum(
[ [
@@ -297,10 +302,10 @@ export function tankCharacteristics(
tank.class === TankClass.TANK_DESTROYER tank.class === TankClass.TANK_DESTROYER
? 0.04 ? 0.04
: tank.class === TankClass.HEAVY : tank.class === TankClass.HEAVY
? 0.03 ? 0.03
: 0.02, : 0.02,
], ],
[true, crewSkills.camouflage * (3 / 100)], [true, crewSkills.camouflage * (3 / 100)]
), ),
], ],
[ [
@@ -308,18 +313,18 @@ export function tankCharacteristics(
tank.class === TankClass.HEAVY tank.class === TankClass.HEAVY
? 0.04 ? 0.04
: tank.class === TankClass.MEDIUM : tank.class === TankClass.MEDIUM
? 0.08 ? 0.08
: tank.class === TankClass.LIGHT : tank.class === TankClass.LIGHT
? 0.14 ? 0.14
: 0.2, : 0.2,
], ]
); );
const size = normalizeBoundingBox( const size = normalizeBoundingBox(
unionBoundingBox( unionBoundingBox(
tankModelDefinition.bounding_box, tankModelDefinition.bounding_box,
turretModelDefinition.bounding_box, turretModelDefinition.bounding_box
), )
); );
const weightKg = const weightKg =
tank.weight + engine.weight + track.weight + turret.weight + gun.weight; tank.weight + engine.weight + track.weight + turret.weight + gun.weight;
@@ -339,14 +344,14 @@ export function tankCharacteristics(
shell, shell,
damageCoefficient, damageCoefficient,
reloadCoefficient, reloadCoefficient,
intraClipCoefficient, intraClipCoefficient
); );
const intraClip = const intraClip =
gun.gun_type?.$case === 'regular' gun.gun_type?.$case === "regular"
? undefined ? undefined
: gun.gun_type!.value.intra_clip * intraClipCoefficient; : gun.gun_type!.value.intra_clip * intraClipCoefficient;
const mostOptimalShellIndex = const mostOptimalShellIndex =
gun.gun_type!.$case === 'auto_reloader' gun.gun_type!.$case === "auto_reloader"
? gun.gun_type!.value.shell_reloads.reduce<null | { ? gun.gun_type!.value.shell_reloads.reduce<null | {
index: number; index: number;
reload: number; reload: number;
@@ -364,7 +369,7 @@ export function tankCharacteristics(
shell.armor_damage * damageCoefficientWithoutAssault; shell.armor_damage * damageCoefficientWithoutAssault;
const damage = shell.armor_damage * damageCoefficient; const damage = shell.armor_damage * damageCoefficient;
const dpmEffective = 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[0] >
gun.gun_type!.value.shell_reloads[1] + gun.gun_type!.value.intra_clip gun.gun_type!.value.shell_reloads[1] + gun.gun_type!.value.intra_clip
? ((armorDamageCoefficient * shell.armor_damage) / ? ((armorDamageCoefficient * shell.armor_damage) /
@@ -389,23 +394,23 @@ export function tankCharacteristics(
gun.gun_type!.value.shell_reloads.slice(1).length gun.gun_type!.value.shell_reloads.slice(1).length
: undefined; : undefined;
const shells = 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 = const shellReloads =
gun.gun_type!.$case === 'auto_reloader' gun.gun_type!.$case === "auto_reloader"
? gun.gun_type!.value.shell_reloads.map( ? gun.gun_type!.value.shell_reloads.map(
(reload) => reload * reloadCoefficient, (reload) => reload * reloadCoefficient
) )
: undefined; : undefined;
const shellReload = const shellReload =
gun.gun_type!.$case === 'auto_reloader' gun.gun_type!.$case === "auto_reloader"
? undefined ? undefined
: (gun.gun_type!.$case === 'regular' : (gun.gun_type!.$case === "regular"
? gun.gun_type!.value.reload ? gun.gun_type!.value.reload
: gun.gun_type!.value.clip_reload) * reloadCoefficient; : gun.gun_type!.value.clip_reload) * reloadCoefficient;
const caliber = shell.caliber; const caliber = shell.caliber;
const penetration = shell.penetration.near * penetrationCoefficient; const penetration = shell.penetration.near * penetrationCoefficient;
const clipDamage = const clipDamage =
gun.gun_type!.$case === 'regular' gun.gun_type!.$case === "regular"
? undefined ? undefined
: gun.gun_type!.value.shell_count * damage; : gun.gun_type!.value.shell_count * damage;
const moduleDamage = shell.module_damage * moduleDamageCoefficient; const moduleDamage = shell.module_damage * moduleDamageCoefficient;
@@ -599,45 +604,45 @@ export const characteristicsOrder: {
items: TankCharacteristicsKey[]; items: TankCharacteristicsKey[];
}[] = [ }[] = [
{ {
name: 'Firepower', name: "Firepower",
items: [ items: [
'gunType', "gunType",
'dpm', "dpm",
'shells', "shells",
'shellReload', "shellReload",
'shellReloads', "shellReloads",
'intraClip', "intraClip",
'penetration', "penetration",
'penetrationAt250m', "penetrationAt250m",
'damage', "damage",
'clipDamage', "clipDamage",
'moduleDamage', "moduleDamage",
'caliber', "caliber",
'shellNormalization', "shellNormalization",
'shellRicochet', "shellRicochet",
'shellVelocity', "shellVelocity",
'shellRange', "shellRange",
'shellCapacity', "shellCapacity",
'aimTime', "aimTime",
'dispersion', "dispersion",
'dispersionMoving', "dispersionMoving",
'dispersionHullTraversing', "dispersionHullTraversing",
'dispersionTurretTraversing', "dispersionTurretTraversing",
'dispersionShooting', "dispersionShooting",
'dispersionGunDamaged', "dispersionGunDamaged",
'gunDepression', "gunDepression",
'gunElevation', "gunElevation",
'gunFrontalDepression', "gunFrontalDepression",
'gunFrontalElevation', "gunFrontalElevation",
'gunRearDepression', "gunRearDepression",
'gunRearElevation', "gunRearElevation",
'azimuthLeft', "azimuthLeft",
'azimuthRight', "azimuthRight",
], ],
}, },
{ {
name: 'Crew', name: "Crew",
items: ['crewCount'], items: ["crewCount"],
}, },
]; ];
@@ -21,6 +21,10 @@ export interface TankFilters {
searching: boolean; searching: boolean;
gunType: CaseType<GunDefinition>[]; gunType: CaseType<GunDefinition>[];
shells: [ShellType | null, ShellType | null, ShellType | null]; shells: [ShellType | null, ShellType | null, ShellType | null];
consumables: number[];
provisions: number[];
abilities: number[];
powers: number[];
showTesting: boolean; showTesting: boolean;
showNonTesting: boolean; showNonTesting: boolean;
@@ -37,6 +41,10 @@ export const TankFilters = new Varuna<TankFilters>({
searching: false, searching: false,
gunType: [], gunType: [],
shells: [null, null, null], shells: [null, null, null],
consumables: [],
provisions: [],
abilities: [],
powers: [],
showTesting: true, showTesting: true,
showNonTesting: true, showNonTesting: true,