* refactored WSA code handling HitMessage, handling SplashMessage, and handling LashMessage; modified projectiles for future functionality

* players can die from being shot now; the damage model is simplistic since main goal was to write around the potential for negative damage ('healed from getting shot'); HitHint works correctly; dedicated AvatarService channel for each avatar helps reduce message spam

* vehicle destruction, and replacement with lightweight wreckage objects upon continent join; made flushing vehicle terminal more accessible

* simple work on vehicle shield charging (amp station benefit) (that's my commit story and I'm sticking with it)

* a flexible calculation workflow that can be applied, grabbing damage information, resistance information, and then combining it with a resolution function; players and vehicles have resistance values; removed redundant damage calculations from WSA

* broke up DamageCalculations, ResistanceCalculations, and ResolutionCalculations into packages under vital; fixed an error with exo-suit calculation resistances; events for dealing with synchronized player and vehicle damage calculations and building the papertrail of those damages; updating codecov.yml file for ignore classes

* added tests for various components (damage model, destroyed vehicle converter, vitality, etc..) and some functionality improvements

* added a field to keep track of how projectiles will be attributed at the time of target death
This commit is contained in:
Fate-JH 2018-07-30 09:28:45 -04:00 committed by GitHub
parent fc78d53ecb
commit 7901f66324
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
52 changed files with 3045 additions and 267 deletions

View file

@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever
package objects
import net.psforever.objects.definition.converter.{ACEConverter, CharacterSelectConverter, REKConverter}
import net.psforever.objects.definition.converter.{ACEConverter, CharacterSelectConverter, DestroyedVehicleConverter, REKConverter}
import net.psforever.objects._
import net.psforever.objects.definition._
import net.psforever.objects.equipment.CItem.{DeployedItem, Unit}
@ -9,6 +9,7 @@ import net.psforever.objects.equipment._
import net.psforever.objects.inventory.InventoryTile
import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.objects.serverobject.tube.SpawnTube
import net.psforever.objects.vehicles.DestroyedVehicle
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.packet.game.objectcreate._
import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire, Vector3}
@ -368,7 +369,40 @@ class ConverterTest extends Specification {
ams.Utilities(4)().GUID = PlanetSideGUID(417)
ams.Definition.Packet.ConstructorData(ams).isSuccess mustEqual true
ok //TODO write more of this test
}
"convert to packet (3)" in {
val
ams = Vehicle(GlobalDefinitions.ams)
ams.GUID = PlanetSideGUID(413)
ams.Health = 0 //destroyed vehicle
ams.Definition.Packet.ConstructorData(ams).isSuccess mustEqual true
//did not initialize the utilities, but the converter did not fail
}
}
"DestroyedVehicle" should {
"not convert a working vehicle" in {
val ams = Vehicle(GlobalDefinitions.ams)
ams.GUID = PlanetSideGUID(413)
ams.Health mustEqual 3000 //not destroyed vehicle
DestroyedVehicleConverter.converter.ConstructorData(ams).isFailure mustEqual true
}
"convert to packet" in {
val ams = Vehicle(GlobalDefinitions.ams)
ams.GUID = PlanetSideGUID(413)
ams.Health = 0
DestroyedVehicleConverter.converter.ConstructorData(ams).isSuccess mustEqual true
//did not initialize the utilities, but the converter did not fail
}
"not convert into a detailed packet" in {
val ams = Vehicle(GlobalDefinitions.ams)
ams.GUID = PlanetSideGUID(413)
ams.Health = 0
DestroyedVehicleConverter.converter.DetailedConstructorData(ams).isFailure mustEqual true
}
}
}

View file

