Maelstrom (#520)

* initial packet and tests for ChainLashMessage; changed Rounds to RoundPerShot for extra clarity; weapon fire and discharge refactor

* lash damage field for maelstrom; chain lashes on hit with damsage proxy

* mend

* must modify all tests that rely on ephemeral logic like this in the future

* adding modifiers to take the place of target-selected distance calculations performed on damage valuesd; simplying the damage model

* suppressor goes in the suppressor slot
This commit is contained in:
Fate-JH 2020-07-28 00:02:43 -04:00 committed by GitHub
parent ed4a52025c
commit 144804139f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 902 additions and 681 deletions

View file

@ -0,0 +1,64 @@
// Copyright (c) 2020 PSForever
package game
import org.specs2.mutable._
import net.psforever.packet._
import net.psforever.packet.game._
import net.psforever.types.{PlanetSideGUID, Vector3}
import scodec.bits._
class ChainLashMessageTest extends Specification {
val string1 = hex"c5 cafe708880df81e910100000043060"
val string2 = hex"c5 5282e910100000093050"
"decode (1)" in {
PacketCoding.DecodePacket(string1).require match {
case ChainLashMessage(u1a, u1b, u2, u3) =>
u1a.isEmpty mustEqual true
u1b.contains(Vector3(7673.164f, 544.1328f, 14.984375f)) mustEqual true
u2 mustEqual 466
u3 mustEqual List(PlanetSideGUID(1603))
case _ =>
ko
}
}
"decode (2)" in {
PacketCoding.DecodePacket(string2).require match {
case ChainLashMessage(u1a, u1b, u2, u3) =>
u1a.contains(PlanetSideGUID(1445)) mustEqual true
u1b.isEmpty mustEqual true
u2 mustEqual 466
u3 mustEqual List(PlanetSideGUID(1427))
case _ =>
ko
}
}
"encode (1)" in {
val msg = ChainLashMessage(Vector3(7673.164f, 544.1328f, 14.984375f), 466, List(PlanetSideGUID(1603)))
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string1
}
"encode (2)" in {
val msg = ChainLashMessage(PlanetSideGUID(1445), 466, List(PlanetSideGUID(1427)))
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string2
}
"encode (fail; 1)" in {
ChainLashMessage(
Some(PlanetSideGUID(1445)),
Some(Vector3(7673.164f, 544.1328f, 14.984375f)),
466,
List(PlanetSideGUID(1427))
) must throwA[AssertionError]
}
"encode (fail; 2)" in {
ChainLashMessage(None, None, 466, List(PlanetSideGUID(1427))) must throwA[AssertionError]
}
}

View file

@ -926,7 +926,7 @@ class ConverterTest extends Specification {
"not convert a working vehicle" in {
val ams = Vehicle(GlobalDefinitions.ams)
ams.GUID = PlanetSideGUID(413)
ams.Health mustEqual 3000 //not destroyed vehicle
(ams.Health > 0) mustEqual true //not destroyed vehicle
DestroyedVehicleConverter.converter.ConstructorData(ams).isFailure mustEqual true
}

View file

@ -2,14 +2,16 @@
package objects
import net.psforever.objects._
import net.psforever.objects.vital.damage.{DamageCalculations, DamageProfile}
import net.psforever.objects.vital.damage.{DamageCalculations, DamageModifiers, DamageProfile}
import DamageCalculations._
import net.psforever.objects.vital.resistance.ResistanceCalculations
import ResistanceCalculations._
import net.psforever.objects.vital.resolution.ResolutionCalculations
import ResolutionCalculations._
import net.psforever.objects.ballistics._
import net.psforever.objects.vital.Vitality
import net.psforever.objects.definition.{ProjectileDefinition, VehicleDefinition}
import net.psforever.objects.vital.{DamageType, Vitality}
import net.psforever.packet.game.objectcreate.ObjectClass
import net.psforever.types._
import org.specs2.mutable.Specification
@ -17,8 +19,8 @@ class DamageCalculationsTests extends Specification {
"DamageCalculations" should {
val wep = GlobalDefinitions.galaxy_gunship_cannon
val wep_fmode = Tool(wep).FireMode
val wep_prof = wep_fmode.Modifiers.asInstanceOf[DamageProfile]
val proj = wep.ProjectileTypes.head
val wep_prof = wep_fmode.Add
val proj = DamageModelTests.projectile
val proj_prof = proj.asInstanceOf[DamageProfile]
val player = Player(Avatar("TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
val projectile = Projectile(proj, wep, wep_fmode, player, Vector3(2, 2, 0), Vector3.Zero)
@ -29,118 +31,139 @@ class DamageCalculationsTests extends Specification {
projectile,
SourceEntry(target),
target.DamageModel,
Vector3(50, 50, 0)
Vector3(15, 0, 0)
)
"extract no damage numbers" in {
NoDamageAgainst(proj_prof) mustEqual 0
AgainstNothing(proj_prof) mustEqual 0
}
"extract damage against exosuit target" in {
DamageAgainstExoSuit(proj_prof) mustEqual 50
AgainstExoSuit(proj_prof) == proj_prof.Damage0 mustEqual true
}
"extract damage against MAX target" in {
DamageAgainstMaxSuit(proj_prof) mustEqual 75
AgainstMaxSuit(proj_prof) == proj_prof.Damage3 mustEqual true
}
"extract damage against vehicle target" in {
DamageAgainstVehicle(proj_prof) mustEqual 82
AgainstVehicle(proj_prof) == proj_prof.Damage1 mustEqual true
}
"extract damage against aircraft target" in {
DamageAgainstAircraft(proj_prof) mustEqual 82
AgainstAircraft(proj_prof) == proj_prof.Damage2 mustEqual true
}
"extract damage against something" in {
DamageAgainstBFR(proj_prof) mustEqual 66
"extract damage against battleframe robotics" in {
AgainstBFR(proj_prof) == proj_prof.Damage4 mustEqual true
}
"extract a complete damage profile (1)" in {
val result = DamageAgainstVehicle(proj_prof) + DamageAgainstVehicle(wep_prof)
val func: (DamageProfile, List[DamageProfile]) => Int = DamageWithModifiers(DamageAgainstVehicle)
func(proj_prof, List(wep_prof)) mustEqual result
"no degrade damage modifier" in {
DamageModifiers.SameHit.Calculate(100, resprojectile) mustEqual 100
}
"extract a complete damage profile (2)" in {
val result = 2 * DamageAgainstVehicle(proj_prof) + DamageAgainstVehicle(wep_prof)
val func: (DamageProfile, List[DamageProfile]) => Int = DamageWithModifiers(DamageAgainstVehicle)
func(proj_prof, List(proj_prof, wep_prof)) mustEqual result
}
"calculate no distance" in {
NoDistance(resprojectile) mustEqual 0
}
"calculate too far distance" in {
TooFar(resprojectile) mustEqual Float.MaxValue
}
"calculate distance between target and source" in {
DistanceBetweenTargetandSource(resprojectile) mustEqual 67.38225f
}
"calculate distance between target and explosion (splash)" in {
DistanceFromExplosionToTarget(resprojectile) mustEqual 63.031242f
}
"calculate no damage from components" in {
val result = DamageWithModifiers(DamageAgainstVehicle)(proj_prof, List(wep_prof))
val distance = DistanceFromExplosionToTarget(resprojectile)
DamageCalculations.NoDamage(projectile, result, distance) mustEqual 0
}
"calculate degraded damage from components (near)" in {
val projectile_alt = Projectile(
GlobalDefinitions.galaxy_gunship_gun_projectile, //need projectile with degrade
wep,
wep_fmode,
player,
Vector3(2, 2, 0),
Vector3.Zero
"degrade over distance damage modifier (no degrade)" in {
val resprojectile2 = ResolvedProjectile(
ProjectileResolution.Splash,
projectile,
SourceEntry(target),
target.DamageModel,
Vector3(10, 0, 0)
)
val result = DamageWithModifiers(DamageAgainstVehicle)(proj_prof, List(wep_prof))
DirectHitDamageWithDegrade(projectile_alt, result, 0) mustEqual 132
DamageModifiers.DistanceDegrade.Calculate(100, resprojectile2) == 100 mustEqual true
}
"calculate degraded damage from components (medium)" in {
val projectile_alt = Projectile(
GlobalDefinitions.galaxy_gunship_gun_projectile, //need projectile with degrade
wep,
wep_fmode,
player,
Vector3(2, 2, 0),
Vector3.Zero
"degrade over distance damage modifier (some degrade)" in {
val resprojectile2 = ResolvedProjectile(
ProjectileResolution.Splash,
projectile,
SourceEntry(target),
target.DamageModel,
Vector3(100, 0, 0)
)
val result = DamageWithModifiers(DamageAgainstVehicle)(proj_prof, List(wep_prof))
DirectHitDamageWithDegrade(projectile_alt, result, 250) mustEqual 103
val damage = DamageModifiers.DistanceDegrade.Calculate(100, resprojectile2)
damage < 100 && damage > 0 mustEqual true
}
"calculate degraded damage from components (far)" in {
val projectile_alt = Projectile(
GlobalDefinitions.galaxy_gunship_gun_projectile, //need projectile with degrade
wep,
wep_fmode,
player,
Vector3(2, 2, 0),
Vector3.Zero
"degrade over distance damage modifier (zero'd)" in {
val resprojectile2 = ResolvedProjectile(
ProjectileResolution.Splash,
projectile,
SourceEntry(target),
target.DamageModel,
Vector3(1000, 0, 0)
)
val result = DamageWithModifiers(DamageAgainstVehicle)(proj_prof, List(wep_prof))
DirectHitDamageWithDegrade(projectile_alt, result, 1000) mustEqual 0
DamageModifiers.DistanceDegrade.Calculate(100, resprojectile2) == 0 mustEqual true
}
"calculate splash damage from components (near)" in {
val result = DamageWithModifiers(DamageAgainstVehicle)(proj_prof, List(wep_prof))
SplashDamageWithRadialDegrade(projectile, result, 0) mustEqual 132
"degrade at radial distance damage modifier (no degrade)" in {
val resprojectile2 = ResolvedProjectile(
ProjectileResolution.Splash,
projectile,
SourceEntry(target),
target.DamageModel,
Vector3(10, 0, 0)
)
DamageModifiers.RadialDegrade.Calculate(100, resprojectile2) == 100 mustEqual true
}
"calculate splash damage from components (medium)" in {
val result = DamageWithModifiers(DamageAgainstVehicle)(proj_prof, List(wep_prof))
SplashDamageWithRadialDegrade(projectile, result, 5) mustEqual 13
"degrade at radial distance damage modifier (some degrade)" in {
val damage = DamageModifiers.RadialDegrade.Calculate(100, resprojectile)
damage < 100 && damage > 0 mustEqual true
}
"calculate splash damage from components (far)" in {
val result = DamageWithModifiers(DamageAgainstVehicle)(proj_prof, List(wep_prof))
SplashDamageWithRadialDegrade(projectile, result, 6) mustEqual 0
"degrade at radial distance damage modifier (zero'd)" in {
val resprojectile2 = ResolvedProjectile(
ProjectileResolution.Splash,
projectile,
SourceEntry(target),
target.DamageModel,
Vector3(1000, 0, 0)
)
DamageModifiers.RadialDegrade.Calculate(100, resprojectile2) == 0 mustEqual true
}
"lash degrade (no lash; too close)" in {
val resprojectile2 = ResolvedProjectile(
ProjectileResolution.Lash,
projectile,
SourceEntry(target),
target.DamageModel,
Vector3(5, 0, 0) //compared to Vector3(2, 2, 0)
)
DamageModifiers.Lash.Calculate(100, resprojectile2) == 0 mustEqual true
}
"lash degrade (lash)" in {
val resprojectile2 = ResolvedProjectile(
ProjectileResolution.Lash,
projectile,
SourceEntry(target),
target.DamageModel,
Vector3(20, 0, 0)
)
val damage = DamageModifiers.Lash.Calculate(100, resprojectile2)
damage < 100 && damage > 0 mustEqual true
}
"lash degrade (no lash; too far)" in {
val resprojectile2 = ResolvedProjectile(
ProjectileResolution.Lash,
projectile,
SourceEntry(target),
target.DamageModel,
Vector3(1000, 0, 0)
)
DamageModifiers.Lash.Calculate(100, resprojectile2) == 0 mustEqual true
}
"extract a complete damage profile" in {
val result1 = DamageModifiers.RadialDegrade.Calculate(
AgainstVehicle(proj_prof) + AgainstVehicle(wep_prof),
resprojectile
)
val result2 = DamageCalculations.DamageWithModifiers(AgainstVehicle, resprojectile)
result1 mustEqual result2
}
}
}
@ -148,7 +171,7 @@ class DamageCalculationsTests extends Specification {
class ResistanceCalculationsTests extends Specification {
val wep = GlobalDefinitions.galaxy_gunship_cannon
val wep_fmode = Tool(wep).FireMode
val proj = wep.ProjectileTypes.head //GlobalDefinitions.heavy_grenade_projectile
val proj = DamageModelTests.projectile
val player = Player(Avatar("TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
val projectile = Projectile(proj, wep, wep_fmode, player, Vector3(2, 2, 0), Vector3.Zero)
@ -249,9 +272,9 @@ class ResistanceCalculationsTests extends Specification {
}
class ResolutionCalculationsTests extends Specification {
val wep = GlobalDefinitions.suppressor
val wep = GlobalDefinitions.galaxy_gunship_cannon
val wep_fmode = Tool(wep).FireMode
val proj = wep.ProjectileTypes.head
val proj = DamageModelTests.projectile
val player = Player(Avatar("TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
player.Spawn
val projectile = Projectile(proj, wep, wep_fmode, player, Vector3(2, 2, 0), Vector3.Zero)
@ -370,22 +393,22 @@ class ResolutionCalculationsTests extends Specification {
)
VehicleDamageAfterResist(resprojectile2)(50, 10) mustEqual 40
}
}
"calculate resisted damage for vehicle target" in {
VehicleDamageAfterResist(50, 10) mustEqual 40
}
"calculate resisted damage for vehicle target" in {
VehicleDamageAfterResist(50, 10) mustEqual 40
}
"calculate un-resisted damage for vehicle target" in {
VehicleDamageAfterResist(50, 0) mustEqual 50
"calculate un-resisted damage for vehicle target" in {
VehicleDamageAfterResist(50, 0) mustEqual 50
}
}
}
class DamageModelTests extends Specification {
val wep = GlobalDefinitions.suppressor
val wep = GlobalDefinitions.galaxy_gunship_cannon
val wep_tool = Tool(wep)
val wep_fmode = wep_tool.FireMode
val proj = wep.ProjectileTypes.head
val proj = DamageModelTests.projectile
val player = Player(Avatar("TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
player.Spawn
val projectile = Projectile(proj, wep, wep_fmode, player, Vector3(2, 2, 0), Vector3.Zero)
@ -427,7 +450,7 @@ class DamageModelTests extends Specification {
val func: Any => ResolvedProjectile = resprojectile.damage_model.Calculate(resprojectile)
func(tplayer)
tplayer.Health mustEqual 87
tplayer.Health mustEqual 54
tplayer.Armor mustEqual 46
}
@ -448,7 +471,7 @@ class DamageModelTests extends Specification {
resprojectile.damage_model.Calculate(resprojectile, ProjectileResolution.Splash)
func(tplayer)
tplayer.Health mustEqual 98
tplayer.Health mustEqual 65
tplayer.Armor mustEqual 35
}
@ -469,12 +492,12 @@ class DamageModelTests extends Specification {
tplayer.Armor = 0
func(tplayer)
tplayer.Health mustEqual 83
tplayer.Health mustEqual 50
tplayer.Armor mustEqual 0
}
"resolve vehicle targets" in {
val vehicle = Vehicle(GlobalDefinitions.fury)
val vehicle = Vehicle(DamageModelTests.vehicle)
vehicle.Health mustEqual 650
val resprojectile = ResolvedProjectile(
@ -487,11 +510,11 @@ class DamageModelTests extends Specification {
val func: Any => ResolvedProjectile = resprojectile.damage_model.Calculate(resprojectile)
func(vehicle)
vehicle.Health mustEqual 641
vehicle.Health mustEqual 518
}
"resolve vehicle targets (with shields)" in {
val vehicle = Vehicle(GlobalDefinitions.fury)
val vehicle = Vehicle(DamageModelTests.vehicle)
vehicle.Shields = 10
vehicle.Health mustEqual 650
vehicle.Shields mustEqual 10
@ -506,12 +529,12 @@ class DamageModelTests extends Specification {
val func: Any => ResolvedProjectile = resprojectile.damage_model.Calculate(resprojectile)
func(vehicle)
vehicle.Health mustEqual 650
vehicle.Shields mustEqual 1
vehicle.Health mustEqual 528
vehicle.Shields mustEqual 0
}
"resolve vehicle targets (losing shields)" in {
val vehicle = Vehicle(GlobalDefinitions.fury)
val vehicle = Vehicle(DamageModelTests.vehicle)
vehicle.Shields = 10
vehicle.Health mustEqual 650
vehicle.Shields mustEqual 10
@ -526,15 +549,15 @@ class DamageModelTests extends Specification {
val func: Any => ResolvedProjectile = resprojectile.damage_model.Calculate(resprojectile)
func(vehicle)
vehicle.Health mustEqual 650
vehicle.Shields mustEqual 1
vehicle.Health mustEqual 528
vehicle.Shields mustEqual 0
func(vehicle)
vehicle.Health mustEqual 642
vehicle.Health mustEqual 396
vehicle.Shields mustEqual 0
}
"resolve vehicle targets in a specific way" in {
val vehicle = Vehicle(GlobalDefinitions.fury)
val vehicle = Vehicle(DamageModelTests.vehicle)
vehicle.Health mustEqual 650
val resprojectile = ResolvedProjectile(
@ -548,7 +571,34 @@ class DamageModelTests extends Specification {
resprojectile.damage_model.Calculate(resprojectile, ProjectileResolution.Splash)
func(vehicle)
vehicle.Health mustEqual 641
vehicle.Health mustEqual 518
}
}
}
object DamageModelTests {
final val projectile = new ProjectileDefinition(Projectiles.heavy_grenade_projectile.id) {
Damage0 = 50
Damage1 = 82
Damage2 = 82
Damage3 = 75
Damage4 = 66
DamageAtEdge = 0.1f
DamageRadius = 5f
DegradeMultiplier = 0.5f
LashRadius = 5f
ProjectileDamageType = DamageType.Splash
InitialVelocity = 75
Lifespan = 5f
ProjectileDefinition.CalculateDerivedFields(pdef = this)
Modifiers = DamageModifiers.RadialDegrade
}
final val vehicle = new VehicleDefinition(ObjectClass.fury) {
MaxHealth = 650
Damageable = true
Repairable = true
RepairIfDestroyed = false
MaxShields = 130 + 1
}
}

View file

@ -18,7 +18,7 @@ class FireModeTest extends Specification {
obj.AmmoTypeIndices mustEqual Nil
obj.AmmoSlotIndex mustEqual 0
obj.Magazine mustEqual 1
obj.Rounds mustEqual 1
obj.RoundsPerShot mustEqual 1
obj.Chamber mustEqual 1
}
@ -31,14 +31,14 @@ class FireModeTest extends Specification {
tdef.FireModes.head.AmmoTypeIndices += 0
tdef.FireModes.head.AmmoSlotIndex = 0
tdef.FireModes.head.Magazine = 18
tdef.FireModes.head.Rounds = 18
tdef.FireModes.head.RoundsPerShot = 18
tdef.FireModes.head.Chamber = 2
tdef.FireModes += new FireModeDefinition
tdef.FireModes(1).AmmoTypeIndices += 1
tdef.FireModes(1).AmmoTypeIndices += 2
tdef.FireModes(1).AmmoSlotIndex = 1
tdef.FireModes(1).Magazine = 9
tdef.FireModes(1).Rounds = 2
tdef.FireModes(1).RoundsPerShot = 2
tdef.FireModes(1).Chamber = 8
tdef.AmmoTypes.toList mustEqual List(GlobalDefinitions.bullet_9mm, GlobalDefinitions.shotgun_shell)
@ -46,12 +46,12 @@ class FireModeTest extends Specification {
tdef.FireModes.head.AmmoTypeIndices.toList mustEqual List(0)
tdef.FireModes.head.AmmoSlotIndex mustEqual 0
tdef.FireModes.head.Magazine mustEqual 18
tdef.FireModes.head.Rounds mustEqual 18
tdef.FireModes.head.RoundsPerShot mustEqual 18
tdef.FireModes.head.Chamber mustEqual 2
tdef.FireModes(1).AmmoTypeIndices.toList mustEqual List(1, 2)
tdef.FireModes(1).AmmoSlotIndex mustEqual 1
tdef.FireModes(1).Magazine mustEqual 9
tdef.FireModes(1).Rounds mustEqual 2
tdef.FireModes(1).RoundsPerShot mustEqual 2
tdef.FireModes(1).Chamber mustEqual 8
}
@ -59,7 +59,7 @@ class FireModeTest extends Specification {
val obj = Tool(GlobalDefinitions.beamer) //see EquipmentTest
obj.FireMode.isInstanceOf[FireModeDefinition] mustEqual true
obj.Magazine mustEqual 16
obj.FireMode.Rounds mustEqual 1
obj.FireMode.RoundsPerShot mustEqual 1
obj.FireMode.Chamber mustEqual 1
obj.Magazine mustEqual 16
@ -77,7 +77,7 @@ class FireModeTest extends Specification {
obj.AmmoTypeIndices mustEqual Nil
obj.AmmoSlotIndex mustEqual 0
obj.Magazine mustEqual 1
obj.Rounds mustEqual 1
obj.RoundsPerShot mustEqual 1
obj.Chamber mustEqual 1
}
@ -85,7 +85,7 @@ class FireModeTest extends Specification {
val obj = Tool(GlobalDefinitions.flechette) //see EquipmentTest
obj.FireMode.isInstanceOf[PelletFireModeDefinition] mustEqual true
obj.Magazine mustEqual 12
obj.FireMode.Rounds mustEqual 1
obj.FireMode.RoundsPerShot mustEqual 1
obj.FireMode.Chamber mustEqual 8
obj.Magazine mustEqual 12
@ -110,7 +110,7 @@ class FireModeTest extends Specification {
obj.AmmoTypeIndices mustEqual Nil
obj.AmmoSlotIndex mustEqual 0
obj.Magazine mustEqual 1
obj.Rounds mustEqual 1
obj.RoundsPerShot mustEqual 1
obj.Chamber mustEqual 1
}
@ -118,7 +118,7 @@ class FireModeTest extends Specification {
val obj = Tool(GlobalDefinitions.magcutter) //see EquipmentTest
obj.FireMode.isInstanceOf[InfiniteFireModeDefinition] mustEqual true
obj.Magazine mustEqual 1
obj.FireMode.Rounds mustEqual 1
obj.FireMode.RoundsPerShot mustEqual 1
obj.FireMode.Chamber mustEqual 1
obj.Magazine mustEqual 1