From 3182c790240ece7cc5479b505583f0f0a3443e7d Mon Sep 17 00:00:00 2001 From: Fate-JH Date: Tue, 23 Dec 2025 13:03:24 -0500 Subject: [PATCH] Armor Goes First (#1334) * conditions where infantry suffer only armor damage until it is depleted (not just as a max); valid for spitfire damage and collision * no bonus damage on armor depletion --- .../global/GlobalDefinitionsProjectile.scala | 3 ++ .../vital/collision/CollisionReason.scala | 1 + .../objects/vital/prop/DamageProperties.scala | 13 +++++++- .../resolution/ResolutionCalculations.scala | 32 ++++++++++++++++--- 4 files changed, 44 insertions(+), 5 deletions(-) diff --git a/src/main/scala/net/psforever/objects/global/GlobalDefinitionsProjectile.scala b/src/main/scala/net/psforever/objects/global/GlobalDefinitionsProjectile.scala index 71687b2d8..28d34a5ca 100644 --- a/src/main/scala/net/psforever/objects/global/GlobalDefinitionsProjectile.scala +++ b/src/main/scala/net/psforever/objects/global/GlobalDefinitionsProjectile.scala @@ -1781,6 +1781,8 @@ object GlobalDefinitionsProjectile { spitfire_aa_ammo_projectile.ProjectileDamageTypeSecondary = DamageType.Splash spitfire_aa_ammo_projectile.InitialVelocity = 100 spitfire_aa_ammo_projectile.Lifespan = 5f + spitfire_aa_ammo_projectile.DamageToArmorFirst = true + spitfire_aa_ammo_projectile.DamageToBattleframeOnly = true ProjectileDefinition.CalculateDerivedFields(spitfire_aa_ammo_projectile) spitfire_aa_ammo_projectile.Modifiers = List( CerberusTurretWrongTarget, @@ -1796,6 +1798,7 @@ object GlobalDefinitionsProjectile { spitfire_ammo_projectile.DegradeMultiplier = 0.5f spitfire_ammo_projectile.InitialVelocity = 100 spitfire_ammo_projectile.Lifespan = .5f + spitfire_ammo_projectile.DamageToArmorFirst = true spitfire_ammo_projectile.DamageToBattleframeOnly = true ProjectileDefinition.CalculateDerivedFields(spitfire_ammo_projectile) diff --git a/src/main/scala/net/psforever/objects/vital/collision/CollisionReason.scala b/src/main/scala/net/psforever/objects/vital/collision/CollisionReason.scala index 40c749d1a..2efb24ae7 100644 --- a/src/main/scala/net/psforever/objects/vital/collision/CollisionReason.scala +++ b/src/main/scala/net/psforever/objects/vital/collision/CollisionReason.scala @@ -99,5 +99,6 @@ object CollisionReason { * Damage is considered `Direct`, however, which defines some resistance. */ val noDamage = new DamageProperties { CausesDamageType = DamageType.Direct + DamageToArmorFirst = true } } diff --git a/src/main/scala/net/psforever/objects/vital/prop/DamageProperties.scala b/src/main/scala/net/psforever/objects/vital/prop/DamageProperties.scala index c7e8c18c3..8051ce3b4 100644 --- a/src/main/scala/net/psforever/objects/vital/prop/DamageProperties.scala +++ b/src/main/scala/net/psforever/objects/vital/prop/DamageProperties.scala @@ -21,7 +21,11 @@ trait DamageProperties private var damageTypeSecondary: DamageType.Value = DamageType.None /** against Infantry targets, damage does not apply to armor damage */ private var damageToHealthOnly: Boolean = false - /** against Vehicle targets, damage does not apply to vehicle shield */ + /** against Infantry targets, damage does not apply to armor damage */ + private var damageToArmorFirst: Boolean = false + /** against Infantry targets, damage applies to armor before it does health; + * regardless of other resistance conditions, non-zero armor is reduced before health; + * should not have priority over the flag for infantry health only */ private var damageToVehicleOnly: Boolean = false /** against battleframe targets, damage does not apply to battleframe robotics shield; * this is equivalent to the property "bfr_permeate_shield" */ @@ -81,6 +85,13 @@ trait DamageProperties DamageToHealthOnly } + def DamageToArmorFirst : Boolean = damageToArmorFirst + + def DamageToArmorFirst_=(armorFirst: Boolean) : Boolean = { + damageToArmorFirst = armorFirst + DamageToArmorFirst + } + def DamageToVehicleOnly : Boolean = damageToVehicleOnly def DamageToVehicleOnly_=(vehicleOnly: Boolean) : Boolean = { diff --git a/src/main/scala/net/psforever/objects/vital/resolution/ResolutionCalculations.scala b/src/main/scala/net/psforever/objects/vital/resolution/ResolutionCalculations.scala index cc6884073..44774f2c8 100644 --- a/src/main/scala/net/psforever/objects/vital/resolution/ResolutionCalculations.scala +++ b/src/main/scala/net/psforever/objects/vital/resolution/ResolutionCalculations.scala @@ -8,13 +8,15 @@ import net.psforever.objects.serverobject.damage.Damageable import net.psforever.objects.sourcing.{PlayerSource, SourceEntry} import net.psforever.objects.vehicles.VehicleSubsystemEntry import net.psforever.objects.vital.base.DamageResolution -import net.psforever.objects.vital.{DamagingActivity, Vitality, InGameHistory} +import net.psforever.objects.vital.{DamagingActivity, InGameHistory, Vitality} import net.psforever.objects.vital.damage.DamageCalculations import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult} import net.psforever.objects.vital.projectile.ProjectileReason import net.psforever.objects.vital.resistance.ResistanceSelection import net.psforever.types.{ExoSuitType, ImplantType} +import scala.annotation.unused + /** * The base for the combining step of all projectile-induced damage calculation function literals. */ @@ -37,13 +39,16 @@ object ResolutionCalculations { type Output = PlanetSideGameObject with FactionAffinity => DamageResult type Form = (DamageCalculations.Selector, ResistanceSelection.Format, DamageInteraction) => Output - def NoDamage(data: DamageInteraction)(a: Int, b: Int): Int = 0 + + def NoDamage(@unused data: DamageInteraction)(@unused a: Int, @unused b: Int): Int = 0 def InfantryDamage(data: DamageInteraction): (Int, Int) => (Int, Int) = { data.target match { case target: PlayerSource => - if(data.cause.source.DamageToHealthOnly) { + if (data.cause.source.DamageToHealthOnly) { DamageToHealthOnly(target.health) + } else if (data.cause.source.DamageToArmorFirst) { + InfantryArmorDamageFirst(target.health, target.armor) } else { InfantryDamageAfterResist(target.health, target.armor) } @@ -84,6 +89,25 @@ object ResolutionCalculations { } } + def InfantryArmorDamageFirst(currentHP: Int, currentArmor: Int)(damages: Int, resistance: Int): (Int, Int) = { + if (damages > 0 && currentHP > 0) { + if (currentArmor <= 0) { + (damages, 0) //no armor; health damage + } else if (damages > resistance) { + val resistedDam = damages - resistance + if (resistedDam <= currentArmor) { + (0, resistedDam) //armor damage + } else { + (resistedDam, currentArmor) //deplete armor; health damage + } + } else { + (0, damages) //too weak; armor damage (less than resistance) + } + } else { + (0, 0) //no damage + } + } + def MaxDamage(data: DamageInteraction): (Int, Int) => (Int, Int) = { data.target match { case target: PlayerSource => @@ -131,7 +155,7 @@ object ResolutionCalculations { } } - def NoApplication(damageValue: Int, data: DamageInteraction)(target: PlanetSideGameObject with FactionAffinity): DamageResult = { + def NoApplication(@unused damageValue: Int, data: DamageInteraction)(target: PlanetSideGameObject with FactionAffinity): DamageResult = { val sameTarget = SourceEntry(target) DamageResult(sameTarget, sameTarget, data) }