From 686676f9b9ce281fd570039cbf762005791fbbb1 Mon Sep 17 00:00:00 2001 From: FateJH Date: Tue, 10 Dec 2019 08:17:39 -0500 Subject: [PATCH] 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))