Merge pull request #309 from Fate-JH/jammered

Jammering
This commit is contained in:
Mazo 2020-01-02 16:21:00 +00:00 committed by GitHub
commit a5f1638fd6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
72 changed files with 1936 additions and 832 deletions

View file

@ -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

View file

@ -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.<br>
* <br>
* 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))
}
}

View file

@ -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))
}
}

View file

@ -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)

View file

@ -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

View file

@ -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))
}
}

View file

@ -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))
}
}

View file

@ -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

View file

@ -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 */

View file

@ -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)

View file

@ -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))
}
}

View file

@ -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

View file

@ -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 _ => ;
}
}

View file

@ -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)
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 = { }

View file

@ -74,7 +74,7 @@ object AvatarConverter {
alt_model_flag,
false,
None,
false,
obj.Jammed,
None,
v5 = None,
PlanetSideGUID(0)

View file

@ -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)
// }
}
}
}

View file

@ -23,7 +23,7 @@ class FieldTurretConverter extends ObjectCreateConverter[TurretDeployable]() {
alternate = false,
true,
None,
false,
jammered = obj.Jammed,
Some(false),
None,
obj.Owner match {

View file

@ -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)

View file

@ -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"))
}
}

View file

@ -23,7 +23,7 @@ class SmallTurretConverter extends ObjectCreateConverter[TurretDeployable]() {
alternate = false,
false,
None,
false,
jammered = obj.Jammed,
Some(true),
None,
obj.Owner match {

View file

@ -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)

View file

@ -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)

View file

@ -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
}
}
}

View file

@ -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))
}
}
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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()
}

View file

@ -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

View file

@ -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`.<br>
@ -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))
}
}
}

View file

@ -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
}

View file

@ -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

View file

@ -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

View file

@ -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`.<br>
@ -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)))
}
}

View file

@ -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)
}

View file

@ -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 = {

View file

@ -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
}
}

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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?)

View file

@ -115,10 +115,10 @@ import scodec.codecs._
* 45 : Advanced Engineering (= Fortification Engineering + Assault Engineering) Must have Combat Engineering<br>
* `25 - Forget certifications (same order as 24)`<br>
* `26 - Certification reset timer (in seconds)`
* `27 - PA_JAMMED - plays jammed buzzing sound`<br>
* `27 - PA_JAMMED - plays jammed buzzing sound in vicinity of target, jams weapon discharge`<br>
* `28 - PA_IMPLANT_ACTIVE - Plays implant sounds. Valid values seem to be up to 20.`<br>
* `29 - PA_VAPORIZED - Visible ?! That's not the cloaked effect, Maybe for spectator mode ?. Value is 0 to visible, 1 to invisible.`<br>
* `31 - Looking for Squad info (marquee and ui):<br>
* `31 - Looking for Squad info (marquee and ui):`<br>
* ` - 0 is LFS`<br>
* ` - 1 is LFSM (Looking for Squad Members)`<br>
* ` - n is the supplemental squad identifier number; same as "LFS;" for the leader, sets "LFSM" after the first manual flagging`<br>
@ -162,7 +162,7 @@ import scodec.codecs._
* `13 - Trunk permissions (same)`<br>
* `21 - Declare a player the vehicle's owner, by globally unique identifier`<br>
* `22 - Toggles gunner and passenger mount points (1 = hides, 0 = reveals; this also locks their permissions)`<br>
* `54 - Vehicle EMP? Plays sound as if vehicle had been hit by EMP`<br>
* `54 - Plays jammed buzzing sound in vicinity of target`<br>
* `68 - Vehicle shield health`<br>
* `79 - ???`<br>
* `80 - Damage vehicle (unknown value)`<br>

View file

@ -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
)
}

View file

@ -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)

View file

@ -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
)
}

View file

@ -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

View file

@ -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

View file

@ -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")

View file

@ -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

View file

@ -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

View file

@ -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 _ => ;
}
}
}

View file

@ -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) =>

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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))