From d70d23deb80373d776f82dabcdce68530f098e8f Mon Sep 17 00:00:00 2001 From: Fate-JH Date: Mon, 21 Sep 2020 23:18:11 -0400 Subject: [PATCH] modifiers for flak burst (#579) --- .../psforever/objects/GlobalDefinitions.scala | 36 +- .../vital/damage/DamageModifiers.scala | 369 +++++++++++------- src/test/scala/objects/DamageModelTests.scala | 195 +++++++++ 3 files changed, 450 insertions(+), 150 deletions(-) diff --git a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index a1ef97557..6514d66bc 100644 --- a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -2356,7 +2356,11 @@ object GlobalDefinitions { burster_projectile.InitialVelocity = 125 burster_projectile.Lifespan = 4f ProjectileDefinition.CalculateDerivedFields(burster_projectile) - //TODO burster_projectile.Modifiers = DamageModifiers.RadialDegrade? + burster_projectile.Modifiers = List( + //DamageModifiers.FlakHit, + DamageModifiers.FlakBurst, + DamageModifiers.MaxDistanceCutoff + ) chainblade_projectile.Name = "chainblade_projectile" // TODO for later, maybe : set_resource_parent chainblade_projectile game_objects melee_ammo_projectile @@ -2396,7 +2400,11 @@ object GlobalDefinitions { colossus_burster_projectile.InitialVelocity = 175 colossus_burster_projectile.Lifespan = 2.5f ProjectileDefinition.CalculateDerivedFields(colossus_burster_projectile) - //TODO colossus_burster_projectile.Modifiers = DamageModifiers.RadialDegrade? + colossus_burster_projectile.Modifiers = List( + //DamageModifiers.FlakHit, + DamageModifiers.FlakBurst, + DamageModifiers.MaxDistanceCutoff + ) colossus_chaingun_projectile.Name = "colossus_chaingun_projectile" // TODO for later, maybe : set_resource_parent colossus_chaingun_projectile game_objects 35mmbullet_projectile @@ -3448,7 +3456,11 @@ object GlobalDefinitions { phalanx_flak_projectile.InitialVelocity = 100 phalanx_flak_projectile.Lifespan = 5f ProjectileDefinition.CalculateDerivedFields(phalanx_flak_projectile) - //TODO phalanx_flak_projectile.Modifiers = DamageModifiers.RadialDegrade? + phalanx_flak_projectile.Modifiers = List( + //DamageModifiers.FlakHit, + DamageModifiers.FlakBurst, + DamageModifiers.MaxDistanceCutoff + ) phalanx_projectile.Name = "phalanx_projectile" phalanx_projectile.Damage0 = 20 @@ -3747,7 +3759,11 @@ object GlobalDefinitions { rocklet_flak_projectile.InitialVelocity = 60 rocklet_flak_projectile.Lifespan = 3.2f ProjectileDefinition.CalculateDerivedFields(rocklet_flak_projectile) - //TODO rocklet_flak_projectile.Modifiers = DamageModifiers.RadialDegrade? + rocklet_flak_projectile.Modifiers = List( + //DamageModifiers.FlakHit, + DamageModifiers.FlakBurst, + DamageModifiers.MaxDistanceCutoff + ) rocklet_jammer_projectile.Name = "rocklet_jammer_projectile" rocklet_jammer_projectile.Damage0 = 0 @@ -3826,7 +3842,11 @@ object GlobalDefinitions { skyguard_flak_cannon_projectile.InitialVelocity = 100 skyguard_flak_cannon_projectile.Lifespan = 5f ProjectileDefinition.CalculateDerivedFields(skyguard_flak_cannon_projectile) - //TODO skyguard_flak_cannon_projectile.Modifiers = DamageModifiers.RadialDegrade? + skyguard_flak_cannon_projectile.Modifiers = List( + //DamageModifiers.FlakHit, + DamageModifiers.FlakBurst, + DamageModifiers.MaxDistanceCutoff + ) sparrow_projectile.Name = "sparrow_projectile" sparrow_projectile.Damage0 = 35 @@ -3894,7 +3914,11 @@ object GlobalDefinitions { spitfire_aa_ammo_projectile.InitialVelocity = 100 spitfire_aa_ammo_projectile.Lifespan = 5f ProjectileDefinition.CalculateDerivedFields(spitfire_aa_ammo_projectile) - //TODO spitfire_aa_ammo_projectile.Modifiers = DamageModifiers.RadialDegrade? + spitfire_aa_ammo_projectile.Modifiers = List( + //DamageModifiers.FlakHit, + DamageModifiers.FlakBurst, + DamageModifiers.MaxDistanceCutoff + ) spitfire_ammo_projectile.Name = "spitfire_ammo_projectile" spitfire_ammo_projectile.Damage0 = 15 diff --git a/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala b/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala index 089a41cce..7a83d0caa 100644 --- a/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala +++ b/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala @@ -47,6 +47,7 @@ object DamageModifiers { private def function(damage: Int, data: ResolvedProjectile): Int = damage } + /** If the calculated distance is greater than the maximum distance of the projectile, damage is zero'd. */ case object MaxDistanceCutoff extends Mod { def Calculate: DamageModifiers.Format = function @@ -62,6 +63,21 @@ object DamageModifiers { } } + /** If the calculated distance is greater than a custom distance, damage is zero'd. */ + case class CustomDistanceCutoff(cutoff: Float) extends Mod { + def Calculate: DamageModifiers.Format = function + + private def function(damage: Int, data: ResolvedProjectile): Int = { + val projectile = data.projectile + val distance = Vector3.Distance(data.hit_pos, projectile.shot_origin) + if (distance <= cutoff) { + damage + } else { + 0 + } + } + } + /** * The input value degrades (lessens) * the further the distance between the point of origin (`shot_origin`) @@ -70,22 +86,7 @@ object DamageModifiers { * If the value is encountered beyond its maximum distance, the value is zero'd. */ case object DistanceDegrade extends Mod { - def Calculate: DamageModifiers.Format = function - - private def function(damage: Int, data: ResolvedProjectile): Int = { - val projectile = data.projectile - val profile = projectile.profile - val distance = Vector3.Distance(data.hit_pos, projectile.shot_origin) - if (distance <= profile.DistanceMax) { - if (profile.DistanceNoDegrade == profile.DistanceMax || distance <= profile.DistanceNoDegrade) { - damage - } else { - damage - ((damage - profile.DegradeMultiplier * damage) * ((distance - profile.DistanceNoDegrade) / (profile.DistanceMax - profile.DistanceNoDegrade))).toInt - } - } else { - 0 - } - } + def Calculate: DamageModifiers.Format = distanceDegradeFunction } /** @@ -95,24 +96,7 @@ object DamageModifiers { * If the value is encountered beyond its maximum radial distance, the value is zero'd. */ case object RadialDegrade extends Mod { - def Calculate: DamageModifiers.Format = function - - private def function(damage: Int, data: ResolvedProjectile): Int = { - val profile = data.projectile.profile - val distance = Vector3.Distance(data.hit_pos, data.target.Position) - val radius = profile.DamageRadius - val radiusMin = profile.DamageRadiusMin - if (distance <= radiusMin) { - damage - } else if (distance <= radius) { - //damage - (damage * profile.DamageAtEdge * (distance - radiusMin) / (radius - radiusMin)).toInt - val base = profile.DamageAtEdge - val radi = radius - radiusMin - (damage * ((1 - base) * ((radi - (distance - radiusMin)) / radi) + base)).toInt - } else { - 0 - } - } + def Calculate: DamageModifiers.Format = radialDegradeFunction } /** @@ -139,7 +123,7 @@ object DamageModifiers { } /* - Below this point are the calculations for sources of aggravated damage. + Aggravated damage. For the most part, these calculations are individualistic and arbitrary. They exist in their current form to satisfy observed shots to kill (STK) of specific weapon systems according to 2012 standards of the Youtube video series by TheLegendaryNarwhal. @@ -150,7 +134,7 @@ object DamageModifiers { */ case object InfantryAggravatedDirect extends Mod { def Calculate: DamageModifiers.Format = - BaseAggravatedFormula(ProjectileResolution.AggravatedDirect, DamageType.Direct) + baseAggravatedFormula(ProjectileResolution.AggravatedDirect, DamageType.Direct) } /** @@ -159,7 +143,7 @@ object DamageModifiers { */ case object InfantryAggravatedSplash extends Mod { def Calculate: DamageModifiers.Format = - BaseAggravatedFormula(ProjectileResolution.AggravatedSplash, DamageType.Splash) + baseAggravatedFormula(ProjectileResolution.AggravatedSplash, DamageType.Splash) } /** @@ -169,7 +153,7 @@ object DamageModifiers { */ case object InfantryAggravatedDirectBurn extends Mod { def Calculate: DamageModifiers.Format = - BaseAggravatedBurnFormula(ProjectileResolution.AggravatedDirectBurn, DamageType.Direct) + baseAggravatedBurnFormula(ProjectileResolution.AggravatedDirectBurn, DamageType.Direct) } /** @@ -179,110 +163,7 @@ object DamageModifiers { */ case object InfantryAggravatedSplashBurn extends Mod { def Calculate: DamageModifiers.Format = - BaseAggravatedBurnFormula(ProjectileResolution.AggravatedSplashBurn, DamageType.Splash) - } - - /** - * For damage application that involves aggravation of a particular damage type, - * calculate that initial damage application for infantry targets - * and produce the modified damage value. - * Infantry wearing mechanized assault exo-suits (MAX) incorporate an additional modifier. - * @see `AggravatedDamage` - * @see `ExoSuitType` - * @see `InfantryAggravatedDirect` - * @see `InfantryAggravatedSplash` - * @see `PlayerSource` - * @see `ProjectileTarget.AggravatesTarget` - * @see `ResolvedProjectile` - * @param resolution the projectile resolution to match against - * @param damageType the damage type to find in as a component of aggravated information - * @param damage the base damage value - * @param data historical information related to the damage interaction - * @return the modified damage - */ - private def BaseAggravatedFormula( - resolution: ProjectileResolution.Value, - damageType : DamageType.Value - ) - ( - damage: Int, - data: ResolvedProjectile - ): Int = { - if (data.resolution == resolution && - data.projectile.quality == ProjectileQuality.AggravatesTarget) { - (data.projectile.profile.Aggravated, data.target) match { - case (Some(aggravation), p: PlayerSource) => - val aggravatedDamage = aggravation.info.find(_.damage_type == damageType) match { - case Some(infos) => - damage * infos.degradation_percentage + damage - case _ => - damage toFloat - } - if(p.ExoSuit == ExoSuitType.MAX) { - (aggravatedDamage * aggravation.max_factor) toInt - } else { - aggravatedDamage toInt - } - case _ => - damage - } - } else { - damage - } - } - - /** - * For damage application that involves aggravation of a particular damage type, - * calculate that damage application burn for each tick for infantry targets - * and produce the modified damage value. - * Infantry wearing mechanized assault exo-suits (MAX) incorporate an additional modifier. - * Vanilla infantry incorporate their resistance value into a slightly different calculation than usual. - * @see `AggravatedDamage` - * @see `ExoSuitType` - * @see `InfantryAggravatedDirectBurn` - * @see `InfantryAggravatedSplashBurn` - * @see `PlayerSource` - * @see `ResolvedProjectile` - * @param resolution the projectile resolution to match against - * @param damageType the damage type to find in as a component of aggravated information - * @param damage the base damage value - * @param data historical information related to the damage interaction - * @return the modified damage - */ - private def BaseAggravatedBurnFormula( - resolution: ProjectileResolution.Value, - damageType : DamageType.Value - ) - ( - damage: Int, - data: ResolvedProjectile - ): Int = { - if (data.resolution == resolution) { - (data.projectile.profile.Aggravated, data.target) match { - case (Some(aggravation), p: PlayerSource) => - val degradation = aggravation.info.find(_.damage_type == damageType) match { - case Some(info) => - info.degradation_percentage - case _ => - 1f - } - if (p.exosuit == ExoSuitType.MAX) { - (damage * degradation * aggravation.max_factor) toInt - } else { - val resist = data.damage_model.ResistUsing(data)(data) - //add resist to offset resist subtraction later - if (damage > resist) { - ((damage - resist) * degradation).toInt + resist - } else { - (damage * degradation).toInt + resist - } - } - case _ => - 0 - } - } else { - damage - } + baseAggravatedBurnFormula(ProjectileResolution.AggravatedSplashBurn, DamageType.Splash) } /** @@ -295,8 +176,8 @@ object DamageModifiers { private def formula(damage: Int, data: ResolvedProjectile): Int = { if (damage > 0 && - data.resolution == ProjectileResolution.AggravatedDirectBurn || - data.resolution == ProjectileResolution.AggravatedSplashBurn) { + (data.resolution == ProjectileResolution.AggravatedDirectBurn || + data.resolution == ProjectileResolution.AggravatedSplashBurn)) { //add resist to offset resist subtraction later 1 + data.damage_model.ResistUsing(data)(data) } else { @@ -425,6 +306,17 @@ object DamageModifiers { } } + /** + * If the projectile has charging properties, + * and the weapon that produced the projectile has charging mechanics, + * calculate the current value of the damage as a sum + * of some minimum damage and scaled normal damage. + * The projectile quality has information about the "factor" of damage scaling. + * @see `ChargeDamage` + * @see `ChargeFireModeDefinition` + * @see `ProjectileQuality` + * @see `ResolvedProjectile` + */ case object SpikerChargeDamage extends Mod { def Calculate: DamageModifiers.Format = formula @@ -439,4 +331,193 @@ object DamageModifiers { } } } + + /** + * If the damage is resolved through a `HitDamage` packet, + * calculate the damage as a function of its degrading value over distance traveled by its carrier projectile. + * @see `distanceDegradeFunction` + * @see `ProjectileQuality` + * @see `ResolvedProjectile` + */ + case object FlakHit extends Mod { + def Calculate: DamageModifiers.Format = formula + + private def formula(damage: Int, data: ResolvedProjectile): Int = { + if(data.resolution == ProjectileResolution.Hit) { + distanceDegradeFunction(damage, data) + } else { + damage + } + } + } + + /** + * If the damage is resolved through a `SplashHitDamage` packet, + * calculate the damage as a function of its degrading value over distance + * between the hit position of the projectile and the position of the target. + * @see `radialDegradeFunction` + * @see `ProjectileQuality` + * @see `ResolvedProjectile` + */ + case object FlakBurst extends Mod { + def Calculate: DamageModifiers.Format = formula + + private def formula(damage: Int, data: ResolvedProjectile): Int = { + if(data.resolution == ProjectileResolution.Splash) { + radialDegradeFunction(damage, data) + } else { + damage + } + } + } + + /* Functions */ + + /** + * The input value degrades (lessens) + * the further the distance between the point of origin (`shot_origin`) + * and the point of encounter (`hit_pos`) of its vector (projectile). + * If the value is not set to degrade over any distance within its maximum distance, the value goes unmodified. + * If the value is encountered beyond its maximum distance, the value is zero'd. + */ + private def distanceDegradeFunction(damage: Int, data: ResolvedProjectile): Int = { + val projectile = data.projectile + val profile = projectile.profile + val distance = Vector3.Distance(data.hit_pos, projectile.shot_origin) + if (distance <= profile.DistanceMax) { + if (profile.DistanceNoDegrade == profile.DistanceMax || distance <= profile.DistanceNoDegrade) { + damage + } else { + damage - ((damage - profile.DegradeMultiplier * damage) * ((distance - profile.DistanceNoDegrade) / (profile.DistanceMax - profile.DistanceNoDegrade))).toInt + } + } else { + 0 + } + } + + /** + * The input value degrades (lessens) + * the further the distance between the point of origin (target position) + * and the point of encounter (`hit_pos`) of its vector (projectile). + * If the value is encountered beyond its maximum radial distance, the value is zero'd. + */ + private def radialDegradeFunction(damage: Int, data: ResolvedProjectile): Int = { + val profile = data.projectile.profile + val distance = Vector3.Distance(data.hit_pos, data.target.Position) + val radius = profile.DamageRadius + val radiusMin = profile.DamageRadiusMin + if (distance <= radiusMin) { + damage + } else if (distance <= radius) { + //damage - (damage * profile.DamageAtEdge * (distance - radiusMin) / (radius - radiusMin)).toInt + val base = profile.DamageAtEdge + val radi = radius - radiusMin + (damage * ((1 - base) * ((radi - (distance - radiusMin)) / radi) + base)).toInt + } else { + 0 + } + } + + /** + * For damage application that involves aggravation of a particular damage type, + * calculate that initial damage application for infantry targets + * and produce the modified damage value. + * Infantry wearing mechanized assault exo-suits (MAX) incorporate an additional modifier. + * @see `AggravatedDamage` + * @see `ExoSuitType` + * @see `InfantryAggravatedDirect` + * @see `InfantryAggravatedSplash` + * @see `PlayerSource` + * @see `ProjectileTarget.AggravatesTarget` + * @see `ResolvedProjectile` + * @param resolution the projectile resolution to match against + * @param damageType the damage type to find in as a component of aggravated information + * @param damage the base damage value + * @param data historical information related to the damage interaction + * @return the modified damage + */ + private def baseAggravatedFormula( + resolution: ProjectileResolution.Value, + damageType : DamageType.Value + ) + ( + damage: Int, + data: ResolvedProjectile + ): Int = { + if (data.resolution == resolution && + data.projectile.quality == ProjectileQuality.AggravatesTarget) { + (data.projectile.profile.Aggravated, data.target) match { + case (Some(aggravation), p: PlayerSource) => + val aggravatedDamage = aggravation.info.find(_.damage_type == damageType) match { + case Some(infos) => + damage * infos.degradation_percentage + damage + case _ => + damage toFloat + } + if(p.ExoSuit == ExoSuitType.MAX) { + (aggravatedDamage * aggravation.max_factor) toInt + } else { + aggravatedDamage toInt + } + case _ => + damage + } + } else { + damage + } + } + + /** + * For damage application that involves aggravation of a particular damage type, + * calculate that damage application burn for each tick for infantry targets + * and produce the modified damage value. + * Infantry wearing mechanized assault exo-suits (MAX) incorporate an additional modifier. + * Vanilla infantry incorporate their resistance value into a slightly different calculation than usual. + * @see `AggravatedDamage` + * @see `ExoSuitType` + * @see `InfantryAggravatedDirectBurn` + * @see `InfantryAggravatedSplashBurn` + * @see `PlayerSource` + * @see `ResolvedProjectile` + * @param resolution the projectile resolution to match against + * @param damageType the damage type to find in as a component of aggravated information + * @param damage the base damage value + * @param data historical information related to the damage interaction + * @return the modified damage + */ + private def baseAggravatedBurnFormula( + resolution: ProjectileResolution.Value, + damageType : DamageType.Value + ) + ( + damage: Int, + data: ResolvedProjectile + ): Int = { + if (data.resolution == resolution) { + (data.projectile.profile.Aggravated, data.target) match { + case (Some(aggravation), p: PlayerSource) => + val degradation = aggravation.info.find(_.damage_type == damageType) match { + case Some(info) => + info.degradation_percentage + case _ => + 1f + } + if (p.exosuit == ExoSuitType.MAX) { + (damage * degradation * aggravation.max_factor) toInt + } else { + val resist = data.damage_model.ResistUsing(data)(data) + //add resist to offset resist subtraction later + if (damage > resist) { + ((damage - resist) * degradation).toInt + resist + } else { + (damage * degradation).toInt + resist + } + } + case _ => + 0 + } + } else { + damage + } + } } diff --git a/src/test/scala/objects/DamageModelTests.scala b/src/test/scala/objects/DamageModelTests.scala index 656e91fc1..89a9a383d 100644 --- a/src/test/scala/objects/DamageModelTests.scala +++ b/src/test/scala/objects/DamageModelTests.scala @@ -67,6 +67,36 @@ class DamageCalculationsTests extends Specification { DamageModifiers.DistanceDegrade.Calculate(100, resprojectile) == 100 mustEqual true } + "cut off damage at max distance (no cutoff)" in { + DamageModifiers.MaxDistanceCutoff.Calculate(100, resprojectile) == 100 mustEqual true + } + + "cut off damage at max distance (cutoff)" in { + val cutoffprojectile = ResolvedProjectile( + ProjectileResolution.Hit, + projectile, + SourceEntry(target), + target.DamageModel, + Vector3(1500, 0, 0) + ) + DamageModifiers.MaxDistanceCutoff.Calculate(100, cutoffprojectile) == 0 mustEqual true + } + + "cut off damage at custom distance (no cutoff)" in { + DamageModifiers.CustomDistanceCutoff(10).Calculate(100, resprojectile) == 0 mustEqual true + } + + "cut off damage at custom distance (cutoff)" in { + val coffprojectile = ResolvedProjectile( + ProjectileResolution.Splash, + projectile, + SourceEntry(target), + target.DamageModel, + Vector3(10, 0, 0) + ) + DamageModifiers.CustomDistanceCutoff(2).Calculate(100, coffprojectile) == 0 mustEqual true + } + "degrade over distance damage modifier (some degrade)" in { val resprojectile2 = ResolvedProjectile( ProjectileResolution.Splash, @@ -158,6 +188,171 @@ class DamageCalculationsTests extends Specification { DamageModifiers.Lash.Calculate(100, resprojectile2) == 0 mustEqual true } + "fireball aggravated damage (aggravated splash burn" in { + val burnWeapon = Tool(GlobalDefinitions.flamethrower) + val burnProjectile = Projectile( + burnWeapon.Projectile, + burnWeapon.Definition, + burnWeapon.FireMode, + player, + Vector3(2, 2, 0), + Vector3.Zero + ) + val burnRes = ResolvedProjectile( + ProjectileResolution.AggravatedSplashBurn, + projectile, + SourceEntry(target), + target.DamageModel, + Vector3(15, 0, 0) + ) + val resistance = burnRes.damage_model.ResistUsing(burnRes)(burnRes) + DamageModifiers.FireballAggravatedBurn.Calculate(100, burnRes) == (1 + resistance) mustEqual true + } + + "fireball aggravated damage (noral splash, no modification)" in { + val burnWeapon = Tool(GlobalDefinitions.flamethrower) + val burnProjectile = Projectile( + burnWeapon.Projectile, + burnWeapon.Definition, + burnWeapon.FireMode, + player, + Vector3(2, 2, 0), + Vector3.Zero + ) + val burnRes = ResolvedProjectile( + ProjectileResolution.Splash, + projectile, + SourceEntry(target), + target.DamageModel, + Vector3(15, 0, 0) + ) + DamageModifiers.FireballAggravatedBurn.Calculate(100, burnRes) == 100 mustEqual true + } + + val charge_weapon = Tool(GlobalDefinitions.spiker) + val charge_projectile = Projectile( + charge_weapon.Projectile, + charge_weapon.Definition, + charge_weapon.FireMode, + player, + Vector3(2, 2, 0), + Vector3.Zero + ) + val minDamageBase = charge_weapon.Projectile.Charging.get.min.Damage0 + val chargeBaseDamage = charge_weapon.Projectile.Damage0 + + "charge (none)" in { + val cprojectile = charge_projectile.quality(ProjectileQuality.Modified(0)) + val rescprojectile = ResolvedProjectile( + ProjectileResolution.Hit, + cprojectile, + SourceEntry(target), + target.DamageModel, + Vector3(15, 0, 0) + ) + val damage = DamageModifiers.SpikerChargeDamage.Calculate(chargeBaseDamage, rescprojectile) + val calcDam = minDamageBase + math.floor(chargeBaseDamage * rescprojectile.projectile.quality.mod) + damage mustEqual calcDam + } + + "charge (half)" in { + val cprojectile = charge_projectile.quality(ProjectileQuality.Modified(0.5f)) + val rescprojectile = ResolvedProjectile( + ProjectileResolution.Hit, + cprojectile, + SourceEntry(target), + target.DamageModel, + Vector3(15, 0, 0) + ) + val damage = DamageModifiers.SpikerChargeDamage.Calculate(chargeBaseDamage, rescprojectile) + val calcDam = minDamageBase + math.floor(chargeBaseDamage * rescprojectile.projectile.quality.mod) + damage mustEqual calcDam + } + + "charge (full)" in { + val cprojectile = charge_projectile.quality(ProjectileQuality.Modified(1)) + val rescprojectile = ResolvedProjectile( + ProjectileResolution.Hit, + cprojectile, + SourceEntry(target), + target.DamageModel, + Vector3(15, 0, 0) + ) + val damage = DamageModifiers.SpikerChargeDamage.Calculate(chargeBaseDamage, rescprojectile) + val calcDam = minDamageBase + chargeBaseDamage + damage mustEqual calcDam + } + + val flak_weapon = Tool(GlobalDefinitions.trhev_burster) + val flak_projectile = Projectile( + flak_weapon.Projectile, + flak_weapon.Definition, + flak_weapon.FireMode, + player, + Vector3(2, 2, 0), + Vector3.Zero + ) + + "flak hit (resolution is splash, no degrade)" in { + val resfprojectile = ResolvedProjectile( + ProjectileResolution.Splash, + flak_projectile, + SourceEntry(target), + target.DamageModel, + Vector3(10, 0, 0) + ) + val damage = DamageModifiers.FlakHit.Calculate(100, resfprojectile) + damage == 100 mustEqual true + } + + "flak hit (resolution is hit, no degrade)" in { + val resfprojectile = ResolvedProjectile( + ProjectileResolution.Hit, + flak_projectile, + SourceEntry(target), + target.DamageModel, + Vector3(10, 0, 0) + ) + val damage = DamageModifiers.FlakHit.Calculate(100, resfprojectile) + damage == 100 mustEqual true + } + + "flak burst (resolution is hit)" in { + val resfprojectile = ResolvedProjectile( + ProjectileResolution.Hit, + flak_projectile, + SourceEntry(target), + target.DamageModel, + Vector3(15, 0, 0) + ) + val damage = DamageModifiers.FlakBurst.Calculate(100, resfprojectile) + damage == 100 mustEqual true + } + + "flak burst (resolution is splash, no degrade)" in { + val resfprojectile = ResolvedProjectile( + ProjectileResolution.Splash, + flak_projectile, + SourceEntry(target), + target.DamageModel, + Vector3(10, 0, 0) + ) + val damage = DamageModifiers.FlakBurst.Calculate(100, resfprojectile) + damage == 100 mustEqual true + } + + "flak burst (resolution is splash, some degrade)" in { + val resfprojectile = ResolvedProjectile( + ProjectileResolution.Splash, + flak_projectile, + SourceEntry(target), + target.DamageModel, + Vector3(5, 0, 0) + ) + val damage = DamageModifiers.FlakBurst.Calculate(100, resfprojectile) + damage < 100 mustEqual true + } + "extract a complete damage profile" in { val result1 = DamageModifiers.RadialDegrade.Calculate( AgainstVehicle(proj_prof) + AgainstVehicle(wep_prof),