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