Feature: track repair time in Tankopedia

Adds track repair time to the Tankopedia survivability stats, matching the value the game lists.

- Added a required `repair_time` to track definitions, filled from each chassis's hysteresis health divided by its health regen per second
- Applied the toolbox, the repair crew skill and the protective kit as multiplicative repair speed bonuses
- Added a Track repair time row under fire chance; it appears once the definitions are regenerated on the next build

Fixes #308
This commit is contained in:
Mark Greene committed 2026-10-08 18:02:05 +13:00
1 parent 7234c39ad8
commit 940c3c537b
7 files changed
+39

No files matched your search

@@ -434,6 +434,7 @@ export abstract class ServerBlitzKitAPI1 extends ServerBlitzKitAPI0 {
resistance_hard: terrainResistances[0], resistance_hard: terrainResistances[0],
resistance_medium: terrainResistances[1], resistance_medium: terrainResistances[1],
resistance_soft: terrainResistances[2], resistance_soft: terrainResistances[2],
repair_time: track.hysteresisHealth / track.healthRegenPerSec,
tier: track.level, tier: track.level,
unlocks: resolveUnlocks( unlocks: resolveUnlocks(
this.blitzModuleTypeToBlitzkit, this.blitzModuleTypeToBlitzkit,
@@ -87,6 +87,7 @@ message TrackDefinition {
required float resistance_medium = 10; required float resistance_medium = 10;
required float resistance_soft = 11; required float resistance_soft = 11;
repeated Unlock unlocks = 12; repeated Unlock unlocks = 12;
required float repair_time = 13;
} }
message EngineDefinition { message EngineDefinition {
@@ -338,6 +338,7 @@ export interface TrackDefinition {
resistance_medium: number; resistance_medium: number;
resistance_soft: number; resistance_soft: number;
unlocks: Unlock[]; unlocks: Unlock[];
repair_time: number;
} }
export interface EngineDefinition { export interface EngineDefinition {
@@ -1759,6 +1760,7 @@ function createBaseTrackDefinition(): TrackDefinition {
resistance_medium: 0, resistance_medium: 0,
resistance_soft: 0, resistance_soft: 0,
unlocks: [], unlocks: [],
repair_time: 0,
}; };
} }
@@ -1800,6 +1802,9 @@ export const TrackDefinition: MessageFns<TrackDefinition> = {
for (const v of message.unlocks) { for (const v of message.unlocks) {
Unlock.encode(v!, writer.uint32(98).fork()).join(); Unlock.encode(v!, writer.uint32(98).fork()).join();
} }
if (message.repair_time !== 0) {
writer.uint32(109).float(message.repair_time);
}
return writer; return writer;
}, },
@@ -1906,6 +1911,14 @@ export const TrackDefinition: MessageFns<TrackDefinition> = {
message.unlocks.push(Unlock.decode(reader, reader.uint32())); message.unlocks.push(Unlock.decode(reader, reader.uint32()));
continue; continue;
} }
case 13: {
if (tag !== 109) {
break;
}
message.repair_time = reader.float();
continue;
}
} }
if ((tag & 7) === 4 || tag === 0) { if ((tag & 7) === 4 || tag === 0) {
break; break;
@@ -1929,6 +1942,7 @@ export const TrackDefinition: MessageFns<TrackDefinition> = {
resistance_medium: isSet(object.resistance_medium) ? globalThis.Number(object.resistance_medium) : 0, resistance_medium: isSet(object.resistance_medium) ? globalThis.Number(object.resistance_medium) : 0,
resistance_soft: isSet(object.resistance_soft) ? globalThis.Number(object.resistance_soft) : 0, resistance_soft: isSet(object.resistance_soft) ? globalThis.Number(object.resistance_soft) : 0,
unlocks: globalThis.Array.isArray(object?.unlocks) ? object.unlocks.map((e: any) => Unlock.fromJSON(e)) : [], unlocks: globalThis.Array.isArray(object?.unlocks) ? object.unlocks.map((e: any) => Unlock.fromJSON(e)) : [],
repair_time: isSet(object.repair_time) ? globalThis.Number(object.repair_time) : 0,
}; };
}, },
@@ -1970,6 +1984,9 @@ export const TrackDefinition: MessageFns<TrackDefinition> = {
if (message.unlocks?.length) { if (message.unlocks?.length) {
obj.unlocks = message.unlocks.map((e) => Unlock.toJSON(e)); obj.unlocks = message.unlocks.map((e) => Unlock.toJSON(e));
} }
if (message.repair_time !== 0) {
obj.repair_time = message.repair_time;
}
return obj; return obj;
}, },
@@ -1994,6 +2011,7 @@ export const TrackDefinition: MessageFns<TrackDefinition> = {
message.resistance_medium = object.resistance_medium ?? 0; message.resistance_medium = object.resistance_medium ?? 0;
message.resistance_soft = object.resistance_soft ?? 0; message.resistance_soft = object.resistance_soft ?? 0;
message.unlocks = object.unlocks?.map((e) => Unlock.fromPartial(e)) || []; message.unlocks = object.unlocks?.map((e) => Unlock.fromPartial(e)) || [];
message.repair_time = object.repair_time ?? 0;
return message; return message;
}, },
}; };
@@ -34,6 +34,8 @@ export interface VehicleDefinitions {
}; };
level: number; level: number;
hullPosition: string; hullPosition: string;
hysteresisHealth: number;
healthRegenPerSec: number;
armor: { armor: {
leftTrack: leftTrack:
| number | number
+1
View File
@@ -683,6 +683,7 @@
"hullTraverseSoftTerrain": "Soft", "hullTraverseSoftTerrain": "Soft",
"health": "Health", "health": "Health",
"fireChance": "Fire chance", "fireChance": "Fire chance",
"trackRepairTime": "Track repair time",
"viewRange": "View range", "viewRange": "View range",
"camouflageStill": "Camouflage still", "camouflageStill": "Camouflage still",
"camouflageMoving": "Moving", "camouflageMoving": "Moving",
@@ -24,6 +24,14 @@ export function Survivability({ stats }: StatsAcceptorProps) {
decimals={0} decimals={0}
value={(stats) => stats.fireChance * 100} value={(stats) => stats.fireChance * 100}
/> />
{stats.trackRepairTime !== undefined && (
<InfoWithDelta
value="trackRepairTime"
stats={stats}
deltaType="lowerIsBetter"
decimals={2}
/>
)}
<InfoWithDelta value="viewRange" stats={stats} decimals={0} /> <InfoWithDelta value="viewRange" stats={stats} decimals={0} />
<ViewRangeVisualizer stats={stats} /> <ViewRangeVisualizer stats={stats} />
<Info <Info
@@ -119,6 +119,7 @@ export function tankCharacteristics(
const hasImprovedVerticalStabilizer = equipment(122); const hasImprovedVerticalStabilizer = equipment(122);
const hasDownImprovedVerticalStabilizer = equipment(124); const hasDownImprovedVerticalStabilizer = equipment(124);
const hasImprovedSuspension = equipment(123); const hasImprovedSuspension = equipment(123);
const hasToolbox = equipment(113);
const hasTungsten = consumable(45); const hasTungsten = consumable(45);
const hasAdrenaline = consumable(18); const hasAdrenaline = consumable(18);
@@ -518,6 +519,12 @@ export function tankCharacteristics(
} }
const fireChance = engine.fire_chance * fireChanceCoefficient; const fireChance = engine.fire_chance * fireChanceCoefficient;
const trackRepairTime = track.repair_time
? track.repair_time /
((hasToolbox ? 1.2 : 1) *
(1 + (crewSkills.repair ?? 0) * 0.04) *
(hasProtectiveKit ? 1.1 : 1))
: undefined;
const viewRange = turret.view_range * viewRangeCoefficient; const viewRange = turret.view_range * viewRangeCoefficient;
const camouflageStill = tank.camouflage_still * camouflageCoefficientStill; const camouflageStill = tank.camouflage_still * camouflageCoefficientStill;
const camouflageMoving = const camouflageMoving =
@@ -602,6 +609,7 @@ export function tankCharacteristics(
hullTraverseSoftTerrain, hullTraverseSoftTerrain,
health, health,
fireChance, fireChance,
trackRepairTime,
viewRange, viewRange,
camouflageStill, camouflageStill,
camouflageMoving, camouflageMoving,