Aggravation (#529)

* introduction of aggravated damage properties and modification of damage pathways such that 'aggravated' is treated as a unique damage type

* initial work and tests for AggravatedDamageMessage

* wrote aggravated damage properties into the definitions of projectiles that convey them; basic workflow for aggravation from projectile damage is in place, but not tested

* merge rebase resolutions

* grenade projectile flag on projectile definition; better timing control on aura behavior and integration with player control

* timing and damage tuning around: standard, reinforced, max; plasma grenades, dragon

* work on starfire damage profile; moved aura behavior into own folder and creates an aura container object; extended aura behavior to vehicles; applied target-control to aggravation effect

* woring Starfire damage calculations; projectiles have open-ended quality modifier

* working comet calculations

* separated aggravation behavior and uara management behavior; moved files into packages and deleted old files

* moved aggravation damage into damage implementations of turrets and vehicles, rather than directly into ther immediate control agencies; revamp projectile quality modifiers; comet now does initial damage and one less tick of aggravation; the implementation progression of damageable entities is different now

* eliminated multiple timers for a single aura effect; added comments; added tests; fixed tests

* aura effects that will run for longer will not get replaced by aura effects that would end sooner

* merge rebase; accommodation for suppressing aura where no aura should be displayed; new radial degrade calculations

* test repairs

* merge rebase hell

* dragon fireball burn damage modification

* fireball burn effect should always be 1 as long as it does damage, despite radial degrade
This commit is contained in:
Fate-JH 2020-08-28 00:32:45 -04:00 committed by GitHub
commit de87845bfe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
53 changed files with 2736 additions and 784 deletions

View file

@ -5,7 +5,7 @@ import akka.actor.Props
import akka.routing.RandomPool import akka.routing.RandomPool
import actor.base.ActorTest import actor.base.ActorTest
import net.psforever.objects._ import net.psforever.objects._
import net.psforever.objects.guid.{GUIDTask, TaskResolver} import net.psforever.objects.guid.{NumberPoolHub, TaskResolver}
import net.psforever.objects.zones.{Zone, ZoneMap} import net.psforever.objects.zones.{Zone, ZoneMap}
import net.psforever.packet.game.objectcreate.{DroppedItemData, ObjectClass, ObjectCreateMessageParent, PlacementData} import net.psforever.packet.game.objectcreate.{DroppedItemData, ObjectClass, ObjectCreateMessageParent, PlacementData}
import net.psforever.packet.game.{ObjectCreateMessage, PlayerStateMessageUpstream} import net.psforever.packet.game.{ObjectCreateMessage, PlayerStateMessageUpstream}
@ -17,6 +17,7 @@ import scala.concurrent.duration._
import akka.actor.typed.scaladsl.adapter._ import akka.actor.typed.scaladsl.adapter._
import net.psforever.actors.zone.ZoneActor import net.psforever.actors.zone.ZoneActor
import net.psforever.objects.avatar.Avatar import net.psforever.objects.avatar.Avatar
import net.psforever.objects.guid.source.LimitedNumberSource
class AvatarService1Test extends ActorTest { class AvatarService1Test extends ActorTest {
"AvatarService" should { "AvatarService" should {
@ -511,10 +512,13 @@ class AvatarReleaseTest extends ActorTest {
val zone = new Zone("test", new ZoneMap("test-map"), 0) { val zone = new Zone("test", new ZoneMap("test-map"), 0) {
override def SetupNumberPools() = { AddPool("dynamic", 1 to 10) } override def SetupNumberPools() = { AddPool("dynamic", 1 to 10) }
} }
val guid1: NumberPoolHub = new NumberPoolHub(new LimitedNumberSource(100))
zone.GUID(guid1)
val service = system.actorOf(Props(classOf[AvatarService], zone), "release-test-service") val service = system.actorOf(Props(classOf[AvatarService], zone), "release-test-service")
val taskResolver = system.actorOf(Props[TaskResolver](), "release-test-resolver")
zone.actor = system.spawn(ZoneActor(zone), "release-test-zone") zone.actor = system.spawn(ZoneActor(zone), "release-test-zone")
val obj = Player(Avatar(0, "TestCharacter", PlanetSideEmpire.VS, CharacterGender.Female, 1, CharacterVoice.Voice1)) val obj = Player(Avatar(0, "TestCharacter", PlanetSideEmpire.VS, CharacterGender.Female, 1, CharacterVoice.Voice1))
guid1.register(obj)
guid1.register(obj.Slot(5).Equipment.get)
obj.Continent = "test" obj.Continent = "test"
obj.Release obj.Release
@ -523,7 +527,6 @@ class AvatarReleaseTest extends ActorTest {
expectNoMessage(100 milliseconds) //spacer expectNoMessage(100 milliseconds) //spacer
service ! Service.Join("test") service ! Service.Join("test")
taskResolver ! GUIDTask.RegisterObjectTask(obj)(zone.GUID)
assert(zone.Corpses.isEmpty) assert(zone.Corpses.isEmpty)
zone.Population ! Zone.Corpse.Add(obj) zone.Population ! Zone.Corpse.Add(obj)
expectNoMessage(200 milliseconds) //spacer expectNoMessage(200 milliseconds) //spacer
@ -561,10 +564,13 @@ class AvatarReleaseEarly1Test extends ActorTest {
val zone = new Zone("test", new ZoneMap("test-map"), 0) { val zone = new Zone("test", new ZoneMap("test-map"), 0) {
override def SetupNumberPools() = { AddPool("dynamic", 1 to 10) } override def SetupNumberPools() = { AddPool("dynamic", 1 to 10) }
} }
val guid1: NumberPoolHub = new NumberPoolHub(new LimitedNumberSource(100))
zone.GUID(guid1)
val service = system.actorOf(Props(classOf[AvatarService], zone), "release-test-service") val service = system.actorOf(Props(classOf[AvatarService], zone), "release-test-service")
val taskResolver = system.actorOf(Props[TaskResolver](), "release-test-resolver")
zone.actor = system.spawn(ZoneActor(zone), "release-test-zone") zone.actor = system.spawn(ZoneActor(zone), "release-test-zone")
val obj = Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.VS, CharacterGender.Female, 1, CharacterVoice.Voice1)) val obj = Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.VS, CharacterGender.Female, 1, CharacterVoice.Voice1))
guid1.register(obj)
guid1.register(obj.Slot(5).Equipment.get)
obj.Continent = "test" obj.Continent = "test"
obj.Release obj.Release
@ -573,7 +579,6 @@ class AvatarReleaseEarly1Test extends ActorTest {
expectNoMessage(100 milliseconds) //spacer expectNoMessage(100 milliseconds) //spacer
service ! Service.Join("test") service ! Service.Join("test")
taskResolver ! GUIDTask.RegisterObjectTask(obj)(zone.GUID)
assert(zone.Corpses.isEmpty) assert(zone.Corpses.isEmpty)
zone.Population ! Zone.Corpse.Add(obj) zone.Population ! Zone.Corpse.Add(obj)
expectNoMessage(200 milliseconds) //spacer expectNoMessage(200 milliseconds) //spacer
@ -612,14 +617,19 @@ class AvatarReleaseEarly2Test extends ActorTest {
val zone = new Zone("test", new ZoneMap("test-map"), 0) { val zone = new Zone("test", new ZoneMap("test-map"), 0) {
override def SetupNumberPools() = { AddPool("dynamic", 1 to 10) } override def SetupNumberPools() = { AddPool("dynamic", 1 to 10) }
} }
val guid1: NumberPoolHub = new NumberPoolHub(new LimitedNumberSource(100))
zone.GUID(guid1)
val service = system.actorOf(Props(classOf[AvatarService], zone), "release-test-service") val service = system.actorOf(Props(classOf[AvatarService], zone), "release-test-service")
val taskResolver = system.actorOf(Props[TaskResolver](), "release-test-resolver")
zone.actor = system.spawn(ZoneActor(zone), "release-test-zone") zone.actor = system.spawn(ZoneActor(zone), "release-test-zone")
val objAlt = val objAlt =
Player( Player(
Avatar(0, "TestCharacter2", PlanetSideEmpire.NC, CharacterGender.Male, 1, CharacterVoice.Voice1) Avatar(0, "TestCharacter2", PlanetSideEmpire.NC, CharacterGender.Male, 1, CharacterVoice.Voice1)
) //necessary clutter ) //necessary clutter
val obj = Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.VS, CharacterGender.Female, 1, CharacterVoice.Voice1)) objAlt.GUID = PlanetSideGUID(3)
objAlt.Slot(5).Equipment.get.GUID = PlanetSideGUID(4)
val obj = Player(Avatar(0, "TestCharacter", PlanetSideEmpire.VS, CharacterGender.Female, 1, CharacterVoice.Voice1))
guid1.register(obj)
guid1.register(obj.Slot(5).Equipment.get)
obj.Continent = "test" obj.Continent = "test"
obj.Release obj.Release
@ -628,7 +638,6 @@ class AvatarReleaseEarly2Test extends ActorTest {
expectNoMessage(100 milliseconds) //spacer expectNoMessage(100 milliseconds) //spacer
service ! Service.Join("test") service ! Service.Join("test")
taskResolver ! GUIDTask.RegisterObjectTask(obj)(zone.GUID)
assert(zone.Corpses.isEmpty) assert(zone.Corpses.isEmpty)
zone.Population ! Zone.Corpse.Add(obj) zone.Population ! Zone.Corpse.Add(obj)
expectNoMessage(200 milliseconds) //spacer expectNoMessage(200 milliseconds) //spacer

View file

@ -9,7 +9,9 @@ import net.psforever.objects.definition.converter.SmallDeployableConverter
import net.psforever.objects.equipment.JammableUnit import net.psforever.objects.equipment.JammableUnit
import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.damage.Damageable import net.psforever.objects.serverobject.damage.Damageable
import net.psforever.objects.vital.{StandardResolutions, Vitality} import net.psforever.objects.serverobject.damage.Damageable.Target
import net.psforever.objects.vital.resolution.ResolutionCalculations.Output
import net.psforever.objects.vital.StandardResolutions
import net.psforever.objects.zones.Zone import net.psforever.objects.zones.Zone
import net.psforever.types.{PlanetSideGUID, Vector3} import net.psforever.types.{PlanetSideGUID, Vector3}
import net.psforever.services.Service import net.psforever.services.Service
@ -63,18 +65,20 @@ class ExplosiveDeployableControl(mine: ExplosiveDeployable) extends Actor with D
case _ => ; case _ => ;
} }
protected def TakesDamage: Receive = { override protected def PerformDamage(
case Vitality.Damage(applyDamageTo) => target: Target,
if (mine.CanDamage) { applyDamageTo: Output
val originalHealth = mine.Health ): Unit = {
val cause = applyDamageTo(mine) if (mine.CanDamage) {
val damage = originalHealth - mine.Health val originalHealth = mine.Health
if (Damageable.CanDamageOrJammer(mine, damage, cause)) { val cause = applyDamageTo(mine)
ExplosiveDeployableControl.DamageResolution(mine, cause, damage) val damage = originalHealth - mine.Health
} else { if (Damageable.CanDamageOrJammer(mine, damage, cause)) {
mine.Health = originalHealth ExplosiveDeployableControl.DamageResolution(mine, cause, damage)
} } else {
mine.Health = originalHealth
} }
}
} }
} }

View file

@ -2,12 +2,13 @@
package net.psforever.objects package net.psforever.objects
import net.psforever.objects.avatar.Certification import net.psforever.objects.avatar.Certification
import net.psforever.objects.ballistics.Projectiles import net.psforever.objects.ballistics.{AggravatedDamage, AggravatedInfo, AggravatedTiming, Projectiles}
import net.psforever.objects.ce.{DeployableCategory, DeployedItem} import net.psforever.objects.ce.{DeployableCategory, DeployedItem}
import net.psforever.objects.definition._ import net.psforever.objects.definition._
import net.psforever.objects.definition.converter._ import net.psforever.objects.definition.converter._
import net.psforever.objects.equipment._ import net.psforever.objects.equipment._
import net.psforever.objects.inventory.InventoryTile import net.psforever.objects.inventory.InventoryTile
import net.psforever.objects.serverobject.aura.Aura
import net.psforever.objects.serverobject.doors.DoorDefinition import net.psforever.objects.serverobject.doors.DoorDefinition
import net.psforever.objects.serverobject.generator.GeneratorDefinition import net.psforever.objects.serverobject.generator.GeneratorDefinition
import net.psforever.objects.serverobject.implantmech.ImplantTerminalMechDefinition import net.psforever.objects.serverobject.implantmech.ImplantTerminalMechDefinition
@ -207,6 +208,8 @@ object GlobalDefinitions {
val flail_projectile = ProjectileDefinition(Projectiles.flail_projectile) val flail_projectile = ProjectileDefinition(Projectiles.flail_projectile)
val flamethrower_fire_cloud = ProjectileDefinition(Projectiles.flamethrower_projectile) //flamethrower_fire_cloud
val flamethrower_fireball = ProjectileDefinition(Projectiles.flamethrower_fireball) val flamethrower_fireball = ProjectileDefinition(Projectiles.flamethrower_fireball)
val flamethrower_projectile = ProjectileDefinition(Projectiles.flamethrower_projectile) val flamethrower_projectile = ProjectileDefinition(Projectiles.flamethrower_projectile)
@ -2037,6 +2040,7 @@ object GlobalDefinitions {
no_projectile.Name = "none" no_projectile.Name = "none"
ProjectileDefinition.CalculateDerivedFields(no_projectile) ProjectileDefinition.CalculateDerivedFields(no_projectile)
no_projectile.Modifiers = Nil
bullet_105mm_projectile.Name = "105mmbullet_projectile" bullet_105mm_projectile.Name = "105mmbullet_projectile"
bullet_105mm_projectile.Damage0 = 150 bullet_105mm_projectile.Damage0 = 150
@ -2269,6 +2273,7 @@ object GlobalDefinitions {
ProjectileDefinition.CalculateDerivedFields(aphelion_laser_projectile) ProjectileDefinition.CalculateDerivedFields(aphelion_laser_projectile)
aphelion_plasma_rocket_projectile.Name = "aphelion_plasma_rocket_projectile" aphelion_plasma_rocket_projectile.Name = "aphelion_plasma_rocket_projectile"
//has property aggravated_damage_max_factor, but it's the aphelion_plasma_cloud that performs aggravated damage
aphelion_plasma_rocket_projectile.Damage0 = 38 aphelion_plasma_rocket_projectile.Damage0 = 38
aphelion_plasma_rocket_projectile.Damage1 = 70 aphelion_plasma_rocket_projectile.Damage1 = 70
aphelion_plasma_rocket_projectile.Damage2 = 95 aphelion_plasma_rocket_projectile.Damage2 = 95
@ -2313,6 +2318,14 @@ object GlobalDefinitions {
aphelion_starfire_projectile.InitialVelocity = 45 aphelion_starfire_projectile.InitialVelocity = 45
aphelion_starfire_projectile.Lifespan = 7f aphelion_starfire_projectile.Lifespan = 7f
aphelion_starfire_projectile.ProjectileDamageType = DamageType.Aggravated aphelion_starfire_projectile.ProjectileDamageType = DamageType.Aggravated
aphelion_starfire_projectile.Aggravated = AggravatedDamage(
AggravatedInfo(DamageType.Direct, 0.25f, 250),
Aura.None,
2000,
0f,
true,
List(TargetValidation(EffectTarget.Category.Aircraft, EffectTarget.Validation.Aircraft))
)
aphelion_starfire_projectile.ExistsOnRemoteClients = true aphelion_starfire_projectile.ExistsOnRemoteClients = true
aphelion_starfire_projectile.RemoteClientData = (39577, 249) //starfire_projectile data aphelion_starfire_projectile.RemoteClientData = (39577, 249) //starfire_projectile data
aphelion_starfire_projectile.AutoLock = true aphelion_starfire_projectile.AutoLock = true
@ -2352,6 +2365,7 @@ object GlobalDefinitions {
chainblade_projectile.InitialVelocity = 100 chainblade_projectile.InitialVelocity = 100
chainblade_projectile.Lifespan = .02f chainblade_projectile.Lifespan = .02f
ProjectileDefinition.CalculateDerivedFields(chainblade_projectile) ProjectileDefinition.CalculateDerivedFields(chainblade_projectile)
chainblade_projectile.Modifiers = DamageModifiers.MaxDistanceCutoff
colossus_100mm_projectile.Name = "colossus_100mm_projectile" colossus_100mm_projectile.Name = "colossus_100mm_projectile"
colossus_100mm_projectile.Damage0 = 58 colossus_100mm_projectile.Damage0 = 58
@ -2437,9 +2451,24 @@ object GlobalDefinitions {
comet_projectile.DamageAtEdge = 0.45f comet_projectile.DamageAtEdge = 0.45f
comet_projectile.DamageRadius = 1.0f comet_projectile.DamageRadius = 1.0f
comet_projectile.ProjectileDamageType = DamageType.Aggravated comet_projectile.ProjectileDamageType = DamageType.Aggravated
comet_projectile.Aggravated = AggravatedDamage(
AggravatedInfo(DamageType.Direct, 0.25f, 500), //originally, .2
Aura.Comet,
AggravatedTiming(2000, 3),
10f,
List(
TargetValidation(EffectTarget.Category.Player, EffectTarget.Validation.Player),
TargetValidation(EffectTarget.Category.Vehicle, EffectTarget.Validation.Vehicle),
TargetValidation(EffectTarget.Category.Turret, EffectTarget.Validation.Turret)
)
)
comet_projectile.InitialVelocity = 80 comet_projectile.InitialVelocity = 80
comet_projectile.Lifespan = 3.1f comet_projectile.Lifespan = 3.1f
ProjectileDefinition.CalculateDerivedFields(comet_projectile) ProjectileDefinition.CalculateDerivedFields(comet_projectile)
comet_projectile.Modifiers = List(
DamageModifiers.CometAggravated,
DamageModifiers.CometAggravatedBurn
)
dualcycler_projectile.Name = "dualcycler_projectile" dualcycler_projectile.Name = "dualcycler_projectile"
dualcycler_projectile.Damage0 = 18 dualcycler_projectile.Damage0 = 18
@ -2457,6 +2486,7 @@ object GlobalDefinitions {
dynomite_projectile.Damage1 = 175 dynomite_projectile.Damage1 = 175
dynomite_projectile.DamageAtEdge = 0.1f dynomite_projectile.DamageAtEdge = 0.1f
dynomite_projectile.DamageRadius = 10f dynomite_projectile.DamageRadius = 10f
dynomite_projectile.GrenadeProjectile = true
dynomite_projectile.ProjectileDamageType = DamageType.Splash dynomite_projectile.ProjectileDamageType = DamageType.Splash
dynomite_projectile.InitialVelocity = 30 dynomite_projectile.InitialVelocity = 30
dynomite_projectile.Lifespan = 3f dynomite_projectile.Lifespan = 3f
@ -2579,12 +2609,28 @@ object GlobalDefinitions {
flamethrower_fireball.Damage2 = 0 flamethrower_fireball.Damage2 = 0
flamethrower_fireball.Damage3 = 20 flamethrower_fireball.Damage3 = 20
flamethrower_fireball.Damage4 = 0 flamethrower_fireball.Damage4 = 0
flamethrower_fireball.DamageToHealthOnly = true
flamethrower_fireball.DamageAtEdge = 0.15f flamethrower_fireball.DamageAtEdge = 0.15f
flamethrower_fireball.DamageRadius = 5f flamethrower_fireball.DamageRadius = 5f
flamethrower_fireball.ProjectileDamageType = DamageType.Aggravated flamethrower_fireball.ProjectileDamageType = DamageType.Aggravated
flamethrower_fireball.Aggravated = AggravatedDamage(
List(AggravatedInfo(DamageType.Direct, 0.9f, 500), AggravatedInfo(DamageType.Splash, 0.9f, 500)),
Aura.Fire,
AggravatedTiming(5000, 10),
0.1f,
false,
false,
List(TargetValidation(EffectTarget.Category.Player, EffectTarget.Validation.Player))
)
flamethrower_fireball.InitialVelocity = 15 flamethrower_fireball.InitialVelocity = 15
flamethrower_fireball.Lifespan = 1.2f flamethrower_fireball.Lifespan = 1.2f
ProjectileDefinition.CalculateDerivedFields(flamethrower_fireball) ProjectileDefinition.CalculateDerivedFields(flamethrower_fireball)
flamethrower_fireball.Modifiers = List(
DamageModifiers.InfantryAggravatedDirect,
DamageModifiers.InfantryAggravatedSplash,
DamageModifiers.RadialDegrade,
DamageModifiers.FireballAggravatedBurn
)
flamethrower_projectile.Name = "flamethrower_projectile" flamethrower_projectile.Name = "flamethrower_projectile"
flamethrower_projectile.Damage0 = 10 flamethrower_projectile.Damage0 = 10
@ -2592,14 +2638,29 @@ object GlobalDefinitions {
flamethrower_projectile.Damage2 = 0 flamethrower_projectile.Damage2 = 0
flamethrower_projectile.Damage3 = 4 flamethrower_projectile.Damage3 = 4
flamethrower_projectile.Damage4 = 0 flamethrower_projectile.Damage4 = 0
flamethrower_projectile.DamageToHealthOnly = true
flamethrower_projectile.Acceleration = -5 flamethrower_projectile.Acceleration = -5
flamethrower_projectile.AccelerationUntil = 2f flamethrower_projectile.AccelerationUntil = 2f
flamethrower_projectile.ProjectileDamageType = DamageType.Aggravated flamethrower_projectile.ProjectileDamageType = DamageType.Aggravated
flamethrower_projectile.Aggravated = AggravatedDamage(
List(AggravatedInfo(DamageType.Direct, 0.5f, 500)),
Aura.Fire,
AggravatedTiming(5000, 10),
0.5f,
false,
false,
List(TargetValidation(EffectTarget.Category.Player, EffectTarget.Validation.Player))
)
flamethrower_projectile.DegradeDelay = 1.0f flamethrower_projectile.DegradeDelay = 1.0f
flamethrower_projectile.DegradeMultiplier = 0.5f flamethrower_projectile.DegradeMultiplier = 0.5f
flamethrower_projectile.InitialVelocity = 10 flamethrower_projectile.InitialVelocity = 10
flamethrower_projectile.Lifespan = 2.0f flamethrower_projectile.Lifespan = 2.0f
ProjectileDefinition.CalculateDerivedFields(flamethrower_projectile) ProjectileDefinition.CalculateDerivedFields(flamethrower_projectile)
flamethrower_projectile.Modifiers = List(
DamageModifiers.InfantryAggravatedDirect,
DamageModifiers.FireballAggravatedBurn,
DamageModifiers.MaxDistanceCutoff
)
flux_cannon_apc_projectile.Name = "flux_cannon_apc_projectile" flux_cannon_apc_projectile.Name = "flux_cannon_apc_projectile"
// TODO for later, maybe : set_resource_parent flux_cannon_apc_projectile game_objects flux_cannon_thresher_projectile // TODO for later, maybe : set_resource_parent flux_cannon_apc_projectile game_objects flux_cannon_thresher_projectile
@ -2651,6 +2712,7 @@ object GlobalDefinitions {
forceblade_projectile.InitialVelocity = 100 forceblade_projectile.InitialVelocity = 100
forceblade_projectile.Lifespan = .02f forceblade_projectile.Lifespan = .02f
ProjectileDefinition.CalculateDerivedFields(forceblade_projectile) ProjectileDefinition.CalculateDerivedFields(forceblade_projectile)
forceblade_projectile.Modifiers = DamageModifiers.MaxDistanceCutoff
frag_cartridge_projectile.Name = "frag_cartridge_projectile" frag_cartridge_projectile.Name = "frag_cartridge_projectile"
// TODO for later, maybe : set_resource_parent frag_cartridge_projectile game_objects frag_grenade_projectile // TODO for later, maybe : set_resource_parent frag_cartridge_projectile game_objects frag_grenade_projectile
@ -2658,6 +2720,7 @@ object GlobalDefinitions {
frag_cartridge_projectile.Damage1 = 100 frag_cartridge_projectile.Damage1 = 100
frag_cartridge_projectile.DamageAtEdge = 0.1f frag_cartridge_projectile.DamageAtEdge = 0.1f
frag_cartridge_projectile.DamageRadius = 7f frag_cartridge_projectile.DamageRadius = 7f
frag_cartridge_projectile.GrenadeProjectile = true
frag_cartridge_projectile.ProjectileDamageType = DamageType.Splash frag_cartridge_projectile.ProjectileDamageType = DamageType.Splash
frag_cartridge_projectile.InitialVelocity = 30 frag_cartridge_projectile.InitialVelocity = 30
frag_cartridge_projectile.Lifespan = 15f frag_cartridge_projectile.Lifespan = 15f
@ -2670,6 +2733,7 @@ object GlobalDefinitions {
frag_cartridge_projectile_b.Damage1 = 100 frag_cartridge_projectile_b.Damage1 = 100
frag_cartridge_projectile_b.DamageAtEdge = 0.1f frag_cartridge_projectile_b.DamageAtEdge = 0.1f
frag_cartridge_projectile_b.DamageRadius = 5f frag_cartridge_projectile_b.DamageRadius = 5f
frag_cartridge_projectile_b.GrenadeProjectile = true
frag_cartridge_projectile_b.ProjectileDamageType = DamageType.Splash frag_cartridge_projectile_b.ProjectileDamageType = DamageType.Splash
frag_cartridge_projectile_b.InitialVelocity = 30 frag_cartridge_projectile_b.InitialVelocity = 30
frag_cartridge_projectile_b.Lifespan = 2f frag_cartridge_projectile_b.Lifespan = 2f
@ -2681,6 +2745,7 @@ object GlobalDefinitions {
frag_grenade_projectile.Damage1 = 100 frag_grenade_projectile.Damage1 = 100
frag_grenade_projectile.DamageAtEdge = 0.1f frag_grenade_projectile.DamageAtEdge = 0.1f
frag_grenade_projectile.DamageRadius = 7f frag_grenade_projectile.DamageRadius = 7f
frag_grenade_projectile.GrenadeProjectile = true
frag_grenade_projectile.ProjectileDamageType = DamageType.Splash frag_grenade_projectile.ProjectileDamageType = DamageType.Splash
frag_grenade_projectile.InitialVelocity = 30 frag_grenade_projectile.InitialVelocity = 30
frag_grenade_projectile.Lifespan = 15f frag_grenade_projectile.Lifespan = 15f
@ -2693,6 +2758,7 @@ object GlobalDefinitions {
frag_grenade_projectile_enh.Damage1 = 100 frag_grenade_projectile_enh.Damage1 = 100
frag_grenade_projectile_enh.DamageAtEdge = 0.1f frag_grenade_projectile_enh.DamageAtEdge = 0.1f
frag_grenade_projectile_enh.DamageRadius = 7f frag_grenade_projectile_enh.DamageRadius = 7f
frag_grenade_projectile_enh.GrenadeProjectile = true
frag_grenade_projectile_enh.ProjectileDamageType = DamageType.Splash frag_grenade_projectile_enh.ProjectileDamageType = DamageType.Splash
frag_grenade_projectile_enh.InitialVelocity = 30 frag_grenade_projectile_enh.InitialVelocity = 30
frag_grenade_projectile_enh.Lifespan = 2f frag_grenade_projectile_enh.Lifespan = 2f
@ -2743,6 +2809,7 @@ object GlobalDefinitions {
heavy_grenade_projectile.Damage4 = 66 heavy_grenade_projectile.Damage4 = 66
heavy_grenade_projectile.DamageAtEdge = 0.1f heavy_grenade_projectile.DamageAtEdge = 0.1f
heavy_grenade_projectile.DamageRadius = 5f heavy_grenade_projectile.DamageRadius = 5f
heavy_grenade_projectile.GrenadeProjectile = true
heavy_grenade_projectile.ProjectileDamageType = DamageType.Splash heavy_grenade_projectile.ProjectileDamageType = DamageType.Splash
heavy_grenade_projectile.InitialVelocity = 75 heavy_grenade_projectile.InitialVelocity = 75
heavy_grenade_projectile.Lifespan = 5f heavy_grenade_projectile.Lifespan = 5f
@ -2828,6 +2895,7 @@ object GlobalDefinitions {
jammer_cartridge_projectile.Damage1 = 0 jammer_cartridge_projectile.Damage1 = 0
jammer_cartridge_projectile.DamageAtEdge = 1.0f jammer_cartridge_projectile.DamageAtEdge = 1.0f
jammer_cartridge_projectile.DamageRadius = 10f jammer_cartridge_projectile.DamageRadius = 10f
jammer_cartridge_projectile.GrenadeProjectile = true
jammer_cartridge_projectile.ProjectileDamageType = DamageType.Splash jammer_cartridge_projectile.ProjectileDamageType = DamageType.Splash
jammer_cartridge_projectile.InitialVelocity = 30 jammer_cartridge_projectile.InitialVelocity = 30
jammer_cartridge_projectile.Lifespan = 15f jammer_cartridge_projectile.Lifespan = 15f
@ -2858,7 +2926,7 @@ object GlobalDefinitions {
EffectTarget.Validation.VehicleNotAMS EffectTarget.Validation.VehicleNotAMS
) -> 10000 ) -> 10000
ProjectileDefinition.CalculateDerivedFields(jammer_cartridge_projectile) ProjectileDefinition.CalculateDerivedFields(jammer_cartridge_projectile)
jammer_cartridge_projectile.Modifiers = Nil jammer_cartridge_projectile.Modifiers = DamageModifiers.MaxDistanceCutoff
jammer_cartridge_projectile_b.Name = "jammer_cartridge_projectile_b" jammer_cartridge_projectile_b.Name = "jammer_cartridge_projectile_b"
// TODO for later, maybe : set_resource_parent jammer_cartridge_projectile_b game_objects jammer_grenade_projectile_enh // TODO for later, maybe : set_resource_parent jammer_cartridge_projectile_b game_objects jammer_grenade_projectile_enh
@ -2866,6 +2934,7 @@ object GlobalDefinitions {
jammer_cartridge_projectile_b.Damage1 = 0 jammer_cartridge_projectile_b.Damage1 = 0
jammer_cartridge_projectile_b.DamageAtEdge = 1.0f jammer_cartridge_projectile_b.DamageAtEdge = 1.0f
jammer_cartridge_projectile_b.DamageRadius = 10f jammer_cartridge_projectile_b.DamageRadius = 10f
jammer_cartridge_projectile_b.GrenadeProjectile = true
jammer_cartridge_projectile_b.ProjectileDamageType = DamageType.Splash jammer_cartridge_projectile_b.ProjectileDamageType = DamageType.Splash
jammer_cartridge_projectile_b.InitialVelocity = 30 jammer_cartridge_projectile_b.InitialVelocity = 30
jammer_cartridge_projectile_b.Lifespan = 2f jammer_cartridge_projectile_b.Lifespan = 2f
@ -2896,13 +2965,14 @@ object GlobalDefinitions {
EffectTarget.Validation.VehicleNotAMS EffectTarget.Validation.VehicleNotAMS
) -> 10000 ) -> 10000
ProjectileDefinition.CalculateDerivedFields(jammer_cartridge_projectile_b) ProjectileDefinition.CalculateDerivedFields(jammer_cartridge_projectile_b)
jammer_cartridge_projectile_b.Modifiers = Nil jammer_cartridge_projectile_b.Modifiers = DamageModifiers.MaxDistanceCutoff
jammer_grenade_projectile.Name = "jammer_grenade_projectile" jammer_grenade_projectile.Name = "jammer_grenade_projectile"
jammer_grenade_projectile.Damage0 = 0 jammer_grenade_projectile.Damage0 = 0
jammer_grenade_projectile.Damage1 = 0 jammer_grenade_projectile.Damage1 = 0
jammer_grenade_projectile.DamageAtEdge = 1.0f jammer_grenade_projectile.DamageAtEdge = 1.0f
jammer_grenade_projectile.DamageRadius = 10f jammer_grenade_projectile.DamageRadius = 10f
jammer_grenade_projectile.GrenadeProjectile = true
jammer_grenade_projectile.ProjectileDamageType = DamageType.Splash jammer_grenade_projectile.ProjectileDamageType = DamageType.Splash
jammer_grenade_projectile.InitialVelocity = 30 jammer_grenade_projectile.InitialVelocity = 30
jammer_grenade_projectile.Lifespan = 15f jammer_grenade_projectile.Lifespan = 15f
@ -2933,7 +3003,7 @@ object GlobalDefinitions {
EffectTarget.Validation.VehicleNotAMS EffectTarget.Validation.VehicleNotAMS
) -> 10000 ) -> 10000
ProjectileDefinition.CalculateDerivedFields(jammer_grenade_projectile) ProjectileDefinition.CalculateDerivedFields(jammer_grenade_projectile)
jammer_grenade_projectile.Modifiers = Nil jammer_grenade_projectile.Modifiers = DamageModifiers.MaxDistanceCutoff
jammer_grenade_projectile_enh.Name = "jammer_grenade_projectile_enh" jammer_grenade_projectile_enh.Name = "jammer_grenade_projectile_enh"
// TODO for later, maybe : set_resource_parent jammer_grenade_projectile_enh game_objects jammer_grenade_projectile // TODO for later, maybe : set_resource_parent jammer_grenade_projectile_enh game_objects jammer_grenade_projectile
@ -2941,6 +3011,7 @@ object GlobalDefinitions {
jammer_grenade_projectile_enh.Damage1 = 0 jammer_grenade_projectile_enh.Damage1 = 0
jammer_grenade_projectile_enh.DamageAtEdge = 1.0f jammer_grenade_projectile_enh.DamageAtEdge = 1.0f
jammer_grenade_projectile_enh.DamageRadius = 10f jammer_grenade_projectile_enh.DamageRadius = 10f
jammer_grenade_projectile_enh.GrenadeProjectile = true
jammer_grenade_projectile_enh.ProjectileDamageType = DamageType.Splash jammer_grenade_projectile_enh.ProjectileDamageType = DamageType.Splash
jammer_grenade_projectile_enh.InitialVelocity = 30 jammer_grenade_projectile_enh.InitialVelocity = 30
jammer_grenade_projectile_enh.Lifespan = 3f jammer_grenade_projectile_enh.Lifespan = 3f
@ -2971,7 +3042,7 @@ object GlobalDefinitions {
EffectTarget.Validation.VehicleNotAMS EffectTarget.Validation.VehicleNotAMS
) -> 10000 ) -> 10000
ProjectileDefinition.CalculateDerivedFields(jammer_grenade_projectile_enh) ProjectileDefinition.CalculateDerivedFields(jammer_grenade_projectile_enh)
jammer_grenade_projectile_enh.Modifiers = Nil jammer_grenade_projectile_enh.Modifiers = DamageModifiers.MaxDistanceCutoff
katana_projectile.Name = "katana_projectile" katana_projectile.Name = "katana_projectile"
katana_projectile.Damage0 = 25 katana_projectile.Damage0 = 25
@ -3014,7 +3085,10 @@ object GlobalDefinitions {
lasher_projectile.LashRadius = 2.5f lasher_projectile.LashRadius = 2.5f
lasher_projectile.Lifespan = 0.75f lasher_projectile.Lifespan = 0.75f
ProjectileDefinition.CalculateDerivedFields(lasher_projectile) ProjectileDefinition.CalculateDerivedFields(lasher_projectile)
lasher_projectile.Modifiers = List(DamageModifiers.DistanceDegrade, DamageModifiers.Lash) lasher_projectile.Modifiers = List(
DamageModifiers.DistanceDegrade,
DamageModifiers.Lash
)
lasher_projectile_ap.Name = "lasher_projectile_ap" lasher_projectile_ap.Name = "lasher_projectile_ap"
lasher_projectile_ap.Damage0 = 12 lasher_projectile_ap.Damage0 = 12
@ -3029,7 +3103,10 @@ object GlobalDefinitions {
lasher_projectile_ap.LashRadius = 2.5f lasher_projectile_ap.LashRadius = 2.5f
lasher_projectile_ap.Lifespan = 0.75f lasher_projectile_ap.Lifespan = 0.75f
ProjectileDefinition.CalculateDerivedFields(lasher_projectile_ap) ProjectileDefinition.CalculateDerivedFields(lasher_projectile_ap)
lasher_projectile_ap.Modifiers = List(DamageModifiers.DistanceDegrade, DamageModifiers.Lash) lasher_projectile_ap.Modifiers = List(
DamageModifiers.DistanceDegrade,
DamageModifiers.Lash
)
liberator_bomb_cluster_bomblet_projectile.Name = "liberator_bomb_cluster_bomblet_projectile" liberator_bomb_cluster_bomblet_projectile.Name = "liberator_bomb_cluster_bomblet_projectile"
liberator_bomb_cluster_bomblet_projectile.Damage0 = 75 liberator_bomb_cluster_bomblet_projectile.Damage0 = 75
@ -3071,6 +3148,7 @@ object GlobalDefinitions {
maelstrom_grenade_projectile.Damage1 = 60 maelstrom_grenade_projectile.Damage1 = 60
maelstrom_grenade_projectile.DamageRadius = 20f maelstrom_grenade_projectile.DamageRadius = 20f
maelstrom_grenade_projectile.LashRadius = 5f maelstrom_grenade_projectile.LashRadius = 5f
maelstrom_grenade_projectile.GrenadeProjectile = true
maelstrom_grenade_projectile.ProjectileDamageType = DamageType.Direct maelstrom_grenade_projectile.ProjectileDamageType = DamageType.Direct
maelstrom_grenade_projectile.InitialVelocity = 30 maelstrom_grenade_projectile.InitialVelocity = 30
maelstrom_grenade_projectile.Lifespan = 2f maelstrom_grenade_projectile.Lifespan = 2f
@ -3084,6 +3162,7 @@ object GlobalDefinitions {
maelstrom_grenade_projectile_contact.Damage1 = 60 maelstrom_grenade_projectile_contact.Damage1 = 60
maelstrom_grenade_projectile_contact.DamageRadius = 20f maelstrom_grenade_projectile_contact.DamageRadius = 20f
maelstrom_grenade_projectile_contact.LashRadius = 5f maelstrom_grenade_projectile_contact.LashRadius = 5f
maelstrom_grenade_projectile_contact.GrenadeProjectile = true
maelstrom_grenade_projectile_contact.ProjectileDamageType = DamageType.Direct maelstrom_grenade_projectile_contact.ProjectileDamageType = DamageType.Direct
maelstrom_grenade_projectile_contact.InitialVelocity = 30 maelstrom_grenade_projectile_contact.InitialVelocity = 30
maelstrom_grenade_projectile_contact.Lifespan = 15f maelstrom_grenade_projectile_contact.Lifespan = 15f
@ -3100,6 +3179,7 @@ object GlobalDefinitions {
maelstrom_stream_projectile.InitialVelocity = 200 maelstrom_stream_projectile.InitialVelocity = 200
maelstrom_stream_projectile.Lifespan = 0.2f maelstrom_stream_projectile.Lifespan = 0.2f
ProjectileDefinition.CalculateDerivedFields(maelstrom_stream_projectile) ProjectileDefinition.CalculateDerivedFields(maelstrom_stream_projectile)
maelstrom_stream_projectile.Modifiers = DamageModifiers.MaxDistanceCutoff
magcutter_projectile.Name = "magcutter_projectile" magcutter_projectile.Name = "magcutter_projectile"
// TODO for later, maybe : set_resource_parent magcutter_projectile game_objects melee_ammo_projectile // TODO for later, maybe : set_resource_parent magcutter_projectile game_objects melee_ammo_projectile
@ -3109,6 +3189,7 @@ object GlobalDefinitions {
magcutter_projectile.InitialVelocity = 100 magcutter_projectile.InitialVelocity = 100
magcutter_projectile.Lifespan = .02f magcutter_projectile.Lifespan = .02f
ProjectileDefinition.CalculateDerivedFields(magcutter_projectile) ProjectileDefinition.CalculateDerivedFields(magcutter_projectile)
magcutter_projectile.Modifiers = DamageModifiers.MaxDistanceCutoff
melee_ammo_projectile.Name = "melee_ammo_projectile" melee_ammo_projectile.Name = "melee_ammo_projectile"
melee_ammo_projectile.Damage0 = 25 melee_ammo_projectile.Damage0 = 25
@ -3117,6 +3198,7 @@ object GlobalDefinitions {
melee_ammo_projectile.InitialVelocity = 100 melee_ammo_projectile.InitialVelocity = 100
melee_ammo_projectile.Lifespan = .02f melee_ammo_projectile.Lifespan = .02f
ProjectileDefinition.CalculateDerivedFields(melee_ammo_projectile) ProjectileDefinition.CalculateDerivedFields(melee_ammo_projectile)
melee_ammo_projectile.Modifiers = DamageModifiers.MaxDistanceCutoff
meteor_common.Name = "meteor_common" meteor_common.Name = "meteor_common"
meteor_common.DamageAtEdge = .1f meteor_common.DamageAtEdge = .1f
@ -3208,6 +3290,7 @@ object GlobalDefinitions {
mine_sweeper_projectile.Damage1 = 0 mine_sweeper_projectile.Damage1 = 0
mine_sweeper_projectile.DamageAtEdge = .33f mine_sweeper_projectile.DamageAtEdge = .33f
mine_sweeper_projectile.DamageRadius = 25f mine_sweeper_projectile.DamageRadius = 25f
mine_sweeper_projectile.GrenadeProjectile = true
mine_sweeper_projectile.ProjectileDamageType = DamageType.Splash mine_sweeper_projectile.ProjectileDamageType = DamageType.Splash
mine_sweeper_projectile.InitialVelocity = 30 mine_sweeper_projectile.InitialVelocity = 30
mine_sweeper_projectile.Lifespan = 15f mine_sweeper_projectile.Lifespan = 15f
@ -3219,6 +3302,7 @@ object GlobalDefinitions {
mine_sweeper_projectile_enh.Damage1 = 0 mine_sweeper_projectile_enh.Damage1 = 0
mine_sweeper_projectile_enh.DamageAtEdge = 0.33f mine_sweeper_projectile_enh.DamageAtEdge = 0.33f
mine_sweeper_projectile_enh.DamageRadius = 25f mine_sweeper_projectile_enh.DamageRadius = 25f
mine_sweeper_projectile_enh.GrenadeProjectile = true
mine_sweeper_projectile_enh.ProjectileDamageType = DamageType.Splash mine_sweeper_projectile_enh.ProjectileDamageType = DamageType.Splash
mine_sweeper_projectile_enh.InitialVelocity = 30 mine_sweeper_projectile_enh.InitialVelocity = 30
mine_sweeper_projectile_enh.Lifespan = 3f mine_sweeper_projectile_enh.Lifespan = 3f
@ -3422,10 +3506,27 @@ object GlobalDefinitions {
plasma_cartridge_projectile.Damage1 = 15 plasma_cartridge_projectile.Damage1 = 15
plasma_cartridge_projectile.DamageAtEdge = 0.2f plasma_cartridge_projectile.DamageAtEdge = 0.2f
plasma_cartridge_projectile.DamageRadius = 7f plasma_cartridge_projectile.DamageRadius = 7f
plasma_cartridge_projectile.GrenadeProjectile = true
plasma_cartridge_projectile.ProjectileDamageType = DamageType.Aggravated plasma_cartridge_projectile.ProjectileDamageType = DamageType.Aggravated
plasma_cartridge_projectile.Aggravated = AggravatedDamage(
List(AggravatedInfo(DamageType.Direct, 0.25f, 750), AggravatedInfo(DamageType.Splash, 0.25f, 1000)),
Aura.Plasma,
AggravatedTiming(3000),
1.5f,
true,
false,
List(TargetValidation(EffectTarget.Category.Player, EffectTarget.Validation.Player))
)
plasma_cartridge_projectile.InitialVelocity = 30 plasma_cartridge_projectile.InitialVelocity = 30
plasma_cartridge_projectile.Lifespan = 15f plasma_cartridge_projectile.Lifespan = 15f
ProjectileDefinition.CalculateDerivedFields(plasma_cartridge_projectile) ProjectileDefinition.CalculateDerivedFields(plasma_cartridge_projectile)
plasma_cartridge_projectile.Modifiers = List(
DamageModifiers.InfantryAggravatedDirect,
DamageModifiers.InfantryAggravatedDirectBurn,
DamageModifiers.InfantryAggravatedSplash,
DamageModifiers.InfantryAggravatedSplashBurn,
DamageModifiers.RadialDegrade
)
plasma_cartridge_projectile_b.Name = "plasma_cartridge_projectile_b" plasma_cartridge_projectile_b.Name = "plasma_cartridge_projectile_b"
// TODO for later, maybe : set_resource_parent plasma_cartridge_projectile_b game_objects plasma_grenade_projectile_B // TODO for later, maybe : set_resource_parent plasma_cartridge_projectile_b game_objects plasma_grenade_projectile_B
@ -3433,20 +3534,54 @@ object GlobalDefinitions {
plasma_cartridge_projectile_b.Damage1 = 15 plasma_cartridge_projectile_b.Damage1 = 15
plasma_cartridge_projectile_b.DamageAtEdge = 0.2f plasma_cartridge_projectile_b.DamageAtEdge = 0.2f
plasma_cartridge_projectile_b.DamageRadius = 7f plasma_cartridge_projectile_b.DamageRadius = 7f
plasma_cartridge_projectile_b.GrenadeProjectile = true
plasma_cartridge_projectile_b.ProjectileDamageType = DamageType.Aggravated plasma_cartridge_projectile_b.ProjectileDamageType = DamageType.Aggravated
plasma_cartridge_projectile_b.Aggravated = AggravatedDamage(
List(AggravatedInfo(DamageType.Direct, 0.25f, 750), AggravatedInfo(DamageType.Splash, 0.25f, 1000)),
Aura.Plasma,
AggravatedTiming(3000),
1.5f,
true,
false,
List(TargetValidation(EffectTarget.Category.Player, EffectTarget.Validation.Player))
)
plasma_cartridge_projectile_b.InitialVelocity = 30 plasma_cartridge_projectile_b.InitialVelocity = 30
plasma_cartridge_projectile_b.Lifespan = 2f plasma_cartridge_projectile_b.Lifespan = 2f
ProjectileDefinition.CalculateDerivedFields(plasma_cartridge_projectile_b) ProjectileDefinition.CalculateDerivedFields(plasma_cartridge_projectile_b)
plasma_cartridge_projectile_b.Modifiers = List(
DamageModifiers.InfantryAggravatedDirect,
DamageModifiers.InfantryAggravatedDirectBurn,
DamageModifiers.InfantryAggravatedSplash,
DamageModifiers.InfantryAggravatedSplashBurn,
DamageModifiers.RadialDegrade
)
plasma_grenade_projectile.Name = "plasma_grenade_projectile" plasma_grenade_projectile.Name = "plasma_grenade_projectile"
plasma_grenade_projectile.Damage0 = 40 plasma_grenade_projectile.Damage0 = 40
plasma_grenade_projectile.Damage1 = 30 plasma_grenade_projectile.Damage1 = 30
plasma_grenade_projectile.DamageAtEdge = 0.1f plasma_grenade_projectile.DamageAtEdge = 0.1f
plasma_grenade_projectile.DamageRadius = 7f plasma_grenade_projectile.DamageRadius = 7f
plasma_grenade_projectile.GrenadeProjectile = true
plasma_grenade_projectile.ProjectileDamageType = DamageType.Aggravated plasma_grenade_projectile.ProjectileDamageType = DamageType.Aggravated
plasma_grenade_projectile.Aggravated = AggravatedDamage(
List(AggravatedInfo(DamageType.Direct, 0.25f, 750), AggravatedInfo(DamageType.Splash, 0.25f, 1000)),
Aura.Plasma,
AggravatedTiming(3000),
1.5f,
true,
false,
List(TargetValidation(EffectTarget.Category.Player, EffectTarget.Validation.Player))
)
plasma_grenade_projectile.InitialVelocity = 30 plasma_grenade_projectile.InitialVelocity = 30
plasma_grenade_projectile.Lifespan = 15f plasma_grenade_projectile.Lifespan = 15f
ProjectileDefinition.CalculateDerivedFields(plasma_grenade_projectile) ProjectileDefinition.CalculateDerivedFields(plasma_grenade_projectile)
plasma_grenade_projectile.Modifiers = List(
DamageModifiers.InfantryAggravatedDirect,
DamageModifiers.InfantryAggravatedDirectBurn,
DamageModifiers.InfantryAggravatedSplash,
DamageModifiers.InfantryAggravatedSplashBurn,
DamageModifiers.RadialDegrade
)
plasma_grenade_projectile_B.Name = "plasma_grenade_projectile_B" plasma_grenade_projectile_B.Name = "plasma_grenade_projectile_B"
// TODO for later, maybe : set_resource_parent plasma_grenade_projectile_B game_objects plasma_grenade_projectile // TODO for later, maybe : set_resource_parent plasma_grenade_projectile_B game_objects plasma_grenade_projectile
@ -3454,10 +3589,27 @@ object GlobalDefinitions {
plasma_grenade_projectile_B.Damage1 = 30 plasma_grenade_projectile_B.Damage1 = 30
plasma_grenade_projectile_B.DamageAtEdge = 0.1f plasma_grenade_projectile_B.DamageAtEdge = 0.1f
plasma_grenade_projectile_B.DamageRadius = 7f plasma_grenade_projectile_B.DamageRadius = 7f
plasma_grenade_projectile_B.GrenadeProjectile = true
plasma_grenade_projectile_B.ProjectileDamageType = DamageType.Aggravated plasma_grenade_projectile_B.ProjectileDamageType = DamageType.Aggravated
plasma_grenade_projectile_B.Aggravated = AggravatedDamage(
List(AggravatedInfo(DamageType.Direct, 0.25f, 750), AggravatedInfo(DamageType.Splash, 0.25f, 1000)),
Aura.Plasma,
AggravatedTiming(3000),
1.5f,
true,
false,
List(TargetValidation(EffectTarget.Category.Player, EffectTarget.Validation.Player))
)
plasma_grenade_projectile_B.InitialVelocity = 30 plasma_grenade_projectile_B.InitialVelocity = 30
plasma_grenade_projectile_B.Lifespan = 3f plasma_grenade_projectile_B.Lifespan = 3f
ProjectileDefinition.CalculateDerivedFields(plasma_grenade_projectile_B) ProjectileDefinition.CalculateDerivedFields(plasma_grenade_projectile_B)
plasma_grenade_projectile_B.Modifiers = List(
DamageModifiers.InfantryAggravatedDirect,
DamageModifiers.InfantryAggravatedDirectBurn,
DamageModifiers.InfantryAggravatedSplash,
DamageModifiers.InfantryAggravatedSplashBurn,
DamageModifiers.RadialDegrade
)
pounder_projectile.Name = "pounder_projectile" pounder_projectile.Name = "pounder_projectile"
pounder_projectile.Damage0 = 31 pounder_projectile.Damage0 = 31
@ -3467,6 +3619,7 @@ object GlobalDefinitions {
pounder_projectile.Damage4 = 132 pounder_projectile.Damage4 = 132
pounder_projectile.DamageAtEdge = 0.1f pounder_projectile.DamageAtEdge = 0.1f
pounder_projectile.DamageRadius = 1f pounder_projectile.DamageRadius = 1f
pounder_projectile.GrenadeProjectile = true
pounder_projectile.ProjectileDamageType = DamageType.Splash pounder_projectile.ProjectileDamageType = DamageType.Splash
pounder_projectile.InitialVelocity = 120 pounder_projectile.InitialVelocity = 120
pounder_projectile.Lifespan = 2.5f pounder_projectile.Lifespan = 2.5f
@ -3482,6 +3635,7 @@ object GlobalDefinitions {
pounder_projectile_enh.Damage4 = 132 pounder_projectile_enh.Damage4 = 132
pounder_projectile_enh.DamageAtEdge = 0.1f pounder_projectile_enh.DamageAtEdge = 0.1f
pounder_projectile_enh.DamageRadius = 1f pounder_projectile_enh.DamageRadius = 1f
pounder_projectile_enh.GrenadeProjectile = true
pounder_projectile_enh.ProjectileDamageType = DamageType.Splash pounder_projectile_enh.ProjectileDamageType = DamageType.Splash
pounder_projectile_enh.InitialVelocity = 120 pounder_projectile_enh.InitialVelocity = 120
pounder_projectile_enh.Lifespan = 3.2f pounder_projectile_enh.Lifespan = 3.2f
@ -3533,6 +3687,7 @@ object GlobalDefinitions {
ProjectileDefinition.CalculateDerivedFields(quasar_projectile) ProjectileDefinition.CalculateDerivedFields(quasar_projectile)
radiator_grenade_projectile.Name = "radiator_grenade_projectile" // Todo : Radiator damages ? radiator_grenade_projectile.Name = "radiator_grenade_projectile" // Todo : Radiator damages ?
radiator_grenade_projectile.GrenadeProjectile = true //not really, but technically yes
radiator_grenade_projectile.ProjectileDamageType = DamageType.Direct radiator_grenade_projectile.ProjectileDamageType = DamageType.Direct
radiator_grenade_projectile.InitialVelocity = 30 radiator_grenade_projectile.InitialVelocity = 30
radiator_grenade_projectile.Lifespan = 3f radiator_grenade_projectile.Lifespan = 3f
@ -3540,6 +3695,7 @@ object GlobalDefinitions {
radiator_sticky_projectile.Name = "radiator_sticky_projectile" radiator_sticky_projectile.Name = "radiator_sticky_projectile"
// TODO for later, maybe : set_resource_parent radiator_sticky_projectile game_objects radiator_grenade_projectile // TODO for later, maybe : set_resource_parent radiator_sticky_projectile game_objects radiator_grenade_projectile
radiator_sticky_projectile.GrenadeProjectile = true //not really, but technically yes
radiator_sticky_projectile.ProjectileDamageType = DamageType.Direct radiator_sticky_projectile.ProjectileDamageType = DamageType.Direct
radiator_sticky_projectile.InitialVelocity = 30 radiator_sticky_projectile.InitialVelocity = 30
radiator_sticky_projectile.Lifespan = 4f radiator_sticky_projectile.Lifespan = 4f
@ -3602,7 +3758,7 @@ object GlobalDefinitions {
rocklet_jammer_projectile.InitialVelocity = 50 rocklet_jammer_projectile.InitialVelocity = 50
rocklet_jammer_projectile.Lifespan = 8f rocklet_jammer_projectile.Lifespan = 8f
ProjectileDefinition.CalculateDerivedFields(rocklet_jammer_projectile) ProjectileDefinition.CalculateDerivedFields(rocklet_jammer_projectile)
rocklet_jammer_projectile.Modifiers = Nil //TODO rocklet_jammer_projectile.Modifiers = DamageModifiers.RadialDegrade?
scattercannon_projectile.Name = "scattercannon_projectile" scattercannon_projectile.Name = "scattercannon_projectile"
scattercannon_projectile.Damage0 = 11 scattercannon_projectile.Damage0 = 11
@ -3722,7 +3878,6 @@ object GlobalDefinitions {
spiker_projectile.InitialVelocity = 40 spiker_projectile.InitialVelocity = 40
spiker_projectile.Lifespan = 5f spiker_projectile.Lifespan = 5f
ProjectileDefinition.CalculateDerivedFields(spiker_projectile) ProjectileDefinition.CalculateDerivedFields(spiker_projectile)
spiker_projectile.Modifiers = DamageModifiers.RadialDegrade
spitfire_aa_ammo_projectile.Name = "spitfire_aa_ammo_projectile" spitfire_aa_ammo_projectile.Name = "spitfire_aa_ammo_projectile"
spitfire_aa_ammo_projectile.Damage0 = 5 spitfire_aa_ammo_projectile.Damage0 = 5
@ -3756,6 +3911,18 @@ object GlobalDefinitions {
starfire_projectile.Acceleration = 12 starfire_projectile.Acceleration = 12
starfire_projectile.AccelerationUntil = 5f starfire_projectile.AccelerationUntil = 5f
starfire_projectile.ProjectileDamageType = DamageType.Aggravated starfire_projectile.ProjectileDamageType = DamageType.Aggravated
starfire_projectile.Aggravated = AggravatedDamage(
AggravatedInfo(DamageType.Direct, 0.25f, 250),
Aura.Comet,
2000,
0f,
true,
List(
TargetValidation(EffectTarget.Category.Player, EffectTarget.Validation.Player),
TargetValidation(EffectTarget.Category.Vehicle, EffectTarget.Validation.Vehicle),
TargetValidation(EffectTarget.Category.Turret, EffectTarget.Validation.Turret)
)
)
starfire_projectile.InitialVelocity = 45 starfire_projectile.InitialVelocity = 45
starfire_projectile.Lifespan = 7.8f starfire_projectile.Lifespan = 7.8f
starfire_projectile.ExistsOnRemoteClients = true starfire_projectile.ExistsOnRemoteClients = true
@ -3763,6 +3930,10 @@ object GlobalDefinitions {
starfire_projectile.AutoLock = true starfire_projectile.AutoLock = true
starfire_projectile.Packet = projectileConverter starfire_projectile.Packet = projectileConverter
ProjectileDefinition.CalculateDerivedFields(starfire_projectile) ProjectileDefinition.CalculateDerivedFields(starfire_projectile)
starfire_projectile.Modifiers = List(
DamageModifiers.StarfireAggravatedBurn,
DamageModifiers.RadialDegrade
)
striker_missile_projectile.Name = "striker_missile_projectile" striker_missile_projectile.Name = "striker_missile_projectile"
striker_missile_projectile.Damage0 = 35 striker_missile_projectile.Damage0 = 35
@ -3813,6 +3984,7 @@ object GlobalDefinitions {
trek_projectile.InitialVelocity = 40 trek_projectile.InitialVelocity = 40
trek_projectile.Lifespan = 7f trek_projectile.Lifespan = 7f
ProjectileDefinition.CalculateDerivedFields(trek_projectile) ProjectileDefinition.CalculateDerivedFields(trek_projectile)
trek_projectile.Modifiers = DamageModifiers.MaxDistanceCutoff
vanu_sentry_turret_projectile.Name = "vanu_sentry_turret_projectile" vanu_sentry_turret_projectile.Name = "vanu_sentry_turret_projectile"
vanu_sentry_turret_projectile.Damage0 = 25 vanu_sentry_turret_projectile.Damage0 = 25
@ -4491,7 +4663,7 @@ object GlobalDefinitions {
flamethrower.FireModes(1).AmmoSlotIndex = 0 flamethrower.FireModes(1).AmmoSlotIndex = 0
flamethrower.FireModes(1).Magazine = 100 flamethrower.FireModes(1).Magazine = 100
flamethrower.FireModes(1).RoundsPerShot = 50 flamethrower.FireModes(1).RoundsPerShot = 50
flamethrower.Tile = InventoryTile.Tile63 flamethrower.Tile = InventoryTile.Tile93
winchester.Name = "winchester" winchester.Name = "winchester"
winchester.Size = EquipmentSize.Rifle winchester.Size = EquipmentSize.Rifle

View file

@ -1,12 +1,20 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects package net.psforever.objects
import net.psforever.objects.avatar.{Avatar, LoadoutManager} import net.psforever.objects.avatar.{
import net.psforever.objects.definition.{AvatarDefinition, ExoSuitDefinition, SpecialExoSuitDefinition} Avatar,
LoadoutManager
}
import net.psforever.objects.definition.{
AvatarDefinition,
ExoSuitDefinition,
SpecialExoSuitDefinition
}
import net.psforever.objects.equipment.{Equipment, EquipmentSize, EquipmentSlot, JammableUnit} import net.psforever.objects.equipment.{Equipment, EquipmentSize, EquipmentSlot, JammableUnit}
import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem} import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem}
import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.aura.AuraContainer
import net.psforever.objects.vital.resistance.ResistanceProfile import net.psforever.objects.vital.resistance.ResistanceProfile
import net.psforever.objects.vital.{DamageResistanceModel, Vitality} import net.psforever.objects.vital.{DamageResistanceModel, Vitality}
import net.psforever.objects.zones.ZoneAware import net.psforever.objects.zones.ZoneAware
@ -22,7 +30,8 @@ class Player(var avatar: Avatar)
with ResistanceProfile with ResistanceProfile
with Container with Container
with JammableUnit with JammableUnit
with ZoneAware { with ZoneAware
with AuraContainer {
Health = 0 //player health is artificially managed as a part of their lifecycle; start entity as dead Health = 0 //player health is artificially managed as a part of their lifecycle; start entity as dead
Destroyed = true //see isAlive Destroyed = true //see isAlive
private var backpack: Boolean = false private var backpack: Boolean = false

View file

@ -44,7 +44,6 @@ class ShieldGeneratorControl(gen: ShieldGeneratorDeployable)
def JammableObject = gen def JammableObject = gen
def DamageableObject = gen def DamageableObject = gen
def RepairableObject = gen def RepairableObject = gen
private var handleDamageToShields: Boolean = false
def receive: Receive = def receive: Receive =
jammableBehavior jammableBehavior
@ -90,18 +89,20 @@ class ShieldGeneratorControl(gen: ShieldGeneratorDeployable)
target, target,
s"BEFORE=$originalHealth/$originalShields, AFTER=$health/$shields, CHANGE=$damageToHealth/$damageToShields" s"BEFORE=$originalHealth/$originalShields, AFTER=$health/$shields, CHANGE=$damageToHealth/$damageToShields"
) )
handleDamageToShields = damageToShields > 0 HandleDamage(target, cause, (damageToHealth, damageToShields))
HandleDamage(target, cause, damageToHealth)
} else { } else {
gen.Health = originalHealth gen.Health = originalHealth
gen.Shields = originalShields gen.Shields = originalShields
} }
} }
override protected def DamageAwareness(target: Damageable.Target, cause: ResolvedProjectile, amount: Int): Unit = { override protected def DamageAwareness(target: Damageable.Target, cause: ResolvedProjectile, amount: Any): Unit = {
super.DamageAwareness(target, cause, amount) val (damageToHealth, damageToShields) = amount match {
ShieldGeneratorControl.DamageAwareness(gen, cause, handleDamageToShields) case (a: Int, b: Int) => (a, b)
handleDamageToShields = false case _ => (0, 0)
}
super.DamageAwareness(target, cause, damageToHealth)
ShieldGeneratorControl.DamageAwareness(gen, cause, damageToShields > 0)
} }
override protected def DestructionAwareness(target: Target, cause: ResolvedProjectile): Unit = { override protected def DestructionAwareness(target: Target, cause: ResolvedProjectile): Unit = {

View file

@ -75,6 +75,11 @@ class TurretControl(turret: TurretDeployable)
def DamageableObject = turret def DamageableObject = turret
def RepairableObject = turret def RepairableObject = turret
override def postStop(): Unit = {
super.postStop()
damageableWeaponTurretPostStop()
}
def receive: Receive = def receive: Receive =
checkBehavior checkBehavior
.orElse(jammableBehavior) .orElse(jammableBehavior)

View file

@ -7,6 +7,7 @@ import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem,
import net.psforever.objects.serverobject.mount.Mountable import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.aura.AuraContainer
import net.psforever.objects.serverobject.deploy.Deployment import net.psforever.objects.serverobject.deploy.Deployment
import net.psforever.objects.serverobject.hackable.Hackable import net.psforever.objects.serverobject.hackable.Hackable
import net.psforever.objects.serverobject.structures.AmenityOwner import net.psforever.objects.serverobject.structures.AmenityOwner
@ -78,7 +79,8 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
with StandardResistanceProfile with StandardResistanceProfile
with JammableUnit with JammableUnit
with CommonNtuContainer with CommonNtuContainer
with Container { with Container
with AuraContainer {
private var faction: PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL private var faction: PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL
private var shields: Int = 0 private var shields: Int = 0
private var decal: Int = 0 private var decal: Int = 0

View file

@ -8,14 +8,17 @@ import net.psforever.objects.ballistics.{PlayerSource, ResolvedProjectile}
import net.psforever.objects.equipment._ import net.psforever.objects.equipment._
import net.psforever.objects.inventory.{GridInventory, InventoryItem} import net.psforever.objects.inventory.{GridInventory, InventoryItem}
import net.psforever.objects.loadouts.Loadout import net.psforever.objects.loadouts.Loadout
import net.psforever.objects.serverobject.aura.{Aura, AuraEffectBehavior}
import net.psforever.objects.serverobject.containable.{Containable, ContainableBehavior} import net.psforever.objects.serverobject.containable.{Containable, ContainableBehavior}
import net.psforever.objects.vital.{PlayerSuicide, Vitality} import net.psforever.objects.serverobject.damage.Damageable.Target
import net.psforever.objects.vital.PlayerSuicide
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject} import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
import net.psforever.objects.serverobject.damage.Damageable import net.psforever.objects.serverobject.damage.{AggravatedBehavior, Damageable}
import net.psforever.objects.serverobject.mount.Mountable import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.serverobject.repair.Repairable import net.psforever.objects.serverobject.repair.Repairable
import net.psforever.objects.serverobject.terminals.Terminal import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.objects.vital._ import net.psforever.objects.vital._
import net.psforever.objects.vital.resolution.ResolutionCalculations.Output
import net.psforever.objects.zones.Zone import net.psforever.objects.zones.Zone
import net.psforever.packet.game._ import net.psforever.packet.game._
import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent
@ -30,13 +33,24 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
extends Actor extends Actor
with JammableBehavior with JammableBehavior
with Damageable with Damageable
with ContainableBehavior { with ContainableBehavior
def JammableObject = player with AggravatedBehavior
with AuraEffectBehavior {
def JammableObject = player
def DamageableObject = player def DamageableObject = player
def ContainerObject = player def ContainerObject = player
def AggravatedObject = player
ApplicableEffect(Aura.Plasma)
ApplicableEffect(Aura.Napalm)
ApplicableEffect(Aura.Comet)
ApplicableEffect(Aura.Fire)
def AuraTargetObject = player
private[this] val log = org.log4s.getLogger(player.Name) private[this] val log = org.log4s.getLogger(player.Name)
private[this] val damageLog = org.log4s.getLogger(Damageable.LogChannel) private[this] val damageLog = org.log4s.getLogger(Damageable.LogChannel)
@ -53,11 +67,15 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
override def postStop(): Unit = { override def postStop(): Unit = {
lockerControlAgent ! akka.actor.PoisonPill lockerControlAgent ! akka.actor.PoisonPill
player.avatar.locker.Actor = Default.Actor player.avatar.locker.Actor = Default.Actor
EndAllEffects()
EndAllAggravation()
} }
def receive: Receive = def receive: Receive =
jammableBehavior jammableBehavior
.orElse(takesDamage) .orElse(takesDamage)
.orElse(aggravatedBehavior)
.orElse(auraBehavior)
.orElse(containerBehavior) .orElse(containerBehavior)
.orElse { .orElse {
case Player.Die() => case Player.Die() =>
@ -480,29 +498,31 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
case _ => ; case _ => ;
} }
protected def TakesDamage: Receive = { override protected def PerformDamage(
case Vitality.Damage(applyDamageTo) => target: Target,
if (player.isAlive && !player.spectator) { applyDamageTo: Output
val originalHealth = player.Health ): Unit = {
val originalArmor = player.Armor if (player.isAlive && !player.spectator) {
val originalStamina = player.avatar.stamina val originalHealth = player.Health
val originalCapacitor = player.Capacitor.toInt val originalArmor = player.Armor
val cause = applyDamageTo(player) val originalStamina = player.avatar.stamina
val health = player.Health val originalCapacitor = player.Capacitor.toInt
val armor = player.Armor val cause = applyDamageTo(player)
val stamina = player.avatar.stamina val health = player.Health
val capacitor = player.Capacitor.toInt val armor = player.Armor
val damageToHealth = originalHealth - health val stamina = player.avatar.stamina
val damageToArmor = originalArmor - armor val capacitor = player.Capacitor.toInt
val damageToStamina = originalStamina - stamina val damageToHealth = originalHealth - health
val damageToCapacitor = originalCapacitor - capacitor val damageToArmor = originalArmor - armor
HandleDamage(player, cause, damageToHealth, damageToArmor, damageToStamina, damageToCapacitor) val damageToStamina = originalStamina - stamina
if (damageToHealth > 0 || damageToArmor > 0 || damageToStamina > 0 || damageToCapacitor > 0) { val damageToCapacitor = originalCapacitor - capacitor
damageLog.info( HandleDamage(player, cause, damageToHealth, damageToArmor, damageToStamina, damageToCapacitor)
s"${player.Name}-infantry: BEFORE=$originalHealth/$originalArmor/$originalStamina/$originalCapacitor, AFTER=$health/$armor/$stamina/$capacitor, CHANGE=$damageToHealth/$damageToArmor/$damageToStamina/$damageToCapacitor" if (damageToHealth > 0 || damageToArmor > 0 || damageToStamina > 0 || damageToCapacitor > 0) {
) damageLog.info(
} s"${player.Name}-infantry: BEFORE=$originalHealth/$originalArmor/$originalStamina/$originalCapacitor, AFTER=$health/$armor/$stamina/$capacitor, CHANGE=$damageToHealth/$damageToArmor/$damageToStamina/$damageToCapacitor"
)
} }
}
} }
/** /**
@ -510,70 +530,110 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
* @param target na * @param target na
*/ */
def HandleDamage( def HandleDamage(
target: Player, target: Player,
cause: ResolvedProjectile, cause: ResolvedProjectile,
damageToHealth: Int, damageToHealth: Int,
damageToArmor: Int, damageToArmor: Int,
damageToStamina: Int, damageToStamina: Int,
damageToCapacitor: Int damageToCapacitor: Int
): Unit = { ): Unit = {
val targetGUID = target.GUID //always do armor update
val zone = target.Zone
val zoneId = zone.id
val events = zone.AvatarEvents
val health = target.Health
if (damageToArmor > 0) { if (damageToArmor > 0) {
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 4, target.Armor)) val zone = target.Zone
zone.AvatarEvents ! AvatarServiceMessage(
zone.id,
AvatarAction.PlanetsideAttributeToAll(target.GUID, 4, target.Armor)
)
} }
if (health > 0) { //choose
if (damageToCapacitor > 0) { if (target.Health > 0) {
events ! AvatarServiceMessage( DamageAwareness(target, cause, damageToHealth, damageToArmor, damageToStamina, damageToCapacitor)
target.Name,
AvatarAction.PlanetsideAttributeSelf(targetGUID, 7, target.Capacitor.toLong)
)
}
if (damageToHealth > 0 || damageToStamina > 0) {
target.History(cause)
if (damageToHealth > 0) {
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 0, health))
}
if (damageToStamina > 0) {
avatarActor ! AvatarActor.ConsumeStamina(damageToStamina)
}
//activity on map
zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
//alert damage source
DamageAwareness(target, cause)
}
if (Damageable.CanJammer(target, cause)) {
target.Actor ! JammableUnit.Jammered(cause)
}
} else { } else {
DestructionAwareness(target, Some(cause)) DestructionAwareness(target, Some(cause))
} }
} }
/** def DamageAwareness(
* na target: Player,
* @param target na cause: ResolvedProjectile,
* @param cause na damageToHealth: Int,
*/ damageToArmor: Int,
def DamageAwareness(target: Player, cause: ResolvedProjectile): Unit = { damageToStamina: Int,
val zone = target.Zone damageToCapacitor: Int
zone.AvatarEvents ! AvatarServiceMessage( ): Unit = {
target.Name, val targetGUID = target.GUID
cause.projectile.owner match { val zone = target.Zone
case pSource: PlayerSource => //player damage val zoneId = zone.id
val name = pSource.Name val events = zone.AvatarEvents
zone.LivePlayers.find(_.Name == name).orElse(zone.Corpses.find(_.Name == name)) match { val health = target.Health
case Some(tplayer) => AvatarAction.HitHint(tplayer.GUID, target.GUID) var announceConfrontation = damageToArmor > 0
case None => //special effects
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, pSource.Position)) if (Damageable.CanJammer(target, cause)) {
TryJammerEffectActivate(target, cause)
}
val aggravated: Boolean = TryAggravationEffectActivate(cause) match {
case Some(aggravation) =>
StartAuraEffect(aggravation.effect_type, aggravation.timing.duration)
announceConfrontation = true //useful if initial damage (to anything) is zero
//initial damage for aggravation, but never treat as "aggravated"
false
case _ =>
cause.projectile.profile.ProjectileDamageTypes.contains(DamageType.Aggravated)
}
//log historical event
target.History(cause)
//stat changes
if (damageToCapacitor > 0) {
events ! AvatarServiceMessage(
target.Name,
AvatarAction.PlanetsideAttributeSelf(targetGUID, 7, target.Capacitor.toLong)
)
announceConfrontation = true
}
if (damageToStamina > 0) {
avatarActor ! AvatarActor.ConsumeStamina(damageToStamina)
announceConfrontation = true
}
if (damageToHealth > 0) {
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 0, health))
announceConfrontation = true
}
val countableDamage = damageToHealth + damageToArmor
if(announceConfrontation) {
if (!aggravated) {
//activity on map
zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
//alert to damage source
zone.AvatarEvents ! AvatarServiceMessage(
target.Name,
cause.projectile.owner match {
case pSource: PlayerSource => //player damage
val name = pSource.Name
zone.LivePlayers.find(_.Name == name).orElse(zone.Corpses.find(_.Name == name)) match {
case Some(tplayer) =>
AvatarAction.HitHint(tplayer.GUID, target.GUID)
case None =>
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(countableDamage, pSource.Position))
}
case source =>
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(countableDamage, source.Position))
} }
case source => )
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, source.Position))
} }
) else {
//general alert
zone.AvatarEvents ! AvatarServiceMessage(
target.Name,
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(countableDamage, Vector3.Zero))
)
}
}
if (aggravated) {
events ! AvatarServiceMessage(
zoneId,
AvatarAction.SendResponse(Service.defaultPlayerGUID, AggravatedDamageMessage(targetGUID, countableDamage))
)
}
} }
/** /**
@ -600,6 +660,10 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
val nameChannel = target.Name val nameChannel = target.Name
val zoneChannel = zone.id val zoneChannel = zone.id
target.Die target.Die
//aura effects cancel
EndAllEffects()
//aggravation cancel
EndAllAggravation()
//unjam //unjam
CancelJammeredSound(target) CancelJammeredSound(target)
CancelJammeredStatus(target) CancelJammeredStatus(target)
@ -864,4 +928,28 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
AvatarAction.ObjectDelete(Service.defaultPlayerGUID, item.GUID) AvatarAction.ObjectDelete(Service.defaultPlayerGUID, item.GUID)
) )
} }
def UpdateAuraEffect(target: AuraEffectBehavior.Target) : Unit = {
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
val zone = target.Zone
val value = target.Aura.foldLeft(0)(_ + PlayerControl.auraEffectToAttributeValue(_))
zone.AvatarEvents ! AvatarServiceMessage(zone.id, AvatarAction.PlanetsideAttributeToAll(target.GUID, 54, value))
}
}
object PlayerControl {
/**
* Transform an applicable Aura effect into its `PlanetsideAttributeMessage` value.
* @see `Aura`
* @see `PlanetsideAttributeMessage`
* @param effect the aura effect
* @return the attribute value for that effect
*/
private def auraEffectToAttributeValue(effect: Aura): Int = effect match {
case Aura.Plasma => 1
case Aura.Comet => 2
case Aura.Napalm => 4
case Aura.Fire => 8
case _ => 0
}
} }

View file

@ -0,0 +1,178 @@
// Copyright (c) 2020 PSForever
package net.psforever.objects.ballistics
import net.psforever.objects.equipment.TargetValidation
import net.psforever.objects.serverobject.aura.Aura
import net.psforever.objects.vital.DamageType
/**
* In what manner of pacing the aggravated damage ticks are applied.
* @param duration for how long the over-all effect is applied
* @param ticks a custom number of damage applications,
* as opposed to whatever calculations normally estimate the number of applications
*/
final case class AggravatedTiming(duration: Long, ticks: Option[Int])
object AggravatedTiming {
/**
* Overloaded constructor that only defines the duration.
* @param duration for how long the over-all effect lasts
* @return an `AggravatedTiming` object
*/
def apply(duration: Long): AggravatedTiming = AggravatedTiming(duration, None)
/**
* Overloaded constructor.
* @param duration for how long the over-all effect lasts
* @param ticks a custom number of damage applications
* @return an `AggravatedTiming` object
*/
def apply(duration: Long, ticks: Int): AggravatedTiming = AggravatedTiming(duration, Some(ticks))
}
/**
* Aggravation damage has components that are mainly divided by the `DamageType` they inflict.
* Only `Direct` and `Splash` are valid damage types, however.
* @param damage_type the type of damage
* @param degradation_percentage by how much the damage is degraded
* @param infliction_rate how often the damage is inflicted (ms)
*/
final case class AggravatedInfo(damage_type: DamageType.Value,
degradation_percentage: Float,
infliction_rate: Long) {
assert(damage_type == DamageType.Direct || damage_type == DamageType.Splash, s"aggravated damage is an unsupported type - $damage_type")
}
/**
* Information related to the aggravated damage.
* @param info the specific kinds of aggravation damage available
* @param effect_type what effect is exhibited by this aggravated damage
* @param timing the timing for the damage application
* @param max_factor na (if the target is a mechanized assault exo-suit?)
* @param cumulative_damage_degrade na (can multiple instances of this type of aggravated damage apply to the same target at once?)
* @param vanu_aggravated na (search me)
* @param targets validation information indicating whether a certain entity is applicable for aggravation
*/
final case class AggravatedDamage(info: List[AggravatedInfo],
effect_type: Aura,
timing: AggravatedTiming,
max_factor: Float,
cumulative_damage_degrade: Boolean,
vanu_aggravated: Boolean,
targets: List[TargetValidation])
object AggravatedDamage {
/**
* Overloaded constructor.
* @param info the specific kinds of aggravation damage available
* @param effect_type what effect is exhibited by this aggravated damage
* @param timing the timing for the damage application
* @param max_factor na
* @param targets validation information indicating whether a certain entity is applicable for aggravation
*/
def apply(info: AggravatedInfo,
effect_type: Aura,
timing: AggravatedTiming,
max_factor: Float,
targets: List[TargetValidation]): AggravatedDamage =
AggravatedDamage(
List(info),
effect_type,
timing,
max_factor,
cumulative_damage_degrade = true,
vanu_aggravated = false,
targets
)
/**
* Overloaded constructor.
* @param info the specific kinds of aggravation damage available
* @param effect_type what effect is exhibited by this aggravated damage
* @param timing the timing for the damage application
* @param max_factor na
* @param vanu_aggravated na
* @param targets validation information indicating whether a certain entity is applicable for aggravation
*/
def apply(info: AggravatedInfo,
effect_type: Aura,
timing: AggravatedTiming,
max_factor: Float,
vanu_aggravated: Boolean,
targets: List[TargetValidation]): AggravatedDamage =
AggravatedDamage(
List(info),
effect_type,
timing,
max_factor,
cumulative_damage_degrade = true,
vanu_aggravated,
targets
)
/**
* Overloaded constructor.
* @param info the specific kinds of aggravation damage available
* @param effect_type what effect is exhibited by this aggravated damage
* @param duration for how long the over-all effect is applied
* @param max_factor na
* @param targets validation information indicating whether a certain entity is applicable for aggravation
*/
def apply(info: AggravatedInfo,
effect_type: Aura,
duration: Long,
max_factor: Float,
targets: List[TargetValidation]): AggravatedDamage =
AggravatedDamage(
List(info),
effect_type,
AggravatedTiming(duration),
max_factor,
cumulative_damage_degrade = true,
vanu_aggravated = false,
targets
)
/**
* Overloaded constructor.
* @param info the specific kinds of aggravation damage available
* @param effect_type what effect is exhibited by this aggravated damage
* @param duration for how long the over-all effect is applied
* @param max_factor na
* @param vanu_aggravated na
* @param targets validation information indicating whether a certain entity is applicable for aggravation
*/
def apply(info: AggravatedInfo,
effect_type: Aura,
duration: Long,
max_factor: Float,
vanu_aggravated: Boolean,
targets: List[TargetValidation]): AggravatedDamage =
AggravatedDamage(
List(info),
effect_type,
AggravatedTiming(duration),
max_factor,
cumulative_damage_degrade = true,
vanu_aggravated,
targets
)
def burning(resolution: ProjectileResolution.Value): ProjectileResolution.Value = {
resolution match {
case ProjectileResolution.AggravatedDirect => ProjectileResolution.AggravatedDirectBurn
case ProjectileResolution.AggravatedSplash => ProjectileResolution.AggravatedSplashBurn
case _ => resolution
}
}
def basicDamageType(resolution: ProjectileResolution.Value): DamageType.Value = {
resolution match {
case ProjectileResolution.AggravatedDirect | ProjectileResolution.AggravatedDirectBurn =>
DamageType.Direct
case ProjectileResolution.AggravatedSplash | ProjectileResolution.AggravatedSplashBurn =>
DamageType.Splash
case _ =>
DamageType.None
}
}
}

View file

@ -1,6 +1,8 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.ballistics package net.psforever.objects.ballistics
import java.util.concurrent.atomic.AtomicLong
import net.psforever.objects.PlanetSideGameObject import net.psforever.objects.PlanetSideGameObject
import net.psforever.objects.definition.{ProjectileDefinition, ToolDefinition} import net.psforever.objects.definition.{ProjectileDefinition, ToolDefinition}
import net.psforever.objects.entity.SimpleWorldEntity import net.psforever.objects.entity.SimpleWorldEntity
@ -10,11 +12,11 @@ import net.psforever.types.Vector3
/** /**
* A summation of weapon (`Tool`) discharge. * A summation of weapon (`Tool`) discharge.
* @see `ProjectileDefinition`<br> * @see `ProjectileDefinition`
* `ToolDefinition`<br> * @see `ToolDefinition`
* `FireModeDefinition`<br> * @see `FireModeDefinition`
* `SourceEntry`<br> * @see `SourceEntry`
* `PlayerSource` * @see `PlayerSource`
* @param profile an explanation of the damage that can be performed by this discharge * @param profile an explanation of the damage that can be performed by this discharge
* @param tool_def the weapon that caused this discharge * @param tool_def the weapon that caused this discharge
* @param fire_mode the current fire mode of the tool used * @param fire_mode the current fire mode of the tool used
@ -25,6 +27,9 @@ import net.psforever.types.Vector3
* if not, then it is a type of vehicle (and owner should have a positive `seated` field) * if not, then it is a type of vehicle (and owner should have a positive `seated` field)
* @param shot_origin where the projectile started * @param shot_origin where the projectile started
* @param shot_angle in which direction the projectile was aimed when it was discharged * @param shot_angle in which direction the projectile was aimed when it was discharged
* @param quality na
* @param id an exclusive identifier for this projectile;
* normally generated internally, but can be manually set
* @param fire_time when the weapon discharged was recorded; * @param fire_time when the weapon discharged was recorded;
* defaults to `System.nanoTime` * defaults to `System.nanoTime`
*/ */
@ -36,6 +41,8 @@ final case class Projectile(
attribute_to: Int, attribute_to: Int,
shot_origin: Vector3, shot_origin: Vector3,
shot_angle: Vector3, shot_angle: Vector3,
quality: ProjectileQuality = ProjectileQuality.Normal,
id: Long = Projectile.idGenerator.getAndIncrement(),
fire_time: Long = System.nanoTime fire_time: Long = System.nanoTime
) extends PlanetSideGameObject { ) extends PlanetSideGameObject {
Position = shot_origin Position = shot_origin
@ -52,6 +59,32 @@ final case class Projectile(
val current: SimpleWorldEntity = new SimpleWorldEntity() val current: SimpleWorldEntity = new SimpleWorldEntity()
private var resolved: ProjectileResolution.Value = ProjectileResolution.Unresolved private var resolved: ProjectileResolution.Value = ProjectileResolution.Unresolved
/**
* Create a copy of this projectile with all the same information
* save for the quality.
* Used mainly for aggravated damage.
* It is important to note that the new projectile shares the (otherwise) exclusive id of the original.
* @param value the new quality
* @return a new `Projectile` entity
*/
def quality(value: ProjectileQuality): Projectile = {
val projectile = Projectile(
profile,
tool_def,
fire_mode,
owner,
attribute_to,
shot_origin,
shot_angle,
value,
id,
fire_time
)
if(isMiss) projectile.Miss()
else if(isResolved) projectile.Resolve()
projectile
}
/** /**
* Mark the projectile as being "encountered" or "managed" at least once. * Mark the projectile as being "encountered" or "managed" at least once.
*/ */
@ -80,6 +113,8 @@ object Projectile {
*/ */
final val rangeUID: Int = 40150 final val rangeUID: Int = 40150
private val idGenerator: AtomicLong = new AtomicLong
/** /**
* Overloaded constructor for an `Unresolved` projectile. * Overloaded constructor for an `Unresolved` projectile.
* @param profile an explanation of the damage that can be performed by this discharge * @param profile an explanation of the damage that can be performed by this discharge

View file

@ -0,0 +1,36 @@
//Copyright (c) 2020 PSForever
package net.psforever.objects.ballistics
/**
* Projectile quality is an external aspect of projectiles
* that is not dependent on hard-coded definitions of the entities
* used to compose the projectile such as the knowlegde of the emitting `Tool` (weapon).
* A flag or a damage modifier, depending on use.
* To the extent that it can be used as a numeric modifier,
* insists on defining a numeric modifier component rather to what it is trying to express.
* That numeric modifier does not have to be used for anything.
*/
sealed trait ProjectileQuality {
def mod: Float
}
/**
* Implement the numeric modifier with the value as one.
*/
sealed trait SameAsQuality extends ProjectileQuality {
def mod: Float = 1f
}
object ProjectileQuality {
/** Standard projectile quality. More of a flag than a modifier. */
case object Normal extends SameAsQuality
/** Quality that flags the first stage of aggravation (initial damage). */
case object AggravatesTarget extends SameAsQuality
/** The complete lack of quality. Even the numeric modifier is zeroed. */
case object Zeroed extends ProjectileQuality { def mod = 0f }
/** Assign a custom numeric qualifier value, usually to be applied to damage calculations. */
case class Modified(mod: Float) extends ProjectileQuality
}

View file

@ -2,16 +2,32 @@
package net.psforever.objects.ballistics package net.psforever.objects.ballistics
/** /**
* An `Enumeration` of outcomes regarding what actually happened to the projectile. * An `Enumeration` of outcomes regarding what actually happened to the projectile,
* complementing normal damage type distinction in directing damage calculations.<br>
* <br>
* Although the latter states reflect what sort of damage the projectile might perform - `Hit`, `Splash`, etc. -
* the state is more a communication about how that damage is interpreted by the server.
* For example, some projectiles:
* perform `Direct` damage, are reported by `HitMessage` packets, and resolve as `Hit`;
* or, perform `Direct` damage, are reported by `LashDamage` packets, and resolve as `Lash`.
* Furthermore, some projectiles:
* perform `Splash` damage, are reported by `SplashHitMessage` packets, and resolve as `Splash`;
* or, perform `Aggravated` damage, are reported by `SplashHitMessage` packets
* and resolve either as `AggravatedDirect` or as `AggravatedSplash`.
*/ */
object ProjectileResolution extends Enumeration { object ProjectileResolution extends Enumeration {
type Type = Value type Type = Value
val Unresolved, //original basic non-resolution val
MissedShot, //projectile did not encounter any collision object and was despawned Unresolved, //original basic non-resolution
Resolved, //a general "projectile encountered something" status with a more specific resolution MissedShot, //projectile did not encounter any collision object and was despawned
Hit, //direct hit, one target Resolved, //a general "projectile encountered something" status with a more specific resolution
Splash, //area of effect damage, potentially multiple targets Hit, //direct hit, one target
Lash //lashing damage, potentially multiple targets Splash, //area of effect damage, potentially multiple targets
Lash, //lashing damage, potentially multiple targets
AggravatedDirect, //direct hit aggravated damage
AggravatedDirectBurn, //continuous direct hit aggravated damage
AggravatedSplash, //splashed aggravated damage
AggravatedSplashBurn //continuous splashed aggravated damage
= Value = Value
} }

View file

@ -9,14 +9,13 @@ import net.psforever.types.Vector3
* about the interaction of weapons discharge and a target * about the interaction of weapons discharge and a target
* to the point that the original event might be reconstructed. * to the point that the original event might be reconstructed.
* Reenacting the calculations of this entry should always produce the same values. * Reenacting the calculations of this entry should always produce the same values.
* @param resolution how the projectile hit was executed
* @param projectile the original projectile * @param projectile the original projectile
* @param target what the projectile hit * @param target what the projectile hit
* @param damage_model the kind of damage model to which the `target` is/was subject * @param damage_model the kind of damage model to which the `target` is/was subject
* @param hit_pos where the projectile hit * @param hit_pos where the projectile hit
*/ */
final case class ResolvedProjectile( final case class ResolvedProjectile(
resolution: ProjectileResolution.Value, resolution : ProjectileResolution.Value,
projectile: Projectile, projectile: Projectile,
target: SourceEntry, target: SourceEntry,
damage_model: DamageResistanceModel, damage_model: DamageResistanceModel,

View file

@ -33,7 +33,7 @@ object Deployable {
def Includes(category: DeployableCategory.Value): List[DeployedItem.Value] = { def Includes(category: DeployableCategory.Value): List[DeployedItem.Value] = {
(for { (for {
(ce, cat) <- deployablesToCategories (ce: DeployedItem.Value, cat: DeployableCategory.Value) <- deployablesToCategories
if cat == category if cat == category
} yield ce) toList } yield ce) toList
} }

View file

@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.definition package net.psforever.objects.definition
import net.psforever.objects.ballistics.Projectiles import net.psforever.objects.ballistics.{AggravatedDamage, Projectiles}
import net.psforever.objects.equipment.JammingUnit import net.psforever.objects.equipment.JammingUnit
import net.psforever.objects.vital.damage.DamageModifiers import net.psforever.objects.vital.damage.DamageModifiers
import net.psforever.objects.vital.{DamageType, StandardDamageProfile} import net.psforever.objects.vital.{DamageType, StandardDamageProfile}
@ -16,30 +16,68 @@ class ProjectileDefinition(objectId: Int)
with JammingUnit with JammingUnit
with StandardDamageProfile with StandardDamageProfile
with DamageModifiers { with DamageModifiers {
/** ascertain that this object is a valid projectile type */
private val projectileType: Projectiles.Value = Projectiles(objectId) //let throw NoSuchElementException private val projectileType: Projectiles.Value = Projectiles(objectId) //let throw NoSuchElementException
/** how much faster (or slower) the projectile moves (m/s^2^) */
private var acceleration: Int = 0 private var acceleration: Int = 0
/** when the acceleration stops being applied (s) */
private var accelerationUntil: Float = 0f private var accelerationUntil: Float = 0f
/** the type of damage that the projectile causes */
private var damageType: DamageType.Value = DamageType.None private var damageType: DamageType.Value = DamageType.None
/** an auxillary type of damage that the projectile causes */
private var damageTypeSecondary: DamageType.Value = DamageType.None private var damageTypeSecondary: DamageType.Value = DamageType.None
/** against Infantry targets, this projectile does not do armor damage */
private var damageToHealthOnly: Boolean = false
/** number of seconds before an airborne projectile's damage begins to degrade (s) */
private var degradeDelay: Float = 1f private var degradeDelay: Float = 1f
/** the rate of degrade of projectile damage after the degrade delay */
private var degradeMultiplier: Float = 1f private var degradeMultiplier: Float = 1f
/** the out-of-the-muzzle speed of a projectile (m/s) */
private var initialVelocity: Int = 1 private var initialVelocity: Int = 1
/** for how long the projectile exists (s) */
private var lifespan: Float = 1f private var lifespan: Float = 1f
/** for radial damage, how much damage has been lost the further away from the impact point (m) */
private var damageAtEdge: Float = 1f private var damageAtEdge: Float = 1f
/** for radial damage, the radial distance of the explosion effect (m) */
private var damageRadius: Float = 1f private var damageRadius: Float = 1f
/** for lashing damage, how far away a target will be affected by the projectile (m) */
private var lashRadius : Float = 0f private var lashRadius : Float = 0f
/** use a specific modifier as a part of damage calculations */
private var useDamage1Subtract: Boolean = false private var useDamage1Subtract: Boolean = false
private var existsOnRemoteClients: Boolean = false //`true` spawns a server-managed object /** the projectile is represented by a server-side entity
private var remoteClientData: (Int, Int) = * that is updated by the projectile owner
(0, 0) //artificial values; for ObjectCreateMessage packet (oicw_little_buddy is undefined) * and transmitted to all projectile observers;
* `true` spawns a server-managed object */
private var existsOnRemoteClients: Boolean = false
/** the values used by the `ObjectCreateMessage` packet for construction of the server-managed projectile
* `0, 0` are artificial values;
* the oicw_little_buddy is undefined for these values */
private var remoteClientData: (Int, Int) = (0, 0)
/** some other entity confers projectile damage;
* a set value should not `None` and not `0` but is preferred to be the damager's uid */
private var damageProxy: Option[Int] = None private var damageProxy: Option[Int] = None
/** this projectile follows its target, after a fashion */
private var autoLock: Boolean = false private var autoLock: Boolean = false
/** na;
* currently used with jammer properties only */
private var additionalEffect: Boolean = false private var additionalEffect: Boolean = false
/** the projectile tries to confer the jammered status effect to its target(s) */
private var jammerProjectile: Boolean = false private var jammerProjectile: Boolean = false
/** projectile takes the form of a type of "grenade";
* grenades arc with gravity rather than travel in a relatively straight path */
private var grenade_projectile : Boolean = false
/** projectile tries to confers aggravated damage burn to its target */
private var aggravated_damage : Option[AggravatedDamage] = None
//derived calculations //derived calculations
/** the calculated distance at which the projectile have traveled far enough to despawn (m);
* typically handled as the projectile no longer performing damage;
* occasionally, this value is purely mathematical as opposed to realistic, e.g., the melee weapons */
private var distanceMax: Float = 0f private var distanceMax: Float = 0f
/** how far the projectile will travel while accelerating (m) */
private var distanceFromAcceleration: Float = 0f private var distanceFromAcceleration: Float = 0f
/** how far the projectile will travel while no degrading (m) */
private var distanceNoDegrade: Float = 0f private var distanceNoDegrade: Float = 0f
/** after acceleration, if any, what is the final speed of the projectile (m/s) */
private var finalVelocity: Float = 0f private var finalVelocity: Float = 0f
Name = "projectile" Name = "projectile"
Modifiers = DamageModifiers.DistanceDegrade Modifiers = DamageModifiers.DistanceDegrade
@ -81,6 +119,17 @@ class ProjectileDefinition(objectId: Int)
ProjectileDamageTypeSecondary ProjectileDamageTypeSecondary
} }
def ProjectileDamageTypes : Set[DamageType.Value] = {
Set(damageType, damageTypeSecondary).filterNot(_ == DamageType.None)
}
def DamageToHealthOnly : Boolean = damageToHealthOnly
def DamageToHealthOnly_=(healthOnly: Boolean) : Boolean = {
damageToHealthOnly = healthOnly
DamageToHealthOnly
}
def DegradeDelay: Float = degradeDelay def DegradeDelay: Float = degradeDelay
def DegradeDelay_=(degradeDelay: Float): Float = { def DegradeDelay_=(degradeDelay: Float): Float = {
@ -174,7 +223,23 @@ class ProjectileDefinition(objectId: Int)
JammerProjectile JammerProjectile
} }
def DistanceMax: Float = distanceMax //accessor only def GrenadeProjectile : Boolean = grenade_projectile
def GrenadeProjectile_=(isGrenade : Boolean) : Boolean = {
grenade_projectile = isGrenade
GrenadeProjectile
}
def Aggravated : Option[AggravatedDamage] = aggravated_damage
def Aggravated_=(damage : AggravatedDamage) : Option[AggravatedDamage] = Aggravated_=(Some(damage))
def Aggravated_=(damage : Option[AggravatedDamage]) : Option[AggravatedDamage] = {
aggravated_damage = damage
Aggravated
}
def DistanceMax : Float = distanceMax //accessor only
def DistanceFromAcceleration: Float = distanceFromAcceleration //accessor only def DistanceFromAcceleration: Float = distanceFromAcceleration //accessor only

View file

@ -89,10 +89,10 @@ object EffectTarget {
false false
} }
def AMS(target: PlanetSideGameObject): Boolean = def Vehicle(target: PlanetSideGameObject): Boolean =
target match { target match {
case v: Vehicle => case v: Vehicle =>
v.Health > 0 && v.Definition == GlobalDefinitions.ams v.Health > 0
case _ => case _ =>
false false
} }
@ -104,5 +104,21 @@ object EffectTarget {
case _ => case _ =>
false false
} }
def AMS(target: PlanetSideGameObject): Boolean =
target match {
case v: Vehicle =>
v.Health > 0 && v.Definition == GlobalDefinitions.ams
case _ =>
false
}
def Aircraft(target: PlanetSideGameObject): Boolean =
target match {
case v: Vehicle =>
GlobalDefinitions.isFlightVehicle(v.Definition) && v.Health > 0
case _ =>
false
}
} }
} }

View file

@ -0,0 +1,33 @@
// Copyright (c) 2020 PSForever
package net.psforever.objects.serverobject.aura
/**
* An effect that can be emitted by a target game object entity.
*/
sealed class Aura()
/**
* Visual effects emitted by a target, usually a `Player` entity.
* Most often paired with aggravated damage.
* Unrelated to the effects emitted by obtaining and transporting
* the lattice logic unit, or a facility module, or the rabbit ball.
*/
object Aura {
/** Since `None` is an actual effect, the "no effect" default is repurposed as "Nothing". */
final case object Nothing extends Aura
/** Conferred by the `aphelion_starfire_projectile`. */
final case object None extends Aura
/** A green emission. */
final case object Plasma extends Aura
/** A purple emission. */
final case object Comet extends Aura
/** A white and yellow starburst emission. */
final case object Napalm extends Aura
/** A red and orange emission. */
final case object Fire extends Aura
}

View file

@ -0,0 +1,25 @@
package net.psforever.objects.serverobject.aura
import net.psforever.objects.serverobject.aura.{Aura => AuraEffect}
/**
* An entity that can display specific special effects that decorate its model.
* These animations confer information about the nature of some status that is affecting the target entity.
*/
trait AuraContainer {
private var aura : Set[AuraEffect] = Set.empty[AuraEffect]
def Aura : Set[AuraEffect] = aura
def AddEffectToAura(effect : AuraEffect) : Set[AuraEffect] = {
if(effect != AuraEffect.None) {
aura = aura + effect
}
Aura
}
def RemoveEffectFromAura(effect : AuraEffect) : Set[AuraEffect] = {
aura = aura - effect
Aura
}
}

View file

@ -0,0 +1,186 @@
// Copyright (c) 2020 PSForever
package net.psforever.objects.serverobject.aura
import akka.actor.{Actor, Cancellable}
import net.psforever.objects.Default
import net.psforever.objects.serverobject.PlanetSideServerObject
import scala.collection.mutable
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
/**
* A mixin that governs the addition, display, and removal of aura particle effects
* on a target with control agency.
* @see `Aura`
* @see `AuraContainer`
* @see `PlayerControl`
*/
trait AuraEffectBehavior {
_ : Actor =>
/** active aura effects are monotonic, but the timer will be updated for continuing and cancelling effects as well<br>
* only effects that are initialized to this mapping are approved for display on this target<br>
* key - aura effect; value - the timer for that effect
* @see `ApplicableEffect`
*/
private val effectToTimer: mutable.HashMap[Aura, AuraEffectBehavior.Entry] = mutable.HashMap.empty[Aura, AuraEffectBehavior.Entry]
def AuraTargetObject: AuraEffectBehavior.Target
val auraBehavior: Receive = {
case AuraEffectBehavior.StartEffect(effect, duration) =>
StartAuraEffect(effect, duration)
case AuraEffectBehavior.EndEffect(effect) =>
EndAuraEffectAndUpdate(effect)
case AuraEffectBehavior.EndAllEffects() =>
EndAllEffectsAndUpdate()
}
/**
* Only pre-apporved aura effects will be emitted by this target.
* @param effect the aura effect
*/
def ApplicableEffect(effect: Aura): Unit = {
//create entry
effectToTimer += effect -> AuraEffectBehavior.Entry()
}
/**
* An aura particle effect is to be emitted by the target.
* If the effect was not previously applied to the target in an ongoing manner,
* animate it appropriately.
* @param effect the effect to be emitted
* @param duration for how long the effect will be emitted
* @return the active effect index number
*/
def StartAuraEffect(effect: Aura, duration: Long): Unit = {
val obj = AuraTargetObject
val auraEffectsBefore = obj.Aura.size
if(StartAuraTimer(effect, duration) && obj.AddEffectToAura(effect).size > auraEffectsBefore) {
//new effect; update visuals
UpdateAuraEffect(AuraTargetObject)
}
}
/**
* As long as the effect has been approved for this target,
* the timer will either start if it is stopped or has never been started,
* or the timer will stop and be recreated with the new duration if is currently running for a shoreter amount of time.
* @param effect the effect to be emitted
* @param duration for how long the effect will be emitted
* @return `true`, if the timer was started or restarted;
* `false`, otherwise
*/
private def StartAuraTimer(effect: Aura, duration: Long): Boolean = {
//pair aura effect with entry
(effectToTimer.get(effect) match {
case Some(timer) if timer.start + timer.duration < System.currentTimeMillis() + duration =>
timer.cancel()
Some(effect)
case _ =>
None
}) match {
case None =>
false
case Some(_) =>
//retime
effectToTimer(effect) = AuraEffectBehavior.Entry(
duration,
context.system.scheduler.scheduleOnce(duration milliseconds, self, AuraEffectBehavior.EndEffect(effect))
)
true
}
}
/**
* Stop the target entity from emitting the aura particle effect, if it currently is.
* @param effect the target effect
* @return `true`, if the effect was being emitted but has been stopped
* `false`, if the effect was not approved or is not being emitted
*/
def EndAuraEffect(effect: Aura): Boolean = {
effectToTimer.get(effect) match {
case Some(timer) if !timer.isCancelled =>
timer.cancel()
//effectToTimer(effect) = Default.Cancellable
AuraTargetObject.RemoveEffectFromAura(effect)
true
case _ =>
false
}
}
/**
* Stop the target entity from emitting all aura particle effects.
*/
def EndAllEffects() : Unit = {
effectToTimer.keysIterator.foreach { effect =>
effectToTimer(effect).cancel()
//effectToTimer(effect) = Default.Cancellable
}
val obj = AuraTargetObject
obj.Aura.foreach { obj.RemoveEffectFromAura }
}
/**
* Stop the target entity from emitting the aura particle effect, if it currently is.
* If the effect has been stopped, animate the new particle effect state.
*/
def EndAuraEffectAndUpdate(effect: Aura) : Unit = {
if(EndAuraEffect(effect)) {
UpdateAuraEffect(AuraTargetObject)
}
}
/**
* Stop the target entity from emitting all aura particle effects.
* Animate the new particle effect state.
*/
def EndAllEffectsAndUpdate() : Unit = {
EndAllEffects()
UpdateAuraEffect(AuraTargetObject)
}
/**
* Is the target entity emitting the aura effect?
* @param effect the effect being tested
* @return `true`, if the effect is currently being emitted;
* `false`, otherwise
*/
def TestForEffect(effect: Aura): Boolean = {
effectToTimer.get(effect) match {
case None => false
case Some(timer) => timer.isCancelled
}
}
/**
* An override callback to display aura effects emitted.
* @param target the entity from which the aura effects are being emitted
*/
def UpdateAuraEffect(target: AuraEffectBehavior.Target) : Unit
}
object AuraEffectBehavior {
type Target = PlanetSideServerObject with AuraContainer
case class Entry(duration: Long, timer: Cancellable) extends Cancellable {
val start: Long = System.currentTimeMillis()
override def isCancelled : Boolean = timer.isCancelled
override def cancel(): Boolean = timer.cancel()
}
object Entry {
def apply(): Entry = Entry(0, Default.Cancellable)
}
final case class StartEffect(effect: Aura, duration: Long)
final case class EndEffect(aura: Aura)
final case class EndAllEffects()
}

View file

@ -0,0 +1,209 @@
// Copyright (c) 2020 PSForever
package net.psforever.objects.serverobject.damage
import akka.actor.{Actor, Cancellable}
import net.psforever.objects.ballistics._
import net.psforever.objects.serverobject.aura.Aura
import net.psforever.objects.vital.{DamageType, Vitality}
import scala.collection.mutable
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
trait AggravatedBehavior {
_ : Actor with Damageable =>
private val entryIdToEntry: mutable.LongMap[AggravatedBehavior.Entry] =
mutable.LongMap.empty[AggravatedBehavior.Entry]
private val aggravationToTimer: mutable.LongMap[Cancellable] =
mutable.LongMap.empty[Cancellable]
/** ongoing flag to indicate whether the target is being afflicted by any form of aggravated damage */
private var ongoingAggravated: Boolean = false
def AggravatedObject: AggravatedBehavior.Target
def TryAggravationEffectActivate(data: ResolvedProjectile): Option[AggravatedDamage] = {
val projectile = data.projectile
projectile.profile.Aggravated match {
case Some(damage)
if projectile.profile.ProjectileDamageTypes.contains(DamageType.Aggravated) &&
damage.info.exists(_.damage_type == AggravatedDamage.basicDamageType(data.resolution)) &&
damage.effect_type != Aura.Nothing &&
(projectile.quality == ProjectileQuality.AggravatesTarget ||
damage.targets.exists(validation => validation.test(AggravatedObject))) =>
TryAggravationEffectActivate(damage, data)
case _ =>
None
}
}
private def TryAggravationEffectActivate(
aggravation: AggravatedDamage,
data: ResolvedProjectile
): Option[AggravatedDamage] = {
val effect = aggravation.effect_type
if(CheckForUniqueUnqueuedProjectile(data.projectile)) {
val sameEffect = entryIdToEntry.values.filter(entry => entry.effect == effect)
if(sameEffect.isEmpty || sameEffect.nonEmpty && aggravation.cumulative_damage_degrade) {
SetupAggravationEntry(aggravation, data)
Some(aggravation)
}
else {
None
}
}
else {
None
}
}
private def CheckForUniqueUnqueuedProjectile(projectile : Projectile): Boolean = {
!entryIdToEntry.values.exists { entry => entry.data.projectile.id == projectile.id }
}
private def SetupAggravationEntry(aggravation: AggravatedDamage, data: ResolvedProjectile): Boolean = {
val effect = aggravation.effect_type
aggravation.info.find(_.damage_type == AggravatedDamage.basicDamageType(data.resolution)) match {
case Some(info) =>
val timing = aggravation.timing
val duration = timing.duration
//setup effect
val id = data.projectile.id
//setup timer data
val (tick: Long, iterations: Int) = timing.ticks match {
case Some(n) if n < 1 =>
val rate = info.infliction_rate
(rate, (duration / rate).toInt)
case Some(ticks) =>
(duration / ticks, ticks)
case None =>
(1000L, (duration / 1000).toInt)
}
//quality per tick
val totalPower = (duration.toFloat / info.infliction_rate).toInt - 1
val averagePowerPerTick = totalPower.toFloat / iterations
val lastTickRemainder = totalPower - averagePowerPerTick * iterations
val qualityPerTick: List[Float] = if (lastTickRemainder > 0) {
0f +: List.fill[Float](iterations - 1)(averagePowerPerTick) :+ (lastTickRemainder + averagePowerPerTick)
}
else {
0f +: List.fill[Float](iterations)(averagePowerPerTick)
}
//pair id with entry
PairIdWithAggravationEntry(id, effect, tick, data, data.target, qualityPerTick)
//pair id with timer
aggravationToTimer += id -> context.system.scheduler.scheduleOnce(tick milliseconds, self, AggravatedBehavior.Aggravate(id, iterations))
ongoingAggravated = true
true
case _ =>
false
}
}
private def PairIdWithAggravationEntry(
id: Long,
effect: Aura,
retime: Long,
data: ResolvedProjectile,
target: SourceEntry,
powerOffset: List[Float]
): AggravatedBehavior.Entry = {
val aggravatedDamageInfo = ResolvedProjectile(
AggravatedDamage.burning(data.resolution),
data.projectile,
target,
data.damage_model,
data.hit_pos
)
val entry = AggravatedBehavior.Entry(id, effect, retime, aggravatedDamageInfo, powerOffset)
entryIdToEntry += id -> entry
entry
}
val aggravatedBehavior: Receive = {
case AggravatedBehavior.Aggravate(id, 0) =>
AggravationCleanup(id)
case AggravatedBehavior.Aggravate(id, iteration) =>
RetimeEventAndPerformAggravation(id, iteration, None)
}
private def RetimeEventAndPerformAggravation(id: Long, iteration: Int, time: Option[Long]) : Unit = {
RetimeAggravation(id, iteration - 1, time) match {
case Some(entry) =>
PerformAggravation(entry, iteration)
case _ => ;
}
}
private def RetimeAggravation(
id: Long,
iteration: Int,
time: Option[Long]
): Option[AggravatedBehavior.Entry] = {
CleanupAggravationTimer(id)
entryIdToEntry.get(id) match {
case out @ Some(oldEntry) =>
aggravationToTimer += id -> context.system.scheduler.scheduleOnce(
time.getOrElse(oldEntry.retime) milliseconds,
self,
AggravatedBehavior.Aggravate(id, iteration)
)
out
case _ =>
AggravationCleanup(id)
None
}
}
def RemoveAggravatedEntry(id: Long): Aura = {
entryIdToEntry.remove(id) match {
case Some(entry) =>
ongoingAggravated = entryIdToEntry.nonEmpty
entry.data.projectile.profile.Aggravated.get.effect_type
case _ =>
Aura.Nothing
}
}
def CleanupAggravationTimer(id: Long): Unit = {
//remove and cancel timer
aggravationToTimer.remove(id) match {
case Some(timer) => timer.cancel()
case _ => ;
}
}
def AggravationCleanup(id: Long): Unit = {
RemoveAggravatedEntry(id)
CleanupAggravationTimer(id)
}
def EndAllAggravation(): Unit = {
entryIdToEntry.clear()
aggravationToTimer.values.foreach { _.cancel() }
aggravationToTimer.clear()
}
def AggravatedReaction: Boolean = ongoingAggravated
private def PerformAggravation(entry: AggravatedBehavior.Entry, tick: Int = 0): Unit = {
val data = entry.data
val model = data.damage_model
val aggravatedProjectileData = ResolvedProjectile(
data.resolution,
data.projectile.quality(ProjectileQuality.Modified(entry.qualityPerTick(tick))),
data.target,
model,
data.hit_pos
)
takesDamage.apply(Vitality.Damage(model.Calculate(aggravatedProjectileData)))
}
}
object AggravatedBehavior {
type Target = Damageable.Target
private case class Entry(id: Long, effect: Aura, retime: Long, data: ResolvedProjectile, qualityPerTick: List[Float])
private case class Aggravate(id: Long, iterations: Int)
}

View file

@ -8,6 +8,7 @@ import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.hackable.Hackable import net.psforever.objects.serverobject.hackable.Hackable
import net.psforever.objects.vital.Vitality import net.psforever.objects.vital.Vitality
import net.psforever.objects.vital.resolution.ResolutionCalculations
/** /**
* The base "control" `Actor` mixin for damage-handling code. * The base "control" `Actor` mixin for damage-handling code.
@ -24,16 +25,35 @@ trait Damageable {
*/ */
def DamageableObject: Damageable.Target def DamageableObject: Damageable.Target
/** the official mixin hook; `orElse` onto the "control" `Actor` `receive` */ /** the official mixin hook;
final val takesDamage: Receive = TakesDamage * `orElse` onto the "control" `Actor` `receive`; or,
* cite the `originalTakesDamage` protocol during inheritance overrides */
val takesDamage: Receive = {
case Vitality.Damage(damage_func) =>
val obj = DamageableObject
if (obj.CanDamage) {
PerformDamage(obj, damage_func)
}
}
/** a duplicate of the core implementation for the default mixin hook, for use in overriding */
final val originalTakesDamage: Receive = {
case Vitality.Damage(damage_func) =>
val obj = DamageableObject
if (obj.CanDamage) {
PerformDamage(obj, damage_func)
}
}
/** /**
* Implementation of the mixin hook will be provided by a child class. * Assess the vital statistics of the target, apply the damage, and determine if any of those statistics changed.
* Override this method only when directly implementing. * By default, only take an interest in the change of "health".
* @see `takesDamage` * If implementing custom damage with no new message handling, override this method.
* @see `DamageableAmenity.PerformDamage` * @see `ResolutionCalculations.Output`
* @param target the entity to be damaged
* @param applyDamageTo the function that applies the damage to the target in a target-tailored fashion
*/ */
protected def TakesDamage: Receive protected def PerformDamage(target: Damageable.Target, applyDamageTo: ResolutionCalculations.Output): Unit
} }
object Damageable { object Damageable {
@ -59,7 +79,7 @@ object Damageable {
*/ */
def CanDamage(obj: Vitality with FactionAffinity, damage: Int, data: ResolvedProjectile): Boolean = { def CanDamage(obj: Vitality with FactionAffinity, damage: Int, data: ResolvedProjectile): Boolean = {
val definition = obj.Definition val definition = obj.Definition
damage > 0 && (damage > 0 || data.projectile.profile.Aggravated.nonEmpty) &&
definition.Damageable && definition.Damageable &&
(definition.DamageableByFriendlyFire || (definition.DamageableByFriendlyFire ||
(data.projectile.owner.Faction != obj.Faction || (data.projectile.owner.Faction != obj.Faction ||

View file

@ -1,10 +1,8 @@
//Copyright (c) 2020 PSForever //Copyright (c) 2020 PSForever
package net.psforever.objects.serverobject.damage package net.psforever.objects.serverobject.damage
import akka.actor.Actor.Receive
import net.psforever.objects.ballistics.ResolvedProjectile import net.psforever.objects.ballistics.ResolvedProjectile
import net.psforever.objects.equipment.JammableUnit import net.psforever.objects.equipment.JammableUnit
import net.psforever.objects.vital.Vitality
import net.psforever.objects.vital.resolution.ResolutionCalculations import net.psforever.objects.vital.resolution.ResolutionCalculations
import net.psforever.objects.zones.Zone import net.psforever.objects.zones.Zone
import net.psforever.types.PlanetSideGUID import net.psforever.types.PlanetSideGUID
@ -42,23 +40,6 @@ trait DamageableEntity extends Damageable {
DamageLog(s"${name.substring(slashPoint + 1, name.length - 1)}: $msg") DamageLog(s"${name.substring(slashPoint + 1, name.length - 1)}: $msg")
} }
/**
* Catch the expected damage message and apply checks to the target.
* If adding custom message handling in an future child implementation,
* override this method and call `super.TakesDamage.orElse { ... }`.
* @see `Damageable.TakesDamage`
* @see `ResolutionCalcultions.Output`
* @see `Vitality.CanDamage`
* @see `Vitality.Damage`
*/
protected def TakesDamage: Receive = {
case Vitality.Damage(damage_func) =>
val obj = DamageableObject
if (obj.CanDamage) {
PerformDamage(obj, damage_func)
}
}
/** /**
* Assess the vital statistics of the target, apply the damage, and determine if any of those statistics changed. * Assess the vital statistics of the target, apply the damage, and determine if any of those statistics changed.
* By default, only take an interest in the change of "health". * By default, only take an interest in the change of "health".
@ -108,7 +89,7 @@ trait DamageableEntity extends Damageable {
* @param cause historical information about the damage * @param cause historical information about the damage
* @param damage the amount of damage * @param damage the amount of damage
*/ */
protected def HandleDamage(target: Damageable.Target, cause: ResolvedProjectile, damage: Int): Unit = { protected def HandleDamage(target: Damageable.Target, cause: ResolvedProjectile, damage: Any): Unit = {
if (!target.Destroyed && target.Health <= target.Definition.DamageDestroysAt) { if (!target.Destroyed && target.Health <= target.Definition.DamageDestroysAt) {
DestructionAwareness(target, cause) DestructionAwareness(target, cause)
} else { } else {
@ -122,8 +103,12 @@ trait DamageableEntity extends Damageable {
* @param cause historical information about the damage * @param cause historical information about the damage
* @param amount the amount of damage * @param amount the amount of damage
*/ */
protected def DamageAwareness(target: Damageable.Target, cause: ResolvedProjectile, amount: Int): Unit = { protected def DamageAwareness(target: Damageable.Target, cause: ResolvedProjectile, amount: Any): Unit = {
DamageableEntity.DamageAwareness(target, cause, amount) amount match {
case value: Int =>
DamageableEntity.DamageAwareness(target, cause, value)
case _ => ;
}
} }
/** /**
@ -162,16 +147,22 @@ object DamageableEntity {
if (Damageable.CanJammer(target, cause)) { if (Damageable.CanJammer(target, cause)) {
target.Actor ! JammableUnit.Jammered(cause) target.Actor ! JammableUnit.Jammered(cause)
} }
if (amount > 0) { if (DamageToHealth(target, cause, amount)) {
target.Zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
}
}
def DamageToHealth(target: Damageable.Target, cause: ResolvedProjectile, amount: Int): Boolean = {
if (amount > 0 && !target.Destroyed) {
val zone = target.Zone val zone = target.Zone
if (!target.Destroyed) { zone.AvatarEvents ! AvatarServiceMessage(
val tguid = target.GUID zone.id,
zone.AvatarEvents ! AvatarServiceMessage( AvatarAction.PlanetsideAttributeToAll(target.GUID, 0, target.Health)
zone.id, )
AvatarAction.PlanetsideAttributeToAll(tguid, 0, target.Health) true
) }
} else {
zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos) false
} }
} }

View file

@ -26,8 +26,13 @@ object DamageableMountable {
* @see `Zone.LivePlayers` * @see `Zone.LivePlayers`
* @param target the entity being damaged * @param target the entity being damaged
* @param cause historical information about the damage * @param cause historical information about the damage
* @param countableDamage the amount of damage being done, translating to the intensity of the damage indicator
*/ */
def DamageAwareness(target: Damageable.Target with Mountable, cause: ResolvedProjectile): Unit = { def DamageAwareness(
target: Damageable.Target with Mountable,
cause: ResolvedProjectile,
countableDamage: Int
): Unit = {
val zone = target.Zone val zone = target.Zone
val events = zone.AvatarEvents val events = zone.AvatarEvents
val occupants = target.Seats.values.collect { val occupants = target.Seats.values.collect {
@ -38,9 +43,10 @@ object DamageableMountable {
case pSource: PlayerSource => //player damage case pSource: PlayerSource => //player damage
val name = pSource.Name val name = pSource.Name
(zone.LivePlayers.find(_.Name == name).orElse(zone.Corpses.find(_.Name == name)) match { (zone.LivePlayers.find(_.Name == name).orElse(zone.Corpses.find(_.Name == name)) match {
case Some(player) => AvatarAction.HitHint(player.GUID, player.GUID) case Some(player) =>
AvatarAction.HitHint(player.GUID, player.GUID)
case None => case None =>
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, pSource.Position)) AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(countableDamage, pSource.Position))
}) match { }) match {
case AvatarAction.HitHint(_, guid) => case AvatarAction.HitHint(_, guid) =>
occupants.map { tplayer => (tplayer.Name, AvatarAction.HitHint(guid, tplayer.GUID)) } occupants.map { tplayer => (tplayer.Name, AvatarAction.HitHint(guid, tplayer.GUID)) }
@ -48,7 +54,7 @@ object DamageableMountable {
occupants.map { tplayer => (tplayer.Name, msg) } occupants.map { tplayer => (tplayer.Name, msg) }
} }
case source => //object damage case source => //object damage
val msg = AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, source.Position)) val msg = AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(countableDamage, source.Position))
occupants.map { tplayer => (tplayer.Name, msg) } occupants.map { tplayer => (tplayer.Name, msg) }
}).foreach { }).foreach {
case (channel, msg) => case (channel, msg) =>

View file

@ -1,43 +1,55 @@
//Copyright (c) 2020 PSForever //Copyright (c) 2020 PSForever
package net.psforever.objects.serverobject.damage package net.psforever.objects.serverobject.damage
import akka.actor.Actor.Receive import akka.actor.Actor
import net.psforever.objects.{Vehicle, Vehicles} import net.psforever.objects.{Vehicle, Vehicles}
import net.psforever.objects.ballistics.ResolvedProjectile import net.psforever.objects.ballistics.ResolvedProjectile
import net.psforever.objects.equipment.JammableUnit
import net.psforever.objects.serverobject.damage.Damageable.Target import net.psforever.objects.serverobject.damage.Damageable.Target
import net.psforever.objects.vital.DamageType
import net.psforever.objects.vital.resolution.ResolutionCalculations import net.psforever.objects.vital.resolution.ResolutionCalculations
import net.psforever.services.Service import net.psforever.services.Service
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage} import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.DamageWithPositionMessage
import net.psforever.types.Vector3
import scala.concurrent.duration._ import scala.concurrent.duration._
/** /**
* The "control" `Actor` mixin for damage-handling code for `Vehicle` objects. * The "control" `Actor` mixin for damage-handling code for `Vehicle` objects.
*/ */
trait DamageableVehicle extends DamageableEntity { trait DamageableVehicle
extends DamageableEntity
with AggravatedBehavior {
_ : Actor =>
/** vehicles (may) have shields; they need to be handled */ def damageableVehiclePostStop(): Unit = {
private var handleDamageToShields: Boolean = false EndAllAggravation()
}
/** whether or not the vehicle has been damaged directly, report that damage has occurred */ /** whether or not the vehicle has been damaged directly, report that damage has occurred */
private var reportDamageToVehicle: Boolean = false private var reportDamageToVehicle: Boolean = false
def DamageableObject: Vehicle def DamageableObject: Vehicle
def AggravatedObject : Vehicle = DamageableObject
override protected def TakesDamage: Receive = override val takesDamage: Receive =
super.TakesDamage.orElse { originalTakesDamage
case DamageableVehicle.Damage(cause, damage) => .orElse(aggravatedBehavior)
//cargo vehicles inherit feedback from carrier .orElse {
reportDamageToVehicle = damage > 0 case DamageableVehicle.Damage(cause, damage) =>
DamageAwareness(DamageableObject, cause, amount = 0) //cargo vehicles inherit feedback from carrier
reportDamageToVehicle = damage > 0
DamageAwareness(DamageableObject, cause, amount = 0)
case DamageableVehicle.Destruction(cause) => case DamageableVehicle.Destruction(cause) =>
//cargo vehicles are destroyed when carrier is destroyed //cargo vehicles are destroyed when carrier is destroyed
val obj = DamageableObject val obj = DamageableObject
obj.Health = 0 obj.Health = 0
obj.History(cause) obj.History(cause)
DestructionAwareness(obj, cause) DestructionAwareness(obj, cause)
} }
/** /**
* Vehicles may have charged shields that absorb damage before the vehicle's own health is affected. * Vehicles may have charged shields that absorb damage before the vehicle's own health is affected.
@ -62,53 +74,13 @@ trait DamageableVehicle extends DamageableEntity {
target, target,
s"BEFORE=$originalHealth/$originalShields, AFTER=$health/$shields, CHANGE=$damageToHealth/$damageToShields" s"BEFORE=$originalHealth/$originalShields, AFTER=$health/$shields, CHANGE=$damageToHealth/$damageToShields"
) )
handleDamageToShields = damageToShields > 0 HandleDamage(target, cause, (damageToHealth, damageToShields))
HandleDamage(target, cause, damageToHealth + damageToShields)
} else { } else {
obj.Health = originalHealth obj.Health = originalHealth
obj.Shields = originalShields obj.Shields = originalShields
} }
} }
override protected def DamageAwareness(target: Target, cause: ResolvedProjectile, amount: Int): Unit = {
val obj = DamageableObject
val handleShields = handleDamageToShields
handleDamageToShields = false
val handleReport = reportDamageToVehicle || amount > 0
reportDamageToVehicle = false
if (Damageable.CanDamageOrJammer(target, amount, cause)) {
super.DamageAwareness(target, cause, amount)
}
if (handleReport) {
DamageableMountable.DamageAwareness(obj, cause)
}
DamageableVehicle.DamageAwareness(obj, cause, amount, handleShields)
}
override protected def DestructionAwareness(target: Target, cause: ResolvedProjectile): Unit = {
super.DestructionAwareness(target, cause)
val obj = DamageableObject
DamageableMountable.DestructionAwareness(obj, cause)
DamageableVehicle.DestructionAwareness(obj, cause)
DamageableWeaponTurret.DestructionAwareness(obj, cause)
}
}
object DamageableVehicle {
/**
* Message for instructing the target's cargo vehicles about a damage source affecting their carrier.
* @param cause historical information about damage
*/
private case class Damage(cause: ResolvedProjectile, amount: Int)
/**
* Message for instructing the target's cargo vehicles that their carrier is destroyed,
* and they should be destroyed too.
* @param cause historical information about damage
*/
private case class Destruction(cause: ResolvedProjectile)
/** /**
* Most all vehicles and the weapons mounted to them can jam * Most all vehicles and the weapons mounted to them can jam
* if the projectile that strikes (near) them has jammering properties. * if the projectile that strikes (near) them has jammering properties.
@ -121,25 +93,76 @@ object DamageableVehicle {
* @see `VehicleServiceMessage` * @see `VehicleServiceMessage`
* @param target the entity being destroyed * @param target the entity being destroyed
* @param cause historical information about the damage * @param cause historical information about the damage
* @param damage how much damage was performed * @param amount how much damage was performed
* @param damageToShields dispatch a shield strength update
*/ */
def DamageAwareness(target: Vehicle, cause: ResolvedProjectile, damage: Int, damageToShields: Boolean): Unit = { override protected def DamageAwareness(target: Target, cause: ResolvedProjectile, amount: Any): Unit = {
//alert cargo occupants to damage source val obj = DamageableObject
target.CargoHolds.values.foreach(hold => { val zone = target.Zone
hold.Occupant match { val events = zone.VehicleEvents
case Some(cargo) => val targetGUID = target.GUID
cargo.Actor ! DamageableVehicle.Damage(cause, damage + (if (damageToShields) 1 else 0)) val zoneId = zone.id
case None => ; val vehicleChannel = s"${obj.Actor}"
val (damageToHealth, damageToShields, totalDamage) = amount match {
case (a: Int, b: Int) => (a, b, a+b)
case _ => (0, 0, 0)
}
var announceConfrontation: Boolean = reportDamageToVehicle || totalDamage > 0
val aggravated = TryAggravationEffectActivate(cause) match {
case Some(_) =>
announceConfrontation = true
false
case _ =>
cause.projectile.profile.ProjectileDamageTypes.contains(DamageType.Aggravated)
}
reportDamageToVehicle = false
//log historical event
target.History(cause)
//damage
if (Damageable.CanDamageOrJammer(target, totalDamage, cause)) {
//jammering
if (Damageable.CanJammer(target, cause)) {
target.Actor ! JammableUnit.Jammered(cause)
} }
}) //stat changes
//shields if (damageToShields > 0) {
if (damageToShields) { events ! VehicleServiceMessage(
val zone = target.Zone vehicleChannel,
zone.VehicleEvents ! VehicleServiceMessage( VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, targetGUID, 68, obj.Shields)
s"${target.Actor}", )
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 68, target.Shields) announceConfrontation = true
) }
if (damageToHealth > 0) {
events ! VehicleServiceMessage(
zoneId,
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, targetGUID, 0, obj.Health)
)
announceConfrontation = true
}
}
if (announceConfrontation) {
if (aggravated) {
val msg = VehicleAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(totalDamage, Vector3.Zero))
obj.Seats.values
.collect { case seat if seat.Occupant.nonEmpty => seat.Occupant.get.Name }
.foreach { channel =>
events ! VehicleServiceMessage(channel, msg)
}
}
else {
//activity on map
zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
//alert to damage source
DamageableMountable.DamageAwareness(obj, cause, totalDamage)
}
//alert cargo occupants to damage source
obj.CargoHolds.values.foreach(hold => {
hold.Occupant match {
case Some(cargo) =>
cargo.Actor ! DamageableVehicle.Damage(cause, totalDamage)
case None => ;
}
})
} }
} }
@ -164,10 +187,15 @@ object DamageableVehicle {
* @param target the entity being destroyed * @param target the entity being destroyed
* @param cause historical information about the damage * @param cause historical information about the damage
*/ */
def DestructionAwareness(target: Vehicle, cause: ResolvedProjectile): Unit = { override protected def DestructionAwareness(target: Target, cause: ResolvedProjectile): Unit = {
super.DestructionAwareness(target, cause)
val obj = DamageableObject
DamageableMountable.DestructionAwareness(obj, cause)
val zone = target.Zone val zone = target.Zone
//aggravation cancel
EndAllAggravation()
//cargo vehicles die with us //cargo vehicles die with us
target.CargoHolds.values.foreach(hold => { obj.CargoHolds.values.foreach(hold => {
hold.Occupant match { hold.Occupant match {
case Some(cargo) => case Some(cargo) =>
cargo.Actor ! DamageableVehicle.Destruction(cause) cargo.Actor ! DamageableVehicle.Destruction(cause)
@ -175,10 +203,10 @@ object DamageableVehicle {
} }
}) })
//special considerations for certain vehicles //special considerations for certain vehicles
Vehicles.BeforeUnloadVehicle(target, zone) Vehicles.BeforeUnloadVehicle(obj, zone)
//shields //shields
if (target.Shields > 0) { if (obj.Shields > 0) {
target.Shields = 0 obj.Shields = 0
zone.VehicleEvents ! VehicleServiceMessage( zone.VehicleEvents ! VehicleServiceMessage(
zone.id, zone.id,
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 68, 0) VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 68, 0)
@ -186,5 +214,22 @@ object DamageableVehicle {
} }
target.Actor ! Vehicle.Deconstruct(Some(1 minute)) target.Actor ! Vehicle.Deconstruct(Some(1 minute))
target.ClearHistory() target.ClearHistory()
DamageableWeaponTurret.DestructionAwareness(obj, cause)
} }
} }
object DamageableVehicle {
/**
* Message for instructing the target's cargo vehicles about a damage source affecting their carrier.
* @param cause historical information about damage
*/
private case class Damage(cause: ResolvedProjectile, amount: Int)
/**
* Message for instructing the target's cargo vehicles that their carrier is destroyed,
* and they should be destroyed too.
* @param cause historical information about damage
*/
private case class Destruction(cause: ResolvedProjectile)
}

View file

@ -1,30 +1,97 @@
//Copyright (c) 2020 PSForever //Copyright (c) 2020 PSForever
package net.psforever.objects.serverobject.damage package net.psforever.objects.serverobject.damage
import akka.actor.Actor
import net.psforever.objects.ballistics.ResolvedProjectile import net.psforever.objects.ballistics.ResolvedProjectile
import net.psforever.objects.equipment.JammableUnit
import net.psforever.objects.serverobject.turret.{TurretUpgrade, WeaponTurret} import net.psforever.objects.serverobject.turret.{TurretUpgrade, WeaponTurret}
import net.psforever.objects.vehicles.MountedWeapons import net.psforever.objects.vehicles.MountedWeapons
import net.psforever.objects.vital.DamageType
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.DamageWithPositionMessage
import net.psforever.types.Vector3
import net.psforever.services.Service import net.psforever.services.Service
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.vehicle.support.TurretUpgrader import net.psforever.services.vehicle.support.TurretUpgrader
import net.psforever.services.vehicle.VehicleServiceMessage import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
/** /**
* The "control" `Actor` mixin for damage-handling code for `WeaponTurret` objects. * The "control" `Actor` mixin for damage-handling code for `WeaponTurret` objects.
*/ */
trait DamageableWeaponTurret extends DamageableEntity { trait DamageableWeaponTurret
def DamageableObject: Damageable.Target with WeaponTurret extends DamageableEntity
with AggravatedBehavior {
_: Actor =>
override protected def DamageAwareness(target: Damageable.Target, cause: ResolvedProjectile, amount: Int): Unit = { def damageableWeaponTurretPostStop(): Unit = {
super.DamageAwareness(target, cause, amount) EndAllAggravation()
if (amount > 0) { }
DamageableMountable.DamageAwareness(DamageableObject, cause)
def DamageableObject: Damageable.Target with WeaponTurret
def AggravatedObject: Damageable.Target with WeaponTurret = DamageableObject
override val takesDamage: Receive = originalTakesDamage.orElse(aggravatedBehavior)
override protected def DamageAwareness(target: Damageable.Target, cause: ResolvedProjectile, amount: Any): Unit = {
val obj = DamageableObject
val zone = target.Zone
val events = zone.VehicleEvents
val targetGUID = target.GUID
val zoneId = zone.id
val damageToHealth = amount match {
case a: Int => a
case _ => 0
}
var announceConfrontation: Boolean = damageToHealth > 0
val aggravated = TryAggravationEffectActivate(cause) match {
case Some(_) =>
announceConfrontation = true
false
case _ =>
cause.projectile.profile.ProjectileDamageTypes.contains(DamageType.Aggravated)
}
//log historical event
target.History(cause)
//damage
if (Damageable.CanDamageOrJammer(target, damageToHealth, cause)) {
//jammering
if (Damageable.CanJammer(target, cause)) {
target.Actor ! JammableUnit.Jammered(cause)
}
//stat changes
//TODO some turrets have shields
if (damageToHealth > 0) {
DamageableMountable.DamageAwareness(DamageableObject, cause, damageToHealth)
events ! VehicleServiceMessage(
zoneId,
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, targetGUID, 0, obj.Health)
)
announceConfrontation = true
}
}
if (announceConfrontation) {
if (aggravated) {
val msg = VehicleAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(damageToHealth, Vector3.Zero))
obj.Seats.values
.collect { case seat if seat.Occupant.nonEmpty => seat.Occupant.get.Name }
.foreach { channel =>
events ! VehicleServiceMessage(channel, msg)
}
}
else {
//activity on map
zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
//alert to damage source
DamageableMountable.DamageAwareness(obj, cause, damageToHealth)
}
} }
} }
override protected def DestructionAwareness(target: Damageable.Target, cause: ResolvedProjectile): Unit = { override protected def DestructionAwareness(target: Damageable.Target, cause: ResolvedProjectile): Unit = {
super.DestructionAwareness(target, cause) super.DestructionAwareness(target, cause)
val obj = DamageableObject val obj = DamageableObject
EndAllAggravation()
DamageableWeaponTurret.DestructionAwareness(obj, cause) DamageableWeaponTurret.DestructionAwareness(obj, cause)
DamageableMountable.DestructionAwareness(obj, cause) DamageableMountable.DestructionAwareness(obj, cause)
} }

View file

@ -88,9 +88,13 @@ class GeneratorControl(gen: Generator)
!imminentExplosion && super.WillAffectTarget(target, damage, cause) !imminentExplosion && super.WillAffectTarget(target, damage, cause)
} }
override protected def DamageAwareness(target: Target, cause: ResolvedProjectile, amount: Int): Unit = { override protected def DamageAwareness(target: Target, cause: ResolvedProjectile, amount: Any): Unit = {
super.DamageAwareness(target, cause, amount) super.DamageAwareness(target, cause, amount)
GeneratorControl.DamageAwareness(gen, cause, amount) val damageTo = amount match {
case a: Int => a
case _ => 0
}
GeneratorControl.DamageAwareness(gen, cause, damageTo)
} }
override protected def DestructionAwareness(target: Target, cause: ResolvedProjectile): Unit = { override protected def DestructionAwareness(target: Target, cause: ResolvedProjectile): Unit = {

View file

@ -72,9 +72,13 @@ class ImplantTerminalMechControl(mech: ImplantTerminalMech)
} }
} }
override protected def DamageAwareness(target: Target, cause: ResolvedProjectile, amount: Int): Unit = { override protected def DamageAwareness(target: Target, cause: ResolvedProjectile, amount: Any): Unit = {
super.DamageAwareness(target, cause, amount) super.DamageAwareness(target, cause, amount)
DamageableMountable.DamageAwareness(DamageableObject, cause) val damageTo = amount match {
case a: Int => a
case _ => 0
}
DamageableMountable.DamageAwareness(DamageableObject, cause, damageTo)
} }
override protected def DestructionAwareness(target: Damageable.Target, cause: ResolvedProjectile): Unit = { override protected def DestructionAwareness(target: Damageable.Target, cause: ResolvedProjectile): Unit = {

View file

@ -44,6 +44,11 @@ class FacilityTurretControl(turret: FacilityTurret)
// Used for timing ammo recharge for vanu turrets in caves // Used for timing ammo recharge for vanu turrets in caves
var weaponAmmoRechargeTimer = Default.Cancellable var weaponAmmoRechargeTimer = Default.Cancellable
override def postStop(): Unit = {
super.postStop()
damageableWeaponTurretPostStop()
}
def receive: Receive = def receive: Receive =
checkBehavior checkBehavior
.orElse(jammableBehavior) .orElse(jammableBehavior)

View file

@ -11,7 +11,7 @@ import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior} import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior}
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
import net.psforever.objects.serverobject.containable.{Containable, ContainableBehavior} import net.psforever.objects.serverobject.containable.{Containable, ContainableBehavior}
import net.psforever.objects.serverobject.damage.DamageableVehicle import net.psforever.objects.serverobject.damage.{AggravatedBehavior, DamageableVehicle}
import net.psforever.objects.serverobject.deploy.Deployment.DeploymentObject import net.psforever.objects.serverobject.deploy.Deployment.DeploymentObject
import net.psforever.objects.serverobject.deploy.{Deployment, DeploymentBehavior} import net.psforever.objects.serverobject.deploy.{Deployment, DeploymentBehavior}
import net.psforever.objects.serverobject.hackable.GenericHackables import net.psforever.objects.serverobject.hackable.GenericHackables
@ -51,11 +51,12 @@ class VehicleControl(vehicle: Vehicle)
with RepairableVehicle with RepairableVehicle
with JammableMountedWeapons with JammableMountedWeapons
with ContainableBehavior with ContainableBehavior
with AntTransferBehavior { with AntTransferBehavior
with AggravatedBehavior {
//make control actors belonging to utilities when making control actor belonging to vehicle //make control actors belonging to utilities when making control actor belonging to vehicle
vehicle.Utilities.foreach({ case (_, util) => util.Setup }) vehicle.Utilities.foreach({ case (_, util) => util.Setup })
def MountableObject = vehicle def MountableObject = vehicle
def CargoObject = vehicle def CargoObject = vehicle
@ -74,7 +75,7 @@ class VehicleControl(vehicle: Vehicle)
def ChargeTransferObject = vehicle def ChargeTransferObject = vehicle
if (vehicle.Definition == GlobalDefinitions.ant) { if(vehicle.Definition == GlobalDefinitions.ant) {
findChargeTargetFunc = Vehicles.FindANTChargingSource findChargeTargetFunc = Vehicles.FindANTChargingSource
findDischargeTargetFunc = Vehicles.FindANTDischargingTarget findDischargeTargetFunc = Vehicles.FindANTDischargingTarget
} }
@ -89,6 +90,7 @@ class VehicleControl(vehicle: Vehicle)
override def postStop(): Unit = { override def postStop(): Unit = {
super.postStop() super.postStop()
damageableVehiclePostStop()
decaying = false decaying = false
decayTimer.cancel() decayTimer.cancel()
vehicle.Utilities.values.foreach { util => vehicle.Utilities.values.foreach { util =>

View file

@ -1,8 +1,8 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.vital package net.psforever.objects.vital
import net.psforever.objects.ballistics.{ProjectileResolution, ResolvedProjectile}
import net.psforever.objects.vital.damage.DamageCalculations import net.psforever.objects.vital.damage.DamageCalculations
import net.psforever.objects.ballistics.ResolvedProjectile
import net.psforever.objects.vital.projectile.ProjectileCalculations import net.psforever.objects.vital.projectile.ProjectileCalculations
import net.psforever.objects.vital.resistance.ResistanceSelection import net.psforever.objects.vital.resistance.ResistanceSelection
import net.psforever.objects.vital.resolution.ResolutionCalculations import net.psforever.objects.vital.resolution.ResolutionCalculations
@ -72,7 +72,7 @@ trait DamageResistanceModel {
* @param resolution an explicit damage resolution overriding the one in the `ResolvedProjectile` object * @param resolution an explicit damage resolution overriding the one in the `ResolvedProjectile` object
* @return a function literal that encapsulates delayed modification instructions for certain objects * @return a function literal that encapsulates delayed modification instructions for certain objects
*/ */
def Calculate(data: ResolvedProjectile, resolution: ProjectileResolution.Value): ResolutionCalculations.Output = { def Calculate(data: ResolvedProjectile, resolution: DamageType.Value): ResolutionCalculations.Output = {
val res: ProjectileCalculations.Form = ResistUsing(resolution) val res: ProjectileCalculations.Form = ResistUsing(resolution)
Model(DamageUsing, res, data) Model(DamageUsing, res, data)
} }

View file

@ -9,5 +9,5 @@ package net.psforever.objects.vital
object DamageType extends Enumeration(1) { object DamageType extends Enumeration(1) {
type Type = Value type Type = Value
final val Direct, Splash, Radiation, Aggravated, Plasma, Comet, None = Value final val Direct, Splash, Lash, Radiation, Aggravated, Plasma, Comet, None = Value
} }

View file

@ -6,70 +6,70 @@ import net.psforever.objects.vital.projectile.ProjectileCalculations
import net.psforever.objects.vital.resistance.{ResistanceCalculations, ResistanceSelection} import net.psforever.objects.vital.resistance.{ResistanceCalculations, ResistanceSelection}
object NoResistance object NoResistance
extends ResistanceCalculations[SourceEntry]( extends ResistanceCalculations[SourceEntry](
ResistanceCalculations.ValidInfantryTarget, ResistanceCalculations.ValidInfantryTarget,
ResistanceCalculations.NoResistExtractor ResistanceCalculations.NoResistExtractor
) )
object InfantryHitResistance object InfantryHitResistance
extends ResistanceCalculations[PlayerSource]( extends ResistanceCalculations[PlayerSource](
ResistanceCalculations.ValidInfantryTarget, ResistanceCalculations.ValidInfantryTarget,
ResistanceCalculations.ExoSuitDirectExtractor ResistanceCalculations.ExoSuitDirectExtractor
) )
object InfantrySplashResistance object InfantrySplashResistance
extends ResistanceCalculations[PlayerSource]( extends ResistanceCalculations[PlayerSource](
ResistanceCalculations.ValidInfantryTarget, ResistanceCalculations.ValidInfantryTarget,
ResistanceCalculations.ExoSuitSplashExtractor ResistanceCalculations.ExoSuitSplashExtractor
) )
object InfantryLashResistance object InfantryLashResistance
extends ResistanceCalculations[PlayerSource]( extends ResistanceCalculations[PlayerSource](
ResistanceCalculations.ValidInfantryTarget, ResistanceCalculations.ValidInfantryTarget,
ResistanceCalculations.MaximumResistance ResistanceCalculations.MaximumResistance
) )
object InfantryAggravatedResistance object InfantryAggravatedResistance
extends ResistanceCalculations[PlayerSource]( extends ResistanceCalculations[PlayerSource](
ResistanceCalculations.ValidInfantryTarget, ResistanceCalculations.ValidInfantryTarget,
ResistanceCalculations.ExoSuitAggravatedExtractor ResistanceCalculations.ExoSuitAggravatedExtractor
) )
object VehicleHitResistance object VehicleHitResistance
extends ResistanceCalculations[VehicleSource]( extends ResistanceCalculations[VehicleSource](
ResistanceCalculations.ValidVehicleTarget, ResistanceCalculations.ValidVehicleTarget,
ResistanceCalculations.VehicleDirectExtractor ResistanceCalculations.VehicleDirectExtractor
) )
object VehicleSplashResistance object VehicleSplashResistance
extends ResistanceCalculations[VehicleSource]( extends ResistanceCalculations[VehicleSource](
ResistanceCalculations.ValidVehicleTarget, ResistanceCalculations.ValidVehicleTarget,
ResistanceCalculations.VehicleSplashExtractor ResistanceCalculations.VehicleSplashExtractor
) )
object VehicleLashResistance object VehicleLashResistance
extends ResistanceCalculations[VehicleSource]( extends ResistanceCalculations[VehicleSource](
ResistanceCalculations.ValidVehicleTarget, ResistanceCalculations.ValidVehicleTarget,
ResistanceCalculations.NoResistExtractor ResistanceCalculations.NoResistExtractor
) )
object VehicleAggravatedResistance object VehicleAggravatedResistance
extends ResistanceCalculations[VehicleSource]( extends ResistanceCalculations[VehicleSource](
ResistanceCalculations.ValidVehicleTarget, ResistanceCalculations.ValidVehicleTarget,
ResistanceCalculations.VehicleAggravatedExtractor ResistanceCalculations.VehicleAggravatedExtractor
) )
object AmenityHitResistance object AmenityHitResistance
extends ResistanceCalculations[ObjectSource]( extends ResistanceCalculations[ObjectSource](
ResistanceCalculations.ValidAmenityTarget, ResistanceCalculations.ValidAmenityTarget,
ResistanceCalculations.OtherDirectExtractor ResistanceCalculations.OtherDirectExtractor
) )
object AMenitySplashResistance object AmenitySplashResistance
extends ResistanceCalculations[ObjectSource]( extends ResistanceCalculations[ObjectSource](
ResistanceCalculations.ValidAmenityTarget, ResistanceCalculations.ValidAmenityTarget,
ResistanceCalculations.OtherSplashExtractor ResistanceCalculations.OtherSplashExtractor
) )
object NoResistanceSelection extends ResistanceSelection { object NoResistanceSelection extends ResistanceSelection {
def Direct: ProjectileCalculations.Form = None def Direct: ProjectileCalculations.Form = None

View file

@ -11,13 +11,13 @@ object NoResolutions
object InfantryResolutions object InfantryResolutions
extends DamageResistCalculations( extends DamageResistCalculations(
ResolutionCalculations.InfantryDamageAfterResist, ResolutionCalculations.InfantryDamage,
ResolutionCalculations.InfantryApplication ResolutionCalculations.InfantryApplication
) )
object MaxResolutions object MaxResolutions
extends DamageResistCalculations( extends DamageResistCalculations(
ResolutionCalculations.MaxDamageAfterResist, ResolutionCalculations.MaxDamage,
ResolutionCalculations.InfantryApplication ResolutionCalculations.InfantryApplication
) )

View file

@ -1,8 +1,9 @@
// Copyright (c) 2020 PSForever // Copyright (c) 2020 PSForever
package net.psforever.objects.vital.damage package net.psforever.objects.vital.damage
import net.psforever.objects.ballistics.{ProjectileResolution, ResolvedProjectile} import net.psforever.objects.ballistics._
import net.psforever.types.Vector3 import net.psforever.objects.vital.DamageType
import net.psforever.types.{ExoSuitType, Vector3}
/** /**
* Adjustments performed on the subsequent manipulations of the "base damage" value of an attack vector * Adjustments performed on the subsequent manipulations of the "base damage" value of an attack vector
@ -45,6 +46,21 @@ object DamageModifiers {
private def function(damage: Int, data: ResolvedProjectile): Int = damage private def function(damage: Int, data: ResolvedProjectile): Int = damage
} }
case object MaxDistanceCutoff 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) {
damage
} else {
0
}
}
}
/** /**
* The input value degrades (lessens) * The input value degrades (lessens)
* the further the distance between the point of origin (`shot_origin`) * the further the distance between the point of origin (`shot_origin`)
@ -81,14 +97,12 @@ object DamageModifiers {
def Calculate: DamageModifiers.Format = function def Calculate: DamageModifiers.Format = function
private def function(damage: Int, data: ResolvedProjectile): Int = { private def function(damage: Int, data: ResolvedProjectile): Int = {
val projectile = data.projectile val profile = data.projectile.profile
val profile = projectile.profile
val distance = Vector3.Distance(data.hit_pos, data.target.Position) val distance = Vector3.Distance(data.hit_pos, data.target.Position)
val radius = profile.DamageRadius val radius = profile.DamageRadius
if (distance <= radius) { if (distance <= radius) {
val base: Float = profile.DamageAtEdge val base: Float = profile.DamageAtEdge
val degrade: Float = (1 - base) * ((radius - distance) / radius) + base (damage * ((1 - base) * ((radius - distance) / radius) + base)).toInt
(damage * degrade).toInt
} else { } else {
0 0
} }
@ -117,4 +131,291 @@ object DamageModifiers {
} }
} }
} }
/*
Below this point are the calculations for sources of 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.
*/
/**
* The initial application of aggravated damage against an infantry target
* where the specific damage component is `Direct`.
*/
case object InfantryAggravatedDirect extends Mod {
def Calculate: DamageModifiers.Format =
BaseAggravatedFormula(ProjectileResolution.AggravatedDirect, DamageType.Direct)
}
/**
* The initial application of aggravated damage against an infantry target
* where the specific damage component is `Splash`.
*/
case object InfantryAggravatedSplash extends Mod {
def Calculate: DamageModifiers.Format =
BaseAggravatedFormula(ProjectileResolution.AggravatedSplash, DamageType.Splash)
}
/**
* The ongoing application of aggravated damage ticks against an infantry target
* where the specific damage component is `Direct`.
* This is called "burning" regardless of what the active aura effect actually is.
*/
case object InfantryAggravatedDirectBurn extends Mod {
def Calculate: DamageModifiers.Format =
BaseAggravatedBurnFormula(ProjectileResolution.AggravatedDirectBurn, DamageType.Direct)
}
/**
* The ongoing application of aggravated damage ticks against an infantry target
* where the specific damage component is `Splash`.
* This is called "burning" regardless of what the active aura effect actually is.
*/
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
}
}
/**
* For damage application that involves aggravation of a fireball (Dragon secondary fire mode),
* perform 1 damage.
* @see `ResolvedProjectile`
*/
case object FireballAggravatedBurn extends Mod {
def Calculate: DamageModifiers.Format = formula
private def formula(damage: Int, data: ResolvedProjectile): Int = {
if (damage > 0 &&
data.resolution == ProjectileResolution.AggravatedDirectBurn ||
data.resolution == ProjectileResolution.AggravatedSplashBurn) {
//add resist to offset resist subtraction later
1 + data.damage_model.ResistUsing(data)(data)
} else {
damage
}
}
}
/**
* The initial application of aggravated damage against an aircraft target.
* Primarily for use in the starfire weapon system.
* @see `AggravatedDamage`
* @see `ProjectileQuality.AggravatesTarget`
* @see `ResolvedProjectile`
*/
case object StarfireAggravated extends Mod {
def Calculate: DamageModifiers.Format = formula
private def formula(damage: Int, data: ResolvedProjectile): Int = {
if (data.resolution == ProjectileResolution.AggravatedDirect &&
data.projectile.quality == ProjectileQuality.AggravatesTarget) {
data.projectile.profile.Aggravated match {
case Some(aggravation) =>
aggravation.info.find(_.damage_type == DamageType.Direct) match {
case Some(infos) =>
(damage * infos.degradation_percentage + damage) toInt
case _ =>
damage
}
case _ =>
damage
}
} else {
damage
}
}
}
/**
* The ongoing application of aggravated damage ticks against an aircraft target.
* Primarily for use in the starfire weapon system.
* This is called "burning" regardless of what the active aura effect actually is.
* @see `AggravatedDamage`
* @see `ProjectileQuality`
* @see `ResolvedProjectile`
*/
case object StarfireAggravatedBurn extends Mod {
def Calculate: DamageModifiers.Format = formula
private def formula(damage: Int, data: ResolvedProjectile): Int = {
if (data.resolution == ProjectileResolution.AggravatedDirectBurn) {
data.projectile.profile.Aggravated match {
case Some(aggravation) =>
aggravation.info.find(_.damage_type == DamageType.Direct) match {
case Some(infos) =>
(math.floor(damage * infos.degradation_percentage) * data.projectile.quality.mod) toInt
case _ =>
damage
}
case _ =>
0
}
} else {
damage
}
}
}
/**
* The initial application of aggravated damage against a target.
* Primarily for use in the comet weapon system.
* @see `AggravatedDamage`
* @see `ProjectileQuality.AggravatesTarget`
* @see `ResolvedProjectile`
*/
case object CometAggravated extends Mod {
def Calculate: DamageModifiers.Format = formula
private def formula(damage: Int, data: ResolvedProjectile): Int = {
if (data.resolution == ProjectileResolution.AggravatedDirect &&
data.projectile.quality == ProjectileQuality.AggravatesTarget) {
data.projectile.profile.Aggravated match {
case Some(aggravation) =>
aggravation.info.find(_.damage_type == DamageType.Direct) match {
case Some(infos) =>
damage - (damage * infos.degradation_percentage) toInt
case _ =>
damage
}
case _ =>
damage
}
} else {
damage
}
}
}
/**
* The ongoing application of aggravated damage ticks against a target.
* Primarily for use in the comet weapon system.
* This is called "burning" regardless of what the active aura effect actually is.
* @see `AggravatedDamage`
* @see `ProjectileQuality`
* @see `ResolvedProjectile`
*/
case object CometAggravatedBurn extends Mod {
def Calculate: DamageModifiers.Format = formula
private def formula(damage: Int, data: ResolvedProjectile): Int = {
if (data.resolution == ProjectileResolution.AggravatedDirectBurn) {
data.projectile.profile.Aggravated match {
case Some(aggravation) =>
aggravation.info.find(_.damage_type == DamageType.Direct) match {
case Some(infos) =>
damage - (damage * infos.degradation_percentage) toInt
case _ =>
damage
}
case _ =>
0
}
} else {
damage
}
}
}
} }

View file

@ -2,7 +2,7 @@
package net.psforever.objects.vital.resistance package net.psforever.objects.vital.resistance
import net.psforever.objects.ballistics._ import net.psforever.objects.ballistics._
import net.psforever.objects.vital.NoResistance import net.psforever.objects.vital.{DamageType, NoResistance}
import net.psforever.objects.vital.projectile.ProjectileCalculations import net.psforever.objects.vital.projectile.ProjectileCalculations
/** /**
@ -17,19 +17,19 @@ trait ResistanceSelection {
def Lash: ProjectileCalculations.Form def Lash: ProjectileCalculations.Form
def Aggravated: ProjectileCalculations.Form def Aggravated: ProjectileCalculations.Form
def apply(data: ResolvedProjectile): ProjectileCalculations.Form = def apply(data : ResolvedProjectile) : ProjectileCalculations.Form = data.projectile.profile.ProjectileDamageType match {
data.resolution match { case DamageType.Direct => Direct
case ProjectileResolution.Hit => Direct case DamageType.Splash => Splash
case ProjectileResolution.Splash => Splash case DamageType.Lash => Lash
case ProjectileResolution.Lash => Lash case DamageType.Aggravated => Aggravated
case _ => None case _ => None
} }
def apply(res: ProjectileResolution.Value): ProjectileCalculations.Form = def apply(res : DamageType.Value) : ProjectileCalculations.Form = res match {
res match { case DamageType.Direct => Direct
case ProjectileResolution.Hit => Direct case DamageType.Splash => Splash
case ProjectileResolution.Splash => Splash case DamageType.Lash => Lash
case ProjectileResolution.Lash => Lash case DamageType.Aggravated => Aggravated
case _ => None case _ => None
} }
} }

View file

@ -38,15 +38,31 @@ object ResolutionCalculations {
def NoDamage(data: ResolvedProjectile)(a: Int, b: Int): Int = 0 def NoDamage(data: ResolvedProjectile)(a: Int, b: Int): Int = 0
def InfantryDamageAfterResist(data: ResolvedProjectile): (Int, Int) => (Int, Int) = { def InfantryDamage(data: ResolvedProjectile): (Int, Int) => (Int, Int) = {
data.target match { data.target match {
case target: PlayerSource => case target: PlayerSource =>
InfantryDamageAfterResist(target.health, target.armor) if(data.projectile.profile.DamageToHealthOnly) {
DamageToHealthOnly(target.health)
} else {
InfantryDamageAfterResist(target.health, target.armor)
}
case _ => case _ =>
InfantryDamageAfterResist(0, 0) InfantryDamageAfterResist(0, 0)
} }
} }
def DamageToHealthOnly(currentHP: Int)(damages: Int, resistance: Int): (Int, Int) = {
if (damages > 0 && currentHP > 0) {
if(damages > resistance) {
(damages - resistance, 0)
} else {
(damages, 0)
}
} else {
(0, 0)
}
}
def InfantryDamageAfterResist(currentHP: Int, currentArmor: Int)(damages: Int, resistance: Int): (Int, Int) = { def InfantryDamageAfterResist(currentHP: Int, currentArmor: Int)(damages: Int, resistance: Int): (Int, Int) = {
if (damages > 0 && currentHP > 0) { if (damages > 0 && currentHP > 0) {
if (currentArmor <= 0) { if (currentArmor <= 0) {
@ -67,10 +83,14 @@ object ResolutionCalculations {
} }
} }
def MaxDamageAfterResist(data: ResolvedProjectile): (Int, Int) => (Int, Int) = { def MaxDamage(data: ResolvedProjectile): (Int, Int) => (Int, Int) = {
data.target match { data.target match {
case target: PlayerSource => case target: PlayerSource =>
MaxDamageAfterResist(target.health, target.armor) if(data.projectile.profile.DamageToHealthOnly) {
DamageToHealthOnly(target.health)
} else {
MaxDamageAfterResist(target.health, target.armor)
}
case _ => case _ =>
MaxDamageAfterResist(0, 0) MaxDamageAfterResist(0, 0)
} }

View file

@ -451,7 +451,7 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
* `false`, if the new pool can not be created because the system has already been started * `false`, if the new pool can not be created because the system has already been started
*/ */
def AddPool(name: String, pool: Seq[Int]): Boolean = { def AddPool(name: String, pool: Seq[Int]): Boolean = {
if (accessor == Default.Actor) { if (accessor == Default.Actor || accessor == null) {
guid.AddPool(name, pool.toList) guid.AddPool(name, pool.toList)
true true
} else { } else {

View file

@ -255,7 +255,7 @@ object GamePacketOpcode extends Enumeration {
// 0x68 // 0x68
case 0x68 => game.DroppodFreefallingMessage.decode case 0x68 => game.DroppodFreefallingMessage.decode
case 0x69 => game.AvatarFirstTimeEventMessage.decode case 0x69 => game.AvatarFirstTimeEventMessage.decode
case 0x6a => noDecoder(AggravatedDamageMessage) case 0x6a => game.AggravatedDamageMessage.decode
case 0x6b => game.TriggerSoundMessage.decode case 0x6b => game.TriggerSoundMessage.decode
case 0x6c => game.LootItemMessage.decode case 0x6c => game.LootItemMessage.decode
case 0x6d => game.VehicleSubStateMessage.decode case 0x6d => game.VehicleSubStateMessage.decode

View file

@ -0,0 +1,32 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
import net.psforever.types.PlanetSideGUID
import scodec.Codec
import scodec.codecs._
/**
* Dispatched from the server to cause a damage reaction from a specific target.
* Infantry targets should be the primary target of this packet, as indicated by their identifier.
* Infantry targets display their flinch animation.
* All targets yelp in agony.
* Infantry targets use their assigned voice.
* Non-infantry targets use the "grizzled"(?) voice.
* @param guid the target entity's global unique identifier
* @param damage the amount of damsge being simulated
*/
final case class AggravatedDamageMessage(guid : PlanetSideGUID,
damage : Long)
extends PlanetSideGamePacket {
type Packet = AggravatedDamageMessage
def opcode = GamePacketOpcode.AggravatedDamageMessage
def encode = AggravatedDamageMessage.encode(this)
}
object AggravatedDamageMessage extends Marshallable[AggravatedDamageMessage] {
implicit val codec : Codec[AggravatedDamageMessage] = (
("guid" | PlanetSideGUID.codec) ::
("damage" | uint32L)
).as[AggravatedDamageMessage]
}

View file

@ -19,7 +19,7 @@ import shapeless.{::, HNil}
* @param unk3b if no global unique identifier (above), the name of the entity absorbing the damage * @param unk3b if no global unique identifier (above), the name of the entity absorbing the damage
* @param unk3c if no global unique identifier (above), the object type of the entity absorbing the damage * @param unk3c if no global unique identifier (above), the object type of the entity absorbing the damage
* @param unk3d na * @param unk3d na
* @param unk4 na * @param unk4 an indicator for the target-specific vital statistic being affected
* @param unk5 the amount of damage * @param unk5 the amount of damage
* @param unk6 na * @param unk6 na
*/ */
@ -66,6 +66,13 @@ final case class DamageFeedbackMessage(
} }
object DamageFeedbackMessage extends Marshallable[DamageFeedbackMessage] { object DamageFeedbackMessage extends Marshallable[DamageFeedbackMessage] {
def apply(unk1: Int,
unk2: PlanetSideGUID,
unk3: PlanetSideGUID,
unk4: Int,
unk5: Long): DamageFeedbackMessage =
DamageFeedbackMessage(unk1, true, Some(unk2), None, None, true, Some(unk3), None, None, None, unk4, unk5, 0)
implicit val codec: Codec[DamageFeedbackMessage] = ( implicit val codec: Codec[DamageFeedbackMessage] = (
("unk1" | uint4) :: ("unk1" | uint4) ::
(bool >>:~ { u2 => (bool >>:~ { u2 =>

View file

@ -5,10 +5,13 @@ import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacke
import net.psforever.types.Vector3 import net.psforever.types.Vector3
import scodec.Codec import scodec.Codec
import scodec.codecs._ import scodec.codecs._
import shapeless.{::, HNil}
/** /**
* Dispatched by the server to indicate a source of damage affecting the player. * Dispatched by the server to indicate a source of damage affecting the player.
* Unlike `HitHint` the damage source is defined by an actual coordinate location rather than a physical target.<br> * Unlike `HitHint` the damage source is defined by an actual coordinate location rather than a physical target.
* Setting the position to the world origin, however,
* can cause the damage tick mark to point towards the previous damaging entity in some situations.<br>
* <br> * <br>
* The player will be shown a fading, outwards drifting, red tick mark. * The player will be shown a fading, outwards drifting, red tick mark.
* The location will indicate a general direction towards the source. * The location will indicate a general direction towards the source.
@ -27,5 +30,14 @@ object DamageWithPositionMessage extends Marshallable[DamageWithPositionMessage]
implicit val codec: Codec[DamageWithPositionMessage] = ( implicit val codec: Codec[DamageWithPositionMessage] = (
("unk" | uint8L) :: ("unk" | uint8L) ::
("pos" | Vector3.codec_pos) ("pos" | Vector3.codec_pos)
).as[DamageWithPositionMessage] ).xmap[DamageWithPositionMessage] (
{
case unk :: pos :: HNil =>
DamageWithPositionMessage(math.max(0, math.min(unk, 255)), pos)
},
{
case DamageWithPositionMessage(unk, pos) =>
unk :: pos :: HNil
}
)
} }

View file

@ -0,0 +1,29 @@
// Copyright (c) 2020 PSForever
package game
import net.psforever.packet._
import net.psforever.packet.game._
import net.psforever.types.PlanetSideGUID
import org.specs2.mutable._
import scodec.bits._
class AggravatedDamageMessageTest extends Specification {
val string = hex"6a350a0e000000"
"decode" in {
PacketCoding.DecodePacket(string).require match {
case AggravatedDamageMessage(guid,unk) =>
guid mustEqual PlanetSideGUID(2613)
unk mustEqual 14
case _ =>
ko
}
}
"encode" in {
val msg = AggravatedDamageMessage(PlanetSideGUID(2613), 14)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string
}
}

View file

@ -0,0 +1,285 @@
// Copyright (c) 2020 PSForever
package objects
import akka.actor.{Actor, ActorRef, Props}
import akka.testkit.TestProbe
import base.ActorTest
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.aura.AuraEffectBehavior.Target
import net.psforever.objects.serverobject.aura.{Aura, AuraContainer, AuraEffectBehavior}
import net.psforever.types.PlanetSideEmpire
import org.specs2.mutable.Specification
import scala.concurrent.duration._
class AuraContainerTest extends Specification {
"AuraContainer" should {
"have no default effects" in {
new AuraTest.Entity().Aura.isEmpty mustEqual true
}
"add effects" in {
val obj = new AuraTest.Entity()
obj.Aura.size mustEqual 0
obj.Aura.contains(Aura.Plasma) mustEqual false
obj.AddEffectToAura(Aura.Plasma)
obj.Aura.size mustEqual 1
obj.Aura.contains(Aura.Plasma) mustEqual true
}
"do nothing if adding repeated effects" in {
val obj = new AuraTest.Entity()
obj.Aura.size mustEqual 0
obj.AddEffectToAura(Aura.Plasma)
obj.Aura.size mustEqual 1
obj.AddEffectToAura(Aura.Plasma)
obj.Aura.size mustEqual 1
}
"remove effects" in {
val obj = new AuraTest.Entity()
obj.Aura.size mustEqual 0
obj.Aura.contains(Aura.Plasma) mustEqual false
obj.AddEffectToAura(Aura.Plasma)
obj.Aura.size mustEqual 1
obj.Aura.contains(Aura.Plasma) mustEqual true
obj.RemoveEffectFromAura(Aura.Plasma)
obj.Aura.size mustEqual 0
obj.Aura.contains(Aura.Plasma) mustEqual false
}
"do nothing if no effects" in {
val obj = new AuraTest.Entity()
obj.Aura.size mustEqual 0
obj.Aura.contains(Aura.Plasma) mustEqual false
obj.RemoveEffectFromAura(Aura.Plasma)
obj.Aura.size mustEqual 0
obj.Aura.contains(Aura.Plasma) mustEqual false
}
"do nothing if trying to remove wrong effect" in {
val obj = new AuraTest.Entity()
obj.Aura.size mustEqual 0
obj.Aura.contains(Aura.Plasma) mustEqual false
obj.Aura.contains(Aura.Fire) mustEqual false
obj.AddEffectToAura(Aura.Plasma)
obj.Aura.size mustEqual 1
obj.Aura.contains(Aura.Plasma) mustEqual true
obj.Aura.contains(Aura.Fire) mustEqual false
obj.RemoveEffectFromAura(Aura.Fire)
obj.Aura.size mustEqual 1
obj.Aura.contains(Aura.Plasma) mustEqual true
obj.Aura.contains(Aura.Fire) mustEqual false
}
}
}
class AuraEffectBehaviorInitTest extends ActorTest {
val obj = new AuraTest.Entity()
"AuraEffectBehavior" should {
"init" in {
obj.Actor = system.actorOf(Props(classOf[AuraTest.Agency], obj, ActorRef.noSender), "aura-test-actor")
expectNoMessage(500 milliseconds)
}
}
}
class AuraEffectBehaviorStartEffectTest extends ActorTest {
val obj = new AuraTest.Entity()
val updateProbe = new TestProbe(system)
obj.Actor = system.actorOf(Props(classOf[AuraTest.Agency], obj, updateProbe.ref), "aura-test-actor")
"AuraEffectBehavior" should {
"start effect (ends naturally)" in {
assert(obj.Aura.isEmpty)
obj.Actor ! AuraEffectBehavior.StartEffect(Aura.Plasma, 2500)
val msg1 = updateProbe.receiveOne(100 milliseconds)
assert(
msg1 match {
case AuraTest.DoUpdateAuraEffect() => true
case _ => false
}
)
assert(obj.Aura.contains(Aura.Plasma))
expectNoMessage(2000 milliseconds)
assert(obj.Aura.contains(Aura.Plasma))
val msg2 = updateProbe.receiveOne(750 milliseconds)
assert(
msg2 match {
case AuraTest.DoUpdateAuraEffect() => true
case _ => false
}
)
assert(obj.Aura.isEmpty)
}
}
}
class AuraEffectBehaviorStartLongerEffectTest extends ActorTest {
val obj = new AuraTest.Entity()
val updateProbe = new TestProbe(system)
obj.Actor = system.actorOf(Props(classOf[AuraTest.Agency], obj, updateProbe.ref), "aura-test-actor")
"AuraEffectBehavior" should {
"replace a shorter effect with a longer one" in {
assert(obj.Aura.isEmpty)
obj.Actor ! AuraEffectBehavior.StartEffect(Aura.Plasma, 500)
val msg1 = updateProbe.receiveOne(100 milliseconds)
assert(
msg1 match {
case AuraTest.DoUpdateAuraEffect() => true
case _ => false
}
)
assert(obj.Aura.contains(Aura.Plasma))
obj.Actor ! AuraEffectBehavior.StartEffect(Aura.Plasma, 2500)
updateProbe.expectNoMessage(2000 milliseconds)
//first effect has not ended naturally (yet)
assert(obj.Aura.contains(Aura.Plasma))
}
}
}
class AuraEffectBehaviorNoRedundantStartEffectTest extends ActorTest {
val obj = new AuraTest.Entity()
val updateProbe = new TestProbe(system)
obj.Actor = system.actorOf(Props(classOf[AuraTest.Agency], obj, updateProbe.ref), "aura-test-actor")
"AuraEffectBehavior" should {
"not start an effect if already active" in {
assert(obj.Aura.isEmpty)
obj.Actor ! AuraEffectBehavior.StartEffect(Aura.Plasma, 2500)
val msg1 = updateProbe.receiveOne(100 milliseconds)
assert(
msg1 match {
case AuraTest.DoUpdateAuraEffect() => true
case _ => false
}
)
assert(obj.Aura.contains(Aura.Plasma))
expectNoMessage(1000 milliseconds) //wait for half of the effect's duration
assert(obj.Aura.contains(Aura.Plasma))
obj.Actor ! AuraEffectBehavior.StartEffect(Aura.Plasma, 2500)
updateProbe.expectNoMessage(1500 milliseconds)
}
}
}
class AuraEffectBehaviorNoOverrideStartEffectTest extends ActorTest {
val obj = new AuraTest.Entity()
val updateProbe = new TestProbe(system)
obj.Actor = system.actorOf(Props(classOf[AuraTest.Agency], obj, updateProbe.ref), "aura-test-actor")
"AuraEffectBehavior" should {
"not replace a long-running effect with a short-running effect" in {
assert(obj.Aura.isEmpty)
obj.Actor ! AuraEffectBehavior.StartEffect(Aura.Plasma, 2500)
val msg1 = updateProbe.receiveOne(100 milliseconds)
assert(
msg1 match {
case AuraTest.DoUpdateAuraEffect() => true
case _ => false
}
)
assert(obj.Aura.contains(Aura.Plasma))
obj.Actor ! AuraEffectBehavior.StartEffect(Aura.Plasma, 500)
updateProbe.expectNoMessage(1500 milliseconds)
//effect has not ended naturally
assert(obj.Aura.contains(Aura.Plasma))
}
}
}
class AuraEffectBehaviorNoStartUnsupportedEffectTest extends ActorTest {
val obj = new AuraTest.Entity()
val updateProbe = new TestProbe(system)
obj.Actor = system.actorOf(Props(classOf[AuraTest.Agency], obj, updateProbe.ref), "aura-test-actor") //supports Plasma only
"AuraEffectBehavior" should {
"not start an effect that is not approved" in {
assert(obj.Aura.isEmpty)
obj.Actor ! AuraEffectBehavior.StartEffect(Aura.Fire, 2500)
assert(obj.Aura.isEmpty)
updateProbe.expectNoMessage(2000 milliseconds)
}
}
}
class AuraEffectBehaviorEndEarlyTest extends ActorTest {
val obj = new AuraTest.Entity()
val updateProbe = new TestProbe(system)
obj.Actor = system.actorOf(Props(classOf[AuraTest.Agency], obj, updateProbe.ref), "aura-test-actor")
"AuraEffectBehavior" should {
"start effect (ends early)" in {
assert(obj.Aura.isEmpty)
obj.Actor ! AuraEffectBehavior.StartEffect(Aura.Plasma, 2500)
val msg1 = updateProbe.receiveOne(100 milliseconds)
assert(
msg1 match {
case AuraTest.DoUpdateAuraEffect() => true
case _ => false
}
)
assert(obj.Aura.contains(Aura.Plasma))
obj.Actor ! AuraEffectBehavior.EndEffect(Aura.Plasma)
val msg2 = updateProbe.receiveOne(100 milliseconds)
assert(
msg2 match {
case AuraTest.DoUpdateAuraEffect() => true
case _ => false
}
)
assert(obj.Aura.isEmpty)
}
}
}
class AuraEffectBehaviorEndNothingTest extends ActorTest {
val obj = new AuraTest.Entity()
val updateProbe = new TestProbe(system)
obj.Actor = system.actorOf(Props(classOf[AuraTest.Agency], obj, updateProbe.ref), "aura-test-actor")
"AuraEffectBehavior" should {
"can not end an effect that is not supported (hence, not started)" in {
assert(obj.Aura.isEmpty)
obj.Actor ! AuraEffectBehavior.StartEffect(Aura.Plasma, 2500)
val msg1 = updateProbe.receiveOne(100 milliseconds)
assert(
msg1 match {
case AuraTest.DoUpdateAuraEffect() => true
case _ => false
}
)
assert(obj.Aura.size == 1)
obj.Actor ! AuraEffectBehavior.EndEffect(Aura.Fire)
updateProbe.expectNoMessage(1000 milliseconds)
assert(obj.Aura.size == 1)
}
}
}
object AuraTest {
class Agency(obj: AuraEffectBehavior.Target, updateRef: ActorRef) extends Actor with AuraEffectBehavior {
def AuraTargetObject : Target = obj
ApplicableEffect(Aura.Plasma)
def receive: Receive = auraBehavior.orElse {
case _ => ;
}
def UpdateAuraEffect(target : Target) : Unit = {
updateRef ! DoUpdateAuraEffect()
}
}
class Entity extends PlanetSideServerObject with AuraContainer {
def Faction = PlanetSideEmpire.NEUTRAL
def Definition = null
}
final case class DoUpdateAuraEffect()
}

View file

@ -28,7 +28,7 @@ class DamageCalculationsTests extends Specification {
val target = Vehicle(GlobalDefinitions.fury) val target = Vehicle(GlobalDefinitions.fury)
target.Position = Vector3(10, 0, 0) target.Position = Vector3(10, 0, 0)
val resprojectile = ResolvedProjectile( val resprojectile = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
projectile, projectile,
SourceEntry(target), SourceEntry(target),
target.DamageModel, target.DamageModel,
@ -64,14 +64,7 @@ class DamageCalculationsTests extends Specification {
} }
"degrade over distance damage modifier (no degrade)" in { "degrade over distance damage modifier (no degrade)" in {
val resprojectile2 = ResolvedProjectile( DamageModifiers.DistanceDegrade.Calculate(100, resprojectile) == 100 mustEqual true
ProjectileResolution.Splash,
projectile,
SourceEntry(target),
target.DamageModel,
Vector3(10, 0, 0)
)
DamageModifiers.DistanceDegrade.Calculate(100, resprojectile2) == 100 mustEqual true
} }
"degrade over distance damage modifier (some degrade)" in { "degrade over distance damage modifier (some degrade)" in {
@ -109,7 +102,14 @@ class DamageCalculationsTests extends Specification {
} }
"degrade at radial distance damage modifier (some degrade)" in { "degrade at radial distance damage modifier (some degrade)" in {
val damage = DamageModifiers.RadialDegrade.Calculate(100, resprojectile) val resprojectile2 = ResolvedProjectile(
ProjectileResolution.Splash,
projectile,
SourceEntry(target),
target.DamageModel,
Vector3(12, 0, 0)
)
val damage = DamageModifiers.RadialDegrade.Calculate(100, resprojectile2)
damage < 100 && damage > 0 mustEqual true damage < 100 && damage > 0 mustEqual true
} }
@ -119,7 +119,7 @@ class DamageCalculationsTests extends Specification {
projectile, projectile,
SourceEntry(target), SourceEntry(target),
target.DamageModel, target.DamageModel,
Vector3(1000, 0, 0) Vector3(100, 0, 0)
) )
DamageModifiers.RadialDegrade.Calculate(100, resprojectile2) == 0 mustEqual true DamageModifiers.RadialDegrade.Calculate(100, resprojectile2) == 0 mustEqual true
} }
@ -180,7 +180,7 @@ class ResistanceCalculationsTests extends Specification {
"ignore all targets" in { "ignore all targets" in {
val target = Vehicle(GlobalDefinitions.fury) val target = Vehicle(GlobalDefinitions.fury)
val resprojectile = ResolvedProjectile( val resprojectile = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
projectile, projectile,
SourceEntry(target), SourceEntry(target),
target.DamageModel, target.DamageModel,
@ -192,7 +192,7 @@ class ResistanceCalculationsTests extends Specification {
"discern standard infantry targets" in { "discern standard infantry targets" in {
val target = player val target = player
val resprojectile = ResolvedProjectile( val resprojectile = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
projectile, projectile,
SourceEntry(target), SourceEntry(target),
target.DamageModel, target.DamageModel,
@ -208,7 +208,7 @@ class ResistanceCalculationsTests extends Specification {
val target = Player(Avatar(0, "TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) val target = Player(Avatar(0, "TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
target.ExoSuit = ExoSuitType.MAX target.ExoSuit = ExoSuitType.MAX
val resprojectile = ResolvedProjectile( val resprojectile = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
projectile, projectile,
SourceEntry(target), SourceEntry(target),
target.DamageModel, target.DamageModel,
@ -223,7 +223,7 @@ class ResistanceCalculationsTests extends Specification {
"discern ground vehicle targets" in { "discern ground vehicle targets" in {
val target = Vehicle(GlobalDefinitions.fury) val target = Vehicle(GlobalDefinitions.fury)
val resprojectile = ResolvedProjectile( val resprojectile = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
projectile, projectile,
SourceEntry(target), SourceEntry(target),
target.DamageModel, target.DamageModel,
@ -238,7 +238,7 @@ class ResistanceCalculationsTests extends Specification {
"discern flying vehicle targets" in { "discern flying vehicle targets" in {
val target = Vehicle(GlobalDefinitions.mosquito) val target = Vehicle(GlobalDefinitions.mosquito)
val resprojectile = ResolvedProjectile( val resprojectile = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
projectile, projectile,
SourceEntry(target), SourceEntry(target),
target.DamageModel, target.DamageModel,
@ -284,7 +284,7 @@ class ResolutionCalculationsTests extends Specification {
"calculate no damage" in { "calculate no damage" in {
val target = player val target = player
val resprojectile = ResolvedProjectile( val resprojectile = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
projectile, projectile,
SourceEntry(target), SourceEntry(target),
target.DamageModel, target.DamageModel,
@ -296,23 +296,23 @@ class ResolutionCalculationsTests extends Specification {
"calculate no infantry damage for vehicles" in { "calculate no infantry damage for vehicles" in {
val target1 = Vehicle(GlobalDefinitions.fury) //! val target1 = Vehicle(GlobalDefinitions.fury) //!
val resprojectile1 = ResolvedProjectile( val resprojectile1 = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
projectile, projectile,
SourceEntry(target1), SourceEntry(target1),
target1.DamageModel, target1.DamageModel,
Vector3.Zero Vector3.Zero
) )
InfantryDamageAfterResist(resprojectile1)(50, 10) mustEqual (0, 0) InfantryDamage(resprojectile1)(50, 10) mustEqual (0, 0)
val target2 = player val target2 = player
val resprojectile2 = ResolvedProjectile( val resprojectile2 = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
projectile, projectile,
SourceEntry(target2), SourceEntry(target2),
target2.DamageModel, target2.DamageModel,
Vector3.Zero Vector3.Zero
) )
InfantryDamageAfterResist(resprojectile2)(50, 10) mustEqual (40, 10) InfantryDamage(resprojectile2)(50, 10) mustEqual (40, 10)
} }
"calculate health and armor damage for infantry target" in { "calculate health and armor damage for infantry target" in {
@ -340,23 +340,23 @@ class ResolutionCalculationsTests extends Specification {
"calculate no max damage for vehicles" in { "calculate no max damage for vehicles" in {
val target1 = Vehicle(GlobalDefinitions.fury) //! val target1 = Vehicle(GlobalDefinitions.fury) //!
val resprojectile1 = ResolvedProjectile( val resprojectile1 = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
projectile, projectile,
SourceEntry(target1), SourceEntry(target1),
target1.DamageModel, target1.DamageModel,
Vector3.Zero Vector3.Zero
) )
MaxDamageAfterResist(resprojectile1)(50, 10) mustEqual (0, 0) MaxDamage(resprojectile1)(50, 10) mustEqual (0, 0)
val target2 = player2 val target2 = player2
val resprojectile2 = ResolvedProjectile( val resprojectile2 = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
projectile, projectile,
SourceEntry(target2), SourceEntry(target2),
target2.DamageModel, target2.DamageModel,
Vector3.Zero Vector3.Zero
) )
MaxDamageAfterResist(resprojectile2)(50, 10) mustEqual (0, 40) MaxDamage(resprojectile2)(50, 10) mustEqual (0, 40)
} }
"calculate health and armor damage for max target" in { "calculate health and armor damage for max target" in {
@ -376,7 +376,7 @@ class ResolutionCalculationsTests extends Specification {
"do not care if target is infantry for vehicle calculations" in { "do not care if target is infantry for vehicle calculations" in {
val target1 = player val target1 = player
val resprojectile1 = ResolvedProjectile( val resprojectile1 = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
projectile, projectile,
SourceEntry(target1), SourceEntry(target1),
target1.DamageModel, target1.DamageModel,
@ -386,7 +386,7 @@ class ResolutionCalculationsTests extends Specification {
val target2 = Vehicle(GlobalDefinitions.fury) //! val target2 = Vehicle(GlobalDefinitions.fury) //!
val resprojectile2 = ResolvedProjectile( val resprojectile2 = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
projectile, projectile,
SourceEntry(target2), SourceEntry(target2),
target2.DamageModel, target2.DamageModel,
@ -450,10 +450,9 @@ class DamageModelTests extends Specification {
Vector3.Zero Vector3.Zero
) )
val func: Any => ResolvedProjectile = resprojectile.damage_model.Calculate(resprojectile) val func: Any => ResolvedProjectile = resprojectile.damage_model.Calculate(resprojectile)
func(tplayer) func(tplayer)
tplayer.Health mustEqual 54 tplayer.Health mustEqual 65
tplayer.Armor mustEqual 46 tplayer.Armor mustEqual 35
} }
"resolve infantry targets in a specific way" in { "resolve infantry targets in a specific way" in {
@ -471,8 +470,7 @@ class DamageModelTests extends Specification {
Vector3.Zero Vector3.Zero
) )
val func: Any => ResolvedProjectile = val func: Any => ResolvedProjectile =
resprojectile.damage_model.Calculate(resprojectile, ProjectileResolution.Splash) resprojectile.damage_model.Calculate(resprojectile, DamageType.Splash)
func(tplayer) func(tplayer)
tplayer.Health mustEqual 65 tplayer.Health mustEqual 65
tplayer.Armor mustEqual 35 tplayer.Armor mustEqual 35
@ -572,7 +570,7 @@ class DamageModelTests extends Specification {
Vector3.Zero Vector3.Zero
) )
val func: Any => ResolvedProjectile = val func: Any => ResolvedProjectile =
resprojectile.damage_model.Calculate(resprojectile, ProjectileResolution.Splash) resprojectile.damage_model.Calculate(resprojectile, DamageType.Splash)
func(vehicle) func(vehicle)
vehicle.Health mustEqual 518 vehicle.Health mustEqual 518

View file

@ -40,7 +40,7 @@ class DamageableTest extends Specification {
"permit damage" in { "permit damage" in {
val target = new SensorDeployable(GlobalDefinitions.motionalarmsensor) val target = new SensorDeployable(GlobalDefinitions.motionalarmsensor)
val resolved = ResolvedProjectile( val resolved = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
Projectile(projectileA, weaponA.Definition, weaponA.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), Projectile(projectileA, weaponA.Definition, weaponA.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero),
SourceEntry(target), SourceEntry(target),
target.DamageModel, target.DamageModel,
@ -53,7 +53,7 @@ class DamageableTest extends Specification {
"ignore attempts at non-zero damage" in { "ignore attempts at non-zero damage" in {
val target = new SensorDeployable(GlobalDefinitions.motionalarmsensor) val target = new SensorDeployable(GlobalDefinitions.motionalarmsensor)
val resolved = ResolvedProjectile( val resolved = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
Projectile( Projectile(
projectileA, projectileA,
weaponA.Definition, weaponA.Definition,
@ -78,7 +78,7 @@ class DamageableTest extends Specification {
Faction = player1.Faction Faction = player1.Faction
} }
val resolved = ResolvedProjectile( val resolved = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
Projectile(projectileA, weaponA.Definition, weaponA.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), Projectile(projectileA, weaponA.Definition, weaponA.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero),
SourceEntry(target), SourceEntry(target),
target.DamageModel, target.DamageModel,
@ -101,7 +101,7 @@ class DamageableTest extends Specification {
Faction = PlanetSideEmpire.NC Faction = PlanetSideEmpire.NC
} }
val resolved = ResolvedProjectile( val resolved = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
Projectile(projectileA, weaponA.Definition, weaponA.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), Projectile(projectileA, weaponA.Definition, weaponA.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero),
SourceEntry(target), SourceEntry(target),
target.DamageModel, target.DamageModel,
@ -128,7 +128,7 @@ class DamageableTest extends Specification {
Faction = player1.Faction Faction = player1.Faction
} }
val resolved = ResolvedProjectile( val resolved = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
Projectile(projectileA, weaponA.Definition, weaponA.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), Projectile(projectileA, weaponA.Definition, weaponA.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero),
SourceEntry(target), SourceEntry(target),
target.DamageModel, target.DamageModel,
@ -152,7 +152,7 @@ class DamageableTest extends Specification {
"permit jamming" in { "permit jamming" in {
val target = new SensorDeployable(GlobalDefinitions.motionalarmsensor) val target = new SensorDeployable(GlobalDefinitions.motionalarmsensor)
val resolved = ResolvedProjectile( val resolved = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
Projectile(projectileB, weaponB.Definition, weaponB.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), Projectile(projectileB, weaponB.Definition, weaponB.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero),
SourceEntry(target), SourceEntry(target),
target.DamageModel, target.DamageModel,
@ -166,7 +166,7 @@ class DamageableTest extends Specification {
"ignore attempts at jamming if the projectile is does not cause the effect" in { "ignore attempts at jamming if the projectile is does not cause the effect" in {
val target = new SensorDeployable(GlobalDefinitions.motionalarmsensor) val target = new SensorDeployable(GlobalDefinitions.motionalarmsensor)
val resolved = ResolvedProjectile( val resolved = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
Projectile(projectileA, weaponA.Definition, weaponA.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), Projectile(projectileA, weaponA.Definition, weaponA.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero),
SourceEntry(target), SourceEntry(target),
target.DamageModel, target.DamageModel,
@ -181,7 +181,7 @@ class DamageableTest extends Specification {
val target = new SensorDeployable(GlobalDefinitions.motionalarmsensor) val target = new SensorDeployable(GlobalDefinitions.motionalarmsensor)
target.Faction = player1.Faction target.Faction = player1.Faction
val resolved = ResolvedProjectile( val resolved = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
Projectile(projectileB, weaponB.Definition, weaponB.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), Projectile(projectileB, weaponB.Definition, weaponB.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero),
SourceEntry(target), SourceEntry(target),
target.DamageModel, target.DamageModel,
@ -196,7 +196,7 @@ class DamageableTest extends Specification {
"ignore attempts at jamming targets that are not jammable" in { "ignore attempts at jamming targets that are not jammable" in {
val target = new TrapDeployable(GlobalDefinitions.tank_traps) val target = new TrapDeployable(GlobalDefinitions.tank_traps)
val resolved = ResolvedProjectile( val resolved = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
Projectile(projectileB, weaponB.Definition, weaponB.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), Projectile(projectileB, weaponB.Definition, weaponB.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero),
SourceEntry(target), SourceEntry(target),
target.DamageModel, target.DamageModel,
@ -216,7 +216,7 @@ class DamageableTest extends Specification {
val target = new SensorDeployable(GlobalDefinitions.motionalarmsensor) val target = new SensorDeployable(GlobalDefinitions.motionalarmsensor)
target.Faction = player1.Faction target.Faction = player1.Faction
val resolved = ResolvedProjectile( val resolved = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
Projectile(projectileB, weaponB.Definition, weaponB.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), Projectile(projectileB, weaponB.Definition, weaponB.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero),
SourceEntry(target), SourceEntry(target),
target.DamageModel, target.DamageModel,
@ -271,7 +271,7 @@ class DamageableEntityDamageTest extends ActorTest {
val weapon = Tool(GlobalDefinitions.phoenix) //decimator val weapon = Tool(GlobalDefinitions.phoenix) //decimator
val projectile = weapon.Projectile val projectile = weapon.Projectile
val resolved = ResolvedProjectile( val resolved = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
Projectile( Projectile(
projectile, projectile,
weapon.Definition, weapon.Definition,
@ -342,7 +342,7 @@ class DamageableEntityDestroyedTest extends ActorTest {
val weapon = Tool(GlobalDefinitions.phoenix) //decimator val weapon = Tool(GlobalDefinitions.phoenix) //decimator
val projectile = weapon.Projectile val projectile = weapon.Projectile
val resolved = ResolvedProjectile( val resolved = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
Projectile( Projectile(
projectile, projectile,
weapon.Definition, weapon.Definition,
@ -416,7 +416,7 @@ class DamageableEntityNotDestroyTwice extends ActorTest {
val weapon = Tool(GlobalDefinitions.phoenix) //decimator val weapon = Tool(GlobalDefinitions.phoenix) //decimator
val projectile = weapon.Projectile val projectile = weapon.Projectile
val resolved = ResolvedProjectile( val resolved = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
Projectile( Projectile(
projectile, projectile,
weapon.Definition, weapon.Definition,
@ -487,7 +487,7 @@ class DamageableAmenityTest extends ActorTest {
val weapon = Tool(GlobalDefinitions.phoenix) //decimator val weapon = Tool(GlobalDefinitions.phoenix) //decimator
val projectile = weapon.Projectile val projectile = weapon.Projectile
val resolved = ResolvedProjectile( val resolved = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
Projectile( Projectile(
projectile, projectile,
weapon.Definition, weapon.Definition,
@ -580,7 +580,7 @@ class DamageableMountableDamageTest extends ActorTest {
val weapon = Tool(GlobalDefinitions.phoenix) //decimator val weapon = Tool(GlobalDefinitions.phoenix) //decimator
val projectile = weapon.Projectile val projectile = weapon.Projectile
val resolved = ResolvedProjectile( val resolved = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
Projectile( Projectile(
projectile, projectile,
weapon.Definition, weapon.Definition,
@ -626,7 +626,7 @@ class DamageableMountableDamageTest extends ActorTest {
msg1_3(1) match { msg1_3(1) match {
case AvatarServiceMessage( case AvatarServiceMessage(
"TestCharacter2", "TestCharacter2",
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, Vector3(2, 2, 2))) AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(_, Vector3(2, 2, 2)))
) => ) =>
true true
case _ => false case _ => false
@ -675,7 +675,7 @@ class DamageableMountableDestroyTest extends ActorTest {
val weapon = Tool(GlobalDefinitions.phoenix) //decimator val weapon = Tool(GlobalDefinitions.phoenix) //decimator
val projectile = weapon.Projectile val projectile = weapon.Projectile
val resolved = ResolvedProjectile( val resolved = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
Projectile( Projectile(
projectile, projectile,
weapon.Definition, weapon.Definition,
@ -737,8 +737,10 @@ class DamageableWeaponTurretDamageTest extends ActorTest {
} }
val activityProbe = TestProbe() val activityProbe = TestProbe()
val avatarProbe = TestProbe() val avatarProbe = TestProbe()
val vehicleProbe = TestProbe()
zone.Activity = activityProbe.ref zone.Activity = activityProbe.ref
zone.AvatarEvents = avatarProbe.ref zone.AvatarEvents = avatarProbe.ref
zone.VehicleEvents = vehicleProbe.ref
val turret = new TurretDeployable(GlobalDefinitions.portable_manned_turret_tr) //2 val turret = new TurretDeployable(GlobalDefinitions.portable_manned_turret_tr) //2
turret.Actor = system.actorOf(Props(classOf[TurretControl], turret), "turret-control") turret.Actor = system.actorOf(Props(classOf[TurretControl], turret), "turret-control")
turret.Zone = zone turret.Zone = zone
@ -787,16 +789,17 @@ class DamageableWeaponTurretDamageTest extends ActorTest {
assert(turret.Health == turret.Definition.DefaultHealth) assert(turret.Health == turret.Definition.DefaultHealth)
turret.Actor ! Vitality.Damage(applyDamageTo) turret.Actor ! Vitality.Damage(applyDamageTo)
val msg1_3 = avatarProbe.receiveN(2, 500 milliseconds) val msg12 = vehicleProbe.receiveOne(500 milliseconds)
val msg2 = activityProbe.receiveOne(500 milliseconds) val msg3 = activityProbe.receiveOne(500 milliseconds)
val msg4 = avatarProbe.receiveOne(500 milliseconds)
assert( assert(
msg1_3.head match { msg12 match {
case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => true case VehicleServiceMessage("test", VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), PlanetSideGUID(2), 0, _)) => true
case _ => false case _ => false
} }
) )
assert( assert(
msg2 match { msg3 match {
case activity: Zone.HotSpot.Activity => case activity: Zone.HotSpot.Activity =>
activity.attacker == PlayerSource(player1) && activity.attacker == PlayerSource(player1) &&
activity.defender == turretSource && activity.defender == turretSource &&
@ -805,10 +808,10 @@ class DamageableWeaponTurretDamageTest extends ActorTest {
} }
) )
assert( assert(
msg1_3(1) match { msg4 match {
case AvatarServiceMessage( case AvatarServiceMessage(
"TestCharacter2", "TestCharacter2",
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, Vector3(2, 2, 2))) AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(_, Vector3(2, 2, 2)))
) => ) =>
true true
case _ => false case _ => false
@ -862,7 +865,7 @@ class DamageableWeaponTurretJammerTest extends ActorTest {
val projectile = weapon.Projectile val projectile = weapon.Projectile
val turretSource = SourceEntry(turret) val turretSource = SourceEntry(turret)
val resolved = ResolvedProjectile( val resolved = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
Projectile( Projectile(
projectile, projectile,
weapon.Definition, weapon.Definition,
@ -964,7 +967,7 @@ class DamageableWeaponTurretDestructionTest extends ActorTest {
val weaponA = Tool(GlobalDefinitions.jammer_grenade) val weaponA = Tool(GlobalDefinitions.jammer_grenade)
val projectileA = weaponA.Projectile val projectileA = weaponA.Projectile
val resolvedA = ResolvedProjectile( val resolvedA = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
Projectile( Projectile(
projectileA, projectileA,
weaponA.Definition, weaponA.Definition,
@ -983,7 +986,7 @@ class DamageableWeaponTurretDestructionTest extends ActorTest {
val weaponB = Tool(GlobalDefinitions.phoenix) //decimator val weaponB = Tool(GlobalDefinitions.phoenix) //decimator
val projectileB = weaponB.Projectile val projectileB = weaponB.Projectile
val resolvedB = ResolvedProjectile( val resolvedB = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
Projectile( Projectile(
projectileB, projectileB,
weaponB.Definition, weaponB.Definition,
@ -1046,8 +1049,8 @@ class DamageableWeaponTurretDestructionTest extends ActorTest {
) )
assert( assert(
msg56.head match { msg56.head match {
case VehicleServiceMessage.TurretUpgrade(SupportActor.ClearSpecific(List(t), _)) if t eq turret => true case VehicleServiceMessage.TurretUpgrade(SupportActor.ClearSpecific(List(t), _)) if turret eq t => true
case _ => false case _ => false
} }
) )
assert( assert(
@ -1130,40 +1133,36 @@ class DamageableVehicleDamageTest extends ActorTest {
assert(atv.Shields == 1) assert(atv.Shields == 1)
atv.Actor ! Vitality.Damage(applyDamageTo) atv.Actor ! Vitality.Damage(applyDamageTo)
val msg1_3 = avatarProbe.receiveN(2, 500 milliseconds) val msg12 = vehicleProbe.receiveN(2, 200 milliseconds)
val msg2 = activityProbe.receiveOne(200 milliseconds) val msg3 = activityProbe.receiveOne(200 milliseconds)
val msg4 = vehicleProbe.receiveOne(200 milliseconds) val msg4 = avatarProbe.receiveOne(200 milliseconds)
assert( assert(
msg1_3.head match { msg12.head match {
case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(1), 0, _)) => true case VehicleServiceMessage(_, VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), PlanetSideGUID(1), 68, _)) => true
case _ => false case _ => false
} }
) )
assert( assert(
msg2 match { msg12(1) match {
case VehicleServiceMessage("test", VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), PlanetSideGUID(1), 0, _)) => true
case _ => false
}
)
assert(
msg3 match {
case activity: Zone.HotSpot.Activity => case activity: Zone.HotSpot.Activity =>
activity.attacker == PlayerSource(player1) && activity.attacker == PlayerSource(player1) &&
activity.defender == vehicleSource && activity.defender == vehicleSource &&
activity.location == Vector3(1, 0, 0) activity.location == Vector3(1, 0, 0)
case _ => false
}
)
assert(
msg1_3(1) match {
case AvatarServiceMessage(
"TestCharacter2",
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, Vector3(2, 0, 0)))
) =>
true
case _ => false case _ => false
} }
) )
assert( assert(
msg4 match { msg4 match {
case VehicleServiceMessage( case AvatarServiceMessage(
channel, "TestCharacter2",
VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), PlanetSideGUID(1), 68, _) AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(9, Vector3(2, 0, 0)))
) if channel.equals(atv.Actor.toString) => ) =>
true true
case _ => false case _ => false
} }
@ -1264,17 +1263,23 @@ class DamageableVehicleDamageMountedTest extends ActorTest {
assert(atv.Shields == 1) assert(atv.Shields == 1)
lodestar.Actor ! Vitality.Damage(applyDamageTo) lodestar.Actor ! Vitality.Damage(applyDamageTo)
val msg1_35 = avatarProbe.receiveN(3, 500 milliseconds) val msg12 = vehicleProbe.receiveN(2, 200 milliseconds)
val msg2 = activityProbe.receiveOne(200 milliseconds) val msg3 = activityProbe.receiveOne(200 milliseconds)
val msg4 = vehicleProbe.receiveOne(200 milliseconds) val msg45 = avatarProbe.receiveN(2,200 milliseconds)
assert( assert(
msg1_35.head match { msg12.head match {
case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(1), 0, _)) => true case VehicleServiceMessage(_, VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), PlanetSideGUID(1), 68, _)) => true
case _ => false case _ => false
} }
) )
assert( assert(
msg2 match { msg12(1) match {
case VehicleServiceMessage("test", VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), PlanetSideGUID(1), 0, _)) => true
case _ => false
}
)
assert(
msg3 match {
case activity: Zone.HotSpot.Activity => case activity: Zone.HotSpot.Activity =>
activity.attacker == PlayerSource(player1) && activity.attacker == PlayerSource(player1) &&
activity.defender == vehicleSource && activity.defender == vehicleSource &&
@ -1283,30 +1288,20 @@ class DamageableVehicleDamageMountedTest extends ActorTest {
} }
) )
assert( assert(
msg1_35(1) match { msg45.head match {
case AvatarServiceMessage( case AvatarServiceMessage(
"TestCharacter2", "TestCharacter2",
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, Vector3(2, 0, 0))) AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(400, Vector3(2, 0, 0)))
) => ) =>
true true
case _ => false case _ => false
} }
) )
assert( assert(
msg4 match { msg45(1) match {
case VehicleServiceMessage(
channel,
VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), PlanetSideGUID(1), 68, _)
) if channel.equals(lodestar.Actor.toString) =>
true
case _ => false
}
)
assert(
msg1_35(2) match {
case AvatarServiceMessage( case AvatarServiceMessage(
"TestCharacter3", "TestCharacter3",
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, Vector3(2, 0, 0))) AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(0, Vector3(2, 0, 0)))
) => ) =>
true true
case _ => false case _ => false
@ -1385,7 +1380,7 @@ class DamageableVehicleJammeringMountedTest extends ActorTest {
val weapon = Tool(GlobalDefinitions.jammer_grenade) val weapon = Tool(GlobalDefinitions.jammer_grenade)
val projectile = weapon.Projectile val projectile = weapon.Projectile
val resolved = ResolvedProjectile( val resolved = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
Projectile( Projectile(
projectile, projectile,
weapon.Definition, weapon.Definition,
@ -1601,7 +1596,7 @@ class DamageableVehicleDestroyMountedTest extends ActorTest {
val weaponA = Tool(GlobalDefinitions.jammer_grenade) val weaponA = Tool(GlobalDefinitions.jammer_grenade)
val projectileA = weaponA.Projectile val projectileA = weaponA.Projectile
val resolvedA = ResolvedProjectile( val resolvedA = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
Projectile( Projectile(
projectileA, projectileA,
weaponA.Definition, weaponA.Definition,
@ -1620,7 +1615,7 @@ class DamageableVehicleDestroyMountedTest extends ActorTest {
val weaponB = Tool(GlobalDefinitions.phoenix) //decimator val weaponB = Tool(GlobalDefinitions.phoenix) //decimator
val projectileB = weaponB.Projectile val projectileB = weaponB.Projectile
val resolvedB = ResolvedProjectile( val resolvedB = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
Projectile( Projectile(
projectileB, projectileB,
weaponB.Definition, weaponB.Definition,

View file

@ -334,7 +334,7 @@ class ExplosiveDeployableJammerTest extends ActorTest {
val pSource = PlayerSource(player1) val pSource = PlayerSource(player1)
val projectile = weapon.Projectile val projectile = weapon.Projectile
val resolved = ResolvedProjectile( val resolved = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
Projectile(projectile, weapon.Definition, weapon.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), Projectile(projectile, weapon.Definition, weapon.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero),
jMineSource, jMineSource,
j_mine.DamageModel, j_mine.DamageModel,
@ -374,7 +374,7 @@ class ExplosiveDeployableJammerTest extends ActorTest {
assert( assert(
msg_local(2) match { msg_local(2) match {
case LocalServiceMessage.Deployables(SupportActor.ClearSpecific(List(target), _zone)) => case LocalServiceMessage.Deployables(SupportActor.ClearSpecific(List(target), _zone)) =>
(target eq j_mine) && (_zone eq zone) (j_mine eq target) && (_zone eq zone)
case _ => false case _ => false
} }
) )
@ -432,7 +432,7 @@ class ExplosiveDeployableJammerExplodeTest extends ActorTest {
val pSource = PlayerSource(player1) val pSource = PlayerSource(player1)
val projectile = weapon.Projectile val projectile = weapon.Projectile
val resolved = ResolvedProjectile( val resolved = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
Projectile(projectile, weapon.Definition, weapon.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), Projectile(projectile, weapon.Definition, weapon.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero),
hMineSource, hMineSource,
h_mine.DamageModel, h_mine.DamageModel,
@ -478,7 +478,7 @@ class ExplosiveDeployableJammerExplodeTest extends ActorTest {
assert( assert(
msg_local(3) match { msg_local(3) match {
case LocalServiceMessage.Deployables(SupportActor.ClearSpecific(List(target), _zone)) => case LocalServiceMessage.Deployables(SupportActor.ClearSpecific(List(target), _zone)) =>
(target eq h_mine) && (_zone eq zone) (h_mine eq target) && (_zone eq zone)
case _ => false case _ => false
} }
) )
@ -542,7 +542,7 @@ class ExplosiveDeployableDestructionTest extends ActorTest {
val pSource = PlayerSource(player1) val pSource = PlayerSource(player1)
val projectile = weapon.Projectile val projectile = weapon.Projectile
val resolved = ResolvedProjectile( val resolved = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
Projectile(projectile, weapon.Definition, weapon.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), Projectile(projectile, weapon.Definition, weapon.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero),
hMineSource, hMineSource,
h_mine.DamageModel, h_mine.DamageModel,
@ -584,7 +584,7 @@ class ExplosiveDeployableDestructionTest extends ActorTest {
assert( assert(
msg_local(2) match { msg_local(2) match {
case LocalServiceMessage.Deployables(SupportActor.ClearSpecific(List(target), _zone)) => case LocalServiceMessage.Deployables(SupportActor.ClearSpecific(List(target), _zone)) =>
(target eq h_mine) && (_zone eq zone) (h_mine eq target) && (_zone eq zone)
case _ => false case _ => false
} }
) )

View file

@ -80,7 +80,7 @@ class GeneratorControlDamageTest extends ActorTest {
val weapon = Tool(GlobalDefinitions.phoenix) //decimator val weapon = Tool(GlobalDefinitions.phoenix) //decimator
val projectile = weapon.Projectile val projectile = weapon.Projectile
val resolved = ResolvedProjectile( val resolved = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
Projectile( Projectile(
projectile, projectile,
weapon.Definition, weapon.Definition,
@ -162,7 +162,7 @@ class GeneratorControlCriticalTest extends ActorTest {
val weapon = Tool(GlobalDefinitions.phoenix) //decimator val weapon = Tool(GlobalDefinitions.phoenix) //decimator
val projectile = weapon.Projectile val projectile = weapon.Projectile
val resolved = ResolvedProjectile( val resolved = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
Projectile( Projectile(
projectile, projectile,
weapon.Definition, weapon.Definition,
@ -253,7 +253,7 @@ class GeneratorControlDestroyedTest extends ActorTest {
val weapon = Tool(GlobalDefinitions.phoenix) //decimator val weapon = Tool(GlobalDefinitions.phoenix) //decimator
val projectile = weapon.Projectile val projectile = weapon.Projectile
val resolved = ResolvedProjectile( val resolved = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
Projectile( Projectile(
projectile, projectile,
weapon.Definition, weapon.Definition,
@ -384,7 +384,7 @@ class GeneratorControlKillsTest extends ActorTest {
val weapon = Tool(GlobalDefinitions.phoenix) //decimator val weapon = Tool(GlobalDefinitions.phoenix) //decimator
val projectile = weapon.Projectile val projectile = weapon.Projectile
val resolved = ResolvedProjectile( val resolved = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
Projectile( Projectile(
projectile, projectile,
weapon.Definition, weapon.Definition,
@ -507,7 +507,7 @@ class GeneratorControlNotDestroyTwice extends ActorTest {
val weapon = Tool(GlobalDefinitions.phoenix) //decimator val weapon = Tool(GlobalDefinitions.phoenix) //decimator
val projectile = weapon.Projectile val projectile = weapon.Projectile
val resolved = ResolvedProjectile( val resolved = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
Projectile( Projectile(
projectile, projectile,
weapon.Definition, weapon.Definition,
@ -597,7 +597,7 @@ class GeneratorControlNotDamageIfExplodingTest extends ActorTest {
val weapon = Tool(GlobalDefinitions.phoenix) //decimator val weapon = Tool(GlobalDefinitions.phoenix) //decimator
val projectile = weapon.Projectile val projectile = weapon.Projectile
val resolved = ResolvedProjectile( val resolved = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
Projectile( Projectile(
projectile, projectile,
weapon.Definition, weapon.Definition,
@ -691,7 +691,7 @@ class GeneratorControlNotRepairIfExplodingTest extends ActorTest {
val weapon = Tool(GlobalDefinitions.phoenix) //decimator val weapon = Tool(GlobalDefinitions.phoenix) //decimator
val projectile = weapon.Projectile val projectile = weapon.Projectile
val resolved = ResolvedProjectile( val resolved = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
Projectile( Projectile(
projectile, projectile,
weapon.Definition, weapon.Definition,

View file

@ -413,14 +413,13 @@ class PlayerControlDamageTest extends ActorTest {
) )
assert( assert(
msg_avatar(1) match { msg_avatar(1) match {
case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => true case AvatarServiceMessage("TestCharacter2", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 2, _)) => true
case _ => false case _ => false
} }
) )
assert( assert(
msg_avatar(2) match { msg_avatar(2) match {
case AvatarServiceMessage("TestCharacter2", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 2, _)) => case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => true
true
case _ => false case _ => false
} }
) )
@ -437,7 +436,7 @@ class PlayerControlDamageTest extends ActorTest {
msg_avatar(3) match { msg_avatar(3) match {
case AvatarServiceMessage( case AvatarServiceMessage(
"TestCharacter2", "TestCharacter2",
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, Vector3(2, 0, 0))) AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(17, Vector3(2, 0, 0)))
) => ) =>
true true
case _ => false case _ => false

View file

@ -358,7 +358,6 @@ class ProjectileTest extends Specification {
fury_dm, fury_dm,
Vector3(1.2f, 3.4f, 5.6f) Vector3(1.2f, 3.4f, 5.6f)
) )
obj.resolution mustEqual ProjectileResolution.Hit
obj.projectile mustEqual projectile obj.projectile mustEqual projectile
obj.target mustEqual p2_source obj.target mustEqual p2_source
obj.damage_model mustEqual fury.DamageModel obj.damage_model mustEqual fury.DamageModel

View file

@ -997,7 +997,7 @@ class VehicleControlShieldsNotChargingTooEarlyTest extends ActorTest {
// val p_source = PlayerSource( Player(Avatar(0, "TestTarget", PlanetSideEmpire.NC, CharacterGender.Female, 1, CharacterVoice.Mute)) ) // val p_source = PlayerSource( Player(Avatar(0, "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 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 fury_dm = Vehicle(GlobalDefinitions.fury).DamageModel
// val obj = ResolvedProjectile(ProjectileResolution.Hit, projectile, p_source, fury_dm, Vector3(1.2f, 3.4f, 5.6f)) // val obj = ResolvedProjectile(projectile, p_source, fury_dm, Vector3(1.2f, 3.4f, 5.6f))
// //
// "not charge vehicle shields if recently damaged" in { // "not charge vehicle shields if recently damaged" in {
// assert(vehicle.Shields == 0) // assert(vehicle.Shields == 0)

View file

@ -21,7 +21,7 @@ class VitalityTest extends Specification {
val pSource = PlayerSource(player) val pSource = PlayerSource(player)
val projectile = Projectile(proj, wep, wep_fmode, player, Vector3(2, 2, 0), Vector3.Zero) val projectile = Projectile(proj, wep, wep_fmode, player, Vector3(2, 2, 0), Vector3.Zero)
val resprojectile = ResolvedProjectile( val resprojectile = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
projectile, projectile,
SourceEntry(player), SourceEntry(player),
player.DamageModel, player.DamageModel,
@ -69,7 +69,7 @@ class VitalityTest extends Specification {
val pSource = PlayerSource(player) val pSource = PlayerSource(player)
val projectile = Projectile(proj, wep, wep_fmode, player, Vector3(2, 2, 0), Vector3.Zero) val projectile = Projectile(proj, wep, wep_fmode, player, Vector3(2, 2, 0), Vector3.Zero)
val resprojectile = ResolvedProjectile( val resprojectile = ResolvedProjectile(
ProjectileResolution.Splash, ProjectileResolution.Hit,
projectile, projectile,
SourceEntry(player), SourceEntry(player),
player.DamageModel, player.DamageModel,

View file

@ -110,7 +110,8 @@ class ZoneTest extends Specification {
zone.AddPool("pool2", (51 to 75).toList) zone.AddPool("pool2", (51 to 75).toList)
val obj = new TestObject() val obj = new TestObject()
guid1.register(obj, "pool2").isSuccess mustEqual true val registration = guid1.register(obj, "pool2")
registration.isSuccess mustEqual true
guid1.WhichPool(obj).contains("pool2") mustEqual true guid1.WhichPool(obj).contains("pool2") mustEqual true
zone.GUID(new NumberPoolHub(new LimitedNumberSource(150))) mustEqual false zone.GUID(new NumberPoolHub(new LimitedNumberSource(150))) mustEqual false
@ -214,13 +215,15 @@ class ZonePopulationTest extends ActorTest {
override def SetupNumberPools() = {} override def SetupNumberPools() = {}
} }
val avatar = Avatar(0, "Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5) val avatar = Avatar(0, "Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
val player = Player(avatar)
player.GUID = PlanetSideGUID(1)
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
expectNoMessage(200 milliseconds) expectNoMessage(200 milliseconds)
assert(zone.Players.isEmpty) assert(zone.Players.isEmpty)
assert(zone.LivePlayers.isEmpty) assert(zone.LivePlayers.isEmpty)
zone.Population ! Zone.Population.Join(avatar) zone.Population ! Zone.Population.Join(avatar)
zone.Population ! Zone.Population.Spawn(avatar, Player(avatar), null) zone.Population ! Zone.Population.Spawn(avatar, player, null)
expectNoMessage(Duration.create(200, "ms")) expectNoMessage(Duration.create(200, "ms"))
assert(zone.Players.size == 1) assert(zone.Players.size == 1)
assert(zone.Players.head == avatar) assert(zone.Players.head == avatar)
@ -232,10 +235,12 @@ class ZonePopulationTest extends ActorTest {
override def SetupNumberPools() = {} override def SetupNumberPools() = {}
} }
val avatar = Avatar(1, "Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5) val avatar = Avatar(1, "Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
val player = Player(avatar)
player.GUID = PlanetSideGUID(1)
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
receiveOne(Duration.create(200, "ms")) //consume receiveOne(Duration.create(200, "ms")) //consume
zone.Population ! Zone.Population.Join(avatar) zone.Population ! Zone.Population.Join(avatar)
zone.Population ! Zone.Population.Spawn(avatar, Player(avatar), null) zone.Population ! Zone.Population.Spawn(avatar, player, null)
expectNoMessage(Duration.create(100, "ms")) expectNoMessage(Duration.create(100, "ms"))
assert(zone.Players.size == 1) assert(zone.Players.size == 1)
@ -251,6 +256,7 @@ class ZonePopulationTest extends ActorTest {
val zone = new Zone("test", new ZoneMap(""), 0) { override def SetupNumberPools() = {} } val zone = new Zone("test", new ZoneMap(""), 0) { override def SetupNumberPools() = {} }
val avatar = Avatar(0, "Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5) val avatar = Avatar(0, "Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
val player = Player(avatar) val player = Player(avatar)
player.GUID = PlanetSideGUID(1)
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
expectNoMessage(200 milliseconds) expectNoMessage(200 milliseconds)
zone.Population ! Zone.Population.Join(avatar) zone.Population ! Zone.Population.Join(avatar)
@ -271,6 +277,7 @@ class ZonePopulationTest extends ActorTest {
val zone = new Zone("test", new ZoneMap(""), 0) { override def SetupNumberPools() = {} } val zone = new Zone("test", new ZoneMap(""), 0) { override def SetupNumberPools() = {} }
val avatar = Avatar(0, "Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5) val avatar = Avatar(0, "Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
val player = Player(avatar) val player = Player(avatar)
player.GUID = PlanetSideGUID(1)
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
expectNoMessage(200 milliseconds) expectNoMessage(200 milliseconds)
zone.Population ! Zone.Population.Join(avatar) zone.Population ! Zone.Population.Join(avatar)
@ -318,7 +325,9 @@ class ZonePopulationTest extends ActorTest {
val zone = new Zone("test", new ZoneMap(""), 0) { override def SetupNumberPools() = {} } val zone = new Zone("test", new ZoneMap(""), 0) { override def SetupNumberPools() = {} }
val avatar = Avatar(0, "Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5) val avatar = Avatar(0, "Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
val player1 = Player(avatar) val player1 = Player(avatar)
player1.GUID = PlanetSideGUID(1)
val player2 = Player(avatar) val player2 = Player(avatar)
player2.GUID = PlanetSideGUID(2)
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
expectNoMessage(200 milliseconds) expectNoMessage(200 milliseconds)
zone.Population ! Zone.Population.Join(avatar) zone.Population ! Zone.Population.Join(avatar)
@ -344,6 +353,7 @@ class ZonePopulationTest extends ActorTest {
val zone = new Zone("test", new ZoneMap(""), 0) { override def SetupNumberPools() = {} } val zone = new Zone("test", new ZoneMap(""), 0) { override def SetupNumberPools() = {} }
val avatar = Avatar(0, "Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5) val avatar = Avatar(0, "Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
val player = Player(avatar) val player = Player(avatar)
player.GUID = PlanetSideGUID(1)
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
expectNoMessage(200 milliseconds) expectNoMessage(200 milliseconds)
@ -364,10 +374,12 @@ class ZonePopulationTest extends ActorTest {
override def SetupNumberPools() = {} override def SetupNumberPools() = {}
} }
val avatar = Avatar(2, "Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5) val avatar = Avatar(2, "Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
val player = Player(avatar)
player.GUID = PlanetSideGUID(1)
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
expectNoMessage(200 milliseconds) expectNoMessage(200 milliseconds)
zone.Population ! Zone.Population.Join(avatar) zone.Population ! Zone.Population.Join(avatar)
zone.Population ! Zone.Population.Spawn(avatar, Player(avatar), null) zone.Population ! Zone.Population.Spawn(avatar, player, null)
expectNoMessage(Duration.create(100, "ms")) expectNoMessage(Duration.create(100, "ms"))
assert(zone.Players.size == 1) assert(zone.Players.size == 1)
@ -388,6 +400,7 @@ class ZonePopulationTest extends ActorTest {
override def SetupNumberPools() = {} override def SetupNumberPools() = {}
} }
val player = Player(Avatar(3, "Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)) val player = Player(Avatar(3, "Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5))
player.GUID = PlanetSideGUID(1)
player.Release player.Release
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
expectNoMessage(200 milliseconds) expectNoMessage(200 milliseconds)
@ -404,6 +417,7 @@ class ZonePopulationTest extends ActorTest {
override def SetupNumberPools() = {} override def SetupNumberPools() = {}
} }
val player = Player(Avatar(4, "Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)) val player = Player(Avatar(4, "Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5))
player.GUID = PlanetSideGUID(1)
player.Release player.Release
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
expectNoMessage(200 milliseconds) expectNoMessage(200 milliseconds)
@ -422,10 +436,13 @@ class ZonePopulationTest extends ActorTest {
override def SetupNumberPools() = {} override def SetupNumberPools() = {}
} }
val player1 = Player(Avatar(5, "Chord1", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)) val player1 = Player(Avatar(5, "Chord1", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5))
player1.GUID = PlanetSideGUID(1)
player1.Release player1.Release
val player2 = Player(Avatar(6, "Chord2", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)) val player2 = Player(Avatar(6, "Chord2", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5))
player2.GUID = PlanetSideGUID(2)
player2.Release player2.Release
val player3 = Player(Avatar(7, "Chord3", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)) val player3 = Player(Avatar(7, "Chord3", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5))
player3.GUID = PlanetSideGUID(3)
player3.Release player3.Release
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
expectNoMessage(200 milliseconds) expectNoMessage(200 milliseconds)
@ -450,6 +467,7 @@ class ZonePopulationTest extends ActorTest {
override def SetupNumberPools() = {} override def SetupNumberPools() = {}
} }
val player = Player(Avatar(8, "Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)) val player = Player(Avatar(8, "Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5))
player.GUID = PlanetSideGUID(1)
//player.Release !!important //player.Release !!important
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
expectNoMessage(200 milliseconds) expectNoMessage(200 milliseconds)