mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-25 11:39:51 +01:00
Compare commits
13
Commits
d80f47a6c8
...
655b87d876
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
655b87d876 | ||
|
|
71caa2f703 | ||
|
|
99c95a4c15 | ||
|
|
93632a7073 | ||
|
|
297265822b | ||
|
|
a7f799b5b6 | ||
|
|
b176ec6c96 | ||
|
|
1a008a4852 | ||
|
|
145de2bbbc | ||
|
|
77c516a6eb | ||
|
|
6835004945 | ||
|
|
e36823c159 | ||
|
|
ec6ac128f2 |
No files matched your search
@@ -1,16 +1,21 @@
|
||||
import { ShellType } from "../protos";
|
||||
import { Equalizer, ShellType } from "../protos";
|
||||
|
||||
export function resolvePenetrationCoefficient(
|
||||
calibratedShells: boolean,
|
||||
equalize: boolean,
|
||||
type: ShellType,
|
||||
equalizerObj?: Equalizer,
|
||||
) {
|
||||
if (!calibratedShells) return 1;
|
||||
const calibrated = calibratedShells
|
||||
? type === ShellType.SHELL_TYPE_AP
|
||||
? 1.08
|
||||
: type === ShellType.SHELL_TYPE_APCR
|
||||
? 1.05
|
||||
: type === ShellType.SHELL_TYPE_HEAT
|
||||
? 1.13
|
||||
: 1.08
|
||||
: 1;
|
||||
const equalizer = (equalize ? equalizerObj?.penetration : undefined) ?? 1;
|
||||
|
||||
return type === ShellType.SHELL_TYPE_AP
|
||||
? 1.08
|
||||
: type === ShellType.SHELL_TYPE_APCR
|
||||
? 1.05
|
||||
: type === ShellType.SHELL_TYPE_HEAT
|
||||
? 1.13
|
||||
: 1.08;
|
||||
return calibrated * equalizer;
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
// @ts-nocheck
|
||||
export * from './average_definitions.ts';
|
||||
export * from './camouflage_definitions.ts';
|
||||
export * from './consumable_definitions.ts';
|
||||
export * from './equipment_definitions.ts';
|
||||
export * from './gallery.ts';
|
||||
export * from './game_definitions.ts';
|
||||
export * from './i18n.ts';
|
||||
export * from './map_definitions.ts';
|
||||
export * from './model_definitions.ts';
|
||||
export * from './popular_tanks.ts';
|
||||
export * from './provision_definitions.ts';
|
||||
export * from './rating_leaderboard.ts';
|
||||
export * from './reviews.ts';
|
||||
export * from './skill_definitions.ts';
|
||||
export * from './tank_definitions.ts';
|
||||
export * from "./average_definitions.ts";
|
||||
export * from "./camouflage_definitions.ts";
|
||||
export * from "./consumable_definitions.ts";
|
||||
export * from "./equipment_definitions.ts";
|
||||
export * from "./gallery.ts";
|
||||
export * from "./game_definitions.ts";
|
||||
export * from "./i18n.ts";
|
||||
export * from "./map_definitions.ts";
|
||||
export * from "./model_definitions.ts";
|
||||
export * from "./popular_tanks.ts";
|
||||
export * from "./provision_definitions.ts";
|
||||
export * from "./rating_leaderboard.ts";
|
||||
export * from "./reviews.ts";
|
||||
export * from "./skill_definitions.ts";
|
||||
export * from "./tank_definitions.ts";
|
||||
File diff suppressed because it is too large.
Load diff
@@ -40,6 +40,15 @@ message TankDefinition {
|
||||
required string equipment_preset = 30;
|
||||
required uint32 weight = 31;
|
||||
required string dev_name = 32;
|
||||
optional Equalizer equalizer = 33;
|
||||
}
|
||||
|
||||
message Equalizer {
|
||||
required float health = 1;
|
||||
required float penetration = 2;
|
||||
required float module_health = 3;
|
||||
required float damage = 4;
|
||||
required float armor = 5;
|
||||
}
|
||||
|
||||
message ResearchCost {
|
||||
|
||||
@@ -291,6 +291,7 @@ export interface TankDefinition {
|
||||
equipment_preset: string;
|
||||
weight: number;
|
||||
dev_name: string;
|
||||
equalizer?: Equalizer | undefined;
|
||||
}
|
||||
|
||||
export interface TankDefinition_RolesEntry {
|
||||
@@ -298,6 +299,14 @@ export interface TankDefinition_RolesEntry {
|
||||
value: number;
|
||||
}
|
||||
|
||||
export interface Equalizer {
|
||||
health: number;
|
||||
penetration: number;
|
||||
module_health: number;
|
||||
damage: number;
|
||||
armor: number;
|
||||
}
|
||||
|
||||
export interface ResearchCost {
|
||||
research_cost_type?:
|
||||
| { $case: "xp"; value: number }
|
||||
@@ -644,6 +653,7 @@ function createBaseTankDefinition(): TankDefinition {
|
||||
equipment_preset: "",
|
||||
weight: 0,
|
||||
dev_name: "",
|
||||
equalizer: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -742,6 +752,9 @@ export const TankDefinition: MessageFns<TankDefinition> = {
|
||||
if (message.dev_name !== "") {
|
||||
writer.uint32(258).string(message.dev_name);
|
||||
}
|
||||
if (message.equalizer !== undefined) {
|
||||
Equalizer.encode(message.equalizer, writer.uint32(266).fork()).join();
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
@@ -1033,6 +1046,14 @@ export const TankDefinition: MessageFns<TankDefinition> = {
|
||||
message.dev_name = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 33: {
|
||||
if (tag !== 266) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.equalizer = Equalizer.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
@@ -1141,6 +1162,7 @@ export const TankDefinition: MessageFns<TankDefinition> = {
|
||||
: isSet(object.dev_name)
|
||||
? globalThis.String(object.dev_name)
|
||||
: "",
|
||||
equalizer: isSet(object.equalizer) ? Equalizer.fromJSON(object.equalizer) : undefined,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -1245,6 +1267,9 @@ export const TankDefinition: MessageFns<TankDefinition> = {
|
||||
if (message.dev_name !== "") {
|
||||
obj.devName = message.dev_name;
|
||||
}
|
||||
if (message.equalizer !== undefined) {
|
||||
obj.equalizer = Equalizer.toJSON(message.equalizer);
|
||||
}
|
||||
return obj;
|
||||
},
|
||||
|
||||
@@ -1298,6 +1323,9 @@ export const TankDefinition: MessageFns<TankDefinition> = {
|
||||
message.equipment_preset = object.equipment_preset ?? "";
|
||||
message.weight = object.weight ?? 0;
|
||||
message.dev_name = object.dev_name ?? "";
|
||||
message.equalizer = (object.equalizer !== undefined && object.equalizer !== null)
|
||||
? Equalizer.fromPartial(object.equalizer)
|
||||
: undefined;
|
||||
return message;
|
||||
},
|
||||
};
|
||||
@@ -1378,6 +1406,134 @@ export const TankDefinition_RolesEntry: MessageFns<TankDefinition_RolesEntry> =
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseEqualizer(): Equalizer {
|
||||
return { health: 0, penetration: 0, module_health: 0, damage: 0, armor: 0 };
|
||||
}
|
||||
|
||||
export const Equalizer: MessageFns<Equalizer> = {
|
||||
encode(message: Equalizer, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
if (message.health !== 0) {
|
||||
writer.uint32(13).float(message.health);
|
||||
}
|
||||
if (message.penetration !== 0) {
|
||||
writer.uint32(21).float(message.penetration);
|
||||
}
|
||||
if (message.module_health !== 0) {
|
||||
writer.uint32(29).float(message.module_health);
|
||||
}
|
||||
if (message.damage !== 0) {
|
||||
writer.uint32(37).float(message.damage);
|
||||
}
|
||||
if (message.armor !== 0) {
|
||||
writer.uint32(45).float(message.armor);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): Equalizer {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseEqualizer();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 13) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.health = reader.float();
|
||||
continue;
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 21) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.penetration = reader.float();
|
||||
continue;
|
||||
}
|
||||
case 3: {
|
||||
if (tag !== 29) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.module_health = reader.float();
|
||||
continue;
|
||||
}
|
||||
case 4: {
|
||||
if (tag !== 37) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.damage = reader.float();
|
||||
continue;
|
||||
}
|
||||
case 5: {
|
||||
if (tag !== 45) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.armor = reader.float();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
|
||||
fromJSON(object: any): Equalizer {
|
||||
return {
|
||||
health: isSet(object.health) ? globalThis.Number(object.health) : 0,
|
||||
penetration: isSet(object.penetration) ? globalThis.Number(object.penetration) : 0,
|
||||
module_health: isSet(object.moduleHealth)
|
||||
? globalThis.Number(object.moduleHealth)
|
||||
: isSet(object.module_health)
|
||||
? globalThis.Number(object.module_health)
|
||||
: 0,
|
||||
damage: isSet(object.damage) ? globalThis.Number(object.damage) : 0,
|
||||
armor: isSet(object.armor) ? globalThis.Number(object.armor) : 0,
|
||||
};
|
||||
},
|
||||
|
||||
toJSON(message: Equalizer): unknown {
|
||||
const obj: any = {};
|
||||
if (message.health !== 0) {
|
||||
obj.health = message.health;
|
||||
}
|
||||
if (message.penetration !== 0) {
|
||||
obj.penetration = message.penetration;
|
||||
}
|
||||
if (message.module_health !== 0) {
|
||||
obj.moduleHealth = message.module_health;
|
||||
}
|
||||
if (message.damage !== 0) {
|
||||
obj.damage = message.damage;
|
||||
}
|
||||
if (message.armor !== 0) {
|
||||
obj.armor = message.armor;
|
||||
}
|
||||
return obj;
|
||||
},
|
||||
|
||||
create<I extends Exact<DeepPartial<Equalizer>, I>>(base?: I): Equalizer {
|
||||
return Equalizer.fromPartial(base ?? ({} as any));
|
||||
},
|
||||
fromPartial<I extends Exact<DeepPartial<Equalizer>, I>>(object: I): Equalizer {
|
||||
const message = createBaseEqualizer();
|
||||
message.health = object.health ?? 0;
|
||||
message.penetration = object.penetration ?? 0;
|
||||
message.module_health = object.module_health ?? 0;
|
||||
message.damage = object.damage ?? 0;
|
||||
message.armor = object.armor ?? 0;
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseResearchCost(): ResearchCost {
|
||||
return { research_cost_type: undefined };
|
||||
}
|
||||
|
||||
@@ -1111,7 +1111,8 @@
|
||||
|
||||
"equalizer": {
|
||||
"title": "Equalizer",
|
||||
"equalize": "Equalize"
|
||||
"equalize": "Equalize",
|
||||
"beta": "Beta"
|
||||
},
|
||||
"modules": {
|
||||
"title": "Modules",
|
||||
@@ -1290,7 +1291,8 @@
|
||||
},
|
||||
"skills": {
|
||||
"clear": "Clear",
|
||||
"maximize": "Maximize"
|
||||
"maximize": "Maximize",
|
||||
"equalize": "Equalize"
|
||||
},
|
||||
"modules": {
|
||||
"stock": "Stock",
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
ConsumableTankCategoryFilterCategory,
|
||||
Crew,
|
||||
CrewType,
|
||||
Equalizer,
|
||||
EquipmentDefinitions,
|
||||
EquipmentSlot,
|
||||
GameDefinitions,
|
||||
@@ -32,6 +33,7 @@ import {
|
||||
} from "@blitzkit/core";
|
||||
import { SUPPORTED_LOCALE_BLITZ_MAP } from "@blitzkit/i18n";
|
||||
import locales from "@blitzkit/i18n/locales.json";
|
||||
import { readFile } from "fs/promises";
|
||||
import { deburr } from "lodash-es";
|
||||
import { parse as parsePath } from "path";
|
||||
import type { Vector3Tuple } from "three";
|
||||
@@ -688,6 +690,24 @@ export async function definitions() {
|
||||
);
|
||||
const consumableNativeNames: Record<string, number> = {};
|
||||
const provisionNativeNames: Record<string, number> = {};
|
||||
let equalizerDefinitions: string[][];
|
||||
|
||||
if (
|
||||
await vfs.resolve("Data/XML/item_defs/vehicles/common/tier_equializer.csv")
|
||||
) {
|
||||
equalizerDefinitions = await vfs.csv(
|
||||
`Data/XML/item_defs/vehicles/common/tier_equializer.csv`,
|
||||
{ delimiter: ";" },
|
||||
);
|
||||
} else {
|
||||
const stubEqualizerDefinitions = await readFile("stub/equalizer.csv").then(
|
||||
(buffer) => buffer.toString(),
|
||||
);
|
||||
|
||||
equalizerDefinitions = await vfs._csv(stubEqualizerDefinitions, {
|
||||
delimiter: ";",
|
||||
});
|
||||
}
|
||||
|
||||
for (const match of squadBattleTypeStyles.Prototypes[0].components.UIDataLocalBindingsComponent.data[1][2].matchAll(
|
||||
/"(\d+)" -> "(battleType\/([a-zA-Z]+))"/g,
|
||||
@@ -998,6 +1018,18 @@ export async function definitions() {
|
||||
})
|
||||
.map(([, camo]) => camo.id);
|
||||
|
||||
let equalizerEntry = equalizerDefinitions.find(
|
||||
(line) => line[0] === `${nation}:${tankKey}`,
|
||||
);
|
||||
|
||||
let equalizer: Equalizer | undefined;
|
||||
|
||||
if (equalizerEntry) {
|
||||
const [health, penetration, module_health, damage, armor] =
|
||||
equalizerEntry?.slice(1).map(Number);
|
||||
equalizer = { health, penetration, module_health, damage, armor };
|
||||
}
|
||||
|
||||
tankDefinitions.tanks[tankId] = {
|
||||
ancestors: [],
|
||||
successors: [],
|
||||
@@ -1037,6 +1069,7 @@ export async function definitions() {
|
||||
turrets: [],
|
||||
engines: [],
|
||||
tracks: [],
|
||||
equalizer,
|
||||
};
|
||||
|
||||
if (tank.combatRole) {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { readDVPL } from "@blitzkit/core";
|
||||
import { Options, parse as parseCSV } from "csv-parse/sync";
|
||||
import { XMLParser } from "fast-xml-parser";
|
||||
import { normalize } from "path/posix";
|
||||
import { parse } from "yaml";
|
||||
import { parse as parseYaml } from "yaml";
|
||||
|
||||
export abstract class AbstractVFS {
|
||||
textDecoder = new TextDecoder();
|
||||
@@ -54,7 +55,7 @@ export abstract class AbstractVFS {
|
||||
|
||||
async yaml<Type>(path: string) {
|
||||
const file = await this.text(path);
|
||||
return parse(file) as Type;
|
||||
return parseYaml(file) as Type;
|
||||
}
|
||||
|
||||
async xml<Type>(path: string) {
|
||||
@@ -62,6 +63,16 @@ export abstract class AbstractVFS {
|
||||
return this.xmlParser.parse(file) as Type;
|
||||
}
|
||||
|
||||
async _csv(text: string, options: Options = {}) {
|
||||
const parsed = parseCSV(text, options);
|
||||
return parsed;
|
||||
}
|
||||
|
||||
async csv(path: string, options: Options = {}) {
|
||||
const text = await this.text(path);
|
||||
return await this._csv(text, options);
|
||||
}
|
||||
|
||||
[Symbol.dispose]() {
|
||||
this.dispose();
|
||||
}
|
||||
|
||||
@@ -168,6 +168,7 @@ if (changedTanks.length > 0) {
|
||||
)
|
||||
.map((id) => currentEquipmentDefinitions.equipments[id]);
|
||||
|
||||
const equalize = false;
|
||||
const currentCharacteristics = tankCharacteristics(
|
||||
{
|
||||
tank: current,
|
||||
@@ -193,6 +194,7 @@ if (changedTanks.length > 0) {
|
||||
stockTurret: current.turrets[0],
|
||||
track: currentTracks,
|
||||
turret: currentTurret,
|
||||
equalize,
|
||||
},
|
||||
{
|
||||
equipmentDefinitions: currentEquipmentDefinitions,
|
||||
@@ -225,6 +227,7 @@ if (changedTanks.length > 0) {
|
||||
stockTurret: preview.turrets[0],
|
||||
track: previewTracks,
|
||||
turret: previewTurret,
|
||||
equalize,
|
||||
},
|
||||
{
|
||||
equipmentDefinitions: previewEquipmentDefinitions,
|
||||
|
||||
@@ -0,0 +1,619 @@
|
||||
name;maxHealthFactor;piercingPowerFactor;moduleHealthFactor;damageFactor;armorFactor
|
||||
china:Ch01_Type59;1.3;1.33;1.5;1.29;1.35
|
||||
china:Ch02_Type62;1.5;1.6;1.5;1.27;1.61
|
||||
china:Ch03_WZ-111;1.4;1.17;1.4;1.3;1.35
|
||||
china:Ch04_T34_1;1.5;1.54;1.4;1.3;1.61
|
||||
china:Ch05_T34_2;1.3;1.33;1.5;1.29;1.34
|
||||
china:Ch07_Vickers_MkE_Type_BT26;5.5;5.07;3.3;7.05;8.8
|
||||
china:Ch08_Type97_Chi_Ha;3.7;3.71;2.2;3.13;5.56
|
||||
china:Ch09_M5;2.9;2.91;1.9;2.54;3.47
|
||||
china:Ch10_IS2;1.6;1.44;1.4;1.33;1.61
|
||||
china:Ch11_110;1.4;1.17;1.4;1.29;1.35
|
||||
china:Ch12_111_1_2_3;1.2;1.02;1.4;1.17;1.14
|
||||
china:Ch14_T34_3;1.3;1.37;1.5;1.13;1.17
|
||||
china:Ch15_59_16;1.9;1.77;1.6;1.57;2.24
|
||||
china:Ch15_59_16_BP;1.9;1.7;1.6;1.56;2.24
|
||||
china:Ch16_WZ_131;1.6;1.52;1.5;1.28;1.61
|
||||
china:Ch17_WZ131_1_WZ132;1.4;1.22;1.5;1.29;1.34
|
||||
china:Ch18_WZ-120;1.1;1.09;1.4;1.11;1.17
|
||||
china:Ch20_Type58;1.9;1.88;1.5;1.57;1.86
|
||||
china:Ch21_T34;2.7;2.1;1.7;1.97;2.12
|
||||
china:Ch23_112;1.4;1.17;1.4;1.31;1.35
|
||||
china:Ch24_Type64;1.9;1.86;1.6;1.57;2.35
|
||||
china:Ch26_59_Patton;1.3;1.33;1.5;1.1;1.16
|
||||
china:Ch28_WZ_132A;1.1;1.04;1.5;1.11;1.24
|
||||
china:Ch34_WZ-131G_FT;2.1;1.57;1.4;1.51;1.73
|
||||
china:Ch34_WZ-131G_FT_BP;2.1;1.57;1.4;1.51;2.02
|
||||
china:Ch35_T-34-2G_FT;1.8;1.48;1.4;1.25;1.61
|
||||
china:Ch36_WZ-111-1G_FT;1.6;1.16;1.4;1.25;1.35
|
||||
china:Ch37_WZ-111G_FT;1.2;1.05;1.4;1.15;1.13
|
||||
china:Ch39_WZ120_1G_FT;1.6;1.16;1.4;1.24;1.35
|
||||
china:Ch40_WZ120G_FT;1.1;1.05;1.4;1.08;1.13
|
||||
china:Ch42_M41D;1.7;1.49;1.5;1.24;1.61
|
||||
china:Ch43_WZ-122_2;1.3;1.21;1.5;1.31;1.35
|
||||
china:Ch45_WZ_114;1.2;1.02;1.4;1.19;1.13
|
||||
china:Ch47_BZ_176;1.2;1.03;1.4;1.17;1.13
|
||||
china:Ch49_BZ_68;1.2;1.02;1.4;1.2;1.16
|
||||
china:Ch50_BZ_166;1.4;1.17;1.4;1.3;1.35
|
||||
china:Ch51_BZ_58;1.6;1.46;1.4;1.29;1.62
|
||||
china:Ch56_BZ_74_1;1.2;1.02;1.4;1.16;1.14
|
||||
china:Ch59_BZ58_2;1.2;1.02;1.4;1.19;1.13
|
||||
china:Ch60_Type59_spc;1.3;1.37;1.5;1.32;1.35
|
||||
china:Ch_LT_vz38;4.3;4.29;2.7;3.99;4.88
|
||||
china:Ch_WZ-112v2;1.4;1.17;1.4;1.31;1.35
|
||||
china:Type_58BP;1.9;1.88;1.5;1.57;2.04
|
||||
european:Cz02_T50;1.1;1.03;1.4;1.12;1.19
|
||||
european:Cz05_T34-100;1.5;1.45;1.4;1.29;1.61
|
||||
european:Cz07_TVP_46;1.3;1.12;1.5;1.29;1.35
|
||||
european:Cz08_T-25;1.9;1.83;1.5;1.56;2.02
|
||||
european:Cz08_T-25BP;1.9;1.83;1.5;1.55;2.02
|
||||
european:Cz08_T-25BPS;1.9;1.83;1.5;1.57;2.02
|
||||
european:Cz13_T-27;1.1;1.07;1.4;1.12;1.19
|
||||
european:Cz14_Skoda_T-56;1.4;1.17;1.4;1.33;1.35
|
||||
european:Cz15_Skoda_T-45;1.7;1.44;1.4;1.27;1.61
|
||||
european:Cz16_TNH_T_vz51;1.2;1.02;1.4;1.17;1.15
|
||||
european:Cz18_TNH_105_1000;1.4;1.16;1.4;1.28;1.35
|
||||
european:Cz19_Vz_44_1;1.7;1.43;1.4;1.3;1.61
|
||||
european:Cz20_ShPTK_TVP_100;1.5;1.16;1.4;1.24;1.34
|
||||
european:Cz29_Skoda_P-JS;2.2;1.67;1.4;1.56;1.78
|
||||
european:Cz29_Skoda_P-JS_BP;2.2;1.67;1.4;1.57;1.79
|
||||
european:F01_Vickers_MkF;5.5;5.07;3.3;6.16;8.8
|
||||
european:It09_P43_ter;1.5;1.54;1.4;1.3;1.61
|
||||
european:It10_P43_BD;1.9;1.74;1.5;1.57;1.82
|
||||
european:It10_P43_bis;1.9;1.74;1.5;1.57;1.82
|
||||
european:It10_P43_bis_BP;1.9;1.7;1.5;1.58;1.82
|
||||
european:It116_Carro_d_assalto_P88;1.6;1.33;1.4;1.28;1.61
|
||||
european:It117_ProgettoCC55mod54;1.3;1.13;1.4;1.29;1.35
|
||||
european:It119_ProgettoC50mod66;1.1;1.02;1.4;1.18;1.13
|
||||
european:It12_Prototipo_Standard_B;1.1;1.04;1.4;1.14;1.17
|
||||
european:It13_Progetto_M35_mod_46;1.3;1.33;1.5;1.27;1.35
|
||||
european:It14_P44_Pantera;1.3;1.21;1.5;1.26;1.35
|
||||
european:It15_Semovente_M41;3.2;1.98;1.5;1.97;2.13
|
||||
european:It16_Bassoto;2;1.59;1.4;1.53;1.8
|
||||
european:It17_Semovente_mod_56;1.7;1.4;1.4;1.28;1.62
|
||||
european:It18_Semovente_mod_64;1.4;1.2;1.4;1.24;1.35
|
||||
european:It19_Controcarro_1MK2_Ciclope;1.1;1.05;1.4;1.19;1.13
|
||||
european:It22_Progetto_C45_mod_71;1.3;1.14;1.4;1.23;1.35
|
||||
european:It30_CC_mod_64_Prem;1.4;1.17;1.4;1.25;1.35
|
||||
european:Pl03_PzV_Pudel;1.9;1.57;1.5;1.56;1.92
|
||||
european:Pl05_50TP_Tyszkiewicza;1.2;1.02;1.4;1.18;1.13
|
||||
european:Pl06_10TP;4.3;4.29;2.7;4.28;7.66
|
||||
european:Pl07_14TP;3.6;3.57;2.1;3.12;5.56
|
||||
european:Pl08_50TP_prot;1.2;1.02;1.4;1.17;1.14
|
||||
european:Pl10_40TP_Habicha;2.1;1.64;1.4;1.57;1.83
|
||||
european:Pl119_CS_52_LIS;1.5;1.56;1.4;1.31;1.61
|
||||
european:Pl11_45TP_Habicha;1.6;1.44;1.4;1.3;1.61
|
||||
european:Pl13_53TP_Markowskiego;1.4;1.14;1.4;1.32;1.35
|
||||
european:Pl17_DS_PZlnz;2.6;2.28;1.7;1.95;2.12
|
||||
european:Pl18_BUGI;1.9;1.57;1.5;1.58;1.94
|
||||
european:Pl20_CS_44;1.5;1.54;1.4;1.3;1.61
|
||||
european:Pl22_CS_59;1.1;1.04;1.4;1.1;1.21
|
||||
european:Pl23_CS_53;1.3;1.31;1.5;1.29;1.35
|
||||
european:Pl24_56TP;1.4;1.15;1.4;1.29;1.35
|
||||
european:Pl27_SDP_Wz_66T;1.5;1.17;1.4;1.27;1.35
|
||||
european:Pl28_SDP_Wz_60;1.1;1.05;1.4;1.11;1.12
|
||||
european:Pl30_SDP_Wz58T;1.4;1.16;1.4;1.26;1.35
|
||||
european:Pl31_SDP_Wz_57D;1.7;1.43;1.4;1.28;1.62
|
||||
european:Pl32_SDP_Wz_44;2;1.57;1.4;1.52;1.76
|
||||
european:Pl33_SDP_Wz_40;3.2;2.13;1.5;1.96;2.13
|
||||
european:S02_StrvM42;2.7;2.22;1.7;1.95;2.11
|
||||
european:S04_Lago-I;3.1;2.69;1.9;2.54;2.32
|
||||
european:S07_Strv74BP;1.9;1.67;1.5;1.55;1.9
|
||||
european:S07_Strv_74;1.9;1.67;1.5;1.55;1.9
|
||||
european:S13_Leo;1.5;1.58;1.4;1.3;1.61
|
||||
european:S13_Leo_NY;1.5;1.58;1.4;1.3;1.61
|
||||
european:S17_EMIL_1952E2;1.2;1.02;1.4;1.16;1.12
|
||||
european:S18_EMIL_1951E1;1.3;1.16;1.4;1.29;1.35
|
||||
european:S23_Strv_81;1.3;1.14;1.5;1.26;1.35
|
||||
european:S25_EMIL_51;1.3;1.12;1.4;1.29;1.35
|
||||
european:S26_Lancen_C;1.3;1.22;1.5;1.31;1.35
|
||||
european:S32_Bofors_Tornvagn;1.1;1.02;1.4;1.17;1.12
|
||||
france:AMX38;4.4;4.29;2.7;4.15;5.95
|
||||
france:AMX40;3;2.88;1.9;2.52;3.53
|
||||
france:AMX50_Foch;1.2;1.05;1.4;1.11;1.14
|
||||
france:AMX_12t;1.9;1.64;1.6;1.6;2.02
|
||||
france:AMX_13_75;1.7;1.61;1.5;1.25;1.61
|
||||
france:AMX_13_90;1.4;1.37;1.5;1.29;1.34
|
||||
france:AMX_50_100;1.4;1.12;1.4;1.26;1.35
|
||||
france:AMX_50_120;1.2;1.02;1.4;1.12;1.17
|
||||
france:AMX_AC_Mle1946;1.7;1.45;1.4;1.24;1.61
|
||||
france:AMX_AC_Mle1948;1.5;1.16;1.4;1.24;1.35
|
||||
france:AMX_M4_1945;1.6;1.47;1.4;1.28;1.61
|
||||
france:ARL_44;2.1;1.57;1.4;1.53;1.82
|
||||
france:ARL_44BP;2.1;1.53;1.4;1.58;1.81
|
||||
france:ARL_V39;2;1.59;1.4;1.52;1.82
|
||||
france:Achilles_FR;2.1;1.6;1.4;1.52;1.83
|
||||
france:B1;4.1;2.8;1.6;2.54;3.64
|
||||
france:BDR_G1B;3.2;1.91;1.5;1.95;2.13
|
||||
france:Bat-Chat-25CL;1.1;1.05;1.5;1.1;1.22
|
||||
france:D1;4.3;4.62;2.7;3.8;6.52
|
||||
france:D2;3.7;3.64;2.2;3;5.52
|
||||
france:ELC_AMX;2.8;1.97;2.3;2.31;2.09
|
||||
france:F113_PzV_FR;1.9;1.65;1.5;1.56;1.89
|
||||
france:F114_Projet_4_1;1.1;1.03;1.4;1.09;1.22
|
||||
france:F115_Lorraine_50t;1.2;1.02;1.4;1.13;1.16
|
||||
france:F116_Bat_Chat_Bourrasque;1.3;1.28;1.5;1.31;1.34
|
||||
france:F117_Alt_Proto_AMX_30;1.3;1.15;1.5;1.29;1.35
|
||||
france:F11_Renault_G1R;3.2;2;1.5;1.96;2.12
|
||||
france:F120_Project_Louis;1.1;1.04;1.4;1.12;1.14
|
||||
france:F123_ERAC_105_Proto;1.3;1.22;1.5;1.3;1.35
|
||||
france:F126_AMX_13_M24;1.5;1.58;1.4;1.32;1.62
|
||||
france:F127_ELC_AMX_901_Proto;1.4;1.28;1.5;1.28;1.34
|
||||
france:F140_Sentinelle;1.4;1.15;1.4;1.28;1.35
|
||||
france:F19_Lorraine40t;1.3;1.17;1.5;1.3;1.34
|
||||
france:F19_Lorraine40t_Com;1.3;1.17;1.5;1.29;1.34
|
||||
france:F34_ARL_V39_BP;2.1;1.59;1.4;1.51;1.82
|
||||
france:F68_AMX_Chasseur_de_char_46;1.3;1.17;1.5;1.25;1.33
|
||||
france:F69_AMX_13D;1.4;1.16;1.5;1.28;1.34
|
||||
france:F69_AMX_13_57GF;1.6;1.49;1.5;1.26;1.61
|
||||
france:F71_AMX_30_prototype;1.1;1.06;1.4;1.12;1.19
|
||||
france:F73_M4A1_Revalorise;1.3;1.23;1.5;1.27;1.34
|
||||
france:F74_AMX_M4_1949;1.4;1.12;1.4;1.28;1.35
|
||||
france:F75_Char_de_25t;1.1;1.02;1.5;1.11;1.22
|
||||
france:F84_Somua_SM;1.4;1.12;1.4;1.14;1.14
|
||||
france:F89_Canon_dassaut_105;1.6;1.15;1.4;1.26;1.35
|
||||
france:FCM_36Pak40;4.2;2.95;1.9;3.18;5.52
|
||||
france:FCM_50t;1.3;1.17;1.5;1.25;1.35
|
||||
france:F_AMX-12BP;1.9;1.64;1.6;1.6;2.02
|
||||
france:RenaultFT_AC;5.5;4.37;2.2;4.49;8.51
|
||||
france:RenaultR35;5.5;5.28;3.3;6.76;7.8
|
||||
france:RenaultUE57;4.2;2.79;1.9;2.68;5.95
|
||||
france:S_35CA;3.2;2.07;1.5;1.96;2.11
|
||||
france:Somua_Sau_40;3.7;2.75;1.8;2.54;3.62
|
||||
germany:B-1bis_captured;4.1;2.96;1.6;2.55;3.89
|
||||
germany:DW_II;3.7;2.52;1.6;2.52;3.53
|
||||
germany:DickerMax;2.1;1.57;1.4;1.53;2.02
|
||||
germany:E-25;1.8;1.46;1.4;1.24;1.61
|
||||
germany:E-50;1.1;1.09;1.4;1.1;1.15
|
||||
germany:E-75;1.2;1.02;1.4;1.17;1.14
|
||||
germany:Ferdinand;1.4;1.16;1.4;1.25;1.35
|
||||
germany:G100_Gtraktor_Krupp;4.9;3.31;1.9;3.49;5.75
|
||||
germany:G103_RU_251;1.4;1.3;1.5;1.25;1.33
|
||||
germany:G106_PzKpfwPanther_AusfF;1.3;1.21;1.5;1.26;1.35
|
||||
germany:G109_Steyr_WT;1.8;1.37;1.4;1.27;1.6
|
||||
germany:G110_Mauschen;1.2;1.02;1.4;1.18;1.12
|
||||
germany:G112_JagdPanzer_105;1.2;1.05;1.4;1.04;1.18
|
||||
germany:G113_SP_I_C;1.7;1.49;1.5;1.28;1.58
|
||||
germany:G114_Rheinmetall_Scorpion;1.6;1.16;1.4;1.24;1.33
|
||||
germany:G115_VK10001P;1.4;1.15;1.4;1.33;1.35
|
||||
germany:G118_VK4503;1.7;1.3;1.4;1.27;1.61
|
||||
germany:G119_Panzer58_SH;1.3;1.22;1.5;1.28;1.35
|
||||
germany:G119_Pz58_Mutz;1.3;1.17;1.5;1.26;1.35
|
||||
germany:G120_M41_90;1.4;1.29;1.5;1.26;1.34
|
||||
germany:G126_HWK_12;1.1;1.05;1.5;1.08;1.29
|
||||
germany:G136_Tiger_131;2.1;1.68;1.4;1.55;1.85
|
||||
germany:G138_VK16801P;1.5;1.15;1.4;1.16;1.13
|
||||
germany:G140_HWK_30;1.4;1.27;1.5;1.27;1.34
|
||||
germany:G142_M48RPz;1.3;1.18;1.5;1.27;1.35
|
||||
germany:G147_mPanzer_64;1.7;1.49;1.5;1.29;1.61
|
||||
germany:G148_Elephant;1.4;1.16;1.4;1.21;1.35
|
||||
germany:G154_Kpz_07_RH;1.3;1.15;1.5;1.26;1.35
|
||||
germany:G162_Project_Kpz_07P_E;1.1;1.02;1.4;1.16;1.14
|
||||
germany:G164_Kpz_Pr_68_P;1.3;1.22;1.5;1.3;1.34
|
||||
germany:G165_Erich_Koncept;1.1;1.06;1.4;1.14;1.19
|
||||
germany:G166_LKpz_70_K;1.1;1.02;1.5;1.11;1.32
|
||||
germany:G167_Tiger_Maus;1.2;1.03;1.4;1.11;1.13
|
||||
germany:G168_KJpz_T_III;1.2;1.05;1.4;1.05;1.29
|
||||
germany:G190_VK_1602_Quby;1.4;1.36;1.5;1.25;1.34
|
||||
germany:G20_Marder_II;4.2;3.01;1.9;3.18;5.54
|
||||
germany:G85_Auf_Panther;1.5;1.49;1.5;1.25;1.61
|
||||
germany:G_E-75RT;1.4;1.14;1.4;1.3;1.35
|
||||
germany:G_E-75TS;1.2;1.03;1.4;1.13;1.13
|
||||
germany:G_VK3502_BP;2.1;1.68;1.4;1.57;1.79
|
||||
germany:H39_captured;4.3;5.58;2.7;3.99;6.04
|
||||
germany:Hetzer;3.7;2.6;1.8;2.54;3.47
|
||||
germany:Indien_Panzer;1.3;1.17;1.5;1.25;1.35
|
||||
germany:JagdPanther;1.8;1.37;1.4;1.25;1.62
|
||||
germany:JagdPantherII;1.6;1.16;1.4;1.23;1.35
|
||||
germany:JagdPzIV;2.1;1.58;1.4;1.49;1.8
|
||||
germany:JagdPzIV_AusfBP;2;1.58;1.4;1.49;1.8
|
||||
germany:JagdTiger;1.1;1.05;1.4;1.04;1.12
|
||||
germany:JagdTiger_SdKfz_185;1.4;1.17;1.4;1.21;1.35
|
||||
germany:JagdTiger_SdKfz_185_Snowstorm;1.4;1.33;1.4;1.21;1.35
|
||||
germany:Krupp-38D;2.6;2.22;1.7;1.95;2.12
|
||||
germany:Lowe;1.4;1.11;1.4;1.29;1.35
|
||||
germany:Nashorn;2;1.51;1.4;1.51;2.24
|
||||
germany:Nashorn_BP;2;1.51;1.4;1.5;2.24
|
||||
germany:Panther_II;1.3;1.21;1.5;1.26;1.35
|
||||
germany:Panther_M10;1.5;1.54;1.4;1.26;1.61
|
||||
germany:PanzerJager_I;5.5;4.33;2.2;4.49;8.8
|
||||
germany:Pro_Ag_A;1.1;1.02;1.4;1.1;1.21
|
||||
germany:Pz35t;4.4;4.9;2.7;3.94;6.05
|
||||
germany:Pz38_NA;2.9;2.78;1.9;2.55;3.65
|
||||
germany:Pz38t;3.6;3.88;2.1;3.05;5.58
|
||||
germany:PzII;5.5;5.28;3.3;6.67;6.81
|
||||
germany:PzIII;3.7;3.69;2.2;3.26;5.49
|
||||
germany:PzIII_A;3.7;3.09;2.1;2.98;5.58
|
||||
germany:PzIII_IV;2.7;2.22;1.7;1.95;2.12
|
||||
germany:PzII_J;3.6;5.5;2.1;2.83;5.46
|
||||
germany:PzII_Luchs;2.9;2.94;1.9;2.55;2.2
|
||||
germany:PzIV;2.7;2.22;1.7;1.95;2.13
|
||||
germany:PzIV_Hydro;2.5;2.22;1.7;1.95;2.11
|
||||
germany:PzIV_schmalturm;1.9;1.65;1.5;1.53;1.87
|
||||
germany:PzV;1.5;1.33;1.4;1.26;1.61
|
||||
germany:PzVI;1.7;1.3;1.4;1.28;1.62
|
||||
germany:PzVIB_Tiger_II;1.4;1.14;1.4;1.27;1.35
|
||||
germany:PzVI_AusfBP;2.1;1.68;1.4;1.56;1.79
|
||||
germany:PzVI_Tiger_P;1.7;1.3;1.4;1.28;1.62
|
||||
germany:PzV_PzIV;2.6;2.22;1.7;1.95;2.12
|
||||
germany:Pz_II_AusfG;3.8;2.9;2.1;2.85;5.55
|
||||
germany:Pz_IV_AusfA;3.6;3.2;2.2;4.05;5.74
|
||||
germany:Pz_IV_AusfBT;2.7;2.53;1.7;1.96;2.12
|
||||
germany:Pz_IV_AusfD;3.1;2.43;1.9;2.54;3.09
|
||||
germany:Pz_Sfl_IVc;3;1.61;1.5;1.98;2.08
|
||||
germany:RhB_Waffentrager;1.6;1.26;1.4;1.28;1.33
|
||||
germany:S01_Frankentank;1.7;1.33;1.4;1.37;1.62
|
||||
germany:S35_captured;3.8;2.85;2.1;2.93;5.52
|
||||
germany:StuGIII;3.2;1.93;1.5;1.97;2.12
|
||||
germany:Sturer_Emil;1.8;1.31;1.4;1.28;1.61
|
||||
germany:T-15;3.8;4.72;2.1;2.82;5.55
|
||||
germany:T-25;2.5;2.13;1.7;1.95;2.11
|
||||
germany:VK1602;2.8;2.9;2.3;1.96;2.11
|
||||
germany:VK2801;1.9;1.53;1.6;1.58;1.95
|
||||
germany:VK2801_BP;1.9;1.53;1.6;1.59;1.95
|
||||
germany:VK3001H;3.2;1.75;1.5;1.96;2.13
|
||||
germany:VK3001P;1.9;1.61;1.5;1.57;1.81
|
||||
germany:VK3002DB;1.5;1.58;1.4;1.28;1.61
|
||||
germany:VK3002DB_BP;1.9;1.65;1.5;1.56;1.87
|
||||
germany:VK3002DB_V1;1.9;1.65;1.5;1.56;1.84
|
||||
germany:VK3002M;1.9;1.65;1.5;1.57;1.91
|
||||
germany:VK3601H;2.1;1.64;1.4;1.57;1.78
|
||||
germany:VK4502A;1.3;1.17;1.4;1.29;1.35
|
||||
germany:VK4502P;1.2;1.02;1.4;1.18;1.13
|
||||
germany:Waffentrager_IV;1.2;1.06;1.4;1.12;1.19
|
||||
germany:Waffentrager_IV_CL;1.2;1.05;1.4;1.06;1.19
|
||||
japan:Chi_Ha;3.6;3.8;2.2;3.2;5.54
|
||||
japan:Chi_He;3;2.72;1.9;2.55;3.24
|
||||
japan:Chi_Ni;4.4;4.62;2.5;4.09;6.52
|
||||
japan:Chi_Nu;2.6;2.02;1.7;1.94;2.12
|
||||
japan:Chi_Nu_Kai;2.6;2.02;1.7;1.95;2.11
|
||||
japan:Chi_Ri;1.5;1.58;1.4;1.25;1.61
|
||||
japan:Chi_To;1.9;1.61;1.5;1.55;1.9
|
||||
japan:Firefly_GuP;1.9;1.49;1.5;1.55;2.01
|
||||
japan:G09_Hetzer_GuP;3.7;2.6;1.8;2.54;3.47
|
||||
japan:Ha_Go;5.7;5.36;3.3;5.99;8.2
|
||||
japan:IS-2_GuP;1.6;1.44;1.4;1.33;1.61
|
||||
japan:J21_Ho_Ri;1.2;1.05;1.4;1.11;1.13
|
||||
japan:J22_Ho_Ri_type96;1.5;1.16;1.4;1.25;1.35
|
||||
japan:J23_Chi_To_SPG;1.8;1.37;1.4;1.25;1.62
|
||||
japan:J24_Type_57;1.4;1.16;1.4;1.33;1.35
|
||||
japan:J26_Type_68;1.2;1.02;1.4;1.13;1.13
|
||||
japan:J27_Chi_Se;1.4;1.15;1.4;1.27;1.35
|
||||
japan:J28_Ju_To;1.7;1.41;1.4;1.32;1.62
|
||||
japan:J29_Ju_Nu;2.1;1.73;1.4;1.57;1.8
|
||||
japan:J29_Ju_Nu_BP;2.1;1.73;1.4;1.57;1.8
|
||||
japan:J30_Mitsu_108;3.2;2.37;1.5;1.97;2.13
|
||||
japan:J31_Type_5_Ka_Ri;1.5;1.16;1.4;1.26;1.35
|
||||
japan:J36_Type_63_HT;1.4;1.15;1.4;1.29;1.35
|
||||
japan:J38_Type_95_Ji_Ro;1.5;1.16;1.4;1.22;1.35
|
||||
japan:Ke_Ho;2.9;2.71;1.9;2.55;1
|
||||
japan:Ke_Ni;3.8;4.07;2.1;2.67;5.71
|
||||
japan:Ke_Ni_B;4.5;4.85;2.1;2.14;5.71
|
||||
japan:PzVI_GuP;2.1;1.64;1.4;1.54;1.81
|
||||
japan:Pz_IV_AusfH;2.6;2.22;1.7;1.95;2.12
|
||||
japan:STA_1;1.3;1.15;1.5;1.28;1.34
|
||||
japan:Type_61;1.1;1.02;1.4;1.1;1.24
|
||||
other:AMX_dracula;1.5;1.58;1.4;1.3;1.61
|
||||
other:Akawara;1.3;1.28;1.5;1.28;1.35
|
||||
other:Oth06_Sega_Lupus;1.7;1.44;1.4;1.34;1.62
|
||||
other:Oth08_WH_Vindicator;1.8;1.74;1.4;1.24;1.61
|
||||
other:Oth09_WH_Predator;1.7;1.47;1.4;1.25;1.62
|
||||
other:Oth11_T-55A;1.1;1.1;1.4;1.09;1.16
|
||||
other:Oth12_Sega_Hafen;1.6;1.66;1.4;1.27;1.61
|
||||
other:Oth14_MdM_AshKnight;1.7;1.47;1.4;1.27;1.62
|
||||
other:Oth15_RustCrab;2.6;2.22;1.7;1.96;2.12
|
||||
other:Oth16_KV_TG;1.6;1.76;1.4;1.27;1.61
|
||||
other:Oth17_BDT-5A;2.6;2.22;1.7;1.95;2.12
|
||||
other:Oth18_BDT-5B;1.9;1.85;1.5;1.55;1.94
|
||||
other:Oth19_BDT-5C;1.5;1.66;1.4;1.25;1.61
|
||||
other:Oth20_M4FL10;1.5;1.67;1.4;1.27;1.61
|
||||
other:Oth22_Sega_Vulcan;1.7;1.41;1.4;1.33;1.62
|
||||
other:Oth23_Werewolf;1.6;1.44;1.4;1.33;1.61
|
||||
other:Oth24_StuH_42HW;3.2;2.6;1.5;1.97;2.13
|
||||
other:Oth25_HWT20_FalconFire;1.7;1.47;1.4;1.25;1.62
|
||||
other:Oth26_StrvM42HW;2.7;2.22;1.7;1.96;2.11
|
||||
other:Oth27_M24_AMX;1.9;1.64;1.6;1.6;2.02
|
||||
other:Oth28_Sturmfeur_HW;1.7;1.41;1.4;1.31;1.61
|
||||
other:Oth29_A15HW;2.8;1.83;2.3;1.96;2.12
|
||||
other:Oth30_T54MS;1.3;1.2;1.5;1.3;1.35
|
||||
other:Oth31_T150BP;2.1;1.71;1.4;1.56;1.79
|
||||
other:Oth33_Blizzard_BP;2;1.68;1.6;1.56;2.21
|
||||
other:Oth34_PanzerMummy;1.7;1.39;1.4;1.3;1.61
|
||||
other:Oth35_PanzerMejai;3.2;2.27;1.5;1.95;2.13
|
||||
other:Oth36_Indien_Panzer_S1;1.5;1.56;1.4;1.28;1.61
|
||||
other:Oth37_T26_E4_SuperPershing_S1;1.3;1.25;1.5;1.3;1.35
|
||||
other:Oth38_50TP_Tyszkiewicza_S1;1.1;1.02;1.4;1.17;1.14
|
||||
other:Oth39_T20_S2;1.5;1.62;1.4;1.28;1.61
|
||||
other:Oth40_TVP_46_S2;1.3;1.15;1.5;1.28;1.35
|
||||
other:Oth41_Centurion_Mk3_S2;1.1;1.05;1.4;1.11;1.18
|
||||
other:Oth42_ForestWitch_HW23;1.7;1.42;1.4;1.32;1.61
|
||||
other:Oth44_Charioteer_T;1.6;1.17;1.4;1.27;1.35
|
||||
other:Oth45_Sandstriker;1.4;1.16;1.4;1.33;1.35
|
||||
other:Oth46_Charioteer_S3;1.8;1.37;1.4;1.27;1.61
|
||||
other:Oth47_AMX_13_90_S3;1.4;1.3;1.5;1.28;1.34
|
||||
other:Oth48_EMIL_1951E1_S3;1.1;1.02;1.4;1.15;1.14
|
||||
other:Oth49_TM_04_NT;1.5;1.63;1.4;1.34;1.61
|
||||
other:Oth50_STT;1.6;1.55;1.5;1.28;1.61
|
||||
other:Oth51_FD_24;1.3;1.18;1.5;1.28;1.35
|
||||
other:Oth52_Toxique;1.3;1.32;1.5;1.31;1.35
|
||||
other:Oth53_Rammer;1.6;1.46;1.4;1.31;1.61
|
||||
other:Oth61_Pz_VM_Set;4.1;2.58;1.6;2.54;3.71
|
||||
other:Oth62_Pz_VlD_Set;3.2;1.97;1.5;1.96;2.13
|
||||
other:Oth63_Pz_VllF_Set;2.1;1.64;1.4;1.57;1.84
|
||||
other:Oth64_Pz_VlllK_Set;1.6;1.44;1.4;1.31;1.61
|
||||
other:Oth65_PzVIllM_Tigeom;2.1;1.67;1.4;1.59;1.86
|
||||
other:Oth66_E100ANN_S4;1.7;1.47;1.4;1.33;1.62
|
||||
other:Oth67_FV215b_183ANN_S4;1.5;1.16;1.4;1.24;1.35
|
||||
other:Oth68_KV2ANN_S4;1.2;1.03;1.4;1.23;1.14
|
||||
other:Oth69_Souleater;1.5;1.17;1.4;1.26;1.35
|
||||
other:Oth70_Lightbringer;1.6;1.47;1.4;1.29;1.61
|
||||
other:Oth71_Titan_Strife;1.3;1.16;1.4;1.28;1.35
|
||||
other:Oth72_Hazard_I;1.5;1.57;1.4;1.3;1.61
|
||||
other:Oth73_Nemini;1.4;1.17;1.4;1.31;1.35
|
||||
other:Oth74_Argonaut;1.1;1.05;1.4;1.06;1.18
|
||||
other:Oth75_Braniaq_DM5;1.1;1.06;1.4;1.14;1.18
|
||||
other:Oth76_Destiny;1.2;1.02;1.4;1.17;1.15
|
||||
other:Oth77_Prowler;1.4;1.16;1.4;1.3;1.35
|
||||
other:Oth78_AC_17_P_Sterope;1.2;1.02;1.4;1.13;1.12
|
||||
other:Oth80_AC_16_Celeno;1.2;1.02;1.4;1.13;1.12
|
||||
other:Oth81_AC_15_Taygete;1.4;1.24;1.4;1.33;1.35
|
||||
other:Oth82_AC_14_Pleione;1.4;1.15;1.4;1.29;1.35
|
||||
other:Oth83_AC_13_Merope;1.7;1.4;1.4;1.32;1.61
|
||||
other:Oth84_FVE_Defender;1.1;1.12;1.4;1.16;1.15
|
||||
other:Oth85_Ghoul;1.3;1.36;1.5;1.28;1.35
|
||||
other:Oth86_Survivalist;1.1;1.06;1.4;1.1;1.12
|
||||
other:Oth92_JagdPantherII_Titan;1.4;1.17;1.4;1.24;1.35
|
||||
other:Oth93_Convoyer;1.4;1.16;1.4;1.24;1.35
|
||||
other:Sega_Edelweiss;1.5;1.62;1.4;1.28;1.61
|
||||
other:Sega_Nameless;1.7;1.41;1.4;1.29;1.62
|
||||
other:Strv_74A2;1.9;1.8;1.5;1.54;1.9
|
||||
other:Van_Helsing;1.8;1.59;1.4;1.25;1.61
|
||||
other:WZ-135G_FT;1.8;1.5;1.4;1.3;1.61
|
||||
uk:GB03_Cruiser_Mk_I;4.4;4.9;2.7;3.96;9.12
|
||||
uk:GB04_Valentine;2.9;1.85;1.9;2.53;3.37
|
||||
uk:GB05_Vickers_Medium_Mk_II;6.5;3.84;2.2;3.8;12.37
|
||||
uk:GB06_Vickers_Medium_Mk_III;4.9;3.9;1.9;2.88;5.74
|
||||
uk:GB07_Matilda;4.1;2.83;1.6;2.56;3.64
|
||||
uk:GB08_Churchill_I;3.3;1.8;1.5;1.96;2.13
|
||||
uk:GB09_Churchill_VII;2.1;1.7;1.4;1.54;1.76
|
||||
uk:GB09_Churchill_VII_BP;2.1;1.67;1.4;1.54;1.79
|
||||
uk:GB101_FV1066_Senlac;1.4;1.16;1.5;1.24;1.33
|
||||
uk:GB106_Cobra;1.1;1.05;1.4;1.14;1.16
|
||||
uk:GB108_A46;1.5;1.56;1.5;1.25;1.6
|
||||
uk:GB109_GSOR_1008;1.6;1.16;1.4;1.25;1.34
|
||||
uk:GB109_GSOR_1008_Fearless;1.6;1.18;1.4;1.24;1.34
|
||||
uk:GB10_Black_Prince;1.7;1.47;1.4;1.23;1.62
|
||||
uk:GB110_FV4201_Chieftain_Prototype;1.1;1.02;1.4;1.13;1.15
|
||||
uk:GB111_Charlemagne;1.4;1.13;1.4;1.31;1.35
|
||||
uk:GB112_Caliban;1.4;1.16;1.4;1.45;1.35
|
||||
uk:GB114_Vickers_MBT_Mk3;1.3;1.28;1.5;1.12;1.14
|
||||
uk:GB116_Harry_Hopkins;2.5;1.97;2.3;1.96;2.12
|
||||
uk:GB11_Caernarvon;1.3;1.13;1.4;1.23;1.35
|
||||
uk:GB11_Caernarvon_D;1.4;1.16;1.4;1.3;1.35
|
||||
uk:GB128_Nemesis;1.3;1.29;1.5;1.28;1.35
|
||||
uk:GB12_Conqueror;1.1;1.02;1.4;1.1;1.13
|
||||
uk:GB19_Sherman_Firefly;1.9;1.49;1.5;1.55;2.01
|
||||
uk:GB20_Crusader;2.7;1.83;2.3;1.96;2.11
|
||||
uk:GB21_A27BP;1.9;1.7;1.5;1.55;1.94
|
||||
uk:GB21_Cromwell;1.9;1.7;1.5;1.55;1.94
|
||||
uk:GB22_Comet;1.5;1.58;1.4;1.25;1.61
|
||||
uk:GB23_Centurion;1.3;1.12;1.5;1.25;1.35
|
||||
uk:GB24_Centurion_Mk3;1.1;1.02;1.4;1.14;1.15
|
||||
uk:GB32_Tortoise;1.1;1.05;1.4;1.05;1.11
|
||||
uk:GB35_Sentinel_AC_IV;1.9;1.7;1.5;1.56;1.94
|
||||
uk:GB39_Universal_CarrierQF2;5.5;4.89;2.2;3.99;11.07
|
||||
uk:GB40_Gun_Carrier_BP;2;1.56;1.4;1.52;1.77
|
||||
uk:GB40_Gun_Carrier_Churchill;2.1;1.48;1.4;1.51;1.79
|
||||
uk:GB41_Challenger;1.8;1.35;1.4;1.26;1.61
|
||||
uk:GB42_Valentine_AT;4.3;2.68;1.9;2.43;5.48
|
||||
uk:GB44_Archer_Custom;3.2;1.76;1.5;1.98;2.08
|
||||
uk:GB51_Excelsior;3.2;2.27;1.5;1.97;2.13
|
||||
uk:GB52_FV201_A45;1.7;1.47;1.4;1.24;1.61
|
||||
uk:GB57_Alecto;3.7;2.93;1.8;2.54;1
|
||||
uk:GB58_Cruiser_Mk_III;4.4;4.9;2.5;4.26;6.52
|
||||
uk:GB59_Cruiser_Mk_IV;3.7;3.82;2.2;3.05;5.52
|
||||
uk:GB60_Covenanter;3;3;1.9;2.55;2.68
|
||||
uk:GB63_TOG_II;1.9;1.53;1.4;1.54;1.8
|
||||
uk:GB63_TOG_II_BP;1.9;1.53;1.4;1.57;1.79
|
||||
uk:GB68_Matilda_Black_Prince;2.5;1.96;1.7;1.98;2.12
|
||||
uk:GB69_Cruiser_Mk_II;5.9;5.88;3.3;5.32;8.2
|
||||
uk:GB71_AT_15A;1.7;1.58;1.4;1.22;1.62
|
||||
uk:GB72_AT15;1.4;1.19;1.4;1.19;1.35
|
||||
uk:GB73_AT2;3.2;2.45;1.5;1.97;2.13
|
||||
uk:GB74_AT8;2;1.6;1.4;1.51;1.76
|
||||
uk:GB75_AT7;1.7;1.33;1.4;1.23;1.62
|
||||
uk:GB76_Mk_VIC;4.3;5.58;2.7;4.01;9.12
|
||||
uk:GB80_Charioteer;1.6;1.16;1.4;1.27;1.34
|
||||
uk:GB81_FV4004;1.2;1.05;1.4;1.11;1.2
|
||||
uk:GB85_Cromwell_Berlin;1.9;1.7;1.5;1.55;1.94
|
||||
uk:GB87_Chieftain_T95_turret;1.4;1.17;1.4;1.25;1.35
|
||||
uk:GB93_Caernarvon_AX;1.3;1.13;1.4;1.25;1.35
|
||||
uk:GB94_Centurion_Mk5-1_RAAC;1.3;1.12;1.5;1.28;1.35
|
||||
uk:GB97_Chimera;1.3;1.15;1.5;1.32;1.35
|
||||
uk:GB99_Turtle_Mk1;1.4;1.18;1.4;1.22;1.35
|
||||
uk:GB_Centurion_MkDF;1.3;1.12;1.5;1.29;1.35
|
||||
uk:GB_Churchill_MkVI;2.1;1.73;1.4;1.54;1.77
|
||||
uk:GB_Churchill_MkVIII;2.1;1.64;1.4;1.56;1.79
|
||||
uk:GB_FV301;1.4;1.07;1.5;1.25;1.35
|
||||
uk:GB_Sherman_V;2.6;2.28;1.7;1.96;2.11
|
||||
uk:GB_Vickers_Cruiser;1.1;1;1.5;1.11;1.2
|
||||
uk:Sentinel_AC1;3;2.88;1.9;2.55;3.47
|
||||
usa:A05_M4_105BD;2.7;2.4;1.7;1.96;2.12
|
||||
usa:A100_T49;1.4;1.53;1.5;1.29;1.34
|
||||
usa:A102_T28_concept;1.7;1.47;1.4;1.27;1.62
|
||||
usa:A104_M4A3E8A;1.9;1.88;1.5;1.54;1.84
|
||||
usa:A111_T25_Pilot;1.3;1.27;1.5;1.26;1.35
|
||||
usa:A115_Chrysler_K;1.4;1.16;1.4;1.3;1.35
|
||||
usa:A117_T26E5;1.4;1.15;1.4;1.27;1.35
|
||||
usa:A121_T26E3;1.5;1.54;1.4;1.31;1.61
|
||||
usa:A122_TS-5;1.4;1.15;1.4;1.23;1.35
|
||||
usa:A124_T54E2;1.4;1.15;1.4;1.29;1.35
|
||||
usa:A125_AEP_1;1.1;1.02;1.4;1.15;1.14
|
||||
usa:A130_Super_Hellcat;1.7;1.51;1.4;1.24;1.6
|
||||
usa:A132_T77;1.3;1.15;1.4;1.29;1.35
|
||||
usa:A136_T42;1.5;1.54;1.4;1.29;1.61
|
||||
usa:A140_ASTRON_REX_105;1.3;1.31;1.5;1.31;1.35
|
||||
usa:A141_M-4-Y;1.4;1.16;1.4;1.27;1.35
|
||||
usa:A142_Pawlack_Tank;2.1;1.68;1.4;1.58;1.85
|
||||
usa:A146_TL_7_120;1.1;1.02;1.4;1.14;1.14
|
||||
usa:A150_MBT_B;1.2;1.02;1.4;1.21;1.13
|
||||
usa:A151_TS_60;1.3;1.14;1.4;1.06;1.15
|
||||
usa:A175_ATAC;1.1;1.02;1.5;1.11;1.32
|
||||
usa:A68_T28D;1.4;1.2;1.4;1.28;1.35
|
||||
usa:A77_T6M;3;2.72;1.9;2.52;3.24
|
||||
usa:A94_T37;1.9;1.73;1.6;1.55;2.21
|
||||
usa:A99_T92E1;1.1;1.1;1.5;1.2;1.2
|
||||
usa:M-3-Y;1.4;1.14;1.4;1.28;1.35
|
||||
usa:M-3-Y-Midas;1.4;1.14;1.4;1.28;1.35
|
||||
usa:M-5-Y;1.1;1.02;1.4;1.2;1.14
|
||||
usa:M-7-Y;1.7;1.32;1.4;1.32;1.61
|
||||
usa:M103;1.1;1.02;1.4;1.14;1.13
|
||||
usa:M10_Wolverine;3.2;2.18;1.5;1.96;2.13
|
||||
usa:M18E2BP;1.9;1.65;1.5;1.57;2.02
|
||||
usa:M18_Hellcat;2.1;1.58;1.4;1.51;2.99
|
||||
usa:M22_Locust;3.8;4.23;2.1;3;5.59
|
||||
usa:M24_Chaffee;1.9;1.73;1.6;1.55;2.36
|
||||
usa:M24_Chaffee_BP;1.9;1.73;1.6;1.55;2.36
|
||||
usa:M2_lt;5.5;5.28;3.3;6.76;7.39
|
||||
usa:M2_med;3.6;4.29;2.2;2.93;5.54
|
||||
usa:M36_BP;2.1;1.58;1.4;1.53;1.85
|
||||
usa:M36_Slagger;2.1;1.58;1.4;1.54;1.85
|
||||
usa:M3_Grant;3;2.38;1.9;2.55;3.13
|
||||
usa:M3_Stuart;4.4;4.29;2.7;4.23;5.89
|
||||
usa:M41_Bulldog;1.6;1.49;1.5;1.25;1.61
|
||||
usa:M46_CL;1.1;1.04;1.4;1.08;1.17
|
||||
usa:M46_Patton;1.1;1.08;1.4;1.09;1.17
|
||||
usa:M4A2E4;2.6;2.28;1.7;1.96;2.13
|
||||
usa:M4A3E8_BP;1.9;1.88;1.5;1.55;1.84
|
||||
usa:M4A3E8_Sherman;1.9;1.88;1.5;1.55;1.84
|
||||
usa:M4_Sherman;2.7;1.98;1.7;1.95;2.12
|
||||
usa:M5_Stuart;3.7;3.48;2.1;2.94;5.57
|
||||
usa:M6;2.1;1.6;1.4;1.58;1.8
|
||||
usa:M6A2E1;1.6;1.32;1.4;1.33;1.62
|
||||
usa:M6A2E1_exp;1.4;1.07;1.4;1.33;1.35
|
||||
usa:M6E2BP;2.1;1.6;1.4;1.57;1.77
|
||||
usa:M6E2V2_BP;2.1;1.64;1.4;1.53;1.78
|
||||
usa:M7_med;3.1;2.38;1.9;2.54;2.98
|
||||
usa:M8A1;4;2.73;1.8;2.54;2.87
|
||||
usa:Pershing;1.3;1.33;1.5;1.25;1.35
|
||||
usa:Ram-II;2.5;2.22;1.7;1.97;2.12
|
||||
usa:Sherman_Jumbo;1.9;1.88;1.5;1.56;1.8
|
||||
usa:T14;3.2;2.35;1.5;1.97;2.13
|
||||
usa:T18;5.5;4.48;2.2;4.43;5.65
|
||||
usa:T1_E6;4.4;5;2.7;4.12;8.54
|
||||
usa:T1_hvy;3.3;2;1.5;1.96;2.13
|
||||
usa:T20;1.5;1.54;1.4;1.28;1.61
|
||||
usa:T21;1.9;1.81;1.6;1.57;2.27
|
||||
usa:T21_BP;1.9;1.81;1.6;1.56;2.24
|
||||
usa:T23E3;1.5;1.54;1.4;1.25;1.61
|
||||
usa:T25_2;1.7;1.4;1.4;1.23;1.61
|
||||
usa:T25_AT;1.7;1.44;1.4;1.26;1.61
|
||||
usa:T26_E4_SuperPershing;1.3;1.2;1.5;1.29;1.35
|
||||
usa:T28;1.4;1.16;1.4;1.23;1.35
|
||||
usa:T28_Prototype;1.4;1.16;1.4;1.24;1.35
|
||||
usa:T29;1.7;1.32;1.4;1.31;1.61
|
||||
usa:T2_lt;4.3;5.45;2.7;3.9;8.26
|
||||
usa:T2_med;4.6;5;2.5;4.23;7.11
|
||||
usa:T30;1.1;1.05;1.4;1.12;1.17
|
||||
usa:T32;1.4;1.16;1.4;1.29;1.35
|
||||
usa:T34_hvy;1.3;1.07;1.4;1.3;1.35
|
||||
usa:T34_hvy_Freedom;1.4;1.07;1.4;1.31;1.35
|
||||
usa:T40;4;2.38;1.8;2.54;3.13
|
||||
usa:T49;3.2;2.18;1.5;1.97;2.08
|
||||
usa:T49_Fearless;1.4;1.53;1.5;1.28;1.34
|
||||
usa:T54E1;1.1;1.06;1.4;1.1;1.16
|
||||
usa:T69;1.3;1.33;1.5;1.27;1.35
|
||||
usa:T71;1.6;1.49;1.5;1.27;1.6
|
||||
usa:T7_Combat_Car;4.4;5.58;2.7;4.26;8.54
|
||||
usa:T82;4.1;13.5;1.9;2.78;5.78
|
||||
usa:T95;1.2;1.05;1.4;1.11;1.12
|
||||
usa:T95E2;1.3;1.33;1.5;1.25;1.35
|
||||
ussr:A-20;3.1;2.72;1.9;2.54;3
|
||||
ussr:A-32;3;2.85;1.9;2.52;3.43
|
||||
ussr:AT-1;5.5;3.99;2.2;3.99;8.51
|
||||
ussr:BT-2;4.3;4.71;2.7;4.18;6.52
|
||||
ussr:BT-7;3.6;2.98;2.1;3.06;5.51
|
||||
ussr:BT-SV;3.8;2.86;2.1;3.16;5.58
|
||||
ussr:Churchill_LL;3.2;2.27;1.5;1.97;2.13
|
||||
ussr:GAZ-74b;4;2.48;1.8;2.53;3.05
|
||||
ussr:IS;1.6;1.44;1.4;1.32;1.62
|
||||
ussr:IS-2Sh;1.4;1.15;1.4;1.31;1.35
|
||||
ussr:IS-3;1.4;1.16;1.4;1.3;1.35
|
||||
ussr:IS8;1.2;1.02;1.4;1.12;1.15
|
||||
ussr:ISU-152;1.6;1.13;1.4;1.38;1.35
|
||||
ussr:K-91;1.1;1.02;1.4;1.1;1.12
|
||||
ussr:KV-13;1.5;1.67;1.4;1.26;1.61
|
||||
ussr:KV-1s;2.3;1.6;1.4;1.6;1.82
|
||||
ussr:KV-1s-BP;2.3;1.5;1.4;1.64;1.83
|
||||
ussr:KV-220;3.2;2.27;1.5;1.96;2.13
|
||||
ussr:KV-3;1.7;1.44;1.4;1.33;1.62
|
||||
ussr:KV-5;1.5;1.15;1.4;1.29;1.35
|
||||
ussr:KV1;3.2;2.11;1.5;1.95;2.13
|
||||
ussr:KV2;2.4;2.25;1.4;1.52;1.85
|
||||
ussr:KV4;1.4;1.13;1.4;1.3;1.35
|
||||
ussr:LTP;3.8;2.88;2.1;3.16;5.56
|
||||
ussr:M3_Stuart_LL;3.7;5.04;2.1;2.86;5.51
|
||||
ussr:MS-1obr21;4.3;4.71;2.7;4.18;6.98
|
||||
ussr:MT25;2;1.79;1.6;1.56;2.06
|
||||
ussr:Matilda_II_LL;2.5;2.22;1.7;1.96;2.12
|
||||
ussr:Object252;1.4;1.14;1.4;1.33;1.35
|
||||
ussr:Object252_Fearless;1.4;1.41;1.4;1.33;1.35
|
||||
ussr:Object_704;1.1;1.05;1.4;1.1;1.13
|
||||
ussr:R105_BT_7A;3.8;6.27;2.1;3.86;5.64
|
||||
ussr:R107_LTB;1.6;1.56;1.5;1.28;1.61
|
||||
ussr:R109_T54S;1.4;1.34;1.5;1.29;1.34
|
||||
ussr:R111_ISU130;1.5;1.16;1.4;1.23;1.35
|
||||
ussr:R112_T54_45;1.3;1.37;1.5;1.31;1.35
|
||||
ussr:R113_Object_730;1.4;1.15;1.4;1.31;1.35
|
||||
ussr:R115_IS-3_auto;1.4;1.15;1.4;1.3;1.35
|
||||
ussr:R116_ISU122C_Berlin;1.8;1.45;1.4;1.22;1.62
|
||||
ussr:R127_T44_100_P;1.3;1.33;1.5;1.27;1.35
|
||||
ussr:R131_LTG;1.6;1.61;1.5;1.28;1.61
|
||||
ussr:R134_Object_252U;1.4;1.14;1.4;1.33;1.35
|
||||
ussr:R140_M4A2_Loza;1.9;1.88;1.5;1.55;1.84
|
||||
ussr:R146_STG;1.3;1.33;1.5;1.33;1.35
|
||||
ussr:R154_T-34E-43;2.6;2.22;1.7;1.96;2.11
|
||||
ussr:R158_LT_432;1.4;1.34;1.5;1.28;1.34
|
||||
ussr:R159_SU_130PM;1.6;1.16;1.4;1.26;1.34
|
||||
ussr:R160_object_244;2.1;1.71;1.4;1.55;1.82
|
||||
ussr:R172_Object_752;1.1;1.02;1.4;1.13;1.13
|
||||
ussr:R175_IS_2E;1.6;1.47;1.4;1.31;1.61
|
||||
ussr:R180_Object_274A;1.3;1.31;1.5;1.3;1.35
|
||||
ussr:R181_Object_452K;1.2;1.02;1.4;1.15;1.13
|
||||
ussr:R187_Object_590;1.3;1.32;1.5;1.29;1.35
|
||||
ussr:R192_K_2;1.4;1.12;1.4;1.33;1.35
|
||||
ussr:R194_ST_62_var_2;1.3;1.31;1.5;1.28;1.35
|
||||
ussr:R217_Object_Kust;1.5;1.16;1.4;1.27;1.35
|
||||
ussr:R43_T-70_57;3.8;3.41;2.1;3.43;5.49
|
||||
ussr:R63_ST_IBD;1.2;1.02;1.4;1.17;1.14
|
||||
ussr:R71_IS_2B;1.6;1.44;1.4;1.33;1.61
|
||||
ussr:R98_T44_85;1.5;1.64;1.4;1.26;1.61
|
||||
ussr:R_Object_84;1.1;1.07;1.5;1.12;1.17
|
||||
ussr:ST_I;1.2;1.02;1.4;1.16;1.13
|
||||
ussr:SU-100;2.1;1.57;1.4;1.55;1.81
|
||||
ussr:SU-101;1.5;1.15;1.4;1.26;1.35
|
||||
ussr:SU-152;1.8;2;1.4;1.38;1.61
|
||||
ussr:SU-76;4.2;3.6;1.9;3.18;5.56
|
||||
ussr:SU-85;3.2;1.99;1.5;1.97;2.12
|
||||
ussr:SU100M1;1.7;1.35;1.4;1.25;1.61
|
||||
ussr:SU100Y;2.1;1.53;1.4;1.57;1.89
|
||||
ussr:SU122_44;1.8;1.45;1.4;1.25;1.61
|
||||
ussr:SU122_54;1.2;1.05;1.4;1.03;1.14
|
||||
ussr:SU76I;4.1;3.31;1.9;3.18;5.47
|
||||
ussr:SU_85I;3.1;2.3;1.5;1.98;2.12
|
||||
ussr:T-127;3.8;2.76;2.1;3.16;5.52
|
||||
ussr:T-26;5.5;5.07;3.3;6.16;8.2
|
||||
ussr:T-28;3;2.85;1.9;2.54;3.09
|
||||
ussr:T-34;2.7;2.01;1.7;1.96;2.12
|
||||
ussr:T-34-85;1.9;1.71;1.5;1.58;1.87
|
||||
ussr:T-34-85_Rudy;1.5;1.67;1.4;1.26;1.61
|
||||
ussr:T-34-85_Victory;1.9;1.88;1.5;1.57;1.87
|
||||
ussr:T-43;1.5;1.6;1.4;1.25;1.61
|
||||
ussr:T-44;1.3;1.33;1.5;1.34;1.35
|
||||
ussr:T-46;3.8;4.32;2.1;2.83;5.6
|
||||
ussr:T-54;1.1;1.1;1.4;1.09;1.16
|
||||
ussr:T150;2.1;1.55;1.4;1.59;1.82
|
||||
ussr:T34_85BP;1.9;1.88;1.5;1.57;1.87
|
||||
ussr:T34_85TUR;1.5;1.58;1.4;1.29;1.62
|
||||
ussr:Tetrarch_LL;4.3;3.88;2.7;3.88;8.51
|
||||
ussr:Valentine_LL;2.9;4.55;1.9;2.55;3.42
|
||||
|
@@ -1,13 +1,20 @@
|
||||
import { ReloadIcon } from "@radix-ui/react-icons";
|
||||
import { Card, Flex, IconButton, Text, TextField } from "@radix-ui/themes";
|
||||
import {
|
||||
Card,
|
||||
Code,
|
||||
Flex,
|
||||
IconButton,
|
||||
Text,
|
||||
TextField,
|
||||
} from "@radix-ui/themes";
|
||||
import { Html } from "@react-three/drei";
|
||||
import { useRef } from "react";
|
||||
import { radToDeg } from "three/src/math/MathUtils.js";
|
||||
import { resolveArmorIndex } from "../../../core/blitzkit/resolveArmorIndex";
|
||||
import { LocaleProvider, useLocale } from "../../../hooks/useLocale";
|
||||
import { App } from "../../../stores/app";
|
||||
import { Duel } from "../../../stores/duel";
|
||||
import { Tankopedia } from "../../../stores/tankopedia";
|
||||
import { CopyableCode } from "../../CopyableCode";
|
||||
import { layerTypeNames } from "./ShotDisplayCard";
|
||||
import { ArmorType } from "./SpacedArmorScene";
|
||||
|
||||
@@ -15,6 +22,7 @@ export function ArmorPlateDisplay() {
|
||||
const highlightArmor = Tankopedia.use((state) => state.highlightArmor);
|
||||
const developerMode = App.useDeferred((state) => state.developerMode, false);
|
||||
const input = useRef<HTMLInputElement>(null);
|
||||
const { locale } = useLocale();
|
||||
|
||||
if (highlightArmor === undefined) return null;
|
||||
|
||||
@@ -24,162 +32,164 @@ export function ArmorPlateDisplay() {
|
||||
center
|
||||
style={{ pointerEvents: highlightArmor.editingPlate ? "auto" : "none" }}
|
||||
>
|
||||
<Card
|
||||
variant="classic"
|
||||
mb="4"
|
||||
style={{
|
||||
whiteSpace: "nowrap",
|
||||
color: highlightArmor.color,
|
||||
transform: "translateY(-50%)",
|
||||
}}
|
||||
>
|
||||
<Flex direction="column">
|
||||
<Text weight="bold">
|
||||
{layerTypeNames[highlightArmor.type]}{" "}
|
||||
{highlightArmor.thickness.toFixed(0)}
|
||||
<Text size="1" weight="regular">
|
||||
mm
|
||||
</Text>
|
||||
</Text>
|
||||
|
||||
{highlightArmor.type !== ArmorType.External && (
|
||||
<Text size="2" color="gray">
|
||||
{highlightArmor.thicknessAngled.toFixed(0)}
|
||||
<Text size="1">
|
||||
mm {radToDeg(highlightArmor.angle).toFixed(0)}°
|
||||
<LocaleProvider locale={locale}>
|
||||
<Card
|
||||
variant="classic"
|
||||
mb="4"
|
||||
style={{
|
||||
whiteSpace: "nowrap",
|
||||
color: highlightArmor.color,
|
||||
transform: "translateY(-50%)",
|
||||
}}
|
||||
>
|
||||
<Flex direction="column">
|
||||
<Text weight="bold">
|
||||
{layerTypeNames[highlightArmor.type]}{" "}
|
||||
{highlightArmor.thickness.toFixed(0)}
|
||||
<Text size="1" weight="regular">
|
||||
mm
|
||||
</Text>
|
||||
</Text>
|
||||
)}
|
||||
|
||||
{developerMode && (
|
||||
<>
|
||||
<Text mt="2" color="gray" size="2">
|
||||
<b>DEV:</b>{" "}
|
||||
<CopyableCode
|
||||
copy={highlightArmor.name}
|
||||
variant="soft"
|
||||
highContrast
|
||||
color="gray"
|
||||
>
|
||||
{highlightArmor.name}
|
||||
</CopyableCode>
|
||||
{highlightArmor.type !== ArmorType.External && (
|
||||
<Text size="2" color="gray">
|
||||
{highlightArmor.thicknessAngled.toFixed(0)}
|
||||
<Text size="1">
|
||||
mm {radToDeg(highlightArmor.angle).toFixed(0)}°
|
||||
</Text>
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
)}
|
||||
|
||||
{highlightArmor.editingPlate && (
|
||||
<Flex gap="2" align="center">
|
||||
<TextField.Root
|
||||
variant="classic"
|
||||
style={{ width: 96 }}
|
||||
mt="2"
|
||||
ref={input}
|
||||
key={highlightArmor.name}
|
||||
type="number"
|
||||
autoFocus
|
||||
onFocus={(event) => event.target.select()}
|
||||
onKeyDown={(event) => {
|
||||
if (!highlightArmor.editingPlate) return;
|
||||
{developerMode && (
|
||||
<>
|
||||
<Text mt="2" color="gray" size="2">
|
||||
<b>DEV:</b>{" "}
|
||||
<Code variant="soft" highContrast color="gray">
|
||||
{highlightArmor.name}
|
||||
</Code>
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
|
||||
const index = resolveArmorIndex(highlightArmor.name);
|
||||
|
||||
if (index === undefined) return;
|
||||
|
||||
const thickness = event.currentTarget.valueAsNumber;
|
||||
|
||||
if (isNaN(thickness) || thickness < 0) return;
|
||||
|
||||
if (event.key === "Enter") {
|
||||
input.current?.blur();
|
||||
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.highlightArmor = undefined;
|
||||
const { protagonist } = Duel.state;
|
||||
const tank = draft.model;
|
||||
const turret = tank.turrets[protagonist.turret.id];
|
||||
const gun = turret.guns[protagonist.gun.id];
|
||||
const track = tank.tracks[protagonist.track.id];
|
||||
|
||||
if (highlightArmor.name.startsWith("hull_")) {
|
||||
tank.armor!.thickness[index] = thickness;
|
||||
} else if (highlightArmor.name.startsWith("turret_")) {
|
||||
turret.armor!.thickness[index] = thickness;
|
||||
} else if (highlightArmor.name.startsWith("gun_")) {
|
||||
if (highlightArmor.name.includes("_armor_")) {
|
||||
gun.armor!.thickness[index] = thickness;
|
||||
} else {
|
||||
gun.thickness = thickness;
|
||||
}
|
||||
} else if (highlightArmor.name.startsWith("chassis_")) {
|
||||
track.thickness = thickness;
|
||||
}
|
||||
});
|
||||
} else if (event.key === "Escape") {
|
||||
input.current?.blur();
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.highlightArmor = undefined;
|
||||
});
|
||||
}
|
||||
}}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}}
|
||||
defaultValue={highlightArmor.thickness.toFixed(0)}
|
||||
>
|
||||
<TextField.Slot side="right">mm</TextField.Slot>
|
||||
</TextField.Root>
|
||||
|
||||
<IconButton
|
||||
mt="2"
|
||||
color="red"
|
||||
onClick={() => {
|
||||
Tankopedia.mutate((draft) => {
|
||||
{highlightArmor.editingPlate && (
|
||||
<Flex gap="2" align="center">
|
||||
<TextField.Root
|
||||
variant="classic"
|
||||
style={{ width: 96 }}
|
||||
mt="2"
|
||||
ref={input}
|
||||
key={highlightArmor.name}
|
||||
type="number"
|
||||
autoFocus
|
||||
onFocus={(event) => event.target.select()}
|
||||
onKeyDown={(event) => {
|
||||
if (!highlightArmor.editingPlate) return;
|
||||
|
||||
const index = resolveArmorIndex(highlightArmor.name);
|
||||
|
||||
if (index === undefined) return;
|
||||
|
||||
draft.highlightArmor = undefined;
|
||||
const initialState = Tankopedia.initial;
|
||||
const { protagonist } = Duel.state;
|
||||
const tank = draft.model;
|
||||
const initialTank = initialState.model;
|
||||
const turret = tank.turrets[protagonist.turret.id];
|
||||
const initialTurret =
|
||||
initialTank.turrets[protagonist.turret.id];
|
||||
const gun = turret.guns[protagonist.gun.id];
|
||||
const initialGun = initialTurret.guns[protagonist.gun.id];
|
||||
const track = tank.tracks[protagonist.track.id];
|
||||
const initialTrack =
|
||||
initialTank.tracks[protagonist.track.id];
|
||||
const thickness = event.currentTarget.valueAsNumber;
|
||||
|
||||
if (highlightArmor.name.startsWith("hull_")) {
|
||||
tank.armor!.thickness[index] =
|
||||
initialTank.armor!.thickness[index];
|
||||
} else if (highlightArmor.name.startsWith("turret_")) {
|
||||
turret.armor!.thickness[index] =
|
||||
initialTurret.armor!.thickness[index];
|
||||
} else if (highlightArmor.name.startsWith("gun_")) {
|
||||
if (highlightArmor.name.includes("_armor_")) {
|
||||
gun.armor!.thickness[index] =
|
||||
initialGun.armor!.thickness[index];
|
||||
} else {
|
||||
gun.thickness = initialGun.thickness;
|
||||
}
|
||||
} else if (highlightArmor.name.startsWith("chassis_")) {
|
||||
track.thickness = initialTrack.thickness;
|
||||
if (isNaN(thickness) || thickness < 0) return;
|
||||
|
||||
if (event.key === "Enter") {
|
||||
input.current?.blur();
|
||||
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.highlightArmor = undefined;
|
||||
const { protagonist } = Duel.state;
|
||||
const tank = draft.model;
|
||||
const turret = tank.turrets[protagonist.turret.id];
|
||||
const gun = turret.guns[protagonist.gun.id];
|
||||
const track = tank.tracks[protagonist.track.id];
|
||||
|
||||
if (highlightArmor.name.startsWith("hull_")) {
|
||||
tank.armor!.thickness[index] = thickness;
|
||||
} else if (
|
||||
highlightArmor.name.startsWith("turret_")
|
||||
) {
|
||||
turret.armor!.thickness[index] = thickness;
|
||||
} else if (highlightArmor.name.startsWith("gun_")) {
|
||||
if (highlightArmor.name.includes("_armor_")) {
|
||||
gun.armor!.thickness[index] = thickness;
|
||||
} else {
|
||||
gun.thickness = thickness;
|
||||
}
|
||||
} else if (
|
||||
highlightArmor.name.startsWith("chassis_")
|
||||
) {
|
||||
track.thickness = thickness;
|
||||
}
|
||||
});
|
||||
} else if (event.key === "Escape") {
|
||||
input.current?.blur();
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.highlightArmor = undefined;
|
||||
});
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
<ReloadIcon />
|
||||
</IconButton>
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
</Card>
|
||||
}}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}}
|
||||
defaultValue={highlightArmor.thickness.toFixed(0)}
|
||||
>
|
||||
<TextField.Slot side="right">mm</TextField.Slot>
|
||||
</TextField.Root>
|
||||
|
||||
<IconButton
|
||||
mt="2"
|
||||
color="red"
|
||||
onClick={() => {
|
||||
Tankopedia.mutate((draft) => {
|
||||
if (!highlightArmor.editingPlate) return;
|
||||
|
||||
const index = resolveArmorIndex(highlightArmor.name);
|
||||
|
||||
if (index === undefined) return;
|
||||
|
||||
draft.highlightArmor = undefined;
|
||||
const initialState = Tankopedia.initial;
|
||||
const { protagonist } = Duel.state;
|
||||
const tank = draft.model;
|
||||
const initialTank = initialState.model;
|
||||
const turret = tank.turrets[protagonist.turret.id];
|
||||
const initialTurret =
|
||||
initialTank.turrets[protagonist.turret.id];
|
||||
const gun = turret.guns[protagonist.gun.id];
|
||||
const initialGun =
|
||||
initialTurret.guns[protagonist.gun.id];
|
||||
const track = tank.tracks[protagonist.track.id];
|
||||
const initialTrack =
|
||||
initialTank.tracks[protagonist.track.id];
|
||||
|
||||
if (highlightArmor.name.startsWith("hull_")) {
|
||||
tank.armor!.thickness[index] =
|
||||
initialTank.armor!.thickness[index];
|
||||
} else if (highlightArmor.name.startsWith("turret_")) {
|
||||
turret.armor!.thickness[index] =
|
||||
initialTurret.armor!.thickness[index];
|
||||
} else if (highlightArmor.name.startsWith("gun_")) {
|
||||
if (highlightArmor.name.includes("_armor_")) {
|
||||
gun.armor!.thickness[index] =
|
||||
initialGun.armor!.thickness[index];
|
||||
} else {
|
||||
gun.thickness = initialGun.thickness;
|
||||
}
|
||||
} else if (highlightArmor.name.startsWith("chassis_")) {
|
||||
track.thickness = initialTrack.thickness;
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
<ReloadIcon />
|
||||
</IconButton>
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
</Card>
|
||||
</LocaleProvider>
|
||||
</Html>
|
||||
</group>
|
||||
);
|
||||
|
||||
+36
-25
@@ -19,7 +19,7 @@ import {
|
||||
import { degToRad } from "three/src/math/MathUtils.js";
|
||||
import { hasEquipment } from "../../../../core/blitzkit/hasEquipment";
|
||||
import { jsxTree } from "../../../../core/blitzkit/jsxTree";
|
||||
import { Duel, type EquipmentMatrix } from "../../../../stores/duel";
|
||||
import { Duel } from "../../../../stores/duel";
|
||||
import { Tankopedia } from "../../../../stores/tankopedia";
|
||||
import { TankopediaPersistent } from "../../../../stores/tankopediaPersistent";
|
||||
import { transitionEvent } from "../../../Tankopedia/HeroSection/components/TankSandbox/components/Lighting";
|
||||
@@ -98,14 +98,8 @@ export function PrimaryArmorSceneComponent({
|
||||
material.uniforms.damage.value = shell.armor_damage;
|
||||
material.uniforms.explosionRadius.value = shell.explosion_radius;
|
||||
|
||||
handleProtagonistEquipmentChange(
|
||||
Duel.state.protagonist.equipmentMatrix,
|
||||
true,
|
||||
);
|
||||
handleAntagonistEquipmentChange(
|
||||
Duel.state.antagonist.equipmentMatrix,
|
||||
true,
|
||||
);
|
||||
handleProtagonistEquipmentChange(true);
|
||||
handleAntagonistEquipmentChange(true);
|
||||
|
||||
invalidate();
|
||||
}
|
||||
@@ -122,25 +116,25 @@ export function PrimaryArmorSceneComponent({
|
||||
function handleWireframeChange(wireframe: boolean) {
|
||||
material.wireframe = wireframe;
|
||||
}
|
||||
function handleProtagonistEquipmentChange(
|
||||
equipment: EquipmentMatrix,
|
||||
noInvalidate = false,
|
||||
) {
|
||||
function handleProtagonistEquipmentChange(noInvalidate = false) {
|
||||
const equipment = Duel.state.protagonist.equipmentMatrix;
|
||||
const hasEnhancedArmor = hasEquipment(
|
||||
110,
|
||||
Duel.state.protagonist.tank.equipment_preset,
|
||||
equipment,
|
||||
);
|
||||
material.uniforms.thickness.value = hasEnhancedArmor
|
||||
? thickness * 1.03
|
||||
: thickness;
|
||||
const equalizer =
|
||||
(Duel.state.equalize
|
||||
? Duel.state.protagonist.tank.equalizer?.armor
|
||||
: undefined) ?? 1;
|
||||
|
||||
material.uniforms.thickness.value =
|
||||
thickness * (hasEnhancedArmor ? 1.03 : 1) * equalizer;
|
||||
|
||||
if (!noInvalidate) invalidate();
|
||||
}
|
||||
function handleAntagonistEquipmentChange(
|
||||
equipment: EquipmentMatrix,
|
||||
noInvalidate = false,
|
||||
) {
|
||||
function handleAntagonistEquipmentChange(noInvalidate = false) {
|
||||
const equipment = Duel.state.antagonist.equipmentMatrix;
|
||||
const tankopediaEphemeral = Tankopedia.state;
|
||||
const shell =
|
||||
tankopediaEphemeral.customShell ?? Duel.state.antagonist.shell;
|
||||
@@ -150,10 +144,20 @@ export function PrimaryArmorSceneComponent({
|
||||
Duel.state.antagonist.tank.equipment_preset,
|
||||
equipment,
|
||||
);
|
||||
const equalize =
|
||||
(Duel.state.equalize
|
||||
? Duel.state.antagonist.tank.equalizer?.penetration
|
||||
: undefined) ?? 1;
|
||||
|
||||
material.uniforms.penetration.value =
|
||||
penetration *
|
||||
resolvePenetrationCoefficient(hasCalibratedShells, shell.type);
|
||||
resolvePenetrationCoefficient(
|
||||
hasCalibratedShells,
|
||||
Duel.state.equalize,
|
||||
shell.type,
|
||||
Duel.state.antagonist.tank.equalizer,
|
||||
) *
|
||||
equalize;
|
||||
|
||||
if (!noInvalidate) invalidate();
|
||||
}
|
||||
@@ -169,8 +173,8 @@ export function PrimaryArmorSceneComponent({
|
||||
);
|
||||
handleOpaqueChange(TankopediaPersistent.state.opaque);
|
||||
handleWireframeChange(TankopediaPersistent.state.wireframe);
|
||||
handleProtagonistEquipmentChange(Duel.state.protagonist.equipmentMatrix);
|
||||
handleAntagonistEquipmentChange(Duel.state.antagonist.equipmentMatrix);
|
||||
handleProtagonistEquipmentChange();
|
||||
handleAntagonistEquipmentChange();
|
||||
|
||||
transitionEvent.on(handleTransitionEvent);
|
||||
|
||||
@@ -188,11 +192,18 @@ export function PrimaryArmorSceneComponent({
|
||||
TankopediaPersistent.on((state) => state.opaque, handleOpaqueChange),
|
||||
Duel.on(
|
||||
(state) => state.protagonist.equipmentMatrix,
|
||||
(equipment) => handleProtagonistEquipmentChange(equipment),
|
||||
() => handleProtagonistEquipmentChange(),
|
||||
),
|
||||
Duel.on(
|
||||
(state) => state.antagonist.equipmentMatrix,
|
||||
(equipment) => handleAntagonistEquipmentChange(equipment),
|
||||
() => handleAntagonistEquipmentChange(),
|
||||
),
|
||||
Duel.on(
|
||||
(state) => state.equalize,
|
||||
() => {
|
||||
handleProtagonistEquipmentChange();
|
||||
handleAntagonistEquipmentChange();
|
||||
},
|
||||
),
|
||||
() => transitionEvent.off(handleTransitionEvent),
|
||||
];
|
||||
|
||||
@@ -52,7 +52,7 @@ export function ShotDisplay() {
|
||||
useEffect(() => {
|
||||
if (shot?.splashRadius !== undefined) {
|
||||
const outlineGeometry = new BufferGeometry().setFromPoints(
|
||||
new Path().absarc(0, 0, 1, 0, Math.PI * 2).getSpacedPoints(50)
|
||||
new Path().absarc(0, 0, 1, 0, Math.PI * 2).getSpacedPoints(50),
|
||||
);
|
||||
const outlineMaterial = new LineDashedMaterial({
|
||||
color: "orange",
|
||||
@@ -61,7 +61,7 @@ export function ShotDisplay() {
|
||||
});
|
||||
const outline = new Line(
|
||||
outlineGeometry,
|
||||
outlineMaterial
|
||||
outlineMaterial,
|
||||
).computeLineDistances();
|
||||
|
||||
splashRadiusWrapper.current?.add(outline);
|
||||
@@ -134,11 +134,11 @@ export function ShotDisplay() {
|
||||
const outLength =
|
||||
shot.out && shot.out.layers.length > 0
|
||||
? (shot.in.layers.at(-1) as ShotLayerNonExternal).point.distanceTo(
|
||||
(shot.out.layers.at(-1) as ShotLayerNonExternal).point
|
||||
(shot.out.layers.at(-1) as ShotLayerNonExternal).point,
|
||||
)
|
||||
: SHOT_DISPLAY_LENGTH_INFINITY;
|
||||
const inLast = shot.in.layers.findLast(
|
||||
(layer) => layer.type !== null
|
||||
(layer) => layer.type !== null,
|
||||
) as ShotLayerNonExternal;
|
||||
const outTitleColor = shot.out
|
||||
? shotStatusColors[shot.out.status]
|
||||
@@ -154,7 +154,7 @@ export function ShotDisplay() {
|
||||
position={inLast.point}
|
||||
ref={splashRadiusWrapper}
|
||||
rotation={new Euler().setFromQuaternion(
|
||||
new Quaternion().setFromUnitVectors(K_HAT, shot.in.surfaceNormal)
|
||||
new Quaternion().setFromUnitVectors(K_HAT, shot.in.surfaceNormal),
|
||||
)}
|
||||
>
|
||||
<mesh>
|
||||
@@ -225,7 +225,7 @@ export function ShotDisplay() {
|
||||
<group
|
||||
position={inLast.point}
|
||||
rotation={new Euler().setFromQuaternion(
|
||||
new Quaternion().setFromUnitVectors(J_HAT, shot.in.surfaceNormal)
|
||||
new Quaternion().setFromUnitVectors(J_HAT, shot.in.surfaceNormal),
|
||||
)}
|
||||
>
|
||||
<mesh position={[0, inLength / 2, 0]} renderOrder={2}>
|
||||
@@ -253,8 +253,8 @@ export function ShotDisplay() {
|
||||
rotation={new Euler().setFromQuaternion(
|
||||
new Quaternion().setFromUnitVectors(
|
||||
J_HAT,
|
||||
shot.out.surfaceNormal.clone()
|
||||
)
|
||||
shot.out.surfaceNormal.clone(),
|
||||
),
|
||||
)}
|
||||
>
|
||||
<mesh position={[0, outLength / 2, 0]} renderOrder={2}>
|
||||
|
||||
+32
-9
@@ -10,7 +10,7 @@ import {
|
||||
import type { ArmorUserData, ExternalModuleVariant } from "../..";
|
||||
import { hasEquipment } from "../../../../../../core/blitzkit/hasEquipment";
|
||||
import { jsxTree } from "../../../../../../core/blitzkit/jsxTree";
|
||||
import { Duel, type EquipmentMatrix } from "../../../../../../stores/duel";
|
||||
import { Duel } from "../../../../../../stores/duel";
|
||||
import { Tankopedia } from "../../../../../../stores/tankopedia";
|
||||
import { ArmorType } from "../../../SpacedArmorScene";
|
||||
import fragmentShader from "./shaders/fragment.glsl?raw";
|
||||
@@ -52,17 +52,23 @@ export function SpacedArmorSubExternal({
|
||||
tankopediaEphemeral.customShell ?? Duel.state.antagonist.shell;
|
||||
material.uniforms.penetration.value = shell.penetration!.near;
|
||||
}
|
||||
function handleProtagonistEquipmentChange(equipment: EquipmentMatrix) {
|
||||
function handleProtagonistEquipmentChange() {
|
||||
const equipment = Duel.state.protagonist.equipmentMatrix;
|
||||
const hasEnhancedArmor = hasEquipment(
|
||||
110,
|
||||
Duel.state.protagonist.tank.equipment_preset,
|
||||
equipment,
|
||||
);
|
||||
material.uniforms.thickness.value = hasEnhancedArmor
|
||||
? thickness * 1.03
|
||||
: thickness;
|
||||
const equalizer =
|
||||
(Duel.state.equalize
|
||||
? Duel.state.protagonist.tank.equalizer?.armor
|
||||
: undefined) ?? 1;
|
||||
|
||||
material.uniforms.thickness.value =
|
||||
thickness * (hasEnhancedArmor ? 1.03 : 1) * equalizer;
|
||||
}
|
||||
function handleAntagonistEquipmentChange(equipment: EquipmentMatrix) {
|
||||
function handleAntagonistEquipmentChange() {
|
||||
const equipment = Duel.state.antagonist.equipmentMatrix;
|
||||
const tankopediaEphemeral = Tankopedia.state;
|
||||
const shell =
|
||||
tankopediaEphemeral.customShell ?? Duel.state.antagonist.shell;
|
||||
@@ -72,15 +78,25 @@ export function SpacedArmorSubExternal({
|
||||
Duel.state.antagonist.tank.equipment_preset,
|
||||
equipment,
|
||||
);
|
||||
const equalize =
|
||||
(Duel.state.equalize
|
||||
? Duel.state.antagonist.tank.equalizer?.penetration
|
||||
: undefined) ?? 1;
|
||||
|
||||
material.uniforms.penetration.value =
|
||||
penetration *
|
||||
resolvePenetrationCoefficient(hasCalibratedShells, shell.type);
|
||||
resolvePenetrationCoefficient(
|
||||
hasCalibratedShells,
|
||||
Duel.state.equalize,
|
||||
shell.type,
|
||||
Duel.state.antagonist.tank.equalizer,
|
||||
) *
|
||||
equalize;
|
||||
}
|
||||
|
||||
handleShellChange();
|
||||
handleProtagonistEquipmentChange(Duel.state.protagonist.equipmentMatrix);
|
||||
handleAntagonistEquipmentChange(Duel.state.antagonist.equipmentMatrix);
|
||||
handleProtagonistEquipmentChange();
|
||||
handleAntagonistEquipmentChange();
|
||||
|
||||
const unsubscribes = [
|
||||
Duel.on((state) => state.antagonist.shell, handleShellChange),
|
||||
@@ -93,6 +109,13 @@ export function SpacedArmorSubExternal({
|
||||
(state) => state.antagonist.equipmentMatrix,
|
||||
handleAntagonistEquipmentChange,
|
||||
),
|
||||
Duel.on(
|
||||
(state) => state.equalize,
|
||||
() => {
|
||||
handleProtagonistEquipmentChange();
|
||||
handleAntagonistEquipmentChange();
|
||||
},
|
||||
),
|
||||
];
|
||||
|
||||
return () => {
|
||||
|
||||
+32
-9
@@ -10,7 +10,7 @@ import { degToRad } from "three/src/math/MathUtils.js";
|
||||
import type { ArmorUserData } from "../..";
|
||||
import { hasEquipment } from "../../../../../../core/blitzkit/hasEquipment";
|
||||
import { jsxTree } from "../../../../../../core/blitzkit/jsxTree";
|
||||
import { Duel, type EquipmentMatrix } from "../../../../../../stores/duel";
|
||||
import { Duel } from "../../../../../../stores/duel";
|
||||
import { Tankopedia } from "../../../../../../stores/tankopedia";
|
||||
import { ArmorType } from "../../../SpacedArmorScene";
|
||||
import fragmentShader from "./shaders/fragment.glsl?raw";
|
||||
@@ -62,17 +62,23 @@ export function SpacedArmorSubSpaced({
|
||||
shell.normalization ?? 0,
|
||||
);
|
||||
}
|
||||
function handleProtagonistEquipmentChange(equipment: EquipmentMatrix) {
|
||||
function handleProtagonistEquipmentChange() {
|
||||
const equipment = Duel.state.protagonist.equipmentMatrix;
|
||||
const hasEnhancedArmor = hasEquipment(
|
||||
110,
|
||||
Duel.state.protagonist.tank.equipment_preset,
|
||||
equipment,
|
||||
);
|
||||
material.uniforms.thickness.value = hasEnhancedArmor
|
||||
? thickness * 1.03
|
||||
: thickness;
|
||||
const equalizer =
|
||||
(Duel.state.equalize
|
||||
? Duel.state.protagonist.tank.equalizer?.armor
|
||||
: undefined) ?? 1;
|
||||
|
||||
material.uniforms.thickness.value =
|
||||
thickness * (hasEnhancedArmor ? 1.03 : 1) * equalizer;
|
||||
}
|
||||
function handleAntagonistEquipmentChange(equipment: EquipmentMatrix) {
|
||||
function handleAntagonistEquipmentChange() {
|
||||
const equipment = Duel.state.antagonist.equipmentMatrix;
|
||||
const tankopediaEphemeral = Tankopedia.state;
|
||||
const shell =
|
||||
tankopediaEphemeral.customShell ?? Duel.state.antagonist.shell;
|
||||
@@ -82,15 +88,25 @@ export function SpacedArmorSubSpaced({
|
||||
Duel.state.antagonist.tank.equipment_preset,
|
||||
equipment,
|
||||
);
|
||||
const equalize =
|
||||
(Duel.state.equalize
|
||||
? Duel.state.antagonist.tank.equalizer?.penetration
|
||||
: undefined) ?? 1;
|
||||
|
||||
material.uniforms.penetration.value =
|
||||
penetration *
|
||||
resolvePenetrationCoefficient(hasCalibratedShells, shell.type);
|
||||
resolvePenetrationCoefficient(
|
||||
hasCalibratedShells,
|
||||
Duel.state.equalize,
|
||||
shell.type,
|
||||
Duel.state.antagonist.tank.equalizer,
|
||||
) *
|
||||
equalize;
|
||||
}
|
||||
|
||||
handleShellChange();
|
||||
handleProtagonistEquipmentChange(Duel.state.protagonist.equipmentMatrix);
|
||||
handleAntagonistEquipmentChange(Duel.state.antagonist.equipmentMatrix);
|
||||
handleProtagonistEquipmentChange();
|
||||
handleAntagonistEquipmentChange();
|
||||
|
||||
const unsubscribes = [
|
||||
Duel.on((state) => state.antagonist.shell, handleShellChange),
|
||||
@@ -103,6 +119,13 @@ export function SpacedArmorSubSpaced({
|
||||
(state) => state.antagonist.equipmentMatrix,
|
||||
handleAntagonistEquipmentChange,
|
||||
),
|
||||
Duel.on(
|
||||
(state) => state.equalize,
|
||||
() => {
|
||||
handleProtagonistEquipmentChange();
|
||||
handleAntagonistEquipmentChange();
|
||||
},
|
||||
),
|
||||
];
|
||||
|
||||
return () => {
|
||||
|
||||
+18
-2
@@ -18,6 +18,7 @@ import {
|
||||
import { degToRad } from "three/src/math/MathUtils.js";
|
||||
import { hasEquipment } from "../../../../core/blitzkit/hasEquipment";
|
||||
import { jsxTree } from "../../../../core/blitzkit/jsxTree";
|
||||
import { defaultEqualizer } from "../../../../core/blitzkit/tankToDuelMember";
|
||||
import { discardClippingPlane } from "../../../../core/three/discardClippingPlane";
|
||||
import { Duel } from "../../../../stores/duel";
|
||||
import {
|
||||
@@ -87,6 +88,13 @@ export function SpacedArmorSceneComponent({
|
||||
) => {
|
||||
const { customShell } = Tankopedia.state;
|
||||
const shell = customShell ?? Duel.state.antagonist.shell;
|
||||
const { equalize } = Duel.state;
|
||||
const protagonistEqualizer =
|
||||
(equalize ? Duel.state.protagonist.tank.equalizer : undefined) ??
|
||||
defaultEqualizer;
|
||||
const antagonistEqualizer =
|
||||
(equalize ? Duel.state.antagonist.tank.equalizer : undefined) ??
|
||||
defaultEqualizer;
|
||||
const cameraNormal = camera.position.clone().sub(point).normalize();
|
||||
const shot: Shot = {
|
||||
splashRadius:
|
||||
@@ -114,8 +122,14 @@ export function SpacedArmorSceneComponent({
|
||||
);
|
||||
const penetration =
|
||||
shell.penetration!.near *
|
||||
resolvePenetrationCoefficient(hasCalibratedShells, shell.type);
|
||||
const thicknessCoefficient = hasEnhancedArmor ? 1.03 : 1;
|
||||
resolvePenetrationCoefficient(
|
||||
hasCalibratedShells,
|
||||
equalize,
|
||||
shell.type,
|
||||
antagonistEqualizer,
|
||||
);
|
||||
const thicknessCoefficient =
|
||||
(hasEnhancedArmor ? 1.03 : 1) * protagonistEqualizer.armor;
|
||||
const filteredIntersections = intersections.filter(
|
||||
(intersection) =>
|
||||
"type" in intersection.object.userData &&
|
||||
@@ -367,6 +381,8 @@ export function SpacedArmorSceneComponent({
|
||||
}
|
||||
}
|
||||
|
||||
shot.damage *= antagonistEqualizer.damage;
|
||||
|
||||
return shot;
|
||||
},
|
||||
[camera],
|
||||
|
||||
@@ -22,7 +22,10 @@ import {
|
||||
modelTransformEvent,
|
||||
type ModelTransformEventData,
|
||||
} from "../../../core/blitzkit/modelTransform";
|
||||
import { defaultEqualizer } from "../../../core/blitzkit/tankToDuelMember";
|
||||
import { discardClippingPlane } from "../../../core/three/discardClippingPlane";
|
||||
import { useEquipment } from "../../../hooks/useEquipment";
|
||||
import { Duel } from "../../../stores/duel";
|
||||
import { Tankopedia } from "../../../stores/tankopedia";
|
||||
import { transitionEvent } from "../../Tankopedia/HeroSection/components/TankSandbox/components/Lighting";
|
||||
import { ArmorType } from "./SpacedArmorScene";
|
||||
@@ -64,46 +67,68 @@ const externalModuleColor = new Color(192 / 255, 192 / 255, 192 / 255);
|
||||
|
||||
export function StaticArmorSceneComponent({
|
||||
name,
|
||||
thickness,
|
||||
thickness: thicknessRaw,
|
||||
thicknessRange,
|
||||
node,
|
||||
onPointerDown,
|
||||
...props
|
||||
}: StaticArmorSceneComponentProps) {
|
||||
const camera = useThree((state) => state.camera);
|
||||
const hasEnhancedArmor = useEquipment(110);
|
||||
const tank = Duel.use((state) => state.protagonist.tank);
|
||||
const equalize = Duel.use((state) => state.equalize);
|
||||
const equalizer = (equalize ? tank.equalizer : undefined) ?? defaultEqualizer;
|
||||
const thicknessCoefficient = (hasEnhancedArmor ? 1.03 : 1) * equalizer.armor;
|
||||
const thickness = thicknessRaw * thicknessCoefficient;
|
||||
const x = thickness / thicknessRange.value;
|
||||
const xClamped = clamp(x, 0, 1);
|
||||
let color: Color;
|
||||
let opacity: number;
|
||||
let depthWrite = true;
|
||||
|
||||
switch (props.type) {
|
||||
case ArmorType.Primary:
|
||||
if (x > 1) {
|
||||
color = new Color(clamp(2 - x, 0.5, 1), 0, 0);
|
||||
} else {
|
||||
color = new Color(-((1 - x) ** 2) + 1, -(x ** 2) + 1, 0);
|
||||
const color = useMemo(() => {
|
||||
switch (props.type) {
|
||||
case ArmorType.Primary: {
|
||||
if (x > 1) {
|
||||
return new Color(clamp(2 - x, 0.5, 1), 0, 0);
|
||||
} else {
|
||||
return new Color(-((1 - x) ** 2) + 1, -(x ** 2) + 1, 0);
|
||||
}
|
||||
}
|
||||
opacity = 1;
|
||||
break;
|
||||
|
||||
case ArmorType.Spaced:
|
||||
color = new Color(
|
||||
clamp(1 - (7 / 8) * xClamped, 0, 1),
|
||||
0,
|
||||
clamp(1 - (1 / 8) * xClamped, 0, 1),
|
||||
);
|
||||
opacity = clamp(x + 1 / 2, 0, 1);
|
||||
break;
|
||||
case ArmorType.Spaced: {
|
||||
return new Color(
|
||||
clamp(1 - (7 / 8) * xClamped, 0, 1),
|
||||
0,
|
||||
clamp(1 - (1 / 8) * xClamped, 0, 1),
|
||||
);
|
||||
}
|
||||
|
||||
case ArmorType.External:
|
||||
color = externalModuleColor;
|
||||
opacity = 1 / 8;
|
||||
depthWrite = false;
|
||||
break;
|
||||
}
|
||||
case ArmorType.External: {
|
||||
return externalModuleColor;
|
||||
}
|
||||
}
|
||||
}, [props.type, xClamped]);
|
||||
const opacity = useMemo(() => {
|
||||
switch (props.type) {
|
||||
case ArmorType.Primary:
|
||||
return 1;
|
||||
|
||||
opacity = clamp(opacity, 0, 1);
|
||||
case ArmorType.Spaced:
|
||||
return clamp(x + 1 / 2, 0, 1);
|
||||
|
||||
case ArmorType.External:
|
||||
return 1 / 8;
|
||||
}
|
||||
}, [props.type, x]);
|
||||
const depthWrite = useMemo(() => {
|
||||
switch (props.type) {
|
||||
case ArmorType.Primary:
|
||||
return true;
|
||||
|
||||
case ArmorType.Spaced:
|
||||
return true;
|
||||
|
||||
case ArmorType.External:
|
||||
return false;
|
||||
}
|
||||
}, [props.type]);
|
||||
|
||||
const surfaceMaterial = useMemo(
|
||||
() =>
|
||||
@@ -115,7 +140,7 @@ export function StaticArmorSceneComponent({
|
||||
opacity: 0,
|
||||
userData: { opacity0: opacity },
|
||||
}),
|
||||
[thickness],
|
||||
[hasEnhancedArmor, equalize],
|
||||
);
|
||||
const outlineMaterial = useMemo(
|
||||
() =>
|
||||
@@ -126,7 +151,7 @@ export function StaticArmorSceneComponent({
|
||||
opacity: 0,
|
||||
transparent: true,
|
||||
}),
|
||||
[thickness],
|
||||
[hasEnhancedArmor, equalize],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -229,7 +254,7 @@ export function StaticArmorSceneComponent({
|
||||
return () => {
|
||||
unsubscribes.forEach((unsubscribe) => unsubscribe());
|
||||
};
|
||||
}, [thickness]);
|
||||
}, [hasEnhancedArmor, equalize]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
import { asset, availableProvisions, permanentSkills } from "@blitzkit/core";
|
||||
import { checkConsumableProvisionInclusivity } from "@blitzkit/core/src/blitzkit/checkConsumableProvisionInclusivity";
|
||||
import { ComponentPlaceholderIcon } from "@radix-ui/react-icons";
|
||||
import { Button, Flex, IconButton, Popover, Table } from "@radix-ui/themes";
|
||||
import {
|
||||
Button,
|
||||
Flex,
|
||||
IconButton,
|
||||
Popover,
|
||||
Switch,
|
||||
Table,
|
||||
Text,
|
||||
} from "@radix-ui/themes";
|
||||
import { times } from "lodash-es";
|
||||
import { awaitableConsumableDefinitions } from "../../core/awaitables/consumableDefinitions";
|
||||
import { awaitableEquipmentDefinitions } from "../../core/awaitables/equipmentDefinitions";
|
||||
@@ -44,6 +52,7 @@ export function CompareTable({ stats }: CompareTableProps) {
|
||||
const crewSkills = CompareEphemeral.use((state) => state.crewSkills);
|
||||
const members = CompareEphemeral.use((state) => state.members);
|
||||
const { unwrap, strings } = useLocale();
|
||||
const equalize = CompareEphemeral.use((state) => state.equalize);
|
||||
|
||||
return (
|
||||
<StickyTableRoot
|
||||
@@ -80,10 +89,12 @@ export function CompareTable({ stats }: CompareTableProps) {
|
||||
<Table.Row>
|
||||
<Table.Cell style={{ height: "100%" }}>
|
||||
<Flex
|
||||
gap="2"
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
}}
|
||||
direction="column"
|
||||
align="center"
|
||||
justify="center"
|
||||
>
|
||||
@@ -155,6 +166,21 @@ export function CompareTable({ stats }: CompareTableProps) {
|
||||
</Flex>
|
||||
</Popover.Content>
|
||||
</Popover.Root>
|
||||
|
||||
<Flex align="center" gap="1">
|
||||
<Text>
|
||||
{strings.website.tools.compare.table.skills.equalize}
|
||||
</Text>
|
||||
<Switch
|
||||
size="1"
|
||||
checked={equalize}
|
||||
onCheckedChange={(checked) => {
|
||||
CompareEphemeral.mutate((draft) => {
|
||||
draft.equalize = checked;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Table.Cell>
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import type { ComponentProps } from "react";
|
||||
|
||||
export function EqualIcon(props: ComponentProps<"svg">) {
|
||||
return (
|
||||
<svg
|
||||
width="15"
|
||||
height="15"
|
||||
viewBox="0 0 15 15"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M1.5 5C1.22386 5 1 5.22386 1 5.5C1 5.77614 1.22386 6 1.5 6H13.5C13.7761 6 14 5.77614 14 5.5C14 5.22386 13.7761 5 13.5 5H1.5ZM1 9.5C1 9.22386 1.22386 9 1.5 9H13.5C13.7761 9 14 9.22386 14 9.5C14 9.77614 13.7761 10 13.5 10H1.5C1.22386 10 1 9.77614 1 9.5Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
+8
-6
@@ -59,11 +59,12 @@ export const InfoWithDelta = memo<InfoWithDeltaProps>(
|
||||
const delta = useDelta(uhWhatDoICallThisVariable);
|
||||
const protagonistTank = Duel.use((state) => state.protagonist.tank);
|
||||
const shellIndex = Duel.use((state) =>
|
||||
state.protagonist.gun.shells.indexOf(state.protagonist.shell)
|
||||
state.protagonist.gun.shells.indexOf(state.protagonist.shell),
|
||||
);
|
||||
const equipmentMatrix = Duel.use(
|
||||
(state) => state.protagonist.equipmentMatrix
|
||||
(state) => state.protagonist.equipmentMatrix,
|
||||
);
|
||||
const equalize = Duel.use((state) => state.equalize);
|
||||
const others = useMemo(() => {
|
||||
const defaultSkills = createDefaultSkills(skillDefinitions);
|
||||
|
||||
@@ -75,7 +76,7 @@ export const InfoWithDelta = memo<InfoWithDeltaProps>(
|
||||
tank.class === protagonistTank.class) ||
|
||||
(relativeAgainst === TankopediaRelativeAgainst.Tier &&
|
||||
tank.tier === protagonistTank.tier) ||
|
||||
relativeAgainst === TankopediaRelativeAgainst.All
|
||||
relativeAgainst === TankopediaRelativeAgainst.All,
|
||||
)
|
||||
.map((tank) => {
|
||||
const member = tankToDuelMember(tank, provisionDefinitions);
|
||||
@@ -105,12 +106,13 @@ export const InfoWithDelta = memo<InfoWithDeltaProps>(
|
||||
track: member.track,
|
||||
turret: member.turret,
|
||||
assaultDistance: member.assaultDistance,
|
||||
equalize,
|
||||
},
|
||||
{
|
||||
equipmentDefinitions,
|
||||
provisionDefinitions,
|
||||
tankModelDefinition: modelDefinitions.models[tank.id],
|
||||
}
|
||||
},
|
||||
);
|
||||
})
|
||||
.filter((tank) => {
|
||||
@@ -123,7 +125,7 @@ export const InfoWithDelta = memo<InfoWithDeltaProps>(
|
||||
|
||||
return othersValue !== undefined;
|
||||
}) as TankCharacteristics[];
|
||||
}, [relativeAgainst, shellIndex, equipmentMatrix]);
|
||||
}, [relativeAgainst, shellIndex, equipmentMatrix, equalize]);
|
||||
const betterTanks = others.filter((tank) => {
|
||||
const othersValue =
|
||||
typeof props.value === "function"
|
||||
@@ -183,5 +185,5 @@ export const InfoWithDelta = memo<InfoWithDeltaProps>(
|
||||
|
||||
(a, b) =>
|
||||
(typeof a.value === "function" ? a.value(a.stats) : a.stats[a.value]) ===
|
||||
(typeof b.value === "function" ? b.value(b.stats) : b.stats[b.value])
|
||||
(typeof b.value === "function" ? b.value(b.stats) : b.stats[b.value]),
|
||||
);
|
||||
+2
@@ -31,6 +31,7 @@ export function Characteristics({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
const equipmentMatrix = Duel.use(
|
||||
(state) => state.protagonist.equipmentMatrix,
|
||||
);
|
||||
const equalize = Duel.use((state) => state.equalize);
|
||||
const { tank, turret, gun, engine, track, shell } = Duel.use(
|
||||
(state) => state.protagonist,
|
||||
);
|
||||
@@ -62,6 +63,7 @@ export function Characteristics({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
applyDynamicArmor: false,
|
||||
applySpallLiner: false,
|
||||
assaultDistance,
|
||||
equalize,
|
||||
},
|
||||
{
|
||||
tankModelDefinition,
|
||||
|
||||
+13
-3
@@ -1,6 +1,7 @@
|
||||
import { asset } from "@blitzkit/core";
|
||||
import { InfoCircledIcon } from "@radix-ui/react-icons";
|
||||
import {
|
||||
Badge,
|
||||
Box,
|
||||
Flex,
|
||||
Heading,
|
||||
@@ -11,23 +12,32 @@ import {
|
||||
} from "@radix-ui/themes";
|
||||
import { useLocale } from "../../../../hooks/useLocale";
|
||||
import { Duel } from "../../../../stores/duel";
|
||||
import { Tankopedia } from "../../../../stores/tankopedia";
|
||||
import { ConfigurationChildWrapper } from "./ConfigurationChildWrapper";
|
||||
|
||||
export function Equalizer() {
|
||||
const { strings } = useLocale();
|
||||
const equalize = Duel.use((state) => state.protagonist.equalize);
|
||||
const equalize = Duel.use((state) => state.equalize);
|
||||
|
||||
return (
|
||||
<ConfigurationChildWrapper>
|
||||
<Heading size="4">
|
||||
{strings.website.tools.tankopedia.configuration.equalizer.title}
|
||||
<Flex gap="2" align="center">
|
||||
{strings.website.tools.tankopedia.configuration.equalizer.title}{" "}
|
||||
<Badge variant="outline">
|
||||
{strings.website.tools.tankopedia.configuration.equalizer.beta}
|
||||
</Badge>
|
||||
</Flex>
|
||||
</Heading>
|
||||
|
||||
<Box
|
||||
mt="2"
|
||||
onClick={() => {
|
||||
Duel.mutate((draft) => {
|
||||
draft.protagonist.equalize = !draft.protagonist.equalize;
|
||||
draft.equalize = !draft.equalize;
|
||||
});
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.shot = undefined;
|
||||
});
|
||||
}}
|
||||
width="100%"
|
||||
|
||||
+21
-1
@@ -38,6 +38,7 @@ import { TankopediaPersistent } from "../../../../../stores/tankopediaPersistent
|
||||
import { TankopediaDisplay } from "../../../../../stores/tankopediaPersistent/constants";
|
||||
import type { MaybeSkeletonComponentProps } from "../../../../../types/maybeSkeletonComponentProps";
|
||||
import type { ThicknessRange } from "../../../../Armor/components/StaticArmor";
|
||||
import { EqualIcon } from "../../../../EqualIcon";
|
||||
import { ModuleButton } from "../../../../ModuleButtons/ModuleButton";
|
||||
import { ScreenshotButton } from "../../../../ScreenshotButton";
|
||||
import { SmallTankIcon } from "../../../../SmallTankIcon";
|
||||
@@ -75,6 +76,8 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
|
||||
const revealed = Tankopedia.use((state) => state.revealed);
|
||||
const disturbed = Tankopedia.use((state) => state.disturbed);
|
||||
const highGraphics = TankopediaPersistent.use((state) => state.highGraphics);
|
||||
const equalize = Duel.use((state) => state.equalize);
|
||||
const equalizer = Duel.use((state) => state.antagonist.tank.equalizer);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -102,7 +105,9 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
|
||||
value: (
|
||||
resolvePenetrationCoefficient(
|
||||
hasCalibratedShells,
|
||||
equalize,
|
||||
antagonistShell.type,
|
||||
equalizer,
|
||||
) * antagonistShell.penetration!.near
|
||||
).toFixed(0),
|
||||
})}
|
||||
@@ -212,7 +217,6 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
|
||||
/>
|
||||
</IconButton>
|
||||
))}
|
||||
|
||||
<CustomShellButton />
|
||||
</Flex>
|
||||
|
||||
@@ -281,6 +285,22 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
|
||||
<DynamicArmorSwitcher />
|
||||
</Suspense>
|
||||
)}
|
||||
|
||||
<IconButton
|
||||
variant="soft"
|
||||
size={{ initial: "2", sm: "3" }}
|
||||
color={equalize ? undefined : "gray"}
|
||||
onClick={() => {
|
||||
Duel.mutate((draft) => {
|
||||
draft.equalize = !draft.equalize;
|
||||
});
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.shot = undefined;
|
||||
});
|
||||
}}
|
||||
>
|
||||
<EqualIcon />
|
||||
</IconButton>
|
||||
</Flex>
|
||||
|
||||
<Flex
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Box, Flex } from "@radix-ui/themes";
|
||||
import { times } from "lodash-es";
|
||||
import { useEffect, useMemo, useRef } from "react";
|
||||
import { awaitableTankDefinitions } from "../../../core/awaitables/tankDefinitions";
|
||||
import { defaultEqualizer } from "../../../core/blitzkit/tankToDuelMember";
|
||||
import { useFullScreen } from "../../../hooks/useFullScreen";
|
||||
import { Duel } from "../../../stores/duel";
|
||||
import { Tankopedia } from "../../../stores/tankopedia";
|
||||
@@ -14,7 +15,7 @@ import { Title } from "./components/TankSandbox/Title";
|
||||
const tankDefinitions = await awaitableTankDefinitions;
|
||||
|
||||
export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
const revealed = Tankopedia.use((state) => state.revealed);
|
||||
const equalize = Duel.use((state) => state.equalize);
|
||||
const disturbed = Tankopedia.use((state) => state.disturbed);
|
||||
const canvas = useRef<HTMLCanvasElement>(null!);
|
||||
const isFullScreen = useFullScreen();
|
||||
@@ -28,14 +29,16 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
(filtered.reduce((accumulator, thisTank) => {
|
||||
return (
|
||||
accumulator +
|
||||
thisTank.turrets.at(-1)!.guns.at(-1)!.shells[0].penetration!.near
|
||||
thisTank.turrets.at(-1)!.guns.at(-1)!.shells[0].penetration!.near *
|
||||
((equalize ? thisTank.equalizer : undefined) ?? defaultEqualizer)
|
||||
.penetration
|
||||
);
|
||||
}, 0) /
|
||||
filtered.length) *
|
||||
(3 / 4);
|
||||
|
||||
return { value } satisfies ThicknessRange;
|
||||
}, [protagonist]);
|
||||
}, [protagonist, equalize]);
|
||||
|
||||
useEffect(() => {
|
||||
if (disturbed) {
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
} from "@blitzkit/core";
|
||||
import { coefficient } from "@blitzkit/core/src/blitzkit/coefficient";
|
||||
import type { EquipmentMatrix } from "../../stores/duel";
|
||||
import { defaultEqualizer } from "./tankToDuelMember";
|
||||
|
||||
export type TankCharacteristics = ReturnType<typeof tankCharacteristics>;
|
||||
export type TankCharacteristicsKey = keyof TankCharacteristics;
|
||||
@@ -47,6 +48,7 @@ export function tankCharacteristics(
|
||||
applyDynamicArmor,
|
||||
applySpallLiner,
|
||||
assaultDistance,
|
||||
equalize,
|
||||
}: {
|
||||
tank: TankDefinition;
|
||||
turret: TurretDefinition;
|
||||
@@ -67,6 +69,7 @@ export function tankCharacteristics(
|
||||
applyDynamicArmor: boolean;
|
||||
applySpallLiner: boolean;
|
||||
assaultDistance: number;
|
||||
equalize: boolean;
|
||||
},
|
||||
{
|
||||
tankModelDefinition,
|
||||
@@ -81,6 +84,7 @@ export function tankCharacteristics(
|
||||
const preset = equipmentDefinitions.presets[tank.equipment_preset];
|
||||
const turretModelDefinition = tankModelDefinition.turrets[turret.id];
|
||||
const gunModelDefinition = turretModelDefinition.guns[gun.id];
|
||||
const equalizer = (equalize ? tank.equalizer : undefined) ?? defaultEqualizer;
|
||||
|
||||
function equipment(id: number) {
|
||||
return preset.slots.some((slot, index) => {
|
||||
@@ -179,13 +183,14 @@ export function tankCharacteristics(
|
||||
coefficient([hasAdrenaline && gun.gun_type!.$case === "regular", 0.17]);
|
||||
const penetrationCoefficient = coefficient([
|
||||
hasCalibratedShells,
|
||||
resolvePenetrationCoefficient(true, shell.type) - 1,
|
||||
resolvePenetrationCoefficient(true, equalize, shell.type, equalizer) - 1,
|
||||
]);
|
||||
const healthCoefficient = coefficient(
|
||||
[hasSandbagArmor, 0.03],
|
||||
[hasEnhancedSandbagArmor, 0.06],
|
||||
[hasImprovedAssembly, 0.04],
|
||||
);
|
||||
const healthCoefficient =
|
||||
coefficient(
|
||||
[hasSandbagArmor, 0.03],
|
||||
[hasEnhancedSandbagArmor, 0.06],
|
||||
[hasImprovedAssembly, 0.04],
|
||||
) * equalizer.health;
|
||||
const shellVelocityCoefficient = coefficient(
|
||||
[hasSupercharger, 0.35],
|
||||
[hasImprovedGunPowder, 0.3],
|
||||
@@ -343,7 +348,8 @@ export function tankCharacteristics(
|
||||
stockTurret.weight +
|
||||
stockGun.weight;
|
||||
const resolvedEnginePower = engine.power * enginePowerCoefficient;
|
||||
const damageCoefficientWithoutAssault = armorDamageCoefficient;
|
||||
const damageCoefficientWithoutAssault =
|
||||
armorDamageCoefficient * equalizer.damage;
|
||||
const damageCoefficient =
|
||||
damageCoefficientWithoutAssault * assaultDamageCoefficient;
|
||||
const dpm = resolveDpm(
|
||||
@@ -527,7 +533,7 @@ export function tankCharacteristics(
|
||||
const shellRange = shell.range;
|
||||
const shellCapacity = gun.shell_capacity;
|
||||
const gunType = gun.gun_type!.$case;
|
||||
const penetrationAt250m = shell.penetration!.far;
|
||||
const penetrationAt250m = shell.penetration!.far * penetrationCoefficient;
|
||||
|
||||
return {
|
||||
crewCount,
|
||||
@@ -654,13 +660,3 @@ export const characteristicsOrder: {
|
||||
items: ["crewCount"],
|
||||
},
|
||||
];
|
||||
|
||||
// export const characteristicsStyling: Record<
|
||||
// TankCharacteristicsKey,
|
||||
// {
|
||||
// name: string;
|
||||
// indent?: boolean;
|
||||
// condition?: TankCharacteristicsCondition[];
|
||||
// }
|
||||
// > = {
|
||||
// };
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
createDefaultProvisions,
|
||||
Equalizer,
|
||||
type ProvisionDefinitions,
|
||||
type TankDefinition,
|
||||
} from "@blitzkit/core";
|
||||
@@ -26,6 +27,13 @@ export function tankToDuelMember(
|
||||
track: tank.tracks.at(-1)!,
|
||||
turret,
|
||||
assaultDistance: (gun.assault_ranges?.ranges[0].distance ?? 0) / 2,
|
||||
equalize: false,
|
||||
} satisfies DuelMember;
|
||||
}
|
||||
|
||||
export const defaultEqualizer: Equalizer = {
|
||||
armor: 1,
|
||||
damage: 1,
|
||||
health: 1,
|
||||
module_health: 1,
|
||||
penetration: 1,
|
||||
};
|
||||
@@ -45,6 +45,7 @@ export function Page({ locale }: LocaleAcceptorProps) {
|
||||
|
||||
function Content() {
|
||||
const members = CompareEphemeral.use((state) => state.members);
|
||||
const equalize = CompareEphemeral.use((state) => state.equalize);
|
||||
const [addTankDialogOpen, setAddTankDialogOpen] = useState(false);
|
||||
const crewSkills = CompareEphemeral.use((state) => state.crewSkills);
|
||||
const { strings } = useLocale();
|
||||
@@ -61,26 +62,30 @@ function Content() {
|
||||
stockTurret: thisMember.tank.turrets[0],
|
||||
applyReactiveArmor: members.some(
|
||||
(member) =>
|
||||
member.key !== thisMember.key && member.consumables.includes(33)
|
||||
member.key !== thisMember.key &&
|
||||
member.consumables.includes(33),
|
||||
),
|
||||
applyDynamicArmor: members.some(
|
||||
(member) =>
|
||||
member.key !== thisMember.key && member.consumables.includes(73)
|
||||
member.key !== thisMember.key &&
|
||||
member.consumables.includes(73),
|
||||
),
|
||||
applySpallLiner: members.some(
|
||||
(member) =>
|
||||
member.key !== thisMember.key && member.provisions.includes(101)
|
||||
member.key !== thisMember.key &&
|
||||
member.provisions.includes(101),
|
||||
),
|
||||
assaultDistance: thisMember.assaultDistance,
|
||||
equalize,
|
||||
},
|
||||
{
|
||||
equipmentDefinitions: equipmentDefinitions,
|
||||
provisionDefinitions: provisionDefinitions,
|
||||
tankModelDefinition: modelDefinitions.models[thisMember.tank.id],
|
||||
}
|
||||
)
|
||||
},
|
||||
),
|
||||
),
|
||||
[members, crewSkills]
|
||||
[members, crewSkills, equalize],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -112,7 +117,7 @@ function Content() {
|
||||
window.history.replaceState(
|
||||
{},
|
||||
"",
|
||||
`${window.location.pathname}?${search.toString()}`
|
||||
`${window.location.pathname}?${search.toString()}`,
|
||||
);
|
||||
}, [members]);
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ export const getStaticPaths = (async () => {
|
||||
parentPaths.map((parent) => ({
|
||||
params: { ...parent.params, slug: tank.slug },
|
||||
props: { ...parent.props, id: tank.id },
|
||||
}))
|
||||
})),
|
||||
)
|
||||
.flat();
|
||||
}) satisfies GetStaticPaths;
|
||||
@@ -86,8 +86,9 @@ const characteristics = tankCharacteristics(
|
||||
stockTrack: tank.tracks[0],
|
||||
track: tank.tracks.at(-1)!,
|
||||
assaultDistance: 0,
|
||||
equalize: false,
|
||||
},
|
||||
{ equipmentDefinitions, provisionDefinitions, tankModelDefinition }
|
||||
{ equipmentDefinitions, provisionDefinitions, tankModelDefinition },
|
||||
);
|
||||
const description = literals(
|
||||
strings.website.tools.tankopedia.tank_seo_description,
|
||||
@@ -98,7 +99,7 @@ const description = literals(
|
||||
penetration: Math.round(characteristics.penetration).toLocaleString(locale),
|
||||
speed: Math.round(characteristics.speedForwards).toLocaleString(locale),
|
||||
health: Math.round(characteristics.health).toLocaleString(locale),
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const guideUrl = `${assertSecret(import.meta.env.PUBLIC_GUIDE_WEBSITE)}/${tank.slug}`;
|
||||
|
||||
@@ -7,6 +7,7 @@ export interface CompareMember extends DuelMember {
|
||||
|
||||
export interface CompareEphemeral {
|
||||
crewSkills: Record<string, number>;
|
||||
equalize: boolean;
|
||||
members: CompareMember[];
|
||||
sorting?: {
|
||||
direction: "ascending" | "descending";
|
||||
@@ -19,5 +20,6 @@ export const CompareEphemeral = new Varuna<
|
||||
Record<string, number>
|
||||
>((crewSkills) => ({
|
||||
crewSkills,
|
||||
equalize: false,
|
||||
members: [],
|
||||
}));
|
||||
@@ -36,12 +36,12 @@ export interface DuelMember {
|
||||
camouflage: boolean;
|
||||
cooldownBooster: number;
|
||||
assaultDistance: number;
|
||||
equalize: boolean;
|
||||
}
|
||||
|
||||
export interface DuelStore {
|
||||
protagonist: DuelMember;
|
||||
antagonist: DuelMember;
|
||||
equalize: boolean;
|
||||
}
|
||||
|
||||
const provisionDefinitions = await awaitableProvisionDefinitions;
|
||||
@@ -52,5 +52,5 @@ export const Duel = new Varuna<
|
||||
>(({ tank, model }) => {
|
||||
const protagonist = tankToDuelMember(tank, provisionDefinitions);
|
||||
|
||||
return { protagonist, antagonist: protagonist, model };
|
||||
return { protagonist, antagonist: protagonist, model, equalize: false };
|
||||
});
|
||||
Reference in new issue
Block a user