mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-28 21:19:52 +01:00
woohoo more data driven stats!
This commit is contained in:
1 parent
9c67fd3204
commit
95318d0a99
11 files changed
+737
-151
No files matched your search
@@ -1,3 +1,3 @@
|
|||||||
export function degressiveStat(crew: number) {
|
export function degressiveStat(crew: number) {
|
||||||
return 0.875 / (0.00375 * 100 * crew + 0.5) - 1;
|
return 0.875 / (0.00375 * 100 * crew + 0.5);
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,6 @@ export * from "./normalizeBoundingBox";
|
|||||||
export * from "./patientFetchJSON";
|
export * from "./patientFetchJSON";
|
||||||
export * from "./popularTanks";
|
export * from "./popularTanks";
|
||||||
export * from "./progressiveStat";
|
export * from "./progressiveStat";
|
||||||
export * from "./resolveDpm";
|
|
||||||
export * from "./resolvePenetrationCoefficient";
|
export * from "./resolvePenetrationCoefficient";
|
||||||
export * from "./reviews";
|
export * from "./reviews";
|
||||||
export * from "./romanize";
|
export * from "./romanize";
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
export function progressiveStat(crew: number) {
|
export function progressiveStat(crew: number) {
|
||||||
return (0.00375 * 100 * crew + 0.5) / 0.875 - 1;
|
return (0.00375 * 100 * crew + 0.5) / 0.875;
|
||||||
}
|
}
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
import {
|
|
||||||
GunDefinition,
|
|
||||||
GunDefinitionAutoReloader,
|
|
||||||
ShellDefinition,
|
|
||||||
} from '../protos';
|
|
||||||
|
|
||||||
export function resolveDpm(
|
|
||||||
gun: GunDefinition,
|
|
||||||
shell: ShellDefinition,
|
|
||||||
damageCoefficient = 1,
|
|
||||||
reloadCoefficient = 1,
|
|
||||||
intraClipCoefficient = 1,
|
|
||||||
) {
|
|
||||||
const alpha = shell.armor_damage * damageCoefficient;
|
|
||||||
let dps: number;
|
|
||||||
|
|
||||||
if (gun.gun_type!.$case === 'regular') {
|
|
||||||
dps = alpha / (reloadCoefficient * gun.gun_type!.value.reload);
|
|
||||||
} else if (gun.gun_type!.$case === 'auto_loader') {
|
|
||||||
const damage = alpha * gun.gun_type!.value.shell_count;
|
|
||||||
let time = gun.gun_type!.value.clip_reload * reloadCoefficient;
|
|
||||||
|
|
||||||
if (gun.burst) {
|
|
||||||
time +=
|
|
||||||
(gun.gun_type!.value.shell_count / gun.burst.count - 1) *
|
|
||||||
gun.gun_type!.value.intra_clip *
|
|
||||||
intraClipCoefficient;
|
|
||||||
|
|
||||||
time +=
|
|
||||||
(gun.gun_type!.value.shell_count / gun.burst.count) *
|
|
||||||
(gun.burst.count - 1) *
|
|
||||||
gun.burst.interval;
|
|
||||||
} else {
|
|
||||||
time +=
|
|
||||||
(gun.gun_type!.value.shell_count - 1) *
|
|
||||||
gun.gun_type!.value.intra_clip *
|
|
||||||
intraClipCoefficient;
|
|
||||||
}
|
|
||||||
|
|
||||||
dps = damage / time;
|
|
||||||
} else {
|
|
||||||
const mostOptimalShell = gun.gun_type!.value.shell_reloads.reduce<null | {
|
|
||||||
index: number;
|
|
||||||
reload: number;
|
|
||||||
}>((current, reloadRaw, index) => {
|
|
||||||
const reload =
|
|
||||||
reloadRaw * reloadCoefficient +
|
|
||||||
(index > 0
|
|
||||||
? (gun.gun_type!.value as GunDefinitionAutoReloader).intra_clip *
|
|
||||||
intraClipCoefficient
|
|
||||||
: 0);
|
|
||||||
|
|
||||||
if (current === null || reload < current.reload) {
|
|
||||||
return { index, reload };
|
|
||||||
}
|
|
||||||
return current;
|
|
||||||
}, null)!;
|
|
||||||
|
|
||||||
dps = alpha / mostOptimalShell?.reload;
|
|
||||||
}
|
|
||||||
|
|
||||||
return dps * 60;
|
|
||||||
}
|
|
||||||
@@ -8,6 +8,13 @@
|
|||||||
"s": "{value}s",
|
"s": "{value}s",
|
||||||
"percentage": "{value}%",
|
"percentage": "{value}%",
|
||||||
"deg_s": "{value}°/s",
|
"deg_s": "{value}°/s",
|
||||||
|
"m_s": "{value}m/s",
|
||||||
|
"km_h": "{value}km/h",
|
||||||
|
"t": "{value}t",
|
||||||
|
"bhp": "{value}bhp",
|
||||||
|
"bhp_t": "{value}bhp/t",
|
||||||
|
"kN": "{value}kN",
|
||||||
|
"hp_s": "{value}hp/s",
|
||||||
"plus": "+{value}"
|
"plus": "+{value}"
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -38,7 +45,7 @@
|
|||||||
"burst_size": "Burst size",
|
"burst_size": "Burst size",
|
||||||
"intra_burst": "Intra-burst",
|
"intra_burst": "Intra-burst",
|
||||||
"penetration": "Penetration",
|
"penetration": "Penetration",
|
||||||
"penetration_loss_by_distance": "Penetration loss by distance",
|
"penetration_loss_by_distance": "Penetration loss by 500m",
|
||||||
"penetration_loss_after_ricochet": "Penetration loss after ricochet",
|
"penetration_loss_after_ricochet": "Penetration loss after ricochet",
|
||||||
"shell_velocity": "Shell velocity",
|
"shell_velocity": "Shell velocity",
|
||||||
"shell_range": "Shell range",
|
"shell_range": "Shell range",
|
||||||
|
|||||||
@@ -1,18 +1,243 @@
|
|||||||
import { blitzkitToBlitzShellKind } from "@blitzkit/core";
|
import {
|
||||||
import { ShellType } from "@blitzkit/protos";
|
blitzkitToBlitzShellKind,
|
||||||
|
isExplosive,
|
||||||
|
normalizeBoundingBox,
|
||||||
|
unionBoundingBox,
|
||||||
|
} from "@blitzkit/core";
|
||||||
|
import { ShellType, TankClass } from "@blitzkit/protos";
|
||||||
import {
|
import {
|
||||||
CharacteristicType,
|
CharacteristicType,
|
||||||
type Characteristic,
|
type Characteristic,
|
||||||
|
type CharacteristicContext,
|
||||||
} from "../types/characteristics";
|
} from "../types/characteristics";
|
||||||
|
|
||||||
|
function intraClipCoefficient({
|
||||||
|
script,
|
||||||
|
}: Pick<CharacteristicContext, "script">) {
|
||||||
|
let coefficient = 1;
|
||||||
|
|
||||||
|
script("protagonist", "NewAutoloader", (effect) => {
|
||||||
|
coefficient *= effect.clipReloadTimeFactor;
|
||||||
|
});
|
||||||
|
|
||||||
|
return coefficient;
|
||||||
|
}
|
||||||
|
|
||||||
|
function aimTimeCoefficient({ script }: Pick<CharacteristicContext, "script">) {
|
||||||
|
let coefficient = 1;
|
||||||
|
|
||||||
|
script("protagonist", "miscAttrs/gunAimingTimeFactor", (effect) => {
|
||||||
|
coefficient += effect.bonusValues!.percent / 100;
|
||||||
|
});
|
||||||
|
|
||||||
|
script("protagonist", "FireControlSystem", (effect) => {
|
||||||
|
coefficient *= effect.aimingTimeFactor;
|
||||||
|
});
|
||||||
|
|
||||||
|
return coefficient;
|
||||||
|
}
|
||||||
|
|
||||||
|
function dispersionCoefficient({
|
||||||
|
script,
|
||||||
|
}: Pick<CharacteristicContext, "script">) {
|
||||||
|
let coefficient = 1;
|
||||||
|
|
||||||
|
script("protagonist", "miscAttrs/shotDispersionAngleFactor", (effect) => {
|
||||||
|
coefficient += effect.bonusValues!.percent / 100;
|
||||||
|
});
|
||||||
|
|
||||||
|
script("protagonist", "FireControlSystem", (effect) => {
|
||||||
|
coefficient *= effect.shotDispersionAngleFactor;
|
||||||
|
});
|
||||||
|
|
||||||
|
return coefficient;
|
||||||
|
}
|
||||||
|
|
||||||
export const characteristics = {
|
export const characteristics = {
|
||||||
gun_type: {
|
gun_type: {
|
||||||
type: CharacteristicType.Enum,
|
type: CharacteristicType.Enum,
|
||||||
|
|
||||||
compute({ gun }) {
|
compute({ gun }) {
|
||||||
return gun.gun_type!.$case;
|
return gun.gun_type!.$case;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
damage: {
|
||||||
|
type: CharacteristicType.Number,
|
||||||
|
|
||||||
|
compute({ shell, environment, equalizer, script, gun }) {
|
||||||
|
let coefficient = 1;
|
||||||
|
|
||||||
|
if (environment.equalize) coefficient *= equalizer.damage;
|
||||||
|
|
||||||
|
script("protagonist", "TungstenTip", (effect) => {
|
||||||
|
coefficient *= (effect.minDamageFactor + effect.maxDamageFactor) / 2;
|
||||||
|
});
|
||||||
|
|
||||||
|
script("antagonist", "ShieldKit", (effect) => {
|
||||||
|
if (shell.type === ShellType.SHELL_TYPE_HE) return;
|
||||||
|
coefficient *= 1 - effect.shieldCoef;
|
||||||
|
});
|
||||||
|
|
||||||
|
script("antagonist", "ArmorMover", (effect) => {
|
||||||
|
coefficient *= 1 - effect.bonusValues!.damageReductionPercent / 100;
|
||||||
|
});
|
||||||
|
|
||||||
|
script("antagonist", "AntiHighExplosive", (effect) => {
|
||||||
|
if (shell.type !== ShellType.SHELL_TYPE_HE) return;
|
||||||
|
coefficient *= effect.bonusValues!.factor;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (gun.assault_ranges?.types.includes(shell.type)) {
|
||||||
|
const range = gun.assault_ranges.ranges.find(
|
||||||
|
({ distance }) => distance >= environment.distance,
|
||||||
|
);
|
||||||
|
|
||||||
|
coefficient *= range?.factor ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return coefficient * shell.armor_damage;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
_reload_coefficient: {
|
||||||
|
type: CharacteristicType.Number,
|
||||||
|
|
||||||
|
compute({ script, degressive }) {
|
||||||
|
let c0 = 1;
|
||||||
|
|
||||||
|
script("protagonist", "miscAttrs/gunReloadTimeFactor", (effect) => {
|
||||||
|
c0 += effect.bonusValues!.percent / 100;
|
||||||
|
});
|
||||||
|
|
||||||
|
const c1 = degressive("loader");
|
||||||
|
|
||||||
|
let c2 = 1;
|
||||||
|
|
||||||
|
script("protagonist", "Berserk", (effect) => {
|
||||||
|
c2 += effect.bonusValues!.gunReloadSpeedIncrease / 100;
|
||||||
|
});
|
||||||
|
|
||||||
|
return (c0 * c1) / c2;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
reload: {
|
||||||
|
type: CharacteristicType.Number,
|
||||||
|
|
||||||
|
should_render({ gun }) {
|
||||||
|
return gun.gun_type!.$case !== "auto_reloader";
|
||||||
|
},
|
||||||
|
|
||||||
|
compute({ gun, characteristic }) {
|
||||||
|
if (gun.gun_type!.$case === "auto_reloader") return -Infinity;
|
||||||
|
|
||||||
|
const c = characteristic("_reload_coefficient") as number;
|
||||||
|
|
||||||
|
if (gun.gun_type!.$case === "regular") {
|
||||||
|
return c * gun.gun_type!.value.reload;
|
||||||
|
}
|
||||||
|
|
||||||
|
return c * gun.gun_type!.value.clip_reload;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
intra_clip: {
|
||||||
|
type: CharacteristicType.Number,
|
||||||
|
|
||||||
|
should_render({ gun }) {
|
||||||
|
return gun.gun_type!.$case !== "regular";
|
||||||
|
},
|
||||||
|
|
||||||
|
compute({ gun, script }) {
|
||||||
|
if (gun.gun_type!.$case === "regular") return 0;
|
||||||
|
|
||||||
|
let c = 1;
|
||||||
|
|
||||||
|
script("protagonist", "NewAutoloader", (effect) => {
|
||||||
|
c = effect.clipReloadTimeFactor;
|
||||||
|
});
|
||||||
|
|
||||||
|
return c * gun.gun_type!.value.intra_clip;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
dpm: {
|
||||||
|
type: CharacteristicType.Number,
|
||||||
|
|
||||||
|
compute({ gun, characteristic }) {
|
||||||
|
const alpha = characteristic("damage") as number;
|
||||||
|
|
||||||
|
let dps: number;
|
||||||
|
|
||||||
|
if (gun.gun_type!.$case === "regular") {
|
||||||
|
const reload = characteristic("reload") as number;
|
||||||
|
dps = alpha / reload;
|
||||||
|
} else if (gun.gun_type!.$case === "auto_loader") {
|
||||||
|
const damage = alpha * gun.gun_type!.value.shell_count;
|
||||||
|
|
||||||
|
const intraClip = characteristic("intra_clip") as number;
|
||||||
|
let time = characteristic("reload") as number;
|
||||||
|
|
||||||
|
if (gun.burst) {
|
||||||
|
time +=
|
||||||
|
(gun.gun_type!.value.shell_count / gun.burst.count - 1) * intraClip;
|
||||||
|
|
||||||
|
time +=
|
||||||
|
(gun.gun_type!.value.shell_count / gun.burst.count) *
|
||||||
|
(gun.burst.count - 1) *
|
||||||
|
gun.burst.interval;
|
||||||
|
} else {
|
||||||
|
time += (gun.gun_type!.value.shell_count - 1) * intraClip;
|
||||||
|
}
|
||||||
|
|
||||||
|
dps = damage / time;
|
||||||
|
} else {
|
||||||
|
const intraClip = characteristic("intra_clip") as number;
|
||||||
|
const reloadCoefficient = characteristic(
|
||||||
|
"_reload_coefficient",
|
||||||
|
) as number;
|
||||||
|
|
||||||
|
const mostOptimalShell =
|
||||||
|
gun.gun_type!.value.shell_reloads.reduce<null | {
|
||||||
|
index: number;
|
||||||
|
reload: number;
|
||||||
|
}>((current, reloadRaw, index) => {
|
||||||
|
const reload =
|
||||||
|
reloadRaw * reloadCoefficient + (index > 0 ? intraClip : 0);
|
||||||
|
|
||||||
|
if (current === null || reload < current.reload) {
|
||||||
|
return { index, reload };
|
||||||
|
}
|
||||||
|
return current;
|
||||||
|
}, null)!;
|
||||||
|
|
||||||
|
dps = alpha / mostOptimalShell?.reload;
|
||||||
|
}
|
||||||
|
|
||||||
|
return dps * 60;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// damage_range: unavailable,
|
||||||
|
|
||||||
|
module_damage: {
|
||||||
|
type: CharacteristicType.Number,
|
||||||
|
compute({ shell, script }) {
|
||||||
|
let c = 1;
|
||||||
|
|
||||||
|
script("protagonist", "TungstenTip", (effect) => {
|
||||||
|
c *= (effect.minDamageFactor + effect.maxDamageFactor) / 2;
|
||||||
|
});
|
||||||
|
|
||||||
|
return c * shell.module_damage;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// module_damage_range: unavailable,
|
||||||
|
|
||||||
|
// reloads: unavailable,
|
||||||
|
|
||||||
clip_size: {
|
clip_size: {
|
||||||
type: CharacteristicType.Number,
|
type: CharacteristicType.Number,
|
||||||
|
|
||||||
@@ -26,99 +251,455 @@ export const characteristics = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
damage: {
|
clipping_potential: {
|
||||||
type: CharacteristicType.Number,
|
type: CharacteristicType.Number,
|
||||||
compute({ shell, environment, equalizer, script, gun }) {
|
|
||||||
let coefficient = 1;
|
|
||||||
|
|
||||||
if (environment.equalize) coefficient *= equalizer.damage;
|
should_render({ gun }) {
|
||||||
|
return gun.gun_type!.$case !== "regular";
|
||||||
|
},
|
||||||
|
|
||||||
// tungsten shells
|
compute({ characteristic }) {
|
||||||
script("protagonist", "TungstenTip", (effect) => {
|
const clipSize = characteristic("clip_size") as number;
|
||||||
coefficient *= (effect.minDamageFactor + effect.maxDamageFactor) / 2;
|
const damage = characteristic("damage") as number;
|
||||||
});
|
|
||||||
|
|
||||||
// reactive armor
|
return clipSize * damage;
|
||||||
script("antagonist", "ShieldKit", (effect) => {
|
|
||||||
if (shell.type === ShellType.SHELL_TYPE_HE) return;
|
|
||||||
coefficient *= 1 - effect.shieldCoef;
|
|
||||||
});
|
|
||||||
|
|
||||||
// dynamic armour system
|
|
||||||
script("antagonist", "ArmorMover", (effect) => {
|
|
||||||
coefficient *= 1 - effect.bonusValues!.damageReductionPercent / 100;
|
|
||||||
});
|
|
||||||
|
|
||||||
// spall liner
|
|
||||||
script("antagonist", "AntiHighExplosive", (effect) => {
|
|
||||||
if (shell.type !== ShellType.SHELL_TYPE_HE) return;
|
|
||||||
coefficient *= effect.bonusValues!.factor;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (gun.assault_ranges && gun.assault_ranges.types.includes(shell.type)) {
|
|
||||||
const range = gun.assault_ranges.ranges.find(
|
|
||||||
({ distance }) => distance >= environment.distance,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (range) coefficient *= range.factor;
|
|
||||||
}
|
|
||||||
|
|
||||||
return coefficient * shell.armor_damage;
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
module_damage: {
|
// TODO: custom type of shell index
|
||||||
|
// optimal_shell: {
|
||||||
|
// type: CharacteristicType.Number,
|
||||||
|
|
||||||
|
// should_render({ gun }) {
|
||||||
|
// return gun.gun_type!.$case === "auto_reloader";
|
||||||
|
// },
|
||||||
|
|
||||||
|
// compute({ gun, script, degressive }) {
|
||||||
|
// if (gun.gun_type!.$case !== "auto_reloader") return -1;
|
||||||
|
|
||||||
|
// const autoReloader = gun.gun_type!.value;
|
||||||
|
// const coefficient = reloadCoefficient({ script, degressive, gun });
|
||||||
|
// const intraClip = intraClipCoefficient({ script });
|
||||||
|
|
||||||
|
// return autoReloader.shell_reloads.reduce(
|
||||||
|
// (bestIndex, reload, index, reloads) => {
|
||||||
|
// const effectiveReload =
|
||||||
|
// reload * coefficient +
|
||||||
|
// (index > 0 ? autoReloader.intra_clip * intraClip : 0);
|
||||||
|
// const bestReload =
|
||||||
|
// reloads[bestIndex] * coefficient +
|
||||||
|
// (bestIndex > 0 ? autoReloader.intra_clip * intraClip : 0);
|
||||||
|
|
||||||
|
// return effectiveReload < bestReload ? index : bestIndex;
|
||||||
|
// },
|
||||||
|
// 0,
|
||||||
|
// );
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
|
||||||
|
burst_size: {
|
||||||
type: CharacteristicType.Number,
|
type: CharacteristicType.Number,
|
||||||
compute({ shell, script }) {
|
|
||||||
let coefficient = 1;
|
|
||||||
|
|
||||||
// tungsten shells
|
should_render({ gun }) {
|
||||||
script("protagonist", "TungstenTip", (effect) => {
|
return gun.burst !== undefined;
|
||||||
coefficient *= (effect.minDamageFactor + effect.maxDamageFactor) / 2;
|
},
|
||||||
});
|
|
||||||
|
|
||||||
return coefficient * shell.module_damage;
|
compute({ gun }) {
|
||||||
|
return gun.burst!.count;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
intra_burst: {
|
||||||
|
type: CharacteristicType.Number,
|
||||||
|
|
||||||
|
should_render({ gun }) {
|
||||||
|
return gun.burst !== undefined;
|
||||||
|
},
|
||||||
|
|
||||||
|
compute({ gun }) {
|
||||||
|
return gun.burst!.interval;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
penetration: {
|
penetration: {
|
||||||
type: CharacteristicType.Number,
|
type: CharacteristicType.Number,
|
||||||
compute({ shell, script }) {
|
|
||||||
let coefficient = 1;
|
compute({ shell, environment, equalizer, script }) {
|
||||||
|
let c = 1;
|
||||||
|
|
||||||
script("protagonist", "CalibratedShells", (effect) => {
|
script("protagonist", "CalibratedShells", (effect) => {
|
||||||
coefficient +=
|
c +=
|
||||||
effect.piercingFactors![blitzkitToBlitzShellKind[shell.type]] / 100;
|
effect.piercingFactors![blitzkitToBlitzShellKind[shell.type]] / 100;
|
||||||
});
|
});
|
||||||
|
|
||||||
return coefficient * shell.penetration!.near;
|
if (environment.equalize) c *= equalizer.penetration;
|
||||||
|
|
||||||
|
return c * shell.penetration!.near;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
reload: {
|
penetration_loss_by_distance: {
|
||||||
type: CharacteristicType.Number,
|
type: CharacteristicType.Number,
|
||||||
|
|
||||||
should_render({ gun }) {
|
compute({ shell, script }) {
|
||||||
return gun.gun_type!.$case !== "auto_reloader";
|
let c = 1;
|
||||||
},
|
|
||||||
|
|
||||||
compute({ gun, script, degressive }) {
|
script("protagonist", "StaticMiscAttrsModifier", (effect) => {
|
||||||
// should never happen! just for typescript
|
c += effect.bonusValues!.piercingPenaltyFactor500m / 100;
|
||||||
if (gun.gun_type!.$case === "auto_reloader") return -Infinity;
|
|
||||||
|
|
||||||
let c0 = 1;
|
|
||||||
|
|
||||||
script("protagonist", "miscAttrs/gunReloadTimeFactor", (effect) => {
|
|
||||||
c0 += effect.bonusValues!.percent / 100;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const c = c0 * degressive;
|
return (
|
||||||
|
c *
|
||||||
|
100 *
|
||||||
|
((shell.penetration!.near - shell.penetration!.far) /
|
||||||
|
shell.penetration!.near)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
if (gun.gun_type!.$case === "regular") {
|
// penetration_loss_after_ricochet: unavailable,
|
||||||
return c * gun.gun_type!.value.reload;
|
|
||||||
|
shell_velocity: {
|
||||||
|
type: CharacteristicType.Number,
|
||||||
|
|
||||||
|
compute({ shell, script }) {
|
||||||
|
let c = 1;
|
||||||
|
|
||||||
|
script("protagonist", "StaticMiscAttrsModifier", (effect) => {
|
||||||
|
c += effect.bonusValues!.projectileSpeedFactor / 100;
|
||||||
|
});
|
||||||
|
|
||||||
|
script("protagonist", "GunPowder", (effect) => {
|
||||||
|
c += effect.bonusValues!.projectileSpeedFactor / 100;
|
||||||
|
});
|
||||||
|
|
||||||
|
return c * shell.velocity;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
shell_range: {
|
||||||
|
type: CharacteristicType.Number,
|
||||||
|
|
||||||
|
compute({ shell }) {
|
||||||
|
return shell.range;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
shell_capacity: {
|
||||||
|
type: CharacteristicType.Number,
|
||||||
|
|
||||||
|
compute({ gun }) {
|
||||||
|
return gun.shell_capacity;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
caliber: {
|
||||||
|
type: CharacteristicType.Number,
|
||||||
|
|
||||||
|
compute({ shell }) {
|
||||||
|
return shell.caliber;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
ricochet: {
|
||||||
|
type: CharacteristicType.Number,
|
||||||
|
|
||||||
|
should_render({ shell }) {
|
||||||
|
return !isExplosive(shell.type);
|
||||||
|
},
|
||||||
|
|
||||||
|
compute({ shell }) {
|
||||||
|
return shell.ricochet ?? 0;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
normalization: {
|
||||||
|
type: CharacteristicType.Number,
|
||||||
|
|
||||||
|
should_render({ shell }) {
|
||||||
|
return !isExplosive(shell.type);
|
||||||
|
},
|
||||||
|
|
||||||
|
compute({ shell }) {
|
||||||
|
return shell.normalization ?? 0;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
speed_forward: {
|
||||||
|
type: CharacteristicType.Number,
|
||||||
|
compute({ tank }) {
|
||||||
|
return tank.speed_forwards;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
speed_backward: {
|
||||||
|
type: CharacteristicType.Number,
|
||||||
|
compute({ tank }) {
|
||||||
|
return tank.speed_backwards;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
aim_time: {
|
||||||
|
type: CharacteristicType.Number,
|
||||||
|
compute({ gun, turret, track, degressive, script, characteristic }) {
|
||||||
|
/*
|
||||||
|
T = (A / C) × ln(√(1 + k² × S)) / (1 − p/100)
|
||||||
|
|
||||||
|
S = (turret rotation speed × turret dispersion factor)²
|
||||||
|
+ (hull rotation speed × hull dispersion factor)²
|
||||||
|
+ (min(top speed, 30) × movement dispersion factor)²
|
||||||
|
A: the gun’s raw aimingTime—the time for the reticle to shrink by a factor of e ≈ 2.718.
|
||||||
|
C: gunner crew factor, accounting for training, provisions, and shared crew roles.
|
||||||
|
k: stabilization multiplier; normally 1, reduced by the stabilizer.
|
||||||
|
p: signed aiming-drive modifier. For −12%, the displayed time is divided by 1.12, reducing it by approximately 10.7%.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const topSpeed = characteristic("speed_forward") as number;
|
||||||
|
|
||||||
|
const S =
|
||||||
|
(turret.traverse_speed * gun.dispersion_traverse) ** 2 +
|
||||||
|
(track.traverse_speed * track.dispersion_traverse) ** 2 +
|
||||||
|
(Math.min(topSpeed, 30) * track.dispersion_move) ** 2;
|
||||||
|
|
||||||
|
const A = gun.aim_time;
|
||||||
|
const C = degressive("gunner");
|
||||||
|
|
||||||
|
let p = 0;
|
||||||
|
|
||||||
|
script("protagonist", "miscAttrs/gunAimingTimeFactor", (effect) => {
|
||||||
|
p = effect.bonusValues!.percent / 100;
|
||||||
|
});
|
||||||
|
|
||||||
|
let k = 1;
|
||||||
|
|
||||||
|
script(
|
||||||
|
"protagonist",
|
||||||
|
"miscAttrs/additiveShotDispersionFactor",
|
||||||
|
(effect) => {
|
||||||
|
k += effect.bonusValues!.percent / 100;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const T = ((A / C) * Math.log(Math.sqrt(1 + k ** 2 * S))) / (1 - p / 100);
|
||||||
|
|
||||||
|
return T;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
dispersion: {
|
||||||
|
type: CharacteristicType.Number,
|
||||||
|
compute({ gun, degressive, script }) {
|
||||||
|
return (
|
||||||
|
gun.dispersion_base *
|
||||||
|
degressive("gunner") *
|
||||||
|
dispersionCoefficient({ script })
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// dispersion_angle: unavailable,
|
||||||
|
|
||||||
|
gun_depression: {
|
||||||
|
type: CharacteristicType.Number,
|
||||||
|
compute({ state, turret, gun }) {
|
||||||
|
const turretModel = state.model.turrets[turret.id];
|
||||||
|
const gunModel = turretModel.guns[gun.id];
|
||||||
|
|
||||||
|
return (
|
||||||
|
gunModel.pitch!.max + (state.model.initial_turret_rotation?.pitch ?? 0)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
gun_elevation: {
|
||||||
|
type: CharacteristicType.Number,
|
||||||
|
compute({ state, turret, gun }) {
|
||||||
|
const turretModel = state.model.turrets[turret.id];
|
||||||
|
const gunModel = turretModel.guns[gun.id];
|
||||||
|
|
||||||
|
return (
|
||||||
|
-gunModel.pitch!.min - (state.model.initial_turret_rotation?.pitch ?? 0)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
azimuth_left: {
|
||||||
|
type: CharacteristicType.Number,
|
||||||
|
should_render({ state, turret }) {
|
||||||
|
return state.model.turrets[turret.id].yaw !== undefined;
|
||||||
|
},
|
||||||
|
compute({ state, turret }) {
|
||||||
|
return -state.model.turrets[turret.id].yaw!.min;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
azimuth_right: {
|
||||||
|
type: CharacteristicType.Number,
|
||||||
|
should_render({ state, turret }) {
|
||||||
|
return state.model.turrets[turret.id].yaw !== undefined;
|
||||||
|
},
|
||||||
|
compute({ state, turret }) {
|
||||||
|
return state.model.turrets[turret.id].yaw!.max;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
weight: {
|
||||||
|
type: CharacteristicType.Number,
|
||||||
|
compute({ tank, engine, track, turret, gun }) {
|
||||||
|
return (
|
||||||
|
(tank.weight +
|
||||||
|
engine.weight +
|
||||||
|
track.weight +
|
||||||
|
turret.weight +
|
||||||
|
gun.weight) /
|
||||||
|
1000
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
engine_power: {
|
||||||
|
type: CharacteristicType.Number,
|
||||||
|
compute({ engine }) {
|
||||||
|
return engine.power;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
power_to_weight: {
|
||||||
|
type: CharacteristicType.Number,
|
||||||
|
compute({ engine, characteristic }) {
|
||||||
|
const weight = characteristic("weight");
|
||||||
|
if (typeof weight !== "number") {
|
||||||
|
throw new Error("Weight characteristic must be numeric");
|
||||||
}
|
}
|
||||||
|
|
||||||
return c * gun.gun_type!.value.clip_reload;
|
return engine.power / weight;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// brake_force: unavailable,
|
||||||
|
// terrain_coefficient: unavailable,
|
||||||
|
|
||||||
|
hull_traverse_speed: {
|
||||||
|
type: CharacteristicType.Number,
|
||||||
|
compute({ track, progressive }) {
|
||||||
|
return track.traverse_speed * progressive("driver");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
turret_traverse_speed: {
|
||||||
|
type: CharacteristicType.Number,
|
||||||
|
compute({ turret, progressive }) {
|
||||||
|
return turret.traverse_speed * progressive("gunner");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
gun_traverse_speed: {
|
||||||
|
type: CharacteristicType.Number,
|
||||||
|
compute({ gun, progressive }) {
|
||||||
|
return gun.rotation_speed * progressive("gunner");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
health: {
|
||||||
|
type: CharacteristicType.Number,
|
||||||
|
compute({ tank, turret, environment, equalizer, script }) {
|
||||||
|
let c0 = 1;
|
||||||
|
|
||||||
|
script("protagonist", "SmallHPStock", (effect) => {
|
||||||
|
c0 += effect.hpStockPercent / 100;
|
||||||
|
});
|
||||||
|
|
||||||
|
script("protagonist", "HighQualityAssembly", (effect) => {
|
||||||
|
c0 += effect.maxHpBonusPercent / 100;
|
||||||
|
});
|
||||||
|
|
||||||
|
let c1 = 1;
|
||||||
|
|
||||||
|
if (environment.equalize) {
|
||||||
|
c1 *= equalizer.health;
|
||||||
|
}
|
||||||
|
|
||||||
|
let health = tank.health + turret.health;
|
||||||
|
|
||||||
|
health *= c0 * c1;
|
||||||
|
|
||||||
|
if (environment.equalize) {
|
||||||
|
health = 50 * Math.round(health / 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
return health;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
fire_chance: {
|
||||||
|
type: CharacteristicType.Number,
|
||||||
|
compute({ engine }) {
|
||||||
|
return engine.fire_chance;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// fire_rate: unavailable,
|
||||||
|
// ramming_resistance: unavailable,
|
||||||
|
|
||||||
|
view_range: {
|
||||||
|
type: CharacteristicType.Number,
|
||||||
|
compute({ turret, progressive }) {
|
||||||
|
return turret.view_range * progressive("commander");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
camouflage: {
|
||||||
|
type: CharacteristicType.Number,
|
||||||
|
compute({ tank, state }) {
|
||||||
|
const camouflageBonus =
|
||||||
|
tank.class === TankClass.TANK_CLASS_TANK_DESTROYER
|
||||||
|
? 0.04
|
||||||
|
: tank.class === TankClass.TANK_CLASS_HEAVY
|
||||||
|
? 0.03
|
||||||
|
: 0.02;
|
||||||
|
|
||||||
|
return (
|
||||||
|
tank.camouflage_still * (1 + (state.camouflage ? camouflageBonus : 0))
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
width: {
|
||||||
|
type: CharacteristicType.Number,
|
||||||
|
compute({ state, turret }) {
|
||||||
|
const size = normalizeBoundingBox(
|
||||||
|
unionBoundingBox(
|
||||||
|
state.model.bounding_box!,
|
||||||
|
state.model.turrets[turret.id].bounding_box!,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return size.z;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
height: {
|
||||||
|
type: CharacteristicType.Number,
|
||||||
|
compute({ state, turret }) {
|
||||||
|
const size = normalizeBoundingBox(
|
||||||
|
unionBoundingBox(
|
||||||
|
state.model.bounding_box!,
|
||||||
|
state.model.turrets[turret.id].bounding_box!,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return size.x;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
length: {
|
||||||
|
type: CharacteristicType.Number,
|
||||||
|
compute({ state, turret }) {
|
||||||
|
const size = normalizeBoundingBox(
|
||||||
|
unionBoundingBox(
|
||||||
|
state.model.bounding_box!,
|
||||||
|
state.model.turrets[turret.id].bounding_box!,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return size.y;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} satisfies Record<string, Characteristic>;
|
} satisfies Record<string, Characteristic>;
|
||||||
@@ -110,7 +110,7 @@ export const characteristicsGroups: CharacteristicsGroup[] = [
|
|||||||
// render: CharacteristicsGroupRender.Statistical,
|
// render: CharacteristicsGroupRender.Statistical,
|
||||||
order: [
|
order: [
|
||||||
{ name: "penetration", units: "mm" },
|
{ name: "penetration", units: "mm" },
|
||||||
{ name: "penetration_loss_by_distance" },
|
{ name: "penetration_loss_by_distance", units: "percentage" },
|
||||||
{ name: "penetration_loss_after_ricochet" },
|
{ name: "penetration_loss_after_ricochet" },
|
||||||
{ name: "shell_velocity", units: "m_s" },
|
{ name: "shell_velocity", units: "m_s" },
|
||||||
{ name: "shell_range", units: "m" },
|
{ name: "shell_range", units: "m" },
|
||||||
@@ -127,7 +127,7 @@ export const characteristicsGroups: CharacteristicsGroup[] = [
|
|||||||
name: "accuracy",
|
name: "accuracy",
|
||||||
// render: CharacteristicsGroupRender.Statistical,
|
// render: CharacteristicsGroupRender.Statistical,
|
||||||
order: [
|
order: [
|
||||||
{ name: "aim_time", decimals: 1, units: "s" },
|
{ name: "aim_time", decimals: 2, units: "s" },
|
||||||
{ name: "dispersion", decimals: 3, units: "m" },
|
{ name: "dispersion", decimals: 3, units: "m" },
|
||||||
{ name: "dispersion_angle", decimals: 3, units: "deg" },
|
{ name: "dispersion_angle", decimals: 3, units: "deg" },
|
||||||
{ toy: "aim_time" },
|
{ toy: "aim_time" },
|
||||||
@@ -178,9 +178,9 @@ export const characteristicsGroups: CharacteristicsGroup[] = [
|
|||||||
// render: CharacteristicsGroupRender.Statistical,
|
// render: CharacteristicsGroupRender.Statistical,
|
||||||
order: [
|
order: [
|
||||||
{ name: "health", units: "hp" },
|
{ name: "health", units: "hp" },
|
||||||
{ name: "fire_chance", units: "%" },
|
{ name: "fire_chance", units: "percentage" },
|
||||||
{ name: "fire_rate", units: "hp_s" },
|
{ name: "fire_rate", units: "hp_s" },
|
||||||
{ name: "ramming_resistance", units: "%" },
|
{ name: "ramming_resistance", units: "percentage" },
|
||||||
|
|
||||||
{ toy: "fire_health_burn" },
|
{ toy: "fire_health_burn" },
|
||||||
|
|
||||||
@@ -193,7 +193,7 @@ export const characteristicsGroups: CharacteristicsGroup[] = [
|
|||||||
// render: CharacteristicsGroupRender.Statistical,
|
// render: CharacteristicsGroupRender.Statistical,
|
||||||
order: [
|
order: [
|
||||||
{ name: "view_range", units: "m" },
|
{ name: "view_range", units: "m" },
|
||||||
{ name: "camouflage", decimals: 0, units: "%" },
|
{ name: "camouflage", decimals: 0, units: "percentage" },
|
||||||
|
|
||||||
{ toy: "view_range" },
|
{ toy: "view_range" },
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
|
import { degressiveStat, progressiveStat } from "@blitzkit/core";
|
||||||
import type { BlitzEffectScript } from "@blitzkit/core/src/types/blitzEffectScript";
|
import type { BlitzEffectScript } from "@blitzkit/core/src/types/blitzEffectScript";
|
||||||
|
import { CrewType } from "@blitzkit/protos";
|
||||||
import { api } from "../api/dynamic";
|
import { api } from "../api/dynamic";
|
||||||
import { characteristics } from "../config/characteristics";
|
import { characteristics } from "../config/characteristics";
|
||||||
import { defaultEqualizer } from "../config/equalizer";
|
import { defaultEqualizer } from "../config/equalizer";
|
||||||
@@ -44,39 +46,91 @@ export function computeCharacteristics(
|
|||||||
(shell) => shell.id === states.protagonist.shell,
|
(shell) => shell.id === states.protagonist.shell,
|
||||||
)!;
|
)!;
|
||||||
|
|
||||||
const equipmentPreset = equipment.presets[tank.equipment_preset];
|
const scriptsMap: Record<DuelSide, Map<string, BlitzEffectScript[]>> = {
|
||||||
|
|
||||||
const scriptsMap: Record<DuelSide, Map<string, BlitzEffectScript>> = {
|
|
||||||
protagonist: new Map(),
|
protagonist: new Map(),
|
||||||
antagonist: new Map(),
|
antagonist: new Map(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function addScript(side: DuelSide, name: string, script: BlitzEffectScript) {
|
||||||
|
if (!scriptsMap[side].has(name)) {
|
||||||
|
scriptsMap[side].set(name, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
scriptsMap[side].get(name)!.push(script);
|
||||||
|
}
|
||||||
|
|
||||||
for (const side of duelSides) {
|
for (const side of duelSides) {
|
||||||
|
const sideTank = tanks.tanks[states[side].tank];
|
||||||
|
const sideEquipmentPreset = equipment.presets[sideTank.equipment_preset];
|
||||||
|
|
||||||
for (const id of states[side].consumables) {
|
for (const id of states[side].consumables) {
|
||||||
if (!(id in scripts.consumables)) continue;
|
if (!(id in scripts.consumables)) continue;
|
||||||
|
|
||||||
const script = scripts.consumables[id];
|
const script = scripts.consumables[id];
|
||||||
scriptsMap.protagonist.set(script["#text"], script);
|
|
||||||
|
addScript(side, script["#text"], script);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const id of states[side].provisions) {
|
for (const id of states[side].provisions) {
|
||||||
if (!(id in scripts.provisions)) continue;
|
if (!(id in scripts.provisions)) continue;
|
||||||
|
|
||||||
const script = scripts.provisions[id];
|
const script = scripts.provisions[id];
|
||||||
scriptsMap.protagonist.set(script["#text"], script);
|
|
||||||
|
addScript(side, script["#text"], script);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const index in states[side].equipment) {
|
for (const index in states[side].equipment) {
|
||||||
const choice = states[side].equipment[index];
|
const choice = states[side].equipment[index];
|
||||||
const id = equipmentPreset.slots[index].options[choice];
|
const id = sideEquipmentPreset.slots[index].options[choice];
|
||||||
|
|
||||||
if (!(id in scripts.equipment)) continue;
|
if (!(id in scripts.equipment)) continue;
|
||||||
|
|
||||||
const script = scripts.equipment[id];
|
const script = scripts.equipment[id];
|
||||||
scriptsMap.protagonist.set(script["#text"], script);
|
|
||||||
|
addScript(side, script["#text"], script);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const protagonist = states.protagonist;
|
||||||
|
const protagonistEquipmentPreset = equipment.presets[tank.equipment_preset];
|
||||||
|
const hasImprovedVentilation = Object.entries(protagonist.equipment).some(
|
||||||
|
([index, choice]) =>
|
||||||
|
protagonistEquipmentPreset.slots[Number(index)]?.options[choice] === 102,
|
||||||
|
);
|
||||||
|
const provisionCrewBonus =
|
||||||
|
protagonist.provisions.reduce(
|
||||||
|
(total, id) =>
|
||||||
|
total +
|
||||||
|
(scripts.provisions[id]?.bonusValues?.crewLevelIncrease ?? 0) / 100,
|
||||||
|
0,
|
||||||
|
) + (hasImprovedVentilation ? 0.08 : 0);
|
||||||
|
const commanderMastery = 1 + provisionCrewBonus;
|
||||||
|
const crewMastery = {
|
||||||
|
commander: commanderMastery,
|
||||||
|
loader:
|
||||||
|
commanderMastery *
|
||||||
|
(tank.crew.some(({ type }) => type === CrewType.CREW_TYPE_LOADER)
|
||||||
|
? 1.1
|
||||||
|
: 1.05),
|
||||||
|
gunner:
|
||||||
|
commanderMastery *
|
||||||
|
(tank.crew.some(({ type }) => type === CrewType.CREW_TYPE_GUNNER)
|
||||||
|
? 1.1
|
||||||
|
: 1.05),
|
||||||
|
driver:
|
||||||
|
commanderMastery *
|
||||||
|
(tank.crew.some(({ type }) => type === CrewType.CREW_TYPE_DRIVER)
|
||||||
|
? 1.1
|
||||||
|
: 1.05),
|
||||||
|
};
|
||||||
|
|
||||||
|
function progressive(role: keyof typeof crewMastery) {
|
||||||
|
return progressiveStat(crewMastery[role]);
|
||||||
|
}
|
||||||
|
function degressive(role: keyof typeof crewMastery) {
|
||||||
|
return degressiveStat(crewMastery[role]);
|
||||||
|
}
|
||||||
|
|
||||||
function characteristic(name: CharacteristicName) {
|
function characteristic(name: CharacteristicName) {
|
||||||
if (!computed.has(name)) {
|
if (!computed.has(name)) {
|
||||||
throw new Error(`Characteristic ${name} not computed yet`);
|
throw new Error(`Characteristic ${name} not computed yet`);
|
||||||
@@ -93,7 +147,12 @@ export function computeCharacteristics(
|
|||||||
callback: (effect: BlitzEffectScript) => void,
|
callback: (effect: BlitzEffectScript) => void,
|
||||||
) {
|
) {
|
||||||
if (!scriptsMap[side].has(name)) return;
|
if (!scriptsMap[side].has(name)) return;
|
||||||
callback(scriptsMap[side].get(name)!);
|
|
||||||
|
const scripts = scriptsMap[side].get(name)!;
|
||||||
|
|
||||||
|
for (const script of scripts) {
|
||||||
|
callback(script);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const context = {
|
const context = {
|
||||||
@@ -125,6 +184,7 @@ export function computeCharacteristics(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!shouldRender) continue;
|
if (!shouldRender) continue;
|
||||||
|
if (!("compute" in characteristic)) continue;
|
||||||
|
|
||||||
const value = characteristic.compute(context);
|
const value = characteristic.compute(context);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export function createTankEquipment() {
|
||||||
|
return { 0: 0, 1: 1, 2: 0 };
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { createDefaultProvisions } from "@blitzkit/core";
|
import { createDefaultProvisions } from "@blitzkit/core";
|
||||||
import type { TankDefinition } from "@blitzkit/protos";
|
import type { TankDefinition } from "@blitzkit/protos";
|
||||||
import { api } from "../api/dynamic";
|
import { api } from "../api/dynamic";
|
||||||
|
import { createTankEquipment } from "./createTankEquipment";
|
||||||
import { createTankStatus } from "./createTankStatus";
|
import { createTankStatus } from "./createTankStatus";
|
||||||
import type { TankState } from "./tankState";
|
import type { TankState } from "./tankState";
|
||||||
|
|
||||||
@@ -27,13 +28,9 @@ export function createTankState(tank: TankDefinition) {
|
|||||||
shell: gun.shells[0].id,
|
shell: gun.shells[0].id,
|
||||||
track: tank.tracks.at(-1)!.id,
|
track: tank.tracks.at(-1)!.id,
|
||||||
|
|
||||||
environment: {
|
|
||||||
assault_distance: (gun.assault_ranges?.ranges[0].distance ?? 0) / 2,
|
|
||||||
},
|
|
||||||
|
|
||||||
speed: 0,
|
speed: 0,
|
||||||
|
|
||||||
equipment: {},
|
equipment: createTankEquipment(),
|
||||||
status: createTankStatus(),
|
status: createTankStatus(),
|
||||||
|
|
||||||
model: models.models[tank.id],
|
model: models.models[tank.id],
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ export interface CharacteristicContext {
|
|||||||
|
|
||||||
equalizer: Equalizer;
|
equalizer: Equalizer;
|
||||||
|
|
||||||
progressive: number;
|
progressive(role: CrewStatRole): number;
|
||||||
degressive: number;
|
degressive(role: CrewStatRole): number;
|
||||||
|
|
||||||
tank: TankDefinition;
|
tank: TankDefinition;
|
||||||
engine: EngineDefinition;
|
engine: EngineDefinition;
|
||||||
@@ -55,6 +55,8 @@ export interface CharacteristicContext {
|
|||||||
): void;
|
): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type CrewStatRole = "commander" | "loader" | "gunner" | "driver";
|
||||||
|
|
||||||
export enum CharacteristicType {
|
export enum CharacteristicType {
|
||||||
Enum,
|
Enum,
|
||||||
Number,
|
Number,
|
||||||
|
|||||||
Reference in new issue
Block a user