From 375edbbf94d40f39a166fa89cee218b3f25b7684 Mon Sep 17 00:00:00 2001 From: FateJH Date: Sat, 7 Dec 2019 00:02:59 -0500 Subject: [PATCH 01/14] adding EMP-related fields to projectile definitions; adjusting target validation funcs for generic application --- .../psforever/objects/GlobalDefinitions.scala | 81 +++++++++----- .../definition/ProjectileDefinition.scala | 25 +++++ .../serverobject/terminals/EffectTarget.scala | 103 ++++++++++++++++++ .../terminals/ProximityDefinition.scala | 10 +- .../terminals/ProximityTarget.scala | 15 --- .../terminals/ProximityTerminalControl.scala | 30 ----- 6 files changed, 187 insertions(+), 77 deletions(-) create mode 100644 common/src/main/scala/net/psforever/objects/serverobject/terminals/EffectTarget.scala delete mode 100644 common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTarget.scala diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index c937e7c9..e00ecab5 100644 --- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -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) -> (1 seconds) + jammer_cartridge_projectile.JammedEffectDuration += TargetValidation(EffectTarget.Category.Vehicle, EffectTarget.Validation.AMS) -> (5 seconds) + jammer_cartridge_projectile.JammedEffectDuration += TargetValidation(EffectTarget.Category.Deployable, EffectTarget.Validation.MotionSensor) -> (30 seconds) + jammer_cartridge_projectile.JammedEffectDuration += TargetValidation(EffectTarget.Category.Deployable, EffectTarget.Validation.Spitfire) -> (30 seconds) + jammer_cartridge_projectile.JammedEffectDuration += TargetValidation(EffectTarget.Category.Turret, EffectTarget.Validation.Turret) -> (30 seconds) + jammer_cartridge_projectile.JammedEffectDuration += TargetValidation(EffectTarget.Category.Vehicle, EffectTarget.Validation.VehicleNotAMS) -> (10 seconds) 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) -> (1 seconds) + jammer_cartridge_projectile_b.JammedEffectDuration += TargetValidation(EffectTarget.Category.Vehicle, EffectTarget.Validation.AMS) -> (5 seconds) + jammer_cartridge_projectile_b.JammedEffectDuration += TargetValidation(EffectTarget.Category.Deployable, EffectTarget.Validation.MotionSensor) -> (30 seconds) + jammer_cartridge_projectile_b.JammedEffectDuration += TargetValidation(EffectTarget.Category.Deployable, EffectTarget.Validation.Spitfire) -> (30 seconds) + jammer_cartridge_projectile_b.JammedEffectDuration += TargetValidation(EffectTarget.Category.Turret, EffectTarget.Validation.Turret) -> (30 seconds) + jammer_cartridge_projectile_b.JammedEffectDuration += TargetValidation(EffectTarget.Category.Vehicle, EffectTarget.Validation.VehicleNotAMS) -> (10 seconds) 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) -> (1 seconds) + jammer_grenade_projectile.JammedEffectDuration += TargetValidation(EffectTarget.Category.Vehicle, EffectTarget.Validation.AMS) -> (5 seconds) + jammer_grenade_projectile.JammedEffectDuration += TargetValidation(EffectTarget.Category.Deployable, EffectTarget.Validation.MotionSensor) -> (30 seconds) + jammer_grenade_projectile.JammedEffectDuration += TargetValidation(EffectTarget.Category.Deployable, EffectTarget.Validation.Spitfire) -> (30 seconds) + jammer_grenade_projectile.JammedEffectDuration += TargetValidation(EffectTarget.Category.Turret, EffectTarget.Validation.Turret) -> (30 seconds) + jammer_grenade_projectile.JammedEffectDuration += TargetValidation(EffectTarget.Category.Vehicle, EffectTarget.Validation.VehicleNotAMS) -> (10 seconds) ProjectileDefinition.CalculateDerivedFields(jammer_grenade_projectile) jammer_grenade_projectile_enh.Name = "jammer_grenade_projectile_enh" @@ -2718,6 +2742,13 @@ 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) -> (1 seconds) + jammer_grenade_projectile_enh.JammedEffectDuration += TargetValidation(EffectTarget.Category.Vehicle, EffectTarget.Validation.AMS) -> (5 seconds) + jammer_grenade_projectile_enh.JammedEffectDuration += TargetValidation(EffectTarget.Category.Deployable, EffectTarget.Validation.MotionSensor) -> (30 seconds) + jammer_grenade_projectile_enh.JammedEffectDuration += TargetValidation(EffectTarget.Category.Deployable, EffectTarget.Validation.Spitfire) -> (30 seconds) + jammer_grenade_projectile_enh.JammedEffectDuration += TargetValidation(EffectTarget.Category.Turret, EffectTarget.Validation.Turret) -> (30 seconds) ProjectileDefinition.CalculateDerivedFields(jammer_grenade_projectile_enh) katana_projectile.Name = "katana_projectile" @@ -6096,62 +6127,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/definition/ProjectileDefinition.scala b/common/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala index e1069612..01e6494c 100644 --- a/common/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala @@ -2,8 +2,12 @@ package net.psforever.objects.definition import net.psforever.objects.ballistics.Projectiles +import net.psforever.objects.serverobject.terminals.TargetValidation import net.psforever.objects.vital.{DamageType, StandardDamageProfile} +import scala.collection.mutable +import scala.concurrent.duration.Duration + /** * The definition that outlines the damage-dealing characteristics of any projectile. * `Tool` objects emit `ProjectileDefinition` objects and that is later wrapped into a `Projectile` object. @@ -26,6 +30,9 @@ 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 + private val jammedEffectDuration : mutable.ListBuffer[(TargetValidation, Duration)] = new mutable.ListBuffer() //derived calculations private var distanceMax : Float = 0f private var distanceFromAcceleration : Float = 0f @@ -133,6 +140,24 @@ 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 HasJammedEffectDuration : Boolean = jammedEffectDuration.isEmpty + + def JammedEffectDuration : mutable.ListBuffer[(TargetValidation, Duration)] = jammedEffectDuration + def DistanceMax : Float = distanceMax //accessor only def DistanceFromAcceleration : Float = distanceFromAcceleration //accessor only diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/EffectTarget.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/EffectTarget.scala new file mode 100644 index 00000000..38e790df --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/EffectTarget.scala @@ -0,0 +1,103 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.serverobject.terminals + +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/serverobject/terminals/ProximityDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityDefinition.scala index 575d6e55..4278cec0 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 @@ -17,7 +17,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 +26,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() } From 686676f9b9ce281fd570039cbf762005791fbbb1 Mon Sep 17 00:00:00 2001 From: FateJH Date: Tue, 10 Dec 2019 08:17:39 -0500 Subject: [PATCH 02/14] jammering criteria selection and determination added; applying calculations to damage target (for projectiles) exposes the underlying cause of the damage --- .../definition/ProjectileDefinition.scala | 11 +- .../objects/equipment/JammingUnit.scala | 15 ++ .../psforever/objects/vital/Vitality.scala | 5 +- .../resolution/DamageResistCalculations.scala | 2 +- .../resolution/ResolutionCalculations.scala | 148 +++++++++--------- .../avatar/AvatarServiceMessage.scala | 3 +- .../avatar/AvatarServiceResponse.scala | 3 +- .../test/scala/objects/DamageModelTests.scala | 14 +- .../src/test/scala/objects/VehicleTest.scala | 2 +- .../src/main/scala/WorldSessionActor.scala | 144 +++++++++++++++-- .../actor/service/AvatarServiceTest.scala | 5 +- 11 files changed, 243 insertions(+), 109 deletions(-) create mode 100644 common/src/main/scala/net/psforever/objects/equipment/JammingUnit.scala 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 01e6494c..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,18 +2,16 @@ package net.psforever.objects.definition import net.psforever.objects.ballistics.Projectiles -import net.psforever.objects.serverobject.terminals.TargetValidation +import net.psforever.objects.equipment.JammingUnit import net.psforever.objects.vital.{DamageType, StandardDamageProfile} -import scala.collection.mutable -import scala.concurrent.duration.Duration - /** * The definition that outlines the damage-dealing characteristics of any projectile. * `Tool` objects emit `ProjectileDefinition` objects and that is later wrapped into a `Projectile` object. * @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 @@ -32,7 +30,6 @@ class ProjectileDefinition(objectId : Int) extends ObjectDefinition(objectId) private var autoLock : Boolean = false private var additionalEffect : Boolean = false private var jammerProjectile : Boolean = false - private val jammedEffectDuration : mutable.ListBuffer[(TargetValidation, Duration)] = new mutable.ListBuffer() //derived calculations private var distanceMax : Float = 0f private var distanceFromAcceleration : Float = 0f @@ -154,10 +151,6 @@ class ProjectileDefinition(objectId : Int) extends ObjectDefinition(objectId) JammerProjectile } - def HasJammedEffectDuration : Boolean = jammedEffectDuration.isEmpty - - def JammedEffectDuration : mutable.ListBuffer[(TargetValidation, Duration)] = jammedEffectDuration - def DistanceMax : Float = distanceMax //accessor only def DistanceFromAcceleration : Float = distanceFromAcceleration //accessor only 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..c336ec15 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/equipment/JammingUnit.scala @@ -0,0 +1,15 @@ +// Copyright (c) 2019 PSForever +package net.psforever.objects.equipment + +import net.psforever.objects.serverobject.terminals.TargetValidation + +import scala.collection.mutable +import scala.concurrent.duration.Duration + +trait JammingUnit { + private val jammedEffectDuration : mutable.ListBuffer[(TargetValidation, Duration)] = new mutable.ListBuffer() + + def HasJammedEffectDuration : Boolean = jammedEffectDuration.isEmpty + + def JammedEffectDuration : mutable.ListBuffer[(TargetValidation, Duration)] = jammedEffectDuration +} 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..1c7628ac 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,9 +103,9 @@ 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. 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..a85f6206 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 @@ -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) { + def SimpleApplication(damage : Int, data : ResolvedProjectile)(target : Any) : ResolvedProjectile = { + target match { + case ce : SimpleDeployable if ce.Health > 0 && damage > 0 => ce.Health -= damage ce.History(data) - } - case turret : FacilityTurret => - if(turret.Health > 0) { + 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/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/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/VehicleTest.scala b/common/src/test/scala/objects/VehicleTest.scala index 99cae765..d1401251 100644 --- a/common/src/test/scala/objects/VehicleTest.scala +++ b/common/src/test/scala/objects/VehicleTest.scala @@ -698,7 +698,7 @@ class VehicleControlShieldsNotChargingDamagedTest extends ActorTest { "not charge vehicle shields if recently damaged" in { assert(vehicle.Shields == 0) - vehicle.Actor ! Vitality.Damage({case v : Vehicle => v.History(obj)}) + vehicle.Actor ! Vitality.Damage({case v : Vehicle => v.History(obj); obj }) val msg = receiveOne(200 milliseconds) assert(msg.isInstanceOf[Vitality.DamageResolution]) diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 882d1a24..a2865e5f 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -1270,7 +1270,7 @@ 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 @@ -1301,10 +1301,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 +1316,20 @@ 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(cause.projectile.profile.JammerProjectile) { + val radius = cause.projectile.profile.DamageRadius + FindJammerDuration(cause.projectile.profile, target) match { + case Some(dur) if Vector3.DistanceSquared(cause.hit_pos, cause.target.Position) < radius * radius => + //TODO jammer messaging here + sendResponse(PlanetsideAttributeMessage(player.GUID, 54, 1)) + case _ => ; + } + } } case AvatarResponse.Destroy(victim, killer, weapon, pos) => @@ -5609,6 +5618,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 +5628,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) => @@ -5642,6 +5653,34 @@ class WorldSessionActor extends Actor with MDCContextAware { case None => ; } +// FindProjectileEntry(projectile_guid) match { +// case Some(projectile) => +// val allTargets = (continent.GUID(direct_victim_uid) match { +// case Some(target : PlanetSideGameObject with FactionAffinity with Vitality) => +// HandleDealingDamage(target, ResolveProjectileEntry(projectile, ProjectileResolution.Splash, target, target.Position).get) +// Seq(target) +// case _ => +// Nil +// }) ++ +// targets +// .map { a => continent.GUID(a.uid) } +// .collect { +// case Some(target : PlanetSideGameObject with FactionAffinity with Vitality) => +// HandleDealingDamage(target, ResolveProjectileEntry(projectile, ProjectileResolution.Splash, target, explosion_pos).get) +// target +// } +// if(projectile.profile.JammerProjectile) { +// val jammableTargets = FindJammerTargetsInScope(projectile.profile, allTargets) +// jammableTargets +// .zip(FindJammerDuration(projectile.profile, jammableTargets)) +// .collect { case (target, Some(time)) => +// //TODO jamming messages here +// } +// } +// +// case None => ; +// } + case msg @ LashMessage(seq_time, killer_guid, victim_guid, projectile_guid, pos, unk1) => log.info(s"Lash: $msg") continent.GUID(victim_guid) match { @@ -7245,7 +7284,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)) @@ -8284,8 +8323,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 +8587,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 @@ -8588,11 +8626,21 @@ class WorldSessionActor extends Actor with MDCContextAware { None } else { - projectile.Resolve() - Some(ResolvedProjectile(resolution, projectile, SourceEntry(target), target.DamageModel, pos)) + ResolveProjectileEntry(projectile, resolution, target, pos) } } + /** + * na + * @param projectile the projectile object + * @param resolution the resolution status to promote the projectile + * @return a copy of the projectile + */ + def ResolveProjectileEntry(projectile : Projectile, resolution : ProjectileResolution.Value, target : PlanetSideGameObject with FactionAffinity with Vitality, pos : Vector3) : Option[ResolvedProjectile] = { + projectile.Resolve() + Some(ResolvedProjectile(resolution, projectile, SourceEntry(target), target.DamageModel, pos)) + } + /** * Common activities/procedure when a player mounts a valid object. * @param tplayer the player @@ -10275,6 +10323,76 @@ class WorldSessionActor extends Actor with MDCContextAware { projectilesToCleanUp(local_index) = false } + def FindJammerTargetsInScope(jammer : Any with JammingUnit) : Seq[PlanetSideGameObject] = { + (jammer match { + case p : ProjectileDefinition if !p.JammerProjectile => None + case p => Some(p) + }) match { + case Some(p : JammingUnit) => + p.JammedEffectDuration + .map { case (a, _) => a } + .collect { + case TargetValidation(EffectTarget.Category.Player, test) if test(player) => Some(player) + case TargetValidation(EffectTarget.Category.Vehicle, test) if { + continent.GUID(player.VehicleSeated) match { + case Some(v) => test(v) + case None => false + } + } => continent.GUID(player.VehicleSeated) + case TargetValidation(EffectTarget.Category.Aircraft, test) if { + continent.GUID(player.VehicleSeated) match { + case Some(v) => test(v) + case None => false + } + } => continent.GUID(player.VehicleSeated) + } collect { + case Some(a) => a + } toSeq + case _ => + Seq.empty[PlanetSideGameObject] + } + } + + def CompileJammerTests(jammer : JammingUnit) : Iterable[EffectTarget.Validation.Value] = { + jammer.JammedEffectDuration map { case (TargetValidation(_, test), _) => test } + } + + def CompileJammerTests(jammer : JammingUnit, target : EffectTarget.Category.Value) : Iterable[EffectTarget.Validation.Value] = { + jammer.JammedEffectDuration collect { case (TargetValidation(filter, test), _) if filter == target => test } + } + + def FindJammerTargetsInScope(jammer : JammingUnit, scope : Seq[PlanetSideGUID], zone : Zone) : Seq[(PlanetSideGUID, PlanetSideGameObject)] = { + val tests = CompileJammerTests(jammer) + (for { + uid <- scope + obj = zone.GUID(uid) + if obj.nonEmpty + } yield (uid, obj.get)) + .collect { + case out @ (_, b) if tests.foldLeft(false)(_ || _(b)) => out + } + } + + def FindJammerTargetsInScope(jammer : JammingUnit, scope : Seq[PlanetSideGameObject]) : Seq[PlanetSideGameObject] = { + val tests = CompileJammerTests(jammer) + scope collect { + case a if tests.foldLeft(false)(_ || _(a)) => a + } + } + + def FindJammerDuration(jammer : JammingUnit, target : PlanetSideGameObject) : Option[Duration] = { + jammer.JammedEffectDuration + .collect { case (TargetValidation(_, test), duration) if test(target) => duration } + .toList + .sortWith(_.toMillis > _.toMillis) + .headOption + } + + def FindJammerDuration(jammer : JammingUnit, targets : Seq[PlanetSideGameObject]) : Seq[Option[Duration]] = { + targets.map { target => FindJammerDuration(jammer, target) } + } + + def failWithError(error : String) = { log.error(error) sendResponse(ConnectionClose()) 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)) From cf8faa207dc3ecab993bbd159618cd9cacb6ed75 Mon Sep 17 00:00:00 2001 From: FateJH Date: Fri, 13 Dec 2019 23:42:02 -0500 Subject: [PATCH 03/14] test jammering of avatars --- .../psforever/objects/GlobalDefinitions.scala | 46 +++--- .../objects/equipment/JammingUnit.scala | 4 +- .../src/main/scala/WorldSessionActor.scala | 139 +++++++++++++----- 3 files changed, 124 insertions(+), 65 deletions(-) diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index e00ecab5..0a69c556 100644 --- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -2688,12 +2688,12 @@ object GlobalDefinitions { 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) -> (1 seconds) - jammer_cartridge_projectile.JammedEffectDuration += TargetValidation(EffectTarget.Category.Vehicle, EffectTarget.Validation.AMS) -> (5 seconds) - jammer_cartridge_projectile.JammedEffectDuration += TargetValidation(EffectTarget.Category.Deployable, EffectTarget.Validation.MotionSensor) -> (30 seconds) - jammer_cartridge_projectile.JammedEffectDuration += TargetValidation(EffectTarget.Category.Deployable, EffectTarget.Validation.Spitfire) -> (30 seconds) - jammer_cartridge_projectile.JammedEffectDuration += TargetValidation(EffectTarget.Category.Turret, EffectTarget.Validation.Turret) -> (30 seconds) - jammer_cartridge_projectile.JammedEffectDuration += TargetValidation(EffectTarget.Category.Vehicle, EffectTarget.Validation.VehicleNotAMS) -> (10 seconds) + 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" @@ -2707,12 +2707,12 @@ object GlobalDefinitions { 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) -> (1 seconds) - jammer_cartridge_projectile_b.JammedEffectDuration += TargetValidation(EffectTarget.Category.Vehicle, EffectTarget.Validation.AMS) -> (5 seconds) - jammer_cartridge_projectile_b.JammedEffectDuration += TargetValidation(EffectTarget.Category.Deployable, EffectTarget.Validation.MotionSensor) -> (30 seconds) - jammer_cartridge_projectile_b.JammedEffectDuration += TargetValidation(EffectTarget.Category.Deployable, EffectTarget.Validation.Spitfire) -> (30 seconds) - jammer_cartridge_projectile_b.JammedEffectDuration += TargetValidation(EffectTarget.Category.Turret, EffectTarget.Validation.Turret) -> (30 seconds) - jammer_cartridge_projectile_b.JammedEffectDuration += TargetValidation(EffectTarget.Category.Vehicle, EffectTarget.Validation.VehicleNotAMS) -> (10 seconds) + 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" @@ -2725,12 +2725,12 @@ object GlobalDefinitions { 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) -> (1 seconds) - jammer_grenade_projectile.JammedEffectDuration += TargetValidation(EffectTarget.Category.Vehicle, EffectTarget.Validation.AMS) -> (5 seconds) - jammer_grenade_projectile.JammedEffectDuration += TargetValidation(EffectTarget.Category.Deployable, EffectTarget.Validation.MotionSensor) -> (30 seconds) - jammer_grenade_projectile.JammedEffectDuration += TargetValidation(EffectTarget.Category.Deployable, EffectTarget.Validation.Spitfire) -> (30 seconds) - jammer_grenade_projectile.JammedEffectDuration += TargetValidation(EffectTarget.Category.Turret, EffectTarget.Validation.Turret) -> (30 seconds) - jammer_grenade_projectile.JammedEffectDuration += TargetValidation(EffectTarget.Category.Vehicle, EffectTarget.Validation.VehicleNotAMS) -> (10 seconds) + 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" @@ -2744,11 +2744,11 @@ object GlobalDefinitions { 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) -> (1 seconds) - jammer_grenade_projectile_enh.JammedEffectDuration += TargetValidation(EffectTarget.Category.Vehicle, EffectTarget.Validation.AMS) -> (5 seconds) - jammer_grenade_projectile_enh.JammedEffectDuration += TargetValidation(EffectTarget.Category.Deployable, EffectTarget.Validation.MotionSensor) -> (30 seconds) - jammer_grenade_projectile_enh.JammedEffectDuration += TargetValidation(EffectTarget.Category.Deployable, EffectTarget.Validation.Spitfire) -> (30 seconds) - jammer_grenade_projectile_enh.JammedEffectDuration += TargetValidation(EffectTarget.Category.Turret, EffectTarget.Validation.Turret) -> (30 seconds) + 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 ProjectileDefinition.CalculateDerivedFields(jammer_grenade_projectile_enh) katana_projectile.Name = "katana_projectile" diff --git a/common/src/main/scala/net/psforever/objects/equipment/JammingUnit.scala b/common/src/main/scala/net/psforever/objects/equipment/JammingUnit.scala index c336ec15..1bd54684 100644 --- a/common/src/main/scala/net/psforever/objects/equipment/JammingUnit.scala +++ b/common/src/main/scala/net/psforever/objects/equipment/JammingUnit.scala @@ -7,9 +7,9 @@ import scala.collection.mutable import scala.concurrent.duration.Duration trait JammingUnit { - private val jammedEffectDuration : mutable.ListBuffer[(TargetValidation, Duration)] = new mutable.ListBuffer() + private val jammedEffectDuration : mutable.ListBuffer[(TargetValidation, Int)] = new mutable.ListBuffer() def HasJammedEffectDuration : Boolean = jammedEffectDuration.isEmpty - def JammedEffectDuration : mutable.ListBuffer[(TargetValidation, Duration)] = jammedEffectDuration + def JammedEffectDuration : mutable.ListBuffer[(TargetValidation, Int)] = jammedEffectDuration } diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index a2865e5f..6033140a 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -151,8 +151,10 @@ 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 + var jammeredEquipment : Seq[PlanetSideGUID] = Nil var amsSpawnPoints : List[SpawnPoint] = Nil var clientKeepAlive : Cancellable = DefaultCancellable.obj @@ -163,6 +165,8 @@ class WorldSessionActor extends Actor with MDCContextAware { var cargoDismountTimer : Cancellable = DefaultCancellable.obj var antChargingTick : Cancellable = DefaultCancellable.obj var antDischargingTick : Cancellable = DefaultCancellable.obj + var jammeredSoundTimer : Cancellable = DefaultCancellable.obj + var jammeredStatusTimer : Cancellable = DefaultCancellable.obj /** @@ -1179,6 +1183,12 @@ class WorldSessionActor extends Actor with MDCContextAware { case _ => ; } + case ClearJammeredSound() => + CancelJammeredSound() + + case ClearJammeredStatus() => + CancelJammeredStatus() + case default => log.warn(s"Invalid packet class received: $default from $sender") } @@ -1325,8 +1335,24 @@ class WorldSessionActor extends Actor with MDCContextAware { val radius = cause.projectile.profile.DamageRadius FindJammerDuration(cause.projectile.profile, target) match { case Some(dur) if Vector3.DistanceSquared(cause.hit_pos, cause.target.Position) < radius * radius => - //TODO jammer messaging here + //implants + DeactivateImplants() + //jammered sound sendResponse(PlanetsideAttributeMessage(player.GUID, 54, 1)) + continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player.GUID, 54, 1)) + import scala.concurrent.ExecutionContext.Implicits.global + jammeredSoundTimer = context.system.scheduler.scheduleOnce(30 seconds, self, ClearJammeredSound()) + //jammered status + skipStaminaRegenForTurns = 5 + sendResponse(PlanetsideAttributeMessage(player.GUID, 2, 0)) + jammeredEquipment = player.Holsters() + .map { _.Equipment } + .collect { + case Some(item) if item.Size != EquipmentSize.Melee => + sendResponse(GenericObjectActionMessage(item.GUID, 156)) + item.GUID + } + jammeredStatusTimer = context.system.scheduler.scheduleOnce(dur milliseconds, self, ClearJammeredStatus()) case _ => ; } } @@ -3897,6 +3923,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 @@ -3928,14 +3955,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 { @@ -3947,18 +3983,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 @@ -3968,8 +3993,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) @@ -3987,7 +4013,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) { @@ -4554,6 +4580,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) => @@ -7951,9 +7978,14 @@ 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 = { + if(!jammeredSoundTimer.isCancelled) { + CancelJammeredSound() + } + jammeredStatusTimer.cancel progressBarUpdate.cancel progressBarValue = None lastTerminalOrderFulfillment = true + skipStaminaRegenForTurns = 0 accessedContainer match { case Some(obj : Vehicle) => if(obj.AccessingTrunk.contains(player.GUID)) { @@ -8612,10 +8644,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 @@ -8626,21 +8655,11 @@ class WorldSessionActor extends Actor with MDCContextAware { None } else { - ResolveProjectileEntry(projectile, resolution, target, pos) + projectile.Resolve() + Some(ResolvedProjectile(resolution, projectile, SourceEntry(target), target.DamageModel, pos)) } } - /** - * na - * @param projectile the projectile object - * @param resolution the resolution status to promote the projectile - * @return a copy of the projectile - */ - def ResolveProjectileEntry(projectile : Projectile, resolution : ProjectileResolution.Value, target : PlanetSideGameObject with FactionAffinity with Vitality, pos : Vector3) : Option[ResolvedProjectile] = { - projectile.Resolve() - Some(ResolvedProjectile(resolution, projectile, SourceEntry(target), target.DamageModel, pos)) - } - /** * Common activities/procedure when a player mounts a valid object. * @param tplayer the player @@ -10380,18 +10399,54 @@ class WorldSessionActor extends Actor with MDCContextAware { } } - def FindJammerDuration(jammer : JammingUnit, target : PlanetSideGameObject) : Option[Duration] = { + def FindJammerDuration(jammer : JammingUnit, target : PlanetSideGameObject) : Option[Int] = { jammer.JammedEffectDuration .collect { case (TargetValidation(_, test), duration) if test(target) => duration } .toList - .sortWith(_.toMillis > _.toMillis) + .sortWith(_ > _) .headOption } - def FindJammerDuration(jammer : JammingUnit, targets : Seq[PlanetSideGameObject]) : Seq[Option[Duration]] = { + def FindJammerDuration(jammer : JammingUnit, targets : Seq[PlanetSideGameObject]) : Seq[Option[Int]] = { targets.map { target => FindJammerDuration(jammer, target) } } + def DeactivateImplants() : Unit = { + DeactivateImplantDarkLight() + DeactivateImplantSurge() + } + + 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 + } + } + + 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 + } + } + + def CancelJammeredSound() : Unit = { + jammeredSoundTimer.cancel + sendResponse(PlanetsideAttributeMessage(player.GUID, 54, 0)) + continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player.GUID, 54, 0)) + } + + def CancelJammeredStatus() : Unit = { + if(!jammeredSoundTimer.isCancelled) { + CancelJammeredSound() + } + jammeredEquipment.foreach { id => sendResponse(GenericObjectActionMessage(id, 152)) } + jammeredEquipment = Nil + } def failWithError(error : String) = { log.error(error) @@ -10558,4 +10613,8 @@ object WorldSessionActor { private final case class FinalizeDeployable(obj : PlanetSideGameObject with Deployable, tool : ConstructionItem, index : Int) private final case class LoadedRemoteProjectile(projectile_guid : PlanetSideGUID, projectile : Option[Projectile]) + + private final case class ClearJammeredSound() + + private final case class ClearJammeredStatus() } From bb26c5d56efe34eb5f6bd3218631c589ec0ca112 Mon Sep 17 00:00:00 2001 From: FateJH Date: Wed, 18 Dec 2019 00:14:39 -0500 Subject: [PATCH 04/14] proper jammering behavior for both infantry and vehicles; moved certain vehicle operations onto the VehicleControl actor --- .../objects/equipment/JammingUnit.scala | 31 ++- .../turret/FacilityTurretControl.scala | 4 +- .../objects/vehicles/VehicleControl.scala | 209 +++++++++++++++++- .../psforever/objects/vital/Vitality.scala | 2 +- .../scala/services/local/LocalService.scala | 4 +- .../services/vehicle/VehicleService.scala | 37 +++- .../src/main/scala/WorldSessionActor.scala | 189 ++-------------- 7 files changed, 290 insertions(+), 186 deletions(-) diff --git a/common/src/main/scala/net/psforever/objects/equipment/JammingUnit.scala b/common/src/main/scala/net/psforever/objects/equipment/JammingUnit.scala index 1bd54684..9199f097 100644 --- a/common/src/main/scala/net/psforever/objects/equipment/JammingUnit.scala +++ b/common/src/main/scala/net/psforever/objects/equipment/JammingUnit.scala @@ -1,10 +1,25 @@ // Copyright (c) 2019 PSForever package net.psforever.objects.equipment +import net.psforever.objects.PlanetSideGameObject import net.psforever.objects.serverobject.terminals.TargetValidation import scala.collection.mutable -import scala.concurrent.duration.Duration + +trait JammableUnit { + private var jammed : Boolean = false + + def Jammed : Boolean = jammed + + def Jammed_=(state : Boolean) : Boolean = { + jammed = state + Jammed + } +} + +object JammableUnit { + final case class Jammer() +} trait JammingUnit { private val jammedEffectDuration : mutable.ListBuffer[(TargetValidation, Int)] = new mutable.ListBuffer() @@ -13,3 +28,17 @@ trait JammingUnit { def JammedEffectDuration : mutable.ListBuffer[(TargetValidation, Int)] = jammedEffectDuration } + +object JammingUnit { + def FindJammerDuration(jammer : JammingUnit, target : PlanetSideGameObject) : Option[Int] = { + jammer.JammedEffectDuration + .collect { case (TargetValidation(_, test), duration) if test(target) => duration } + .toList + .sortWith(_ > _) + .headOption + } + + def FindJammerDuration(jammer : JammingUnit, targets : Seq[PlanetSideGameObject]) : Seq[Option[Int]] = { + targets.map { target => FindJammerDuration(jammer, target) } + } +} 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..906e49b5 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 @@ -42,13 +42,13 @@ 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) + sender ! Vitality.DamageResolution(turret, cause) } case _ => ; 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..765ff003 100644 --- a/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala +++ b/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala @@ -1,14 +1,23 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.vehicles -import akka.actor.{Actor, ActorRef} -import net.psforever.objects.Vehicle -import net.psforever.objects.ballistics.VehicleSource +import akka.actor.{Actor, ActorRef, Cancellable} +import net.psforever.objects.{DefaultCancellable, GlobalDefinitions, Tool, Vehicle} +import net.psforever.objects.ballistics.{ResolvedProjectile, VehicleSource} +import net.psforever.objects.equipment.JammingUnit 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} + +import scala.concurrent.duration._ /** * An `Actor` that handles messages being dispatched to a specific `Vehicle`.
@@ -22,6 +31,9 @@ class VehicleControl(vehicle : Vehicle) extends Actor with DeploymentBehavior with MountableBehavior.Mount with MountableBehavior.Dismount { + var jammeredSoundTimer : Cancellable = DefaultCancellable.obj + var jammeredStatusTimer : Cancellable = DefaultCancellable.obj + //make control actors belonging to utilities when making control actor belonging to vehicle vehicle.Utilities.foreach({case (_, util) => util.Setup }) @@ -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.HandleVehicleDamageResolution(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,6 +115,15 @@ class VehicleControl(vehicle : Vehicle) extends Actor } sender ! FactionAffinity.AssertFactionAffinity(vehicle, faction) + case VehicleControl.Jammered(cause) => + TryJammerVehicleWithProjectile(vehicle, cause) + + case VehicleControl.ClearJammeredSound() => + CancelJammeredSound(vehicle) + + case VehicleControl.ClearJammeredStatus() => + StopJammeredStatus(vehicle) + case Vehicle.PrepareForDeletion => context.become(Disabled) @@ -110,14 +133,61 @@ class VehicleControl(vehicle : Vehicle) extends Actor def Disabled : Receive = checkBehavior .orElse(dismountBehavior) .orElse { + case VehicleControl.ClearJammeredSound() => + CancelJammeredSound(vehicle) + + case VehicleControl.ClearJammeredStatus() => + StopJammeredStatus(vehicle) + case Vehicle.Reactivate => context.become(Enabled) case _ => ; } + + def TryJammerVehicleWithProjectile(target : Vehicle, cause : ResolvedProjectile) : Unit = { + val radius = cause.projectile.profile.DamageRadius + JammingUnit.FindJammerDuration(cause.projectile.profile, target) match { + case Some(dur) if Vector3.DistanceSquared(cause.hit_pos, cause.target.Position) < radius * radius => + //jammered sound + target.Zone.VehicleEvents ! VehicleServiceMessage(target.Zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 54, 1)) + import scala.concurrent.ExecutionContext.Implicits.global + jammeredSoundTimer = context.system.scheduler.scheduleOnce(30 seconds, self, VehicleControl.ClearJammeredSound()) + //jammered status + StartJammeredStatus(target, dur) + case _ => ; + } + } + + def StartJammeredStatus(target : Vehicle, dur : Int) : Boolean = { + if(jammeredStatusTimer.isCancelled) { + VehicleControl.JammeredStatus(target, 1) + import scala.concurrent.ExecutionContext.Implicits.global + jammeredStatusTimer = context.system.scheduler.scheduleOnce(dur milliseconds, self, VehicleControl.ClearJammeredStatus()) + true + } + else { + false + } + } + + def StopJammeredStatus(target : Vehicle) : Boolean = { + VehicleControl.JammeredStatus(target, 0) + jammeredStatusTimer.cancel + } + + def CancelJammeredSound(target : Vehicle) : Unit = { + jammeredSoundTimer.cancel + target.Zone.VehicleEvents ! VehicleServiceMessage(target.Zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 54, 0)) + } } object VehicleControl { + private final case class Jammered(cause : ResolvedProjectile) + + private final case class ClearJammeredSound() + + private final case class ClearJammeredStatus() import net.psforever.objects.vital.{DamageFromProjectile, VehicleShieldCharge, VitalsActivity} import scala.concurrent.duration._ @@ -136,4 +206,121 @@ object VehicleControl { case _ => false } } + + /** + * na + * @param target na + */ + def HandleVehicleDamageResolution(target : Vehicle, cause : ResolvedProjectile, damage : Int) : Unit = { + 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 _ => PlanetSideGUID(0) + } + if(target.Health > 0) { + //activity on map + if(damage > 0) { + target.Zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos) + //alert occupants to damage source + HandleVehicleDamageAwareness(target, playerGUID, cause) + } + if(cause.projectile.profile.JammerProjectile) { + target.Actor ! VehicleControl.Jammered(cause) + } + } + else { + //alert to vehicle death (hence, occupants' deaths) + HandleVehicleDestructionAwareness(target, playerGUID, cause) + } + target.Zone.VehicleEvents ! VehicleServiceMessage(target.Zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, targetGUID, 0, target.Health)) + target.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 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 + target.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) + HandleVehicleDamageAwareness(cargo, attribution, lastShot) + case None => ; + } + }) + } + + /** + * na + * @param target na + * @param attribution na + * @param lastShot na + */ + def HandleVehicleDestructionAwareness(target : Vehicle, attribution : PlanetSideGUID, lastShot : ResolvedProjectile) : Unit = { + val continentId = target.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 + target.Zone.AvatarEvents ! AvatarServiceMessage(tplayer.Name, AvatarAction.KilledWhileInVehicle(tplayerGUID)) + target.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 + target.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 + HandleVehicleDestructionAwareness(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, target.Zone) + target.Zone.LocalEvents ! LocalServiceMessage(target.Zone.Id, LocalAction.ToggleTeleportSystem(PlanetSideGUID(0), target, None)) + case _ => ; + } + target.Zone.AvatarEvents ! AvatarServiceMessage(continentId, AvatarAction.Destroy(target.GUID, attribution, attribution, target.Position)) + target.Zone.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(target), target.Zone)) + target.Zone.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.AddTask(target, target.Zone, Some(1 minute))) + } + + def JammeredStatus(target : Vehicle, statusCode : Int) : Unit = { + target.Zone.VehicleEvents ! VehicleServiceMessage(target.Zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 27, statusCode)) + target.Weapons.values + .map { _.Equipment } + .collect { + case Some(item : Tool) => + target.Zone.VehicleEvents ! VehicleServiceMessage(target.Zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, item.GUID, 27, statusCode)) + } + } } 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 1c7628ac..17a862bc 100644 --- a/common/src/main/scala/net/psforever/objects/vital/Vitality.scala +++ b/common/src/main/scala/net/psforever/objects/vital/Vitality.scala @@ -111,5 +111,5 @@ object Vitality { * 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/services/local/LocalService.scala b/common/src/main/scala/services/local/LocalService.scala index 69002e53..1c5818ed 100644 --- a/common/src/main/scala/services/local/LocalService.scala +++ b/common/src/main/scala/services/local/LocalService.scala @@ -283,8 +283,8 @@ class LocalService(zone : Zone) extends Actor { //synchronized damage calculations case Vitality.DamageOn(target : Deployable, func) => - func(target) - sender ! Vitality.DamageResolution(target) + val cause = 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/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/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 6033140a..c551b170 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -1104,10 +1104,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 @@ -1116,7 +1113,7 @@ class WorldSessionActor extends Actor with MDCContextAware { AnnounceDestroyDeployable(target, None) } - case Vitality.DamageResolution(target : SensorDeployable) => + case Vitality.DamageResolution(target : SensorDeployable, _) => //sensors val guid = target.GUID val health = target.Health @@ -1125,7 +1122,7 @@ class WorldSessionActor extends Actor with MDCContextAware { AnnounceDestroyDeployable(target, Some(0 seconds)) } - case Vitality.DamageResolution(target : SimpleDeployable) => + case Vitality.DamageResolution(target : SimpleDeployable, _) => //boomers, mines if(target.Health <= 0) { //update if destroyed @@ -1134,10 +1131,10 @@ class WorldSessionActor extends Actor with MDCContextAware { AnnounceDestroyDeployable(target, Some(0 seconds)) } - case Vitality.DamageResolution(target : TurretDeployable) => + case Vitality.DamageResolution(target : TurretDeployable, _) => HandleTurretDeployableDamageResolution(target) - case Vitality.DamageResolution(target : ComplexDeployable) => + case Vitality.DamageResolution(target : ComplexDeployable, _) => //shield_generators val health = target.Health val guid = target.GUID @@ -1146,15 +1143,12 @@ class WorldSessionActor extends Actor with MDCContextAware { AnnounceDestroyDeployable(target, None) } - case Vitality.DamageResolution(target : FacilityTurret) => + 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) @@ -1331,9 +1325,9 @@ class WorldSessionActor extends Actor with MDCContextAware { } } } - if(cause.projectile.profile.JammerProjectile) { + if(target.isAlive && cause.projectile.profile.JammerProjectile) { val radius = cause.projectile.profile.DamageRadius - FindJammerDuration(cause.projectile.profile, target) match { + JammingUnit.FindJammerDuration(cause.projectile.profile, target) match { case Some(dur) if Vector3.DistanceSquared(cause.hit_pos, cause.target.Position) < radius * radius => //implants DeactivateImplants() @@ -1344,12 +1338,11 @@ class WorldSessionActor extends Actor with MDCContextAware { jammeredSoundTimer = context.system.scheduler.scheduleOnce(30 seconds, self, ClearJammeredSound()) //jammered status skipStaminaRegenForTurns = 5 - sendResponse(PlanetsideAttributeMessage(player.GUID, 2, 0)) jammeredEquipment = player.Holsters() .map { _.Equipment } .collect { case Some(item) if item.Size != EquipmentSize.Melee => - sendResponse(GenericObjectActionMessage(item.GUID, 156)) + sendResponse(PlanetsideAttributeMessage(item.GUID, 24, 1)) item.GUID } jammeredStatusTimer = context.system.scheduler.scheduleOnce(dur milliseconds, self, ClearJammeredStatus()) @@ -2493,7 +2486,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) { @@ -2969,113 +2961,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 @@ -5531,6 +5416,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) { @@ -5680,34 +5574,6 @@ class WorldSessionActor extends Actor with MDCContextAware { case None => ; } -// FindProjectileEntry(projectile_guid) match { -// case Some(projectile) => -// val allTargets = (continent.GUID(direct_victim_uid) match { -// case Some(target : PlanetSideGameObject with FactionAffinity with Vitality) => -// HandleDealingDamage(target, ResolveProjectileEntry(projectile, ProjectileResolution.Splash, target, target.Position).get) -// Seq(target) -// case _ => -// Nil -// }) ++ -// targets -// .map { a => continent.GUID(a.uid) } -// .collect { -// case Some(target : PlanetSideGameObject with FactionAffinity with Vitality) => -// HandleDealingDamage(target, ResolveProjectileEntry(projectile, ProjectileResolution.Splash, target, explosion_pos).get) -// target -// } -// if(projectile.profile.JammerProjectile) { -// val jammableTargets = FindJammerTargetsInScope(projectile.profile, allTargets) -// jammableTargets -// .zip(FindJammerDuration(projectile.profile, jammableTargets)) -// .collect { case (target, Some(time)) => -// //TODO jamming messages here -// } -// } -// -// case None => ; -// } - case msg @ LashMessage(seq_time, killer_guid, victim_guid, projectile_guid, pos, unk1) => log.info(s"Lash: $msg") continent.GUID(victim_guid) match { @@ -9870,7 +9736,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 ...") @@ -10337,7 +10202,7 @@ 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 } @@ -10399,18 +10264,6 @@ class WorldSessionActor extends Actor with MDCContextAware { } } - def FindJammerDuration(jammer : JammingUnit, target : PlanetSideGameObject) : Option[Int] = { - jammer.JammedEffectDuration - .collect { case (TargetValidation(_, test), duration) if test(target) => duration } - .toList - .sortWith(_ > _) - .headOption - } - - def FindJammerDuration(jammer : JammingUnit, targets : Seq[PlanetSideGameObject]) : Seq[Option[Int]] = { - targets.map { target => FindJammerDuration(jammer, target) } - } - def DeactivateImplants() : Unit = { DeactivateImplantDarkLight() DeactivateImplantSurge() @@ -10444,7 +10297,7 @@ class WorldSessionActor extends Actor with MDCContextAware { if(!jammeredSoundTimer.isCancelled) { CancelJammeredSound() } - jammeredEquipment.foreach { id => sendResponse(GenericObjectActionMessage(id, 152)) } + jammeredEquipment.foreach { id => sendResponse(PlanetsideAttributeMessage(id, 24, 0)) } jammeredEquipment = Nil } From fa2123f2530617ec65006eb5b1af4b1938a878a3 Mon Sep 17 00:00:00 2001 From: FateJH Date: Thu, 19 Dec 2019 11:12:36 -0500 Subject: [PATCH 05/14] proper jamming behavior for facility turrets; moved certain facility turret operations onto FacilityTurretControl actor; corrected issue with revisiting jammed status --- .../objects/equipment/JammingUnit.scala | 7 + .../turret/FacilityTurretControl.scala | 164 +++++++++++++++++- .../objects/vehicles/VehicleControl.scala | 103 ++++++----- .../game/PlanetsideAttributeMessage.scala | 2 +- .../src/main/scala/WorldSessionActor.scala | 75 ++------ 5 files changed, 232 insertions(+), 119 deletions(-) diff --git a/common/src/main/scala/net/psforever/objects/equipment/JammingUnit.scala b/common/src/main/scala/net/psforever/objects/equipment/JammingUnit.scala index 9199f097..cbc58a62 100644 --- a/common/src/main/scala/net/psforever/objects/equipment/JammingUnit.scala +++ b/common/src/main/scala/net/psforever/objects/equipment/JammingUnit.scala @@ -2,6 +2,7 @@ package net.psforever.objects.equipment import net.psforever.objects.PlanetSideGameObject +import net.psforever.objects.ballistics.ResolvedProjectile import net.psforever.objects.serverobject.terminals.TargetValidation import scala.collection.mutable @@ -19,6 +20,12 @@ trait JammableUnit { object JammableUnit { final case class Jammer() + + final case class Jammered(cause : ResolvedProjectile) + + final case class ClearJammeredSound() + + final case class ClearJammeredStatus() } trait JammingUnit { 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 906e49b5..2d79b22e 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 @@ -1,10 +1,24 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.serverobject.turret -import akka.actor.Actor +import akka.actor.{Actor, Cancellable} +import net.psforever.objects.{DefaultCancellable, Tool} +import net.psforever.objects.ballistics.ResolvedProjectile +import net.psforever.objects.equipment.{JammableUnit, JammingUnit} +import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior} import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} +import net.psforever.objects.vehicles.MountedWeapons import net.psforever.objects.vital.Vitality +import net.psforever.objects.zones.Zone +import net.psforever.packet.game.PlanetSideGUID +import net.psforever.types.Vector3 +import services.Service +import services.avatar.{AvatarAction, AvatarServiceMessage} +import services.vehicle.{VehicleAction, VehicleServiceMessage} +import services.vehicle.support.TurretUpgrader + +import scala.concurrent.duration._ /** * An `Actor` that handles messages being dispatched to a specific `MannedTurret`.
@@ -17,6 +31,9 @@ import net.psforever.objects.vital.Vitality class FacilityTurretControl(turret : FacilityTurret) extends Actor with FactionAffinityBehavior.Check with MountableBehavior.Dismount { + var jammeredSoundTimer : Cancellable = DefaultCancellable.obj + var jammeredStatusTimer : Cancellable = DefaultCancellable.obj + def MountableObject = turret //do not add type! def FactionObject : FactionAffinity = turret @@ -45,12 +62,149 @@ class FacilityTurretControl(turret : FacilityTurret) extends Actor 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, cause) + 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 JammableUnit.Jammered(cause) => + TryJammerWithProjectile(turret, cause) + + case JammableUnit.ClearJammeredSound() => + CancelJammeredSound(turret) + + case JammableUnit.ClearJammeredStatus() => + StopJammeredStatus(turret) + case _ => ; } + + def TryJammerWithProjectile(target : FacilityTurret, cause : ResolvedProjectile) : Unit = { + val radius = cause.projectile.profile.DamageRadius + JammingUnit.FindJammerDuration(cause.projectile.profile, target) match { + case Some(dur) if Vector3.DistanceSquared(cause.hit_pos, cause.target.Position) < radius * radius => + //jammered sound + target.Zone.VehicleEvents ! VehicleServiceMessage(target.Zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 54, 1)) + import scala.concurrent.ExecutionContext.Implicits.global + jammeredSoundTimer = context.system.scheduler.scheduleOnce(30 seconds, self, JammableUnit.ClearJammeredSound()) + //jammered status + StartJammeredStatus(target, dur) + case _ => ; + } + } + + def StartJammeredStatus(target : PlanetSideServerObject with MountedWeapons, dur : Int) : Unit = { + jammeredStatusTimer.cancel + FacilityTurretControl.JammeredStatus(target, 1) + import scala.concurrent.ExecutionContext.Implicits.global + jammeredStatusTimer = context.system.scheduler.scheduleOnce(dur milliseconds, self, JammableUnit.ClearJammeredStatus()) + } + + def StopJammeredStatus(target : PlanetSideServerObject with MountedWeapons) : Boolean = { + FacilityTurretControl.JammeredStatus(target, 0) + jammeredStatusTimer.cancel + } + + def CancelJammeredSound(target : PlanetSideServerObject) : Unit = { + jammeredSoundTimer.cancel + target.Zone.VehicleEvents ! VehicleServiceMessage(target.Zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 54, 0)) + } +} + +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)) + } + } + + def JammeredStatus(target : PlanetSideServerObject with MountedWeapons, statusCode : Int) : Unit = { + val zone = target.Zone + val zoneId = zone.Id + zone.VehicleEvents ! VehicleServiceMessage(zoneId, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 27, statusCode)) + target.Weapons.values + .map { _.Equipment } + .collect { + case Some(item : Tool) => + zone.VehicleEvents ! VehicleServiceMessage(zoneId, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, item.GUID, 27, statusCode)) + } + } } 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 765ff003..cb091b58 100644 --- a/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala +++ b/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala @@ -4,7 +4,8 @@ package net.psforever.objects.vehicles import akka.actor.{Actor, ActorRef, Cancellable} import net.psforever.objects.{DefaultCancellable, GlobalDefinitions, Tool, Vehicle} import net.psforever.objects.ballistics.{ResolvedProjectile, VehicleSource} -import net.psforever.objects.equipment.JammingUnit +import net.psforever.objects.equipment.{JammableUnit, JammingUnit} +import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior} import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} import net.psforever.objects.serverobject.deploy.{Deployment, DeploymentBehavior} @@ -90,7 +91,7 @@ class VehicleControl(vehicle : Vehicle) extends Actor val shields = vehicle.Shields val damageToHealth = originalHealth - health val damageToShields = originalShields - shields - VehicleControl.HandleVehicleDamageResolution(vehicle, cause, damageToHealth + damageToShields) + VehicleControl.HandleDamageResolution(vehicle, cause, damageToHealth + damageToShields) if(damageToHealth > 0 || damageToShields > 0) { val name = vehicle.Actor.toString val slashPoint = name.lastIndexOf("/") @@ -115,13 +116,13 @@ class VehicleControl(vehicle : Vehicle) extends Actor } sender ! FactionAffinity.AssertFactionAffinity(vehicle, faction) - case VehicleControl.Jammered(cause) => - TryJammerVehicleWithProjectile(vehicle, cause) + case JammableUnit.Jammered(cause) => + TryJammerWithProjectile(vehicle, cause) - case VehicleControl.ClearJammeredSound() => + case JammableUnit.ClearJammeredSound() => CancelJammeredSound(vehicle) - case VehicleControl.ClearJammeredStatus() => + case JammableUnit.ClearJammeredStatus() => StopJammeredStatus(vehicle) case Vehicle.PrepareForDeletion => @@ -133,10 +134,10 @@ class VehicleControl(vehicle : Vehicle) extends Actor def Disabled : Receive = checkBehavior .orElse(dismountBehavior) .orElse { - case VehicleControl.ClearJammeredSound() => + case JammableUnit.ClearJammeredSound() => CancelJammeredSound(vehicle) - case VehicleControl.ClearJammeredStatus() => + case JammableUnit.ClearJammeredStatus() => StopJammeredStatus(vehicle) case Vehicle.Reactivate => @@ -145,49 +146,40 @@ class VehicleControl(vehicle : Vehicle) extends Actor case _ => ; } - def TryJammerVehicleWithProjectile(target : Vehicle, cause : ResolvedProjectile) : Unit = { + def TryJammerWithProjectile(target : Vehicle, cause : ResolvedProjectile) : Unit = { val radius = cause.projectile.profile.DamageRadius JammingUnit.FindJammerDuration(cause.projectile.profile, target) match { case Some(dur) if Vector3.DistanceSquared(cause.hit_pos, cause.target.Position) < radius * radius => //jammered sound + jammeredSoundTimer.cancel target.Zone.VehicleEvents ! VehicleServiceMessage(target.Zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 54, 1)) import scala.concurrent.ExecutionContext.Implicits.global - jammeredSoundTimer = context.system.scheduler.scheduleOnce(30 seconds, self, VehicleControl.ClearJammeredSound()) + jammeredSoundTimer = context.system.scheduler.scheduleOnce(30 seconds, self, JammableUnit.ClearJammeredSound()) //jammered status StartJammeredStatus(target, dur) case _ => ; } } - def StartJammeredStatus(target : Vehicle, dur : Int) : Boolean = { - if(jammeredStatusTimer.isCancelled) { - VehicleControl.JammeredStatus(target, 1) - import scala.concurrent.ExecutionContext.Implicits.global - jammeredStatusTimer = context.system.scheduler.scheduleOnce(dur milliseconds, self, VehicleControl.ClearJammeredStatus()) - true - } - else { - false - } + def StartJammeredStatus(target : PlanetSideServerObject with MountedWeapons, dur : Int) : Unit = { + jammeredStatusTimer.cancel + VehicleControl.JammeredStatus(target, 1) + import scala.concurrent.ExecutionContext.Implicits.global + jammeredStatusTimer = context.system.scheduler.scheduleOnce(dur milliseconds, self, JammableUnit.ClearJammeredStatus()) } - def StopJammeredStatus(target : Vehicle) : Boolean = { + def StopJammeredStatus(target : PlanetSideServerObject with MountedWeapons) : Boolean = { VehicleControl.JammeredStatus(target, 0) jammeredStatusTimer.cancel } - def CancelJammeredSound(target : Vehicle) : Unit = { + def CancelJammeredSound(target : PlanetSideServerObject) : Unit = { jammeredSoundTimer.cancel target.Zone.VehicleEvents ! VehicleServiceMessage(target.Zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 54, 0)) } } object VehicleControl { - private final case class Jammered(cause : ResolvedProjectile) - - private final case class ClearJammeredSound() - - private final case class ClearJammeredStatus() import net.psforever.objects.vital.{DamageFromProjectile, VehicleShieldCharge, VitalsActivity} import scala.concurrent.duration._ @@ -211,29 +203,30 @@ object VehicleControl { * na * @param target na */ - def HandleVehicleDamageResolution(target : Vehicle, cause : ResolvedProjectile, damage : Int) : Unit = { + def HandleDamageResolution(target : Vehicle, 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 { + 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) { - target.Zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos) + zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos) //alert occupants to damage source - HandleVehicleDamageAwareness(target, playerGUID, cause) + HandleDamageAwareness(target, playerGUID, cause) } if(cause.projectile.profile.JammerProjectile) { - target.Actor ! VehicleControl.Jammered(cause) + target.Actor ! JammableUnit.Jammered(cause) } } else { //alert to vehicle death (hence, occupants' deaths) - HandleVehicleDestructionAwareness(target, playerGUID, cause) + HandleDestructionAwareness(target, playerGUID, cause) } - target.Zone.VehicleEvents ! VehicleServiceMessage(target.Zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, targetGUID, 0, target.Health)) - target.Zone.VehicleEvents ! VehicleServiceMessage(s"${target.Actor}", VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, targetGUID, 68, target.Shields)) + 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)) } /** @@ -242,13 +235,14 @@ object VehicleControl { * @param attribution na * @param lastShot na */ - def HandleVehicleDamageAwareness(target : Vehicle, attribution : PlanetSideGUID, lastShot : ResolvedProjectile) : Unit = { + 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 - target.Zone.AvatarEvents ! AvatarServiceMessage(tplayer.Name, AvatarAction.HitHint(attribution, tplayer.GUID)) + zone.AvatarEvents ! AvatarServiceMessage(tplayer.Name, AvatarAction.HitHint(attribution, tplayer.GUID)) }) //alert cargo occupants to damage source target.CargoHolds.values.foreach(hold => { @@ -257,7 +251,7 @@ object VehicleControl { cargo.Health = 0 cargo.Shields = 0 cargo.History(lastShot) - HandleVehicleDamageAwareness(cargo, attribution, lastShot) + HandleDamageAwareness(cargo, attribution, lastShot) case None => ; } }) @@ -269,16 +263,19 @@ object VehicleControl { * @param attribution na * @param lastShot na */ - def HandleVehicleDestructionAwareness(target : Vehicle, attribution : PlanetSideGUID, lastShot : ResolvedProjectile) : Unit = { - val continentId = target.Zone.Id + 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 - target.Zone.AvatarEvents ! AvatarServiceMessage(tplayer.Name, AvatarAction.KilledWhileInVehicle(tplayerGUID)) - target.Zone.AvatarEvents ! AvatarServiceMessage(continentId, AvatarAction.ObjectDelete(tplayerGUID, tplayerGUID)) //dead player still sees self + 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 @@ -287,7 +284,7 @@ object VehicleControl { } .foreach(slot => { val wep = slot.Equipment.get - target.Zone.AvatarEvents ! AvatarServiceMessage(continentId, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, wep.GUID)) + zone.AvatarEvents ! AvatarServiceMessage(continentId, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, wep.GUID)) }) target.CargoHolds.values.foreach(hold => { hold.Occupant match { @@ -296,7 +293,7 @@ object VehicleControl { cargo.Shields = 0 cargo.Position += Vector3.z(1) cargo.History(lastShot) //necessary to kill cargo vehicle occupants //TODO: collision damage - HandleVehicleDestructionAwareness(cargo, attribution, lastShot) //might cause redundant packets + HandleDestructionAwareness(cargo, attribution, lastShot) //might cause redundant packets case None => ; } }) @@ -305,22 +302,24 @@ object VehicleControl { target.Actor ! Deployment.TryDeploymentChange(DriveState.Undeploying) case GlobalDefinitions.router => target.Actor ! Deployment.TryDeploymentChange(DriveState.Undeploying) - VehicleService.BeforeUnloadVehicle(target, target.Zone) - target.Zone.LocalEvents ! LocalServiceMessage(target.Zone.Id, LocalAction.ToggleTeleportSystem(PlanetSideGUID(0), target, None)) + VehicleService.BeforeUnloadVehicle(target, zone) + zone.LocalEvents ! LocalServiceMessage(zone.Id, LocalAction.ToggleTeleportSystem(PlanetSideGUID(0), target, None)) case _ => ; } - target.Zone.AvatarEvents ! AvatarServiceMessage(continentId, AvatarAction.Destroy(target.GUID, attribution, attribution, target.Position)) - target.Zone.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(target), target.Zone)) - target.Zone.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.AddTask(target, target.Zone, Some(1 minute))) + 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))) } - def JammeredStatus(target : Vehicle, statusCode : Int) : Unit = { - target.Zone.VehicleEvents ! VehicleServiceMessage(target.Zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 27, statusCode)) + def JammeredStatus(target : PlanetSideServerObject with MountedWeapons, statusCode : Int) : Unit = { + val zone = target.Zone + val zoneId = zone.Id + zone.VehicleEvents ! VehicleServiceMessage(zoneId, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 27, statusCode)) target.Weapons.values .map { _.Equipment } .collect { case Some(item : Tool) => - target.Zone.VehicleEvents ! VehicleServiceMessage(target.Zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, item.GUID, 27, statusCode)) + zone.VehicleEvents ! VehicleServiceMessage(zoneId, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, item.GUID, 27, statusCode)) } } } 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..b09ffb41 100644 --- a/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala @@ -118,7 +118,7 @@ import scodec.codecs._ * `27 - PA_JAMMED - plays jammed buzzing sound`
* `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`
diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index c551b170..6db3e6d7 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -1143,9 +1143,6 @@ class WorldSessionActor extends Actor with MDCContextAware { AnnounceDestroyDeployable(target, None) } - case Vitality.DamageResolution(target : FacilityTurret, _) => - HandleFacilityTurretDamageResolution(target) - case Vitality.DamageResolution(target : PlanetSideGameObject, _) => log.warn(s"Vital target ${target.Definition.Name} damage resolution not supported using this method") @@ -1335,16 +1332,18 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(PlanetsideAttributeMessage(player.GUID, 54, 1)) continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player.GUID, 54, 1)) import scala.concurrent.ExecutionContext.Implicits.global + jammeredSoundTimer.cancel jammeredSoundTimer = context.system.scheduler.scheduleOnce(30 seconds, self, ClearJammeredSound()) //jammered status skipStaminaRegenForTurns = 5 - jammeredEquipment = player.Holsters() + jammeredEquipment = (jammeredEquipment ++ player.Holsters() .map { _.Equipment } .collect { case Some(item) if item.Size != EquipmentSize.Melee => - sendResponse(PlanetsideAttributeMessage(item.GUID, 24, 1)) + sendResponse(PlanetsideAttributeMessage(item.GUID, 27, 1)) item.GUID - } + }).distinct + jammeredStatusTimer.cancel jammeredStatusTimer = context.system.scheduler.scheduleOnce(dur milliseconds, self, ClearJammeredStatus()) case _ => ; } @@ -2995,48 +2994,6 @@ class WorldSessionActor extends Actor with MDCContextAware { 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 @@ -4691,7 +4648,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 { @@ -7844,10 +7801,8 @@ 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 = { - if(!jammeredSoundTimer.isCancelled) { - CancelJammeredSound() - } - jammeredStatusTimer.cancel + CancelJammeredSound() + CancelJammeredStatus() progressBarUpdate.cancel progressBarValue = None lastTerminalOrderFulfillment = true @@ -8571,14 +8526,14 @@ class WorldSessionActor extends Actor with MDCContextAware { //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`) + //damage is synchronized on the vehicle actor + obj.Actor ! Vitality.Damage(func) + case obj : FacilityTurret => + //damage is synchronized on the turret actor obj.Actor ! Vitality.Damage(func) case obj : Deployable => //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 _ => ; } } @@ -10294,10 +10249,8 @@ class WorldSessionActor extends Actor with MDCContextAware { } def CancelJammeredStatus() : Unit = { - if(!jammeredSoundTimer.isCancelled) { - CancelJammeredSound() - } - jammeredEquipment.foreach { id => sendResponse(PlanetsideAttributeMessage(id, 24, 0)) } + jammeredStatusTimer.cancel + jammeredEquipment.foreach { id => sendResponse(PlanetsideAttributeMessage(id, 27, 0)) } jammeredEquipment = Nil } From a568e52590d95426406faeb9ada84d6c4de34021 Mon Sep 17 00:00:00 2001 From: FateJH Date: Fri, 20 Dec 2019 00:47:22 -0500 Subject: [PATCH 06/14] moved common jammering behavior into a mixin trait and redundant code out of the specific object control actors; created placeholder PlayerControl actor but am not ready to move damage/jammering functionality out from WSA; common jammering behavior modified for WSA use --- .../scala/net/psforever/objects/Player.scala | 6 +- .../scala/net/psforever/objects/Vehicle.scala | 3 +- .../objects/avatar/PlayerControl.scala | 15 +++ .../objects/equipment/JammingUnit.scala | 111 +++++++++++++++++- .../serverobject/turret/FacilityTurret.scala | 2 + .../turret/FacilityTurretControl.scala | 71 ++--------- .../objects/vehicles/VehicleControl.scala | 78 ++---------- .../objects/zones/ZonePopulationActor.scala | 12 +- .../game/PlanetsideAttributeMessage.scala | 4 +- .../src/main/scala/WorldSessionActor.scala | 111 ++++++++++-------- 10 files changed, 222 insertions(+), 191 deletions(-) create mode 100644 common/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala 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/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/equipment/JammingUnit.scala b/common/src/main/scala/net/psforever/objects/equipment/JammingUnit.scala index cbc58a62..a6c6705c 100644 --- a/common/src/main/scala/net/psforever/objects/equipment/JammingUnit.scala +++ b/common/src/main/scala/net/psforever/objects/equipment/JammingUnit.scala @@ -1,11 +1,19 @@ // Copyright (c) 2019 PSForever package net.psforever.objects.equipment -import net.psforever.objects.PlanetSideGameObject +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.serverobject.terminals.TargetValidation +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._ trait JammableUnit { private var jammed : Boolean = false @@ -49,3 +57,104 @@ object JammingUnit { targets.map { target => FindJammerDuration(jammer, target) } } } + +trait JammableBehavior { + _ : Actor => + protected var jammeredSoundTimer : Cancellable = DefaultCancellable.obj + protected var jammeredStatusTimer : Cancellable = DefaultCancellable.obj + + def JammableObject : PlanetSideServerObject with JammableUnit with ZoneAware + + def TryJammerEffectActivate(target : Any, cause : ResolvedProjectile) : Unit + + def StartJammeredSound(target : Any, dur : Int = 30000) : Unit = { + import scala.concurrent.ExecutionContext.Implicits.global + jammeredSoundTimer.cancel + jammeredSoundTimer = context.system.scheduler.scheduleOnce(30 seconds, self, JammableUnit.ClearJammeredSound()) + } + + 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()) + } + + def CancelJammeredSound(target : Any) : Unit = { + jammeredSoundTimer.cancel + } + + def CancelJammeredStatus(target : Any) : Unit = { + JammableObject.Jammed = false + jammeredStatusTimer.cancel + } + + def jammableBehavior : Receive = { + case JammableUnit.Jammered(cause) => + TryJammerEffectActivate(JammableObject, cause) + + case JammableUnit.ClearJammeredSound() => + CancelJammeredSound(JammableObject) + + case JammableUnit.ClearJammeredStatus() => + CancelJammeredStatus(JammableObject) + } +} + +trait JammableMountedWeapons extends Actor with JammableBehavior { + _ : Actor => + + def TryJammerEffectActivate(target : Any, cause : ResolvedProjectile) : Unit = target match { + case obj : PlanetSideServerObject with MountedWeapons => + 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) + StartJammeredStatus(obj, dur) + case _ => ; + } + case _ => ; + } + + override def StartJammeredSound(target : Any, dur : Int) : Unit = target match { + case obj : PlanetSideServerObject with MountedWeapons => + 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 MountedWeapons => + JammableMountedWeapons.JammeredStatus(obj, 1) + super.StartJammeredStatus(obj, dur) + case _ => ; + } + + override def CancelJammeredSound(target : Any) : Unit = target match { + case obj : PlanetSideServerObject => + obj.Zone.VehicleEvents ! VehicleServiceMessage(obj.Zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, obj.GUID, 54, 0)) + super.CancelJammeredSound(obj) + case _ => ; + } + + override def CancelJammeredStatus(target : Any) : Unit = target match { + case obj : PlanetSideServerObject with MountedWeapons => + JammableMountedWeapons.JammeredStatus(obj, 0) + super.CancelJammeredStatus(obj) + case _ => ; + } +} + +object JammableMountedWeapons { + def JammeredStatus(target : PlanetSideServerObject with MountedWeapons, statusCode : Int) : Unit = { + val zone = target.Zone + val zoneId = zone.Id + zone.VehicleEvents ! VehicleServiceMessage(zoneId, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 27, statusCode)) + target.Weapons.values + .map { _.Equipment } + .collect { + case Some(item : Tool) => + zone.VehicleEvents ! VehicleServiceMessage(zoneId, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, item.GUID, 27, statusCode)) + } + } +} 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..5edb2384 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 with WeaponTurret + with JammableUnit with Vitality with StandardResistanceProfile { /** some turrets can be updated; they all start without updates */ 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 2d79b22e..dbb8e7b1 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 @@ -1,25 +1,19 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.serverobject.turret -import akka.actor.{Actor, Cancellable} -import net.psforever.objects.{DefaultCancellable, Tool} +import akka.actor.Actor import net.psforever.objects.ballistics.ResolvedProjectile -import net.psforever.objects.equipment.{JammableUnit, JammingUnit} -import net.psforever.objects.serverobject.PlanetSideServerObject +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.vehicles.MountedWeapons import net.psforever.objects.vital.Vitality import net.psforever.objects.zones.Zone import net.psforever.packet.game.PlanetSideGUID -import net.psforever.types.Vector3 import services.Service import services.avatar.{AvatarAction, AvatarServiceMessage} import services.vehicle.{VehicleAction, VehicleServiceMessage} import services.vehicle.support.TurretUpgrader -import scala.concurrent.duration._ - /** * An `Actor` that handles messages being dispatched to a specific `MannedTurret`.
*
@@ -30,11 +24,12 @@ import scala.concurrent.duration._ */ class FacilityTurretControl(turret : FacilityTurret) extends Actor with FactionAffinityBehavior.Check - with MountableBehavior.Dismount { - var jammeredSoundTimer : Cancellable = DefaultCancellable.obj - var jammeredStatusTimer : Cancellable = DefaultCancellable.obj + with MountableBehavior.Dismount + with JammableMountedWeapons { - def MountableObject = turret //do not add type! + def MountableObject = turret + + def JammableObject = turret def FactionObject : FactionAffinity = turret @@ -70,48 +65,8 @@ class FacilityTurretControl(turret : FacilityTurret) extends Actor } } - case JammableUnit.Jammered(cause) => - TryJammerWithProjectile(turret, cause) - - case JammableUnit.ClearJammeredSound() => - CancelJammeredSound(turret) - - case JammableUnit.ClearJammeredStatus() => - StopJammeredStatus(turret) - case _ => ; } - - def TryJammerWithProjectile(target : FacilityTurret, cause : ResolvedProjectile) : Unit = { - val radius = cause.projectile.profile.DamageRadius - JammingUnit.FindJammerDuration(cause.projectile.profile, target) match { - case Some(dur) if Vector3.DistanceSquared(cause.hit_pos, cause.target.Position) < radius * radius => - //jammered sound - target.Zone.VehicleEvents ! VehicleServiceMessage(target.Zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 54, 1)) - import scala.concurrent.ExecutionContext.Implicits.global - jammeredSoundTimer = context.system.scheduler.scheduleOnce(30 seconds, self, JammableUnit.ClearJammeredSound()) - //jammered status - StartJammeredStatus(target, dur) - case _ => ; - } - } - - def StartJammeredStatus(target : PlanetSideServerObject with MountedWeapons, dur : Int) : Unit = { - jammeredStatusTimer.cancel - FacilityTurretControl.JammeredStatus(target, 1) - import scala.concurrent.ExecutionContext.Implicits.global - jammeredStatusTimer = context.system.scheduler.scheduleOnce(dur milliseconds, self, JammableUnit.ClearJammeredStatus()) - } - - def StopJammeredStatus(target : PlanetSideServerObject with MountedWeapons) : Boolean = { - FacilityTurretControl.JammeredStatus(target, 0) - jammeredStatusTimer.cancel - } - - def CancelJammeredSound(target : PlanetSideServerObject) : Unit = { - jammeredSoundTimer.cancel - target.Zone.VehicleEvents ! VehicleServiceMessage(target.Zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 54, 0)) - } } object FacilityTurretControl { @@ -195,16 +150,4 @@ object FacilityTurretControl { zone.VehicleEvents ! VehicleServiceMessage.TurretUpgrade(TurretUpgrader.AddTask(target, zone, TurretUpgrade.None)) } } - - def JammeredStatus(target : PlanetSideServerObject with MountedWeapons, statusCode : Int) : Unit = { - val zone = target.Zone - val zoneId = zone.Id - zone.VehicleEvents ! VehicleServiceMessage(zoneId, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 27, statusCode)) - target.Weapons.values - .map { _.Equipment } - .collect { - case Some(item : Tool) => - zone.VehicleEvents ! VehicleServiceMessage(zoneId, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, item.GUID, 27, statusCode)) - } - } } 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 cb091b58..85933857 100644 --- a/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala +++ b/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala @@ -1,11 +1,10 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.vehicles -import akka.actor.{Actor, ActorRef, Cancellable} -import net.psforever.objects.{DefaultCancellable, GlobalDefinitions, Tool, Vehicle} +import akka.actor.{Actor, ActorRef} +import net.psforever.objects.{GlobalDefinitions, Vehicle} import net.psforever.objects.ballistics.{ResolvedProjectile, VehicleSource} -import net.psforever.objects.equipment.{JammableUnit, JammingUnit} -import net.psforever.objects.serverobject.PlanetSideServerObject +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.{Deployment, DeploymentBehavior} @@ -18,8 +17,6 @@ import services.avatar.{AvatarAction, AvatarServiceMessage} import services.local.{LocalAction, LocalServiceMessage} import services.vehicle.{VehicleAction, VehicleService, VehicleServiceMessage} -import scala.concurrent.duration._ - /** * An `Actor` that handles messages being dispatched to a specific `Vehicle`.
*
@@ -31,15 +28,16 @@ class VehicleControl(vehicle : Vehicle) extends Actor with FactionAffinityBehavior.Check with DeploymentBehavior with MountableBehavior.Mount - with MountableBehavior.Dismount { - var jammeredSoundTimer : Cancellable = DefaultCancellable.obj - var jammeredStatusTimer : Cancellable = DefaultCancellable.obj + 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 @@ -57,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 @@ -116,16 +115,9 @@ class VehicleControl(vehicle : Vehicle) extends Actor } sender ! FactionAffinity.AssertFactionAffinity(vehicle, faction) - case JammableUnit.Jammered(cause) => - TryJammerWithProjectile(vehicle, cause) - - case JammableUnit.ClearJammeredSound() => - CancelJammeredSound(vehicle) - - case JammableUnit.ClearJammeredStatus() => - StopJammeredStatus(vehicle) - case Vehicle.PrepareForDeletion => + CancelJammeredSound(vehicle) + CancelJammeredStatus(vehicle) context.become(Disabled) case _ => ; @@ -134,49 +126,11 @@ class VehicleControl(vehicle : Vehicle) extends Actor def Disabled : Receive = checkBehavior .orElse(dismountBehavior) .orElse { - case JammableUnit.ClearJammeredSound() => - CancelJammeredSound(vehicle) - - case JammableUnit.ClearJammeredStatus() => - StopJammeredStatus(vehicle) - case Vehicle.Reactivate => context.become(Enabled) case _ => ; } - - def TryJammerWithProjectile(target : Vehicle, cause : ResolvedProjectile) : Unit = { - val radius = cause.projectile.profile.DamageRadius - JammingUnit.FindJammerDuration(cause.projectile.profile, target) match { - case Some(dur) if Vector3.DistanceSquared(cause.hit_pos, cause.target.Position) < radius * radius => - //jammered sound - jammeredSoundTimer.cancel - target.Zone.VehicleEvents ! VehicleServiceMessage(target.Zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 54, 1)) - import scala.concurrent.ExecutionContext.Implicits.global - jammeredSoundTimer = context.system.scheduler.scheduleOnce(30 seconds, self, JammableUnit.ClearJammeredSound()) - //jammered status - StartJammeredStatus(target, dur) - case _ => ; - } - } - - def StartJammeredStatus(target : PlanetSideServerObject with MountedWeapons, dur : Int) : Unit = { - jammeredStatusTimer.cancel - VehicleControl.JammeredStatus(target, 1) - import scala.concurrent.ExecutionContext.Implicits.global - jammeredStatusTimer = context.system.scheduler.scheduleOnce(dur milliseconds, self, JammableUnit.ClearJammeredStatus()) - } - - def StopJammeredStatus(target : PlanetSideServerObject with MountedWeapons) : Boolean = { - VehicleControl.JammeredStatus(target, 0) - jammeredStatusTimer.cancel - } - - def CancelJammeredSound(target : PlanetSideServerObject) : Unit = { - jammeredSoundTimer.cancel - target.Zone.VehicleEvents ! VehicleServiceMessage(target.Zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 54, 0)) - } } object VehicleControl { @@ -310,16 +264,4 @@ object VehicleControl { zone.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(target), zone)) zone.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.AddTask(target, zone, Some(1 minute))) } - - def JammeredStatus(target : PlanetSideServerObject with MountedWeapons, statusCode : Int) : Unit = { - val zone = target.Zone - val zoneId = zone.Id - zone.VehicleEvents ! VehicleServiceMessage(zoneId, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 27, statusCode)) - target.Weapons.values - .map { _.Equipment } - .collect { - case Some(item : Tool) => - zone.VehicleEvents ! VehicleServiceMessage(zoneId, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, item.GUID, 27, statusCode)) - } - } } 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/PlanetsideAttributeMessage.scala b/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala index b09ffb41..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,7 +115,7 @@ 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):`
@@ -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/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 6db3e6d7..f986a8ad 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 @@ -165,8 +167,6 @@ class WorldSessionActor extends Actor with MDCContextAware { var cargoDismountTimer : Cancellable = DefaultCancellable.obj var antChargingTick : Cancellable = DefaultCancellable.obj var antDischargingTick : Cancellable = DefaultCancellable.obj - var jammeredSoundTimer : Cancellable = DefaultCancellable.obj - var jammeredStatusTimer : Cancellable = DefaultCancellable.obj /** @@ -178,12 +178,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() @@ -191,6 +192,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 @@ -320,7 +322,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) @@ -1174,12 +1176,6 @@ class WorldSessionActor extends Actor with MDCContextAware { case _ => ; } - case ClearJammeredSound() => - CancelJammeredSound() - - case ClearJammeredStatus() => - CancelJammeredStatus() - case default => log.warn(s"Invalid packet class received: $default from $sender") } @@ -1323,30 +1319,7 @@ class WorldSessionActor extends Actor with MDCContextAware { } } if(target.isAlive && cause.projectile.profile.JammerProjectile) { - val radius = cause.projectile.profile.DamageRadius - JammingUnit.FindJammerDuration(cause.projectile.profile, target) match { - case Some(dur) if Vector3.DistanceSquared(cause.hit_pos, cause.target.Position) < radius * radius => - //implants - DeactivateImplants() - //jammered sound - sendResponse(PlanetsideAttributeMessage(player.GUID, 54, 1)) - continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player.GUID, 54, 1)) - import scala.concurrent.ExecutionContext.Implicits.global - jammeredSoundTimer.cancel - jammeredSoundTimer = context.system.scheduler.scheduleOnce(30 seconds, self, ClearJammeredSound()) - //jammered status - skipStaminaRegenForTurns = 5 - jammeredEquipment = (jammeredEquipment ++ player.Holsters() - .map { _.Equipment } - .collect { - case Some(item) if item.Size != EquipmentSize.Melee => - sendResponse(PlanetsideAttributeMessage(item.GUID, 27, 1)) - item.GUID - }).distinct - jammeredStatusTimer.cancel - jammeredStatusTimer = context.system.scheduler.scheduleOnce(dur milliseconds, self, ClearJammeredStatus()) - case _ => ; - } + self ! JammableUnit.Jammered(cause) } } @@ -3425,6 +3398,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 @@ -7801,8 +7775,8 @@ 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() - CancelJammeredStatus() + CancelJammeredSound(player) + CancelJammeredStatus(player) progressBarUpdate.cancel progressBarValue = None lastTerminalOrderFulfillment = true @@ -10242,16 +10216,55 @@ class WorldSessionActor extends Actor with MDCContextAware { } } - def CancelJammeredSound() : Unit = { - jammeredSoundTimer.cancel - sendResponse(PlanetsideAttributeMessage(player.GUID, 54, 0)) - continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player.GUID, 54, 0)) + def TryJammerEffectActivate(target : Any, cause : ResolvedProjectile) : Unit = target match { + case obj : Player => + 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 => + DeactivateImplants() + skipStaminaRegenForTurns = 5 + StartJammeredSound(obj) + StartJammeredStatus(obj, dur) + case _ => ; + } + case _ => ; } - def CancelJammeredStatus() : Unit = { - jammeredStatusTimer.cancel - jammeredEquipment.foreach { id => sendResponse(PlanetsideAttributeMessage(id, 27, 0)) } - jammeredEquipment = Nil + override def StartJammeredSound(target : Any, dur : Int) : Unit = target match { + case obj : Player => + sendResponse(PlanetsideAttributeMessage(obj.GUID, 27, 1)) + continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(obj.GUID, 27, 1)) + super.StartJammeredSound(obj, dur) + case _ => ; + } + + override def StartJammeredStatus(target : Any, dur : Int) : Unit = target match { + case obj : Player => + jammeredEquipment = (jammeredEquipment ++ obj.Holsters() + .map { _.Equipment } + .collect { + case Some(item) if item.Size != EquipmentSize.Melee => + sendResponse(PlanetsideAttributeMessage(item.GUID, 27, 1)) + item.GUID + }).distinct + super.StartJammeredStatus(obj, dur) + case _ => ; + } + + override def CancelJammeredSound(target : Any) : Unit = target match { + case obj : Player => + sendResponse(PlanetsideAttributeMessage(obj.GUID, 27, 0)) + continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(obj.GUID, 27, 0)) + super.CancelJammeredSound(obj) + case _ => ; + } + + override def CancelJammeredStatus(target : Any) : Unit = target match { + case obj : Player => + jammeredEquipment.foreach { id => sendResponse(PlanetsideAttributeMessage(id, 27, 0)) } + jammeredEquipment = Nil + super.CancelJammeredStatus(obj) + case _ => ; } def failWithError(error : String) = { @@ -10412,15 +10425,11 @@ 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) private final case class LoadedRemoteProjectile(projectile_guid : PlanetSideGUID, projectile : Option[Projectile]) - - private final case class ClearJammeredSound() - - private final case class ClearJammeredStatus() } From fee001596f1fe3f5f4af026a9003ce54109376b6 Mon Sep 17 00:00:00 2001 From: FateJH Date: Sat, 21 Dec 2019 09:08:06 -0500 Subject: [PATCH 07/14] correcting the inheritance of turrets (FacilityTurret and TurretDeployable) by untangling their definition structures; damage and jammering code for ComplexDeployable objects moved onto that object's control actor; setting up SimpleDeployable objects for jammering status; correcting an oversight with FacilityTurret jammering --- .../psforever/objects/BoomerDeployable.scala | 4 +- .../objects/ExplosiveDeployable.scala | 6 +- .../psforever/objects/GlobalDefinitions.scala | 20 +-- .../psforever/objects/SensorDeployable.scala | 6 +- .../objects/ShieldGeneratorDeployable.scala | 130 +++++++++++++- .../psforever/objects/TelepadDeployable.scala | 4 +- .../psforever/objects/TrapDeployable.scala | 4 +- .../psforever/objects/TurretDeployable.scala | 170 +++++++++++++++--- .../objects/ballistics/SourceEntry.scala | 3 +- .../objects/ce/ComplexDeployable.scala | 4 +- .../net/psforever/objects/ce/Deployable.scala | 6 +- .../objects/ce/SimpleDeployable.scala | 4 +- ...scala => SimpleDeployableDefinition.scala} | 17 +- .../objects/equipment/JammingUnit.scala | 28 ++- .../serverobject/turret/FacilityTurret.scala | 10 +- .../turret/FacilityTurretControl.scala | 1 + .../turret/FacilityTurretDefinition.scala | 17 ++ .../turret/TurretDefinition.scala | 12 +- .../psforever/objects/vehicles/Utility.scala | 4 +- .../objects/zones/ZoneDeployableActor.scala | 1 + .../scala/objects/FacilityTurretTest.scala | 4 +- .../src/main/scala/WorldSessionActor.scala | 100 +++-------- 22 files changed, 378 insertions(+), 177 deletions(-) rename common/src/main/scala/net/psforever/objects/definition/{DeployableDefinition.scala => SimpleDeployableDefinition.scala} (80%) create mode 100644 common/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretDefinition.scala diff --git a/common/src/main/scala/net/psforever/objects/BoomerDeployable.scala b/common/src/main/scala/net/psforever/objects/BoomerDeployable.scala index f218da87..21d66ea2 100644 --- a/common/src/main/scala/net/psforever/objects/BoomerDeployable.scala +++ b/common/src/main/scala/net/psforever/objects/BoomerDeployable.scala @@ -1,9 +1,9 @@ // Copyright (c) 2017 PSForever package net.psforever.objects -import net.psforever.objects.definition.DeployableDefinition +import net.psforever.objects.definition.SimpleDeployableDefinition -class BoomerDeployable(cdef : DeployableDefinition) extends ExplosiveDeployable(cdef) { +class BoomerDeployable(cdef : SimpleDeployableDefinition) extends ExplosiveDeployable(cdef) { private var trigger : Option[BoomerTrigger] = None def Trigger : Option[BoomerTrigger] = trigger diff --git a/common/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala b/common/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala index 9d03bf74..b324c666 100644 --- a/common/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala +++ b/common/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala @@ -2,9 +2,11 @@ package net.psforever.objects import net.psforever.objects.ce.SimpleDeployable -import net.psforever.objects.definition.DeployableDefinition +import net.psforever.objects.definition.SimpleDeployableDefinition +import net.psforever.objects.equipment.JammableUnit -class ExplosiveDeployable(cdef : DeployableDefinition) extends SimpleDeployable(cdef) { +class ExplosiveDeployable(cdef : SimpleDeployableDefinition) extends SimpleDeployable(cdef) + with JammableUnit { private var exploded : Boolean = false def Exploded : Boolean = exploded diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index 0a69c556..13066bd9 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 = SimpleDeployableDefinition(DeployedItem.boomer) - val he_mine = DeployableDefinition(DeployedItem.he_mine) + val he_mine = SimpleDeployableDefinition(DeployedItem.he_mine) - val jammer_mine = DeployableDefinition(DeployedItem.jammer_mine) + val jammer_mine = SimpleDeployableDefinition(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 = SimpleDeployableDefinition(DeployedItem.motionalarmsensor) - val sensor_shield = DeployableDefinition(DeployedItem.sensor_shield) + val sensor_shield = SimpleDeployableDefinition(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) diff --git a/common/src/main/scala/net/psforever/objects/SensorDeployable.scala b/common/src/main/scala/net/psforever/objects/SensorDeployable.scala index eccfcc7e..1c886cd9 100644 --- a/common/src/main/scala/net/psforever/objects/SensorDeployable.scala +++ b/common/src/main/scala/net/psforever/objects/SensorDeployable.scala @@ -2,8 +2,10 @@ package net.psforever.objects import net.psforever.objects.ce.SimpleDeployable -import net.psforever.objects.definition.DeployableDefinition +import net.psforever.objects.definition.SimpleDeployableDefinition +import net.psforever.objects.equipment.JammableUnit import net.psforever.objects.serverobject.hackable.Hackable -class SensorDeployable(cdef : DeployableDefinition) extends SimpleDeployable(cdef) +class SensorDeployable(cdef : SimpleDeployableDefinition) extends SimpleDeployable(cdef) with Hackable + with JammableUnit diff --git a/common/src/main/scala/net/psforever/objects/ShieldGeneratorDeployable.scala b/common/src/main/scala/net/psforever/objects/ShieldGeneratorDeployable.scala index bbdedd91..ffe8dcb1 100644 --- a/common/src/main/scala/net/psforever/objects/ShieldGeneratorDeployable.scala +++ b/common/src/main/scala/net/psforever/objects/ShieldGeneratorDeployable.scala @@ -1,15 +1,139 @@ // 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.{DeployableInfo, DeploymentAction, PlanetSideGUID} +import services.avatar.{AvatarAction, AvatarServiceMessage} +import services.local.{LocalAction, LocalServiceMessage} +import services.{RemoverActor, Service} +import services.vehicle.{VehicleAction, VehicleServiceMessage} + +import scala.concurrent.duration.FiniteDuration 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 _ => ; + } + + override def StartJammeredSound(target : Any, dur : Int) : Unit = { } + + override def StartJammeredStatus(target : Any, dur : Int) : Unit = target match { + case obj : PlanetSideServerObject => + obj.Zone.VehicleEvents ! VehicleServiceMessage(obj.Zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, obj.GUID, 54, 1)) + super.StartJammeredStatus(obj, dur) + case _ => ; + } + + override def CancelJammeredSound(target : Any) : Unit = { } + + override def CancelJammeredStatus(target : Any) : Unit = target match { + case obj : PlanetSideServerObject => + obj.Zone.VehicleEvents ! VehicleServiceMessage(obj.Zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, obj.GUID, 54, 0)) + super.CancelJammeredStatus(obj) + case _ => ; + } +} + +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.VehicleEvents ! VehicleServiceMessage(zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, 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 + AnnounceDestroyDeployable(target, None) + zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.Destroy(target.GUID, attribution, attribution, target.Position)) + } + + 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/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/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..23b57441 100644 --- a/common/src/main/scala/net/psforever/objects/TurretDeployable.scala +++ b/common/src/main/scala/net/psforever/objects/TurretDeployable.scala @@ -2,65 +2,59 @@ 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.{DeployableInfo, DeploymentAction, PlanetSideGUID} +import services.{RemoverActor, Service} +import services.avatar.{AvatarAction, AvatarServiceMessage} +import services.local.{LocalAction, LocalServiceMessage} +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 +68,134 @@ 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 { + import scala.concurrent.duration._ + + /** + * 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)) + }) + AnnounceDestroyDeployable(target, None) + zone.AvatarEvents ! AvatarServiceMessage(continentId, AvatarAction.Destroy(target.GUID, attribution, attribution, target.Position)) + } + + 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/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/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/equipment/JammingUnit.scala b/common/src/main/scala/net/psforever/objects/equipment/JammingUnit.scala index a6c6705c..cab7622a 100644 --- a/common/src/main/scala/net/psforever/objects/equipment/JammingUnit.scala +++ b/common/src/main/scala/net/psforever/objects/equipment/JammingUnit.scala @@ -65,7 +65,17 @@ trait JammableBehavior { def JammableObject : PlanetSideServerObject with JammableUnit with ZoneAware - def TryJammerEffectActivate(target : Any, cause : ResolvedProjectile) : Unit + 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) + StartJammeredStatus(obj, dur) + case _ => ; + } + case _ => ; + } def StartJammeredSound(target : Any, dur : Int = 30000) : Unit = { import scala.concurrent.ExecutionContext.Implicits.global @@ -89,7 +99,7 @@ trait JammableBehavior { jammeredStatusTimer.cancel } - def jammableBehavior : Receive = { + val jammableBehavior : Receive = { case JammableUnit.Jammered(cause) => TryJammerEffectActivate(JammableObject, cause) @@ -101,21 +111,9 @@ trait JammableBehavior { } } -trait JammableMountedWeapons extends Actor with JammableBehavior { +trait JammableMountedWeapons extends JammableBehavior { _ : Actor => - def TryJammerEffectActivate(target : Any, cause : ResolvedProjectile) : Unit = target match { - case obj : PlanetSideServerObject with MountedWeapons => - 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) - StartJammeredStatus(obj, dur) - case _ => ; - } - case _ => ; - } - override def StartJammeredSound(target : Any, dur : Int) : Unit = target match { case obj : PlanetSideServerObject with MountedWeapons => obj.Zone.VehicleEvents ! VehicleServiceMessage(obj.Zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, obj.GUID, 54, 1)) 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 5edb2384..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 @@ -6,7 +6,7 @@ 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 @@ -55,7 +55,7 @@ class FacilityTurret(tDef : TurretDefinition) extends Amenity def DamageModel = Definition.asInstanceOf[DamageResistanceModel] - def Definition : TurretDefinition = tDef + def Definition : FacilityTurretDefinition = tDef } object FacilityTurret { @@ -64,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) } @@ -75,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 dbb8e7b1..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 @@ -34,6 +34,7 @@ class FacilityTurretControl(turret : FacilityTurret) extends Actor def FactionObject : FactionAffinity = turret def receive : Receive = checkBehavior + .orElse(jammableBehavior) .orElse(dismountBehavior) .orElse { case Mountable.TryMount(user, seat_num) => 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/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/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/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index f986a8ad..4459a930 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -915,16 +915,8 @@ class WorldSessionActor extends Actor 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 + //spitfires and deployable field turrets and the deployable_shield_generator StartBundlingPackets() DeployableBuildActivity(obj) CommonDestroyConstructionItem(tool, index) @@ -1017,7 +1009,7 @@ class WorldSessionActor extends Actor 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 @@ -1133,18 +1125,6 @@ class WorldSessionActor extends Actor 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 : PlanetSideGameObject, _) => log.warn(s"Vital target ${target.Definition.Name} damage resolution not supported using this method") @@ -1243,22 +1223,19 @@ class WorldSessionActor extends Actor 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? } } @@ -1274,19 +1251,17 @@ class WorldSessionActor extends Actor 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)) } @@ -2933,40 +2908,6 @@ class WorldSessionActor extends Actor msgs } - /** - * 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)) - } - /** * na * @param tplayer na @@ -8499,13 +8440,12 @@ class WorldSessionActor extends Actor 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 - obj.Actor ! Vitality.Damage(func) - case obj : FacilityTurret => - //damage is synchronized on the turret actor - 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 _ => ; @@ -10216,7 +10156,7 @@ class WorldSessionActor extends Actor } } - def TryJammerEffectActivate(target : Any, cause : ResolvedProjectile) : Unit = target match { + override def TryJammerEffectActivate(target : Any, cause : ResolvedProjectile) : Unit = target match { case obj : Player => val radius = cause.projectile.profile.DamageRadius JammingUnit.FindJammerDuration(cause.projectile.profile, obj) match { From 036f00f119fdaeed7e6c099989c55b4880f53ecb Mon Sep 17 00:00:00 2001 From: FateJH Date: Wed, 25 Dec 2019 00:31:35 -0500 Subject: [PATCH 08/14] proper jammering behavior for boomers --- .../scala/services/local/LocalService.scala | 22 ++++++++++++-- .../services/local/LocalServiceMessage.scala | 1 + .../services/local/LocalServiceResponse.scala | 1 + .../src/main/scala/WorldSessionActor.scala | 30 +++++++++++++------ 4 files changed, 43 insertions(+), 11 deletions(-) diff --git a/common/src/main/scala/services/local/LocalService.scala b/common/src/main/scala/services/local/LocalService.scala index 1c5818ed..21b137c8 100644 --- a/common/src/main/scala/services/local/LocalService.scala +++ b/common/src/main/scala/services/local/LocalService.scala @@ -7,6 +7,7 @@ import net.psforever.objects.serverobject.structures.{Amenity, Building} import net.psforever.objects.serverobject.terminals.{CaptureTerminal, Terminal} import net.psforever.objects.zones.Zone import net.psforever.objects._ +import net.psforever.objects.equipment.JammableUnit import net.psforever.packet.game.{PlanetSideGUID, TriggeredEffect, TriggeredEffectLocation} import net.psforever.objects.vital.Vitality import net.psforever.types.Vector3 @@ -64,6 +65,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,8 +287,21 @@ class LocalService(zone : Zone) extends Actor { } //synchronized damage calculations - case Vitality.DamageOn(target : Deployable, func) => - val cause = func(target) + case Vitality.DamageOn(target : Deployable with JammableUnit, damage_func) => + if(target.Health > 0) { + val cause = damage_func(target) + target.Jammed = if(cause.projectile.profile.JammerProjectile) { + val radius = cause.projectile.profile.DamageRadius + Vector3.DistanceSquared(cause.hit_pos, cause.target.Position) < radius * radius + } + else { + false + } + sender ! Vitality.DamageResolution(target, cause) + } + + case Vitality.DamageOn(target : Deployable, damage_func) => + val cause = damage_func(target) sender ! Vitality.DamageResolution(target, cause) case msg => 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/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 4459a930..1fd11214 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -1116,6 +1116,16 @@ class WorldSessionActor extends Actor AnnounceDestroyDeployable(target, Some(0 seconds)) } + case Vitality.DamageResolution(target : BoomerDeployable, _) => + //boomer + if(target.Jammed) { + continent.LocalEvents ! LocalServiceMessage(continent.Id, LocalAction.Detonate(target.GUID, target)) + AnnounceDestroyDeployable(target, Some(500 milliseconds)) + } + else if(target.Health <= 0) { + AnnounceDestroyDeployable(target, Some(0 seconds)) + } + case Vitality.DamageResolution(target : SimpleDeployable, _) => //boomers, mines if(target.Health <= 0) { @@ -1531,6 +1541,14 @@ class WorldSessionActor extends Actor 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) => + log.warn(s"LocalAction.Detonate: ${obj.Definition.Name} not configured to explode correctly") + case LocalResponse.DoorOpens(door_guid) => if(tplayer_guid != guid) { sendResponse(GenericObjectStateMsg(door_guid, 16)) @@ -1556,7 +1574,7 @@ class WorldSessionActor extends Actor } case LocalResponse.EliminateDeployable(obj : ExplosiveDeployable, guid, pos) => - if(obj.Exploded || obj.Health == 0) { + if(obj.Exploded || obj.Jammed || obj.Health == 0) { DeconstructDeployable(obj, guid, pos) } else { @@ -4196,15 +4214,9 @@ class WorldSessionActor extends Actor 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)) + AnnounceDestroyDeployable(boomer, Some(500 milliseconds)) case Some(_) | None => ; } FindEquipmentToDelete(item_guid, trigger) From 555ee35f8c1b8ce2e9272d3bb89c07b6cb93b1a3 Mon Sep 17 00:00:00 2001 From: FateJH Date: Thu, 26 Dec 2019 11:22:32 -0500 Subject: [PATCH 09/14] proper jammering behavior for mines, in general --- .../src/main/scala/WorldSessionActor.scala | 48 +++++++++++++------ 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 1fd11214..e2dd5569 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -1126,6 +1126,22 @@ class WorldSessionActor extends Actor AnnounceDestroyDeployable(target, Some(0 seconds)) } + case Vitality.DamageResolution(target : ExplosiveDeployable, _) if target.Definition eq he_mine => + //he_mine + if(target.Jammed) { + continent.LocalEvents ! LocalServiceMessage(continent.Id, LocalAction.Detonate(target.GUID, target)) + AnnounceDestroyDeployable(target, Some(500 milliseconds)) + } + else if(target.Health <= 0) { + AnnounceDestroyDeployable(target, Some(0 seconds)) + } + + case Vitality.DamageResolution(target : ExplosiveDeployable, _) if target.Definition eq jammer_mine => + //jammer_mine + if(target.Jammed || target.Health <= 0) { + AnnounceDestroyDeployable(target, Some(0 seconds)) + } + case Vitality.DamageResolution(target : SimpleDeployable, _) => //boomers, mines if(target.Health <= 0) { @@ -1444,13 +1460,7 @@ class WorldSessionActor extends Actor 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) { @@ -1546,8 +1556,13 @@ class WorldSessionActor extends Actor sendResponse(PlanetsideAttributeMessage(guid, 29, 1)) sendResponse(ObjectDeleteMessage(guid, 0)) + case LocalResponse.Detonate(guid, obj : ExplosiveDeployable) => + sendResponse(GenericObjectActionMessage(guid, 76)) + sendResponse(PlanetsideAttributeMessage(guid, 29, 1)) + sendResponse(ObjectDeleteMessage(guid, 0)) + case LocalResponse.Detonate(guid, obj) => - log.warn(s"LocalAction.Detonate: ${obj.Definition.Name} not configured to explode correctly") + log.warn(s"LocalResponse.Detonate: ${obj.Definition.Name} not configured to explode correctly") case LocalResponse.DoorOpens(door_guid) => if(tplayer_guid != guid) { @@ -3346,15 +3361,15 @@ class WorldSessionActor extends Actor //player.Position = Vector3(4262.211f ,4067.0625f ,262.35938f) //z6, Akna.tower //player.Orientation = Vector3(0f, 0f, 132.1875f) // player.ExoSuit = ExoSuitType.MAX //TODO strange issue; divide number above by 10 when uncommenting - player.Slot(0).Equipment = Tool(GlobalDefinitions.StandardPistol(player.Faction)) + player.Slot(0).Equipment = Tool(jammer_grenade) //Tool(GlobalDefinitions.StandardPistol(player.Faction)) player.Slot(2).Equipment = Tool(suppressor) player.Slot(4).Equipment = Tool(GlobalDefinitions.StandardMelee(player.Faction)) - player.Slot(6).Equipment = AmmoBox(bullet_9mm) - player.Slot(9).Equipment = AmmoBox(bullet_9mm) - player.Slot(12).Equipment = AmmoBox(bullet_9mm) - player.Slot(33).Equipment = AmmoBox(bullet_9mm_AP) - player.Slot(36).Equipment = AmmoBox(GlobalDefinitions.StandardPistolAmmo(player.Faction)) - player.Slot(39).Equipment = SimpleItem(remote_electronics_kit) + player.Slot(6).Equipment = ConstructionItem(ace) //AmmoBox(bullet_9mm) + player.Slot(9).Equipment = ConstructionItem(ace) //AmmoBox(bullet_9mm) + player.Slot(12).Equipment = ConstructionItem(ace) //AmmoBox(bullet_9mm) + player.Slot(33).Equipment = ConstructionItem(ace) //AmmoBox(bullet_9mm_AP) + player.Slot(36).Equipment = ConstructionItem(ace) //AmmoBox(GlobalDefinitions.StandardPistolAmmo(player.Faction)) + player.Slot(39).Equipment = Tool(jammer_grenade) //SimpleItem(remote_electronics_kit) player.Locker.Inventory += 0 -> SimpleItem(remote_electronics_kit) player.Inventory.Items.foreach { _.obj.Faction = faction } player.Actor = self @@ -3761,6 +3776,9 @@ class WorldSessionActor extends Actor DeactivateImplants() } //implants and stamina management finish + if(is_crouching && !player.Crouching) { + // ... + } player.Position = pos player.Velocity = vel player.Orientation = Vector3(player.Orientation.x, pitch, yaw) From 879be9386338ddb50bd2c7b31d174c33304bfbab Mon Sep 17 00:00:00 2001 From: FateJH Date: Fri, 27 Dec 2019 00:18:46 -0500 Subject: [PATCH 10/14] converted explosive-type and sensor-type deployables into complex deployables that have integrated control actors --- .../psforever/objects/BoomerDeployable.scala | 4 +- .../net/psforever/objects/Deployables.scala | 45 +++++++ .../objects/ExplosiveDeployable.scala | 99 +++++++++++++- .../psforever/objects/GlobalDefinitions.scala | 26 ++-- .../psforever/objects/SensorDeployable.scala | 126 +++++++++++++++++- .../objects/ShieldGeneratorDeployable.scala | 28 +--- .../psforever/objects/TurretDeployable.scala | 26 +--- .../resolution/ResolutionCalculations.scala | 8 +- .../scala/services/local/LocalService.scala | 14 -- .../src/main/scala/WorldSessionActor.scala | 67 +++------- 10 files changed, 300 insertions(+), 143 deletions(-) diff --git a/common/src/main/scala/net/psforever/objects/BoomerDeployable.scala b/common/src/main/scala/net/psforever/objects/BoomerDeployable.scala index 21d66ea2..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.SimpleDeployableDefinition - -class BoomerDeployable(cdef : SimpleDeployableDefinition) 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 b324c666..75482ecb 100644 --- a/common/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala +++ b/common/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala @@ -1,11 +1,22 @@ // Copyright (c) 2017 PSForever package net.psforever.objects -import net.psforever.objects.ce.SimpleDeployable -import net.psforever.objects.definition.SimpleDeployableDefinition +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 : SimpleDeployableDefinition) extends SimpleDeployable(cdef) +import scala.concurrent.duration._ + +class ExplosiveDeployable(cdef : ExplosiveDeployableDefinition) extends ComplexDeployable(cdef) with JammableUnit { private var exploded : Boolean = false @@ -15,4 +26,86 @@ class ExplosiveDeployable(cdef : SimpleDeployableDefinition) extends SimpleDeplo 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 13066bd9..af18d1f7 100644 --- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -870,11 +870,11 @@ object GlobalDefinitions { /* combat engineering deployables */ - val boomer = SimpleDeployableDefinition(DeployedItem.boomer) + val boomer = ExplosiveDeployableDefinition(DeployedItem.boomer) - val he_mine = SimpleDeployableDefinition(DeployedItem.he_mine) + val he_mine = ExplosiveDeployableDefinition(DeployedItem.he_mine) - val jammer_mine = SimpleDeployableDefinition(DeployedItem.jammer_mine) + val jammer_mine = ExplosiveDeployableDefinition(DeployedItem.jammer_mine) val spitfire_turret = TurretDeployableDefinition(DeployedItem.spitfire_turret) @@ -882,9 +882,9 @@ object GlobalDefinitions { val spitfire_aa = TurretDeployableDefinition(DeployedItem.spitfire_aa) - val motionalarmsensor = SimpleDeployableDefinition(DeployedItem.motionalarmsensor) + val motionalarmsensor = SensorDeployableDefinition(DeployedItem.motionalarmsensor) - val sensor_shield = SimpleDeployableDefinition(DeployedItem.sensor_shield) + val sensor_shield = SensorDeployableDefinition(DeployedItem.sensor_shield) val tank_traps = SimpleDeployableDefinition(DeployedItem.tank_traps) @@ -5901,24 +5901,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 @@ -5928,7 +5924,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 @@ -5938,7 +5934,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 @@ -5950,16 +5946,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" diff --git a/common/src/main/scala/net/psforever/objects/SensorDeployable.scala b/common/src/main/scala/net/psforever/objects/SensorDeployable.scala index 1c886cd9..71d3373a 100644 --- a/common/src/main/scala/net/psforever/objects/SensorDeployable.scala +++ b/common/src/main/scala/net/psforever/objects/SensorDeployable.scala @@ -1,11 +1,129 @@ // Copyright (c) 2017 PSForever package net.psforever.objects -import net.psforever.objects.ce.SimpleDeployable -import net.psforever.objects.definition.SimpleDeployableDefinition -import net.psforever.objects.equipment.JammableUnit +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 : SimpleDeployableDefinition) 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 => + 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 => + 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 => + obj.Zone.VehicleEvents ! VehicleServiceMessage(obj.Zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, obj.GUID, 54, 0)) + super.CancelJammeredSound(obj) + case _ => ; + } + + override def CancelJammeredStatus(target : Any) : Unit = target match { + case obj : PlanetSideServerObject => + sensor.Zone.LocalEvents ! LocalServiceMessage(sensor.Zone.Id, LocalAction.TriggerEffectInfo(Service.defaultPlayerGUID, "on", obj.GUID, true, 1000)) + super.CancelJammeredStatus(obj) + case _ => ; + } +} + +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 ffe8dcb1..d304bb93 100644 --- a/common/src/main/scala/net/psforever/objects/ShieldGeneratorDeployable.scala +++ b/common/src/main/scala/net/psforever/objects/ShieldGeneratorDeployable.scala @@ -11,14 +11,11 @@ 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.{DeployableInfo, DeploymentAction, PlanetSideGUID} +import net.psforever.packet.game.PlanetSideGUID import services.avatar.{AvatarAction, AvatarServiceMessage} -import services.local.{LocalAction, LocalServiceMessage} -import services.{RemoverActor, Service} +import services.Service import services.vehicle.{VehicleAction, VehicleServiceMessage} -import scala.concurrent.duration.FiniteDuration - class ShieldGeneratorDeployable(cdef : ShieldGeneratorDefinition) extends ComplexDeployable(cdef) with Hackable with JammableUnit @@ -103,7 +100,7 @@ object ShieldGeneratorControl { else { HandleDestructionAwareness(target, playerGUID, cause) } - zone.VehicleEvents ! VehicleServiceMessage(zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, targetGUID, 0, target.Health)) + zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.PlanetsideAttribute(targetGUID, 0, target.Health)) } /** @@ -116,24 +113,7 @@ object ShieldGeneratorControl { target.Actor ! JammableUnit.ClearJammeredSound() target.Actor ! JammableUnit.ClearJammeredStatus() val zone = target.Zone - AnnounceDestroyDeployable(target, None) + Deployables.AnnounceDestroyDeployable(target, None) zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.Destroy(target.GUID, attribution, attribution, target.Position)) } - - 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/TurretDeployable.scala b/common/src/main/scala/net/psforever/objects/TurretDeployable.scala index 23b57441..46d9601a 100644 --- a/common/src/main/scala/net/psforever/objects/TurretDeployable.scala +++ b/common/src/main/scala/net/psforever/objects/TurretDeployable.scala @@ -14,10 +14,9 @@ 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.{DeployableInfo, DeploymentAction, PlanetSideGUID} -import services.{RemoverActor, Service} +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 TurretDeployable(tdef : TurretDeployableDefinition) extends ComplexDeployable(tdef) @@ -101,8 +100,6 @@ class TurretControl(turret : TurretDeployable) extends Actor } object TurretControl { - import scala.concurrent.duration._ - /** * na * @param target na @@ -178,24 +175,7 @@ object TurretControl { val wep = slot.Equipment.get zone.AvatarEvents ! AvatarServiceMessage(continentId, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, wep.GUID)) }) - AnnounceDestroyDeployable(target, None) + Deployables.AnnounceDestroyDeployable(target, None) zone.AvatarEvents ! AvatarServiceMessage(continentId, AvatarAction.Destroy(target.GUID, attribution, attribution, target.Position)) } - - 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/vital/resolution/ResolutionCalculations.scala b/common/src/main/scala/net/psforever/objects/vital/resolution/ResolutionCalculations.scala index a85f6206..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 @@ -192,9 +192,9 @@ object ResolutionCalculations { def SimpleApplication(damage : Int, data : ResolvedProjectile)(target : Any) : ResolvedProjectile = { target match { - case ce : SimpleDeployable if ce.Health > 0 && damage > 0 => - ce.Health -= damage - ce.History(data) + 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) diff --git a/common/src/main/scala/services/local/LocalService.scala b/common/src/main/scala/services/local/LocalService.scala index 21b137c8..369e653b 100644 --- a/common/src/main/scala/services/local/LocalService.scala +++ b/common/src/main/scala/services/local/LocalService.scala @@ -7,7 +7,6 @@ import net.psforever.objects.serverobject.structures.{Amenity, Building} import net.psforever.objects.serverobject.terminals.{CaptureTerminal, Terminal} import net.psforever.objects.zones.Zone import net.psforever.objects._ -import net.psforever.objects.equipment.JammableUnit import net.psforever.packet.game.{PlanetSideGUID, TriggeredEffect, TriggeredEffectLocation} import net.psforever.objects.vital.Vitality import net.psforever.types.Vector3 @@ -287,19 +286,6 @@ class LocalService(zone : Zone) extends Actor { } //synchronized damage calculations - case Vitality.DamageOn(target : Deployable with JammableUnit, damage_func) => - if(target.Health > 0) { - val cause = damage_func(target) - target.Jammed = if(cause.projectile.profile.JammerProjectile) { - val radius = cause.projectile.profile.DamageRadius - Vector3.DistanceSquared(cause.hit_pos, cause.target.Position) < radius * radius - } - else { - false - } - sender ! Vitality.DamageResolution(target, cause) - } - case Vitality.DamageOn(target : Deployable, damage_func) => val cause = damage_func(target) sender ! Vitality.DamageResolution(target, cause) diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index e2dd5569..5f2a7ab6 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -915,10 +915,11 @@ class WorldSessionActor extends Actor continent.Deployables ! Zone.Deployable.Dismiss(obj) } - case WorldSessionActor.FinalizeDeployable(obj : ComplexDeployable, tool, index) => - //spitfires and deployable field turrets and the 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() @@ -953,11 +954,10 @@ class WorldSessionActor extends Actor 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() @@ -1107,43 +1107,8 @@ class WorldSessionActor extends Actor 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 : BoomerDeployable, _) => - //boomer - if(target.Jammed) { - continent.LocalEvents ! LocalServiceMessage(continent.Id, LocalAction.Detonate(target.GUID, target)) - AnnounceDestroyDeployable(target, Some(500 milliseconds)) - } - else if(target.Health <= 0) { - AnnounceDestroyDeployable(target, Some(0 seconds)) - } - - case Vitality.DamageResolution(target : ExplosiveDeployable, _) if target.Definition eq he_mine => - //he_mine - if(target.Jammed) { - continent.LocalEvents ! LocalServiceMessage(continent.Id, LocalAction.Detonate(target.GUID, target)) - AnnounceDestroyDeployable(target, Some(500 milliseconds)) - } - else if(target.Health <= 0) { - AnnounceDestroyDeployable(target, Some(0 seconds)) - } - - case Vitality.DamageResolution(target : ExplosiveDeployable, _) if target.Definition eq jammer_mine => - //jammer_mine - if(target.Jammed || target.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 @@ -1580,14 +1545,6 @@ class WorldSessionActor extends Actor DeconstructDeployable(obj, guid, pos, obj.Orientation, if(obj.MountPoints.isEmpty) 2 else 1) } - case LocalResponse.EliminateDeployable(obj : ComplexDeployable, guid, pos) => - if(obj.Health == 0) { - DeconstructDeployable(obj, guid, pos) - } - else { - DeconstructDeployable(obj, guid, pos, obj.Orientation, 1) - } - case LocalResponse.EliminateDeployable(obj : ExplosiveDeployable, guid, pos) => if(obj.Exploded || obj.Jammed || obj.Health == 0) { DeconstructDeployable(obj, guid, pos) @@ -1596,6 +1553,14 @@ class WorldSessionActor extends Actor DeconstructDeployable(obj, guid, pos, obj.Orientation, 2) } + case LocalResponse.EliminateDeployable(obj : ComplexDeployable, guid, pos) => + if(obj.Health == 0) { + DeconstructDeployable(obj, guid, pos) + } + else { + DeconstructDeployable(obj, guid, pos, obj.Orientation, 1) + } + case LocalResponse.EliminateDeployable(obj : TelepadDeployable, guid, pos) => //if active, deactivate if(obj.Active) { @@ -4234,7 +4199,7 @@ class WorldSessionActor extends Actor case Some(boomer : BoomerDeployable) => boomer.Exploded = true continent.LocalEvents ! LocalServiceMessage(continent.Id, LocalAction.Detonate(boomer.GUID, boomer)) - AnnounceDestroyDeployable(boomer, Some(500 milliseconds)) + Deployables.AnnounceDestroyDeployable(boomer, Some(500 milliseconds)) case Some(_) | None => ; } FindEquipmentToDelete(item_guid, trigger) From 6c769976759a3f38c25771f2a9b0a10a7a5cc3c7 Mon Sep 17 00:00:00 2001 From: FateJH Date: Mon, 30 Dec 2019 08:50:16 -0500 Subject: [PATCH 11/14] adjusted CommonFieldData to support jammering effect flag; fixed tests; made jammering sound and status contingent on state, but made cancelling sound and status always call up --- .../psforever/objects/SensorDeployable.scala | 28 +- .../objects/ShieldGeneratorDeployable.scala | 16 +- .../scala/net/psforever/objects/Tool.scala | 3 +- .../converter/AvatarConverter.scala | 2 +- .../converter/CharacterSelectConverter.scala | 43 ++- .../converter/FieldTurretConverter.scala | 2 +- .../converter/ShieldGeneratorConverter.scala | 4 +- .../converter/SmallDeployableConverter.scala | 12 +- .../converter/SmallTurretConverter.scala | 2 +- .../definition/converter/ToolConverter.scala | 4 +- .../converter/VehicleConverter.scala | 4 +- .../EffectTarget.scala | 2 +- .../objects/equipment/JammingUnit.scala | 171 +++++++-- .../terminals/ProximityDefinition.scala | 1 + .../objects/vehicles/VehicleControl.scala | 6 +- .../CharacterAppearanceData.scala | 2 +- .../game/objectcreate/CommonFieldData.scala | 25 +- .../OneMannedFieldTurretData.scala | 4 +- .../vehicle/support/VehicleRemover.scala | 2 +- .../AegisShieldGeneratorDataTest.scala | 2 +- .../game/objectcreate/CharacterDataTest.scala | 6 +- .../OneMannedFieldTurretDataTest.scala | 2 +- .../RemoteProjectileDataTest.scala | 4 +- .../objectcreate/SmallTurretDataTest.scala | 4 +- .../game/objectcreate/TRAPDataTest.scala | 2 +- .../DetailedCharacterDataTest.scala | 14 +- .../DetailedConstructionToolDataTest.scala | 8 +- .../MountedVehiclesTest.scala | 4 +- .../NonstandardVehiclesTest.scala | 2 +- .../NormalVehiclesTest.scala | 2 +- .../UtilityVehiclesTest.scala | 4 +- .../src/test/scala/objects/VehicleTest.scala | 336 ++++++++++-------- .../src/main/scala/WorldSessionActor.scala | 148 ++++---- 33 files changed, 518 insertions(+), 353 deletions(-) rename common/src/main/scala/net/psforever/objects/{serverobject/terminals => equipment}/EffectTarget.scala (98%) diff --git a/common/src/main/scala/net/psforever/objects/SensorDeployable.scala b/common/src/main/scala/net/psforever/objects/SensorDeployable.scala index 71d3373a..d06688a2 100644 --- a/common/src/main/scala/net/psforever/objects/SensorDeployable.scala +++ b/common/src/main/scala/net/psforever/objects/SensorDeployable.scala @@ -61,31 +61,35 @@ class SensorDeployableControl(sensor : SensorDeployable) extends Actor } override def StartJammeredSound(target : Any, dur : Int) : Unit = target match { - case obj : PlanetSideServerObject => + 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 => + 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 => - obj.Zone.VehicleEvents ! VehicleServiceMessage(obj.Zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, obj.GUID, 54, 0)) - super.CancelJammeredSound(obj) - 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 => - sensor.Zone.LocalEvents ! LocalServiceMessage(sensor.Zone.Id, LocalAction.TriggerEffectInfo(Service.defaultPlayerGUID, "on", obj.GUID, true, 1000)) - super.CancelJammeredStatus(obj) - case _ => ; + 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) } } diff --git a/common/src/main/scala/net/psforever/objects/ShieldGeneratorDeployable.scala b/common/src/main/scala/net/psforever/objects/ShieldGeneratorDeployable.scala index d304bb93..96a243b3 100644 --- a/common/src/main/scala/net/psforever/objects/ShieldGeneratorDeployable.scala +++ b/common/src/main/scala/net/psforever/objects/ShieldGeneratorDeployable.scala @@ -60,19 +60,21 @@ class ShieldGeneratorControl(gen : ShieldGeneratorDeployable) extends Actor override def StartJammeredSound(target : Any, dur : Int) : Unit = { } override def StartJammeredStatus(target : Any, dur : Int) : Unit = target match { - case obj : PlanetSideServerObject => - obj.Zone.VehicleEvents ! VehicleServiceMessage(obj.Zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, obj.GUID, 54, 1)) + 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 => - obj.Zone.VehicleEvents ! VehicleServiceMessage(obj.Zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, obj.GUID, 54, 0)) - super.CancelJammeredStatus(obj) - case _ => ; + 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) } } 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/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/serverobject/terminals/EffectTarget.scala b/common/src/main/scala/net/psforever/objects/equipment/EffectTarget.scala similarity index 98% rename from common/src/main/scala/net/psforever/objects/serverobject/terminals/EffectTarget.scala rename to common/src/main/scala/net/psforever/objects/equipment/EffectTarget.scala index 38e790df..8871250a 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/EffectTarget.scala +++ b/common/src/main/scala/net/psforever/objects/equipment/EffectTarget.scala @@ -1,5 +1,5 @@ // Copyright (c) 2017 PSForever -package net.psforever.objects.serverobject.terminals +package net.psforever.objects.equipment import net.psforever.objects._ import net.psforever.objects.ce.DeployableCategory diff --git a/common/src/main/scala/net/psforever/objects/equipment/JammingUnit.scala b/common/src/main/scala/net/psforever/objects/equipment/JammingUnit.scala index cab7622a..9d211e4e 100644 --- a/common/src/main/scala/net/psforever/objects/equipment/JammingUnit.scala +++ b/common/src/main/scala/net/psforever/objects/equipment/JammingUnit.scala @@ -5,7 +5,6 @@ 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.serverobject.terminals.TargetValidation import net.psforever.objects.vehicles.MountedWeapons import net.psforever.objects.zones.ZoneAware import net.psforever.types.Vector3 @@ -15,7 +14,12 @@ 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 @@ -27,16 +31,34 @@ trait JammableUnit { } 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 @@ -45,6 +67,15 @@ trait JammingUnit { } 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 } @@ -53,18 +84,45 @@ object JammingUnit { .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 { - _ : Actor => + 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 @@ -77,12 +135,30 @@ trait JammableBehavior { 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 = { - import scala.concurrent.ExecutionContext.Implicits.global - jammeredSoundTimer.cancel - jammeredSoundTimer = context.system.scheduler.scheduleOnce(30 seconds, self, JammableUnit.ClearJammeredSound()) + if(!jammedSound) { + jammedSound = true + import scala.concurrent.ExecutionContext.Implicits.global + jammeredSoundTimer.cancel + jammeredSoundTimer = context.system.scheduler.scheduleOnce(30 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 @@ -90,10 +166,23 @@ trait JammableBehavior { 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 @@ -111,47 +200,71 @@ trait JammableBehavior { } } +/** + * 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 => - obj.Zone.VehicleEvents ! VehicleServiceMessage(obj.Zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, obj.GUID, 54, 1)) - super.StartJammeredSound(obj, dur) - case _ => ; + 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 => - JammableMountedWeapons.JammeredStatus(obj, 1) - super.StartJammeredStatus(obj, 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 => - obj.Zone.VehicleEvents ! VehicleServiceMessage(obj.Zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, obj.GUID, 54, 0)) - super.CancelJammeredSound(obj) - 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 => - JammableMountedWeapons.JammeredStatus(obj, 0) - super.CancelJammeredStatus(obj) - case _ => ; + 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 - zone.VehicleEvents ! VehicleServiceMessage(zoneId, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 27, statusCode)) 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 4278cec0..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 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 85933857..78b6a72e 100644 --- a/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala +++ b/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala @@ -115,7 +115,7 @@ class VehicleControl(vehicle : Vehicle) extends Actor } sender ! FactionAffinity.AssertFactionAffinity(vehicle, faction) - case Vehicle.PrepareForDeletion => + case Vehicle.PrepareForDeletion() => CancelJammeredSound(vehicle) CancelJammeredStatus(vehicle) context.become(Disabled) @@ -126,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 _ => } } 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/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/VehicleTest.scala b/common/src/test/scala/objects/VehicleTest.scala index d1401251..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); 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 5f2a7ab6..86d71313 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -3326,15 +3326,15 @@ class WorldSessionActor extends Actor //player.Position = Vector3(4262.211f ,4067.0625f ,262.35938f) //z6, Akna.tower //player.Orientation = Vector3(0f, 0f, 132.1875f) // player.ExoSuit = ExoSuitType.MAX //TODO strange issue; divide number above by 10 when uncommenting - player.Slot(0).Equipment = Tool(jammer_grenade) //Tool(GlobalDefinitions.StandardPistol(player.Faction)) + player.Slot(0).Equipment = Tool(GlobalDefinitions.StandardPistol(player.Faction)) player.Slot(2).Equipment = Tool(suppressor) player.Slot(4).Equipment = Tool(GlobalDefinitions.StandardMelee(player.Faction)) - player.Slot(6).Equipment = ConstructionItem(ace) //AmmoBox(bullet_9mm) - player.Slot(9).Equipment = ConstructionItem(ace) //AmmoBox(bullet_9mm) - player.Slot(12).Equipment = ConstructionItem(ace) //AmmoBox(bullet_9mm) - player.Slot(33).Equipment = ConstructionItem(ace) //AmmoBox(bullet_9mm_AP) - player.Slot(36).Equipment = ConstructionItem(ace) //AmmoBox(GlobalDefinitions.StandardPistolAmmo(player.Faction)) - player.Slot(39).Equipment = Tool(jammer_grenade) //SimpleItem(remote_electronics_kit) + player.Slot(6).Equipment = AmmoBox(bullet_9mm) + player.Slot(9).Equipment = AmmoBox(bullet_9mm) + player.Slot(12).Equipment = AmmoBox(bullet_9mm) + player.Slot(33).Equipment = AmmoBox(bullet_9mm_AP) + player.Slot(36).Equipment = AmmoBox(GlobalDefinitions.StandardPistolAmmo(player.Faction)) + 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 @@ -3741,9 +3741,6 @@ class WorldSessionActor extends Actor DeactivateImplants() } //implants and stamina management finish - if(is_crouching && !player.Crouching) { - // ... - } player.Position = pos player.Velocity = vel player.Orientation = Vector3(player.Orientation.x, pitch, yaw) @@ -10071,68 +10068,20 @@ class WorldSessionActor extends Actor projectilesToCleanUp(local_index) = false } - def FindJammerTargetsInScope(jammer : Any with JammingUnit) : Seq[PlanetSideGameObject] = { - (jammer match { - case p : ProjectileDefinition if !p.JammerProjectile => None - case p => Some(p) - }) match { - case Some(p : JammingUnit) => - p.JammedEffectDuration - .map { case (a, _) => a } - .collect { - case TargetValidation(EffectTarget.Category.Player, test) if test(player) => Some(player) - case TargetValidation(EffectTarget.Category.Vehicle, test) if { - continent.GUID(player.VehicleSeated) match { - case Some(v) => test(v) - case None => false - } - } => continent.GUID(player.VehicleSeated) - case TargetValidation(EffectTarget.Category.Aircraft, test) if { - continent.GUID(player.VehicleSeated) match { - case Some(v) => test(v) - case None => false - } - } => continent.GUID(player.VehicleSeated) - } collect { - case Some(a) => a - } toSeq - case _ => - Seq.empty[PlanetSideGameObject] - } - } - - def CompileJammerTests(jammer : JammingUnit) : Iterable[EffectTarget.Validation.Value] = { - jammer.JammedEffectDuration map { case (TargetValidation(_, test), _) => test } - } - - def CompileJammerTests(jammer : JammingUnit, target : EffectTarget.Category.Value) : Iterable[EffectTarget.Validation.Value] = { - jammer.JammedEffectDuration collect { case (TargetValidation(filter, test), _) if filter == target => test } - } - - def FindJammerTargetsInScope(jammer : JammingUnit, scope : Seq[PlanetSideGUID], zone : Zone) : Seq[(PlanetSideGUID, PlanetSideGameObject)] = { - val tests = CompileJammerTests(jammer) - (for { - uid <- scope - obj = zone.GUID(uid) - if obj.nonEmpty - } yield (uid, obj.get)) - .collect { - case out @ (_, b) if tests.foldLeft(false)(_ || _(b)) => out - } - } - - def FindJammerTargetsInScope(jammer : JammingUnit, scope : Seq[PlanetSideGameObject]) : Seq[PlanetSideGameObject] = { - val tests = CompileJammerTests(jammer) - scope collect { - case a if tests.foldLeft(false)(_ || _(a)) => a - } - } - + /** + * 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 @@ -10142,6 +10091,10 @@ class WorldSessionActor extends Actor } } + /** + * 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 @@ -10151,34 +10104,39 @@ class WorldSessionActor extends Actor } } - override def TryJammerEffectActivate(target : Any, cause : ResolvedProjectile) : Unit = target match { - case obj : Player => - 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 => - DeactivateImplants() - skipStaminaRegenForTurns = 5 - StartJammeredSound(obj) - StartJammeredStatus(obj, dur) - case _ => ; - } - case _ => ; - } - + /** + * Start the jammered buzzing. + * @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 => + 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, dur) 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 => + case obj : Player if !obj.Jammed => + DeactivateImplants() + skipStaminaRegenForTurns = 10 jammeredEquipment = (jammeredEquipment ++ obj.Holsters() .map { _.Equipment } .collect { - case Some(item) if item.Size != EquipmentSize.Melee => + case Some(item : Tool) if item.Size != EquipmentSize.Melee => + item.Jammed = true sendResponse(PlanetsideAttributeMessage(item.GUID, 27, 1)) item.GUID }).distinct @@ -10186,17 +10144,33 @@ class WorldSessionActor extends Actor 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 => + 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 _ => ; } + /** + * Reset jammered status of previously-affected equipment. + * @see `JammableHevaior.CancelJammeredStatus` + * @param target an object that can be affected by the jammered status + */ override def CancelJammeredStatus(target : Any) : Unit = target match { - case obj : Player => - jammeredEquipment.foreach { id => sendResponse(PlanetsideAttributeMessage(id, 27, 0)) } + case obj : Player if obj.Jammed => + jammeredEquipment.foreach { id => + continent.GUID(id) match { + case Some(item : JammableUnit) => item.Jammed = false + case _ => ; + } + sendResponse(PlanetsideAttributeMessage(id, 27, 0)) + } jammeredEquipment = Nil super.CancelJammeredStatus(obj) case _ => ; From 9c0738e8649ae4c27057ed4ef8389a1be4651063 Mon Sep 17 00:00:00 2001 From: FateJH Date: Mon, 30 Dec 2019 09:35:26 -0500 Subject: [PATCH 12/14] made all jammable objects have the same duration for the jamming effect and the jamming sound, except for Infantry which have a longer sound time --- .../objects/equipment/JammingUnit.scala | 4 +-- .../src/main/scala/WorldSessionActor.scala | 36 +++---------------- 2 files changed, 7 insertions(+), 33 deletions(-) diff --git a/common/src/main/scala/net/psforever/objects/equipment/JammingUnit.scala b/common/src/main/scala/net/psforever/objects/equipment/JammingUnit.scala index 9d211e4e..874474a0 100644 --- a/common/src/main/scala/net/psforever/objects/equipment/JammingUnit.scala +++ b/common/src/main/scala/net/psforever/objects/equipment/JammingUnit.scala @@ -128,7 +128,7 @@ trait JammableBehavior { 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) + StartJammeredSound(obj, dur) StartJammeredStatus(obj, dur) case _ => ; } @@ -148,7 +148,7 @@ trait JammableBehavior { jammedSound = true import scala.concurrent.ExecutionContext.Implicits.global jammeredSoundTimer.cancel - jammeredSoundTimer = context.system.scheduler.scheduleOnce(30 seconds, self, JammableUnit.ClearJammeredSound()) + jammeredSoundTimer = context.system.scheduler.scheduleOnce(dur seconds, self, JammableUnit.ClearJammeredSound()) } } diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 86d71313..a0574387 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -156,7 +156,6 @@ class WorldSessionActor extends Actor var skipStaminaRegenForTurns : Int = 0 lazy val unsignedIntMaxValue : Long = Int.MaxValue.toLong * 2L + 1L var serverTime : Long = 0 - var jammeredEquipment : Seq[PlanetSideGUID] = Nil var amsSpawnPoints : List[SpawnPoint] = Nil var clientKeepAlive : Cancellable = DefaultCancellable.obj @@ -10106,6 +10105,8 @@ class WorldSessionActor extends Actor /** * 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; @@ -10115,7 +10116,7 @@ class WorldSessionActor extends Actor 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, dur) + super.StartJammeredSound(obj, 3000) case _ => ; } @@ -10129,18 +10130,10 @@ class WorldSessionActor extends Actor * @param dur the duration of the timer, in milliseconds */ override def StartJammeredStatus(target : Any, dur : Int) : Unit = target match { - case obj : Player if !obj.Jammed => + case obj : Player => DeactivateImplants() skipStaminaRegenForTurns = 10 - jammeredEquipment = (jammeredEquipment ++ obj.Holsters() - .map { _.Equipment } - .collect { - case Some(item : Tool) if item.Size != EquipmentSize.Melee => - item.Jammed = true - sendResponse(PlanetsideAttributeMessage(item.GUID, 27, 1)) - item.GUID - }).distinct - super.StartJammeredStatus(obj, dur) + super.StartJammeredStatus(target, dur) case _ => ; } @@ -10157,25 +10150,6 @@ class WorldSessionActor extends Actor case _ => ; } - /** - * Reset jammered status of previously-affected equipment. - * @see `JammableHevaior.CancelJammeredStatus` - * @param target an object that can be affected by the jammered status - */ - override def CancelJammeredStatus(target : Any) : Unit = target match { - case obj : Player if obj.Jammed => - jammeredEquipment.foreach { id => - continent.GUID(id) match { - case Some(item : JammableUnit) => item.Jammed = false - case _ => ; - } - sendResponse(PlanetsideAttributeMessage(id, 27, 0)) - } - jammeredEquipment = Nil - super.CancelJammeredStatus(obj) - case _ => ; - } - def failWithError(error : String) = { log.error(error) sendResponse(ConnectionClose()) From 50b0aa16af0501231f7b34aca2827377f63187f6 Mon Sep 17 00:00:00 2001 From: FateJH Date: Tue, 31 Dec 2019 09:22:33 -0500 Subject: [PATCH 13/14] grenade_grenade_projectile_enh was missing an important target validation --- .../src/main/scala/net/psforever/objects/GlobalDefinitions.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index af18d1f7..a77ec812 100644 --- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -2749,6 +2749,7 @@ object GlobalDefinitions { 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" From 9d972351cfe092a334353ad80b1e6cc6055997c8 Mon Sep 17 00:00:00 2001 From: FateJH Date: Wed, 1 Jan 2020 22:41:43 -0500 Subject: [PATCH 14/14] commentary; adjusting the GOAM used to cause mines to explode in accordance with the change in field bitsize --- .../net/psforever/objects/ShieldGeneratorDeployable.scala | 6 +++++- .../psforever/packet/game/GenericObjectActionMessage.scala | 1 + pslogin/src/main/scala/WorldSessionActor.scala | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/common/src/main/scala/net/psforever/objects/ShieldGeneratorDeployable.scala b/common/src/main/scala/net/psforever/objects/ShieldGeneratorDeployable.scala index 96a243b3..8eb16fb5 100644 --- a/common/src/main/scala/net/psforever/objects/ShieldGeneratorDeployable.scala +++ b/common/src/main/scala/net/psforever/objects/ShieldGeneratorDeployable.scala @@ -56,7 +56,11 @@ class ShieldGeneratorControl(gen : ShieldGeneratorDeployable) extends Actor 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 { 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/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index a0574387..f7e5c9d3 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -1521,7 +1521,7 @@ class WorldSessionActor extends Actor sendResponse(ObjectDeleteMessage(guid, 0)) case LocalResponse.Detonate(guid, obj : ExplosiveDeployable) => - sendResponse(GenericObjectActionMessage(guid, 76)) + sendResponse(GenericObjectActionMessage(guid, 19)) sendResponse(PlanetsideAttributeMessage(guid, 29, 1)) sendResponse(ObjectDeleteMessage(guid, 0))