mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-11-12 12:16:11 +01:00
Compare commits
15
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1558b69931 | ||
|
|
a560a3d3ae | ||
|
|
b620110d0f | ||
|
|
23ea72bf40 | ||
|
|
aaee0b7965 | ||
|
|
347523f9e7 | ||
|
|
3205630a0f | ||
|
|
8446e3fbbd | ||
|
|
6d25a97fc8 | ||
|
|
bdbfce0e18 | ||
|
|
8033e8f7cc | ||
|
|
f98c801d6c | ||
|
|
339663adaf | ||
|
|
2b15357dee | ||
|
|
f22f4eeccd |
No files matched your search
@@ -29,6 +29,8 @@ env:
|
||||
PUBLIC_DISCORD_SERVER_ID: ${{ secrets.PUBLIC_DISCORD_SERVER_ID }}
|
||||
PUBLIC_ASSET_BASE: ${{ secrets.PUBLIC_ASSET_BASE }}
|
||||
PUBLIC_BRANCH: ${{ secrets.PUBLIC_BRANCH }}
|
||||
PUBLIC_GUIDE_WEBSITE: ${{ secrets.PUBLIC_GUIDE_WEBSITE }}
|
||||
SERVICING: ${{ secrets.SERVICING }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# BlitzKit 1.27.17
|
||||
|
||||
This update was released early for the website. Today, it'll be officially announced and released for the bot
|
||||
|
||||
## Features
|
||||
|
||||
- Added the shell reload visualizer to aid people in getting a feel for how long shell reloads really are
|
||||
- Added lines of stats for the burst shells
|
||||
|
||||
## Changes
|
||||
|
||||
- Removed the redundant word "Shell" from "Shell reload" and "Shell reloads"
|
||||
- Removed the penetration over distance slider
|
||||
|
||||
## Fixes
|
||||
|
||||
- Fixed the DPM of tanks with burst shells like the ATAC
|
||||
- The DPM will still be different from the game due to a bug in World of Tanks Blitz; I am trying to get Wargaming to fix it; trust BlitzKit!
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "blitzkit",
|
||||
"private": true,
|
||||
"version": "1.27.16",
|
||||
"version": "1.27.17",
|
||||
"description": "🎉 Tools for everything World of Tanks Blitz",
|
||||
"type": "module",
|
||||
"types": "./types.d.ts",
|
||||
|
||||
@@ -17,12 +17,27 @@ export function resolveDpm(
|
||||
if (gun.gun_type!.$case === 'regular') {
|
||||
dps = alpha / (reloadCoefficient * gun.gun_type!.value.reload);
|
||||
} else if (gun.gun_type!.$case === 'auto_loader') {
|
||||
dps =
|
||||
(alpha * gun.gun_type!.value.shell_count) /
|
||||
(gun.gun_type!.value.clip_reload * reloadCoefficient +
|
||||
const damage = alpha * gun.gun_type!.value.shell_count;
|
||||
let time = gun.gun_type!.value.clip_reload * reloadCoefficient;
|
||||
|
||||
if (gun.burst) {
|
||||
time +=
|
||||
(gun.gun_type!.value.shell_count / gun.burst.count - 1) *
|
||||
gun.gun_type!.value.intra_clip *
|
||||
intraClipCoefficient;
|
||||
|
||||
time +=
|
||||
(gun.gun_type!.value.shell_count / gun.burst.count) *
|
||||
(gun.burst.count - 1) *
|
||||
gun.burst.interval;
|
||||
} else {
|
||||
time +=
|
||||
(gun.gun_type!.value.shell_count - 1) *
|
||||
gun.gun_type!.value.intra_clip *
|
||||
intraClipCoefficient);
|
||||
gun.gun_type!.value.intra_clip *
|
||||
intraClipCoefficient;
|
||||
}
|
||||
|
||||
dps = damage / time;
|
||||
} else {
|
||||
const mostOptimalShell = gun.gun_type!.value.shell_reloads.reduce<null | {
|
||||
index: number;
|
||||
|
||||
@@ -136,7 +136,6 @@ message GunDefinitionAutoLoader {
|
||||
required float clip_reload = 1;
|
||||
required float intra_clip = 2;
|
||||
required float shell_count = 3;
|
||||
optional Burst burst = 4;
|
||||
}
|
||||
|
||||
message Burst {
|
||||
|
||||
@@ -408,7 +408,6 @@ export interface GunDefinitionAutoLoader {
|
||||
clip_reload: number;
|
||||
intra_clip: number;
|
||||
shell_count: number;
|
||||
burst?: Burst;
|
||||
}
|
||||
|
||||
export interface Burst {
|
||||
@@ -3023,7 +3022,7 @@ export const GunDefinitionRegular: MessageFns<GunDefinitionRegular> = {
|
||||
};
|
||||
|
||||
function createBaseGunDefinitionAutoLoader(): GunDefinitionAutoLoader {
|
||||
return { clip_reload: 0, intra_clip: 0, shell_count: 0, burst: undefined };
|
||||
return { clip_reload: 0, intra_clip: 0, shell_count: 0 };
|
||||
}
|
||||
|
||||
export const GunDefinitionAutoLoader: MessageFns<GunDefinitionAutoLoader> = {
|
||||
@@ -3040,9 +3039,6 @@ export const GunDefinitionAutoLoader: MessageFns<GunDefinitionAutoLoader> = {
|
||||
if (message.shell_count !== 0) {
|
||||
writer.uint32(29).float(message.shell_count);
|
||||
}
|
||||
if (message.burst !== undefined) {
|
||||
Burst.encode(message.burst, writer.uint32(34).fork()).join();
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
@@ -3081,14 +3077,6 @@ export const GunDefinitionAutoLoader: MessageFns<GunDefinitionAutoLoader> = {
|
||||
message.shell_count = reader.float();
|
||||
continue;
|
||||
}
|
||||
case 4: {
|
||||
if (tag !== 34) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.burst = Burst.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
@@ -3109,7 +3097,6 @@ export const GunDefinitionAutoLoader: MessageFns<GunDefinitionAutoLoader> = {
|
||||
shell_count: isSet(object.shell_count)
|
||||
? globalThis.Number(object.shell_count)
|
||||
: 0,
|
||||
burst: isSet(object.burst) ? Burst.fromJSON(object.burst) : undefined,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -3124,9 +3111,6 @@ export const GunDefinitionAutoLoader: MessageFns<GunDefinitionAutoLoader> = {
|
||||
if (message.shell_count !== 0) {
|
||||
obj.shell_count = message.shell_count;
|
||||
}
|
||||
if (message.burst !== undefined) {
|
||||
obj.burst = Burst.toJSON(message.burst);
|
||||
}
|
||||
return obj;
|
||||
},
|
||||
|
||||
@@ -3142,10 +3126,6 @@ export const GunDefinitionAutoLoader: MessageFns<GunDefinitionAutoLoader> = {
|
||||
message.clip_reload = object.clip_reload ?? 0;
|
||||
message.intra_clip = object.intra_clip ?? 0;
|
||||
message.shell_count = object.shell_count ?? 0;
|
||||
message.burst =
|
||||
object.burst !== undefined && object.burst !== null
|
||||
? Burst.fromPartial(object.burst)
|
||||
: undefined;
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1096,8 +1096,8 @@
|
||||
"dpmEffective": "Effective at 60s",
|
||||
"shells": "Shells",
|
||||
"mostOptimalShellIndex": "Most optimal shell",
|
||||
"shellReloads": "Shell reloads",
|
||||
"shellReload": "Shell reload",
|
||||
"shellReloads": "Reloads",
|
||||
"shellReload": "Reload",
|
||||
"intraClip": "Intra-clip",
|
||||
"caliber": "Caliber",
|
||||
"penetration": "Penetration",
|
||||
@@ -1155,7 +1155,9 @@
|
||||
"shellRange": "Shell range",
|
||||
"shellCapacity": "Shell capacity",
|
||||
"gunType": "Gun type",
|
||||
"penetrationAt250m": "Penetration at 250m"
|
||||
"penetrationAt250m": "Penetration at 250m",
|
||||
"burstShells": "Burst shell count",
|
||||
"burstInterShell": "Burst inter-shell"
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -810,6 +810,12 @@
|
||||
"percentage": "%s1%",
|
||||
"ghost_factory": "Ghost Factory",
|
||||
"dead_rail": "Dead Rail"
|
||||
},
|
||||
"flexibility": {
|
||||
"yaw": "Yaw",
|
||||
"pitch": "Pitch",
|
||||
"min": "Min",
|
||||
"max": "Max"
|
||||
}
|
||||
},
|
||||
"sandbox": {
|
||||
@@ -912,7 +918,6 @@
|
||||
},
|
||||
"review": {
|
||||
"title": "Vídeos de reseña",
|
||||
"disclaimer": "BlitzKit no respalda ningún vídeo de reseñas.",
|
||||
"join": "Aplica para el programa de YouTube de %s1"
|
||||
},
|
||||
"configuration": {
|
||||
@@ -973,8 +978,8 @@
|
||||
"dpmEffective": "Effective at 60s",
|
||||
"shells": "Proyectiles",
|
||||
"mostOptimalShellIndex": "Proyectil más óptimo",
|
||||
"shellReloads": "Tiempos de recarga",
|
||||
"shellReload": "Tiempo de recarga",
|
||||
"shellReloads": "Reloads",
|
||||
"shellReload": "Recarga",
|
||||
"intraClip": "Intra-clip",
|
||||
"caliber": "Calibre",
|
||||
"penetration": "Penetración",
|
||||
@@ -1032,7 +1037,9 @@
|
||||
"shellRange": "Rango del proyectil",
|
||||
"shellCapacity": "Capacidad de proyectiles",
|
||||
"gunType": "Tipo de cañón",
|
||||
"penetrationAt250m": "Penetración a 250 m"
|
||||
"penetrationAt250m": "Penetración a 250 m",
|
||||
"burstShells": "Burst shell count",
|
||||
"burstInterShell": "Burst inter-shell"
|
||||
}
|
||||
},
|
||||
"firepower": {
|
||||
|
||||
@@ -810,6 +810,12 @@
|
||||
"percentage": "%s1%",
|
||||
"ghost_factory": "Ghost Factory",
|
||||
"dead_rail": "Dead Rail"
|
||||
},
|
||||
"flexibility": {
|
||||
"yaw": "Yaw",
|
||||
"pitch": "Pitch",
|
||||
"min": "Min",
|
||||
"max": "Max"
|
||||
}
|
||||
},
|
||||
"sandbox": {
|
||||
@@ -912,7 +918,6 @@
|
||||
},
|
||||
"review": {
|
||||
"title": "Review videos",
|
||||
"disclaimer": "BlitzKit does not endorse any review videos.",
|
||||
"join": "Apply to %s1 YouTuber program"
|
||||
},
|
||||
"configuration": {
|
||||
@@ -973,8 +978,8 @@
|
||||
"dpmEffective": "Effective at 60s",
|
||||
"shells": "Shells",
|
||||
"mostOptimalShellIndex": "Most optimal shell",
|
||||
"shellReloads": "Shell reloads",
|
||||
"shellReload": "Shell reload",
|
||||
"shellReloads": "Reloads",
|
||||
"shellReload": "Reload",
|
||||
"intraClip": "Intra-clip",
|
||||
"caliber": "Caliber",
|
||||
"penetration": "Penetration",
|
||||
@@ -1032,7 +1037,9 @@
|
||||
"shellRange": "Shell range",
|
||||
"shellCapacity": "Shell capacity",
|
||||
"gunType": "Gun type",
|
||||
"penetrationAt250m": "Penetration at 250m"
|
||||
"penetrationAt250m": "Penetration at 250m",
|
||||
"burstShells": "Burst shell count",
|
||||
"burstInterShell": "Burst inter-shell"
|
||||
}
|
||||
},
|
||||
"firepower": {
|
||||
|
||||
@@ -810,6 +810,12 @@
|
||||
"percentage": "%s1%",
|
||||
"ghost_factory": "Ghost Factory",
|
||||
"dead_rail": "Dead Rail"
|
||||
},
|
||||
"flexibility": {
|
||||
"yaw": "Yaw",
|
||||
"pitch": "Pitch",
|
||||
"min": "Min",
|
||||
"max": "Max"
|
||||
}
|
||||
},
|
||||
"sandbox": {
|
||||
@@ -912,7 +918,6 @@
|
||||
},
|
||||
"review": {
|
||||
"title": "Review videos",
|
||||
"disclaimer": "BlitzKitはいかなるレビュー動画も後援していません。",
|
||||
"join": "%s1 YouTuberプログラムに応募する"
|
||||
},
|
||||
"configuration": {
|
||||
@@ -973,7 +978,7 @@
|
||||
"dpmEffective": "60秒での有効値",
|
||||
"shells": "砲弾",
|
||||
"mostOptimalShellIndex": "最適な弾数",
|
||||
"shellReloads": "自動装填時間",
|
||||
"shellReloads": "Reloads",
|
||||
"shellReload": "装填時間",
|
||||
"intraClip": "砲弾再装填時間",
|
||||
"caliber": "口径",
|
||||
@@ -1032,7 +1037,9 @@
|
||||
"shellRange": "射程",
|
||||
"shellCapacity": "総弾数",
|
||||
"gunType": "主砲タイプ",
|
||||
"penetrationAt250m": "250m での貫通力"
|
||||
"penetrationAt250m": "250m での貫通力",
|
||||
"burstShells": "Burst shell count",
|
||||
"burstInterShell": "Burst inter-shell"
|
||||
}
|
||||
},
|
||||
"firepower": {
|
||||
|
||||
@@ -810,6 +810,12 @@
|
||||
"percentage": "%s1%",
|
||||
"ghost_factory": "Ghost Factory",
|
||||
"dead_rail": "Dead Rail"
|
||||
},
|
||||
"flexibility": {
|
||||
"yaw": "Yaw",
|
||||
"pitch": "Pitch",
|
||||
"min": "Min",
|
||||
"max": "Max"
|
||||
}
|
||||
},
|
||||
"sandbox": {
|
||||
@@ -912,7 +918,6 @@
|
||||
},
|
||||
"review": {
|
||||
"title": "Review videos",
|
||||
"disclaimer": "BlitzKit does not endorse any review videos.",
|
||||
"join": "Apply to %s1 YouTuber program"
|
||||
},
|
||||
"configuration": {
|
||||
@@ -973,8 +978,8 @@
|
||||
"dpmEffective": "Effective at 60s",
|
||||
"shells": "Shells",
|
||||
"mostOptimalShellIndex": "Most optimal shell",
|
||||
"shellReloads": "Shell reloads",
|
||||
"shellReload": "Shell reload",
|
||||
"shellReloads": "Reloads",
|
||||
"shellReload": "Reload",
|
||||
"intraClip": "Intra-clip",
|
||||
"caliber": "Caliber",
|
||||
"penetration": "Penetration",
|
||||
@@ -1032,7 +1037,9 @@
|
||||
"shellRange": "Shell range",
|
||||
"shellCapacity": "Shell capacity",
|
||||
"gunType": "Gun type",
|
||||
"penetrationAt250m": "Penetration at 250m"
|
||||
"penetrationAt250m": "Penetration at 250m",
|
||||
"burstShells": "Burst shell count",
|
||||
"burstInterShell": "Burst inter-shell"
|
||||
}
|
||||
},
|
||||
"firepower": {
|
||||
|
||||
@@ -810,6 +810,12 @@
|
||||
"percentage": "%s1%",
|
||||
"ghost_factory": "Ghost Factory",
|
||||
"dead_rail": "Dead Rail"
|
||||
},
|
||||
"flexibility": {
|
||||
"yaw": "Yaw",
|
||||
"pitch": "Pitch",
|
||||
"min": "Min",
|
||||
"max": "Max"
|
||||
}
|
||||
},
|
||||
"sandbox": {
|
||||
@@ -912,7 +918,6 @@
|
||||
},
|
||||
"review": {
|
||||
"title": "Vídeos de análise",
|
||||
"disclaimer": "BlitzKit não aprova nenhum vídeo de análise.",
|
||||
"join": "Candidate-se para %s1 um programa YouTuber"
|
||||
},
|
||||
"configuration": {
|
||||
@@ -973,8 +978,8 @@
|
||||
"dpmEffective": "Efetivo aos 60s",
|
||||
"shells": "Projéteis",
|
||||
"mostOptimalShellIndex": "Projétil mais ideal",
|
||||
"shellReloads": "Recarga de projétil",
|
||||
"shellReload": "Recarregamento de projétil",
|
||||
"shellReloads": "Reloads",
|
||||
"shellReload": "Recarga",
|
||||
"intraClip": "Intra-clipe",
|
||||
"caliber": "Calibre",
|
||||
"penetration": "Penetração",
|
||||
@@ -1032,7 +1037,9 @@
|
||||
"shellRange": "Alcance de projétil",
|
||||
"shellCapacity": "Capacidade de projéteis",
|
||||
"gunType": "Tipo de arma",
|
||||
"penetrationAt250m": "Penetração à 250m"
|
||||
"penetrationAt250m": "Penetração à 250m",
|
||||
"burstShells": "Burst shell count",
|
||||
"burstInterShell": "Burst inter-shell"
|
||||
}
|
||||
},
|
||||
"firepower": {
|
||||
|
||||
@@ -788,7 +788,7 @@
|
||||
"tank_seo_description": "%s1 броня, статистика, оборудование, снабжение и многое другое. %s2 УВМ, %s3hp альфа, %s4mm пробитие, %s5км/ч лучшая скорость, %s6hp здоровье.",
|
||||
"visualizers": {
|
||||
"assault": {
|
||||
"title": "Assault shells"
|
||||
"title": "Штурмовые снаряды"
|
||||
},
|
||||
"rotator": {
|
||||
"hull": "Корпус",
|
||||
@@ -810,6 +810,12 @@
|
||||
"percentage": "%s1%",
|
||||
"ghost_factory": "Промзона",
|
||||
"dead_rail": "Эшелон"
|
||||
},
|
||||
"flexibility": {
|
||||
"yaw": "Отклонение",
|
||||
"pitch": "Наклон",
|
||||
"min": "Вниз",
|
||||
"max": "Вверх"
|
||||
}
|
||||
},
|
||||
"sandbox": {
|
||||
@@ -912,7 +918,6 @@
|
||||
},
|
||||
"review": {
|
||||
"title": "Видеообзоры",
|
||||
"disclaimer": "BlitzKit не несёт ответственности за видеообзоры.",
|
||||
"join": "Предложить ютубера для программы %s1"
|
||||
},
|
||||
"configuration": {
|
||||
@@ -973,13 +978,13 @@
|
||||
"dpmEffective": "Эффективность за 60с",
|
||||
"shells": "Снарядов в барабане",
|
||||
"mostOptimalShellIndex": "Самый эффективный снаряд",
|
||||
"shellReloads": "Перезарядка снарядов",
|
||||
"shellReloads": "Reloads",
|
||||
"shellReload": "Перезарядка",
|
||||
"intraClip": "Между снарядами",
|
||||
"caliber": "Калибр",
|
||||
"penetration": "Пробитие",
|
||||
"damage": "Урон",
|
||||
"damageWithoutAssault": "Damage without assault shells",
|
||||
"damageWithoutAssault": "Урон без штурмовых снарядов",
|
||||
"clipDamage": "Урон с барабана",
|
||||
"moduleDamage": "Урон по модулям",
|
||||
"explosionRadius": "Разлет осколков",
|
||||
@@ -1032,7 +1037,9 @@
|
||||
"shellRange": "Дальность снаряда",
|
||||
"shellCapacity": "Боезапас",
|
||||
"gunType": "Тип орудия",
|
||||
"penetrationAt250m": "Пробитие на 250м"
|
||||
"penetrationAt250m": "Пробитие на 250м",
|
||||
"burstShells": "Burst shell count",
|
||||
"burstInterShell": "Burst inter-shell"
|
||||
}
|
||||
},
|
||||
"firepower": {
|
||||
|
||||
@@ -810,6 +810,12 @@
|
||||
"percentage": "%s1%",
|
||||
"ghost_factory": "Ghost Factory",
|
||||
"dead_rail": "Dead Rail"
|
||||
},
|
||||
"flexibility": {
|
||||
"yaw": "Yaw",
|
||||
"pitch": "Pitch",
|
||||
"min": "Min",
|
||||
"max": "Max"
|
||||
}
|
||||
},
|
||||
"sandbox": {
|
||||
@@ -912,7 +918,6 @@
|
||||
},
|
||||
"review": {
|
||||
"title": "Review videos",
|
||||
"disclaimer": "BlitzKit does not endorse any review videos.",
|
||||
"join": "Apply to %s1 YouTuber program"
|
||||
},
|
||||
"configuration": {
|
||||
@@ -973,8 +978,8 @@
|
||||
"dpmEffective": "Effective at 60s",
|
||||
"shells": "Снаряди",
|
||||
"mostOptimalShellIndex": "Most optimal shell",
|
||||
"shellReloads": "Shell reloads",
|
||||
"shellReload": "Shell reload",
|
||||
"shellReloads": "Reloads",
|
||||
"shellReload": "Час перезаряджання",
|
||||
"intraClip": "Intra-clip",
|
||||
"caliber": "Калібр",
|
||||
"penetration": "Бронепробиття",
|
||||
@@ -1032,7 +1037,9 @@
|
||||
"shellRange": "Shell range",
|
||||
"shellCapacity": "Shell capacity",
|
||||
"gunType": "Тип гармати",
|
||||
"penetrationAt250m": "Penetration at 250m"
|
||||
"penetrationAt250m": "Penetration at 250m",
|
||||
"burstShells": "Burst shell count",
|
||||
"burstInterShell": "Burst inter-shell"
|
||||
}
|
||||
},
|
||||
"firepower": {
|
||||
|
||||
@@ -810,6 +810,12 @@
|
||||
"percentage": "%s1%",
|
||||
"ghost_factory": "幽灵工厂",
|
||||
"dead_rail": "死亡铁道"
|
||||
},
|
||||
"flexibility": {
|
||||
"yaw": "Yaw",
|
||||
"pitch": "Pitch",
|
||||
"min": "Min",
|
||||
"max": "Max"
|
||||
}
|
||||
},
|
||||
"sandbox": {
|
||||
@@ -912,7 +918,6 @@
|
||||
},
|
||||
"review": {
|
||||
"title": "回顾影片",
|
||||
"disclaimer": "BlitzKit 不支援任何影片回顾。",
|
||||
"join": "应用到 %s1 YouTuber程序"
|
||||
},
|
||||
"configuration": {
|
||||
@@ -973,8 +978,8 @@
|
||||
"dpmEffective": "60秒内生效",
|
||||
"shells": "炮弹 ",
|
||||
"mostOptimalShellIndex": "最优炮弹",
|
||||
"shellReloads": "弹匣装填",
|
||||
"shellReload": "炮弹装填",
|
||||
"shellReloads": "Reloads",
|
||||
"shellReload": "装填时间",
|
||||
"intraClip": "炮弹装填",
|
||||
"caliber": "口径",
|
||||
"penetration": "穿深",
|
||||
@@ -1032,7 +1037,9 @@
|
||||
"shellRange": "射程",
|
||||
"shellCapacity": "携弹量",
|
||||
"gunType": "炮管类型",
|
||||
"penetrationAt250m": "两百五十米处的穿透力"
|
||||
"penetrationAt250m": "两百五十米处的穿透力",
|
||||
"burstShells": "Burst shell count",
|
||||
"burstInterShell": "Burst inter-shell"
|
||||
}
|
||||
},
|
||||
"firepower": {
|
||||
|
||||
@@ -1324,7 +1324,13 @@ export async function definitions() {
|
||||
unlocks: resolveUnlocks(gun.unlocks),
|
||||
shell_capacity: gun.maxAmmo ?? gunListEntry.maxAmmo,
|
||||
assault_ranges,
|
||||
|
||||
burst:
|
||||
gun.burst && gun.burst.count > 1
|
||||
? {
|
||||
count: gun.burst.count,
|
||||
interval: 60 / gun.burst.rate,
|
||||
}
|
||||
: undefined,
|
||||
gun_type:
|
||||
gunType === 'regular'
|
||||
? {
|
||||
@@ -1350,13 +1356,6 @@ export async function definitions() {
|
||||
intra_clip: 60 / gun.clip!.rate,
|
||||
clip_reload: gun.reloadTime,
|
||||
shell_count: gunClipCount,
|
||||
burst:
|
||||
gun.burst && gun.burst.count > 1
|
||||
? {
|
||||
count: gun.burst.count,
|
||||
interval: 60 / gun.burst.rate,
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
},
|
||||
} satisfies GunDefinition);
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
import { Heading, Link, Text } from '@radix-ui/themes';
|
||||
import { PageWrapper } from './PageWrapper';
|
||||
---
|
||||
|
||||
<PageWrapper align="center" justify="center" gap="0">
|
||||
<Heading>BlitzKit down for servicing</Heading>
|
||||
<Text color="gray">
|
||||
<Link color="blue" href="https://discord.gg/nDt7AjGJQH">
|
||||
Join the official Discord server
|
||||
</Link>
|
||||
for updates.
|
||||
</Text>
|
||||
</PageWrapper>
|
||||
+4
-78
@@ -5,18 +5,9 @@ import {
|
||||
resolvePenetrationCoefficient,
|
||||
} from '@blitzkit/core';
|
||||
import { literals } from '@blitzkit/i18n';
|
||||
import {
|
||||
Flex,
|
||||
Heading,
|
||||
IconButton,
|
||||
Slider,
|
||||
Text,
|
||||
TextField,
|
||||
Tooltip,
|
||||
} from '@radix-ui/themes';
|
||||
import { Flex, Heading, IconButton, Tooltip } from '@radix-ui/themes';
|
||||
import { debounce } from 'lodash-es';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { lerp } from 'three/src/math/MathUtils.js';
|
||||
import { useEquipment } from '../../../../../../hooks/useEquipment';
|
||||
import { useLocale } from '../../../../../../hooks/useLocale';
|
||||
import { useTankModelDefinition } from '../../../../../../hooks/useTankModelDefinition';
|
||||
@@ -41,7 +32,9 @@ export function Firepower({
|
||||
const setPenetrationDistanceDebounced = debounce((value: number) => {
|
||||
setPenetrationDistance(value);
|
||||
}, 500);
|
||||
const { gun, shell, turret } = Duel.use((state) => state.protagonist);
|
||||
const turret = Duel.use((state) => state.protagonist.turret);
|
||||
const gun = Duel.use((state) => state.protagonist.gun);
|
||||
const shell = Duel.use((state) => state.protagonist.shell);
|
||||
const penetrationDistanceInput = useRef<HTMLInputElement>(null);
|
||||
const hasSupercharger = useEquipment(107);
|
||||
const hasCalibratedShells = useEquipment(103);
|
||||
@@ -185,73 +178,6 @@ export function Firepower({
|
||||
)}
|
||||
<ReloadVisualizer stats={stats} />
|
||||
|
||||
{typeof shell.penetration !== 'number' && (
|
||||
<>
|
||||
<InfoWithDelta
|
||||
stats={stats}
|
||||
indent
|
||||
decimals={0}
|
||||
name={literals(
|
||||
strings.website.tools.tankopedia.characteristics.values
|
||||
.at_distance,
|
||||
[`${penetrationDistance}`],
|
||||
)}
|
||||
noRanking
|
||||
value={() =>
|
||||
lerp(
|
||||
shell.penetration.near,
|
||||
shell.penetration.far,
|
||||
(penetrationDistance * penetrationLossOverDistanceCoefficient) /
|
||||
500,
|
||||
) * penetrationCoefficient
|
||||
}
|
||||
/>
|
||||
<Flex align="center" gap="2" style={{ paddingLeft: 24 }}>
|
||||
<Text color="gray">
|
||||
{
|
||||
strings.website.tools.tankopedia.characteristics.values
|
||||
.at_distance_slider_label
|
||||
}
|
||||
</Text>
|
||||
<Slider
|
||||
variant="classic"
|
||||
key={penetrationDistance}
|
||||
min={0}
|
||||
max={500}
|
||||
style={{ flex: 1 }}
|
||||
defaultValue={[penetrationDistance]}
|
||||
onValueChange={([value]) => {
|
||||
setPenetrationDistanceDebounced(value);
|
||||
|
||||
if (!penetrationDistanceInput.current) return;
|
||||
|
||||
penetrationDistanceInput.current.value = value.toString();
|
||||
}}
|
||||
/>
|
||||
<TextField.Root
|
||||
variant="classic"
|
||||
style={{ width: 64 }}
|
||||
ref={penetrationDistanceInput}
|
||||
defaultValue={penetrationDistance}
|
||||
onBlur={() => {
|
||||
setPenetrationDistance(
|
||||
Math.min(
|
||||
parseInt(penetrationDistanceInput.current!.value),
|
||||
500,
|
||||
),
|
||||
);
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter') {
|
||||
event.currentTarget.blur();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<TextField.Slot side="right">m</TextField.Slot>
|
||||
</TextField.Root>
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
{isExplosive(shell.type) && (
|
||||
<InfoWithDelta
|
||||
stats={stats}
|
||||
|
||||
+1
-3
@@ -40,7 +40,7 @@ export function Shell({ index, stats }: ShellProps & StatsAcceptorProps) {
|
||||
<Box
|
||||
left="50%"
|
||||
position="absolute"
|
||||
ml="calc(25% + 2.5rem)"
|
||||
ml="40%"
|
||||
width="1.25rem"
|
||||
height="1.25rem"
|
||||
style={{
|
||||
@@ -70,11 +70,9 @@ export function Shell({ index, stats }: ShellProps & StatsAcceptorProps) {
|
||||
overflow: 'hidden',
|
||||
maskImage:
|
||||
'url(/assets/images/tankopedia/visualizers/reload/shell.png)',
|
||||
// maskComposite: 'exclude',
|
||||
maskSize: 'contain',
|
||||
maskRepeat: 'no-repeat',
|
||||
maskPosition: 'center',
|
||||
// background: 'red',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
#reload-visualizer-flash {
|
||||
background: transparent;
|
||||
transition-duration: 200ms;
|
||||
}
|
||||
|
||||
#reload-visualizer-flash.go {
|
||||
background: var(--white-a3);
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.reload {
|
||||
width: 65%;
|
||||
height: 65%;
|
||||
transition-duration: 200ms;
|
||||
}
|
||||
|
||||
.reload.go {
|
||||
width: 70%;
|
||||
height: 70%;
|
||||
transition: none;
|
||||
}
|
||||
+121
-51
@@ -1,13 +1,15 @@
|
||||
import { type Datum, type Serie } from '@nivo/line';
|
||||
import { Box } from '@radix-ui/themes';
|
||||
import { Box, Code } from '@radix-ui/themes';
|
||||
import { times } from 'lodash-es';
|
||||
import { Quicklime, type QuicklimeCallback } from 'quicklime';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { Var } from '../../../../../../../core/radix/var';
|
||||
import type { StatsAcceptorProps } from '../TraverseVisualizer';
|
||||
import { VisualizerCard } from '../VisualizerCard';
|
||||
import { Shell } from './components/Shell';
|
||||
import { Target } from './components/Target';
|
||||
import './index.css';
|
||||
|
||||
const PRECISION = 2;
|
||||
|
||||
export interface ReloadUpdateData {
|
||||
reload: number;
|
||||
@@ -16,29 +18,29 @@ export interface ReloadUpdateData {
|
||||
|
||||
export const reloadUpdate = new Quicklime<ReloadUpdateData>();
|
||||
|
||||
const T = 60;
|
||||
|
||||
export function ReloadVisualizer({ stats }: StatsAcceptorProps) {
|
||||
const reloadCircle = useRef<HTMLDivElement>(null);
|
||||
const reloadCore = useRef<HTMLDivElement>(null);
|
||||
const reloadGlow = useRef<HTMLDivElement>(null);
|
||||
const totalTime = useRef<HTMLSpanElement>(null);
|
||||
const progressTime = useRef<HTMLSpanElement>(null);
|
||||
const flash = useRef<HTMLDivElement>(null);
|
||||
|
||||
/**
|
||||
* 3-shell gun example:
|
||||
*
|
||||
* 0-1: clip reload
|
||||
* ~~~: shell shot
|
||||
* 1-2: intra-clip
|
||||
* ~~~: shell shot
|
||||
* 2-3: intra-clip
|
||||
* ~~~: shell shot & reset to 0
|
||||
*
|
||||
* intra-clip time = intraClip * (n - 1)
|
||||
*/
|
||||
const state = useRef(0);
|
||||
const dpm = useRef(0);
|
||||
const shotPool = useRef(new Set<number>());
|
||||
const data = useRef<Serie[]>([{ id: 'value', data: [] }]);
|
||||
|
||||
const shoot = useCallback(() => {
|
||||
flash.current?.classList.add('go');
|
||||
reloadCircle.current?.classList.add('go');
|
||||
reloadGlow.current?.classList.add('go');
|
||||
|
||||
void flash.current?.offsetWidth;
|
||||
void reloadCircle.current?.offsetWidth;
|
||||
void reloadGlow.current?.offsetWidth;
|
||||
|
||||
flash.current?.classList.remove('go');
|
||||
reloadCircle.current?.classList.remove('go');
|
||||
reloadGlow.current?.classList.remove('go');
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
let cancel = false;
|
||||
@@ -46,37 +48,76 @@ export function ReloadVisualizer({ stats }: StatsAcceptorProps) {
|
||||
let lastState = state.current;
|
||||
const shellArray = new Array(stats.shells);
|
||||
|
||||
function shoot() {
|
||||
shotPool.current.add(Date.now() / 1000);
|
||||
dpm.current += stats.damage;
|
||||
}
|
||||
|
||||
function frame() {
|
||||
if (!totalTime.current || !progressTime.current) return;
|
||||
|
||||
const reloadThreshold = stats.shellReloads
|
||||
? stats.shellReloads.length
|
||||
: 1;
|
||||
const now = Date.now() / 1000;
|
||||
const dt = now - lastT;
|
||||
let reload;
|
||||
let shells;
|
||||
|
||||
shotPool.current.forEach((t) => {
|
||||
if (now - t > 60) shotPool.current.delete(t);
|
||||
});
|
||||
if (state.current < reloadThreshold) {
|
||||
const shellReload =
|
||||
reloadThreshold === 1
|
||||
? stats.shellReload!
|
||||
: stats.shellReloads![Math.floor(state.current)];
|
||||
|
||||
dpm.current = shotPool.current.size * stats.damage;
|
||||
|
||||
if (state.current < 1) {
|
||||
state.current = Math.min(1, state.current + dt / stats.shellReload!);
|
||||
reload = state.current;
|
||||
state.current = Math.min(
|
||||
reloadThreshold,
|
||||
state.current + dt / shellReload,
|
||||
);
|
||||
reload = state.current % 1;
|
||||
shells = shellArray.fill(reload);
|
||||
|
||||
if (state.current >= 1) shoot();
|
||||
} else if (state.current < stats.shells) {
|
||||
if (reloadThreshold > 1) {
|
||||
shells = shells.map((_, index) =>
|
||||
Math.max(0, state.current - shellArray.length + index + 1),
|
||||
);
|
||||
}
|
||||
|
||||
totalTime.current.innerHTML = shellReload.toFixed(PRECISION);
|
||||
progressTime.current.innerHTML = ((1 - reload) * shellReload).toFixed(
|
||||
PRECISION,
|
||||
);
|
||||
|
||||
if (state.current >= reloadThreshold) shoot();
|
||||
} else if (state.current < stats.shells + reloadThreshold - 1) {
|
||||
let interval;
|
||||
|
||||
if (
|
||||
stats.burstShells === 1 ||
|
||||
Math.floor(state.current) % stats.burstShells === 0
|
||||
) {
|
||||
interval = stats.intraClip!;
|
||||
} else {
|
||||
interval = stats.burstInterShell!;
|
||||
}
|
||||
|
||||
state.current = Math.min(
|
||||
stats.shells,
|
||||
state.current + dt / stats.intraClip!,
|
||||
stats.shells + reloadThreshold,
|
||||
state.current + dt / interval,
|
||||
);
|
||||
shells = times(stats.shells, (index) => index > state.current - 1);
|
||||
reload = state.current % 1;
|
||||
|
||||
if (reloadThreshold > 1) {
|
||||
shells = shellArray.map((_, index) =>
|
||||
Math.max(
|
||||
0,
|
||||
Math.floor(index + 1 + reloadThreshold - state.current),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
shells = times(stats.shells, (index) => index > state.current - 1);
|
||||
}
|
||||
|
||||
totalTime.current.innerHTML = interval.toFixed(PRECISION);
|
||||
progressTime.current.innerHTML = ((1 - reload) * interval).toFixed(
|
||||
PRECISION,
|
||||
);
|
||||
|
||||
if (lastState % 1 > state.current % 1) shoot();
|
||||
} else {
|
||||
state.current = 0;
|
||||
@@ -90,15 +131,6 @@ export function ReloadVisualizer({ stats }: StatsAcceptorProps) {
|
||||
|
||||
lastT = now;
|
||||
lastState = state.current;
|
||||
|
||||
(data.current[0].data as Datum[]).push({
|
||||
x: now,
|
||||
y: dpm.current / (stats.dpm + stats.damage),
|
||||
});
|
||||
(data.current[0].data as Datum[]).splice(
|
||||
0,
|
||||
data.current[0].data.findIndex((d) => now - (d.x as number) < T),
|
||||
);
|
||||
}
|
||||
|
||||
frame();
|
||||
@@ -151,8 +183,6 @@ export function ReloadVisualizer({ stats }: StatsAcceptorProps) {
|
||||
position="absolute"
|
||||
top="50%"
|
||||
left="50%"
|
||||
width="12rem"
|
||||
height="12rem"
|
||||
className="reload"
|
||||
ref={reloadGlow}
|
||||
style={{
|
||||
@@ -167,8 +197,6 @@ export function ReloadVisualizer({ stats }: StatsAcceptorProps) {
|
||||
position="absolute"
|
||||
top="50%"
|
||||
left="50%"
|
||||
width="12rem"
|
||||
height="12rem"
|
||||
className="reload"
|
||||
ref={reloadCircle}
|
||||
style={{
|
||||
@@ -216,10 +244,52 @@ export function ReloadVisualizer({ stats }: StatsAcceptorProps) {
|
||||
}}
|
||||
/>
|
||||
|
||||
<Code
|
||||
ref={progressTime}
|
||||
size="1"
|
||||
variant="ghost"
|
||||
weight="bold"
|
||||
color="gray"
|
||||
highContrast
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
right: 'calc(50% + var(--space-8))',
|
||||
transform: 'translateX(50%)',
|
||||
}}
|
||||
>
|
||||
0.00
|
||||
</Code>
|
||||
<Code
|
||||
ref={totalTime}
|
||||
size="1"
|
||||
variant="ghost"
|
||||
weight="bold"
|
||||
color="gray"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: 'calc(50% + var(--space-8))',
|
||||
transform: 'translateX(-50%)',
|
||||
}}
|
||||
>
|
||||
0.00
|
||||
</Code>
|
||||
|
||||
{stats.shells > 1 &&
|
||||
times(stats.shells, (index) => (
|
||||
<Shell stats={stats} index={index} key={index} />
|
||||
))}
|
||||
|
||||
<Box
|
||||
id="reload-visualizer-flash"
|
||||
ref={flash}
|
||||
position="absolute"
|
||||
top="0"
|
||||
left="0"
|
||||
width="100%"
|
||||
height="100%"
|
||||
/>
|
||||
</VisualizerCard>
|
||||
);
|
||||
}
|
||||
@@ -151,6 +151,8 @@ export function tankCharacteristics(
|
||||
commanderMastery *
|
||||
(tank.crew.some(({ type }) => type === CrewType.DRIVER) ? 1.1 : 1.05);
|
||||
const intraClipCoefficient = coefficient([hasShellReloadBoost, -0.3]);
|
||||
const burstShells = gun.burst?.count ?? 1;
|
||||
const burstInterShell = gun.burst?.interval;
|
||||
const armorDamageCoefficient =
|
||||
coefficient([hasTungsten, 0.15]) *
|
||||
coefficient(
|
||||
@@ -581,6 +583,8 @@ export function tankCharacteristics(
|
||||
shellCapacity,
|
||||
gunType,
|
||||
penetrationAt250m,
|
||||
burstShells,
|
||||
burstInterShell,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import Navbar from '../components/Navbar.astro';
|
||||
import Party3 from '../components/Party3.astro';
|
||||
import './base.css';
|
||||
import { DEFAULT_LOCALE } from '@blitzkit/i18n';
|
||||
import Servicing from '../components/Servicing.astro';
|
||||
|
||||
export interface Props {
|
||||
noFooter?: boolean;
|
||||
@@ -15,6 +16,7 @@ export interface Props {
|
||||
integrated?: boolean;
|
||||
}
|
||||
|
||||
const servicing = import.meta.env.SERVICING;
|
||||
const { noFooter, noNavbar, integrated } = Astro.props;
|
||||
const locale = Astro.params.locale ?? DEFAULT_LOCALE;
|
||||
---
|
||||
@@ -65,7 +67,7 @@ const locale = Astro.params.locale ?? DEFAULT_LOCALE;
|
||||
|
||||
<Checks {locale} client:only="react" />
|
||||
|
||||
<slot />
|
||||
{servicing ? <Servicing /> : <slot />}
|
||||
|
||||
{!noFooter && <Footer />}
|
||||
</Flex>
|
||||
|
||||
Reference in new issue
Block a user