use crate::game_params::ttx::modifiers::ModifierBundle;
pub const MAX_SMALL_CATEGORY_LEVEL: u32 = 7;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum StatWeaponType {
MainAp,
MainHe,
MainCs,
AtbaAp,
AtbaHe,
AtbaCs,
Other,
}
impl StatWeaponType {
pub fn is_main(self) -> bool {
matches!(self, StatWeaponType::MainAp | StatWeaponType::MainHe | StatWeaponType::MainCs)
}
pub fn is_atba(self) -> bool {
matches!(self, StatWeaponType::AtbaAp | StatWeaponType::AtbaHe | StatWeaponType::AtbaCs)
}
pub fn is_hecs(self) -> bool {
matches!(self, StatWeaponType::MainHe | StatWeaponType::MainCs)
}
pub fn is_csap(self) -> bool {
matches!(
self,
StatWeaponType::AtbaCs | StatWeaponType::AtbaAp | StatWeaponType::MainAp | StatWeaponType::MainCs
)
}
pub fn to_atba(self) -> StatWeaponType {
match self {
StatWeaponType::MainAp => StatWeaponType::AtbaAp,
StatWeaponType::MainHe => StatWeaponType::AtbaHe,
StatWeaponType::MainCs => StatWeaponType::AtbaCs,
other => other,
}
}
}
pub fn ammo_to_stat_weapon_table(ammo: &str) -> StatWeaponType {
match ammo {
"AP" => StatWeaponType::MainAp,
"HE" => StatWeaponType::MainHe,
"CS" => StatWeaponType::MainCs,
_ => StatWeaponType::Other,
}
}
pub fn alpha_damage_coeff(weapon: StatWeaponType, modifier: &ModifierBundle, is_alt_mode: bool) -> f32 {
let mut coeff = modifier.coef("allAlphaMultiplier");
if weapon.is_main() {
coeff *= modifier.coef("GMAlphaFactor");
} else if weapon.is_atba() {
coeff *= modifier.coef("GSAlphaFactor");
if is_alt_mode {
coeff *= modifier.coef("GSMAlphaFactor");
}
}
coeff
}
pub fn artillery_damage_coeff(
caliber_m: f32,
weapon: StatWeaponType,
modifier: &ModifierBundle,
heavy_cruiser_shell_diameter_m: f32,
) -> f32 {
if weapon.is_main() {
let mut y = modifier.coef("GMDamageCoeff");
match weapon {
StatWeaponType::MainAp => {
y *= modifier.coef("GMAPDamageCoeff");
if caliber_m >= heavy_cruiser_shell_diameter_m {
y *= modifier.coef("GMHeavyCruiserCaliberDamageCoeff");
}
}
StatWeaponType::MainCs => y *= modifier.coef("GMCSDamageCoeff"),
_ => {}
}
if weapon.is_hecs() {
y *= modifier.coef("GMHECSDamageCoeff");
}
return y;
}
if weapon.is_atba() {
let mut y = modifier.coef("GSDamageCoeff");
match weapon {
StatWeaponType::AtbaCs => y *= modifier.coef("GSCSDamageCoeff"),
StatWeaponType::AtbaAp => y *= modifier.coef("GSAPDamageCoeff"),
_ => {}
}
return y;
}
1.0
}
pub fn is_small_projectile(bullet_diametr_m: f32, small_projectile_max_diameter_m: f32) -> bool {
bullet_diametr_m <= small_projectile_max_diameter_m
}
pub fn calculate_burn_chance(
ammo_owner_level: u32,
initial_burn_prob: f32,
modifier: &ModifierBundle,
is_small: bool,
) -> f32 {
let mut prob = initial_burn_prob;
if ammo_owner_level > MAX_SMALL_CATEGORY_LEVEL {
prob *= modifier.coef("burnChanceFactorHighLevel");
} else {
prob *= modifier.coef("burnChanceFactorLowLevel");
}
prob *= modifier.coef("burnChanceGMGSMultiplier");
prob += modifier.bonus("artilleryBurnChanceBonus");
prob *= modifier.coef("burnChanceMultiplier");
prob += modifier.bonus("burnChanceBonus");
prob += if is_small { modifier.bonus("burnChanceFactorSmall") } else { modifier.bonus("burnChanceFactorBig") };
prob.max(0.0)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::game_params::types::CrewSkillModifier;
use crate::game_params::types::Species;
const BUILD: u32 = 11791718;
fn stock() -> ModifierBundle {
ModifierBundle::empty(Species::Cruiser)
}
fn modifier(name: &str, value: f32) -> CrewSkillModifier {
CrewSkillModifier::builder()
.name(name.to_string())
.aircraft_carrier(value)
.auxiliary(value)
.battleship(value)
.cruiser(value)
.destroyer(value)
.submarine(value)
.excluded_consumables(Vec::new())
.build()
}
#[test]
fn ammo_strings_map_to_main_weapon() {
assert_eq!(ammo_to_stat_weapon_table("HE"), StatWeaponType::MainHe);
assert_eq!(ammo_to_stat_weapon_table("AP"), StatWeaponType::MainAp);
assert_eq!(ammo_to_stat_weapon_table("CS"), StatWeaponType::MainCs);
assert_eq!(ammo_to_stat_weapon_table("torpedo"), StatWeaponType::Other);
}
#[test]
fn csap_set_membership() {
assert!(StatWeaponType::MainAp.is_csap());
assert!(StatWeaponType::MainCs.is_csap());
assert!(StatWeaponType::AtbaAp.is_csap());
assert!(StatWeaponType::AtbaCs.is_csap());
assert!(!StatWeaponType::MainHe.is_csap());
assert!(!StatWeaponType::AtbaHe.is_csap());
}
#[test]
fn hecs_set_membership() {
assert!(StatWeaponType::MainHe.is_hecs());
assert!(StatWeaponType::MainCs.is_hecs());
assert!(!StatWeaponType::MainAp.is_hecs());
assert!(!StatWeaponType::AtbaHe.is_hecs());
}
#[test]
fn main_to_atba_mapping() {
assert_eq!(StatWeaponType::MainAp.to_atba(), StatWeaponType::AtbaAp);
assert_eq!(StatWeaponType::MainHe.to_atba(), StatWeaponType::AtbaHe);
assert_eq!(StatWeaponType::MainCs.to_atba(), StatWeaponType::AtbaCs);
assert_eq!(StatWeaponType::AtbaAp.to_atba(), StatWeaponType::AtbaAp);
}
#[test]
fn stock_alpha_damage_coeff_is_one() {
assert_eq!(alpha_damage_coeff(StatWeaponType::MainHe, &stock(), false), 1.0);
assert_eq!(alpha_damage_coeff(StatWeaponType::AtbaHe, &stock(), false), 1.0);
}
#[test]
fn alpha_damage_coeff_folds_main_factors() {
let mods = [modifier("allAlphaMultiplier", 1.1), modifier("GMAlphaFactor", 1.2)];
let bundle = ModifierBundle::from_modifiers(&mods, Species::Cruiser, BUILD);
let got = alpha_damage_coeff(StatWeaponType::MainAp, &bundle, false);
assert!((got - 1.32).abs() < 1e-5, "got {got}");
}
#[test]
fn stock_artillery_damage_coeff_he_is_one() {
let got = artillery_damage_coeff(0.152, StatWeaponType::MainHe, &stock(), 0.149);
assert_eq!(got, 1.0);
}
#[test]
fn artillery_damage_coeff_ap_heavy_cruiser_gate() {
let mods = [modifier("GMAPDamageCoeff", 1.5), modifier("GMHeavyCruiserCaliberDamageCoeff", 2.0)];
let bundle = ModifierBundle::from_modifiers(&mods, Species::Cruiser, BUILD);
let below = artillery_damage_coeff(0.152, StatWeaponType::MainAp, &bundle, 0.190);
assert!((below - 1.5).abs() < 1e-5, "got {below}");
let above = artillery_damage_coeff(0.203, StatWeaponType::MainAp, &bundle, 0.190);
assert!((above - 3.0).abs() < 1e-5, "got {above}");
}
#[test]
fn stock_burn_chance_high_level_is_burn_prob() {
let got = calculate_burn_chance(10, 0.12, &stock(), false);
assert!((got - 0.12).abs() < 1e-6, "got {got}");
}
#[test]
fn stock_burn_chance_low_level_is_burn_prob() {
let got = calculate_burn_chance(5, 0.08, &stock(), true);
assert!((got - 0.08).abs() < 1e-6, "got {got}");
}
#[test]
fn burn_chance_full_formula_high_level() {
let mods = [
modifier("burnChanceFactorHighLevel", 0.9),
modifier("burnChanceGMGSMultiplier", 0.95),
modifier("artilleryBurnChanceBonus", 0.02),
modifier("burnChanceMultiplier", 1.1),
modifier("burnChanceBonus", 0.01),
modifier("burnChanceFactorBig", -0.03),
];
let bundle = ModifierBundle::from_modifiers(&mods, Species::Cruiser, BUILD);
let got = calculate_burn_chance(10, 0.12, &bundle, false);
let expected = ((0.12f32 * 0.9 * 0.95) + 0.02) * 1.1 + 0.01 - 0.03;
assert!((got - expected).abs() < 1e-6, "got {got} expected {expected}");
}
#[test]
fn burn_chance_clamps_to_zero() {
let mods = [modifier("burnChanceBonus", -1.0)];
let bundle = ModifierBundle::from_modifiers(&mods, Species::Cruiser, BUILD);
let got = calculate_burn_chance(10, 0.12, &bundle, false);
assert_eq!(got, 0.0);
}
#[test]
fn burn_chance_ap_na_clamps_to_zero() {
let got = calculate_burn_chance(10, -0.5, &stock(), false);
assert_eq!(got, 0.0);
}
#[test]
fn small_projectile_threshold() {
assert!(is_small_projectile(0.1, 0.149));
assert!(!is_small_projectile(0.152, 0.149));
}
}