diff --git a/common/src/main/scala/net/psforever/objects/BoomerDeployable.scala b/common/src/main/scala/net/psforever/objects/BoomerDeployable.scala
index f218da87..39523d25 100644
--- a/common/src/main/scala/net/psforever/objects/BoomerDeployable.scala
+++ b/common/src/main/scala/net/psforever/objects/BoomerDeployable.scala
@@ -1,9 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects
-import net.psforever.objects.definition.DeployableDefinition
-
-class BoomerDeployable(cdef : DeployableDefinition) extends ExplosiveDeployable(cdef) {
+class BoomerDeployable(cdef : ExplosiveDeployableDefinition) extends ExplosiveDeployable(cdef) {
private var trigger : Option[BoomerTrigger] = None
def Trigger : Option[BoomerTrigger] = trigger
diff --git a/common/src/main/scala/net/psforever/objects/Deployables.scala b/common/src/main/scala/net/psforever/objects/Deployables.scala
index de1c9ce0..3c0af9b0 100644
--- a/common/src/main/scala/net/psforever/objects/Deployables.scala
+++ b/common/src/main/scala/net/psforever/objects/Deployables.scala
@@ -2,6 +2,12 @@
package net.psforever.objects
import net.psforever.objects.ce.{Deployable, DeployedItem}
+import net.psforever.objects.serverobject.PlanetSideServerObject
+import net.psforever.packet.game.{DeployableInfo, DeploymentAction, PlanetSideGUID}
+import services.RemoverActor
+import services.local.{LocalAction, LocalServiceMessage}
+
+import scala.concurrent.duration.FiniteDuration
object Deployables {
object Make {
@@ -26,4 +32,43 @@ object Deployables {
DeployedItem.router_telepad_deployable -> { () => new TelepadDeployable(GlobalDefinitions.router_telepad_deployable) }
).withDefaultValue( { ()=> new ExplosiveDeployable(GlobalDefinitions.boomer) } )
}
+
+ /**
+ * Distribute information that a deployable has been destroyed.
+ * The deployable may not have yet been eliminated from the game world (client or server),
+ * but its health is zero and it has entered the conditions where it is nearly irrelevant.
+ *
+ * The typical use case of this function involves destruction via weapon fire, attributed to a particular player.
+ * Contrast this to simply destroying a deployable by being the deployable's owner and using the map icon controls.
+ * This function eventually invokes the same routine
+ * but mainly goes into effect when the deployable has been destroyed
+ * and may still leave a physical component in the game world to be cleaned up later.
+ * That is the task `EliminateDeployable` performs.
+ * Additionally, since the player who destroyed the deployable isn't necessarily the owner,
+ * and the real owner will still be aware of the existence of the deployable,
+ * that player must be informed of the loss of the deployable directly.
+ * @see `DeployableRemover`
+ * @see `Vitality.DamageResolution`
+ * @see `LocalResponse.EliminateDeployable`
+ * @see `DeconstructDeployable`
+ * @param target the deployable that is destroyed
+ * @param time length of time that the deployable is allowed to exist in the game world;
+ * `None` indicates the normal un-owned existence time (180 seconds)
+ */
+ def AnnounceDestroyDeployable(target : PlanetSideServerObject with Deployable, time : Option[FiniteDuration]) : Unit = {
+ val zone = target.Zone
+ target.OwnerName match {
+ case Some(owner) =>
+ target.OwnerName = None
+ zone.LocalEvents ! LocalServiceMessage(owner, LocalAction.AlertDestroyDeployable(PlanetSideGUID(0), target))
+ case None => ;
+ }
+ zone.LocalEvents ! LocalServiceMessage(s"${target.Faction}", LocalAction.DeployableMapIcon(
+ PlanetSideGUID(0),
+ DeploymentAction.Dismiss,
+ DeployableInfo(target.GUID, Deployable.Icon(target.Definition.Item), target.Position, PlanetSideGUID(0)))
+ )
+ zone.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.ClearSpecific(List(target), zone))
+ zone.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.AddTask(target, zone, time))
+ }
}
diff --git a/common/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala b/common/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala
index 9d03bf74..75482ecb 100644
--- a/common/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala
+++ b/common/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala
@@ -1,10 +1,23 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects
-import net.psforever.objects.ce.SimpleDeployable
-import net.psforever.objects.definition.DeployableDefinition
+import akka.actor.{Actor, ActorContext, Props}
+import net.psforever.objects.ballistics.ResolvedProjectile
+import net.psforever.objects.ce._
+import net.psforever.objects.definition.{ComplexDeployableDefinition, SimpleDeployableDefinition}
+import net.psforever.objects.definition.converter.SmallDeployableConverter
+import net.psforever.objects.equipment.JammableUnit
+import net.psforever.objects.serverobject.PlanetSideServerObject
+import net.psforever.objects.vital.{StandardResolutions, Vitality}
+import net.psforever.packet.game.PlanetSideGUID
+import net.psforever.types.Vector3
+import services.avatar.{AvatarAction, AvatarServiceMessage}
+import services.local.{LocalAction, LocalServiceMessage}
-class ExplosiveDeployable(cdef : DeployableDefinition) extends SimpleDeployable(cdef) {
+import scala.concurrent.duration._
+
+class ExplosiveDeployable(cdef : ExplosiveDeployableDefinition) extends ComplexDeployable(cdef)
+ with JammableUnit {
private var exploded : Boolean = false
def Exploded : Boolean = exploded
@@ -13,4 +26,86 @@ class ExplosiveDeployable(cdef : DeployableDefinition) extends SimpleDeployable(
exploded = fuse
Exploded
}
+
+ override def Definition : ExplosiveDeployableDefinition = cdef
}
+
+class ExplosiveDeployableDefinition(private val objectId : Int) extends ComplexDeployableDefinition(objectId) {
+ Name = "explosive_deployable"
+ DeployCategory = DeployableCategory.Mines
+ Model = StandardResolutions.SimpleDeployables
+ Packet = new SmallDeployableConverter
+
+ private var detonateOnJamming : Boolean = true
+
+ def DetonateOnJamming : Boolean = detonateOnJamming
+
+ def DetonateOnJamming_=(detonate : Boolean) : Boolean = {
+ detonateOnJamming = detonate
+ DetonateOnJamming
+ }
+
+ override def Initialize(obj : PlanetSideServerObject with Deployable, context : ActorContext) = {
+ obj.Actor = context.actorOf(Props(classOf[ExplosiveDeployableControl], obj), s"${obj.Definition.Name}_${obj.GUID.guid}")
+ }
+
+ override def Uninitialize(obj : PlanetSideServerObject with Deployable, context : ActorContext) = {
+ SimpleDeployableDefinition.SimpleUninitialize(obj, context)
+ }
+}
+
+object ExplosiveDeployableDefinition {
+ def apply(dtype : DeployedItem.Value) : ExplosiveDeployableDefinition = {
+ new ExplosiveDeployableDefinition(dtype.id)
+ }
+}
+
+class ExplosiveDeployableControl(mine : ExplosiveDeployable) extends Actor {
+ def receive : Receive = {
+ case Vitality.Damage(damage_func) =>
+ val originalHealth = mine.Health
+ if(originalHealth > 0) {
+ val cause = damage_func(mine)
+ ExplosiveDeployableControl.HandleDamageResolution(mine, cause, originalHealth - mine.Health)
+ }
+
+ case _ => ;
+ }
+}
+
+object ExplosiveDeployableControl {
+ def HandleDamageResolution(target : ExplosiveDeployable, cause : ResolvedProjectile, damage : Int) : Unit = {
+ val zone = target.Zone
+ val playerGUID = zone.LivePlayers.find { p => cause.projectile.owner.Name.equals(p.Name) } match {
+ case Some(player) => player.GUID
+ case _ => PlanetSideGUID(0)
+ }
+ if(target.Health == 0) {
+ HandleDestructionAwareness(target, playerGUID, cause)
+ }
+ else if(!target.Jammed && cause.projectile.profile.JammerProjectile) {
+ if(target.Jammed = {
+ val radius = cause.projectile.profile.DamageRadius
+ Vector3.DistanceSquared(cause.hit_pos, cause.target.Position) < radius * radius
+ }) {
+ if(target.Definition.DetonateOnJamming) {
+ target.Zone.LocalEvents ! LocalServiceMessage(target.Zone.Id, LocalAction.Detonate(target.GUID, target))
+ }
+ HandleDestructionAwareness(target, playerGUID, cause)
+ }
+ }
+ }
+
+ /**
+ * na
+ * @param target na
+ * @param attribution na
+ * @param lastShot na
+ */
+ def HandleDestructionAwareness(target : ExplosiveDeployable, attribution : PlanetSideGUID, lastShot : ResolvedProjectile) : Unit = {
+ val zone = target.Zone
+ Deployables.AnnounceDestroyDeployable(target, Some(if(target.Jammed) 0 seconds else 500 milliseconds))
+ zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.Destroy(target.GUID, attribution, attribution, target.Position))
+ }
+}
+
diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
index c937e7c9..a77ec812 100644
--- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
+++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
@@ -17,7 +17,7 @@ import net.psforever.objects.serverobject.terminals._
import net.psforever.objects.serverobject.tube.SpawnTubeDefinition
import net.psforever.objects.serverobject.resourcesilo.ResourceSiloDefinition
import net.psforever.objects.serverobject.structures.SphereOfInfluence
-import net.psforever.objects.serverobject.turret.{TurretDefinition, TurretUpgrade}
+import net.psforever.objects.serverobject.turret.{FacilityTurretDefinition, TurretUpgrade}
import net.psforever.objects.vehicles.{DestroyedVehicle, SeatArmorRestriction, UtilityType}
import net.psforever.objects.vital.{DamageType, StandardMaxDamage, StandardResolutions}
import net.psforever.types.{CertificationType, ExoSuitType, PlanetSideEmpire, Vector3}
@@ -870,11 +870,11 @@ object GlobalDefinitions {
/*
combat engineering deployables
*/
- val boomer = DeployableDefinition(DeployedItem.boomer)
+ val boomer = ExplosiveDeployableDefinition(DeployedItem.boomer)
- val he_mine = DeployableDefinition(DeployedItem.he_mine)
+ val he_mine = ExplosiveDeployableDefinition(DeployedItem.he_mine)
- val jammer_mine = DeployableDefinition(DeployedItem.jammer_mine)
+ val jammer_mine = ExplosiveDeployableDefinition(DeployedItem.jammer_mine)
val spitfire_turret = TurretDeployableDefinition(DeployedItem.spitfire_turret)
@@ -882,11 +882,11 @@ object GlobalDefinitions {
val spitfire_aa = TurretDeployableDefinition(DeployedItem.spitfire_aa)
- val motionalarmsensor = DeployableDefinition(DeployedItem.motionalarmsensor)
+ val motionalarmsensor = SensorDeployableDefinition(DeployedItem.motionalarmsensor)
- val sensor_shield = DeployableDefinition(DeployedItem.sensor_shield)
+ val sensor_shield = SensorDeployableDefinition(DeployedItem.sensor_shield)
- val tank_traps = DeployableDefinition(DeployedItem.tank_traps)
+ val tank_traps = SimpleDeployableDefinition(DeployedItem.tank_traps)
val portable_manned_turret = TurretDeployableDefinition(DeployedItem.portable_manned_turret)
@@ -898,10 +898,10 @@ object GlobalDefinitions {
val deployable_shield_generator = new ShieldGeneratorDefinition
- val router_telepad_deployable = DeployableDefinition(DeployedItem.router_telepad_deployable)
+ val router_telepad_deployable = SimpleDeployableDefinition(DeployedItem.router_telepad_deployable)
//this is only treated like a deployable
- val internal_router_telepad_deployable = DeployableDefinition(DeployedItem.router_telepad_deployable)
+ val internal_router_telepad_deployable = SimpleDeployableDefinition(DeployedItem.router_telepad_deployable)
init_deployables()
/*
@@ -989,7 +989,7 @@ object GlobalDefinitions {
val ground_rearm_terminal = new OrderTerminalDefinition(384)
- val manned_turret = new TurretDefinition(480)
+ val manned_turret = new FacilityTurretDefinition(480)
val painbox = new PainboxDefinition(622)
val painbox_continuous = new PainboxDefinition(623)
@@ -2686,6 +2686,14 @@ object GlobalDefinitions {
jammer_cartridge_projectile.ProjectileDamageType = DamageType.Splash
jammer_cartridge_projectile.InitialVelocity = 30
jammer_cartridge_projectile.Lifespan = 15f
+ jammer_cartridge_projectile.AdditionalEffect = true
+ jammer_cartridge_projectile.JammerProjectile = true
+ jammer_cartridge_projectile.JammedEffectDuration += TargetValidation(EffectTarget.Category.Player, EffectTarget.Validation.Player) -> 1000
+ jammer_cartridge_projectile.JammedEffectDuration += TargetValidation(EffectTarget.Category.Vehicle, EffectTarget.Validation.AMS) -> 5000
+ jammer_cartridge_projectile.JammedEffectDuration += TargetValidation(EffectTarget.Category.Deployable, EffectTarget.Validation.MotionSensor) -> 30000
+ jammer_cartridge_projectile.JammedEffectDuration += TargetValidation(EffectTarget.Category.Deployable, EffectTarget.Validation.Spitfire) -> 30000
+ jammer_cartridge_projectile.JammedEffectDuration += TargetValidation(EffectTarget.Category.Turret, EffectTarget.Validation.Turret) -> 30000
+ jammer_cartridge_projectile.JammedEffectDuration += TargetValidation(EffectTarget.Category.Vehicle, EffectTarget.Validation.VehicleNotAMS) -> 10000
ProjectileDefinition.CalculateDerivedFields(jammer_cartridge_projectile)
jammer_cartridge_projectile_b.Name = "jammer_cartridge_projectile_b"
@@ -2697,6 +2705,14 @@ object GlobalDefinitions {
jammer_cartridge_projectile_b.ProjectileDamageType = DamageType.Splash
jammer_cartridge_projectile_b.InitialVelocity = 30
jammer_cartridge_projectile_b.Lifespan = 2f
+ jammer_cartridge_projectile_b.AdditionalEffect = true
+ jammer_cartridge_projectile_b.JammerProjectile = true
+ jammer_cartridge_projectile_b.JammedEffectDuration += TargetValidation(EffectTarget.Category.Player, EffectTarget.Validation.Player) -> 1000
+ jammer_cartridge_projectile_b.JammedEffectDuration += TargetValidation(EffectTarget.Category.Vehicle, EffectTarget.Validation.AMS) -> 5000
+ jammer_cartridge_projectile_b.JammedEffectDuration += TargetValidation(EffectTarget.Category.Deployable, EffectTarget.Validation.MotionSensor) -> 30000
+ jammer_cartridge_projectile_b.JammedEffectDuration += TargetValidation(EffectTarget.Category.Deployable, EffectTarget.Validation.Spitfire) -> 30000
+ jammer_cartridge_projectile_b.JammedEffectDuration += TargetValidation(EffectTarget.Category.Turret, EffectTarget.Validation.Turret) -> 30000
+ jammer_cartridge_projectile_b.JammedEffectDuration += TargetValidation(EffectTarget.Category.Vehicle, EffectTarget.Validation.VehicleNotAMS) -> 10000
ProjectileDefinition.CalculateDerivedFields(jammer_cartridge_projectile_b)
jammer_grenade_projectile.Name = "jammer_grenade_projectile"
@@ -2707,6 +2723,14 @@ object GlobalDefinitions {
jammer_grenade_projectile.ProjectileDamageType = DamageType.Splash
jammer_grenade_projectile.InitialVelocity = 30
jammer_grenade_projectile.Lifespan = 15f
+ jammer_grenade_projectile.AdditionalEffect = true
+ jammer_grenade_projectile.JammerProjectile = true
+ jammer_grenade_projectile.JammedEffectDuration += TargetValidation(EffectTarget.Category.Player, EffectTarget.Validation.Player) -> 1000
+ jammer_grenade_projectile.JammedEffectDuration += TargetValidation(EffectTarget.Category.Vehicle, EffectTarget.Validation.AMS) -> 5000
+ jammer_grenade_projectile.JammedEffectDuration += TargetValidation(EffectTarget.Category.Deployable, EffectTarget.Validation.MotionSensor) -> 30000
+ jammer_grenade_projectile.JammedEffectDuration += TargetValidation(EffectTarget.Category.Deployable, EffectTarget.Validation.Spitfire) -> 30000
+ jammer_grenade_projectile.JammedEffectDuration += TargetValidation(EffectTarget.Category.Turret, EffectTarget.Validation.Turret) -> 30000
+ jammer_grenade_projectile.JammedEffectDuration += TargetValidation(EffectTarget.Category.Vehicle, EffectTarget.Validation.VehicleNotAMS) -> 10000
ProjectileDefinition.CalculateDerivedFields(jammer_grenade_projectile)
jammer_grenade_projectile_enh.Name = "jammer_grenade_projectile_enh"
@@ -2718,6 +2742,14 @@ object GlobalDefinitions {
jammer_grenade_projectile_enh.ProjectileDamageType = DamageType.Splash
jammer_grenade_projectile_enh.InitialVelocity = 30
jammer_grenade_projectile_enh.Lifespan = 3f
+ jammer_grenade_projectile_enh.AdditionalEffect = true
+ jammer_grenade_projectile_enh.JammerProjectile = true
+ jammer_grenade_projectile_enh.JammedEffectDuration += TargetValidation(EffectTarget.Category.Player, EffectTarget.Validation.Player) -> 1000
+ jammer_grenade_projectile_enh.JammedEffectDuration += TargetValidation(EffectTarget.Category.Vehicle, EffectTarget.Validation.AMS) -> 5000
+ jammer_grenade_projectile_enh.JammedEffectDuration += TargetValidation(EffectTarget.Category.Deployable, EffectTarget.Validation.MotionSensor) -> 30000
+ jammer_grenade_projectile_enh.JammedEffectDuration += TargetValidation(EffectTarget.Category.Deployable, EffectTarget.Validation.Spitfire) -> 30000
+ jammer_grenade_projectile_enh.JammedEffectDuration += TargetValidation(EffectTarget.Category.Turret, EffectTarget.Validation.Turret) -> 30000
+ jammer_grenade_projectile_enh.JammedEffectDuration += TargetValidation(EffectTarget.Category.Vehicle, EffectTarget.Validation.VehicleNotAMS) -> 10000
ProjectileDefinition.CalculateDerivedFields(jammer_grenade_projectile_enh)
katana_projectile.Name = "katana_projectile"
@@ -5870,24 +5902,20 @@ object GlobalDefinitions {
boomer.MaxHealth = 100
boomer.DeployCategory = DeployableCategory.Boomers
boomer.DeployTime = Duration.create(1000, "ms")
- boomer.Model = StandardResolutions.SimpleDeployables
he_mine.Name = "he_mine"
he_mine.Descriptor = "Mines"
he_mine.MaxHealth = 100
- he_mine.DeployCategory = DeployableCategory.Mines
he_mine.DeployTime = Duration.create(1000, "ms")
- he_mine.Model = StandardResolutions.SimpleDeployables
jammer_mine.Name = "jammer_mine"
jammer_mine.Descriptor = "JammerMines"
jammer_mine.MaxHealth = 100
- jammer_mine.DeployCategory = DeployableCategory.Mines
jammer_mine.DeployTime = Duration.create(1000, "ms")
- jammer_mine.Model = StandardResolutions.SimpleDeployables
+ jammer_mine.DetonateOnJamming = false
spitfire_turret.Name = "spitfire_turret"
- spitfire_turret.Descriptor= "Spitfires"
+ spitfire_turret.Descriptor = "Spitfires"
spitfire_turret.MaxHealth = 100
spitfire_turret.Weapons += 1 -> new mutable.HashMap()
spitfire_turret.Weapons(1) += TurretUpgrade.None -> spitfire_weapon
@@ -5897,7 +5925,7 @@ object GlobalDefinitions {
spitfire_turret.Model = StandardResolutions.ComplexDeployables
spitfire_cloaked.Name = "spitfire_cloaked"
- spitfire_cloaked.Descriptor= "CloakingSpitfires"
+ spitfire_cloaked.Descriptor = "CloakingSpitfires"
spitfire_cloaked.MaxHealth = 100
spitfire_cloaked.Weapons += 1 -> new mutable.HashMap()
spitfire_cloaked.Weapons(1) += TurretUpgrade.None -> spitfire_weapon
@@ -5907,7 +5935,7 @@ object GlobalDefinitions {
spitfire_cloaked.Model = StandardResolutions.ComplexDeployables
spitfire_aa.Name = "spitfire_aa"
- spitfire_aa.Descriptor= "FlakSpitfires"
+ spitfire_aa.Descriptor = "FlakSpitfires"
spitfire_aa.MaxHealth = 100
spitfire_aa.Weapons += 1 -> new mutable.HashMap()
spitfire_aa.Weapons(1) += TurretUpgrade.None -> spitfire_aa_weapon
@@ -5919,16 +5947,12 @@ object GlobalDefinitions {
motionalarmsensor.Name = "motionalarmsensor"
motionalarmsensor.Descriptor = "MotionSensors"
motionalarmsensor.MaxHealth = 100
- motionalarmsensor.DeployCategory = DeployableCategory.Sensors
motionalarmsensor.DeployTime = Duration.create(1000, "ms")
- motionalarmsensor.Model = StandardResolutions.SimpleDeployables
sensor_shield.Name = "sensor_shield"
sensor_shield.Descriptor = "SensorShields"
sensor_shield.MaxHealth = 100
- sensor_shield.DeployCategory = DeployableCategory.Sensors
sensor_shield.DeployTime = Duration.create(5000, "ms")
- sensor_shield.Model = StandardResolutions.SimpleDeployables
tank_traps.Name = "tank_traps"
tank_traps.Descriptor = "TankTraps"
@@ -6096,62 +6120,62 @@ object GlobalDefinitions {
teleportpad_terminal.Name = "teleportpad_terminal"
teleportpad_terminal.Tab += 0 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.routerTerminal)
- adv_med_terminal.Name = "adv_med_terminal"
- adv_med_terminal.Interval = 500
- adv_med_terminal.HealAmount = 8
- adv_med_terminal.ArmorAmount = 15
- adv_med_terminal.UseRadius = 0.75f
- adv_med_terminal.TargetValidation += ProximityTarget.Player -> ProximityTerminalControl.Validation.Medical
-
- crystals_health_a.Name = "crystals_health_a"
- crystals_health_a.Interval = 500
- crystals_health_a.HealAmount = 4
- crystals_health_a.UseRadius = 5
- crystals_health_a.TargetValidation += ProximityTarget.Player -> ProximityTerminalControl.Validation.HealthCrystal
-
- crystals_health_b.Name = "crystals_health_b"
- crystals_health_b.Interval = 500
- crystals_health_b.HealAmount = 4
- crystals_health_b.UseRadius = 1.3f
- crystals_health_b.TargetValidation += ProximityTarget.Player -> ProximityTerminalControl.Validation.HealthCrystal
-
medical_terminal.Name = "medical_terminal"
medical_terminal.Interval = 500
medical_terminal.HealAmount = 5
medical_terminal.ArmorAmount = 10
medical_terminal.UseRadius = 0.75f
- medical_terminal.TargetValidation += ProximityTarget.Player -> ProximityTerminalControl.Validation.Medical
+ medical_terminal.TargetValidation += EffectTarget.Category.Player -> EffectTarget.Validation.Medical
+
+ adv_med_terminal.Name = "adv_med_terminal"
+ adv_med_terminal.Interval = 500
+ adv_med_terminal.HealAmount = 8
+ adv_med_terminal.ArmorAmount = 15
+ adv_med_terminal.UseRadius = 0.75f
+ adv_med_terminal.TargetValidation += EffectTarget.Category.Player -> EffectTarget.Validation.Medical
+
+ crystals_health_a.Name = "crystals_health_a"
+ crystals_health_a.Interval = 500
+ crystals_health_a.HealAmount = 4
+ crystals_health_a.UseRadius = 5
+ crystals_health_a.TargetValidation += EffectTarget.Category.Player -> EffectTarget.Validation.HealthCrystal
+
+ crystals_health_b.Name = "crystals_health_b"
+ crystals_health_b.Interval = 500
+ crystals_health_b.HealAmount = 4
+ crystals_health_b.UseRadius = 1.3f
+ crystals_health_b.TargetValidation += EffectTarget.Category.Player -> EffectTarget.Validation.HealthCrystal
portable_med_terminal.Name = "portable_med_terminal"
portable_med_terminal.Interval = 500
portable_med_terminal.HealAmount = 5
portable_med_terminal.ArmorAmount = 10
portable_med_terminal.UseRadius = 3
- portable_med_terminal.TargetValidation += ProximityTarget.Player -> ProximityTerminalControl.Validation.Medical
+ portable_med_terminal.TargetValidation += EffectTarget.Category.Player -> EffectTarget.Validation.Medical
pad_landing_frame.Name = "pad_landing_frame"
pad_landing_frame.Interval = 1000
pad_landing_frame.HealAmount = 60
pad_landing_frame.UseRadius = 20
- pad_landing_frame.TargetValidation += ProximityTarget.Aircraft -> ProximityTerminalControl.Validation.PadLanding
+ pad_landing_frame.TargetValidation += EffectTarget.Category.Aircraft -> EffectTarget.Validation.PadLanding
pad_landing_tower_frame.Name = "pad_landing_tower_frame"
pad_landing_tower_frame.Interval = 1000
pad_landing_tower_frame.HealAmount = 60
pad_landing_tower_frame.UseRadius = 20
- pad_landing_tower_frame.TargetValidation += ProximityTarget.Aircraft -> ProximityTerminalControl.Validation.PadLanding
+ pad_landing_tower_frame.TargetValidation += EffectTarget.Category.Aircraft -> EffectTarget.Validation.PadLanding
repair_silo.Name = "repair_silo"
repair_silo.Interval = 1000
repair_silo.HealAmount = 60
repair_silo.UseRadius = 20
- repair_silo.TargetValidation += ProximityTarget.Vehicle -> ProximityTerminalControl.Validation.RepairSilo
+ repair_silo.TargetValidation += EffectTarget.Category.Vehicle -> EffectTarget.Validation.RepairSilo
lodestar_repair_terminal.Name = "lodestar_repair_terminal"
lodestar_repair_terminal.Interval = 1000
lodestar_repair_terminal.HealAmount = 60
lodestar_repair_terminal.UseRadius = 20
- lodestar_repair_terminal.TargetValidation += ProximityTarget.Vehicle -> ProximityTerminalControl.Validation.RepairSilo
+ lodestar_repair_terminal.TargetValidation += EffectTarget.Category.Vehicle -> EffectTarget.Validation.RepairSilo
multivehicle_rearm_terminal.Name = "multivehicle_rearm_terminal"
multivehicle_rearm_terminal.Tab += 3 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.vehicleAmmunition)
diff --git a/common/src/main/scala/net/psforever/objects/Player.scala b/common/src/main/scala/net/psforever/objects/Player.scala
index a0c84a0b..08e7150a 100644
--- a/common/src/main/scala/net/psforever/objects/Player.scala
+++ b/common/src/main/scala/net/psforever/objects/Player.scala
@@ -3,8 +3,9 @@ package net.psforever.objects
import net.psforever.objects.avatar.LoadoutManager
import net.psforever.objects.definition.{AvatarDefinition, ExoSuitDefinition, SpecialExoSuitDefinition}
-import net.psforever.objects.equipment.{Equipment, EquipmentSize, EquipmentSlot}
+import net.psforever.objects.equipment.{Equipment, EquipmentSize, EquipmentSlot, JammableUnit}
import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem}
+import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.vital.resistance.ResistanceProfile
import net.psforever.objects.vital.{DamageResistanceModel, Vitality}
@@ -16,11 +17,12 @@ import net.psforever.types._
import scala.annotation.tailrec
import scala.util.{Success, Try}
-class Player(private val core : Avatar) extends PlanetSideGameObject
+class Player(private val core : Avatar) extends PlanetSideServerObject
with FactionAffinity
with Vitality
with ResistanceProfile
with Container
+ with JammableUnit
with ZoneAware {
private var alive : Boolean = false
private var backpack : Boolean = false
diff --git a/common/src/main/scala/net/psforever/objects/SensorDeployable.scala b/common/src/main/scala/net/psforever/objects/SensorDeployable.scala
index eccfcc7e..d06688a2 100644
--- a/common/src/main/scala/net/psforever/objects/SensorDeployable.scala
+++ b/common/src/main/scala/net/psforever/objects/SensorDeployable.scala
@@ -1,9 +1,133 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects
-import net.psforever.objects.ce.SimpleDeployable
-import net.psforever.objects.definition.DeployableDefinition
+import akka.actor.{Actor, ActorContext, Props}
+import net.psforever.objects.ballistics.ResolvedProjectile
+import net.psforever.objects.ce._
+import net.psforever.objects.definition.converter.SmallDeployableConverter
+import net.psforever.objects.definition.{ComplexDeployableDefinition, SimpleDeployableDefinition}
+import net.psforever.objects.equipment.{JammableBehavior, JammableUnit}
+import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.hackable.Hackable
+import net.psforever.objects.vital.{StandardResolutions, Vitality}
+import net.psforever.objects.zones.Zone
+import net.psforever.packet.game.PlanetSideGUID
+import services.Service
+import services.avatar.{AvatarAction, AvatarServiceMessage}
+import services.local.{LocalAction, LocalServiceMessage}
+import services.vehicle.{VehicleAction, VehicleServiceMessage}
-class SensorDeployable(cdef : DeployableDefinition) extends SimpleDeployable(cdef)
+import scala.concurrent.duration._
+
+class SensorDeployable(cdef : SensorDeployableDefinition) extends ComplexDeployable(cdef)
with Hackable
+ with JammableUnit
+
+class SensorDeployableDefinition(private val objectId : Int) extends ComplexDeployableDefinition(objectId) {
+ Name = "sensor_deployable"
+ DeployCategory = DeployableCategory.Sensors
+ Model = StandardResolutions.SimpleDeployables
+ Packet = new SmallDeployableConverter
+
+ override def Initialize(obj : PlanetSideServerObject with Deployable, context : ActorContext) = {
+ obj.Actor = context.actorOf(Props(classOf[SensorDeployableControl], obj), s"${obj.Definition.Name}_${obj.GUID.guid}")
+ }
+
+ override def Uninitialize(obj : PlanetSideServerObject with Deployable, context : ActorContext) = {
+ SimpleDeployableDefinition.SimpleUninitialize(obj, context)
+ }
+}
+
+object SensorDeployableDefinition {
+ def apply(dtype : DeployedItem.Value) : SensorDeployableDefinition = {
+ new SensorDeployableDefinition(dtype.id)
+ }
+}
+
+class SensorDeployableControl(sensor : SensorDeployable) extends Actor
+ with JammableBehavior {
+
+ def JammableObject = sensor
+
+ def receive : Receive = jammableBehavior.orElse {
+ case Vitality.Damage(damage_func) =>
+ val originalHealth = sensor.Health
+ if(originalHealth > 0) {
+ val cause = damage_func(sensor)
+ SensorDeployableControl.HandleDamageResolution(sensor, cause, originalHealth - sensor.Health)
+ }
+
+ case _ => ;
+ }
+
+ override def StartJammeredSound(target : Any, dur : Int) : Unit = target match {
+ case obj : PlanetSideServerObject if !jammedSound =>
+ obj.Zone.VehicleEvents ! VehicleServiceMessage(obj.Zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, obj.GUID, 54, 1))
+ super.StartJammeredSound(obj, dur)
+ case _ => ;
+ }
+
+ override def StartJammeredStatus(target : Any, dur : Int) : Unit = target match {
+ case obj : PlanetSideServerObject with JammableUnit if !obj.Jammed =>
+ sensor.Zone.LocalEvents ! LocalServiceMessage(sensor.Zone.Id, LocalAction.TriggerEffectInfo(Service.defaultPlayerGUID, "on", obj.GUID, false, 1000))
+ super.StartJammeredStatus(obj, dur)
+ case _ => ;
+ }
+
+ override def CancelJammeredSound(target : Any) : Unit = {
+ target match {
+ case obj : PlanetSideServerObject if jammedSound =>
+ obj.Zone.VehicleEvents ! VehicleServiceMessage(obj.Zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, obj.GUID, 54, 0))
+ case _ => ;
+ }
+ super.CancelJammeredSound(target)
+ }
+
+ override def CancelJammeredStatus(target : Any) : Unit = {
+ target match {
+ case obj : PlanetSideServerObject with JammableUnit if obj.Jammed =>
+ sensor.Zone.LocalEvents ! LocalServiceMessage(sensor.Zone.Id, LocalAction.TriggerEffectInfo(Service.defaultPlayerGUID, "on", obj.GUID, true, 1000))
+ case _ => ;
+ }
+ super.CancelJammeredStatus(target)
+ }
+}
+
+object SensorDeployableControl {
+ def HandleDamageResolution(target : SensorDeployable, cause : ResolvedProjectile, damage : Int) : Unit = {
+ val zone = target.Zone
+ val targetGUID = target.GUID
+ val playerGUID = zone.LivePlayers.find { p => cause.projectile.owner.Name.equals(p.Name) } match {
+ case Some(player) => player.GUID
+ case _ => PlanetSideGUID(0)
+ }
+ if(target.Health > 0) {
+ //activity on map
+ if(damage > 0) {
+ zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
+ }
+ if(cause.projectile.profile.JammerProjectile) {
+ target.Actor ! JammableUnit.Jammered(cause)
+ }
+ }
+ else {
+ HandleDestructionAwareness(target, playerGUID, cause)
+ }
+ zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.PlanetsideAttribute(targetGUID, 0, target.Health))
+ }
+
+ /**
+ * na
+ * @param target na
+ * @param attribution na
+ * @param lastShot na
+ */
+ def HandleDestructionAwareness(target : SensorDeployable, attribution : PlanetSideGUID, lastShot : ResolvedProjectile) : Unit = {
+ target.Actor ! JammableUnit.ClearJammeredSound()
+ target.Actor ! JammableUnit.ClearJammeredStatus()
+ val zone = target.Zone
+ Deployables.AnnounceDestroyDeployable(target, Some(0 seconds))
+ zone.LocalEvents ! LocalServiceMessage(zone.Id, LocalAction.TriggerEffectInfo(Service.defaultPlayerGUID, "on", target.GUID, false, 1000))
+ zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.Destroy(target.GUID, attribution, attribution, target.Position))
+ }
+}
diff --git a/common/src/main/scala/net/psforever/objects/ShieldGeneratorDeployable.scala b/common/src/main/scala/net/psforever/objects/ShieldGeneratorDeployable.scala
index bbdedd91..8eb16fb5 100644
--- a/common/src/main/scala/net/psforever/objects/ShieldGeneratorDeployable.scala
+++ b/common/src/main/scala/net/psforever/objects/ShieldGeneratorDeployable.scala
@@ -1,15 +1,125 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects
-import net.psforever.objects.ce.{ComplexDeployable, DeployableCategory}
-import net.psforever.objects.definition.DeployableDefinition
+import akka.actor.{Actor, ActorContext, Props}
+import net.psforever.objects.ballistics.ResolvedProjectile
+import net.psforever.objects.ce.{ComplexDeployable, Deployable, DeployableCategory}
+import net.psforever.objects.definition.{ComplexDeployableDefinition, SimpleDeployableDefinition}
import net.psforever.objects.definition.converter.ShieldGeneratorConverter
+import net.psforever.objects.equipment.{JammableBehavior, JammableUnit}
+import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.hackable.Hackable
+import net.psforever.objects.vital.Vitality
+import net.psforever.objects.zones.Zone
+import net.psforever.packet.game.PlanetSideGUID
+import services.avatar.{AvatarAction, AvatarServiceMessage}
+import services.Service
+import services.vehicle.{VehicleAction, VehicleServiceMessage}
class ShieldGeneratorDeployable(cdef : ShieldGeneratorDefinition) extends ComplexDeployable(cdef)
with Hackable
+ with JammableUnit
-class ShieldGeneratorDefinition extends DeployableDefinition(240) {
+class ShieldGeneratorDefinition extends ComplexDeployableDefinition(240) {
Packet = new ShieldGeneratorConverter
DeployCategory = DeployableCategory.ShieldGenerators
+
+ override def Initialize(obj : PlanetSideServerObject with Deployable, context : ActorContext) = {
+ obj.Actor = context.actorOf(Props(classOf[ShieldGeneratorControl], obj), s"${obj.Definition.Name}_${obj.GUID.guid}")
+ }
+
+ override def Uninitialize(obj : PlanetSideServerObject with Deployable, context : ActorContext) = {
+ SimpleDeployableDefinition.SimpleUninitialize(obj, context)
+ }
+}
+
+class ShieldGeneratorControl(gen : ShieldGeneratorDeployable) extends Actor
+ with JammableBehavior {
+
+ def JammableObject = gen
+
+ def receive : Receive = jammableBehavior
+ .orElse {
+ case Vitality.Damage(damage_func) => //note: damage status is reported as vehicle events, not local events
+ if(gen.Health > 0) {
+ val originalHealth = gen.Health
+ val cause = damage_func(gen)
+ val health = gen.Health
+ val damageToHealth = originalHealth - health
+ ShieldGeneratorControl.HandleDamageResolution(gen, cause, damageToHealth)
+ if(damageToHealth > 0) {
+ val name = gen.Actor.toString
+ val slashPoint = name.lastIndexOf("/")
+ org.log4s.getLogger("DamageResolution").info(s"${name.substring(slashPoint + 1, name.length - 1)}: BEFORE=$originalHealth, AFTER=$health, CHANGE=$damageToHealth")
+ }
+ }
+
+ case _ => ;
+ }
+ /*
+ while the shield generator is technically a supported jammable target, how that works is currently unknown
+ electing to use a "status only, no sound" approach by overriding one with an empty function is not entirely arbitrary
+ the superclass of "status" calls also sets the jammed object property
+ */
+ override def StartJammeredSound(target : Any, dur : Int) : Unit = { }
+
+ override def StartJammeredStatus(target : Any, dur : Int) : Unit = target match {
+ case obj : PlanetSideServerObject with JammableUnit if !obj.Jammed =>
+ obj.Zone.VehicleEvents ! VehicleServiceMessage(obj.Zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, obj.GUID, 27, 1))
+ super.StartJammeredStatus(obj, dur)
+ case _ => ;
+ }
+
+ override def CancelJammeredSound(target : Any) : Unit = { }
+
+ override def CancelJammeredStatus(target : Any) : Unit = {
+ target match {
+ case obj : PlanetSideServerObject with JammableUnit if obj.Jammed =>
+ obj.Zone.VehicleEvents ! VehicleServiceMessage(obj.Zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, obj.GUID, 27, 0))
+ case _ => ;
+ }
+ super.CancelJammeredStatus(target)
+ }
+}
+
+object ShieldGeneratorControl {
+ /**
+ * na
+ * @param target na
+ */
+ def HandleDamageResolution(target : ShieldGeneratorDeployable, cause : ResolvedProjectile, damage : Int) : Unit = {
+ val zone = target.Zone
+ val targetGUID = target.GUID
+ val playerGUID = zone.LivePlayers.find { p => cause.projectile.owner.Name.equals(p.Name) } match {
+ case Some(player) => player.GUID
+ case _ => PlanetSideGUID(0)
+ }
+ if(target.Health > 0) {
+ //activity on map
+ if(damage > 0) {
+ zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
+ }
+ if(cause.projectile.profile.JammerProjectile) {
+ target.Actor ! JammableUnit.Jammered(cause)
+ }
+ }
+ else {
+ HandleDestructionAwareness(target, playerGUID, cause)
+ }
+ zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.PlanetsideAttribute(targetGUID, 0, target.Health))
+ }
+
+ /**
+ * na
+ * @param target na
+ * @param attribution na
+ * @param lastShot na
+ */
+ def HandleDestructionAwareness(target : ShieldGeneratorDeployable, attribution : PlanetSideGUID, lastShot : ResolvedProjectile) : Unit = {
+ target.Actor ! JammableUnit.ClearJammeredSound()
+ target.Actor ! JammableUnit.ClearJammeredStatus()
+ val zone = target.Zone
+ Deployables.AnnounceDestroyDeployable(target, None)
+ zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.Destroy(target.GUID, attribution, attribution, target.Position))
+ }
}
diff --git a/common/src/main/scala/net/psforever/objects/TelepadDeployable.scala b/common/src/main/scala/net/psforever/objects/TelepadDeployable.scala
index 3916384b..42774eec 100644
--- a/common/src/main/scala/net/psforever/objects/TelepadDeployable.scala
+++ b/common/src/main/scala/net/psforever/objects/TelepadDeployable.scala
@@ -2,7 +2,7 @@
package net.psforever.objects
import net.psforever.objects.ce.{SimpleDeployable, TelepadLike}
-import net.psforever.objects.definition.DeployableDefinition
+import net.psforever.objects.definition.SimpleDeployableDefinition
-class TelepadDeployable(ddef : DeployableDefinition) extends SimpleDeployable(ddef)
+class TelepadDeployable(ddef : SimpleDeployableDefinition) extends SimpleDeployable(ddef)
with TelepadLike
diff --git a/common/src/main/scala/net/psforever/objects/Tool.scala b/common/src/main/scala/net/psforever/objects/Tool.scala
index 6702808d..e5aec38c 100644
--- a/common/src/main/scala/net/psforever/objects/Tool.scala
+++ b/common/src/main/scala/net/psforever/objects/Tool.scala
@@ -18,7 +18,8 @@ import scala.annotation.tailrec
* @param toolDef the `ObjectDefinition` that constructs this item and maintains some of its immutable fields
*/
class Tool(private val toolDef : ToolDefinition) extends Equipment
- with FireModeSwitch[FireModeDefinition] {
+ with FireModeSwitch[FireModeDefinition]
+ with JammableUnit {
/** index of the current fire mode on the `ToolDefinition`'s list of fire modes */
private var fireModeIndex : Int = toolDef.DefaultFireModeIndex
/** current ammunition slot being used by this fire mode */
diff --git a/common/src/main/scala/net/psforever/objects/TrapDeployable.scala b/common/src/main/scala/net/psforever/objects/TrapDeployable.scala
index 488c4bc6..e330e999 100644
--- a/common/src/main/scala/net/psforever/objects/TrapDeployable.scala
+++ b/common/src/main/scala/net/psforever/objects/TrapDeployable.scala
@@ -2,6 +2,6 @@
package net.psforever.objects
import net.psforever.objects.ce.SimpleDeployable
-import net.psforever.objects.definition.DeployableDefinition
+import net.psforever.objects.definition.SimpleDeployableDefinition
-class TrapDeployable(cdef : DeployableDefinition) extends SimpleDeployable(cdef)
+class TrapDeployable(cdef : SimpleDeployableDefinition) extends SimpleDeployable(cdef)
diff --git a/common/src/main/scala/net/psforever/objects/TurretDeployable.scala b/common/src/main/scala/net/psforever/objects/TurretDeployable.scala
index c72d59cd..46d9601a 100644
--- a/common/src/main/scala/net/psforever/objects/TurretDeployable.scala
+++ b/common/src/main/scala/net/psforever/objects/TurretDeployable.scala
@@ -2,65 +2,58 @@
package net.psforever.objects
import akka.actor.{Actor, ActorContext, Props}
-import net.psforever.objects.ce.{Deployable, DeployedItem}
-import net.psforever.objects.definition.{BaseDeployableDefinition, DeployableDefinition}
+import net.psforever.objects.ballistics.ResolvedProjectile
+import net.psforever.objects.ce.{ComplexDeployable, Deployable, DeployedItem}
+import net.psforever.objects.definition.{ComplexDeployableDefinition, SimpleDeployableDefinition}
import net.psforever.objects.definition.converter.SmallTurretConverter
+import net.psforever.objects.equipment.{JammableMountedWeapons, JammableUnit}
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
import net.psforever.objects.serverobject.hackable.Hackable
import net.psforever.objects.serverobject.mount.MountableBehavior
import net.psforever.objects.serverobject.turret.{TurretDefinition, WeaponTurret}
+import net.psforever.objects.vital.{StandardResolutions, StandardVehicleDamage, StandardVehicleResistance, Vitality}
+import net.psforever.objects.zones.Zone
+import net.psforever.packet.game.PlanetSideGUID
+import services.Service
+import services.avatar.{AvatarAction, AvatarServiceMessage}
+import services.vehicle.{VehicleAction, VehicleServiceMessage}
-class TurretDeployable(tdef : TurretDeployableDefinition) extends PlanetSideServerObject
- with Deployable
+class TurretDeployable(tdef : TurretDeployableDefinition) extends ComplexDeployable(tdef)
with WeaponTurret
+ with JammableUnit
with Hackable {
- private var shields : Int = 0
-
WeaponTurret.LoadDefinition(this) //calls the equivalent of Health = Definition.MaxHealth
- def MaxHealth : Int = Definition.MaxHealth
-
- def Shields : Int = shields
-
- def Shields_=(toShields : Int) : Int = {
- shields = math.min(math.max(0, toShields), MaxShields)
- Shields
- }
-
- def MaxShields : Int = {
- 0//Definition.MaxShields
- }
-
def MountPoints : Map[Int, Int] = Definition.MountPoints.toMap
//override to clarify inheritance conflict
- override def Health : Int = super[Deployable].Health
+ override def Health : Int = super[ComplexDeployable].Health
//override to clarify inheritance conflict
- override def Health_=(toHealth : Int) : Int = super[Deployable].Health_=(toHealth)
+ override def Health_=(toHealth : Int) : Int = super[ComplexDeployable].Health_=(toHealth)
override def Definition = tdef
}
-class TurretDeployableDefinition(private val objectId : Int) extends TurretDefinition(objectId)
- with BaseDeployableDefinition {
- private val item = DeployedItem(objectId) //let throw NoSuchElementException
+class TurretDeployableDefinition(private val objectId : Int) extends ComplexDeployableDefinition(objectId)
+ with TurretDefinition {
Name = "turret_deployable"
Packet = new SmallTurretConverter
-
- def Item : DeployedItem.Value = item
+ Damage = StandardVehicleDamage
+ Resistance = StandardVehicleResistance
+ Model = StandardResolutions.FacilityTurrets
//override to clarify inheritance conflict
- override def MaxHealth : Int = super[BaseDeployableDefinition].MaxHealth
+ override def MaxHealth : Int = super[ComplexDeployableDefinition].MaxHealth
//override to clarify inheritance conflict
- override def MaxHealth_=(max : Int) : Int = super[BaseDeployableDefinition].MaxHealth_=(max)
+ override def MaxHealth_=(max : Int) : Int = super[ComplexDeployableDefinition].MaxHealth_=(max)
override def Initialize(obj : PlanetSideServerObject with Deployable, context : ActorContext) = {
obj.Actor = context.actorOf(Props(classOf[TurretControl], obj), s"${obj.Definition.Name}_${obj.GUID.guid}")
}
override def Uninitialize(obj : PlanetSideServerObject with Deployable, context : ActorContext) = {
- DeployableDefinition.SimpleUninitialize(obj, context)
+ SimpleDeployableDefinition.SimpleUninitialize(obj, context)
}
}
@@ -74,16 +67,115 @@ object TurretDeployableDefinition {
class TurretControl(turret : TurretDeployable) extends Actor
with FactionAffinityBehavior.Check
+ with JammableMountedWeapons //note: jammable status is reported as vehicle events, not local events
with MountableBehavior.Mount
with MountableBehavior.Dismount {
def MountableObject = turret //do not add type!
+ def JammableObject = turret
+
def FactionObject : FactionAffinity = turret
def receive : Receive = checkBehavior
+ .orElse(jammableBehavior)
.orElse(dismountBehavior)
.orElse(turretMountBehavior)
.orElse {
+ case Vitality.Damage(damage_func) => //note: damage status is reported as vehicle events, not local events
+ if(turret.Health > 0) {
+ val originalHealth = turret.Health
+ val cause = damage_func(turret)
+ val health = turret.Health
+ val damageToHealth = originalHealth - health
+ TurretControl.HandleDamageResolution(turret, cause, damageToHealth)
+ if(damageToHealth > 0) {
+ val name = turret.Actor.toString
+ val slashPoint = name.lastIndexOf("/")
+ org.log4s.getLogger("DamageResolution").info(s"${name.substring(slashPoint + 1, name.length - 1)}: BEFORE=$originalHealth, AFTER=$health, CHANGE=$damageToHealth")
+ }
+ }
+
case _ => ;
}
}
+
+object TurretControl {
+ /**
+ * na
+ * @param target na
+ */
+ def HandleDamageResolution(target : TurretDeployable, cause : ResolvedProjectile, damage : Int) : Unit = {
+ val zone = target.Zone
+ val targetGUID = target.GUID
+ val playerGUID = zone.LivePlayers.find { p => cause.projectile.owner.Name.equals(p.Name) } match {
+ case Some(player) => player.GUID
+ case _ => PlanetSideGUID(0)
+ }
+ if(target.Health > 0) {
+ //activity on map
+ if(damage > 0) {
+ zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
+ //alert occupants to damage source
+ HandleDamageAwareness(target, playerGUID, cause)
+ }
+ if(cause.projectile.profile.JammerProjectile) {
+ target.Actor ! JammableUnit.Jammered(cause)
+ }
+ }
+ else {
+ //alert to turret death (hence, occupants' deaths)
+ HandleDestructionAwareness(target, playerGUID, cause)
+ }
+ zone.VehicleEvents ! VehicleServiceMessage(zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, targetGUID, 0, target.Health))
+ }
+
+ /**
+ * na
+ * @param target na
+ * @param attribution na
+ * @param lastShot na
+ */
+ def HandleDamageAwareness(target : TurretDeployable, attribution : PlanetSideGUID, lastShot : ResolvedProjectile) : Unit = {
+ val zone = target.Zone
+ //alert occupants to damage source
+ target.Seats.values.filter(seat => {
+ seat.isOccupied && seat.Occupant.get.isAlive
+ }).foreach(seat => {
+ val tplayer = seat.Occupant.get
+ zone.AvatarEvents ! AvatarServiceMessage(tplayer.Name, AvatarAction.HitHint(attribution, tplayer.GUID))
+ })
+ }
+
+ /**
+ * na
+ * @param target na
+ * @param attribution na
+ * @param lastShot na
+ */
+ def HandleDestructionAwareness(target : TurretDeployable, attribution : PlanetSideGUID, lastShot : ResolvedProjectile) : Unit = {
+ target.Actor ! JammableUnit.ClearJammeredSound()
+ target.Actor ! JammableUnit.ClearJammeredStatus()
+ val zone = target.Zone
+ val continentId = zone.Id
+ //alert to vehicle death (hence, occupants' deaths)
+ target.Seats.values.filter(seat => {
+ seat.isOccupied && seat.Occupant.get.isAlive
+ }).foreach(seat => {
+ val tplayer = seat.Occupant.get
+ val tplayerGUID = tplayer.GUID
+ zone.AvatarEvents ! AvatarServiceMessage(tplayer.Name, AvatarAction.KilledWhileInVehicle(tplayerGUID))
+ zone.AvatarEvents ! AvatarServiceMessage(continentId, AvatarAction.ObjectDelete(tplayerGUID, tplayerGUID)) //dead player still sees self
+ })
+ //vehicle wreckage has no weapons
+ target.Weapons.values
+ .filter {
+ _.Equipment.nonEmpty
+ }
+ .foreach(slot => {
+ val wep = slot.Equipment.get
+ zone.AvatarEvents ! AvatarServiceMessage(continentId, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, wep.GUID))
+ })
+ Deployables.AnnounceDestroyDeployable(target, None)
+ zone.AvatarEvents ! AvatarServiceMessage(continentId, AvatarAction.Destroy(target.GUID, attribution, attribution, target.Position))
+ }
+}
diff --git a/common/src/main/scala/net/psforever/objects/Vehicle.scala b/common/src/main/scala/net/psforever/objects/Vehicle.scala
index 1330af04..a9adc2ba 100644
--- a/common/src/main/scala/net/psforever/objects/Vehicle.scala
+++ b/common/src/main/scala/net/psforever/objects/Vehicle.scala
@@ -3,7 +3,7 @@ package net.psforever.objects
import akka.actor.ActorRef
import net.psforever.objects.definition.VehicleDefinition
-import net.psforever.objects.equipment.{Equipment, EquipmentSize, EquipmentSlot}
+import net.psforever.objects.equipment.{Equipment, EquipmentSize, EquipmentSlot, JammableUnit}
import net.psforever.objects.inventory.{Container, GridInventory, InventoryTile}
import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.serverobject.PlanetSideServerObject
@@ -73,6 +73,7 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends AmenityOwner
with Vitality
with OwnableByPlayer
with StandardResistanceProfile
+ with JammableUnit
with Container {
private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.TR
private var health : Int = 1
diff --git a/common/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala b/common/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala
new file mode 100644
index 00000000..6071b018
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala
@@ -0,0 +1,15 @@
+// Copyright (c) 2019 PSForever
+package net.psforever.objects.avatar
+
+import akka.actor.Actor
+import net.psforever.objects.Player
+
+/**
+ * na;
+ * stub for future development
+ */
+class PlayerControl(player : Player) extends Actor {
+ def receive : Receive = {
+ case _ => ;
+ }
+}
diff --git a/common/src/main/scala/net/psforever/objects/ballistics/SourceEntry.scala b/common/src/main/scala/net/psforever/objects/ballistics/SourceEntry.scala
index bb050e9c..442d1739 100644
--- a/common/src/main/scala/net/psforever/objects/ballistics/SourceEntry.scala
+++ b/common/src/main/scala/net/psforever/objects/ballistics/SourceEntry.scala
@@ -3,7 +3,7 @@ package net.psforever.objects.ballistics
import net.psforever.objects.ce.{ComplexDeployable, SimpleDeployable}
import net.psforever.objects.definition.ObjectDefinition
-import net.psforever.objects.{PlanetSideGameObject, Player, TurretDeployable, Vehicle}
+import net.psforever.objects.{PlanetSideGameObject, Player, Vehicle}
import net.psforever.objects.entity.WorldEntity
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.vital.resistance.ResistanceProfile
@@ -34,7 +34,6 @@ object SourceEntry {
case obj : Player => PlayerSource(obj)
case obj : Vehicle => VehicleSource(obj)
case obj : ComplexDeployable => ComplexDeployableSource(obj)
- case obj : TurretDeployable => ComplexDeployableSource(obj)
case obj : SimpleDeployable => DeployableSource(obj)
case _ => ObjectSource(target)
}
diff --git a/common/src/main/scala/net/psforever/objects/ce/ComplexDeployable.scala b/common/src/main/scala/net/psforever/objects/ce/ComplexDeployable.scala
index 5dec6c65..65665c9e 100644
--- a/common/src/main/scala/net/psforever/objects/ce/ComplexDeployable.scala
+++ b/common/src/main/scala/net/psforever/objects/ce/ComplexDeployable.scala
@@ -1,10 +1,10 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.ce
-import net.psforever.objects.definition.DeployableDefinition
+import net.psforever.objects.definition.ComplexDeployableDefinition
import net.psforever.objects.serverobject.PlanetSideServerObject
-abstract class ComplexDeployable(cdef : DeployableDefinition) extends PlanetSideServerObject
+abstract class ComplexDeployable(cdef : ComplexDeployableDefinition) extends PlanetSideServerObject
with Deployable {
Health = Definition.MaxHealth
diff --git a/common/src/main/scala/net/psforever/objects/ce/Deployable.scala b/common/src/main/scala/net/psforever/objects/ce/Deployable.scala
index 799751cf..b552874e 100644
--- a/common/src/main/scala/net/psforever/objects/ce/Deployable.scala
+++ b/common/src/main/scala/net/psforever/objects/ce/Deployable.scala
@@ -5,12 +5,14 @@ import net.psforever.objects._
import net.psforever.objects.definition.{BaseDeployableDefinition, ObjectDefinition}
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.vital.{DamageResistanceModel, Vitality}
-import net.psforever.packet.game.{DeployableIcon, PlanetSideGUID}
+import net.psforever.objects.zones.ZoneAware
+import net.psforever.packet.game.DeployableIcon
import net.psforever.types.PlanetSideEmpire
trait Deployable extends FactionAffinity
with Vitality
- with OwnableByPlayer {
+ with OwnableByPlayer
+ with ZoneAware {
this : PlanetSideGameObject =>
private var health : Int = 1
private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL
diff --git a/common/src/main/scala/net/psforever/objects/ce/SimpleDeployable.scala b/common/src/main/scala/net/psforever/objects/ce/SimpleDeployable.scala
index 38200737..76362ea9 100644
--- a/common/src/main/scala/net/psforever/objects/ce/SimpleDeployable.scala
+++ b/common/src/main/scala/net/psforever/objects/ce/SimpleDeployable.scala
@@ -2,9 +2,9 @@
package net.psforever.objects.ce
import net.psforever.objects.PlanetSideGameObject
-import net.psforever.objects.definition.DeployableDefinition
+import net.psforever.objects.definition.SimpleDeployableDefinition
-abstract class SimpleDeployable(cdef : DeployableDefinition) extends PlanetSideGameObject
+abstract class SimpleDeployable(cdef : SimpleDeployableDefinition) extends PlanetSideGameObject
with Deployable {
Health = Definition.MaxHealth
diff --git a/common/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala b/common/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala
index e1069612..2b4ab83c 100644
--- a/common/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala
+++ b/common/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala
@@ -2,6 +2,7 @@
package net.psforever.objects.definition
import net.psforever.objects.ballistics.Projectiles
+import net.psforever.objects.equipment.JammingUnit
import net.psforever.objects.vital.{DamageType, StandardDamageProfile}
/**
@@ -10,6 +11,7 @@ import net.psforever.objects.vital.{DamageType, StandardDamageProfile}
* @param objectId the object's identifier number
*/
class ProjectileDefinition(objectId : Int) extends ObjectDefinition(objectId)
+ with JammingUnit
with StandardDamageProfile {
private val projectileType : Projectiles.Value = Projectiles(objectId) //let throw NoSuchElementException
private var acceleration : Int = 0
@@ -26,6 +28,8 @@ class ProjectileDefinition(objectId : Int) extends ObjectDefinition(objectId)
private var existsOnRemoteClients : Boolean = false //`true` spawns a server-managed object
private var remoteClientData : (Int, Int) = (0, 0) //artificial values; for ObjectCreateMessage packet (oicw_little_buddy is undefined)
private var autoLock : Boolean = false
+ private var additionalEffect : Boolean = false
+ private var jammerProjectile : Boolean = false
//derived calculations
private var distanceMax : Float = 0f
private var distanceFromAcceleration : Float = 0f
@@ -133,6 +137,20 @@ class ProjectileDefinition(objectId : Int) extends ObjectDefinition(objectId)
AutoLock
}
+ def AdditionalEffect : Boolean = additionalEffect
+
+ def AdditionalEffect_=(effect : Boolean) : Boolean = {
+ additionalEffect = effect
+ AdditionalEffect
+ }
+
+ def JammerProjectile : Boolean = jammerProjectile
+
+ def JammerProjectile_=(effect : Boolean) : Boolean = {
+ jammerProjectile = effect
+ JammerProjectile
+ }
+
def DistanceMax : Float = distanceMax //accessor only
def DistanceFromAcceleration : Float = distanceFromAcceleration //accessor only
diff --git a/common/src/main/scala/net/psforever/objects/definition/DeployableDefinition.scala b/common/src/main/scala/net/psforever/objects/definition/SimpleDeployableDefinition.scala
similarity index 80%
rename from common/src/main/scala/net/psforever/objects/definition/DeployableDefinition.scala
rename to common/src/main/scala/net/psforever/objects/definition/SimpleDeployableDefinition.scala
index d3c1b7c2..fc9e0cbb 100644
--- a/common/src/main/scala/net/psforever/objects/definition/DeployableDefinition.scala
+++ b/common/src/main/scala/net/psforever/objects/definition/SimpleDeployableDefinition.scala
@@ -7,7 +7,7 @@ import net.psforever.objects.ce.{Deployable, DeployableCategory, DeployedItem}
import net.psforever.objects.definition.converter.SmallDeployableConverter
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.vital.resistance.ResistanceProfileMutators
-import net.psforever.objects.vital.{DamageResistanceModel, NoResistanceSelection, StandardDeployableDamage, StandardResistanceProfile}
+import net.psforever.objects.vital.{DamageResistanceModel, NoResistanceSelection, StandardDeployableDamage}
import scala.concurrent.duration._
@@ -53,7 +53,7 @@ trait BaseDeployableDefinition extends DamageResistanceModel
def Uninitialize(obj : PlanetSideServerObject with Deployable, context : ActorContext) : Unit = { }
}
-class DeployableDefinition(private val objectId : Int) extends ObjectDefinition(objectId)
+class SimpleDeployableDefinition(private val objectId : Int) extends ObjectDefinition(objectId)
with BaseDeployableDefinition {
private val item = DeployedItem(objectId) //let throw NoSuchElementException
Packet = new SmallDeployableConverter
@@ -61,9 +61,16 @@ class DeployableDefinition(private val objectId : Int) extends ObjectDefinition(
def Item : DeployedItem.Value = item
}
-object DeployableDefinition {
- def apply(item : DeployedItem.Value) : DeployableDefinition =
- new DeployableDefinition(item.id)
+abstract class ComplexDeployableDefinition(private val objectId : Int) extends ObjectDefinition(objectId)
+ with BaseDeployableDefinition {
+ private val item = DeployedItem(objectId) //let throw NoSuchElementException
+
+ def Item : DeployedItem.Value = item
+}
+
+object SimpleDeployableDefinition {
+ def apply(item : DeployedItem.Value) : SimpleDeployableDefinition =
+ new SimpleDeployableDefinition(item.id)
def SimpleUninitialize(obj : PlanetSideGameObject, context : ActorContext) : Unit = { }
diff --git a/common/src/main/scala/net/psforever/objects/definition/converter/AvatarConverter.scala b/common/src/main/scala/net/psforever/objects/definition/converter/AvatarConverter.scala
index 4b7a217f..8ed7f5f2 100644
--- a/common/src/main/scala/net/psforever/objects/definition/converter/AvatarConverter.scala
+++ b/common/src/main/scala/net/psforever/objects/definition/converter/AvatarConverter.scala
@@ -74,7 +74,7 @@ object AvatarConverter {
alt_model_flag,
false,
None,
- false,
+ obj.Jammed,
None,
v5 = None,
PlanetSideGUID(0)
diff --git a/common/src/main/scala/net/psforever/objects/definition/converter/CharacterSelectConverter.scala b/common/src/main/scala/net/psforever/objects/definition/converter/CharacterSelectConverter.scala
index 7a4414a5..7d716e56 100644
--- a/common/src/main/scala/net/psforever/objects/definition/converter/CharacterSelectConverter.scala
+++ b/common/src/main/scala/net/psforever/objects/definition/converter/CharacterSelectConverter.scala
@@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.definition.converter
-import net.psforever.objects.Player
+import net.psforever.objects.{Player, Tool}
import net.psforever.objects.equipment.{Equipment, EquipmentSlot}
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.packet.game.objectcreate._
@@ -136,17 +136,38 @@ class CharacterSelectConverter extends AvatarConverter {
}
else {
val slot : EquipmentSlot = iter.next
- if(slot.Equipment.isDefined) {
- val equip : Equipment = slot.Equipment.get
- recursiveMakeHolsters(
- iter,
- list :+ AvatarConverter.BuildDetailedEquipment(index, equip),
- index + 1
- )
- }
- else {
- recursiveMakeHolsters(iter, list, index + 1)
+ slot.Equipment match {
+ case Some(equip : Tool) =>
+ val jammed = equip.Jammed
+ equip.Jammed = false
+ val slot = AvatarConverter.BuildDetailedEquipment(index, equip)
+ equip.Jammed = jammed
+ recursiveMakeHolsters(
+ iter,
+ list :+ slot,
+ index + 1
+ )
+ case Some(equip) =>
+ recursiveMakeHolsters(
+ iter,
+ list :+ AvatarConverter.BuildDetailedEquipment(index, equip),
+ index + 1
+ )
+ case _ =>
+ recursiveMakeHolsters(iter, list, index + 1)
}
+// if(slot.Equipment.isDefined) {
+//
+// val equip : Equipment = slot.Equipment.get
+// recursiveMakeHolsters(
+// iter,
+// list :+ AvatarConverter.BuildDetailedEquipment(index, equip),
+// index + 1
+// )
+// }
+// else {
+// recursiveMakeHolsters(iter, list, index + 1)
+// }
}
}
}
diff --git a/common/src/main/scala/net/psforever/objects/definition/converter/FieldTurretConverter.scala b/common/src/main/scala/net/psforever/objects/definition/converter/FieldTurretConverter.scala
index 9ced731e..63b0e21a 100644
--- a/common/src/main/scala/net/psforever/objects/definition/converter/FieldTurretConverter.scala
+++ b/common/src/main/scala/net/psforever/objects/definition/converter/FieldTurretConverter.scala
@@ -23,7 +23,7 @@ class FieldTurretConverter extends ObjectCreateConverter[TurretDeployable]() {
alternate = false,
true,
None,
- false,
+ jammered = obj.Jammed,
Some(false),
None,
obj.Owner match {
diff --git a/common/src/main/scala/net/psforever/objects/definition/converter/ShieldGeneratorConverter.scala b/common/src/main/scala/net/psforever/objects/definition/converter/ShieldGeneratorConverter.scala
index 75b77a9c..6e50230f 100644
--- a/common/src/main/scala/net/psforever/objects/definition/converter/ShieldGeneratorConverter.scala
+++ b/common/src/main/scala/net/psforever/objects/definition/converter/ShieldGeneratorConverter.scala
@@ -21,7 +21,7 @@ class ShieldGeneratorConverter extends ObjectCreateConverter[ShieldGeneratorDepl
alternate = false,
v1 = false,
v2 = None,
- v3 = false,
+ jammered = obj.Jammed,
None,
None,
obj.Owner match {
@@ -45,7 +45,7 @@ class ShieldGeneratorConverter extends ObjectCreateConverter[ShieldGeneratorDepl
alternate = true,
v1 = false,
v2 = None,
- v3 = false,
+ jammered = obj.Jammed,
None,
None,
PlanetSideGUID(0)
diff --git a/common/src/main/scala/net/psforever/objects/definition/converter/SmallDeployableConverter.scala b/common/src/main/scala/net/psforever/objects/definition/converter/SmallDeployableConverter.scala
index 7b10159b..a9175aab 100644
--- a/common/src/main/scala/net/psforever/objects/definition/converter/SmallDeployableConverter.scala
+++ b/common/src/main/scala/net/psforever/objects/definition/converter/SmallDeployableConverter.scala
@@ -3,6 +3,7 @@ package net.psforever.objects.definition.converter
import net.psforever.objects.ce.Deployable
import net.psforever.objects.PlanetSideGameObject
+import net.psforever.objects.equipment.JammableUnit
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.packet.game.objectcreate._
@@ -15,11 +16,14 @@ class SmallDeployableConverter extends ObjectCreateConverter[PlanetSideGameObjec
PlacementData(obj.Position, obj.Orientation),
CommonFieldData(
obj.Faction,
- false,
- false,
+ bops = false,
+ alternate = false,
false,
None,
- false,
+ jammered = obj match {
+ case o : JammableUnit => o.Jammed
+ case _ => false
+ },
Some(false),
None,
obj.Owner match {
@@ -33,4 +37,4 @@ class SmallDeployableConverter extends ObjectCreateConverter[PlanetSideGameObjec
override def DetailedConstructorData(obj : PlanetSideGameObject with Deployable) : Try[CommonFieldDataWithPlacement] =
Failure(new Exception("converter should not be used to generate detailed small deployable data"))
-}
\ No newline at end of file
+}
diff --git a/common/src/main/scala/net/psforever/objects/definition/converter/SmallTurretConverter.scala b/common/src/main/scala/net/psforever/objects/definition/converter/SmallTurretConverter.scala
index ec01eccc..d740e62a 100644
--- a/common/src/main/scala/net/psforever/objects/definition/converter/SmallTurretConverter.scala
+++ b/common/src/main/scala/net/psforever/objects/definition/converter/SmallTurretConverter.scala
@@ -23,7 +23,7 @@ class SmallTurretConverter extends ObjectCreateConverter[TurretDeployable]() {
alternate = false,
false,
None,
- false,
+ jammered = obj.Jammed,
Some(true),
None,
obj.Owner match {
diff --git a/common/src/main/scala/net/psforever/objects/definition/converter/ToolConverter.scala b/common/src/main/scala/net/psforever/objects/definition/converter/ToolConverter.scala
index a7886d44..a51804be 100644
--- a/common/src/main/scala/net/psforever/objects/definition/converter/ToolConverter.scala
+++ b/common/src/main/scala/net/psforever/objects/definition/converter/ToolConverter.scala
@@ -21,7 +21,7 @@ class ToolConverter extends ObjectCreateConverter[Tool]() {
alternate = false,
true,
None,
- false,
+ obj.Jammed,
None,
None,
PlanetSideGUID(0)
@@ -45,7 +45,7 @@ class ToolConverter extends ObjectCreateConverter[Tool]() {
alternate = false,
true,
None,
- false,
+ obj.Jammed,
None,
None,
PlanetSideGUID(0)
diff --git a/common/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala b/common/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala
index e0eb5461..2444bef4 100644
--- a/common/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala
+++ b/common/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala
@@ -25,7 +25,7 @@ class VehicleConverter extends ObjectCreateConverter[Vehicle]() {
alternate = false,
v1 = false,
v2 = None,
- v3 = false,
+ jammered = obj.Jammed,
v4 = Some(false),
v5 = None,
obj.Owner match {
@@ -56,7 +56,7 @@ class VehicleConverter extends ObjectCreateConverter[Vehicle]() {
alternate = true,
v1 = false,
v2 = None,
- v3 = false,
+ jammered = obj.Jammed,
v4 = Some(false),
v5 = None,
guid = PlanetSideGUID(0)
diff --git a/common/src/main/scala/net/psforever/objects/equipment/EffectTarget.scala b/common/src/main/scala/net/psforever/objects/equipment/EffectTarget.scala
new file mode 100644
index 00000000..8871250a
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/equipment/EffectTarget.scala
@@ -0,0 +1,103 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.objects.equipment
+
+import net.psforever.objects._
+import net.psforever.objects.ce.DeployableCategory
+import net.psforever.objects.serverobject.turret.FacilityTurret
+
+final case class TargetValidation(category : EffectTarget.Category.Value, test : EffectTarget.Validation.Value)
+
+object EffectTarget {
+ /**
+ * A classification of the target of interactions.
+ * Arbitrary, but useful.
+ */
+ object Category extends Enumeration {
+ val
+ Aircraft,
+ Deployable,
+ Equipment,
+ Player,
+ Turret,
+ Vehicle
+ = Value
+ }
+
+ object Validation {
+ type Value = PlanetSideGameObject => Boolean
+
+ def Invalid(target : PlanetSideGameObject) : Boolean = false
+
+ def Medical(target : PlanetSideGameObject) : Boolean = target match {
+ case p : Player =>
+ p.Health > 0 && (p.Health < p.MaxHealth || p.Armor < p.MaxArmor)
+ case _ =>
+ false
+ }
+
+ def HealthCrystal(target : PlanetSideGameObject) : Boolean = target match {
+ case p : Player =>
+ p.Health > 0 && p.Health < p.MaxHealth
+ case _ =>
+ false
+ }
+
+ def RepairSilo(target : PlanetSideGameObject) : Boolean = target match {
+ case v : Vehicle =>
+ !GlobalDefinitions.isFlightVehicle(v.Definition) && v.Health > 0 && v.Health < v.MaxHealth
+ case _ =>
+ false
+ }
+
+ def PadLanding(target : PlanetSideGameObject) : Boolean = target match {
+ case v : Vehicle =>
+ GlobalDefinitions.isFlightVehicle(v.Definition) && v.Health > 0 && v.Health < v.MaxHealth
+ case _ =>
+ false
+ }
+
+ def Player(target : PlanetSideGameObject) : Boolean = target match {
+ case p : Player =>
+ p.isAlive
+ case _ =>
+ false
+ }
+
+ def MotionSensor(target : PlanetSideGameObject) : Boolean = target match {
+ case s : SensorDeployable =>
+ s.Health > 0
+ case _ =>
+ false
+ }
+
+ def Spitfire(target : PlanetSideGameObject) : Boolean = target match {
+ case t : TurretDeployable =>
+ t.Definition.DeployCategory == DeployableCategory.SmallTurrets && t.Health > 0
+ case _ =>
+ false
+ }
+
+ def Turret(target : PlanetSideGameObject) : Boolean = target match {
+ case t : TurretDeployable =>
+ t.Definition.DeployCategory == DeployableCategory.FieldTurrets && t.Health > 0
+ case t : FacilityTurret =>
+ t.Health > 0
+ case _ =>
+ false
+ }
+
+ def AMS(target : PlanetSideGameObject) : Boolean = target match {
+ case v : Vehicle =>
+ v.Health > 0 && v.Definition == GlobalDefinitions.ams
+ case _ =>
+ false
+ }
+
+ def VehicleNotAMS(target : PlanetSideGameObject) : Boolean = target match {
+ case v : Vehicle =>
+ v.Health > 0 && v.Definition != GlobalDefinitions.ams
+ case _ =>
+ false
+ }
+ }
+}
diff --git a/common/src/main/scala/net/psforever/objects/equipment/JammingUnit.scala b/common/src/main/scala/net/psforever/objects/equipment/JammingUnit.scala
new file mode 100644
index 00000000..874474a0
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/equipment/JammingUnit.scala
@@ -0,0 +1,271 @@
+// Copyright (c) 2019 PSForever
+package net.psforever.objects.equipment
+
+import akka.actor.{Actor, Cancellable}
+import net.psforever.objects.{DefaultCancellable, PlanetSideGameObject, Tool}
+import net.psforever.objects.ballistics.ResolvedProjectile
+import net.psforever.objects.serverobject.PlanetSideServerObject
+import net.psforever.objects.vehicles.MountedWeapons
+import net.psforever.objects.zones.ZoneAware
+import net.psforever.types.Vector3
+import services.Service
+import services.vehicle.{VehicleAction, VehicleServiceMessage}
+
+import scala.collection.mutable
+import scala.concurrent.duration._
+
+/**
+ * A property conferred to game objects that can be affected by an electromagnetic pulse.
+ * Being "jammered" is a status that causes weakness due to temporary equipment disabling or the elimination of certain objects.
+ */
+trait JammableUnit {
+ /** being jammed (jammered) is an on/off state */
+ private var jammed : Boolean = false
+
+ def Jammed : Boolean = jammed
+
+ def Jammed_=(state : Boolean) : Boolean = {
+ jammed = state
+ Jammed
+ }
+}
+
+object JammableUnit {
+ /**
+ * A message for generic jammering.
+ * Currently, unused.
+ */
+ final case class Jammer()
+ /**
+ * A message for jammering due to a projectile.
+ * @param cause information pertaining to the projectile
+ */
+ final case class Jammered(cause : ResolvedProjectile)
+ /**
+ * Stop the auditory aspect of being jammered.
+ */
+ final case class ClearJammeredSound()
+ /**
+ * Stop the status effects of being jammered.
+ */
+ final case class ClearJammeredStatus()
+}
+
+/**
+ * A property conferred onto game objects that can induce the effects of an electromagnetic pulse.
+ * @see `TargetValidation`
+ * @see `EffectTarget`
+ */
+trait JammingUnit {
+ /** a list of qualifying conditional tests for determining if an object is to be affected by the jammered status;
+ * if qualifying, that object will be inflicted with a number of milliseconds of the jammered status */
+ private val jammedEffectDuration : mutable.ListBuffer[(TargetValidation, Int)] = new mutable.ListBuffer()
+
+ def HasJammedEffectDuration : Boolean = jammedEffectDuration.isEmpty
+
+ def JammedEffectDuration : mutable.ListBuffer[(TargetValidation, Int)] = jammedEffectDuration
+}
+
+object JammingUnit {
+ /**
+ * Determine whether an object that can be jammered is to be jammered by this source,
+ * and for how long.
+ * If the object succeeds for multiple qualification tests,
+ * prioritize the lengthiest duration.
+ * @param jammer the source of the "jammered" status
+ * @param target the object to be determined if affected by the source's jammering
+ * @return the duration to be jammered, if any, in milliseconds
+ */
+ def FindJammerDuration(jammer : JammingUnit, target : PlanetSideGameObject) : Option[Int] = {
+ jammer.JammedEffectDuration
+ .collect { case (TargetValidation(_, test), duration) if test(target) => duration }
+ .toList
+ .sortWith(_ > _)
+ .headOption
+ }
+
+ /**
+ * Determine whether a group of objects that can be jammered is to be jammered by this source,
+ * and for how long.
+ * If the object succeeds for multiple qualification tests,
+ * prioritize the lengthiest duration.
+ * @param jammer the source of the "jammered" status
+ * @param targets the objects to be determined if affected by the source's jammering
+ * @return the indexed durations to be jammered, if any, in milliseconds
+ */
+ def FindJammerDuration(jammer : JammingUnit, targets : Seq[PlanetSideGameObject]) : Seq[Option[Int]] = {
+ targets.map { target => FindJammerDuration(jammer, target) }
+ }
+}
+
+/**
+ * An `Actor` control object mix-in that manages common responses to the "jammerable" status.
+ * Two aspects to jammering are supported -
+ * a telling buzzing sound that follows the affected target
+ * and actual effects upon the target's actions -
+ * and are controlled independently.
+ * The primary purpose of this behavior is to control timers that toggle the states of these two aspects.
+ */
+trait JammableBehavior {
+ this : Actor =>
+ /** flag for jammed sound */
+ protected var jammedSound : Boolean = false
+ /** the sound timer */
+ protected var jammeredSoundTimer : Cancellable = DefaultCancellable.obj
+ /** the effect timer */
+ protected var jammeredStatusTimer : Cancellable = DefaultCancellable.obj
+
+ /** `ZoneAware` is used for callback to the event systems */
+ def JammableObject : PlanetSideServerObject with JammableUnit with ZoneAware
+
+ /**
+ * If the target can be validated against, affect it with the jammered status.
+ * @param target the objects to be determined if affected by the source's jammering
+ * @param cause the source of the "jammered" status
+ */
+ def TryJammerEffectActivate(target : Any, cause : ResolvedProjectile) : Unit = target match {
+ case obj : PlanetSideServerObject =>
+ val radius = cause.projectile.profile.DamageRadius
+ JammingUnit.FindJammerDuration(cause.projectile.profile, obj) match {
+ case Some(dur) if Vector3.DistanceSquared(cause.hit_pos, cause.target.Position) < radius * radius =>
+ StartJammeredSound(obj, dur)
+ StartJammeredStatus(obj, dur)
+ case _ => ;
+ }
+ case _ => ;
+ }
+
+ /**
+ * Activate a distinctive buzzing sound effect.
+ * Due to considerations of the object that is the target, this is left to be implemented by a subclass.
+ * We merely start the timer.
+ * @param target an object that can be affected by the jammered status
+ * @param dur the duration of the timer, in milliseconds;
+ * by default, 30000
+ */
+ def StartJammeredSound(target : Any, dur : Int = 30000) : Unit = {
+ if(!jammedSound) {
+ jammedSound = true
+ import scala.concurrent.ExecutionContext.Implicits.global
+ jammeredSoundTimer.cancel
+ jammeredSoundTimer = context.system.scheduler.scheduleOnce(dur seconds, self, JammableUnit.ClearJammeredSound())
+ }
+ }
+
+ /**
+ * Deactivate the effects of the jammered status.
+ * Due to considerations of the object that is the target, this is left to be implemented by a subclass.
+ * We merely stop the timer.
+ * @param target an object that can be affected by the jammered status
+ * @param dur the duration of the timer, in milliseconds
+ */
+ def StartJammeredStatus(target : Any, dur : Int) : Unit = {
+ JammableObject.Jammed = true
+ jammeredStatusTimer.cancel
+ import scala.concurrent.ExecutionContext.Implicits.global
+ jammeredStatusTimer = context.system.scheduler.scheduleOnce(dur milliseconds, self, JammableUnit.ClearJammeredStatus())
+ }
+
+ /**
+ * Deactivate a distinctive buzzing sound effect.
+ * Due to considerations of the object that is the target, this is left to be implemented by a subclass.
+ * We merely stop the timer.
+ * @param target an object that can be affected by the jammered status
+ */
+ def CancelJammeredSound(target : Any) : Unit = {
+ jammedSound = false
+ jammeredSoundTimer.cancel
+ }
+
+ /**
+ * Deactivate the effects of the jammered status.
+ * Due to considerations of the object that is the target, this is left to be implemented by a subclass.
+ * We merely stop the timer.
+ * @param target an object that can be affected by the jammered status
+ */
+ def CancelJammeredStatus(target : Any) : Unit = {
+ JammableObject.Jammed = false
+ jammeredStatusTimer.cancel
+ }
+
+ val jammableBehavior : Receive = {
+ case JammableUnit.Jammered(cause) =>
+ TryJammerEffectActivate(JammableObject, cause)
+
+ case JammableUnit.ClearJammeredSound() =>
+ CancelJammeredSound(JammableObject)
+
+ case JammableUnit.ClearJammeredStatus() =>
+ CancelJammeredStatus(JammableObject)
+ }
+}
+
+/**
+ * A common mix-in variation to manage common responses to the "jammerable" status for game objects with mounted weapons.
+ * @see `MountedWeapons`
+ * @see `Service`
+ * @see `VehicleAction`
+ * @see `VehicleService`
+ * @see `VehicleServiceMessage`
+ * @see `Zone.VehicleEvents`
+ */
+trait JammableMountedWeapons extends JammableBehavior {
+ _ : Actor =>
+
+ override def StartJammeredSound(target : Any, dur : Int) : Unit = {
+ target match {
+ case obj : PlanetSideServerObject with MountedWeapons with JammableUnit if !jammedSound =>
+ obj.Zone.VehicleEvents ! VehicleServiceMessage(obj.Zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, obj.GUID, 27, 1))
+ super.StartJammeredSound(target, dur)
+ case _ => ;
+ }
+ }
+
+ override def StartJammeredStatus(target : Any, dur : Int) : Unit = {
+ target match {
+ case obj : PlanetSideServerObject with MountedWeapons with JammableUnit if !obj.Jammed =>
+ JammableMountedWeapons.JammeredStatus(obj, 1)
+ super.StartJammeredStatus(target, dur)
+ case _ => ;
+ }
+ }
+
+ override def CancelJammeredSound(target : Any) : Unit = {
+ target match {
+ case obj : PlanetSideServerObject if jammedSound =>
+ obj.Zone.VehicleEvents ! VehicleServiceMessage(obj.Zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, obj.GUID, 27, 0))
+ case _ => ;
+ }
+ super.CancelJammeredSound(target)
+ }
+
+ override def CancelJammeredStatus(target : Any) : Unit = {
+ target match {
+ case obj : PlanetSideServerObject with MountedWeapons with JammableUnit if obj.Jammed =>
+ JammableMountedWeapons.JammeredStatus(obj, 0)
+ case _ => ;
+ }
+ super.CancelJammeredStatus(target)
+ }
+}
+
+object JammableMountedWeapons {
+ /**
+ * Retrieve all of the weapons on a `MountedWeapons` target object and apply a jammered status effect to each.
+ * @param target an object that can be affected by the jammered status
+ * @param statusCode the jammered status condition;
+ * 0 for deactivation;
+ * 1 for activation
+ */
+ def JammeredStatus(target : PlanetSideServerObject with MountedWeapons, statusCode : Int) : Unit = {
+ val zone = target.Zone
+ val zoneId = zone.Id
+ target.Weapons.values
+ .map { _.Equipment }
+ .collect {
+ case Some(item : Tool) =>
+ item.Jammed = statusCode==1
+ zone.VehicleEvents ! VehicleServiceMessage(zoneId, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, item.GUID, 27, statusCode))
+ }
+ }
+}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityDefinition.scala
index 575d6e55..e2b9f889 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityDefinition.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityDefinition.scala
@@ -3,6 +3,7 @@ package net.psforever.objects.serverobject.terminals
import net.psforever.objects.PlanetSideGameObject
import net.psforever.objects.definition.ObjectDefinition
+import net.psforever.objects.equipment.EffectTarget
import scala.collection.mutable
@@ -17,7 +18,7 @@ trait ProximityDefinition {
this : ObjectDefinition =>
private var useRadius : Float = 0f //TODO belongs on a wider range of object definitions
- private val targetValidation : mutable.HashMap[ProximityTarget.Value, PlanetSideGameObject=>Boolean] = new mutable.HashMap[ProximityTarget.Value, PlanetSideGameObject=>Boolean]()
+ private val targetValidation : mutable.HashMap[EffectTarget.Category.Value, PlanetSideGameObject=>Boolean] = new mutable.HashMap[EffectTarget.Category.Value, PlanetSideGameObject=>Boolean]()
def UseRadius : Float = useRadius
@@ -26,18 +27,14 @@ trait ProximityDefinition {
UseRadius
}
- def TargetValidation : mutable.HashMap[ProximityTarget.Value, PlanetSideGameObject=>Boolean] = targetValidation
+ def TargetValidation : mutable.HashMap[EffectTarget.Category.Value, PlanetSideGameObject=>Boolean] = targetValidation
def Validations : Seq[PlanetSideGameObject=>Boolean] = {
targetValidation.headOption match {
case Some(_) =>
targetValidation.values.toSeq
case None =>
- Seq(ProximityDefinition.Invalid)
+ Seq(EffectTarget.Validation.Invalid)
}
}
}
-
-object ProximityDefinition {
- protected val Invalid : PlanetSideGameObject=>Boolean = (_ : PlanetSideGameObject) => false
-}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTarget.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTarget.scala
deleted file mode 100644
index 9c02fba8..00000000
--- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTarget.scala
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright (c) 2017 PSForever
-package net.psforever.objects.serverobject.terminals
-
-/**
- * A classification of the target of this terminal's interactions.
- * Arbitrary, but useful.
- */
-object ProximityTarget extends Enumeration {
- val
- Aircraft,
- Equipment,
- Player,
- Vehicle
- = Value
-}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala
index 8fd7cc53..c7b2e742 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala
@@ -126,35 +126,5 @@ class ProximityTerminalControl(term : Terminal with ProximityUnit) extends Actor
}
object ProximityTerminalControl {
- object Validation {
- def Medical(target : PlanetSideGameObject) : Boolean = target match {
- case p : Player =>
- p.Health > 0 && (p.Health < p.MaxHealth || p.Armor < p.MaxArmor)
- case _ =>
- false
- }
-
- def HealthCrystal(target : PlanetSideGameObject) : Boolean = target match {
- case p : Player =>
- p.Health > 0 && p.Health < p.MaxHealth
- case _ =>
- false
- }
-
- def RepairSilo(target : PlanetSideGameObject) : Boolean = target match {
- case v : Vehicle =>
- !GlobalDefinitions.isFlightVehicle(v.Definition) && v.Health > 0 && v.Health < v.MaxHealth
- case _ =>
- false
- }
-
- def PadLanding(target : PlanetSideGameObject) : Boolean = target match {
- case v : Vehicle =>
- GlobalDefinitions.isFlightVehicle(v.Definition) && v.Health > 0 && v.Health < v.MaxHealth
- case _ =>
- false
- }
- }
-
private case class TerminalAction()
}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurret.scala b/common/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurret.scala
index 4a09e220..8d4bc7c3 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurret.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurret.scala
@@ -1,12 +1,14 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.turret
+import net.psforever.objects.equipment.JammableUnit
import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.types.Vector3
import net.psforever.objects.vital.{DamageResistanceModel, StandardResistanceProfile, Vitality}
-class FacilityTurret(tDef : TurretDefinition) extends Amenity
+class FacilityTurret(tDef : FacilityTurretDefinition) extends Amenity
with WeaponTurret
+ with JammableUnit
with Vitality
with StandardResistanceProfile {
/** some turrets can be updated; they all start without updates */
@@ -53,7 +55,7 @@ class FacilityTurret(tDef : TurretDefinition) extends Amenity
def DamageModel = Definition.asInstanceOf[DamageResistanceModel]
- def Definition : TurretDefinition = tDef
+ def Definition : FacilityTurretDefinition = tDef
}
object FacilityTurret {
@@ -62,7 +64,7 @@ object FacilityTurret {
* @param tDef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields
* @return a `FacilityTurret` object
*/
- def apply(tDef : TurretDefinition) : FacilityTurret = {
+ def apply(tDef : FacilityTurretDefinition) : FacilityTurret = {
new FacilityTurret(tDef)
}
@@ -73,14 +75,14 @@ object FacilityTurret {
* @param context a context to allow the object to properly set up `ActorSystem` functionality
* @return the `MannedTurret` object
*/
- def Constructor(tdef : TurretDefinition)(id : Int, context : ActorContext) : FacilityTurret = {
+ def Constructor(tdef : FacilityTurretDefinition)(id : Int, context : ActorContext) : FacilityTurret = {
import akka.actor.Props
val obj = FacilityTurret(tdef)
obj.Actor = context.actorOf(Props(classOf[FacilityTurretControl], obj), s"${tdef.Name}_$id")
obj
}
- def Constructor(pos: Vector3, tdef : TurretDefinition)(id : Int, context : ActorContext) : FacilityTurret = {
+ def Constructor(pos: Vector3, tdef : FacilityTurretDefinition)(id : Int, context : ActorContext) : FacilityTurret = {
import akka.actor.Props
val obj = FacilityTurret(tdef)
obj.Position = pos
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala
index 0e70e0f6..4a1bb4b3 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala
@@ -2,9 +2,17 @@
package net.psforever.objects.serverobject.turret
import akka.actor.Actor
+import net.psforever.objects.ballistics.ResolvedProjectile
+import net.psforever.objects.equipment.{JammableMountedWeapons, JammableUnit}
import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior}
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
import net.psforever.objects.vital.Vitality
+import net.psforever.objects.zones.Zone
+import net.psforever.packet.game.PlanetSideGUID
+import services.Service
+import services.avatar.{AvatarAction, AvatarServiceMessage}
+import services.vehicle.{VehicleAction, VehicleServiceMessage}
+import services.vehicle.support.TurretUpgrader
/**
* An `Actor` that handles messages being dispatched to a specific `MannedTurret`.
@@ -16,12 +24,17 @@ import net.psforever.objects.vital.Vitality
*/
class FacilityTurretControl(turret : FacilityTurret) extends Actor
with FactionAffinityBehavior.Check
- with MountableBehavior.Dismount {
- def MountableObject = turret //do not add type!
+ with MountableBehavior.Dismount
+ with JammableMountedWeapons {
+
+ def MountableObject = turret
+
+ def JammableObject = turret
def FactionObject : FactionAffinity = turret
def receive : Receive = checkBehavior
+ .orElse(jammableBehavior)
.orElse(dismountBehavior)
.orElse {
case Mountable.TryMount(user, seat_num) =>
@@ -42,15 +55,100 @@ class FacilityTurretControl(turret : FacilityTurret) extends Actor
case Vitality.Damage(damage_func) =>
if(turret.Health > 0) {
val originalHealth = turret.Health
- damage_func(turret)
+ val cause = damage_func(turret)
val health = turret.Health
val damageToHealth = originalHealth - health
- val name = turret.Actor.toString
- val slashPoint = name.lastIndexOf("/")
- org.log4s.getLogger("DamageResolution").info(s"${name.substring(slashPoint+1, name.length-1)}: BEFORE=$originalHealth, AFTER=$health, CHANGE=$damageToHealth")
- sender ! Vitality.DamageResolution(turret)
+ FacilityTurretControl.HandleDamageResolution(turret, cause, damageToHealth)
+ if(damageToHealth > 0) {
+ val name = turret.Actor.toString
+ val slashPoint = name.lastIndexOf("/")
+ org.log4s.getLogger("DamageResolution").info(s"${name.substring(slashPoint + 1, name.length - 1)}: BEFORE=$originalHealth, AFTER=$health, CHANGE=$damageToHealth")
+ }
}
case _ => ;
}
}
+
+object FacilityTurretControl {
+ def HandleDamageResolution(target : FacilityTurret, cause : ResolvedProjectile, damage : Int) : Unit = {
+ val zone = target.Zone
+ val targetGUID = target.GUID
+ val playerGUID = target.Zone.LivePlayers.find { p => cause.projectile.owner.Name.equals(p.Name) } match {
+ case Some(player) => player.GUID
+ case _ => targetGUID
+ }
+ val continentId = zone.Id
+ if(target.Health > 1) {
+ //alert occupants to damage source
+ if(damage > 0) {
+ zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
+ //alert occupants to damage source
+ HandleDamageAwareness(target, playerGUID, cause)
+ }
+ if(cause.projectile.profile.JammerProjectile) {
+ target.Actor ! JammableUnit.Jammered(cause)
+ }
+ }
+ else {
+ //alert to vehicle death (hence, occupants' deaths)
+ HandleDestructionAwareness(target, playerGUID, cause)
+ }
+ zone.VehicleEvents ! VehicleServiceMessage(continentId, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, targetGUID, 0, target.Health))
+ }
+
+ /**
+ * na
+ * @param target na
+ * @param attribution na
+ * @param lastShot na
+ */
+ def HandleDamageAwareness(target : FacilityTurret, attribution : PlanetSideGUID, lastShot : ResolvedProjectile) : Unit = {
+ val zone = target.Zone
+ val zoneId = zone.Id
+ //alert occupants to damage source
+ target.Seats.values.filter(seat => {
+ seat.isOccupied && seat.Occupant.get.isAlive
+ }).foreach(seat => {
+ val tplayer = seat.Occupant.get
+ zone.AvatarEvents ! AvatarServiceMessage(zoneId, AvatarAction.HitHint(attribution, tplayer.GUID))
+ })
+ }
+
+ /**
+ * na
+ * @param target na
+ * @param attribution na
+ * @param lastShot na
+ */
+ def HandleDestructionAwareness(target : FacilityTurret, attribution : PlanetSideGUID, lastShot : ResolvedProjectile) : Unit = {
+ target.Actor ! JammableUnit.ClearJammeredSound()
+ target.Actor ! JammableUnit.ClearJammeredStatus()
+ val zone = target.Zone
+ val zoneId = zone.Id
+ target.Seats.values.filter(seat => {
+ seat.isOccupied && seat.Occupant.get.isAlive
+ }).foreach(seat => {
+ val tplayer = seat.Occupant.get
+ val tplayerGUID = tplayer.GUID
+ zone.AvatarEvents ! AvatarServiceMessage(tplayer.Name, AvatarAction.KilledWhileInVehicle(tplayerGUID))
+ zone.AvatarEvents ! AvatarServiceMessage(zoneId, AvatarAction.ObjectDelete(tplayerGUID, tplayerGUID)) //dead player still sees self
+ })
+ //turret wreckage has no weapons
+ // target.Weapons.values
+ // .filter {
+ // _.Equipment.nonEmpty
+ // }
+ // .foreach(slot => {
+ // val wep = slot.Equipment.get
+ // zone.AvatarEvents ! AvatarServiceMessage(continentId, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, wep.GUID))
+ // })
+ // zone.AvatarEvents ! AvatarServiceMessage(continentId, AvatarAction.Destroy(targetGUID, playerGUID, playerGUID, player.Position))
+ target.Health = 1 //TODO turret "death" at 0, as is proper
+ zone.VehicleEvents ! VehicleServiceMessage(zoneId, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 0, target.Health)) //TODO not necessary
+ if(target.Upgrade != TurretUpgrade.None) {
+ zone.VehicleEvents ! VehicleServiceMessage.TurretUpgrade(TurretUpgrader.ClearSpecific(List(target), zone))
+ zone.VehicleEvents ! VehicleServiceMessage.TurretUpgrade(TurretUpgrader.AddTask(target, zone, TurretUpgrade.None))
+ }
+ }
+}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretDefinition.scala
new file mode 100644
index 00000000..43dd4db0
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretDefinition.scala
@@ -0,0 +1,17 @@
+// Copyright (c) 2019 PSForever
+package net.psforever.objects.serverobject.turret
+
+import net.psforever.objects.definition.ObjectDefinition
+import net.psforever.objects.vital.{StandardResolutions, StandardVehicleDamage, StandardVehicleResistance}
+
+/**
+ * The definition for any `FacilityTurret`.
+ * @param objectId the object's identifier number
+ */
+class FacilityTurretDefinition(private val objectId : Int) extends ObjectDefinition(objectId)
+ with TurretDefinition {
+ Damage = StandardVehicleDamage
+ Resistance = StandardVehicleResistance
+ Model = StandardResolutions.FacilityTurrets
+}
+
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/turret/TurretDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/turret/TurretDefinition.scala
index 90abe882..d9a7eae2 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/turret/TurretDefinition.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/turret/TurretDefinition.scala
@@ -3,19 +3,18 @@ package net.psforever.objects.serverobject.turret
import net.psforever.objects.definition.{ObjectDefinition, ToolDefinition}
import net.psforever.objects.vehicles.Turrets
-import net.psforever.objects.vital.{DamageResistanceModel, StandardResolutions, StandardVehicleDamage, StandardVehicleResistance}
+import net.psforever.objects.vital.DamageResistanceModel
import net.psforever.objects.vital.resistance.ResistanceProfileMutators
import scala.collection.mutable
/**
* The definition for any `MannedTurret`.
- * @param objectId the object's identifier number
*/
-class TurretDefinition(private val objectId : Int) extends ObjectDefinition(objectId)
- with ResistanceProfileMutators
+trait TurretDefinition extends ResistanceProfileMutators
with DamageResistanceModel {
- Turrets(objectId) //let throw NoSuchElementException
+ odef : ObjectDefinition =>
+ Turrets(odef.ObjectId) //let throw NoSuchElementException
private var maxHealth : Int = 100
/* key - entry point index, value - seat index */
@@ -29,9 +28,6 @@ class TurretDefinition(private val objectId : Int) extends ObjectDefinition(obje
/** creates internal ammunition reserves that can not become depleted
* see `MannedTurret.TurretAmmoBox` for details */
private var hasReserveAmmunition : Boolean = false
- Damage = StandardVehicleDamage
- Resistance = StandardVehicleResistance
- Model = StandardResolutions.FacilityTurrets
def MaxHealth : Int = maxHealth
diff --git a/common/src/main/scala/net/psforever/objects/vehicles/Utility.scala b/common/src/main/scala/net/psforever/objects/vehicles/Utility.scala
index 97a8e745..64f1b9af 100644
--- a/common/src/main/scala/net/psforever/objects/vehicles/Utility.scala
+++ b/common/src/main/scala/net/psforever/objects/vehicles/Utility.scala
@@ -2,7 +2,7 @@
package net.psforever.objects.vehicles
import akka.actor.ActorContext
-import net.psforever.objects.definition.DeployableDefinition
+import net.psforever.objects.definition.SimpleDeployableDefinition
import net.psforever.objects._
import net.psforever.objects.ce.TelepadLike
import net.psforever.objects.serverobject.structures.Amenity
@@ -193,7 +193,7 @@ object Utility {
* and allows it to serve as one of the terminal points of a Router-telepad teleportation system.
* @param ddef na
*/
- class InternalTelepad(ddef : DeployableDefinition) extends Amenity
+ class InternalTelepad(ddef : SimpleDeployableDefinition) extends Amenity
with TelepadLike {
/** a link to the telepad that serves as the other endpoint of this teleportation system */
private var activeTelepad : Option[PlanetSideGUID] = None
diff --git a/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala b/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala
index deafa3ae..78b6a72e 100644
--- a/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala
+++ b/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala
@@ -2,13 +2,20 @@
package net.psforever.objects.vehicles
import akka.actor.{Actor, ActorRef}
-import net.psforever.objects.Vehicle
-import net.psforever.objects.ballistics.VehicleSource
+import net.psforever.objects.{GlobalDefinitions, Vehicle}
+import net.psforever.objects.ballistics.{ResolvedProjectile, VehicleSource}
+import net.psforever.objects.equipment.{JammableMountedWeapons, JammableUnit}
import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior}
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
-import net.psforever.objects.serverobject.deploy.DeploymentBehavior
+import net.psforever.objects.serverobject.deploy.{Deployment, DeploymentBehavior}
import net.psforever.objects.vital.{VehicleShieldCharge, Vitality}
-import net.psforever.types.ExoSuitType
+import net.psforever.objects.zones.Zone
+import net.psforever.packet.game.PlanetSideGUID
+import net.psforever.types.{DriveState, ExoSuitType, Vector3}
+import services.{RemoverActor, Service}
+import services.avatar.{AvatarAction, AvatarServiceMessage}
+import services.local.{LocalAction, LocalServiceMessage}
+import services.vehicle.{VehicleAction, VehicleService, VehicleServiceMessage}
/**
* An `Actor` that handles messages being dispatched to a specific `Vehicle`.
@@ -21,12 +28,16 @@ class VehicleControl(vehicle : Vehicle) extends Actor
with FactionAffinityBehavior.Check
with DeploymentBehavior
with MountableBehavior.Mount
- with MountableBehavior.Dismount {
+ with MountableBehavior.Dismount
+ with JammableMountedWeapons {
+
//make control actors belonging to utilities when making control actor belonging to vehicle
vehicle.Utilities.foreach({case (_, util) => util.Setup })
def MountableObject = vehicle
+ def JammableObject = vehicle
+
def FactionObject = vehicle
def DeploymentObject = vehicle
@@ -44,6 +55,7 @@ class VehicleControl(vehicle : Vehicle) extends Actor
def Enabled : Receive = checkBehavior
.orElse(deployBehavior)
.orElse(dismountBehavior)
+ .orElse(jammableBehavior)
.orElse {
case Mountable.TryMount(user, seat_num) =>
val exosuit = user.ExoSuit
@@ -73,15 +85,17 @@ class VehicleControl(vehicle : Vehicle) extends Actor
if(vehicle.Health > 0) {
val originalHealth = vehicle.Health
val originalShields = vehicle.Shields
- damage_func(vehicle)
+ val cause = damage_func(vehicle)
val health = vehicle.Health
val shields = vehicle.Shields
val damageToHealth = originalHealth - health
val damageToShields = originalShields - shields
- val name = vehicle.Actor.toString
- val slashPoint = name.lastIndexOf("/")
- org.log4s.getLogger("DamageResolution").info(s"${name.substring(slashPoint+1, name.length-1)}: BEFORE=$originalHealth/$originalShields, AFTER=$health/$shields, CHANGE=$damageToHealth/$damageToShields")
- sender ! Vitality.DamageResolution(vehicle)
+ VehicleControl.HandleDamageResolution(vehicle, cause, damageToHealth + damageToShields)
+ if(damageToHealth > 0 || damageToShields > 0) {
+ val name = vehicle.Actor.toString
+ val slashPoint = name.lastIndexOf("/")
+ org.log4s.getLogger("DamageResolution").info(s"${name.substring(slashPoint + 1, name.length - 1)}: BEFORE=$originalHealth/$originalShields, AFTER=$health/$shields, CHANGE=$damageToHealth/$damageToShields")
+ }
}
case Vehicle.ChargeShields(amount) =>
@@ -91,7 +105,7 @@ class VehicleControl(vehicle : Vehicle) extends Actor
!vehicle.History.exists(VehicleControl.LastShieldChargeOrDamage(now))) {
vehicle.History(VehicleShieldCharge(VehicleSource(vehicle), amount))
vehicle.Shields = vehicle.Shields + amount
- sender ! Vehicle.UpdateShieldsCharge(vehicle)
+ vehicle.Zone.VehicleEvents ! VehicleServiceMessage(s"${vehicle.Actor}", VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), vehicle.GUID, 68, vehicle.Shields))
}
case FactionAffinity.ConvertFactionAffinity(faction) =>
@@ -101,7 +115,9 @@ class VehicleControl(vehicle : Vehicle) extends Actor
}
sender ! FactionAffinity.AssertFactionAffinity(vehicle, faction)
- case Vehicle.PrepareForDeletion =>
+ case Vehicle.PrepareForDeletion() =>
+ CancelJammeredSound(vehicle)
+ CancelJammeredStatus(vehicle)
context.become(Disabled)
case _ => ;
@@ -110,10 +126,10 @@ class VehicleControl(vehicle : Vehicle) extends Actor
def Disabled : Receive = checkBehavior
.orElse(dismountBehavior)
.orElse {
- case Vehicle.Reactivate =>
+ case Vehicle.Reactivate() =>
context.become(Enabled)
- case _ => ;
+ case _ =>
}
}
@@ -136,4 +152,116 @@ object VehicleControl {
case _ => false
}
}
+
+ /**
+ * na
+ * @param target na
+ */
+ def HandleDamageResolution(target : Vehicle, cause : ResolvedProjectile, damage : Int) : Unit = {
+ val zone = target.Zone
+ val targetGUID = target.GUID
+ val playerGUID = zone.LivePlayers.find { p => cause.projectile.owner.Name.equals(p.Name) } match {
+ case Some(player) => player.GUID
+ case _ => PlanetSideGUID(0)
+ }
+ if(target.Health > 0) {
+ //activity on map
+ if(damage > 0) {
+ zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
+ //alert occupants to damage source
+ HandleDamageAwareness(target, playerGUID, cause)
+ }
+ if(cause.projectile.profile.JammerProjectile) {
+ target.Actor ! JammableUnit.Jammered(cause)
+ }
+ }
+ else {
+ //alert to vehicle death (hence, occupants' deaths)
+ HandleDestructionAwareness(target, playerGUID, cause)
+ }
+ zone.VehicleEvents ! VehicleServiceMessage(zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, targetGUID, 0, target.Health))
+ zone.VehicleEvents ! VehicleServiceMessage(s"${target.Actor}", VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, targetGUID, 68, target.Shields))
+ }
+
+ /**
+ * na
+ * @param target na
+ * @param attribution na
+ * @param lastShot na
+ */
+ def HandleDamageAwareness(target : Vehicle, attribution : PlanetSideGUID, lastShot : ResolvedProjectile) : Unit = {
+ val zone = target.Zone
+ //alert occupants to damage source
+ target.Seats.values.filter(seat => {
+ seat.isOccupied && seat.Occupant.get.isAlive
+ }).foreach(seat => {
+ val tplayer = seat.Occupant.get
+ zone.AvatarEvents ! AvatarServiceMessage(tplayer.Name, AvatarAction.HitHint(attribution, tplayer.GUID))
+ })
+ //alert cargo occupants to damage source
+ target.CargoHolds.values.foreach(hold => {
+ hold.Occupant match {
+ case Some(cargo) =>
+ cargo.Health = 0
+ cargo.Shields = 0
+ cargo.History(lastShot)
+ HandleDamageAwareness(cargo, attribution, lastShot)
+ case None => ;
+ }
+ })
+ }
+
+ /**
+ * na
+ * @param target na
+ * @param attribution na
+ * @param lastShot na
+ */
+ def HandleDestructionAwareness(target : Vehicle, attribution : PlanetSideGUID, lastShot : ResolvedProjectile) : Unit = {
+ target.Actor ! JammableUnit.ClearJammeredSound()
+ target.Actor ! JammableUnit.ClearJammeredStatus()
+ val zone = target.Zone
+ val continentId = zone.Id
+ //alert to vehicle death (hence, occupants' deaths)
+ target.Seats.values.filter(seat => {
+ seat.isOccupied && seat.Occupant.get.isAlive
+ }).foreach(seat => {
+ val tplayer = seat.Occupant.get
+ val tplayerGUID = tplayer.GUID
+ zone.AvatarEvents ! AvatarServiceMessage(tplayer.Name, AvatarAction.KilledWhileInVehicle(tplayerGUID))
+ zone.AvatarEvents ! AvatarServiceMessage(continentId, AvatarAction.ObjectDelete(tplayerGUID, tplayerGUID)) //dead player still sees self
+ })
+ //vehicle wreckage has no weapons
+ target.Weapons.values
+ .filter {
+ _.Equipment.nonEmpty
+ }
+ .foreach(slot => {
+ val wep = slot.Equipment.get
+ zone.AvatarEvents ! AvatarServiceMessage(continentId, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, wep.GUID))
+ })
+ target.CargoHolds.values.foreach(hold => {
+ hold.Occupant match {
+ case Some(cargo) =>
+ cargo.Health = 0
+ cargo.Shields = 0
+ cargo.Position += Vector3.z(1)
+ cargo.History(lastShot) //necessary to kill cargo vehicle occupants //TODO: collision damage
+ HandleDestructionAwareness(cargo, attribution, lastShot) //might cause redundant packets
+ case None => ;
+ }
+ })
+ target.Definition match {
+ case GlobalDefinitions.ams =>
+ target.Actor ! Deployment.TryDeploymentChange(DriveState.Undeploying)
+ case GlobalDefinitions.router =>
+ target.Actor ! Deployment.TryDeploymentChange(DriveState.Undeploying)
+ VehicleService.BeforeUnloadVehicle(target, zone)
+ zone.LocalEvents ! LocalServiceMessage(zone.Id, LocalAction.ToggleTeleportSystem(PlanetSideGUID(0), target, None))
+ case _ => ;
+ }
+ zone.AvatarEvents ! AvatarServiceMessage(continentId, AvatarAction.Destroy(target.GUID, attribution, attribution, target.Position))
+ zone.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(target), zone))
+ zone.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.AddTask(target, zone, Some(1 minute)))
+ }
}
diff --git a/common/src/main/scala/net/psforever/objects/vital/Vitality.scala b/common/src/main/scala/net/psforever/objects/vital/Vitality.scala
index e15c696f..17a862bc 100644
--- a/common/src/main/scala/net/psforever/objects/vital/Vitality.scala
+++ b/common/src/main/scala/net/psforever/objects/vital/Vitality.scala
@@ -5,6 +5,7 @@ import net.psforever.objects.PlanetSideGameObject
import net.psforever.objects.ballistics.{PlayerSource, ResolvedProjectile, SourceEntry, VehicleSource}
import net.psforever.objects.definition.KitDefinition
import net.psforever.objects.serverobject.terminals.TerminalDefinition
+import net.psforever.objects.vital.resolution.ResolutionCalculations
import net.psforever.types.{ExoSuitType, ImplantType}
abstract class VitalsActivity(target : SourceEntry) {
@@ -102,13 +103,13 @@ object Vitality {
* upon a given vital object.
* @param func a function literal
*/
- final case class Damage(func : (Any)=>Unit)
+ final case class Damage(func : ResolutionCalculations.Output)
- final case class DamageOn(obj : Vitality, func : (Any)=>Unit)
+ final case class DamageOn(obj : Vitality, func : ResolutionCalculations.Output)
/**
* Report that a vitals object must be updated due to damage.
* @param obj the vital object
*/
- final case class DamageResolution(obj : Vitality)
+ final case class DamageResolution(obj : Vitality, cause : ResolvedProjectile)
}
diff --git a/common/src/main/scala/net/psforever/objects/vital/resolution/DamageResistCalculations.scala b/common/src/main/scala/net/psforever/objects/vital/resolution/DamageResistCalculations.scala
index 4d434092..bc8527ff 100644
--- a/common/src/main/scala/net/psforever/objects/vital/resolution/DamageResistCalculations.scala
+++ b/common/src/main/scala/net/psforever/objects/vital/resolution/DamageResistCalculations.scala
@@ -14,7 +14,7 @@ import net.psforever.objects.vital.projectile.ProjectileCalculations
* @tparam A an internal type that converts between `calcFunc`'s output and `applyFunc`'s input;
* never has to be defined explicitly, but will be checked upon object definition
*/
-abstract class DamageResistCalculations[A](calcFunc : (ResolvedProjectile)=>((Int, Int)=>A),
+abstract class DamageResistCalculations[A](calcFunc : ResolvedProjectile=>(Int, Int)=>A,
applyFunc : (A, ResolvedProjectile)=>ResolutionCalculations.Output)
extends ResolutionCalculations {
def Calculate(damages : ProjectileCalculations.Form, resistances : ProjectileCalculations.Form, data : ResolvedProjectile) : ResolutionCalculations.Output = {
diff --git a/common/src/main/scala/net/psforever/objects/vital/resolution/ResolutionCalculations.scala b/common/src/main/scala/net/psforever/objects/vital/resolution/ResolutionCalculations.scala
index 1e2d968a..bd395792 100644
--- a/common/src/main/scala/net/psforever/objects/vital/resolution/ResolutionCalculations.scala
+++ b/common/src/main/scala/net/psforever/objects/vital/resolution/ResolutionCalculations.scala
@@ -3,7 +3,7 @@ package net.psforever.objects.vital.resolution
import net.psforever.objects.{Player, TurretDeployable, Vehicle}
import net.psforever.objects.ballistics.{PlayerSource, ResolvedProjectile}
-import net.psforever.objects.ce.{ComplexDeployable, SimpleDeployable}
+import net.psforever.objects.ce.{ComplexDeployable, Deployable}
import net.psforever.objects.serverobject.turret.FacilityTurret
import net.psforever.objects.vital.projectile.ProjectileCalculations
@@ -22,7 +22,7 @@ trait ResolutionCalculations {
}
object ResolutionCalculations {
- type Output = (Any)=>Unit
+ type Output = Any=>ResolvedProjectile
type Form = (ProjectileCalculations.Form, ProjectileCalculations.Form, ResolvedProjectile)=>Output
def NoDamage(data : ResolvedProjectile)(a : Int, b : Int) : Int = 0
@@ -107,7 +107,7 @@ object ResolutionCalculations {
}
}
- def NoApplication(damageValue : Int, data : ResolvedProjectile)(target : Any) : Unit = { }
+ def NoApplication(damageValue : Int, data : ResolvedProjectile)(target : Any) : ResolvedProjectile = data
def SubtractWithRemainder(current : Int, damage : Int) : (Int, Int) = {
val a = Math.max(0, current - damage)
@@ -123,41 +123,44 @@ object ResolutionCalculations {
* @param data the historical `ResolvedProjectile` information
* @param target the `Player` object to be affected by these damage values (at some point)
*/
- def InfantryApplication(damageValues : (Int, Int), data : ResolvedProjectile)(target : Any) : Unit = target match {
- case player : Player =>
- var (a, b) = damageValues
- var result = (0, 0)
- //TODO Personal Shield implant test should go here and modify the values a and b
- if(player.isAlive && !(a == 0 && b == 0)) {
- player.History(data)
- if(player.Capacitor.toInt > 0 && player.isShielded) {
- // Subtract armour damage from capacitor
- result = SubtractWithRemainder(player.Capacitor.toInt, b)
- player.Capacitor = result._1
+ def InfantryApplication(damageValues : (Int, Int), data : ResolvedProjectile)(target : Any) : ResolvedProjectile = {
+ target match {
+ case player : Player =>
+ var (a, b) = damageValues
+ var result = (0, 0)
+ //TODO Personal Shield implant test should go here and modify the values a and b
+ if(player.isAlive && !(a == 0 && b == 0)) {
+ player.History(data)
+ if(player.Capacitor.toInt > 0 && player.isShielded) {
+ // Subtract armour damage from capacitor
+ result = SubtractWithRemainder(player.Capacitor.toInt, b)
+ player.Capacitor = result._1
+ b = result._2
+
+ // Then follow up with health damage if any capacitor is left
+ result = SubtractWithRemainder(player.Capacitor.toInt, a)
+ player.Capacitor = result._1
+ a = result._2
+ }
+
+ // Subtract any remaining armour damage from armour
+ result = SubtractWithRemainder(player.Armor, b)
+ player.Armor = result._1
b = result._2
- // Then follow up with health damage if any capacitor is left
- result = SubtractWithRemainder(player.Capacitor.toInt, a)
- player.Capacitor = result._1
+ // Then bleed through to health if armour ran out
+ result = SubtractWithRemainder(player.Health, b)
+ player.Health = result._1
+ b = result._2
+
+ // Finally, apply health damage to health
+ result = SubtractWithRemainder(player.Health, a)
+ player.Health = result._1
a = result._2
}
-
- // Subtract any remaining armour damage from armour
- result = SubtractWithRemainder(player.Armor, b)
- player.Armor = result._1
- b = result._2
-
- // Then bleed through to health if armour ran out
- result = SubtractWithRemainder(player.Health, b)
- player.Health = result._1
- b = result._2
-
- // Finally, apply health damage to health
- result = SubtractWithRemainder(player.Health, a)
- player.Health = result._1
- a = result._2
- }
- case _ =>
+ case _ =>
+ }
+ data
}
/**
@@ -167,9 +170,9 @@ object ResolutionCalculations {
* @param data the historical `ResolvedProjectile` information
* @param target the `Vehicle` object to be affected by these damage values (at some point)
*/
- def VehicleApplication(damage : Int, data : ResolvedProjectile)(target : Any) : Unit = target match {
- case vehicle : Vehicle =>
- if(vehicle.Health > 0) {
+ def VehicleApplication(damage : Int, data : ResolvedProjectile)(target : Any) : ResolvedProjectile = {
+ target match {
+ case vehicle : Vehicle if vehicle.Health > 0 && damage > 0 =>
vehicle.History(data)
val shields = vehicle.Shields
if(shields > damage) {
@@ -182,57 +185,58 @@ object ResolutionCalculations {
else {
vehicle.Health = vehicle.Health - damage
}
- }
- case _ => ;
+ case _ => ;
+ }
+ data
}
- def SimpleApplication(damage : Int, data : ResolvedProjectile)(target : Any) : Unit = target match {
- case ce : SimpleDeployable =>
- if(ce.Health > 0) {
- ce.Health -= damage
- ce.History(data)
- }
- case turret : FacilityTurret =>
- if(turret.Health > 0) {
+ def SimpleApplication(damage : Int, data : ResolvedProjectile)(target : Any) : ResolvedProjectile = {
+ target match {
+ case obj : Deployable if obj.Health > 0 =>
+ obj.Health -= damage
+ obj.History(data)
+ case turret : FacilityTurret if turret.Health > 0 =>
turret.Health -= damage
turret.History(data)
- }
- case _ =>
+ case _ => ;
+ }
+ data
}
- def ComplexDeployableApplication(damage : Int, data : ResolvedProjectile)(target : Any) : Unit = target match {
- case ce : ComplexDeployable =>
- if(ce.Shields > 0) {
- if(damage > ce.Shields) {
- ce.Health -= (damage - ce.Shields)
- ce.Shields = 0
+ def ComplexDeployableApplication(damage : Int, data : ResolvedProjectile)(target : Any) : ResolvedProjectile = {
+ target match {
+ case ce : ComplexDeployable if ce.Health > 0 && damage > 0 =>
+ ce.History(data)
+ if(ce.Shields > 0) {
+ if(damage > ce.Shields) {
+ ce.Health -= (damage - ce.Shields)
+ ce.Shields = 0
+ }
+ else {
+ ce.Shields -= damage
+ }
}
else {
- ce.Shields -= damage
+ ce.Health -= damage
}
- ce.History(data)
- }
- else if(ce.Health > 0) {
- ce.Health -= damage
- ce.History(data)
- }
- case ce : TurretDeployable =>
- if(ce.Shields > 0) {
- if(damage > ce.Shields) {
- ce.Health -= (damage - ce.Shields)
- ce.Shields = 0
+ case ce : TurretDeployable if ce.Health > 0 && damage > 0 =>
+ ce.History(data)
+ if(ce.Shields > 0) {
+ if(damage > ce.Shields) {
+ ce.Health -= (damage - ce.Shields)
+ ce.Shields = 0
+ }
+ else {
+ ce.Shields -= damage
+ }
}
else {
- ce.Shields -= damage
+ ce.Health -= damage
}
- ce.History(data)
- }
- else if(ce.Health > 0) {
- ce.Health -= damage
- ce.History(data)
- }
- case _ => ;
+ case _ => ;
+ }
+ data
}
}
diff --git a/common/src/main/scala/net/psforever/objects/zones/ZoneDeployableActor.scala b/common/src/main/scala/net/psforever/objects/zones/ZoneDeployableActor.scala
index 593d1847..a53ea99d 100644
--- a/common/src/main/scala/net/psforever/objects/zones/ZoneDeployableActor.scala
+++ b/common/src/main/scala/net/psforever/objects/zones/ZoneDeployableActor.scala
@@ -25,6 +25,7 @@ class ZoneDeployableActor(zone : Zone, deployableList : ListBuffer[PlanetSideGam
case _ =>
obj.Definition.Initialize(obj, context)
}
+ obj.Zone = zone
sender ! Zone.Deployable.DeployableIsBuilt(obj, tool)
}
diff --git a/common/src/main/scala/net/psforever/objects/zones/ZonePopulationActor.scala b/common/src/main/scala/net/psforever/objects/zones/ZonePopulationActor.scala
index a22e9b43..f43ecb26 100644
--- a/common/src/main/scala/net/psforever/objects/zones/ZonePopulationActor.scala
+++ b/common/src/main/scala/net/psforever/objects/zones/ZonePopulationActor.scala
@@ -1,7 +1,9 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.zones
-import akka.actor.Actor
+import akka.actor.{Actor, ActorRef, Props}
+import net.psforever.objects.avatar.PlayerControl
+import net.psforever.objects.vehicles.VehicleControl
import net.psforever.objects.{Avatar, Player}
import scala.annotation.tailrec
@@ -34,16 +36,22 @@ class ZonePopulationActor(zone : Zone, playerMap : TrieMap[Avatar, Option[Player
case Zone.Population.Spawn(avatar, player) =>
PopulationSpawn(avatar, player, playerMap) match {
case Some(tplayer) =>
+ tplayer.Zone = zone
if(tplayer ne player) {
sender ! Zone.Population.PlayerAlreadySpawned(zone, player)
}
+ else {
+ player.Actor = context.actorOf(Props(classOf[PlayerControl], player), s"${player.Name}_${player.GUID.guid}")
+ }
case None =>
sender ! Zone.Population.PlayerCanNotSpawn(zone, player)
}
case Zone.Population.Release(avatar) =>
PopulationRelease(avatar, playerMap) match {
- case Some(_) => ;
+ case Some(tplayer) =>
+ tplayer.Actor ! akka.actor.PoisonPill
+ tplayer.Actor = ActorRef.noSender
case None =>
sender ! Zone.Population.PlayerHasLeft(zone, None)
}
diff --git a/common/src/main/scala/net/psforever/packet/game/GenericObjectActionMessage.scala b/common/src/main/scala/net/psforever/packet/game/GenericObjectActionMessage.scala
index 53a8f3fe..5d87b924 100644
--- a/common/src/main/scala/net/psforever/packet/game/GenericObjectActionMessage.scala
+++ b/common/src/main/scala/net/psforever/packet/game/GenericObjectActionMessage.scala
@@ -23,6 +23,7 @@ import shapeless.{::, HNil}
* 15 - Displays "This facility's generator is under attack!"
* 16 - Displays "Generator has Overloaded! Evacuate Generator Room Immediately!"
* 17 - Displays "This facility's generator is back on line"
+ * 19 - Cause mines to explode
* 20 - Hit flinch? (orig, 82->80)
* 21 - Reset build cooldown from using an ACE
* 22 - ???? (Has been seen on vehicle pad objects, possibly some sort of reset flag after base faction flip / hack clear?)
diff --git a/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala b/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala
index 014df10d..ba67539b 100644
--- a/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala
+++ b/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala
@@ -115,10 +115,10 @@ import scodec.codecs._
* 45 : Advanced Engineering (= Fortification Engineering + Assault Engineering) Must have Combat Engineering
* `25 - Forget certifications (same order as 24)`
* `26 - Certification reset timer (in seconds)`
- * `27 - PA_JAMMED - plays jammed buzzing sound`
+ * `27 - PA_JAMMED - plays jammed buzzing sound in vicinity of target, jams weapon discharge`
* `28 - PA_IMPLANT_ACTIVE - Plays implant sounds. Valid values seem to be up to 20.`
* `29 - PA_VAPORIZED - Visible ?! That's not the cloaked effect, Maybe for spectator mode ?. Value is 0 to visible, 1 to invisible.`
- * `31 - Looking for Squad info (marquee and ui):
+ * `31 - Looking for Squad info (marquee and ui):`
* ` - 0 is LFS`
* ` - 1 is LFSM (Looking for Squad Members)`
* ` - n is the supplemental squad identifier number; same as "LFS;" for the leader, sets "LFSM" after the first manual flagging`
@@ -162,7 +162,7 @@ import scodec.codecs._
* `13 - Trunk permissions (same)`
* `21 - Declare a player the vehicle's owner, by globally unique identifier`
* `22 - Toggles gunner and passenger mount points (1 = hides, 0 = reveals; this also locks their permissions)`
- * `54 - Vehicle EMP? Plays sound as if vehicle had been hit by EMP`
+ * `54 - Plays jammed buzzing sound in vicinity of target`
* `68 - Vehicle shield health`
* `79 - ???`
* `80 - Damage vehicle (unknown value)`
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/CharacterAppearanceData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/CharacterAppearanceData.scala
index 7e2f302a..54f38376 100644
--- a/common/src/main/scala/net/psforever/packet/game/objectcreate/CharacterAppearanceData.scala
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/CharacterAppearanceData.scala
@@ -327,7 +327,7 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
}
else if(data.faction == PlanetSideEmpire.NEUTRAL) {
Attempt.successful(
- CommonFieldData(faction, data.bops, data.alternate, data.v1, data.v2, data.v3, None, data.v5, PlanetSideGUID(0)) ::
+ CommonFieldData(faction, data.bops, data.alternate, data.v1, data.v2, data.jammered, None, data.v5, PlanetSideGUID(0)) ::
name :: suit :: u5 :: sex :: head :: v1 :: u6 :: u7 :: u8 :: u9 :: uA :: HNil
)
}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/CommonFieldData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/CommonFieldData.scala
index 6c9ef68c..7620af20 100644
--- a/common/src/main/scala/net/psforever/packet/game/objectcreate/CommonFieldData.scala
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/CommonFieldData.scala
@@ -8,13 +8,13 @@ import scodec.{Attempt, Codec, Err}
import scodec.codecs._
import shapeless.{::, HNil}
-final case class CommonFieldDataExtra(unk1 : Int, unk2 : Boolean) extends StreamBitSize {
+final case class CommonFieldDataExtra(unk1 : Option[Int], unk2 : Boolean) extends StreamBitSize {
override def bitsize : Long = 17L
}
object CommonFieldDataExtra {
- implicit val codec : Codec[CommonFieldDataExtra] = (
- ("unk1" | uint16L) ::
+ def codec(unk1 : Boolean) : Codec[CommonFieldDataExtra] = (
+ ("unk1" | conditional(unk1, uint16L)) :: //not sure what flags this field
("unk2" | bool)
).as[CommonFieldDataExtra]
}
@@ -28,10 +28,9 @@ object CommonFieldDataExtra {
* when set on a tool, that tool will be rendered nonfunctional instead (though it can still be equipped)
* @param v1 na
* @param v2 na;
- * optional data whose reading is triggered in unknown conditions;
- * flag a weapon as "jammered"
- * @param v3 na;
- * for weapons, works like `alternate`
+ * optional data whose reading is triggered in unknown conditions
+ * @param jammered flag as "jammered;"
+ * set on most game objects, that object will produce the characteristic jammered buzz
* @param v4 na;
* a field used by a second encoding format for this data
* @param v5 na;
@@ -43,7 +42,7 @@ final case class CommonFieldData(faction : PlanetSideEmpire.Value,
alternate : Boolean,
v1 : Boolean,
v2 : Option[CommonFieldDataExtra],
- v3 : Boolean,
+ jammered : Boolean,
v4 : Option[Boolean],
v5 : Option[Int],
guid : PlanetSideGUID
@@ -64,7 +63,7 @@ final case class CommonFieldData(faction : PlanetSideEmpire.Value,
23L + extraSize + v4Size + v5Size
}
- def apply(flag : Boolean) : CommonFieldData = CommonFieldData(faction, bops, alternate, v1, v2, v3, Some(flag), v5, guid)
+ def apply(flag : Boolean) : CommonFieldData = CommonFieldData(faction, bops, alternate, v1, v2, jammered, Some(flag), v5, guid)
}
object CommonFieldData extends Marshallable[CommonFieldData] {
@@ -100,8 +99,8 @@ object CommonFieldData extends Marshallable[CommonFieldData] {
("bops" | bool) ::
("alternate" | bool) ::
("v1" | bool) :: //the purpose of this bit changes depending on the previous bit
- conditional(extra, "v2" | CommonFieldDataExtra.codec) ::
- ("v3" | bool) ::
+ conditional(extra, "v2" | CommonFieldDataExtra.codec(unk1 = false)) ::
+ ("jammered" | bool) ::
optional(bool, "v5" | uint16L) ::
("guid" | PlanetSideGUID.codec)
).xmap[CommonFieldData] (
@@ -122,8 +121,8 @@ object CommonFieldData extends Marshallable[CommonFieldData] {
("bops" | bool) ::
("alternate" | bool) ::
("v1" | bool) :: //though the code path differs depending on the previous bit, this one gets read one way or another
- conditional(extra, "v2" | CommonFieldDataExtra.codec) ::
- ("v3" | bool) ::
+ conditional(extra, "v2" | CommonFieldDataExtra.codec(unk1 = false)) ::
+ ("jammered" | bool) ::
optional(bool, "v5" | uint16L) ::
("v4" | bool) ::
("guid" | PlanetSideGUID.codec)
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/OneMannedFieldTurretData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/OneMannedFieldTurretData.scala
index 7035050d..7140afd6 100644
--- a/common/src/main/scala/net/psforever/packet/game/objectcreate/OneMannedFieldTurretData.scala
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/OneMannedFieldTurretData.scala
@@ -71,7 +71,7 @@ object OneMannedFieldTurretData extends Marshallable[OneMannedFieldTurretData] {
OneMannedFieldTurretData(
CommonFieldDataWithPlacement(
deploy.pos,
- CommonFieldData(data.faction, data.bops, data.alternate, data.v1, data.v2, data.v3, data.v4, data.v5, player)
+ CommonFieldData(data.faction, data.bops, data.alternate, data.v1, data.v2, data.jammered, data.v4, data.v5, player)
),
newHealth,
newInternals
@@ -92,7 +92,7 @@ object OneMannedFieldTurretData extends Marshallable[OneMannedFieldTurretData] {
Attempt.successful(
CommonFieldDataWithPlacement(
pos,
- CommonFieldData(data.faction, data.bops, data.alternate, data.v1, data.v2, data.v3, data.v4, data.v5, PlanetSideGUID(0))
+ CommonFieldData(data.faction, data.bops, data.alternate, data.v1, data.v2, data.jammered, data.v4, data.v5, PlanetSideGUID(0))
) :: data.guid :: false :: newHealth :: 0 :: 0xF :: 0 :: newInternals :: HNil
)
}
diff --git a/common/src/main/scala/services/avatar/AvatarServiceMessage.scala b/common/src/main/scala/services/avatar/AvatarServiceMessage.scala
index 0396a4c3..a3e219aa 100644
--- a/common/src/main/scala/services/avatar/AvatarServiceMessage.scala
+++ b/common/src/main/scala/services/avatar/AvatarServiceMessage.scala
@@ -6,6 +6,7 @@ import net.psforever.objects.ballistics.{Projectile, SourceEntry}
import net.psforever.objects.ce.Deployable
import net.psforever.objects.equipment.Equipment
import net.psforever.objects.inventory.Container
+import net.psforever.objects.vital.resolution.ResolutionCalculations
import net.psforever.objects.zones.Zone
import net.psforever.packet.PlanetSideGamePacket
import net.psforever.packet.game.PlanetSideGUID
@@ -31,7 +32,7 @@ object AvatarAction {
final case class ChangeFireState_Stop(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID) extends Action
final case class ConcealPlayer(player_guid : PlanetSideGUID) extends Action
final case class EnvironmentalDamage(player_guid : PlanetSideGUID, amount: Int) extends Action
- final case class Damage(player_guid : PlanetSideGUID, target : Player, resolution_function : Any=>Unit) extends Action
+ final case class Damage(player_guid : PlanetSideGUID, target : Player, resolution_function : ResolutionCalculations.Output) extends Action
final case class DeployItem(player_guid : PlanetSideGUID, item : PlanetSideGameObject with Deployable) extends Action
final case class Destroy(victim : PlanetSideGUID, killer : PlanetSideGUID, weapon : PlanetSideGUID, pos : Vector3) extends Action
final case class DestroyDisplay(killer : SourceEntry, victim : SourceEntry, method : Int, unk : Int = 121) extends Action
diff --git a/common/src/main/scala/services/avatar/AvatarServiceResponse.scala b/common/src/main/scala/services/avatar/AvatarServiceResponse.scala
index 9ee2e90a..2140dbf7 100644
--- a/common/src/main/scala/services/avatar/AvatarServiceResponse.scala
+++ b/common/src/main/scala/services/avatar/AvatarServiceResponse.scala
@@ -4,6 +4,7 @@ package services.avatar
import net.psforever.objects.Player
import net.psforever.objects.ballistics.{Projectile, SourceEntry}
import net.psforever.objects.equipment.Equipment
+import net.psforever.objects.vital.resolution.ResolutionCalculations
import net.psforever.packet.PlanetSideGamePacket
import net.psforever.packet.game.objectcreate.ConstructorData
import net.psforever.packet.game.{ObjectCreateMessage, PlanetSideGUID}
@@ -25,7 +26,7 @@ object AvatarResponse {
final case class ChangeFireState_Stop(weapon_guid : PlanetSideGUID) extends Response
final case class ConcealPlayer() extends Response
final case class EnvironmentalDamage(target : PlanetSideGUID, amount : Int) extends Response
- final case class DamageResolution(target : Player, resolution_function : Any=>Unit) extends Response
+ final case class DamageResolution(target : Player, resolution_function : ResolutionCalculations.Output) extends Response
final case class Destroy(victim : PlanetSideGUID, killer : PlanetSideGUID, weapon : PlanetSideGUID, pos : Vector3) extends Response
final case class DestroyDisplay(killer : SourceEntry, victim : SourceEntry, method : Int, unk : Int) extends Response
final case class DropItem(pkt : ObjectCreateMessage) extends Response
diff --git a/common/src/main/scala/services/local/LocalService.scala b/common/src/main/scala/services/local/LocalService.scala
index 69002e53..369e653b 100644
--- a/common/src/main/scala/services/local/LocalService.scala
+++ b/common/src/main/scala/services/local/LocalService.scala
@@ -64,6 +64,10 @@ class LocalService(zone : Zone) extends Actor {
LocalEvents.publish(
LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.DeployableMapIcon(behavior, deployInfo))
)
+ case LocalAction.Detonate(guid, obj) =>
+ LocalEvents.publish(
+ LocalServiceResponse(s"/$forChannel/Local", Service.defaultPlayerGUID, LocalResponse.Detonate(guid, obj))
+ )
case LocalAction.DoorOpens(player_guid, _, door) =>
doorCloser ! DoorCloseActor.DoorIsOpen(door, zone)
LocalEvents.publish(
@@ -282,9 +286,9 @@ class LocalService(zone : Zone) extends Actor {
}
//synchronized damage calculations
- case Vitality.DamageOn(target : Deployable, func) =>
- func(target)
- sender ! Vitality.DamageResolution(target)
+ case Vitality.DamageOn(target : Deployable, damage_func) =>
+ val cause = damage_func(target)
+ sender ! Vitality.DamageResolution(target, cause)
case msg =>
log.warn(s"Unhandled message $msg from $sender")
diff --git a/common/src/main/scala/services/local/LocalServiceMessage.scala b/common/src/main/scala/services/local/LocalServiceMessage.scala
index 3e81c479..7c1a9a9b 100644
--- a/common/src/main/scala/services/local/LocalServiceMessage.scala
+++ b/common/src/main/scala/services/local/LocalServiceMessage.scala
@@ -25,6 +25,7 @@ object LocalAction {
final case class AlertDestroyDeployable(player_guid : PlanetSideGUID, obj : PlanetSideGameObject with Deployable) extends Action
final case class DeployableMapIcon(player_guid : PlanetSideGUID, behavior : DeploymentAction.Value, deployInfo : DeployableInfo) extends Action
+ final case class Detonate(guid : PlanetSideGUID, obj : PlanetSideGameObject) extends Action
final case class DoorOpens(player_guid : PlanetSideGUID, continent : Zone, door : Door) extends Action
final case class DoorCloses(player_guid : PlanetSideGUID, door_guid : PlanetSideGUID) extends Action
final case class HackClear(player_guid : PlanetSideGUID, target : PlanetSideServerObject, unk1 : Long, unk2 : Long = 8L) extends Action
diff --git a/common/src/main/scala/services/local/LocalServiceResponse.scala b/common/src/main/scala/services/local/LocalServiceResponse.scala
index cd31aa7a..abc752aa 100644
--- a/common/src/main/scala/services/local/LocalServiceResponse.scala
+++ b/common/src/main/scala/services/local/LocalServiceResponse.scala
@@ -19,6 +19,7 @@ object LocalResponse {
final case class AlertDestroyDeployable(obj : PlanetSideGameObject with Deployable) extends Response
final case class DeployableMapIcon(action : DeploymentAction.Value, deployInfo : DeployableInfo) extends Response
+ final case class Detonate(guid : PlanetSideGUID, obj : PlanetSideGameObject) extends Response
final case class DoorOpens(door_guid : PlanetSideGUID) extends Response
final case class DoorCloses(door_guid : PlanetSideGUID) extends Response
final case class EliminateDeployable(obj : PlanetSideGameObject with Deployable, object_guid : PlanetSideGUID, pos : Vector3) extends Response
diff --git a/common/src/main/scala/services/vehicle/VehicleService.scala b/common/src/main/scala/services/vehicle/VehicleService.scala
index a12ab503..418c7f15 100644
--- a/common/src/main/scala/services/vehicle/VehicleService.scala
+++ b/common/src/main/scala/services/vehicle/VehicleService.scala
@@ -2,16 +2,18 @@
package services.vehicle
import akka.actor.{Actor, ActorRef, Props}
-import net.psforever.objects.Vehicle
+import net.psforever.objects.{GlobalDefinitions, TelepadDeployable, Vehicle}
import net.psforever.objects.ballistics.VehicleSource
import net.psforever.objects.serverobject.pad.VehicleSpawnPad
import net.psforever.objects.serverobject.terminals.{MedicalTerminalDefinition, ProximityUnit}
+import net.psforever.objects.vehicles.{Utility, UtilityType}
import net.psforever.objects.vital.RepairFromTerm
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.{ObjectCreateMessage, PlanetSideGUID}
import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent
import services.vehicle.support.{TurretUpgrader, VehicleRemover}
import net.psforever.types.DriveState
+import services.local.LocalServiceMessage
import services.{GenericEventBus, RemoverActor, Service}
import scala.concurrent.duration._
@@ -253,3 +255,36 @@ class VehicleService(zone : Zone) extends Actor {
.map(util => util().asInstanceOf[SpawnTube])
}
}
+
+object VehicleService {
+ /**
+ * Before a vehicle is removed from the game world, the following actions must be performed.
+ * @param vehicle the vehicle
+ */
+ def BeforeUnloadVehicle(vehicle : Vehicle, zone : Zone) : Unit = {
+ vehicle.Definition match {
+ case GlobalDefinitions.ams =>
+ zone.VehicleEvents ! VehicleServiceMessage.AMSDeploymentChange(zone)
+ case GlobalDefinitions.router =>
+ RemoveTelepads(vehicle, zone)
+ case _ => ;
+ }
+ }
+
+ def RemoveTelepads(vehicle: Vehicle, zone : Zone) : Unit = {
+ (vehicle.Utility(UtilityType.internal_router_telepad_deployable) match {
+ case Some(util : Utility.InternalTelepad) =>
+ val telepad = util.Telepad
+ util.Telepad = None
+ zone.GUID(telepad)
+ case _ =>
+ None
+ }) match {
+ case Some(telepad : TelepadDeployable) =>
+ telepad.Active = false
+ zone.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.ClearSpecific(List(telepad), zone))
+ zone.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.AddTask(telepad, zone, Some(0 seconds)))
+ case _ => ;
+ }
+ }
+}
diff --git a/common/src/main/scala/services/vehicle/support/VehicleRemover.scala b/common/src/main/scala/services/vehicle/support/VehicleRemover.scala
index de832306..4e45c037 100644
--- a/common/src/main/scala/services/vehicle/support/VehicleRemover.scala
+++ b/common/src/main/scala/services/vehicle/support/VehicleRemover.scala
@@ -26,7 +26,7 @@ class VehicleRemover extends RemoverActor {
val vehicle = entry.obj.asInstanceOf[Vehicle]
val vehicleGUID = vehicle.GUID
val zoneId = entry.zone.Id
- vehicle.Actor ! Vehicle.PrepareForDeletion
+ vehicle.Actor ! Vehicle.PrepareForDeletion()
//escape being someone else's cargo
(vehicle.MountedIn match {
case Some(carrierGUID) =>
diff --git a/common/src/test/scala/game/objectcreate/AegisShieldGeneratorDataTest.scala b/common/src/test/scala/game/objectcreate/AegisShieldGeneratorDataTest.scala
index 49d16d86..a2e07d99 100644
--- a/common/src/test/scala/game/objectcreate/AegisShieldGeneratorDataTest.scala
+++ b/common/src/test/scala/game/objectcreate/AegisShieldGeneratorDataTest.scala
@@ -29,7 +29,7 @@ class AegisShieldGeneratorDataTest extends Specification {
basic.data.alternate mustEqual false
basic.data.v1 mustEqual true
basic.data.v2.isDefined mustEqual false
- basic.data.v3 mustEqual false
+ basic.data.jammered mustEqual false
basic.data.v5.isDefined mustEqual false
basic.data.guid mustEqual PlanetSideGUID(2366)
diff --git a/common/src/test/scala/game/objectcreate/CharacterDataTest.scala b/common/src/test/scala/game/objectcreate/CharacterDataTest.scala
index 5a8f6343..19b51356 100644
--- a/common/src/test/scala/game/objectcreate/CharacterDataTest.scala
+++ b/common/src/test/scala/game/objectcreate/CharacterDataTest.scala
@@ -43,7 +43,7 @@ class CharacterDataTest extends Specification {
a.data.bops mustEqual false
a.data.v1 mustEqual false
a.data.v2.isEmpty mustEqual true
- a.data.v3 mustEqual false
+ a.data.jammered mustEqual false
a.data.v4.isEmpty mustEqual true
a.data.v5.isEmpty mustEqual true
a.exosuit mustEqual ExoSuitType.Reinforced
@@ -162,7 +162,7 @@ class CharacterDataTest extends Specification {
a.data.bops mustEqual false
a.data.v1 mustEqual false
a.data.v2.isEmpty mustEqual true
- a.data.v3 mustEqual false
+ a.data.jammered mustEqual false
a.data.v4.isEmpty mustEqual true
a.data.v5.isEmpty mustEqual true
a.exosuit mustEqual ExoSuitType.Reinforced
@@ -231,7 +231,7 @@ class CharacterDataTest extends Specification {
a.data.bops mustEqual false
a.data.v1 mustEqual false
a.data.v2.isEmpty mustEqual true
- a.data.v3 mustEqual false
+ a.data.jammered mustEqual false
a.data.v4.isEmpty mustEqual true
a.data.v5.isEmpty mustEqual true
a.exosuit mustEqual ExoSuitType.MAX
diff --git a/common/src/test/scala/game/objectcreate/OneMannedFieldTurretDataTest.scala b/common/src/test/scala/game/objectcreate/OneMannedFieldTurretDataTest.scala
index b94884ab..79e8e32e 100644
--- a/common/src/test/scala/game/objectcreate/OneMannedFieldTurretDataTest.scala
+++ b/common/src/test/scala/game/objectcreate/OneMannedFieldTurretDataTest.scala
@@ -28,7 +28,7 @@ class OneMannedFieldTurretDataTest extends Specification {
deploy.alternate mustEqual false
deploy.v1 mustEqual true
deploy.v2.isEmpty mustEqual true
- deploy.v3 mustEqual false
+ deploy.jammered mustEqual false
deploy.v4.contains(false) mustEqual true
deploy.v5.isEmpty mustEqual true
deploy.guid mustEqual PlanetSideGUID(2502)
diff --git a/common/src/test/scala/game/objectcreate/RemoteProjectileDataTest.scala b/common/src/test/scala/game/objectcreate/RemoteProjectileDataTest.scala
index 8e09c859..a6cf8062 100644
--- a/common/src/test/scala/game/objectcreate/RemoteProjectileDataTest.scala
+++ b/common/src/test/scala/game/objectcreate/RemoteProjectileDataTest.scala
@@ -29,7 +29,7 @@ class RemoteProjectileDataTest extends Specification {
deploy.alternate mustEqual false
deploy.v1 mustEqual true
deploy.v2.isEmpty mustEqual true
- deploy.v3 mustEqual false
+ deploy.jammered mustEqual false
deploy.v4.isEmpty mustEqual true
deploy.v5.isEmpty mustEqual true
deploy.guid mustEqual PlanetSideGUID(0)
@@ -63,7 +63,7 @@ class RemoteProjectileDataTest extends Specification {
deploy.alternate mustEqual false
deploy.v1 mustEqual true
deploy.v2.isEmpty mustEqual true
- deploy.v3 mustEqual false
+ deploy.jammered mustEqual false
deploy.v4.isEmpty mustEqual true
deploy.v5.isEmpty mustEqual true
deploy.guid mustEqual PlanetSideGUID(0)
diff --git a/common/src/test/scala/game/objectcreate/SmallTurretDataTest.scala b/common/src/test/scala/game/objectcreate/SmallTurretDataTest.scala
index fc901305..929cd99d 100644
--- a/common/src/test/scala/game/objectcreate/SmallTurretDataTest.scala
+++ b/common/src/test/scala/game/objectcreate/SmallTurretDataTest.scala
@@ -30,7 +30,7 @@ class SmallTurretDataTest extends Specification {
deploy.alternate mustEqual true
deploy.v1 mustEqual true
deploy.v2.isEmpty mustEqual true
- deploy.v3 mustEqual false
+ deploy.jammered mustEqual false
deploy.v4.contains(false) mustEqual true
deploy.v5.isEmpty mustEqual true
deploy.guid mustEqual PlanetSideGUID(7742)
@@ -61,7 +61,7 @@ class SmallTurretDataTest extends Specification {
deploy.alternate mustEqual false
deploy.v1 mustEqual true
deploy.v2.isEmpty mustEqual true
- deploy.v3 mustEqual false
+ deploy.jammered mustEqual false
deploy.v4.contains(true) mustEqual true
deploy.v5.isEmpty mustEqual true
deploy.guid mustEqual PlanetSideGUID(8208)
diff --git a/common/src/test/scala/game/objectcreate/TRAPDataTest.scala b/common/src/test/scala/game/objectcreate/TRAPDataTest.scala
index 6e899523..63c81efa 100644
--- a/common/src/test/scala/game/objectcreate/TRAPDataTest.scala
+++ b/common/src/test/scala/game/objectcreate/TRAPDataTest.scala
@@ -28,7 +28,7 @@ class TRAPDataTest extends Specification {
deploy.alternate mustEqual false
deploy.v1 mustEqual true
deploy.v2.isEmpty mustEqual true
- deploy.v3 mustEqual false
+ deploy.jammered mustEqual false
deploy.v4.contains(true) mustEqual true
deploy.v5.isEmpty mustEqual true
deploy.guid mustEqual PlanetSideGUID(4748)
diff --git a/common/src/test/scala/game/objectcreatedetailed/DetailedCharacterDataTest.scala b/common/src/test/scala/game/objectcreatedetailed/DetailedCharacterDataTest.scala
index 1178a619..81b17668 100644
--- a/common/src/test/scala/game/objectcreatedetailed/DetailedCharacterDataTest.scala
+++ b/common/src/test/scala/game/objectcreatedetailed/DetailedCharacterDataTest.scala
@@ -71,7 +71,7 @@ class DetailedCharacterDataTest extends Specification {
a.data.bops mustEqual false
a.data.v1 mustEqual true
a.data.v2.isEmpty mustEqual true
- a.data.v3 mustEqual false
+ a.data.jammered mustEqual false
a.data.v4.isEmpty mustEqual true
a.data.v5.isEmpty mustEqual true
a.exosuit mustEqual ExoSuitType.Standard
@@ -259,7 +259,7 @@ class DetailedCharacterDataTest extends Specification {
a.data.bops mustEqual false
a.data.v1 mustEqual false
a.data.v2.isEmpty mustEqual true
- a.data.v3 mustEqual false
+ a.data.jammered mustEqual false
a.data.v4.isEmpty mustEqual true
a.data.v5.isEmpty mustEqual true
a.exosuit mustEqual ExoSuitType.Standard
@@ -444,7 +444,7 @@ class DetailedCharacterDataTest extends Specification {
a.data.bops mustEqual false
a.data.v1 mustEqual true
a.data.v2.isEmpty mustEqual true
- a.data.v3 mustEqual false
+ a.data.jammered mustEqual false
a.data.v4.isEmpty mustEqual true
a.data.v5.isEmpty mustEqual true
a.exosuit mustEqual ExoSuitType.MAX
@@ -652,7 +652,7 @@ class DetailedCharacterDataTest extends Specification {
a.data.bops mustEqual false
a.data.v1 mustEqual true
a.data.v2.isEmpty mustEqual true
- a.data.v3 mustEqual false
+ a.data.jammered mustEqual false
a.data.v4.isEmpty mustEqual true
a.data.v5.isEmpty mustEqual true
a.exosuit mustEqual ExoSuitType.Agile
@@ -1104,7 +1104,7 @@ class DetailedCharacterDataTest extends Specification {
cdata.data.alternate mustEqual false
cdata.data.v1 mustEqual true
cdata.data.v2.isEmpty mustEqual true
- cdata.data.v3 mustEqual false
+ cdata.data.jammered mustEqual false
cdata.data.v4.isEmpty mustEqual true
cdata.data.v5.isEmpty mustEqual true
cdata.data.guid mustEqual PlanetSideGUID(0)
@@ -1160,7 +1160,7 @@ class DetailedCharacterDataTest extends Specification {
a.data.alternate mustEqual false
a.data.v1 mustEqual false
a.data.v2.isEmpty mustEqual true
- a.data.v3 mustEqual false
+ a.data.jammered mustEqual false
a.data.v4.isEmpty mustEqual true
a.data.v5.isEmpty mustEqual true
a.exosuit mustEqual ExoSuitType.Standard
@@ -1310,7 +1310,7 @@ class DetailedCharacterDataTest extends Specification {
a.data.alternate mustEqual false
a.data.v1 mustEqual false
a.data.v2.isEmpty mustEqual true
- a.data.v3 mustEqual false
+ a.data.jammered mustEqual false
a.data.v4.isEmpty mustEqual true
a.data.v5.isEmpty mustEqual true
a.exosuit mustEqual ExoSuitType.Standard
diff --git a/common/src/test/scala/game/objectcreatedetailed/DetailedConstructionToolDataTest.scala b/common/src/test/scala/game/objectcreatedetailed/DetailedConstructionToolDataTest.scala
index a363b799..c4e16b0b 100644
--- a/common/src/test/scala/game/objectcreatedetailed/DetailedConstructionToolDataTest.scala
+++ b/common/src/test/scala/game/objectcreatedetailed/DetailedConstructionToolDataTest.scala
@@ -31,7 +31,7 @@ class DetailedConstructionToolDataTest extends Specification {
cdata.alternate mustEqual false
cdata.v1 mustEqual true
cdata.v2.isEmpty mustEqual true
- cdata.v3 mustEqual false
+ cdata.jammered mustEqual false
cdata.v4.isEmpty mustEqual true
cdata.v5.isEmpty mustEqual true
cdata.guid mustEqual PlanetSideGUID(0)
@@ -71,7 +71,7 @@ class DetailedConstructionToolDataTest extends Specification {
cdata.alternate mustEqual false
cdata.v1 mustEqual true
cdata.v2.isEmpty mustEqual true
- cdata.v3 mustEqual false
+ cdata.jammered mustEqual false
cdata.v4.isEmpty mustEqual true
cdata.v5.isEmpty mustEqual true
cdata.guid mustEqual PlanetSideGUID(0)
@@ -111,7 +111,7 @@ class DetailedConstructionToolDataTest extends Specification {
cdata.alternate mustEqual false
cdata.v1 mustEqual true
cdata.v2.isEmpty mustEqual true
- cdata.v3 mustEqual false
+ cdata.jammered mustEqual false
cdata.v4.isEmpty mustEqual true
cdata.v5.contains(564) mustEqual true
cdata.guid mustEqual PlanetSideGUID(0)
@@ -139,7 +139,7 @@ class DetailedConstructionToolDataTest extends Specification {
cdata.alternate mustEqual false
cdata.v1 mustEqual false
cdata.v2.isEmpty mustEqual true
- cdata.v3 mustEqual false
+ cdata.jammered mustEqual false
cdata.v4.isEmpty mustEqual true
cdata.v5.isEmpty mustEqual true
cdata.guid mustEqual PlanetSideGUID(0)
diff --git a/common/src/test/scala/game/objectcreatevehicle/MountedVehiclesTest.scala b/common/src/test/scala/game/objectcreatevehicle/MountedVehiclesTest.scala
index 4cb2c906..ea206eab 100644
--- a/common/src/test/scala/game/objectcreatevehicle/MountedVehiclesTest.scala
+++ b/common/src/test/scala/game/objectcreatevehicle/MountedVehiclesTest.scala
@@ -32,7 +32,7 @@ class MountedVehiclesTest extends Specification {
vdata.data.bops mustEqual false
vdata.data.alternate mustEqual false
vdata.data.v1 mustEqual false
- vdata.data.v3 mustEqual false
+ vdata.data.jammered mustEqual false
vdata.data.v5.isEmpty mustEqual true
vdata.data.guid mustEqual PlanetSideGUID(3776)
vdata.health mustEqual 255
@@ -57,7 +57,7 @@ class MountedVehiclesTest extends Specification {
a.data.bops mustEqual false
a.data.v1 mustEqual false
a.data.v2.isEmpty mustEqual true
- a.data.v3 mustEqual false
+ a.data.jammered mustEqual false
a.data.v4.isEmpty mustEqual true
a.data.v5.isEmpty mustEqual true
a.exosuit mustEqual ExoSuitType.Agile
diff --git a/common/src/test/scala/game/objectcreatevehicle/NonstandardVehiclesTest.scala b/common/src/test/scala/game/objectcreatevehicle/NonstandardVehiclesTest.scala
index 849e0d3d..ca0aaeec 100644
--- a/common/src/test/scala/game/objectcreatevehicle/NonstandardVehiclesTest.scala
+++ b/common/src/test/scala/game/objectcreatevehicle/NonstandardVehiclesTest.scala
@@ -31,7 +31,7 @@ class NonstandardVehiclesTest extends Specification {
basic.data.alternate mustEqual false
basic.data.v1 mustEqual true
basic.data.v2.isDefined mustEqual false
- basic.data.v3 mustEqual false
+ basic.data.jammered mustEqual false
basic.data.v5.isDefined mustEqual false
basic.data.guid mustEqual PlanetSideGUID(0)
diff --git a/common/src/test/scala/game/objectcreatevehicle/NormalVehiclesTest.scala b/common/src/test/scala/game/objectcreatevehicle/NormalVehiclesTest.scala
index 5d5dcc4c..a0e38289 100644
--- a/common/src/test/scala/game/objectcreatevehicle/NormalVehiclesTest.scala
+++ b/common/src/test/scala/game/objectcreatevehicle/NormalVehiclesTest.scala
@@ -173,7 +173,7 @@ class NormalVehiclesTest extends Specification {
vdata.faction mustEqual PlanetSideEmpire.NC
vdata.alternate mustEqual false
vdata.v1 mustEqual true
- vdata.v3 mustEqual false
+ vdata.jammered mustEqual false
vdata.v5.isEmpty mustEqual true
vdata.guid mustEqual PlanetSideGUID(0)
diff --git a/common/src/test/scala/game/objectcreatevehicle/UtilityVehiclesTest.scala b/common/src/test/scala/game/objectcreatevehicle/UtilityVehiclesTest.scala
index a7e2e093..6ad1af20 100644
--- a/common/src/test/scala/game/objectcreatevehicle/UtilityVehiclesTest.scala
+++ b/common/src/test/scala/game/objectcreatevehicle/UtilityVehiclesTest.scala
@@ -29,7 +29,7 @@ class UtilityVehiclesTest extends Specification {
ant.data.faction mustEqual PlanetSideEmpire.VS
ant.data.alternate mustEqual false
ant.data.v1 mustEqual true
- ant.data.v3 mustEqual false
+ ant.data.jammered mustEqual false
ant.data.v5.isEmpty mustEqual true
ant.data.guid mustEqual PlanetSideGUID(0)
ant.driveState mustEqual DriveState.Mobile
@@ -59,7 +59,7 @@ class UtilityVehiclesTest extends Specification {
ams.data.faction mustEqual PlanetSideEmpire.VS
ams.data.alternate mustEqual false
ams.data.v1 mustEqual false
- ams.data.v3 mustEqual false
+ ams.data.jammered mustEqual false
ams.data.v5.isEmpty mustEqual true
ams.data.guid mustEqual PlanetSideGUID(2885)
ams.driveState mustEqual DriveState.Deployed
diff --git a/common/src/test/scala/objects/DamageModelTests.scala b/common/src/test/scala/objects/DamageModelTests.scala
index bc6e6fad..3409f588 100644
--- a/common/src/test/scala/objects/DamageModelTests.scala
+++ b/common/src/test/scala/objects/DamageModelTests.scala
@@ -324,7 +324,7 @@ class DamageModelTests extends Specification {
tplayer.Armor mustEqual 50
val resprojectile = ResolvedProjectile(ProjectileResolution.Hit, projectile, SourceEntry(tplayer), tplayer.DamageModel, Vector3.Zero)
- val func : (Any)=>Unit = resprojectile.damage_model.Calculate(resprojectile)
+ val func : Any=>ResolvedProjectile = resprojectile.damage_model.Calculate(resprojectile)
func(tplayer)
tplayer.Health mustEqual 87
@@ -338,7 +338,7 @@ class DamageModelTests extends Specification {
tplayer.Armor mustEqual 50
val resprojectile = ResolvedProjectile(ProjectileResolution.Hit, projectile, SourceEntry(tplayer), tplayer.DamageModel, Vector3.Zero)
- val func : (Any)=>Unit = resprojectile.damage_model.Calculate(resprojectile, ProjectileResolution.Splash)
+ val func : Any=>ResolvedProjectile = resprojectile.damage_model.Calculate(resprojectile, ProjectileResolution.Splash)
func(tplayer)
tplayer.Health mustEqual 98
@@ -352,7 +352,7 @@ class DamageModelTests extends Specification {
tplayer.Armor mustEqual 50
val resprojectile = ResolvedProjectile(ProjectileResolution.Hit, projectile, SourceEntry(tplayer), tplayer.DamageModel, Vector3.Zero)
- val func : (Any)=>Unit = resprojectile.damage_model.Calculate(resprojectile)
+ val func : Any=>ResolvedProjectile = resprojectile.damage_model.Calculate(resprojectile)
tplayer.Armor = 0
func(tplayer)
@@ -365,7 +365,7 @@ class DamageModelTests extends Specification {
vehicle.Health mustEqual 650
val resprojectile = ResolvedProjectile(ProjectileResolution.Hit, projectile, SourceEntry(vehicle), vehicle.DamageModel, Vector3.Zero)
- val func : (Any)=>Unit = resprojectile.damage_model.Calculate(resprojectile)
+ val func : Any=>ResolvedProjectile = resprojectile.damage_model.Calculate(resprojectile)
func(vehicle)
vehicle.Health mustEqual 641
@@ -378,7 +378,7 @@ class DamageModelTests extends Specification {
vehicle.Shields mustEqual 10
val resprojectile = ResolvedProjectile(ProjectileResolution.Hit, projectile, SourceEntry(vehicle), vehicle.DamageModel, Vector3.Zero)
- val func : (Any)=>Unit = resprojectile.damage_model.Calculate(resprojectile)
+ val func : Any=>ResolvedProjectile = resprojectile.damage_model.Calculate(resprojectile)
func(vehicle)
vehicle.Health mustEqual 650
@@ -392,7 +392,7 @@ class DamageModelTests extends Specification {
vehicle.Shields mustEqual 10
val resprojectile = ResolvedProjectile(ProjectileResolution.Hit, projectile, SourceEntry(vehicle), vehicle.DamageModel, Vector3.Zero)
- val func : (Any)=>Unit = resprojectile.damage_model.Calculate(resprojectile)
+ val func : Any=>ResolvedProjectile = resprojectile.damage_model.Calculate(resprojectile)
func(vehicle)
vehicle.Health mustEqual 650
@@ -407,7 +407,7 @@ class DamageModelTests extends Specification {
vehicle.Health mustEqual 650
val resprojectile = ResolvedProjectile(ProjectileResolution.Hit, projectile, SourceEntry(vehicle), vehicle.DamageModel, Vector3.Zero)
- val func : (Any)=>Unit = resprojectile.damage_model.Calculate(resprojectile, ProjectileResolution.Splash)
+ val func : Any=>ResolvedProjectile = resprojectile.damage_model.Calculate(resprojectile, ProjectileResolution.Splash)
func(vehicle)
vehicle.Health mustEqual 641
diff --git a/common/src/test/scala/objects/FacilityTurretTest.scala b/common/src/test/scala/objects/FacilityTurretTest.scala
index 8c8ffe30..09f73b9f 100644
--- a/common/src/test/scala/objects/FacilityTurretTest.scala
+++ b/common/src/test/scala/objects/FacilityTurretTest.scala
@@ -19,7 +19,7 @@ import scala.concurrent.duration._
class FacilityTurretTest extends Specification {
"FacilityTurretTest" should {
"define" in {
- val obj = new TurretDefinition(480)
+ val obj = new FacilityTurretDefinition(480)
obj.Weapons mustEqual mutable.HashMap.empty[TurretUpgrade.Value, ToolDefinition]
obj.ReserveAmmunition mustEqual false
obj.FactionLocked mustEqual true
@@ -152,7 +152,7 @@ class FacilityTurretControl3Test extends ActorTest {
class FacilityTurretControl4Test extends ActorTest {
val player = Player(Avatar("", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
- val objDef = new TurretDefinition(480)
+ val objDef = new FacilityTurretDefinition(480)
objDef.FactionLocked = false
val obj = FacilityTurret(objDef)
obj.GUID = PlanetSideGUID(1)
diff --git a/common/src/test/scala/objects/VehicleTest.scala b/common/src/test/scala/objects/VehicleTest.scala
index 99cae765..66a3104d 100644
--- a/common/src/test/scala/objects/VehicleTest.scala
+++ b/common/src/test/scala/objects/VehicleTest.scala
@@ -2,6 +2,7 @@
package objects
import akka.actor.Props
+import akka.testkit.TestProbe
import base.ActorTest
import net.psforever.objects._
import net.psforever.objects.ballistics.{PlayerSource, Projectile, ProjectileResolution, ResolvedProjectile}
@@ -9,9 +10,11 @@ import net.psforever.objects.definition.{SeatDefinition, VehicleDefinition}
import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.vehicles._
import net.psforever.objects.vital.{VehicleShieldCharge, Vitality}
+import net.psforever.objects.zones.{Zone, ZoneMap}
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types._
import org.specs2.mutable._
+import services.vehicle.{VehicleAction, VehicleServiceMessage}
import scala.concurrent.duration._
@@ -324,66 +327,82 @@ class VehicleControlStopMountingTest extends ActorTest {
val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
vehicle.GUID = PlanetSideGUID(3)
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
+ vehicle.Zone = new Zone("test", new ZoneMap("test"), 0) {
+ VehicleEvents = new TestProbe(system).ref //necessary
+ }
+ vehicle.Weapons(2).Equipment.get.GUID = PlanetSideGUID(4)
+ val probe = new TestProbe(system)
- vehicle.Actor ! Mountable.TryMount(player1, 0)
- val reply = receiveOne(Duration.create(100, "ms"))
+ vehicle.Actor.tell(Mountable.TryMount(player1, 0), probe.ref)
+ val reply = probe.receiveOne(Duration.create(200, "ms"))
assert(reply.isInstanceOf[Mountable.MountMessages])
- vehicle.Actor ! Vehicle.PrepareForDeletion
- vehicle.Actor ! Mountable.TryMount(player2, 1)
- expectNoMsg(Duration.create(200, "ms"))
+ vehicle.Actor.tell(Vehicle.PrepareForDeletion(), probe.ref)
+ vehicle.Actor.tell(Mountable.TryMount(player2, 1), probe.ref)
+ probe.expectNoMsg(Duration.create(200, "ms")) //assertion failed: received unexpected message MountMessages(CanMount
}
}
}
class VehicleControlRestartMountingTest extends ActorTest {
+ val player1 = Player(VehicleTest.avatar1)
+ player1.GUID = PlanetSideGUID(1)
+ val player2 = Player(VehicleTest.avatar2)
+ player2.GUID = PlanetSideGUID(2)
+ val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
+ vehicle.GUID = PlanetSideGUID(3)
+ vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
+ vehicle.Zone = new Zone("test", new ZoneMap("test"), 0) {
+ VehicleEvents = new TestProbe(system).ref
+ }
+ vehicle.Weapons(2).Equipment.get.GUID = PlanetSideGUID(4)
+ val probe = new TestProbe(system)
+
"Vehicle Control" should {
"reactivate and resume handling mount messages" in {
- val player1 = Player(VehicleTest.avatar1)
- player1.GUID = PlanetSideGUID(1)
- val player2 = Player(VehicleTest.avatar2)
- player2.GUID = PlanetSideGUID(2)
- val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
- vehicle.GUID = PlanetSideGUID(3)
- vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
+ vehicle.Actor.tell(Mountable.TryMount(player1, 0), probe.ref)
+ probe.receiveOne(Duration.create(200, "ms")) //discard
+ vehicle.Actor.tell(Vehicle.PrepareForDeletion(), probe.ref)
+ vehicle.Actor.tell(Mountable.TryMount(player2, 1), probe.ref)
+ probe.expectNoMsg(Duration.create(200, "ms"))
- vehicle.Actor ! Mountable.TryMount(player1, 0)
- receiveOne(Duration.create(100, "ms")) //discard
- vehicle.Actor ! Vehicle.PrepareForDeletion
- vehicle.Actor ! Mountable.TryMount(player2, 1)
- expectNoMsg(Duration.create(200, "ms"))
-
- vehicle.Actor ! Vehicle.Reactivate
- vehicle.Actor ! Mountable.TryMount(player2, 1)
- val reply = receiveOne(Duration.create(100, "ms"))
+ vehicle.Actor.tell(Vehicle.Reactivate(), probe.ref)
+ vehicle.Actor.tell(Mountable.TryMount(player2, 1), probe.ref)
+ val reply = probe.receiveOne(Duration.create(200, "ms"))
assert(reply.isInstanceOf[Mountable.MountMessages])
}
}
}
class VehicleControlAlwaysDismountTest extends ActorTest {
+ val probe = new TestProbe(system)
+ val player1 = Player(VehicleTest.avatar1)
+ player1.GUID = PlanetSideGUID(1)
+ val player2 = Player(VehicleTest.avatar2)
+ player2.GUID = PlanetSideGUID(2)
+ val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
+ vehicle.GUID = PlanetSideGUID(3)
+ vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
+ vehicle.Zone = new Zone("test", new ZoneMap("test"), 0) {
+ VehicleEvents = new TestProbe(system).ref
+ }
+ vehicle.Weapons(2).Equipment.get.GUID = PlanetSideGUID(4)
+
"Vehicle Control" should {
"always allow dismount messages" in {
- val player1 = Player(VehicleTest.avatar1)
- player1.GUID = PlanetSideGUID(1)
- val player2 = Player(VehicleTest.avatar2)
- player2.GUID = PlanetSideGUID(2)
- val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
- vehicle.GUID = PlanetSideGUID(3)
- vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
- vehicle.Actor ! Mountable.TryMount(player1, 0)
- receiveOne(Duration.create(100, "ms")) //discard
- vehicle.Actor ! Mountable.TryMount(player2, 1)
- receiveOne(Duration.create(100, "ms")) //discard
+ vehicle.Actor.tell(Mountable.TryMount(player1, 0), probe.ref)
+ probe.receiveOne(Duration.create(100, "ms")) //discard
+ vehicle.Actor.tell(Mountable.TryMount(player2, 1), probe.ref)
+ probe.receiveOne(Duration.create(100, "ms")) //discard
- vehicle.Actor ! Mountable.TryDismount(player2, 1) //player2 requests dismount
- val reply1 = receiveOne(Duration.create(100, "ms"))
+ vehicle.Actor.tell(Mountable.TryDismount(player2, 1), probe.ref) //player2 requests dismount
+ val reply1 = probe.receiveOne(Duration.create(100, "ms"))
assert(reply1.isInstanceOf[Mountable.MountMessages])
assert(reply1.asInstanceOf[Mountable.MountMessages].response.isInstanceOf[Mountable.CanDismount]) //player2 dismounts
- vehicle.Actor ! Vehicle.PrepareForDeletion
- vehicle.Actor ! Mountable.TryDismount(player1, 0) //player1 requests dismount
- val reply2 = receiveOne(Duration.create(100, "ms"))
+ vehicle.Actor.tell(Vehicle.PrepareForDeletion(), probe.ref)
+ vehicle.Actor.tell(Mountable.TryDismount(player1, 0), probe.ref) //player1 requests dismount
+ val reply2 = probe.receiveOne(Duration.create(100, "ms"))
assert(reply2.isInstanceOf[Mountable.MountMessages])
assert(reply2.asInstanceOf[Mountable.MountMessages].response.isInstanceOf[Mountable.CanDismount]) //player1 dismounts
}
@@ -391,8 +410,9 @@ class VehicleControlAlwaysDismountTest extends ActorTest {
}
class VehicleControlMountingBlockedExosuitTest extends ActorTest {
+ val probe = new TestProbe(system)
def checkCanNotMount() : Unit = {
- val reply = receiveOne(Duration.create(100, "ms"))
+ val reply = probe.receiveOne(Duration.create(100, "ms"))
reply match {
case msg : Mountable.MountMessages =>
assert(msg.response.isInstanceOf[Mountable.CanNotMount])
@@ -402,7 +422,7 @@ class VehicleControlMountingBlockedExosuitTest extends ActorTest {
}
def checkCanMount() : Unit = {
- val reply = receiveOne(Duration.create(100, "ms"))
+ val reply = probe.receiveOne(Duration.create(100, "ms"))
reply match {
case msg : Mountable.MountMessages =>
assert(msg.response.isInstanceOf[Mountable.CanMount])
@@ -410,49 +430,49 @@ class VehicleControlMountingBlockedExosuitTest extends ActorTest {
assert(false)
}
}
+ val vehicle = Vehicle(GlobalDefinitions.apc_tr)
+ vehicle.GUID = PlanetSideGUID(10)
+ vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
+
+ val player1 = Player(VehicleTest.avatar1)
+ player1.ExoSuit = ExoSuitType.Reinforced
+ player1.GUID = PlanetSideGUID(1)
+ val player2 = Player(VehicleTest.avatar1)
+ player2.ExoSuit = ExoSuitType.MAX
+ player2.GUID = PlanetSideGUID(2)
+ val player3 = Player(VehicleTest.avatar1)
+ player3.ExoSuit = ExoSuitType.Agile
+ player3.GUID = PlanetSideGUID(3)
"Vehicle Control" should {
"block players from sitting if their exo-suit is not allowed by the seat" in {
- val vehicle = Vehicle(GlobalDefinitions.apc_tr)
- vehicle.GUID = PlanetSideGUID(10)
- vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
-
- val player1 = Player(VehicleTest.avatar1)
- player1.ExoSuit = ExoSuitType.Reinforced
- player1.GUID = PlanetSideGUID(1)
- val player2 = Player(VehicleTest.avatar1)
- player2.ExoSuit = ExoSuitType.MAX
- player2.GUID = PlanetSideGUID(2)
- val player3 = Player(VehicleTest.avatar1)
- player3.ExoSuit = ExoSuitType.Agile
- player3.GUID = PlanetSideGUID(3)
-
//disallow
- vehicle.Actor ! Mountable.TryMount(player1, 0) //Reinforced in non-MAX seat
+ vehicle.Actor.tell(Mountable.TryMount(player1, 0), probe.ref) //Reinforced in non-MAX seat
checkCanNotMount()
- vehicle.Actor ! Mountable.TryMount(player2, 0) //MAX in non-Reinforced seat
+ vehicle.Actor.tell(Mountable.TryMount(player2, 0), probe.ref) //MAX in non-Reinforced seat
checkCanNotMount()
- vehicle.Actor ! Mountable.TryMount(player2, 1) //MAX in non-MAX seat
+ vehicle.Actor.tell(Mountable.TryMount(player2, 1), probe.ref) //MAX in non-MAX seat
checkCanNotMount()
- vehicle.Actor ! Mountable.TryMount(player1, 9) //Reinforced in MAX-only seat
+ vehicle.Actor.tell(Mountable.TryMount(player1, 9), probe.ref) //Reinforced in MAX-only seat
checkCanNotMount()
- vehicle.Actor ! Mountable.TryMount(player3, 9) //Agile in MAX-only seat
+ vehicle.Actor.tell(Mountable.TryMount(player3, 9), probe.ref) //Agile in MAX-only seat
checkCanNotMount()
//allow
- vehicle.Actor ! Mountable.TryMount(player1, 1)
+ vehicle.Actor.tell(Mountable.TryMount(player1, 1), probe.ref)
checkCanMount()
- vehicle.Actor ! Mountable.TryMount(player2, 9)
+ vehicle.Actor.tell(Mountable.TryMount(player2, 9), probe.ref)
checkCanMount()
- vehicle.Actor ! Mountable.TryMount(player3, 0)
+ vehicle.Actor.tell(Mountable.TryMount(player3, 0), probe.ref)
checkCanMount()
}
}
}
class VehicleControlMountingBlockedSeatPermissionTest extends ActorTest {
+ val probe = new TestProbe(system)
def checkCanNotMount() : Unit = {
- val reply = receiveOne(Duration.create(100, "ms"))
+ val reply = probe.receiveOne(Duration.create(100, "ms"))
reply match {
case msg : Mountable.MountMessages =>
assert(msg.response.isInstanceOf[Mountable.CanNotMount])
@@ -462,7 +482,7 @@ class VehicleControlMountingBlockedSeatPermissionTest extends ActorTest {
}
def checkCanMount() : Unit = {
- val reply = receiveOne(Duration.create(100, "ms"))
+ val reply = probe.receiveOne(Duration.create(100, "ms"))
reply match {
case msg : Mountable.MountMessages =>
assert(msg.response.isInstanceOf[Mountable.CanMount])
@@ -470,32 +490,33 @@ class VehicleControlMountingBlockedSeatPermissionTest extends ActorTest {
assert(false)
}
}
+ val vehicle = Vehicle(GlobalDefinitions.apc_tr)
+ vehicle.GUID = PlanetSideGUID(10)
+ vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
+
+ val player1 = Player(VehicleTest.avatar1)
+ player1.GUID = PlanetSideGUID(1)
+ val player2 = Player(VehicleTest.avatar1)
+ player2.GUID = PlanetSideGUID(2)
"Vehicle Control" should {
//11 June 2018: Group is not supported yet so do not bother testing it
"block players from sitting if the seat does not allow it" in {
- val vehicle = Vehicle(GlobalDefinitions.apc_tr)
- vehicle.GUID = PlanetSideGUID(10)
- vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
-
- val player1 = Player(VehicleTest.avatar1)
- player1.GUID = PlanetSideGUID(1)
- val player2 = Player(VehicleTest.avatar1)
- player2.GUID = PlanetSideGUID(2)
vehicle.PermissionGroup(2,3) //passenger group -> empire
- vehicle.Actor ! Mountable.TryMount(player1, 3) //passenger seat
+ vehicle.Actor.tell(Mountable.TryMount(player1, 3), probe.ref) //passenger seat
checkCanMount()
vehicle.PermissionGroup(2,0) //passenger group -> locked
- vehicle.Actor ! Mountable.TryMount(player2, 4) //passenger seat
+ vehicle.Actor.tell(Mountable.TryMount(player2, 4), probe.ref) //passenger seat
checkCanNotMount()
}
}
}
class VehicleControlMountingDriverSeatTest extends ActorTest {
+ val probe = new TestProbe(system)
def checkCanMount() : Unit = {
- val reply = receiveOne(Duration.create(100, "ms"))
+ val reply = probe.receiveOne(Duration.create(100, "ms"))
reply match {
case msg : Mountable.MountMessages =>
assert(msg.response.isInstanceOf[Mountable.CanMount])
@@ -503,19 +524,18 @@ class VehicleControlMountingDriverSeatTest extends ActorTest {
assert(false)
}
}
+ val vehicle = Vehicle(GlobalDefinitions.apc_tr)
+ vehicle.GUID = PlanetSideGUID(10)
+ vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
+ val player1 = Player(VehicleTest.avatar1)
+ player1.GUID = PlanetSideGUID(1)
"Vehicle Control" should {
"allow players to sit in the driver seat, even if it is locked, if the vehicle is unowned" in {
- val vehicle = Vehicle(GlobalDefinitions.apc_tr)
- vehicle.GUID = PlanetSideGUID(10)
- vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
- val player1 = Player(VehicleTest.avatar1)
- player1.GUID = PlanetSideGUID(1)
-
assert(vehicle.PermissionGroup(0).contains(VehicleLockState.Locked)) //driver group -> locked
assert(vehicle.Seats(0).Occupant.isEmpty)
assert(vehicle.Owner.isEmpty)
- vehicle.Actor ! Mountable.TryMount(player1, 0)
+ vehicle.Actor.tell(Mountable.TryMount(player1, 0), probe.ref)
checkCanMount()
assert(vehicle.Seats(0).Occupant.nonEmpty)
}
@@ -523,8 +543,9 @@ class VehicleControlMountingDriverSeatTest extends ActorTest {
}
class VehicleControlMountingOwnedLockedDriverSeatTest extends ActorTest {
+ val probe = new TestProbe(system)
def checkCanNotMount() : Unit = {
- val reply = receiveOne(Duration.create(100, "ms"))
+ val reply = probe.receiveOne(Duration.create(100, "ms"))
reply match {
case msg : Mountable.MountMessages =>
assert(msg.response.isInstanceOf[Mountable.CanNotMount])
@@ -534,7 +555,7 @@ class VehicleControlMountingOwnedLockedDriverSeatTest extends ActorTest {
}
def checkCanMount() : Unit = {
- val reply = receiveOne(Duration.create(100, "ms"))
+ val reply = probe.receiveOne(Duration.create(100, "ms"))
reply match {
case msg : Mountable.MountMessages =>
assert(msg.response.isInstanceOf[Mountable.CanMount])
@@ -542,30 +563,28 @@ class VehicleControlMountingOwnedLockedDriverSeatTest extends ActorTest {
assert(false)
}
}
+ val vehicle = Vehicle(GlobalDefinitions.apc_tr)
+ vehicle.GUID = PlanetSideGUID(10)
+ vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
+ val player1 = Player(VehicleTest.avatar1)
+ player1.GUID = PlanetSideGUID(1)
+ val player2 = Player(VehicleTest.avatar1)
+ player2.GUID = PlanetSideGUID(2)
"Vehicle Control" should {
"block players that are not the current owner from sitting in the driver seat (locked)" in {
- val vehicle = Vehicle(GlobalDefinitions.apc_tr)
- vehicle.GUID = PlanetSideGUID(10)
- vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
-
- val player1 = Player(VehicleTest.avatar1)
- player1.GUID = PlanetSideGUID(1)
- val player2 = Player(VehicleTest.avatar1)
- player2.GUID = PlanetSideGUID(2)
-
assert(vehicle.PermissionGroup(0).contains(VehicleLockState.Locked)) //driver group -> locked
assert(vehicle.Seats(0).Occupant.isEmpty)
vehicle.Owner = player1.GUID
- vehicle.Actor ! Mountable.TryMount(player1, 0)
+ vehicle.Actor.tell(Mountable.TryMount(player1, 0), probe.ref)
checkCanMount()
assert(vehicle.Seats(0).Occupant.nonEmpty)
- vehicle.Actor ! Mountable.TryDismount(player1, 0)
- receiveOne(Duration.create(100, "ms")) //discard
+ vehicle.Actor.tell(Mountable.TryDismount(player1, 0), probe.ref)
+ probe.receiveOne(Duration.create(100, "ms")) //discard
assert(vehicle.Seats(0).Occupant.isEmpty)
- vehicle.Actor ! Mountable.TryMount(player2, 0)
+ vehicle.Actor.tell(Mountable.TryMount(player2, 0), probe.ref)
checkCanNotMount()
assert(vehicle.Seats(0).Occupant.isEmpty)
}
@@ -573,8 +592,9 @@ class VehicleControlMountingOwnedLockedDriverSeatTest extends ActorTest {
}
class VehicleControlMountingOwnedUnlockedDriverSeatTest extends ActorTest {
+ val probe = new TestProbe(system)
def checkCanMount() : Unit = {
- val reply = receiveOne(Duration.create(100, "ms"))
+ val reply = probe.receiveOne(Duration.create(100, "ms"))
reply match {
case msg : Mountable.MountMessages =>
assert(msg.response.isInstanceOf[Mountable.CanMount])
@@ -582,31 +602,29 @@ class VehicleControlMountingOwnedUnlockedDriverSeatTest extends ActorTest {
assert(false)
}
}
+ val vehicle = Vehicle(GlobalDefinitions.apc_tr)
+ vehicle.GUID = PlanetSideGUID(10)
+ vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
+ val player1 = Player(VehicleTest.avatar1)
+ player1.GUID = PlanetSideGUID(1)
+ val player2 = Player(VehicleTest.avatar1)
+ player2.GUID = PlanetSideGUID(2)
"Vehicle Control" should {
"allow players that are not the current owner to sit in the driver seat (empire)" in {
- val vehicle = Vehicle(GlobalDefinitions.apc_tr)
- vehicle.GUID = PlanetSideGUID(10)
- vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
-
- val player1 = Player(VehicleTest.avatar1)
- player1.GUID = PlanetSideGUID(1)
- val player2 = Player(VehicleTest.avatar1)
- player2.GUID = PlanetSideGUID(2)
-
vehicle.PermissionGroup(0,3) //passenger group -> empire
assert(vehicle.PermissionGroup(0).contains(VehicleLockState.Empire)) //driver group -> empire
assert(vehicle.Seats(0).Occupant.isEmpty)
vehicle.Owner = player1.GUID //owner set
- vehicle.Actor ! Mountable.TryMount(player1, 0)
+ vehicle.Actor.tell(Mountable.TryMount(player1, 0), probe.ref)
checkCanMount()
assert(vehicle.Seats(0).Occupant.nonEmpty)
- vehicle.Actor ! Mountable.TryDismount(player1, 0)
- receiveOne(Duration.create(100, "ms")) //discard
+ vehicle.Actor.tell(Mountable.TryDismount(player1, 0), probe.ref)
+ probe.receiveOne(Duration.create(100, "ms")) //discard
assert(vehicle.Seats(0).Occupant.isEmpty)
- vehicle.Actor ! Mountable.TryMount(player2, 0)
+ vehicle.Actor.tell(Mountable.TryMount(player2, 0), probe.ref)
checkCanMount()
assert(vehicle.Seats(0).Occupant.nonEmpty)
}
@@ -614,26 +632,37 @@ class VehicleControlMountingOwnedUnlockedDriverSeatTest extends ActorTest {
}
class VehicleControlShieldsChargingTest extends ActorTest {
+ val probe = new TestProbe(system)
val vehicle = Vehicle(GlobalDefinitions.fury)
vehicle.GUID = PlanetSideGUID(10)
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
+ vehicle.Zone = new Zone("test", new ZoneMap("test"), 0) {
+ VehicleEvents = probe.ref
+ }
"charge vehicle shields" in {
assert(vehicle.Shields == 0)
assert(!vehicle.History.exists({p => p.isInstanceOf[VehicleShieldCharge]}))
- vehicle.Actor ! Vehicle.ChargeShields(15)
- val msg = receiveOne(500 milliseconds)
- assert(msg.isInstanceOf[Vehicle.UpdateShieldsCharge])
+ vehicle.Actor ! Vehicle.ChargeShields(15)
+ val msg = probe.receiveOne(500 milliseconds)
+ assert(msg match {
+ case VehicleServiceMessage(_, VehicleAction.PlanetsideAttribute(_, PlanetSideGUID(10), 68, 15)) => true
+ case _ => false
+ })
assert(vehicle.Shields == 15)
assert(vehicle.History.exists({p => p.isInstanceOf[VehicleShieldCharge]}))
}
}
class VehicleControlShieldsNotChargingVehicleDeadTest extends ActorTest {
+ val probe = new TestProbe(system)
val vehicle = Vehicle(GlobalDefinitions.fury)
vehicle.GUID = PlanetSideGUID(10)
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
+ vehicle.Zone = new Zone("test", new ZoneMap("test"), 0) {
+ VehicleEvents = probe.ref
+ }
"not charge vehicle shields if the vehicle is destroyed" in {
assert(vehicle.Health > 0)
@@ -641,18 +670,22 @@ class VehicleControlShieldsNotChargingVehicleDeadTest extends ActorTest {
assert(vehicle.Health == 0)
assert(vehicle.Shields == 0)
assert(!vehicle.History.exists({p => p.isInstanceOf[VehicleShieldCharge]}))
- vehicle.Actor ! Vehicle.ChargeShields(15)
+ vehicle.Actor.tell(Vehicle.ChargeShields(15), probe.ref)
- expectNoMsg(1 seconds)
+ probe.expectNoMsg(1 seconds)
assert(vehicle.Shields == 0)
assert(!vehicle.History.exists({p => p.isInstanceOf[VehicleShieldCharge]}))
}
}
class VehicleControlShieldsNotChargingVehicleShieldsFullTest extends ActorTest {
+ val probe = new TestProbe(system)
val vehicle = Vehicle(GlobalDefinitions.fury)
vehicle.GUID = PlanetSideGUID(10)
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
+ vehicle.Zone = new Zone("test", new ZoneMap("test"), 0) {
+ VehicleEvents = probe.ref
+ }
"not charge vehicle shields if the vehicle is destroyed" in {
assert(vehicle.Shields == 0)
@@ -661,54 +694,67 @@ class VehicleControlShieldsNotChargingVehicleShieldsFullTest extends ActorTest {
assert(!vehicle.History.exists({p => p.isInstanceOf[VehicleShieldCharge]}))
vehicle.Actor ! Vehicle.ChargeShields(15)
- expectNoMsg(1 seconds)
+ probe.expectNoMsg(1 seconds)
assert(!vehicle.History.exists({p => p.isInstanceOf[VehicleShieldCharge]}))
}
}
class VehicleControlShieldsNotChargingTooEarlyTest extends ActorTest {
+ val probe = new TestProbe(system)
val vehicle = Vehicle(GlobalDefinitions.fury)
vehicle.GUID = PlanetSideGUID(10)
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
+ vehicle.Zone = new Zone("test", new ZoneMap("test"), 0) {
+ VehicleEvents = probe.ref
+ }
"charge vehicle shields" in {
assert(vehicle.Shields == 0)
- vehicle.Actor ! Vehicle.ChargeShields(15)
- val msg = receiveOne(200 milliseconds)
- assert(msg.isInstanceOf[Vehicle.UpdateShieldsCharge])
+ vehicle.Actor ! Vehicle.ChargeShields(15)
+ val msg = probe.receiveOne(200 milliseconds)
+ //assert(msg.isInstanceOf[Vehicle.UpdateShieldsCharge])
+ assert(msg match {
+ case VehicleServiceMessage(_, VehicleAction.PlanetsideAttribute(_, PlanetSideGUID(10), 68, 15)) => true
+ case _ => false
+ })
assert(vehicle.Shields == 15)
- vehicle.Actor ! Vehicle.ChargeShields(15)
- expectNoMsg(200 milliseconds)
+ vehicle.Actor ! Vehicle.ChargeShields(15)
+ probe.expectNoMsg(200 milliseconds)
assert(vehicle.Shields == 15)
}
}
-class VehicleControlShieldsNotChargingDamagedTest extends ActorTest {
- val vehicle = Vehicle(GlobalDefinitions.fury)
- vehicle.GUID = PlanetSideGUID(10)
- vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
- //
- val beamer_wep = Tool(GlobalDefinitions.beamer)
- val p_source = PlayerSource( Player(Avatar("TestTarget", PlanetSideEmpire.NC, CharacterGender.Female, 1, CharacterVoice.Mute)) )
- val projectile = Projectile(beamer_wep.Projectile, GlobalDefinitions.beamer, beamer_wep.FireMode, p_source, GlobalDefinitions.beamer.ObjectId, Vector3.Zero, Vector3.Zero)
- val fury_dm = Vehicle(GlobalDefinitions.fury).DamageModel
- val obj = ResolvedProjectile(ProjectileResolution.Hit, projectile, p_source, fury_dm, Vector3(1.2f, 3.4f, 5.6f), System.nanoTime)
-
- "not charge vehicle shields if recently damaged" in {
- assert(vehicle.Shields == 0)
- vehicle.Actor ! Vitality.Damage({case v : Vehicle => v.History(obj)})
-
- val msg = receiveOne(200 milliseconds)
- assert(msg.isInstanceOf[Vitality.DamageResolution])
- assert(vehicle.Shields == 0)
- vehicle.Actor ! Vehicle.ChargeShields(15)
-
- expectNoMsg(200 milliseconds)
- assert(vehicle.Shields == 0)
- }
-}
+//TODO implement message protocol for zone startup completion
+//class VehicleControlShieldsNotChargingDamagedTest extends ActorTest {
+// val probe = new TestProbe(system)
+// val vehicle = Vehicle(GlobalDefinitions.fury)
+// vehicle.GUID = PlanetSideGUID(10)
+// vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
+// vehicle.Zone = new Zone("test", new ZoneMap("test"), 0) {
+// VehicleEvents = probe.ref
+// }
+// //
+// val beamer_wep = Tool(GlobalDefinitions.beamer)
+// val p_source = PlayerSource( Player(Avatar("TestTarget", PlanetSideEmpire.NC, CharacterGender.Female, 1, CharacterVoice.Mute)) )
+// val projectile = Projectile(beamer_wep.Projectile, GlobalDefinitions.beamer, beamer_wep.FireMode, p_source, GlobalDefinitions.beamer.ObjectId, Vector3.Zero, Vector3.Zero)
+// val fury_dm = Vehicle(GlobalDefinitions.fury).DamageModel
+// val obj = ResolvedProjectile(ProjectileResolution.Hit, projectile, p_source, fury_dm, Vector3(1.2f, 3.4f, 5.6f), System.nanoTime)
+//
+// "not charge vehicle shields if recently damaged" in {
+// assert(vehicle.Shields == 0)
+// vehicle.Actor.tell(Vitality.Damage({case v : Vehicle => v.History(obj); obj }), probe.ref)
+//
+// val msg = probe.receiveOne(200 milliseconds)
+// assert(msg.isInstanceOf[Vitality.DamageResolution])
+// assert(vehicle.Shields == 0)
+// vehicle.Actor.tell(Vehicle.ChargeShields(15), probe.ref)
+//
+// probe.expectNoMsg(200 milliseconds)
+// assert(vehicle.Shields == 0)
+// }
+//}
object VehicleTest {
import net.psforever.objects.Avatar
diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala
index 882d1a24..f7e5c9d3 100644
--- a/pslogin/src/main/scala/WorldSessionActor.scala
+++ b/pslogin/src/main/scala/WorldSessionActor.scala
@@ -71,7 +71,9 @@ import services.support.SupportActor
import scala.collection.mutable
-class WorldSessionActor extends Actor with MDCContextAware {
+class WorldSessionActor extends Actor
+ with MDCContextAware
+ with JammableBehavior {
import WorldSessionActor._
private[this] val log = org.log4s.getLogger
@@ -151,6 +153,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
var timeDL : Long = 0
var timeSurge : Long = 0
+ var skipStaminaRegenForTurns : Int = 0
lazy val unsignedIntMaxValue : Long = Int.MaxValue.toLong * 2L + 1L
var serverTime : Long = 0
@@ -174,12 +177,13 @@ class WorldSessionActor extends Actor with MDCContextAware {
import scala.language.implicitConversions
implicit def boolToInt(b : Boolean) : Int = if(b) 1 else 0
+ def JammableObject = player
+
override def postStop() : Unit = {
//TODO normally, player avatar persists a minute or so after disconnect; we are subject to the SessionReaper
clientKeepAlive.cancel
reviveTimer.cancel
respawnTimer.cancel
- PlayerActionsToCancel()
chatService ! Service.Leave()
continent.AvatarEvents ! Service.Leave()
continent.LocalEvents ! Service.Leave()
@@ -187,6 +191,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
galaxyService ! Service.Leave()
LivePlayerList.Remove(sessionId)
if(player != null && player.HasGUID) {
+ PlayerActionsToCancel()
squadService ! Service.Leave(Some(player.CharId.toString))
val player_guid = player.GUID
//handle orphaned deployables
@@ -316,7 +321,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
context.stop(self)
}
- def Started : Receive = {
+ def Started : Receive = jammableBehavior.orElse {
case ServiceManager.LookupResult("chat", endpoint) =>
chatService = endpoint
log.info("ID: " + sessionId + " Got chat service " + endpoint)
@@ -909,18 +914,11 @@ class WorldSessionActor extends Actor with MDCContextAware {
continent.Deployables ! Zone.Deployable.Dismiss(obj)
}
- case WorldSessionActor.FinalizeDeployable(obj : TurretDeployable, tool, index) =>
- //spitfires and deployable field turrets
- StartBundlingPackets()
- DeployableBuildActivity(obj)
- CommonDestroyConstructionItem(tool, index)
- FindReplacementConstructionItem(tool, index)
- StopBundlingPackets()
-
- case WorldSessionActor.FinalizeDeployable(obj : ComplexDeployable, tool, index) =>
- //deployable_shield_generator
+ case WorldSessionActor.FinalizeDeployable(obj : SensorDeployable, tool, index) =>
+ //motion alarm sensor and sensor disruptor
StartBundlingPackets()
DeployableBuildActivity(obj)
+ continent.LocalEvents ! LocalServiceMessage(continent.Id, LocalAction.TriggerEffectInfo(player.GUID, "on", obj.GUID, true, 1000))
CommonDestroyConstructionItem(tool, index)
FindReplacementConstructionItem(tool, index)
StopBundlingPackets()
@@ -955,11 +953,10 @@ class WorldSessionActor extends Actor with MDCContextAware {
FindReplacementConstructionItem(tool, index)
StopBundlingPackets()
- case WorldSessionActor.FinalizeDeployable(obj : SensorDeployable, tool, index) =>
- //motion alarm sensor and sensor disruptor
+ case WorldSessionActor.FinalizeDeployable(obj : ComplexDeployable, tool, index) =>
+ //spitfires and deployable field turrets and the deployable_shield_generator
StartBundlingPackets()
DeployableBuildActivity(obj)
- continent.LocalEvents ! LocalServiceMessage(continent.Id, LocalAction.TriggerEffectInfo(player.GUID, "on", obj.GUID, true, 1000))
CommonDestroyConstructionItem(tool, index)
FindReplacementConstructionItem(tool, index)
StopBundlingPackets()
@@ -1011,7 +1008,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
StartBundlingPackets()
sendResponse(GenericObjectActionMessage(guid, 21)) //reset build cooldown
sendResponse(ObjectDeployedMessage.Failure(definition.Name))
- log.warn(s"FinalizeDeployable: deployable ${definition.asInstanceOf[DeployableDefinition].Item}@$guid not handled by specific case")
+ log.warn(s"FinalizeDeployable: deployable ${definition.asInstanceOf[SimpleDeployableDefinition].Item}@$guid not handled by specific case")
log.warn(s"FinalizeDeployable: deployable will be cleaned up, but may not get unregistered properly")
TryDropConstructionTool(tool, index, obj.Position)
obj.Position = Vector3.Zero
@@ -1100,10 +1097,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
case HackingProgress(progressType, tplayer, target, tool_guid, delta, completeAction, tickAction) =>
HandleHackingProgress(progressType, tplayer, target, tool_guid, delta, completeAction, tickAction)
- case Vitality.DamageResolution(target : Vehicle) =>
- HandleVehicleDamageResolution(target)
-
- case Vitality.DamageResolution(target : TrapDeployable) =>
+ case Vitality.DamageResolution(target : TrapDeployable, _) =>
//tank_traps
val guid = target.GUID
val health = target.Health
@@ -1112,17 +1106,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
AnnounceDestroyDeployable(target, None)
}
- case Vitality.DamageResolution(target : SensorDeployable) =>
- //sensors
- val guid = target.GUID
- val health = target.Health
- continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(guid, 0, health))
- if(health <= 0) {
- AnnounceDestroyDeployable(target, Some(0 seconds))
- }
-
- case Vitality.DamageResolution(target : SimpleDeployable) =>
- //boomers, mines
+ case Vitality.DamageResolution(target : TelepadDeployable, _) =>
+ //telepads
if(target.Health <= 0) {
//update if destroyed
val guid = target.GUID
@@ -1130,27 +1115,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
AnnounceDestroyDeployable(target, Some(0 seconds))
}
- case Vitality.DamageResolution(target : TurretDeployable) =>
- HandleTurretDeployableDamageResolution(target)
-
- case Vitality.DamageResolution(target : ComplexDeployable) =>
- //shield_generators
- val health = target.Health
- val guid = target.GUID
- continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(guid, 0, health))
- if(health <= 0) {
- AnnounceDestroyDeployable(target, None)
- }
-
- case Vitality.DamageResolution(target : FacilityTurret) =>
- HandleFacilityTurretDamageResolution(target)
-
- case Vitality.DamageResolution(target : PlanetSideGameObject) =>
+ case Vitality.DamageResolution(target : PlanetSideGameObject, _) =>
log.warn(s"Vital target ${target.Definition.Name} damage resolution not supported using this method")
- case Vehicle.UpdateShieldsCharge(vehicle) =>
- continent.VehicleEvents ! VehicleServiceMessage(s"${vehicle.Actor}", VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), vehicle.GUID, 68, vehicle.Shields))
-
case ResponseToSelf(pkt) =>
log.info(s"Received a direct message: $pkt")
sendResponse(pkt)
@@ -1246,22 +1213,19 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(GenericObjectActionMessage(guid, 9))
case AvatarResponse.EnvironmentalDamage(target, amount) =>
- if(player.isAlive) {
+ if(player.isAlive && amount != 0) {
+ val armor = player.Armor
+ val capacitor = player.Capacitor
val originalHealth = player.Health
- player.Health = player.Health - amount
+ player.Health = originalHealth - amount
sendResponse(PlanetsideAttributeMessage(target, 0, player.Health))
continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(target, 0, player.Health))
- damageLog.info(s"${player.Name}-infantry: BEFORE=$originalHealth, AFTER=${player.Health}, CHANGE=$amount")
- if(amount != 0) {
- val playerGUID = player.GUID
- sendResponse(PlanetsideAttributeMessage(playerGUID, 0, player.Health))
- continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(playerGUID, 0, player.Health))
- if(player.Health == 0 && player.isAlive) {
- KillPlayer(player)
- }
- else {
- //todo: History?
- }
+ damageLog.info(s"${player.Name}-infantry: BEFORE=$originalHealth/$armor/$capacitor, AFTER=${player.Health}/$armor/$capacitor, CHANGE=$amount/0/0")
+ if(player.Health == 0 && player.isAlive) {
+ KillPlayer(player)
+ }
+ else {
+ //todo: History?
}
}
@@ -1270,26 +1234,24 @@ class WorldSessionActor extends Actor with MDCContextAware {
val originalHealth = player.Health
val originalArmor = player.Armor
val originalCapacitor = player.Capacitor.toInt
- resolution_function(target)
+ val cause = resolution_function(target)
val health = player.Health
val armor = player.Armor
val capacitor = player.Capacitor.toInt
val damageToHealth = originalHealth - health
val damageToArmor = originalArmor - armor
val damageToCapacitor = originalCapacitor - capacitor
- damageLog.info(s"${player.Name}-infantry: BEFORE=$originalHealth/$originalArmor/$originalCapacitor, AFTER=$health/$armor/$capacitor, CHANGE=$damageToHealth/$damageToArmor/$damageToCapacitor")
if(damageToHealth != 0 || damageToArmor != 0 || damageToCapacitor != 0) {
+ damageLog.info(s"${player.Name}-infantry: BEFORE=$originalHealth/$originalArmor/$originalCapacitor, AFTER=$health/$armor/$capacitor, CHANGE=$damageToHealth/$damageToArmor/$damageToCapacitor")
val playerGUID = player.GUID
if(damageToHealth > 0) {
sendResponse(PlanetsideAttributeMessage(playerGUID, 0, health))
continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(playerGUID, 0, health))
}
-
if(damageToArmor > 0) {
sendResponse(PlanetsideAttributeMessage(playerGUID, 4, armor))
continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(playerGUID, 4, armor))
}
-
if(damageToCapacitor > 0) {
sendResponse(PlanetsideAttributeMessage(playerGUID, 7, capacitor))
}
@@ -1301,10 +1263,10 @@ class WorldSessionActor extends Actor with MDCContextAware {
KillPlayer(player)
}
else {
- //first damage entry -> most recent damage source -> killing blow
- target.History.find(p => p.isInstanceOf[DamagingActivity]) match {
- case Some(data : DamageFromProjectile) =>
- val owner = data.data.projectile.owner
+ //damage cause -> recent damage source -> killing blow?
+ cause match {
+ case data : ResolvedProjectile =>
+ val owner = data.projectile.owner
owner match {
case pSource : PlayerSource =>
continent.LivePlayers.find(_.Name == pSource.Name) match {
@@ -1316,11 +1278,14 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(DamageWithPositionMessage(damageToHealth + damageToArmor, vSource.Position))
case _ => ;
}
- continent.Activity ! Zone.HotSpot.Activity(owner, data.Target, target.Position)
+ continent.Activity ! Zone.HotSpot.Activity(owner, data.target, target.Position)
case _ => ;
}
}
}
+ if(target.isAlive && cause.projectile.profile.JammerProjectile) {
+ self ! JammableUnit.Jammered(cause)
+ }
}
case AvatarResponse.Destroy(victim, killer, weapon, pos) =>
@@ -1459,13 +1424,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
case AvatarResponse.ProjectileState(projectile_guid, shot_pos, shot_vel, shot_orient, seq, end, target_guid) =>
if(tplayer_guid != guid) {
sendResponse(ProjectileStateMessage(projectile_guid, shot_pos, shot_vel, shot_orient, seq, end, target_guid))
-// continent.GUID(projectile_guid) match {
-// case Some(obj) =>
-// val definition = obj.Definition
-// sendResponse(ObjectCreateMessage(definition.ObjectId, projectile_guid, definition.Packet.ConstructorData(obj).get))
-// case _ => ;
-// }
- }
+ }
case AvatarResponse.PutDownFDU(target) =>
if(tplayer_guid != guid) {
@@ -1556,6 +1515,19 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(DeployableObjectsInfoMessage(behavior, deployInfo))
}
+ case LocalResponse.Detonate(guid, obj : BoomerDeployable) =>
+ sendResponse(TriggerEffectMessage(guid, "detonate_boomer"))
+ sendResponse(PlanetsideAttributeMessage(guid, 29, 1))
+ sendResponse(ObjectDeleteMessage(guid, 0))
+
+ case LocalResponse.Detonate(guid, obj : ExplosiveDeployable) =>
+ sendResponse(GenericObjectActionMessage(guid, 19))
+ sendResponse(PlanetsideAttributeMessage(guid, 29, 1))
+ sendResponse(ObjectDeleteMessage(guid, 0))
+
+ case LocalResponse.Detonate(guid, obj) =>
+ log.warn(s"LocalResponse.Detonate: ${obj.Definition.Name} not configured to explode correctly")
+
case LocalResponse.DoorOpens(door_guid) =>
if(tplayer_guid != guid) {
sendResponse(GenericObjectStateMsg(door_guid, 16))
@@ -1572,6 +1544,14 @@ class WorldSessionActor extends Actor with MDCContextAware {
DeconstructDeployable(obj, guid, pos, obj.Orientation, if(obj.MountPoints.isEmpty) 2 else 1)
}
+ case LocalResponse.EliminateDeployable(obj : ExplosiveDeployable, guid, pos) =>
+ if(obj.Exploded || obj.Jammed || obj.Health == 0) {
+ DeconstructDeployable(obj, guid, pos)
+ }
+ else {
+ DeconstructDeployable(obj, guid, pos, obj.Orientation, 2)
+ }
+
case LocalResponse.EliminateDeployable(obj : ComplexDeployable, guid, pos) =>
if(obj.Health == 0) {
DeconstructDeployable(obj, guid, pos)
@@ -1580,14 +1560,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
DeconstructDeployable(obj, guid, pos, obj.Orientation, 1)
}
- case LocalResponse.EliminateDeployable(obj : ExplosiveDeployable, guid, pos) =>
- if(obj.Exploded || obj.Health == 0) {
- DeconstructDeployable(obj, guid, pos)
- }
- else {
- DeconstructDeployable(obj, guid, pos, obj.Orientation, 2)
- }
-
case LocalResponse.EliminateDeployable(obj : TelepadDeployable, guid, pos) =>
//if active, deactivate
if(obj.Active) {
@@ -2458,7 +2430,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
case VehicleResponse.ConcealPlayer(player_guid) =>
sendResponse(GenericObjectActionMessage(player_guid, 9))
- //sendResponse(PlanetsideAttributeMessage(player_guid, 29, 1))
case VehicleResponse.DismountVehicle(bailType, wasKickedByDriver) =>
if(tplayer_guid != guid) {
@@ -2934,189 +2905,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
msgs
}
- /**
- * na
- * @param target na
- */
- def HandleVehicleDamageResolution(target : Vehicle) : Unit = {
- val targetGUID = target.GUID
- val playerGUID = player.GUID
- val players = target.Seats.values.filter(seat => {
- seat.isOccupied && seat.Occupant.get.isAlive
- })
- target.LastShot match { //TODO: collision damage from/in history
- case Some(shot) =>
- if(target.Health > 0) {
- //activity on map
- continent.Activity ! Zone.HotSpot.Activity(shot.target, shot.projectile.owner, shot.hit_pos)
- //alert occupants to damage source
- HandleVehicleDamageAwareness(target, playerGUID, shot)
- }
- else {
- //alert to vehicle death (hence, occupants' deaths)
- HandleVehicleDestructionAwareness(target, shot)
- }
- continent.VehicleEvents ! VehicleServiceMessage(continent.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, targetGUID, 0, target.Health))
- continent.VehicleEvents ! VehicleServiceMessage(s"${target.Actor}", VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, targetGUID, 68, target.Shields))
- case None => ;
- }
- }
-
- /**
- * na
- * @param target na
- * @param attribution na
- * @param lastShot na
- */
- def HandleVehicleDamageAwareness(target : Vehicle, attribution : PlanetSideGUID, lastShot : ResolvedProjectile) : Unit = {
- //alert occupants to damage source
- target.Seats.values.filter(seat => {
- seat.isOccupied && seat.Occupant.get.isAlive
- }).foreach(seat => {
- val tplayer = seat.Occupant.get
- continent.AvatarEvents ! AvatarServiceMessage(tplayer.Name, AvatarAction.HitHint(attribution, tplayer.GUID))
- })
- //alert cargo occupants to damage source
- target.CargoHolds.values.foreach(hold => {
- hold.Occupant match {
- case Some(cargo) =>
- cargo.Health = 0
- cargo.Shields = 0
- cargo.History(lastShot)
- HandleVehicleDamageAwareness(cargo, attribution, lastShot)
- case None => ;
- }
- })
- }
-
- /**
- * na
- * @param target na
- * @param attribution na
- * @param lastShot na
- */
- def HandleVehicleDestructionAwareness(target : Vehicle, lastShot : ResolvedProjectile) : Unit = {
- val playerGUID = player.GUID
- val continentId = continent.Id
- //alert to vehicle death (hence, occupants' deaths)
- target.Seats.values.filter(seat => {
- seat.isOccupied && seat.Occupant.get.isAlive
- }).foreach(seat => {
- val tplayer = seat.Occupant.get
- val tplayerGUID = tplayer.GUID
- continent.AvatarEvents ! AvatarServiceMessage(tplayer.Name, AvatarAction.KilledWhileInVehicle(tplayerGUID))
- continent.AvatarEvents ! AvatarServiceMessage(continentId, AvatarAction.ObjectDelete(tplayerGUID, tplayerGUID)) //dead player still sees self
- })
- //vehicle wreckage has no weapons
- target.Weapons.values
- .filter {
- _.Equipment.nonEmpty
- }
- .foreach(slot => {
- val wep = slot.Equipment.get
- continent.AvatarEvents ! AvatarServiceMessage(continentId, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, wep.GUID))
- })
- target.CargoHolds.values.foreach(hold => {
- hold.Occupant match {
- case Some(cargo) =>
- cargo.Health = 0
- cargo.Shields = 0
- cargo.Position += Vector3.z(1)
- cargo.History(lastShot) //necessary to kill cargo vehicle occupants //TODO: collision damage
- HandleVehicleDestructionAwareness(cargo, lastShot) //might cause redundant packets
- case None => ;
- }
- })
- target.Definition match {
- case GlobalDefinitions.ams =>
- target.Actor ! Deployment.TryDeploymentChange(DriveState.Undeploying)
- case GlobalDefinitions.router =>
- target.Actor ! Deployment.TryDeploymentChange(DriveState.Undeploying)
- BeforeUnloadVehicle(target)
- continent.LocalEvents ! LocalServiceMessage(continent.Id, LocalAction.ToggleTeleportSystem(PlanetSideGUID(0), target, None))
- case _ => ;
- }
- continent.AvatarEvents ! AvatarServiceMessage(continentId, AvatarAction.Destroy(target.GUID, playerGUID, playerGUID, target.Position))
- continent.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(target), continent))
- continent.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.AddTask(target, continent, Some(1 minute)))
- }
-
- /**
- * na
- * @param target na
- */
- def HandleTurretDeployableDamageResolution(target : TurretDeployable) : Unit = {
- //spitfires and field turrets
- val health = target.Health
- val guid = target.GUID
- val continentId = continent.Id
- if(health <= 0) {
- //if occupants, kill them
- target.Seats.values
- .filter(seat => {
- seat.isOccupied && seat.Occupant.get.isAlive
- })
- .foreach(seat => {
- val tplayer = seat.Occupant.get
- val tplayerGUID = tplayer.GUID
- continent.AvatarEvents ! AvatarServiceMessage(tplayer.Name, AvatarAction.KilledWhileInVehicle(tplayerGUID))
- continent.AvatarEvents ! AvatarServiceMessage(continentId, AvatarAction.ObjectDelete(tplayerGUID, tplayerGUID)) //dead player still sees self
- })
- //destroy weapons
- target.Weapons.values
- .map(slot => slot.Equipment)
- .collect { case Some(weapon) =>
- val wguid = weapon.GUID
- sendResponse(ObjectDeleteMessage(wguid, 0))
- continent.AvatarEvents ! AvatarServiceMessage(continentId, AvatarAction.ObjectDelete(player.GUID, wguid))
- }
- AnnounceDestroyDeployable(target, None)
- }
- continent.AvatarEvents ! AvatarServiceMessage(continentId, AvatarAction.PlanetsideAttribute(guid, 0, health))
- }
-
- def HandleFacilityTurretDamageResolution(target : FacilityTurret) : Unit = {
- val targetGUID = target.GUID
- val playerGUID = player.GUID
- val continentId = continent.Id
- val players = target.Seats.values.filter(seat => {
- seat.isOccupied && seat.Occupant.get.isAlive
- })
- if(target.Health > 1) { //TODO turret "death" at 0, as is proper
- //alert occupants to damage source
- players.foreach(seat => {
- val tplayer = seat.Occupant.get
- continent.AvatarEvents ! AvatarServiceMessage(tplayer.Name, AvatarAction.HitHint(playerGUID, tplayer.GUID))
- })
- }
- else {
- //alert to vehicle death (hence, occupants' deaths)
- players.foreach(seat => {
- val tplayer = seat.Occupant.get
- val tplayerGUID = tplayer.GUID
- continent.AvatarEvents ! AvatarServiceMessage(tplayer.Name, AvatarAction.KilledWhileInVehicle(tplayerGUID))
- continent.AvatarEvents ! AvatarServiceMessage(continentId, AvatarAction.ObjectDelete(tplayerGUID, tplayerGUID)) //dead player still sees self
- })
- //turret wreckage has no weapons
-// target.Weapons.values
-// .filter {
-// _.Equipment.nonEmpty
-// }
-// .foreach(slot => {
-// val wep = slot.Equipment.get
-// continent.AvatarEvents ! AvatarServiceMessage(continentId, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, wep.GUID))
-// })
-// continent.AvatarEvents ! AvatarServiceMessage(continentId, AvatarAction.Destroy(targetGUID, playerGUID, playerGUID, player.Position))
- target.Health = 1
- continent.VehicleEvents ! VehicleServiceMessage(continentId, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, targetGUID, 0, target.MaxHealth)) //TODO not necessary
- if(target.Upgrade != TurretUpgrade.None) {
- continent.VehicleEvents ! VehicleServiceMessage.TurretUpgrade(TurretUpgrader.ClearSpecific(List(target), continent))
- continent.VehicleEvents ! VehicleServiceMessage.TurretUpgrade(TurretUpgrader.AddTask(target, continent, TurretUpgrade.None))
- }
- }
- continent.VehicleEvents ! VehicleServiceMessage(continentId, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, targetGUID, 0, target.Health))
- }
-
/**
* na
* @param tplayer na
@@ -3548,6 +3336,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
player.Slot(39).Equipment = SimpleItem(remote_electronics_kit)
player.Locker.Inventory += 0 -> SimpleItem(remote_electronics_kit)
player.Inventory.Items.foreach { _.obj.Faction = faction }
+ player.Actor = self
//TODO end temp player character auto-loading
self ! ListAccountCharacters
import scala.concurrent.ExecutionContext.Implicits.global
@@ -3888,6 +3677,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
case msg @ PlayerStateMessageUpstream(avatar_guid, pos, vel, yaw, pitch, yaw_upper, seq_time, unk3, is_crouching, is_jumping, jump_thrust, is_cloaking, unk5, unk6) =>
val isMoving = WorldEntity.isMoving(vel)
+ val isMovingPlus = isMoving || is_jumping || jump_thrust
//implants and stamina management start
val implantsAreActive = avatar.Implants(0).Active || avatar.Implants(1).Active
val staminaBefore = player.Stamina
@@ -3919,14 +3709,23 @@ class WorldSessionActor extends Actor with MDCContextAware {
}
}
}
- CapacitorTick(jump_thrust)
- //if the player lost all stamina this turn (had stamina at the start), do not renew 1 stamina
- if(!isMoving && (if(player.Stamina > 0) player.Stamina < player.MaxStamina else !hadStaminaBefore)) {
- player.Stamina = player.Stamina + 1
- true
+ if(skipStaminaRegenForTurns > 0) {
+ //do not renew stamina for a while
+ skipStaminaRegenForTurns -= 1
+ player.Stamina > 0
+ }
+ else if(player.Stamina == 0 && hadStaminaBefore) {
+ //if the player lost all stamina this turn (had stamina at the start), do not renew stamina for a while
+ skipStaminaRegenForTurns = 4
+ player.Stamina > 0
+ }
+ else if(isMovingPlus || player.Stamina == player.MaxStamina) {
+ //ineligible for stamina regen
+ player.Stamina > 0
}
else {
- player.Stamina > 0
+ player.Stamina = player.Stamina + 1
+ true
}
}
else {
@@ -3938,18 +3737,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(PlanetsideAttributeMessage(player.GUID, 2, player.Stamina))
}
if(implantsAreActive && !hasStaminaAfter) { //implants deactivated at 0 stamina
- if(avatar.Implants(0).Active) {
- avatar.Implants(0).Active = false
- continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player.GUID, 28, avatar.Implant(0).id * 2))
- sendResponse(AvatarImplantMessage(PlanetSideGUID(player.GUID.guid), ImplantAction.Activation, 0, 0))
- timeDL = 0
- }
- if(avatar.Implants(1).Active) {
- avatar.Implants(1).Active = false
- continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player.GUID, 28, avatar.Implant(1).id * 2))
- sendResponse(AvatarImplantMessage(PlanetSideGUID(player.GUID.guid), ImplantAction.Activation, 1, 0))
- timeSurge = 0
- }
+ DeactivateImplants()
}
//implants and stamina management finish
player.Position = pos
@@ -3959,8 +3747,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
player.Crouching = is_crouching
player.Jumping = is_jumping
player.Cloaked = player.ExoSuit == ExoSuitType.Infiltration && is_cloaking
+ CapacitorTick(jump_thrust)
- if(isMoving && usingMedicalTerminal.isDefined) {
+ if(isMovingPlus && usingMedicalTerminal.isDefined) {
continent.GUID(usingMedicalTerminal) match {
case Some(term : Terminal with ProximityUnit) =>
StopUsingProximityUnit(term)
@@ -3978,7 +3767,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
accessedContainer = None
}
case Some(container) => //just in case
- if(isMoving) {
+ if(isMovingPlus) {
val guid = player.GUID
// If the container is a corpse and gets removed just as this runs it can cause a client disconnect, so we'll check the container has a GUID first.
if(container.HasGUID) {
@@ -4404,15 +4193,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Start(playerGUID, item_guid))
continent.GUID(trigger.Companion) match {
case Some(boomer : BoomerDeployable) =>
- val boomerGUID = boomer.GUID
boomer.Exploded = true
- sendResponse(TriggerEffectMessage(boomerGUID, "detonate_boomer"))
- sendResponse(PlanetsideAttributeMessage(boomerGUID, 29, 1))
- sendResponse(ObjectDeleteMessage(boomerGUID, 0))
- continent.LocalEvents ! LocalServiceMessage(continent.Id, LocalAction.TriggerEffect(playerGUID, "detonate_boomer", boomerGUID))
- continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(boomerGUID, 29, 1))
- continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(playerGUID, boomerGUID))
- continent.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.AddTask(boomer, continent, Some(0 seconds)))
+ continent.LocalEvents ! LocalServiceMessage(continent.Id, LocalAction.Detonate(boomer.GUID, boomer))
+ Deployables.AnnounceDestroyDeployable(boomer, Some(500 milliseconds))
case Some(_) | None => ;
}
FindEquipmentToDelete(item_guid, trigger)
@@ -4545,6 +4328,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
case msg @ AvatarJumpMessage(state) =>
//log.info("AvatarJump: " + msg)
player.Stamina = player.Stamina - 10
+ skipStaminaRegenForTurns = 5
sendResponse(PlanetsideAttributeMessage(player.GUID, 2, player.Stamina))
case msg @ ZipLineMessage(player_guid,origin_side,action,id,pos) =>
@@ -4770,7 +4554,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
}
case msg @ UseItemMessage(avatar_guid, item_used_guid, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType) =>
- log.info("UseItem: " + msg)
+ //log.info("UseItem: " + msg)
// TODO: Not all fields in the response are identical to source in real packet logs (but seems to be ok)
// TODO: Not all incoming UseItemMessage's respond with another UseItemMessage (i.e. doors only send out GenericObjectStateMsg)
continent.GUID(object_guid) match {
@@ -5495,6 +5279,15 @@ class WorldSessionActor extends Actor with MDCContextAware {
case _ => ;
}
+ case msg @ WeaponJammedMessage(weapon_guid) =>
+ FindWeapon match {
+ case Some(tool : Tool) =>
+ log.info(s"WeaponJammed: ${tool.Definition.Name}@${weapon_guid.guid}")
+ //TODO
+ case _ =>
+ log.info(s"WeaponJammed: ${weapon_guid.guid}")
+ }
+
case msg @ WeaponFireMessage(seq_time, weapon_guid, projectile_guid, shot_origin, unk1, unk2, unk3, unk4, unk5, unk6, unk7) =>
log.info(s"WeaponFire: $msg")
if(player.isShielded) {
@@ -5609,6 +5402,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
case Some(projectile) =>
projectile.Position = explosion_pos
projectile.Velocity = projectile_vel
+ //direct_victim_uid
continent.GUID(direct_victim_uid) match {
case Some(target : PlanetSideGameObject with FactionAffinity with Vitality) =>
ResolveProjectileEntry(projectile, ProjectileResolution.Splash, target, target.Position) match {
@@ -5618,6 +5412,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
}
case _ => ;
}
+ //other victims
targets.foreach(elem => {
continent.GUID(elem.uid) match {
case Some(target : PlanetSideGameObject with FactionAffinity with Vitality) =>
@@ -7245,7 +7040,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
val orient : Vector3 = Vector3(0f, 0f, sourceOrientZ)
continent.Ground ! Zone.Ground.DropItem(item2, pos, orient)
sendResponse(ObjectDetachMessage(destination_guid, item2_guid, pos, sourceOrientZ)) //ground
- val objDef = item2.Definition
+ val objDef = item2.Definition
destination match {
case obj : Vehicle =>
continent.VehicleEvents ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.UnstowEquipment(player_guid, item2_guid))
@@ -7912,9 +7707,12 @@ class WorldSessionActor extends Actor with MDCContextAware {
* This is not a complete list but, for the purpose of enforcement, some pointers will be documented here.
*/
def PlayerActionsToCancel() : Unit = {
+ CancelJammeredSound(player)
+ CancelJammeredStatus(player)
progressBarUpdate.cancel
progressBarValue = None
lastTerminalOrderFulfillment = true
+ skipStaminaRegenForTurns = 0
accessedContainer match {
case Some(obj : Vehicle) =>
if(obj.AccessingTrunk.contains(player.GUID)) {
@@ -8284,8 +8082,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
*/
def FindProximityUnitTargetsInScope(terminal : Terminal with ProximityUnit) : Seq[PlanetSideGameObject] = {
terminal.Definition.asInstanceOf[ProximityDefinition].TargetValidation.keySet collect {
- case ProximityTarget.Player => Some(player)
- case ProximityTarget.Vehicle | ProximityTarget.Aircraft => continent.GUID(player.VehicleSeated)
+ case EffectTarget.Category.Player => Some(player)
+ case EffectTarget.Category.Vehicle | EffectTarget.Category.Aircraft => continent.GUID(player.VehicleSeated)
} collect {
case Some(a) => a
} toSeq
@@ -8548,8 +8346,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
def ResolveProjectileEntry(projectile_guid : PlanetSideGUID, resolution : ProjectileResolution.Value, target : PlanetSideGameObject with FactionAffinity with Vitality, pos : Vector3) : Option[ResolvedProjectile] = {
FindProjectileEntry(projectile_guid) match {
case Some(projectile) =>
- val index = projectile_guid.guid - Projectile.BaseUID
- ResolveProjectileEntry(projectile, index, resolution, target, pos)
+ ResolveProjectileEntry(projectile, resolution, target, pos)
case None =>
log.warn(s"ResolveProjectile: expected projectile, but ${projectile_guid.guid} not found")
None
@@ -8574,10 +8371,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
}
/**
- * Find a projectile with the given globally unique identifier and mark it as a resolved shot.
- * A `Resolved` shot has either encountered an obstacle or is being cleaned up for not finding an obstacle.
- * The internal copy of the projectile is retained as merely `Resolved`
- * while the observed projectile is promoted to the suggested resolution status.
+ * na
* @param projectile the projectile object
* @param resolution the resolution status to promote the projectile
* @return a copy of the projectile
@@ -8637,15 +8431,14 @@ class WorldSessionActor extends Actor with MDCContextAware {
case obj : Player =>
//damage is synchronized on the target player's `WSA` (results distributed from there)
continent.AvatarEvents ! AvatarServiceMessage(obj.Name, AvatarAction.Damage(player.GUID, obj, func))
- case obj : Vehicle =>
- //damage is synchronized on the vehicle actor (results returned to and distributed from this `WSA`)
- obj.Actor ! Vitality.Damage(func)
- case obj : Deployable =>
+
+ case obj : Vehicle => obj.Actor ! Vitality.Damage(func)
+ case obj : FacilityTurret => obj.Actor ! Vitality.Damage(func)
+ case obj : ComplexDeployable => obj.Actor ! Vitality.Damage(func)
+
+ case obj : SimpleDeployable =>
//damage is synchronized on `LSA` (results returned to and distributed from this `WSA`)
continent.LocalEvents ! Vitality.DamageOn(obj, func)
- case obj : FacilityTurret =>
- //damage is synchronized on the turret actor (results returned to and distributed from this `WSA`)
- obj.Actor ! Vitality.Damage(func)
case _ => ;
}
}
@@ -9803,7 +9596,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
case GlobalDefinitions.ams if vehicle.Faction == player.Faction =>
log.info("BeforeUnload: cleaning up after a mobile spawn vehicle ...")
continent.VehicleEvents ! VehicleServiceMessage(continent.Id, VehicleAction.UpdateAmsSpawnPoint(continent))
- None
case GlobalDefinitions.router =>
//this may repeat for multiple players on the same continent but that's okay(?)
log.info("BeforeUnload: cleaning up after a router ...")
@@ -10270,11 +10062,94 @@ class WorldSessionActor extends Actor with MDCContextAware {
taskResolver ! UnregisterProjectile(projectile)
projectiles(local_index) match {
case Some(obj) if !obj.isResolved => obj.Miss
- case None => ;
+ case _ => ;
}
projectilesToCleanUp(local_index) = false
}
+ /**
+ * Deactivate all active implants.
+ * This method is intended to support only the current Live server implants that are functional,
+ * the darklight vision implant and the surge implant.
+ */
+ def DeactivateImplants() : Unit = {
+ DeactivateImplantDarkLight()
+ DeactivateImplantSurge()
+ }
+
+ /**
+ * Deactivate the darklight vision implant.
+ * This method is intended to support only the current Live server implants.
+ */
+ def DeactivateImplantDarkLight() : Unit = {
+ if(avatar.Implants(0).Active) {
+ avatar.Implants(0).Active = false
+ continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player.GUID, 28, avatar.Implant(0).id * 2))
+ sendResponse(AvatarImplantMessage(PlanetSideGUID(player.GUID.guid), ImplantAction.Activation, 0, 0))
+ timeDL = 0
+ }
+ }
+
+ /**
+ * Deactivate the surge implant.
+ * This method is intended to support only the current Live server implants.
+ */
+ def DeactivateImplantSurge() : Unit = {
+ if(avatar.Implants(1).Active) {
+ avatar.Implants(1).Active = false
+ continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player.GUID, 28, avatar.Implant(1).id * 2))
+ sendResponse(AvatarImplantMessage(PlanetSideGUID(player.GUID.guid), ImplantAction.Activation, 1, 0))
+ timeSurge = 0
+ }
+ }
+
+ /**
+ * Start the jammered buzzing.
+ * Although, as a rule, the jammering sound effect should last as long as the jammering status,
+ * Infantry seem to hear the sound for a bit longer than the effect.
+ * @see `JammableHevaior.StartJammeredSound`
+ * @param target an object that can be affected by the jammered status
+ * @param dur the duration of the timer, in milliseconds;
+ * by default, 30000
+ */
+ override def StartJammeredSound(target : Any, dur : Int) : Unit = target match {
+ case obj : Player if !jammedSound =>
+ sendResponse(PlanetsideAttributeMessage(obj.GUID, 27, 1))
+ continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(obj.GUID, 27, 1))
+ super.StartJammeredSound(obj, 3000)
+ case _ => ;
+ }
+
+ /**
+ * Perform a variety of tasks to indicate being jammered.
+ * Deactivate implants (should also uninitialize them),
+ * delay stamina regeneration for a certain number of turns,
+ * and set the jammered status on specific holstered equipment.
+ * @see `JammableHevaior.StartJammeredStatus`
+ * @param target an object that can be affected by the jammered status
+ * @param dur the duration of the timer, in milliseconds
+ */
+ override def StartJammeredStatus(target : Any, dur : Int) : Unit = target match {
+ case obj : Player =>
+ DeactivateImplants()
+ skipStaminaRegenForTurns = 10
+ super.StartJammeredStatus(target, dur)
+ case _ => ;
+ }
+
+ /**
+ * Stop the jammered buzzing.
+ * @see `JammableHevaior.CancelJammeredSound`
+ * @param target an object that can be affected by the jammered status
+ */
+ override def CancelJammeredSound(target : Any) : Unit = target match {
+ case obj : Player if jammedSound =>
+ sendResponse(PlanetsideAttributeMessage(obj.GUID, 27, 0))
+ continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(obj.GUID, 27, 0))
+ super.CancelJammeredSound(obj)
+ case _ => ;
+ }
+
def failWithError(error : String) = {
log.error(error)
sendResponse(ConnectionClose())
@@ -10433,8 +10308,8 @@ object WorldSessionActor {
protected final case class SquadUIElement(name : String, index : Int, zone : Int, health : Int, armor : Int, position : Vector3)
- private final case class NtuCharging(tplayer: Player,
- vehicle: Vehicle)
+ private final case class NtuCharging(tplayer: Player, vehicle: Vehicle)
+
private final case class NtuDischarging(tplayer: Player, vehicle: Vehicle, silo_guid: PlanetSideGUID)
private final case class FinalizeDeployable(obj : PlanetSideGameObject with Deployable, tool : ConstructionItem, index : Int)
diff --git a/pslogin/src/test/scala/actor/service/AvatarServiceTest.scala b/pslogin/src/test/scala/actor/service/AvatarServiceTest.scala
index 9fce22da..7c6a60ca 100644
--- a/pslogin/src/test/scala/actor/service/AvatarServiceTest.scala
+++ b/pslogin/src/test/scala/actor/service/AvatarServiceTest.scala
@@ -5,6 +5,7 @@ import akka.actor.Props
import akka.routing.RandomPool
import actor.base.ActorTest
import net.psforever.objects._
+import net.psforever.objects.ballistics.ResolvedProjectile
import net.psforever.objects.guid.{GUIDTask, TaskResolver}
import net.psforever.objects.zones.{Zone, ZoneActor, ZoneMap}
import net.psforever.packet.game.objectcreate.{DroppedItemData, ObjectClass, ObjectCreateMessageParent, PlacementData}
@@ -370,8 +371,8 @@ class ChangeFireStateStopTest extends ActorTest {
}
class DamageTest extends ActorTest {
- val test_func_ref : (Any)=>Unit = {
- def test_func(o : Any) : Unit = { }
+ val test_func_ref : Any=>ResolvedProjectile = {
+ def test_func(o : Any) : ResolvedProjectile = { null }
test_func
}
val player = Player(Avatar("TestCharacter", PlanetSideEmpire.VS, CharacterGender.Female, 1, CharacterVoice.Voice1))