@ -0,0 +1,416 @@
// Copyright (c) 2017 PSForever
package objects
import net.psforever.objects._
import net.psforever.objects.vital.damage.{DamageCalculations, 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.types._
import org.specs2.mutable.Specification
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 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)
val target = Vehicle(GlobalDefinitions.fury)
target.Position = Vector3(10, 0, 0)
val resprojectile = ResolvedProjectile(ProjectileResolution.Splash, projectile, SourceEntry(target), target.DamageModel, Vector3(50, 50, 0))
"extract no damage numbers" in {
NoDamageAgainst(proj_prof) mustEqual 0
}
"extract damage against exosuit target" in {
DamageAgainstExoSuit(proj_prof) mustEqual 50
}
"extract damage against MAX target" in {
DamageAgainstMaxSuit(proj_prof) mustEqual 75
}
"extract damage against vehicle target" in {
DamageAgainstVehicle(proj_prof) mustEqual 82
}
"extract damage against aircraft target" in {
DamageAgainstAircraft(proj_prof) mustEqual 82
}
"extract damage against something" in {
DamageAgainstUnknown(proj_prof) mustEqual 66
}
"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
}
"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 10
}
"calculate distance between target and explosion (splash)" in {
DistanceFromExplosionToTarget(resprojectile) mustEqual 64.03124f
}
"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)
val result = DamageWithModifiers(DamageAgainstVehicle)(proj_prof, List(wep_prof))
DirectHitDamageWithDegrade(projectile_alt, result, 0) mustEqual 132
}
"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)
val result = DamageWithModifiers(DamageAgainstVehicle)(proj_prof, List(wep_prof))
DirectHitDamageWithDegrade(projectile_alt, result, 250) mustEqual 103
}
"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)
val result = DamageWithModifiers(DamageAgainstVehicle)(proj_prof, List(wep_prof))
DirectHitDamageWithDegrade(projectile_alt, result, 1000) mustEqual 0
}
"calculate splash damage from components (near)" in {
val result = DamageWithModifiers(DamageAgainstVehicle)(proj_prof, List(wep_prof))
SplashDamageWithRadialDegrade(projectile, result, 0) mustEqual 264
}
"calculate splash damage from components (medium)" in {
val result = DamageWithModifiers(DamageAgainstVehicle)(proj_prof, List(wep_prof))
SplashDamageWithRadialDegrade(projectile, result, 5) mustEqual 145
}
"calculate splash damage from components (far)" in {
val result = DamageWithModifiers(DamageAgainstVehicle)(proj_prof, List(wep_prof))
SplashDamageWithRadialDegrade(projectile, result, 6) mustEqual 0
}
}
}
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 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)
"ResistanceCalculations" should {
"ignore all targets" in {
val target = Vehicle(GlobalDefinitions.fury)
val resprojectile = ResolvedProjectile(ProjectileResolution.Splash, projectile, SourceEntry(target), target.DamageModel, Vector3.Zero)
InvalidTarget(resprojectile).isFailure mustEqual true
}
"discern standard infantry targets" in {
val target = player
val resprojectile = ResolvedProjectile(ProjectileResolution.Splash, projectile, SourceEntry(target), target.DamageModel, Vector3.Zero)
ValidInfantryTarget(resprojectile).isSuccess mustEqual true
ValidMaxTarget(resprojectile).isSuccess mustEqual false
ValidVehicleTarget(resprojectile).isSuccess mustEqual false
ValidAircraftTarget(resprojectile).isSuccess mustEqual false
}
"discern mechanized infantry targets" in {
val target = Player(Avatar("TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
target.ExoSuit = ExoSuitType.MAX
val resprojectile = ResolvedProjectile(ProjectileResolution.Splash, projectile, SourceEntry(target), target.DamageModel, Vector3.Zero)
ValidInfantryTarget(resprojectile).isSuccess mustEqual false
ValidMaxTarget(resprojectile).isSuccess mustEqual true
ValidVehicleTarget(resprojectile).isSuccess mustEqual false
ValidAircraftTarget(resprojectile).isSuccess mustEqual false
}
"discern ground vehicle targets" in {
val target = Vehicle(GlobalDefinitions.fury)
val resprojectile = ResolvedProjectile(ProjectileResolution.Splash, projectile, SourceEntry(target), target.DamageModel, Vector3.Zero)
ValidInfantryTarget(resprojectile).isSuccess mustEqual false
ValidMaxTarget(resprojectile).isSuccess mustEqual false
ValidVehicleTarget(resprojectile).isSuccess mustEqual true
ValidAircraftTarget(resprojectile).isSuccess mustEqual false
}
"discern flying vehicle targets" in {
val target = Vehicle(GlobalDefinitions.mosquito)
val resprojectile = ResolvedProjectile(ProjectileResolution.Splash, projectile, SourceEntry(target), target.DamageModel, Vector3.Zero)
ValidInfantryTarget(resprojectile).isSuccess mustEqual false
ValidMaxTarget(resprojectile).isSuccess mustEqual false
ValidVehicleTarget(resprojectile).isSuccess mustEqual false
ValidAircraftTarget(resprojectile).isSuccess mustEqual true
}
"extract no resistance values" in {
NoResistExtractor(SourceEntry(player)) mustEqual 0
}
"extract resistance values from exo-suit" in {
val pSource = PlayerSource(player)
ExoSuitDirectExtractor(pSource) mustEqual 4
ExoSuitSplashExtractor(pSource) mustEqual 15
ExoSuitAggravatedExtractor(pSource) mustEqual 8
ExoSuitRadiationExtractor(pSource) mustEqual 0
}
"extract resistance values from vehicle" in {
val vSource = VehicleSource(Vehicle(GlobalDefinitions.fury))
VehicleDirectExtractor(vSource) mustEqual 0
VehicleSplashExtractor(vSource) mustEqual 0
VehicleAggravatedExtractor(vSource) mustEqual 0
VehicleRadiationExtractor(vSource) mustEqual 0
}
}
}
class ResolutionCalculationsTests extends Specification {
val wep = GlobalDefinitions.suppressor
val wep_fmode = Tool(wep).FireMode
val proj = wep.ProjectileTypes.head
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)
"ResolutionCalculations" should {
"calculate no damage" in {
val target = player
val resprojectile = ResolvedProjectile(ProjectileResolution.Splash, projectile, SourceEntry(target), target.DamageModel, Vector3.Zero)
ResolutionCalculations.NoDamage(resprojectile)(50,50) mustEqual 0
}
"calculate no infantry damage for vehicles" in {
val target1 = Vehicle(GlobalDefinitions.fury) //!
val resprojectile1 = ResolvedProjectile(ProjectileResolution.Splash, projectile, SourceEntry(target1), target1.DamageModel, Vector3.Zero)
InfantryDamageAfterResist(resprojectile1)(50, 10) mustEqual (0,0)
val target2 = player
val resprojectile2 = ResolvedProjectile(ProjectileResolution.Splash, projectile, SourceEntry(target2), target2.DamageModel, Vector3.Zero)
InfantryDamageAfterResist(resprojectile2)(50, 10) mustEqual (40,10)
}
"calculate health and armor damage for infantry target" in {
InfantryDamageAfterResist(100,100)(50, 10) mustEqual (40,10)
}
"calculate health and armor damage, with bonus damage, for infantry target" in {
//health = 100, armor = 5 -> resist 10 but only have 5, so rollover extra -> damages (40+5, 5)
InfantryDamageAfterResist(100,5)(50, 10) mustEqual (45,5)
}
"calculate health damage for infantry target" in {
//health = 100, armor = 0
InfantryDamageAfterResist(100,0)(50, 10) mustEqual (50,0)
}
"calculate armor damage for infantry target" in {
//resistance > damage
InfantryDamageAfterResist(100,100)(50, 60) mustEqual (0,50)
}
val player2 = Player(Avatar("TestCharacter2", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
player2.ExoSuit = ExoSuitType.MAX
player2.Spawn
"calculate no max damage for vehicles" in {
val target1 = Vehicle(GlobalDefinitions.fury) //!
val resprojectile1 = ResolvedProjectile(ProjectileResolution.Splash, projectile, SourceEntry(target1), target1.DamageModel, Vector3.Zero)
MaxDamageAfterResist(resprojectile1)(50, 10) mustEqual (0,0)
val target2 = player2
val resprojectile2 = ResolvedProjectile(ProjectileResolution.Splash, projectile, SourceEntry(target2), target2.DamageModel, Vector3.Zero)
MaxDamageAfterResist(resprojectile2)(50, 10) mustEqual (0,40)
}
"calculate health and armor damage for max target" in {
MaxDamageAfterResist(100,5)(50, 10) mustEqual (35,5)
}
"calculate health damage for max target" in {
//health = 100, armor = 0
MaxDamageAfterResist(100,0)(50, 10) mustEqual (40,0)
}
"calculate armor damage for max target" in {
//resistance > damage
MaxDamageAfterResist(100,100)(50, 10) mustEqual (0,40)
}
"do not care if target is infantry for vehicle calculations" in {
val target1 = player
val resprojectile1 = ResolvedProjectile(ProjectileResolution.Splash, projectile, SourceEntry(target1), target1.DamageModel, Vector3.Zero)
VehicleDamageAfterResist(resprojectile1)(50, 10) mustEqual 40
val target2 = Vehicle(GlobalDefinitions.fury) //!
val resprojectile2 = ResolvedProjectile(ProjectileResolution.Splash, projectile, SourceEntry(target2), target2.DamageModel, Vector3.Zero)
VehicleDamageAfterResist(resprojectile2)(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
}
}
class DamageModelTests extends Specification {
val wep = GlobalDefinitions.suppressor
val wep_tool = Tool(wep)
val wep_fmode = wep_tool.FireMode
val proj = wep.ProjectileTypes.head
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)
"DamageModel" should {
"be a part of vitality" in {
player.isInstanceOf[Vitality] mustEqual true
try {
player.getClass.getDeclaredMethod("DamageModel").hashCode()
}
catch {
case _ : Exception =>
ko //the method doesn't exist
}
wep_tool.isInstanceOf[Vitality] mustEqual false
try {
wep_tool.getClass.getDeclaredMethod("DamageModel").hashCode()
ko
}
catch {
case _ : Exception =>
ok //the method doesn't exist
}
ok
}
"resolve infantry targets" in {
val tplayer = Player(Avatar("TestCharacter2", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
tplayer.Spawn
tplayer.Health mustEqual 100
tplayer.Armor mustEqual 50
val resprojectile = ResolvedProjectile(ProjectileResolution.Hit, projectile, SourceEntry(tplayer), tplayer.DamageModel, Vector3.Zero)
val func : (Any)=>Unit = resprojectile.damage_model.Calculate(resprojectile)
func(tplayer)
tplayer.Health mustEqual 87
tplayer.Armor mustEqual 46
}
"resolve infantry targets in a specific way" in {
val tplayer = Player(Avatar("TestCharacter2", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
tplayer.Spawn
tplayer.Health mustEqual 100
tplayer.Armor mustEqual 50
val resprojectile = ResolvedProjectile(ProjectileResolution.Hit, projectile, SourceEntry(tplayer), tplayer.DamageModel, Vector3.Zero)
val func : (Any)=>Unit = resprojectile.damage_model.Calculate(resprojectile, ProjectileResolution.Splash)
func(tplayer)
tplayer.Health mustEqual 81
tplayer.Armor mustEqual 35
}
"resolve infantry targets, with damage overflow" in {
val tplayer = Player(Avatar("TestCharacter2", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
tplayer.Spawn
tplayer.Health mustEqual 100
tplayer.Armor mustEqual 50
val resprojectile = ResolvedProjectile(ProjectileResolution.Hit, projectile, SourceEntry(tplayer), tplayer.DamageModel, Vector3.Zero)
val func : (Any)=>Unit = resprojectile.damage_model.Calculate(resprojectile)
tplayer.Armor = 0
func(tplayer)
tplayer.Health mustEqual 83
tplayer.Armor mustEqual 0
}
"resolve vehicle targets" in {
val vehicle = Vehicle(GlobalDefinitions.fury)
vehicle.Health mustEqual 650
val resprojectile = ResolvedProjectile(ProjectileResolution.Hit, projectile, SourceEntry(vehicle), vehicle.DamageModel, Vector3.Zero)
val func : (Any)=>Unit = resprojectile.damage_model.Calculate(resprojectile)
func(vehicle)
vehicle.Health mustEqual 641
}
"resolve vehicle targets (with shields)" in {
val vehicle = Vehicle(GlobalDefinitions.fury)
vehicle.Shields = 10
vehicle.Health mustEqual 650
vehicle.Shields mustEqual 10
val resprojectile = ResolvedProjectile(ProjectileResolution.Hit, projectile, SourceEntry(vehicle), vehicle.DamageModel, Vector3.Zero)
val func : (Any)=>Unit = resprojectile.damage_model.Calculate(resprojectile)
func(vehicle)
vehicle.Health mustEqual 650
vehicle.Shields mustEqual 1
}
"resolve vehicle targets (losing shields)" in {
val vehicle = Vehicle(GlobalDefinitions.fury)
vehicle.Shields = 10
vehicle.Health mustEqual 650
vehicle.Shields mustEqual 10
val resprojectile = ResolvedProjectile(ProjectileResolution.Hit, projectile, SourceEntry(vehicle), vehicle.DamageModel, Vector3.Zero)
val func : (Any)=>Unit = resprojectile.damage_model.Calculate(resprojectile)
func(vehicle)
vehicle.Health mustEqual 650
vehicle.Shields mustEqual 1
func(vehicle)
vehicle.Health mustEqual 642
vehicle.Shields mustEqual 0
}
"resolve vehicle targets in a specific way" in {
val vehicle = Vehicle(GlobalDefinitions.fury)
vehicle.Health mustEqual 650
val resprojectile = ResolvedProjectile(ProjectileResolution.Hit, projectile, SourceEntry(vehicle), vehicle.DamageModel, Vector3.Zero)
val func : (Any)=>Unit = resprojectile.damage_model.Calculate(resprojectile, ProjectileResolution.Splash)
func(vehicle)
vehicle.Health mustEqual 632
}
}
}

View file

@ -1,19 +1,29 @@
// Copyright (c) 2017 PSForever
package objects
import net.psforever.objects.{GlobalDefinitions, LocalProjectile, Tool}
import net.psforever.objects.ballistics.{DamageType, Projectile, ProjectileResolution, Projectiles}
import net.psforever.objects._
import net.psforever.objects.ballistics._
import net.psforever.objects.definition.ProjectileDefinition
import net.psforever.types.Vector3
import net.psforever.objects.serverobject.mblocker.Locker
import net.psforever.objects.vital.DamageType
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types._
import org.specs2.mutable.Specification
class ProjectileTest extends Specification {
val player = Player(Avatar("TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
val fury = Vehicle(GlobalDefinitions.fury)
"LocalProjectile" should {
"construct" in {
val obj = new LocalProjectile() //since they're just placeholders, they only need to construct
obj.Definition.ObjectId mustEqual 0
obj.Definition.Name mustEqual "projectile"
}
"local projectile range" in {
Projectile.BaseUID < Projectile.RangeUID mustEqual true
}
}
"ProjectileDefinition" should {
@ -151,48 +161,156 @@ class ProjectileTest extends Specification {
}
}
"Projectile" should {
"construct" in {
val beamer_wep = Tool(GlobalDefinitions.beamer)
val projectile = beamer_wep.Projectile
val obj = Projectile(projectile, beamer_wep.Definition, Vector3(1.2f, 3.4f, 5.6f), Vector3(0.2f, 0.4f, 0.6f))
"SourceEntry" should {
"construct for players" in {
SourceEntry(player) match {
case o : PlayerSource =>
o.Name mustEqual "TestCharacter"
o.Faction mustEqual PlanetSideEmpire.TR
o.Seated mustEqual false
o.ExoSuit mustEqual ExoSuitType.Standard
o.Health mustEqual 0
o.Armor mustEqual 0
o.Definition mustEqual GlobalDefinitions.avatar
o.Position mustEqual Vector3.Zero
o.Orientation mustEqual Vector3.Zero
o.Velocity mustEqual None
case _ =>
ko
}
}
obj.profile mustEqual beamer_wep.Projectile
obj.tool_def mustEqual GlobalDefinitions.beamer
"construct for vehicles" in {
SourceEntry(fury) match {
case o : VehicleSource =>
o.Name mustEqual "Fury"
o.Faction mustEqual PlanetSideEmpire.TR
o.Definition mustEqual GlobalDefinitions.fury
o.Health mustEqual 650
o.Shields mustEqual 0
o.Position mustEqual Vector3.Zero
o.Orientation mustEqual Vector3.Zero
o.Velocity mustEqual None
case _ =>
ko
}
}
"construct for generic object" in {
val obj = Locker()
SourceEntry(obj) match {
case o : ObjectSource =>
o.obj mustEqual obj
o.Name mustEqual "Mb Locker"
o.Faction mustEqual PlanetSideEmpire.NEUTRAL
o.Definition mustEqual GlobalDefinitions.mb_locker
o.Position mustEqual Vector3.Zero
o.Orientation mustEqual Vector3.Zero
o.Velocity mustEqual None
case _ =>
ko
}
}
"contain timely information" in {
val obj = Player(Avatar("TestCharacter-alt", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
obj.VehicleSeated = Some(PlanetSideGUID(1))
obj.Position = Vector3(1.2f, 3.4f, 5.6f)
obj.Orientation = Vector3(2.1f, 4.3f, 6.5f)
obj.Velocity = Some(Vector3(1.1f, 2.2f, 3.3f))
val sobj = SourceEntry(obj)
obj.VehicleSeated = None
obj.Position = Vector3.Zero
obj.Orientation = Vector3.Zero
obj.Velocity = None
obj.ExoSuit = ExoSuitType.Agile
sobj match {
case o : PlayerSource =>
o.Name mustEqual "TestCharacter-alt"
o.Faction mustEqual PlanetSideEmpire.TR
o.Seated mustEqual true
o.ExoSuit mustEqual ExoSuitType.Standard
o.Definition mustEqual GlobalDefinitions.avatar
o.Position mustEqual Vector3(1.2f, 3.4f, 5.6f)
o.Orientation mustEqual Vector3(2.1f, 4.3f, 6.5f)
o.Velocity mustEqual Some(Vector3(1.1f, 2.2f, 3.3f))
case _ =>
ko
}
}
}
"Projectile" should {
val beamer_def = GlobalDefinitions.beamer
val beamer_wep = Tool(beamer_def)
val firemode = beamer_wep.FireMode
val projectile = beamer_wep.Projectile
"construct" in {
val obj = Projectile(beamer_wep.Projectile, beamer_wep.Definition, beamer_wep.FireMode, PlayerSource(player), beamer_def.ObjectId, Vector3(1.2f, 3.4f, 5.6f), Vector3(0.2f, 0.4f, 0.6f))
obj.profile mustEqual projectile
obj.tool_def mustEqual beamer_def
obj.fire_mode mustEqual firemode
obj.owner match {
case _ : PlayerSource =>
ok
case _ =>
ko
}
obj.attribute_to mustEqual obj.tool_def.ObjectId
obj.shot_origin mustEqual Vector3(1.2f, 3.4f, 5.6f)
obj.shot_angle mustEqual Vector3(0.2f, 0.4f, 0.6f)
obj.resolution mustEqual ProjectileResolution.Unresolved
obj.fire_time <= System.nanoTime mustEqual true
obj.hit_time mustEqual 0
obj.isResolved mustEqual false
}
"construct (different attribute)" in {
val obj1 = Projectile(beamer_wep.Projectile, beamer_wep.Definition, beamer_wep.FireMode, player, Vector3(1.2f, 3.4f, 5.6f), Vector3(0.2f, 0.4f, 0.6f))
obj1.attribute_to mustEqual obj1.tool_def.ObjectId
val obj2 = Projectile(beamer_wep.Projectile, beamer_wep.Definition, beamer_wep.FireMode, PlayerSource(player), 65, Vector3(1.2f, 3.4f, 5.6f), Vector3(0.2f, 0.4f, 0.6f))
obj2.attribute_to == obj2.tool_def.ObjectId mustEqual false
obj2.attribute_to mustEqual 65
}
"resolve" in {
val beamer_wep = Tool(GlobalDefinitions.beamer)
val projectile = beamer_wep.Projectile
val obj = Projectile(projectile, beamer_wep.Definition, Vector3(1.2f, 3.4f, 5.6f), Vector3(0.2f, 0.4f, 0.6f))
val obj2 = obj.Resolve(ProjectileResolution.MissedShot)
val obj = Projectile(projectile, beamer_def, firemode, PlayerSource(player), beamer_def.ObjectId, Vector3.Zero, Vector3.Zero)
obj.isResolved mustEqual false
obj.isMiss mustEqual false
obj.resolution mustEqual ProjectileResolution.Unresolved
obj.fire_time <= System.nanoTime mustEqual true
obj.hit_time mustEqual 0
obj2.resolution mustEqual ProjectileResolution.MissedShot
obj2.fire_time == obj.fire_time mustEqual true
obj2.hit_time <= System.nanoTime mustEqual true
obj2.fire_time <= obj2.hit_time mustEqual true
obj.Resolve()
obj.isResolved mustEqual true
obj.isMiss mustEqual false
}
"resolve, with coordinates" in {
val beamer_wep = Tool(GlobalDefinitions.beamer)
val projectile = beamer_wep.Projectile
val obj = Projectile(projectile, beamer_wep.Definition, Vector3(1.2f, 3.4f, 5.6f), Vector3(0.2f, 0.4f, 0.6f))
val obj2 = obj.Resolve(Vector3(7.2f, 8.4f, 9.6f), Vector3(1.2f, 1.4f, 1.6f), ProjectileResolution.Resolved)
"missed" in {
val obj = Projectile(projectile, beamer_def, firemode, PlayerSource(player), beamer_def.ObjectId, Vector3.Zero, Vector3.Zero)
obj.isResolved mustEqual false
obj.isMiss mustEqual false
obj.resolution mustEqual ProjectileResolution.Unresolved
obj.current.Position mustEqual Vector3.Zero
obj.current.Orientation mustEqual Vector3.Zero
obj2.resolution mustEqual ProjectileResolution.Resolved
obj2.current.Position mustEqual Vector3(7.2f, 8.4f, 9.6f)
obj2.current.Orientation mustEqual Vector3(1.2f, 1.4f, 1.6f)
obj.Miss()
obj.isResolved mustEqual true
obj.isMiss mustEqual true
}
}
"ResolvedProjectile" should {
val beamer_wep = Tool(GlobalDefinitions.beamer)
val p_source = PlayerSource(player)
val player2 = Player(Avatar("TestTarget", PlanetSideEmpire.NC, CharacterGender.Female, 1, CharacterVoice.Mute))
val p2_source = PlayerSource(player2)
val projectile = Projectile(beamer_wep.Projectile, GlobalDefinitions.beamer, beamer_wep.FireMode, p_source, GlobalDefinitions.beamer.ObjectId, Vector3.Zero, Vector3.Zero)
val fury_dm = fury.DamageModel
"construct" in {
val obj = ResolvedProjectile(ProjectileResolution.Hit, projectile, PlayerSource(player2), fury_dm, Vector3(1.2f, 3.4f, 5.6f), 123456L)
obj.resolution mustEqual ProjectileResolution.Hit
obj.projectile mustEqual projectile
obj.target mustEqual p2_source
obj.damage_model mustEqual fury.DamageModel
obj.hit_pos mustEqual Vector3(1.2f, 3.4f, 5.6f)
obj.hit_time mustEqual 123456L
}
}
}

View file

@ -4,14 +4,16 @@ package objects
import akka.actor.Props
import base.ActorTest
import net.psforever.objects._
import net.psforever.objects.ballistics.{PlayerSource, Projectile, ProjectileResolution, ResolvedProjectile}
import net.psforever.objects.definition.{SeatDefinition, VehicleDefinition}
import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.vehicles._
import net.psforever.objects.vital.{VehicleShieldCharge, Vitality}
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types.{CharacterVoice, ExoSuitType}
import net.psforever.types._
import org.specs2.mutable._
import scala.concurrent.duration.Duration
import scala.concurrent.duration._
class VehicleTest extends Specification {
import VehicleTest._
@ -611,6 +613,103 @@ class VehicleControlMountingOwnedUnlockedDriverSeatTest extends ActorTest {
}
}
class VehicleControlShieldsChargingTest extends ActorTest {
val vehicle = Vehicle(GlobalDefinitions.fury)
vehicle.GUID = PlanetSideGUID(10)
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
"charge vehicle shields" in {
assert(vehicle.Shields == 0)
assert(!vehicle.History.exists({p => p.isInstanceOf[VehicleShieldCharge]}))
vehicle.Actor ! Vehicle.ChargeShields(15)
val msg = receiveOne(500 milliseconds)
assert(msg.isInstanceOf[Vehicle.UpdateShieldsCharge])
assert(vehicle.Shields == 15)
assert(vehicle.History.exists({p => p.isInstanceOf[VehicleShieldCharge]}))
}
}
class VehicleControlShieldsNotChargingVehicleDeadTest extends ActorTest {
val vehicle = Vehicle(GlobalDefinitions.fury)
vehicle.GUID = PlanetSideGUID(10)
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
"not charge vehicle shields if the vehicle is destroyed" in {
assert(vehicle.Health > 0)
vehicle.Health = 0
assert(vehicle.Health == 0)
assert(vehicle.Shields == 0)
assert(!vehicle.History.exists({p => p.isInstanceOf[VehicleShieldCharge]}))
vehicle.Actor ! Vehicle.ChargeShields(15)
expectNoMsg(1 seconds)
assert(vehicle.Shields == 0)
assert(!vehicle.History.exists({p => p.isInstanceOf[VehicleShieldCharge]}))
}
}
class VehicleControlShieldsNotChargingVehicleShieldsFullTest extends ActorTest {
val vehicle = Vehicle(GlobalDefinitions.fury)
vehicle.GUID = PlanetSideGUID(10)
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
"not charge vehicle shields if the vehicle is destroyed" in {
assert(vehicle.Shields == 0)
vehicle.Shields = vehicle.MaxShields
assert(vehicle.Shields == vehicle.MaxShields)
assert(!vehicle.History.exists({p => p.isInstanceOf[VehicleShieldCharge]}))
vehicle.Actor ! Vehicle.ChargeShields(15)
expectNoMsg(1 seconds)
assert(!vehicle.History.exists({p => p.isInstanceOf[VehicleShieldCharge]}))
}
}
class VehicleControlShieldsNotChargingTooEarlyTest extends ActorTest {
val vehicle = Vehicle(GlobalDefinitions.fury)
vehicle.GUID = PlanetSideGUID(10)
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
"charge vehicle shields" in {
assert(vehicle.Shields == 0)
vehicle.Actor ! Vehicle.ChargeShields(15)
val msg = receiveOne(200 milliseconds)
assert(msg.isInstanceOf[Vehicle.UpdateShieldsCharge])
assert(vehicle.Shields == 15)
vehicle.Actor ! Vehicle.ChargeShields(15)
expectNoMsg(200 milliseconds)
assert(vehicle.Shields == 15)
}
}
class VehicleControlShieldsNotChargingDamagedTest extends ActorTest {
val vehicle = Vehicle(GlobalDefinitions.fury)
vehicle.GUID = PlanetSideGUID(10)
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
//
val beamer_wep = Tool(GlobalDefinitions.beamer)
val p_source = PlayerSource( Player(Avatar("TestTarget", PlanetSideEmpire.NC, CharacterGender.Female, 1, CharacterVoice.Mute)) )
val projectile = Projectile(beamer_wep.Projectile, GlobalDefinitions.beamer, beamer_wep.FireMode, p_source, GlobalDefinitions.beamer.ObjectId, Vector3.Zero, Vector3.Zero)
val fury_dm = Vehicle(GlobalDefinitions.fury).DamageModel
val obj = ResolvedProjectile(ProjectileResolution.Hit, projectile, p_source, fury_dm, Vector3(1.2f, 3.4f, 5.6f), System.nanoTime)
"charge vehicle shields" in {
assert(vehicle.Shields == 0)
vehicle.Actor ! Vitality.Damage({case v : Vehicle => v.History(obj)})
val msg = receiveOne(200 milliseconds)
assert(msg.isInstanceOf[Vitality.DamageResolution])
assert(vehicle.Shields == 0)
vehicle.Actor ! Vehicle.ChargeShields(15)
expectNoMsg(200 milliseconds)
assert(vehicle.Shields == 0)
}
}
object VehicleTest {
import net.psforever.objects.Avatar
import net.psforever.types.{CharacterGender, PlanetSideEmpire}

View file

@ -0,0 +1,83 @@
// Copyright (c) 2017 PSForever
package objects
import net.psforever.objects.ballistics._
import net.psforever.objects._
import net.psforever.objects.vital._
import net.psforever.types._
import org.specs2.mutable.Specification
class VitalityTest extends Specification {
"Vitality" should {
val wep = GlobalDefinitions.galaxy_gunship_cannon
val wep_fmode = Tool(wep).FireMode
val proj = wep.ProjectileTypes.head
val vehicle = Vehicle(GlobalDefinitions.fury)
val vSource = VehicleSource(vehicle)
"accept a variety of events" in {
val player = Player(Avatar("TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
val pSource = PlayerSource(player)
val projectile = Projectile(proj, wep, wep_fmode, player, Vector3(2, 2, 0), Vector3.Zero)
val resprojectile = ResolvedProjectile(ProjectileResolution.Splash, projectile, SourceEntry(player), player.DamageModel, Vector3(50, 50, 0))
player.History(resprojectile) //ResolvedProjectile, straight-up
player.History(DamageFromProjectile(resprojectile))
player.History(HealFromKit(pSource, 10, GlobalDefinitions.medkit))
player.History(HealFromTerm(pSource, 10, 0, GlobalDefinitions.order_terminal))
player.History(HealFromImplant(pSource, 10, ImplantType.AdvancedRegen))
player.History(HealFromExoSuitChange(pSource, ExoSuitType.Standard))
player.History(RepairFromTerm(vSource, 10, GlobalDefinitions.order_terminal))
player.History(VehicleShieldCharge(vSource, 10))
player.History(PlayerSuicide(pSource))
ok
}
"return and clear the former list of vital activities" in {
val player = Player(Avatar("TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
val pSource = PlayerSource(player)
player.History(HealFromKit(pSource, 10, GlobalDefinitions.medkit))
player.History(HealFromTerm(pSource, 10, 0, GlobalDefinitions.order_terminal))
player.History(HealFromImplant(pSource, 10, ImplantType.AdvancedRegen))
player.History(HealFromExoSuitChange(pSource, ExoSuitType.Standard))
player.History(RepairFromTerm(vSource, 10, GlobalDefinitions.order_terminal))
player.History(VehicleShieldCharge(vSource, 10))
player.History(PlayerSuicide(pSource))
player.History.size mustEqual 7
val list = player.ClearHistory()
player.History.size mustEqual 0
list.head.isInstanceOf[PlayerSuicide] mustEqual true
list(1).isInstanceOf[VehicleShieldCharge] mustEqual true
list(2).isInstanceOf[RepairFromTerm] mustEqual true
list(3).isInstanceOf[HealFromExoSuitChange] mustEqual true
list(4).isInstanceOf[HealFromImplant] mustEqual true
list(5).isInstanceOf[HealFromTerm] mustEqual true
list(6).isInstanceOf[HealFromKit] mustEqual true
}
"get exactly one entry that was caused by projectile damage" in {
val player = Player(Avatar("TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
val pSource = PlayerSource(player)
val projectile = Projectile(proj, wep, wep_fmode, player, Vector3(2, 2, 0), Vector3.Zero)
val resprojectile = ResolvedProjectile(ProjectileResolution.Splash, projectile, SourceEntry(player), player.DamageModel, Vector3(50, 50, 0))
player.History(DamageFromProjectile(resprojectile))
player.History(HealFromKit(pSource, 10, GlobalDefinitions.medkit))
player.History(HealFromTerm(pSource, 10, 0, GlobalDefinitions.order_terminal))
player.History(HealFromImplant(pSource, 10, ImplantType.AdvancedRegen))
player.History(HealFromExoSuitChange(pSource, ExoSuitType.Standard))
player.History(RepairFromTerm(vSource, 10, GlobalDefinitions.order_terminal))
player.History(VehicleShieldCharge(vSource, 10))
player.History(PlayerSuicide(pSource))
player.LastShot match {
case Some(resolved_projectile) =>
resolved_projectile.projectile mustEqual projectile
case None =>
ko
}
}
}
}