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 // Copyright (c) 2017 PSForever
package net.psforever.objects package net.psforever.objects
import net.psforever.objects.definition.DeployableDefinition class BoomerDeployable(cdef : ExplosiveDeployableDefinition) extends ExplosiveDeployable(cdef) {
class BoomerDeployable(cdef : DeployableDefinition) extends ExplosiveDeployable(cdef) {
private var trigger : Option[BoomerTrigger] = None private var trigger : Option[BoomerTrigger] = None
def Trigger : Option[BoomerTrigger] = trigger def Trigger : Option[BoomerTrigger] = trigger

View file

@ -2,6 +2,12 @@
package net.psforever.objects package net.psforever.objects
import net.psforever.objects.ce.{Deployable, DeployedItem} 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 Deployables {
object Make { object Make {
@ -26,4 +32,43 @@ object Deployables {
DeployedItem.router_telepad_deployable -> { () => new TelepadDeployable(GlobalDefinitions.router_telepad_deployable) } DeployedItem.router_telepad_deployable -> { () => new TelepadDeployable(GlobalDefinitions.router_telepad_deployable) }
).withDefaultValue( { ()=> new ExplosiveDeployable(GlobalDefinitions.boomer) } ) ).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 // Copyright (c) 2017 PSForever
package net.psforever.objects package net.psforever.objects
import net.psforever.objects.ce.SimpleDeployable import akka.actor.{Actor, ActorContext, Props}
import net.psforever.objects.definition.DeployableDefinition 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 private var exploded : Boolean = false
def Exploded : Boolean = exploded def Exploded : Boolean = exploded
@ -13,4 +26,86 @@ class ExplosiveDeployable(cdef : DeployableDefinition) extends SimpleDeployable(
exploded = fuse exploded = fuse
Exploded 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.tube.SpawnTubeDefinition
import net.psforever.objects.serverobject.resourcesilo.ResourceSiloDefinition import net.psforever.objects.serverobject.resourcesilo.ResourceSiloDefinition
import net.psforever.objects.serverobject.structures.SphereOfInfluence 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.vehicles.{DestroyedVehicle, SeatArmorRestriction, UtilityType}
import net.psforever.objects.vital.{DamageType, StandardMaxDamage, StandardResolutions} import net.psforever.objects.vital.{DamageType, StandardMaxDamage, StandardResolutions}
import net.psforever.types.{CertificationType, ExoSuitType, PlanetSideEmpire, Vector3} import net.psforever.types.{CertificationType, ExoSuitType, PlanetSideEmpire, Vector3}
@ -870,11 +870,11 @@ object GlobalDefinitions {
/* /*
combat engineering deployables 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) val spitfire_turret = TurretDeployableDefinition(DeployedItem.spitfire_turret)
@ -882,11 +882,11 @@ object GlobalDefinitions {
val spitfire_aa = TurretDeployableDefinition(DeployedItem.spitfire_aa) 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) val portable_manned_turret = TurretDeployableDefinition(DeployedItem.portable_manned_turret)
@ -898,10 +898,10 @@ object GlobalDefinitions {
val deployable_shield_generator = new ShieldGeneratorDefinition 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 //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() init_deployables()
/* /*
@ -989,7 +989,7 @@ object GlobalDefinitions {
val ground_rearm_terminal = new OrderTerminalDefinition(384) 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 = new PainboxDefinition(622)
val painbox_continuous = new PainboxDefinition(623) val painbox_continuous = new PainboxDefinition(623)
@ -2686,6 +2686,14 @@ object GlobalDefinitions {
jammer_cartridge_projectile.ProjectileDamageType = DamageType.Splash jammer_cartridge_projectile.ProjectileDamageType = DamageType.Splash
jammer_cartridge_projectile.InitialVelocity = 30 jammer_cartridge_projectile.InitialVelocity = 30
jammer_cartridge_projectile.Lifespan = 15f 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) ProjectileDefinition.CalculateDerivedFields(jammer_cartridge_projectile)
jammer_cartridge_projectile_b.Name = "jammer_cartridge_projectile_b" 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.ProjectileDamageType = DamageType.Splash
jammer_cartridge_projectile_b.InitialVelocity = 30 jammer_cartridge_projectile_b.InitialVelocity = 30
jammer_cartridge_projectile_b.Lifespan = 2f 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) ProjectileDefinition.CalculateDerivedFields(jammer_cartridge_projectile_b)
jammer_grenade_projectile.Name = "jammer_grenade_projectile" jammer_grenade_projectile.Name = "jammer_grenade_projectile"
@ -2707,6 +2723,14 @@ object GlobalDefinitions {
jammer_grenade_projectile.ProjectileDamageType = DamageType.Splash jammer_grenade_projectile.ProjectileDamageType = DamageType.Splash
jammer_grenade_projectile.InitialVelocity = 30 jammer_grenade_projectile.InitialVelocity = 30
jammer_grenade_projectile.Lifespan = 15f 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) ProjectileDefinition.CalculateDerivedFields(jammer_grenade_projectile)
jammer_grenade_projectile_enh.Name = "jammer_grenade_projectile_enh" 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.ProjectileDamageType = DamageType.Splash
jammer_grenade_projectile_enh.InitialVelocity = 30 jammer_grenade_projectile_enh.InitialVelocity = 30
jammer_grenade_projectile_enh.Lifespan = 3f 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) ProjectileDefinition.CalculateDerivedFields(jammer_grenade_projectile_enh)
katana_projectile.Name = "katana_projectile" katana_projectile.Name = "katana_projectile"
@ -5870,24 +5902,20 @@ object GlobalDefinitions {
boomer.MaxHealth = 100 boomer.MaxHealth = 100
boomer.DeployCategory = DeployableCategory.Boomers boomer.DeployCategory = DeployableCategory.Boomers
boomer.DeployTime = Duration.create(1000, "ms") boomer.DeployTime = Duration.create(1000, "ms")
boomer.Model = StandardResolutions.SimpleDeployables
he_mine.Name = "he_mine" he_mine.Name = "he_mine"
he_mine.Descriptor = "Mines" he_mine.Descriptor = "Mines"
he_mine.MaxHealth = 100 he_mine.MaxHealth = 100
he_mine.DeployCategory = DeployableCategory.Mines
he_mine.DeployTime = Duration.create(1000, "ms") he_mine.DeployTime = Duration.create(1000, "ms")
he_mine.Model = StandardResolutions.SimpleDeployables
jammer_mine.Name = "jammer_mine" jammer_mine.Name = "jammer_mine"
jammer_mine.Descriptor = "JammerMines" jammer_mine.Descriptor = "JammerMines"
jammer_mine.MaxHealth = 100 jammer_mine.MaxHealth = 100
jammer_mine.DeployCategory = DeployableCategory.Mines
jammer_mine.DeployTime = Duration.create(1000, "ms") jammer_mine.DeployTime = Duration.create(1000, "ms")
jammer_mine.Model = StandardResolutions.SimpleDeployables jammer_mine.DetonateOnJamming = false
spitfire_turret.Name = "spitfire_turret" spitfire_turret.Name = "spitfire_turret"
spitfire_turret.Descriptor= "Spitfires" spitfire_turret.Descriptor = "Spitfires"
spitfire_turret.MaxHealth = 100 spitfire_turret.MaxHealth = 100
spitfire_turret.Weapons += 1 -> new mutable.HashMap() spitfire_turret.Weapons += 1 -> new mutable.HashMap()
spitfire_turret.Weapons(1) += TurretUpgrade.None -> spitfire_weapon spitfire_turret.Weapons(1) += TurretUpgrade.None -> spitfire_weapon
@ -5897,7 +5925,7 @@ object GlobalDefinitions {
spitfire_turret.Model = StandardResolutions.ComplexDeployables spitfire_turret.Model = StandardResolutions.ComplexDeployables
spitfire_cloaked.Name = "spitfire_cloaked" spitfire_cloaked.Name = "spitfire_cloaked"
spitfire_cloaked.Descriptor= "CloakingSpitfires" spitfire_cloaked.Descriptor = "CloakingSpitfires"
spitfire_cloaked.MaxHealth = 100 spitfire_cloaked.MaxHealth = 100
spitfire_cloaked.Weapons += 1 -> new mutable.HashMap() spitfire_cloaked.Weapons += 1 -> new mutable.HashMap()
spitfire_cloaked.Weapons(1) += TurretUpgrade.None -> spitfire_weapon spitfire_cloaked.Weapons(1) += TurretUpgrade.None -> spitfire_weapon
@ -5907,7 +5935,7 @@ object GlobalDefinitions {
spitfire_cloaked.Model = StandardResolutions.ComplexDeployables spitfire_cloaked.Model = StandardResolutions.ComplexDeployables
spitfire_aa.Name = "spitfire_aa" spitfire_aa.Name = "spitfire_aa"
spitfire_aa.Descriptor= "FlakSpitfires" spitfire_aa.Descriptor = "FlakSpitfires"
spitfire_aa.MaxHealth = 100 spitfire_aa.MaxHealth = 100
spitfire_aa.Weapons += 1 -> new mutable.HashMap() spitfire_aa.Weapons += 1 -> new mutable.HashMap()
spitfire_aa.Weapons(1) += TurretUpgrade.None -> spitfire_aa_weapon spitfire_aa.Weapons(1) += TurretUpgrade.None -> spitfire_aa_weapon
@ -5919,16 +5947,12 @@ object GlobalDefinitions {
motionalarmsensor.Name = "motionalarmsensor" motionalarmsensor.Name = "motionalarmsensor"
motionalarmsensor.Descriptor = "MotionSensors" motionalarmsensor.Descriptor = "MotionSensors"
motionalarmsensor.MaxHealth = 100 motionalarmsensor.MaxHealth = 100
motionalarmsensor.DeployCategory = DeployableCategory.Sensors
motionalarmsensor.DeployTime = Duration.create(1000, "ms") motionalarmsensor.DeployTime = Duration.create(1000, "ms")
motionalarmsensor.Model = StandardResolutions.SimpleDeployables
sensor_shield.Name = "sensor_shield" sensor_shield.Name = "sensor_shield"
sensor_shield.Descriptor = "SensorShields" sensor_shield.Descriptor = "SensorShields"
sensor_shield.MaxHealth = 100 sensor_shield.MaxHealth = 100
sensor_shield.DeployCategory = DeployableCategory.Sensors
sensor_shield.DeployTime = Duration.create(5000, "ms") sensor_shield.DeployTime = Duration.create(5000, "ms")
sensor_shield.Model = StandardResolutions.SimpleDeployables
tank_traps.Name = "tank_traps" tank_traps.Name = "tank_traps"
tank_traps.Descriptor = "TankTraps" tank_traps.Descriptor = "TankTraps"
@ -6096,62 +6120,62 @@ object GlobalDefinitions {
teleportpad_terminal.Name = "teleportpad_terminal" teleportpad_terminal.Name = "teleportpad_terminal"
teleportpad_terminal.Tab += 0 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.routerTerminal) 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.Name = "medical_terminal"
medical_terminal.Interval = 500 medical_terminal.Interval = 500
medical_terminal.HealAmount = 5 medical_terminal.HealAmount = 5
medical_terminal.ArmorAmount = 10 medical_terminal.ArmorAmount = 10
medical_terminal.UseRadius = 0.75f 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.Name = "portable_med_terminal"
portable_med_terminal.Interval = 500 portable_med_terminal.Interval = 500
portable_med_terminal.HealAmount = 5 portable_med_terminal.HealAmount = 5
portable_med_terminal.ArmorAmount = 10 portable_med_terminal.ArmorAmount = 10
portable_med_terminal.UseRadius = 3 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.Name = "pad_landing_frame"
pad_landing_frame.Interval = 1000 pad_landing_frame.Interval = 1000
pad_landing_frame.HealAmount = 60 pad_landing_frame.HealAmount = 60
pad_landing_frame.UseRadius = 20 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.Name = "pad_landing_tower_frame"
pad_landing_tower_frame.Interval = 1000 pad_landing_tower_frame.Interval = 1000
pad_landing_tower_frame.HealAmount = 60 pad_landing_tower_frame.HealAmount = 60
pad_landing_tower_frame.UseRadius = 20 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.Name = "repair_silo"
repair_silo.Interval = 1000 repair_silo.Interval = 1000
repair_silo.HealAmount = 60 repair_silo.HealAmount = 60
repair_silo.UseRadius = 20 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.Name = "lodestar_repair_terminal"
lodestar_repair_terminal.Interval = 1000 lodestar_repair_terminal.Interval = 1000
lodestar_repair_terminal.HealAmount = 60 lodestar_repair_terminal.HealAmount = 60
lodestar_repair_terminal.UseRadius = 20 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.Name = "multivehicle_rearm_terminal"
multivehicle_rearm_terminal.Tab += 3 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.vehicleAmmunition) 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.avatar.LoadoutManager
import net.psforever.objects.definition.{AvatarDefinition, ExoSuitDefinition, SpecialExoSuitDefinition} 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.inventory.{Container, GridInventory, InventoryItem}
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.vital.resistance.ResistanceProfile import net.psforever.objects.vital.resistance.ResistanceProfile
import net.psforever.objects.vital.{DamageResistanceModel, Vitality} import net.psforever.objects.vital.{DamageResistanceModel, Vitality}
@ -16,11 +17,12 @@ import net.psforever.types._
import scala.annotation.tailrec import scala.annotation.tailrec
import scala.util.{Success, Try} import scala.util.{Success, Try}
class Player(private val core : Avatar) extends PlanetSideGameObject class Player(private val core : Avatar) extends PlanetSideServerObject
with FactionAffinity with FactionAffinity
with Vitality with Vitality
with ResistanceProfile with ResistanceProfile
with Container with Container
with JammableUnit
with ZoneAware { with ZoneAware {
private var alive : Boolean = false private var alive : Boolean = false
private var backpack : Boolean = false private var backpack : Boolean = false

View file

@ -1,9 +1,133 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects package net.psforever.objects
import net.psforever.objects.ce.SimpleDeployable import akka.actor.{Actor, ActorContext, Props}
import net.psforever.objects.definition.DeployableDefinition 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.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 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 // Copyright (c) 2017 PSForever
package net.psforever.objects package net.psforever.objects
import net.psforever.objects.ce.{ComplexDeployable, DeployableCategory} import akka.actor.{Actor, ActorContext, Props}
import net.psforever.objects.definition.DeployableDefinition 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.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.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) class ShieldGeneratorDeployable(cdef : ShieldGeneratorDefinition) extends ComplexDeployable(cdef)
with Hackable with Hackable
with JammableUnit
class ShieldGeneratorDefinition extends DeployableDefinition(240) { class ShieldGeneratorDefinition extends ComplexDeployableDefinition(240) {
Packet = new ShieldGeneratorConverter Packet = new ShieldGeneratorConverter
DeployCategory = DeployableCategory.ShieldGenerators 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 package net.psforever.objects
import net.psforever.objects.ce.{SimpleDeployable, TelepadLike} 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 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 * @param toolDef the `ObjectDefinition` that constructs this item and maintains some of its immutable fields
*/ */
class Tool(private val toolDef : ToolDefinition) extends Equipment 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 */ /** index of the current fire mode on the `ToolDefinition`'s list of fire modes */
private var fireModeIndex : Int = toolDef.DefaultFireModeIndex private var fireModeIndex : Int = toolDef.DefaultFireModeIndex
/** current ammunition slot being used by this fire mode */ /** current ammunition slot being used by this fire mode */

View file

@ -2,6 +2,6 @@
package net.psforever.objects package net.psforever.objects
import net.psforever.objects.ce.SimpleDeployable 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 package net.psforever.objects
import akka.actor.{Actor, ActorContext, Props} import akka.actor.{Actor, ActorContext, Props}
import net.psforever.objects.ce.{Deployable, DeployedItem} import net.psforever.objects.ballistics.ResolvedProjectile
import net.psforever.objects.definition.{BaseDeployableDefinition, DeployableDefinition} 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.definition.converter.SmallTurretConverter
import net.psforever.objects.equipment.{JammableMountedWeapons, JammableUnit}
import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
import net.psforever.objects.serverobject.hackable.Hackable import net.psforever.objects.serverobject.hackable.Hackable
import net.psforever.objects.serverobject.mount.MountableBehavior import net.psforever.objects.serverobject.mount.MountableBehavior
import net.psforever.objects.serverobject.turret.{TurretDefinition, WeaponTurret} 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 class TurretDeployable(tdef : TurretDeployableDefinition) extends ComplexDeployable(tdef)
with Deployable
with WeaponTurret with WeaponTurret
with JammableUnit
with Hackable { with Hackable {
private var shields : Int = 0
WeaponTurret.LoadDefinition(this) //calls the equivalent of Health = Definition.MaxHealth 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 def MountPoints : Map[Int, Int] = Definition.MountPoints.toMap
//override to clarify inheritance conflict //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 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 override def Definition = tdef
} }
class TurretDeployableDefinition(private val objectId : Int) extends TurretDefinition(objectId) class TurretDeployableDefinition(private val objectId : Int) extends ComplexDeployableDefinition(objectId)
with BaseDeployableDefinition { with TurretDefinition {
private val item = DeployedItem(objectId) //let throw NoSuchElementException
Name = "turret_deployable" Name = "turret_deployable"
Packet = new SmallTurretConverter Packet = new SmallTurretConverter
Damage = StandardVehicleDamage
def Item : DeployedItem.Value = item Resistance = StandardVehicleResistance
Model = StandardResolutions.FacilityTurrets
//override to clarify inheritance conflict //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 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) = { override def Initialize(obj : PlanetSideServerObject with Deployable, context : ActorContext) = {
obj.Actor = context.actorOf(Props(classOf[TurretControl], obj), s"${obj.Definition.Name}_${obj.GUID.guid}") obj.Actor = context.actorOf(Props(classOf[TurretControl], obj), s"${obj.Definition.Name}_${obj.GUID.guid}")
} }
override def Uninitialize(obj : PlanetSideServerObject with Deployable, context : ActorContext) = { 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 class TurretControl(turret : TurretDeployable) extends Actor
with FactionAffinityBehavior.Check with FactionAffinityBehavior.Check
with JammableMountedWeapons //note: jammable status is reported as vehicle events, not local events
with MountableBehavior.Mount with MountableBehavior.Mount
with MountableBehavior.Dismount { with MountableBehavior.Dismount {
def MountableObject = turret //do not add type! def MountableObject = turret //do not add type!
def JammableObject = turret
def FactionObject : FactionAffinity = turret def FactionObject : FactionAffinity = turret
def receive : Receive = checkBehavior def receive : Receive = checkBehavior
.orElse(jammableBehavior)
.orElse(dismountBehavior) .orElse(dismountBehavior)
.orElse(turretMountBehavior) .orElse(turretMountBehavior)
.orElse { .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 _ => ; 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 akka.actor.ActorRef
import net.psforever.objects.definition.VehicleDefinition 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.inventory.{Container, GridInventory, InventoryTile}
import net.psforever.objects.serverobject.mount.Mountable import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.PlanetSideServerObject
@ -73,6 +73,7 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends AmenityOwner
with Vitality with Vitality
with OwnableByPlayer with OwnableByPlayer
with StandardResistanceProfile with StandardResistanceProfile
with JammableUnit
with Container { with Container {
private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.TR private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.TR
private var health : Int = 1 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.ce.{ComplexDeployable, SimpleDeployable}
import net.psforever.objects.definition.ObjectDefinition 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.entity.WorldEntity
import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.vital.resistance.ResistanceProfile import net.psforever.objects.vital.resistance.ResistanceProfile
@ -34,7 +34,6 @@ object SourceEntry {
case obj : Player => PlayerSource(obj) case obj : Player => PlayerSource(obj)
case obj : Vehicle => VehicleSource(obj) case obj : Vehicle => VehicleSource(obj)
case obj : ComplexDeployable => ComplexDeployableSource(obj) case obj : ComplexDeployable => ComplexDeployableSource(obj)
case obj : TurretDeployable => ComplexDeployableSource(obj)
case obj : SimpleDeployable => DeployableSource(obj) case obj : SimpleDeployable => DeployableSource(obj)
case _ => ObjectSource(target) case _ => ObjectSource(target)
} }

View file

@ -1,10 +1,10 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.ce package net.psforever.objects.ce
import net.psforever.objects.definition.DeployableDefinition import net.psforever.objects.definition.ComplexDeployableDefinition
import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.PlanetSideServerObject
abstract class ComplexDeployable(cdef : DeployableDefinition) extends PlanetSideServerObject abstract class ComplexDeployable(cdef : ComplexDeployableDefinition) extends PlanetSideServerObject
with Deployable { with Deployable {
Health = Definition.MaxHealth Health = Definition.MaxHealth

View file

@ -5,12 +5,14 @@ import net.psforever.objects._
import net.psforever.objects.definition.{BaseDeployableDefinition, ObjectDefinition} import net.psforever.objects.definition.{BaseDeployableDefinition, ObjectDefinition}
import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.vital.{DamageResistanceModel, Vitality} 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 import net.psforever.types.PlanetSideEmpire
trait Deployable extends FactionAffinity trait Deployable extends FactionAffinity
with Vitality with Vitality
with OwnableByPlayer { with OwnableByPlayer
with ZoneAware {
this : PlanetSideGameObject => this : PlanetSideGameObject =>
private var health : Int = 1 private var health : Int = 1
private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL

View file

@ -2,9 +2,9 @@
package net.psforever.objects.ce package net.psforever.objects.ce
import net.psforever.objects.PlanetSideGameObject 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 { with Deployable {
Health = Definition.MaxHealth Health = Definition.MaxHealth

View file

@ -2,6 +2,7 @@
package net.psforever.objects.definition package net.psforever.objects.definition
import net.psforever.objects.ballistics.Projectiles import net.psforever.objects.ballistics.Projectiles
import net.psforever.objects.equipment.JammingUnit
import net.psforever.objects.vital.{DamageType, StandardDamageProfile} 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 * @param objectId the object's identifier number
*/ */
class ProjectileDefinition(objectId : Int) extends ObjectDefinition(objectId) class ProjectileDefinition(objectId : Int) extends ObjectDefinition(objectId)
with JammingUnit
with StandardDamageProfile { with StandardDamageProfile {
private val projectileType : Projectiles.Value = Projectiles(objectId) //let throw NoSuchElementException private val projectileType : Projectiles.Value = Projectiles(objectId) //let throw NoSuchElementException
private var acceleration : Int = 0 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 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 remoteClientData : (Int, Int) = (0, 0) //artificial values; for ObjectCreateMessage packet (oicw_little_buddy is undefined)
private var autoLock : Boolean = false private var autoLock : Boolean = false
private var additionalEffect : Boolean = false
private var jammerProjectile : Boolean = false
//derived calculations //derived calculations
private var distanceMax : Float = 0f private var distanceMax : Float = 0f
private var distanceFromAcceleration : Float = 0f private var distanceFromAcceleration : Float = 0f
@ -133,6 +137,20 @@ class ProjectileDefinition(objectId : Int) extends ObjectDefinition(objectId)
AutoLock 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 DistanceMax : Float = distanceMax //accessor only
def DistanceFromAcceleration : Float = distanceFromAcceleration //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.definition.converter.SmallDeployableConverter
import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.vital.resistance.ResistanceProfileMutators 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._ import scala.concurrent.duration._
@ -53,7 +53,7 @@ trait BaseDeployableDefinition extends DamageResistanceModel
def Uninitialize(obj : PlanetSideServerObject with Deployable, context : ActorContext) : Unit = { } 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 { with BaseDeployableDefinition {
private val item = DeployedItem(objectId) //let throw NoSuchElementException private val item = DeployedItem(objectId) //let throw NoSuchElementException
Packet = new SmallDeployableConverter Packet = new SmallDeployableConverter
@ -61,9 +61,16 @@ class DeployableDefinition(private val objectId : Int) extends ObjectDefinition(
def Item : DeployedItem.Value = item def Item : DeployedItem.Value = item
} }
object DeployableDefinition { abstract class ComplexDeployableDefinition(private val objectId : Int) extends ObjectDefinition(objectId)
def apply(item : DeployedItem.Value) : DeployableDefinition = with BaseDeployableDefinition {
new DeployableDefinition(item.id) 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 = { } def SimpleUninitialize(obj : PlanetSideGameObject, context : ActorContext) : Unit = { }

View file

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

View file

@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.definition.converter 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.objects.equipment.{Equipment, EquipmentSlot}
import net.psforever.packet.game.PlanetSideGUID import net.psforever.packet.game.PlanetSideGUID
import net.psforever.packet.game.objectcreate._ import net.psforever.packet.game.objectcreate._
@ -136,17 +136,38 @@ class CharacterSelectConverter extends AvatarConverter {
} }
else { else {
val slot : EquipmentSlot = iter.next val slot : EquipmentSlot = iter.next
if(slot.Equipment.isDefined) { slot.Equipment match {
val equip : Equipment = slot.Equipment.get case Some(equip : Tool) =>
recursiveMakeHolsters( val jammed = equip.Jammed
iter, equip.Jammed = false
list :+ AvatarConverter.BuildDetailedEquipment(index, equip), val slot = AvatarConverter.BuildDetailedEquipment(index, equip)
index + 1 equip.Jammed = jammed
) recursiveMakeHolsters(
} iter,
else { list :+ slot,
recursiveMakeHolsters(iter, list, index + 1) 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, alternate = false,
true, true,
None, None,
false, jammered = obj.Jammed,
Some(false), Some(false),
None, None,
obj.Owner match { obj.Owner match {

View file

@ -21,7 +21,7 @@ class ShieldGeneratorConverter extends ObjectCreateConverter[ShieldGeneratorDepl
alternate = false, alternate = false,
v1 = false, v1 = false,
v2 = None, v2 = None,
v3 = false, jammered = obj.Jammed,
None, None,
None, None,
obj.Owner match { obj.Owner match {
@ -45,7 +45,7 @@ class ShieldGeneratorConverter extends ObjectCreateConverter[ShieldGeneratorDepl
alternate = true, alternate = true,
v1 = false, v1 = false,
v2 = None, v2 = None,
v3 = false, jammered = obj.Jammed,
None, None,
None, None,
PlanetSideGUID(0) PlanetSideGUID(0)

View file

@ -3,6 +3,7 @@ package net.psforever.objects.definition.converter
import net.psforever.objects.ce.Deployable import net.psforever.objects.ce.Deployable
import net.psforever.objects.PlanetSideGameObject import net.psforever.objects.PlanetSideGameObject
import net.psforever.objects.equipment.JammableUnit
import net.psforever.packet.game.PlanetSideGUID import net.psforever.packet.game.PlanetSideGUID
import net.psforever.packet.game.objectcreate._ import net.psforever.packet.game.objectcreate._
@ -15,11 +16,14 @@ class SmallDeployableConverter extends ObjectCreateConverter[PlanetSideGameObjec
PlacementData(obj.Position, obj.Orientation), PlacementData(obj.Position, obj.Orientation),
CommonFieldData( CommonFieldData(
obj.Faction, obj.Faction,
false, bops = false,
false, alternate = false,
false, false,
None, None,
false, jammered = obj match {
case o : JammableUnit => o.Jammed
case _ => false
},
Some(false), Some(false),
None, None,
obj.Owner match { obj.Owner match {

View file

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

View file

@ -21,7 +21,7 @@ class ToolConverter extends ObjectCreateConverter[Tool]() {
alternate = false, alternate = false,
true, true,
None, None,
false, obj.Jammed,
None, None,
None, None,
PlanetSideGUID(0) PlanetSideGUID(0)
@ -45,7 +45,7 @@ class ToolConverter extends ObjectCreateConverter[Tool]() {
alternate = false, alternate = false,
true, true,
None, None,
false, obj.Jammed,
None, None,
None, None,
PlanetSideGUID(0) PlanetSideGUID(0)

View file

@ -25,7 +25,7 @@ class VehicleConverter extends ObjectCreateConverter[Vehicle]() {
alternate = false, alternate = false,
v1 = false, v1 = false,
v2 = None, v2 = None,
v3 = false, jammered = obj.Jammed,
v4 = Some(false), v4 = Some(false),
v5 = None, v5 = None,
obj.Owner match { obj.Owner match {
@ -56,7 +56,7 @@ class VehicleConverter extends ObjectCreateConverter[Vehicle]() {
alternate = true, alternate = true,
v1 = false, v1 = false,
v2 = None, v2 = None,
v3 = false, jammered = obj.Jammed,
v4 = Some(false), v4 = Some(false),
v5 = None, v5 = None,
guid = PlanetSideGUID(0) 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.PlanetSideGameObject
import net.psforever.objects.definition.ObjectDefinition import net.psforever.objects.definition.ObjectDefinition
import net.psforever.objects.equipment.EffectTarget
import scala.collection.mutable import scala.collection.mutable
@ -17,7 +18,7 @@ trait ProximityDefinition {
this : ObjectDefinition => this : ObjectDefinition =>
private var useRadius : Float = 0f //TODO belongs on a wider range of object definitions 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 def UseRadius : Float = useRadius
@ -26,18 +27,14 @@ trait ProximityDefinition {
UseRadius 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] = { def Validations : Seq[PlanetSideGameObject=>Boolean] = {
targetValidation.headOption match { targetValidation.headOption match {
case Some(_) => case Some(_) =>
targetValidation.values.toSeq targetValidation.values.toSeq
case None => 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 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() private case class TerminalAction()
} }

View file

@ -1,12 +1,14 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.turret package net.psforever.objects.serverobject.turret
import net.psforever.objects.equipment.JammableUnit
import net.psforever.objects.serverobject.structures.Amenity import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.types.Vector3 import net.psforever.types.Vector3
import net.psforever.objects.vital.{DamageResistanceModel, StandardResistanceProfile, Vitality} import net.psforever.objects.vital.{DamageResistanceModel, StandardResistanceProfile, Vitality}
class FacilityTurret(tDef : TurretDefinition) extends Amenity class FacilityTurret(tDef : FacilityTurretDefinition) extends Amenity
with WeaponTurret with WeaponTurret
with JammableUnit
with Vitality with Vitality
with StandardResistanceProfile { with StandardResistanceProfile {
/** some turrets can be updated; they all start without updates */ /** 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 DamageModel = Definition.asInstanceOf[DamageResistanceModel]
def Definition : TurretDefinition = tDef def Definition : FacilityTurretDefinition = tDef
} }
object FacilityTurret { object FacilityTurret {
@ -62,7 +64,7 @@ object FacilityTurret {
* @param tDef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields * @param tDef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields
* @return a `FacilityTurret` object * @return a `FacilityTurret` object
*/ */
def apply(tDef : TurretDefinition) : FacilityTurret = { def apply(tDef : FacilityTurretDefinition) : FacilityTurret = {
new FacilityTurret(tDef) new FacilityTurret(tDef)
} }
@ -73,14 +75,14 @@ object FacilityTurret {
* @param context a context to allow the object to properly set up `ActorSystem` functionality * @param context a context to allow the object to properly set up `ActorSystem` functionality
* @return the `MannedTurret` object * @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 import akka.actor.Props
val obj = FacilityTurret(tdef) val obj = FacilityTurret(tdef)
obj.Actor = context.actorOf(Props(classOf[FacilityTurretControl], obj), s"${tdef.Name}_$id") obj.Actor = context.actorOf(Props(classOf[FacilityTurretControl], obj), s"${tdef.Name}_$id")
obj 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 import akka.actor.Props
val obj = FacilityTurret(tdef) val obj = FacilityTurret(tdef)
obj.Position = pos obj.Position = pos

View file

@ -2,9 +2,17 @@
package net.psforever.objects.serverobject.turret package net.psforever.objects.serverobject.turret
import akka.actor.Actor 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.mount.{Mountable, MountableBehavior}
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
import net.psforever.objects.vital.Vitality 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> * 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 class FacilityTurretControl(turret : FacilityTurret) extends Actor
with FactionAffinityBehavior.Check with FactionAffinityBehavior.Check
with MountableBehavior.Dismount { with MountableBehavior.Dismount
def MountableObject = turret //do not add type! with JammableMountedWeapons {
def MountableObject = turret
def JammableObject = turret
def FactionObject : FactionAffinity = turret def FactionObject : FactionAffinity = turret
def receive : Receive = checkBehavior def receive : Receive = checkBehavior
.orElse(jammableBehavior)
.orElse(dismountBehavior) .orElse(dismountBehavior)
.orElse { .orElse {
case Mountable.TryMount(user, seat_num) => case Mountable.TryMount(user, seat_num) =>
@ -42,15 +55,100 @@ class FacilityTurretControl(turret : FacilityTurret) extends Actor
case Vitality.Damage(damage_func) => case Vitality.Damage(damage_func) =>
if(turret.Health > 0) { if(turret.Health > 0) {
val originalHealth = turret.Health val originalHealth = turret.Health
damage_func(turret) val cause = damage_func(turret)
val health = turret.Health val health = turret.Health
val damageToHealth = originalHealth - health val damageToHealth = originalHealth - health
val name = turret.Actor.toString FacilityTurretControl.HandleDamageResolution(turret, cause, damageToHealth)
val slashPoint = name.lastIndexOf("/") if(damageToHealth > 0) {
org.log4s.getLogger("DamageResolution").info(s"${name.substring(slashPoint+1, name.length-1)}: BEFORE=$originalHealth, AFTER=$health, CHANGE=$damageToHealth") val name = turret.Actor.toString
sender ! Vitality.DamageResolution(turret) val slashPoint = name.lastIndexOf("/")
org.log4s.getLogger("DamageResolution").info(s"${name.substring(slashPoint + 1, name.length - 1)}: BEFORE=$originalHealth, AFTER=$health, CHANGE=$damageToHealth")
}
} }
case _ => ; 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.definition.{ObjectDefinition, ToolDefinition}
import net.psforever.objects.vehicles.Turrets 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 net.psforever.objects.vital.resistance.ResistanceProfileMutators
import scala.collection.mutable import scala.collection.mutable
/** /**
* The definition for any `MannedTurret`. * The definition for any `MannedTurret`.
* @param objectId the object's identifier number
*/ */
class TurretDefinition(private val objectId : Int) extends ObjectDefinition(objectId) trait TurretDefinition extends ResistanceProfileMutators
with ResistanceProfileMutators
with DamageResistanceModel { with DamageResistanceModel {
Turrets(objectId) //let throw NoSuchElementException odef : ObjectDefinition =>
Turrets(odef.ObjectId) //let throw NoSuchElementException
private var maxHealth : Int = 100 private var maxHealth : Int = 100
/* key - entry point index, value - seat index */ /* 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 /** creates internal ammunition reserves that can not become depleted
* see `MannedTurret.TurretAmmoBox` for details */ * see `MannedTurret.TurretAmmoBox` for details */
private var hasReserveAmmunition : Boolean = false private var hasReserveAmmunition : Boolean = false
Damage = StandardVehicleDamage
Resistance = StandardVehicleResistance
Model = StandardResolutions.FacilityTurrets
def MaxHealth : Int = maxHealth def MaxHealth : Int = maxHealth

View file

@ -2,7 +2,7 @@
package net.psforever.objects.vehicles package net.psforever.objects.vehicles
import akka.actor.ActorContext import akka.actor.ActorContext
import net.psforever.objects.definition.DeployableDefinition import net.psforever.objects.definition.SimpleDeployableDefinition
import net.psforever.objects._ import net.psforever.objects._
import net.psforever.objects.ce.TelepadLike import net.psforever.objects.ce.TelepadLike
import net.psforever.objects.serverobject.structures.Amenity 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. * and allows it to serve as one of the terminal points of a Router-telepad teleportation system.
* @param ddef na * @param ddef na
*/ */
class InternalTelepad(ddef : DeployableDefinition) extends Amenity class InternalTelepad(ddef : SimpleDeployableDefinition) extends Amenity
with TelepadLike { with TelepadLike {
/** a link to the telepad that serves as the other endpoint of this teleportation system */ /** a link to the telepad that serves as the other endpoint of this teleportation system */
private var activeTelepad : Option[PlanetSideGUID] = None private var activeTelepad : Option[PlanetSideGUID] = None

View file

@ -2,13 +2,20 @@
package net.psforever.objects.vehicles package net.psforever.objects.vehicles
import akka.actor.{Actor, ActorRef} import akka.actor.{Actor, ActorRef}
import net.psforever.objects.Vehicle import net.psforever.objects.{GlobalDefinitions, Vehicle}
import net.psforever.objects.ballistics.VehicleSource 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.mount.{Mountable, MountableBehavior}
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} 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.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> * 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 FactionAffinityBehavior.Check
with DeploymentBehavior with DeploymentBehavior
with MountableBehavior.Mount with MountableBehavior.Mount
with MountableBehavior.Dismount { with MountableBehavior.Dismount
with JammableMountedWeapons {
//make control actors belonging to utilities when making control actor belonging to vehicle //make control actors belonging to utilities when making control actor belonging to vehicle
vehicle.Utilities.foreach({case (_, util) => util.Setup }) vehicle.Utilities.foreach({case (_, util) => util.Setup })
def MountableObject = vehicle def MountableObject = vehicle
def JammableObject = vehicle
def FactionObject = vehicle def FactionObject = vehicle
def DeploymentObject = vehicle def DeploymentObject = vehicle
@ -44,6 +55,7 @@ class VehicleControl(vehicle : Vehicle) extends Actor
def Enabled : Receive = checkBehavior def Enabled : Receive = checkBehavior
.orElse(deployBehavior) .orElse(deployBehavior)
.orElse(dismountBehavior) .orElse(dismountBehavior)
.orElse(jammableBehavior)
.orElse { .orElse {
case Mountable.TryMount(user, seat_num) => case Mountable.TryMount(user, seat_num) =>
val exosuit = user.ExoSuit val exosuit = user.ExoSuit
@ -73,15 +85,17 @@ class VehicleControl(vehicle : Vehicle) extends Actor
if(vehicle.Health > 0) { if(vehicle.Health > 0) {
val originalHealth = vehicle.Health val originalHealth = vehicle.Health
val originalShields = vehicle.Shields val originalShields = vehicle.Shields
damage_func(vehicle) val cause = damage_func(vehicle)
val health = vehicle.Health val health = vehicle.Health
val shields = vehicle.Shields val shields = vehicle.Shields
val damageToHealth = originalHealth - health val damageToHealth = originalHealth - health
val damageToShields = originalShields - shields val damageToShields = originalShields - shields
val name = vehicle.Actor.toString VehicleControl.HandleDamageResolution(vehicle, cause, damageToHealth + damageToShields)
val slashPoint = name.lastIndexOf("/") if(damageToHealth > 0 || damageToShields > 0) {
org.log4s.getLogger("DamageResolution").info(s"${name.substring(slashPoint+1, name.length-1)}: BEFORE=$originalHealth/$originalShields, AFTER=$health/$shields, CHANGE=$damageToHealth/$damageToShields") val name = vehicle.Actor.toString
sender ! Vitality.DamageResolution(vehicle) 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) => case Vehicle.ChargeShields(amount) =>
@ -91,7 +105,7 @@ class VehicleControl(vehicle : Vehicle) extends Actor
!vehicle.History.exists(VehicleControl.LastShieldChargeOrDamage(now))) { !vehicle.History.exists(VehicleControl.LastShieldChargeOrDamage(now))) {
vehicle.History(VehicleShieldCharge(VehicleSource(vehicle), amount)) vehicle.History(VehicleShieldCharge(VehicleSource(vehicle), amount))
vehicle.Shields = vehicle.Shields + 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) => case FactionAffinity.ConvertFactionAffinity(faction) =>
@ -101,7 +115,9 @@ class VehicleControl(vehicle : Vehicle) extends Actor
} }
sender ! FactionAffinity.AssertFactionAffinity(vehicle, faction) sender ! FactionAffinity.AssertFactionAffinity(vehicle, faction)
case Vehicle.PrepareForDeletion => case Vehicle.PrepareForDeletion() =>
CancelJammeredSound(vehicle)
CancelJammeredStatus(vehicle)
context.become(Disabled) context.become(Disabled)
case _ => ; case _ => ;
@ -110,10 +126,10 @@ class VehicleControl(vehicle : Vehicle) extends Actor
def Disabled : Receive = checkBehavior def Disabled : Receive = checkBehavior
.orElse(dismountBehavior) .orElse(dismountBehavior)
.orElse { .orElse {
case Vehicle.Reactivate => case Vehicle.Reactivate() =>
context.become(Enabled) context.become(Enabled)
case _ => ; case _ =>
} }
} }
@ -136,4 +152,116 @@ object VehicleControl {
case _ => false 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.ballistics.{PlayerSource, ResolvedProjectile, SourceEntry, VehicleSource}
import net.psforever.objects.definition.KitDefinition import net.psforever.objects.definition.KitDefinition
import net.psforever.objects.serverobject.terminals.TerminalDefinition import net.psforever.objects.serverobject.terminals.TerminalDefinition
import net.psforever.objects.vital.resolution.ResolutionCalculations
import net.psforever.types.{ExoSuitType, ImplantType} import net.psforever.types.{ExoSuitType, ImplantType}
abstract class VitalsActivity(target : SourceEntry) { abstract class VitalsActivity(target : SourceEntry) {
@ -102,13 +103,13 @@ object Vitality {
* upon a given vital object. * upon a given vital object.
* @param func a function literal * @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. * Report that a vitals object must be updated due to damage.
* @param obj the vital object * @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; * @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 * 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) applyFunc : (A, ResolvedProjectile)=>ResolutionCalculations.Output)
extends ResolutionCalculations { extends ResolutionCalculations {
def Calculate(damages : ProjectileCalculations.Form, resistances : ProjectileCalculations.Form, data : ResolvedProjectile) : ResolutionCalculations.Output = { 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.{Player, TurretDeployable, Vehicle}
import net.psforever.objects.ballistics.{PlayerSource, ResolvedProjectile} 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.serverobject.turret.FacilityTurret
import net.psforever.objects.vital.projectile.ProjectileCalculations import net.psforever.objects.vital.projectile.ProjectileCalculations
@ -22,7 +22,7 @@ trait ResolutionCalculations {
} }
object ResolutionCalculations { object ResolutionCalculations {
type Output = (Any)=>Unit type Output = Any=>ResolvedProjectile
type Form = (ProjectileCalculations.Form, ProjectileCalculations.Form, ResolvedProjectile)=>Output type Form = (ProjectileCalculations.Form, ProjectileCalculations.Form, ResolvedProjectile)=>Output
def NoDamage(data : ResolvedProjectile)(a : Int, b : Int) : Int = 0 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) = { def SubtractWithRemainder(current : Int, damage : Int) : (Int, Int) = {
val a = Math.max(0, current - damage) val a = Math.max(0, current - damage)
@ -123,41 +123,44 @@ object ResolutionCalculations {
* @param data the historical `ResolvedProjectile` information * @param data the historical `ResolvedProjectile` information
* @param target the `Player` object to be affected by these damage values (at some point) * @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 { def InfantryApplication(damageValues : (Int, Int), data : ResolvedProjectile)(target : Any) : ResolvedProjectile = {
case player : Player => target match {
var (a, b) = damageValues case player : Player =>
var result = (0, 0) var (a, b) = damageValues
//TODO Personal Shield implant test should go here and modify the values a and b var result = (0, 0)
if(player.isAlive && !(a == 0 && b == 0)) { //TODO Personal Shield implant test should go here and modify the values a and b
player.History(data) if(player.isAlive && !(a == 0 && b == 0)) {
if(player.Capacitor.toInt > 0 && player.isShielded) { player.History(data)
// Subtract armour damage from capacitor if(player.Capacitor.toInt > 0 && player.isShielded) {
result = SubtractWithRemainder(player.Capacitor.toInt, b) // Subtract armour damage from capacitor
player.Capacitor = result._1 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 b = result._2
// Then follow up with health damage if any capacitor is left // Then bleed through to health if armour ran out
result = SubtractWithRemainder(player.Capacitor.toInt, a) result = SubtractWithRemainder(player.Health, b)
player.Capacitor = result._1 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 a = result._2
} }
case _ =>
// Subtract any remaining armour damage from armour }
result = SubtractWithRemainder(player.Armor, b) data
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 _ =>
} }
/** /**
@ -167,9 +170,9 @@ object ResolutionCalculations {
* @param data the historical `ResolvedProjectile` information * @param data the historical `ResolvedProjectile` information
* @param target the `Vehicle` object to be affected by these damage values (at some point) * @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 { def VehicleApplication(damage : Int, data : ResolvedProjectile)(target : Any) : ResolvedProjectile = {
case vehicle : Vehicle => target match {
if(vehicle.Health > 0) { case vehicle : Vehicle if vehicle.Health > 0 && damage > 0 =>
vehicle.History(data) vehicle.History(data)
val shields = vehicle.Shields val shields = vehicle.Shields
if(shields > damage) { if(shields > damage) {
@ -182,57 +185,58 @@ object ResolutionCalculations {
else { else {
vehicle.Health = vehicle.Health - damage vehicle.Health = vehicle.Health - damage
} }
} case _ => ;
case _ => ; }
data
} }
def SimpleApplication(damage : Int, data : ResolvedProjectile)(target : Any) : Unit = target match { def SimpleApplication(damage : Int, data : ResolvedProjectile)(target : Any) : ResolvedProjectile = {
case ce : SimpleDeployable => target match {
if(ce.Health > 0) { case obj : Deployable if obj.Health > 0 =>
ce.Health -= damage obj.Health -= damage
ce.History(data) obj.History(data)
} case turret : FacilityTurret if turret.Health > 0 =>
case turret : FacilityTurret =>
if(turret.Health > 0) {
turret.Health -= damage turret.Health -= damage
turret.History(data) turret.History(data)
} case _ => ;
case _ => }
data
} }
def ComplexDeployableApplication(damage : Int, data : ResolvedProjectile)(target : Any) : Unit = target match { def ComplexDeployableApplication(damage : Int, data : ResolvedProjectile)(target : Any) : ResolvedProjectile = {
case ce : ComplexDeployable => target match {
if(ce.Shields > 0) { case ce : ComplexDeployable if ce.Health > 0 && damage > 0 =>
if(damage > ce.Shields) { ce.History(data)
ce.Health -= (damage - ce.Shields) if(ce.Shields > 0) {
ce.Shields = 0 if(damage > ce.Shields) {
ce.Health -= (damage - ce.Shields)
ce.Shields = 0
}
else {
ce.Shields -= damage
}
} }
else { else {
ce.Shields -= damage ce.Health -= damage
} }
ce.History(data)
}
else if(ce.Health > 0) {
ce.Health -= damage
ce.History(data)
}
case ce : TurretDeployable => case ce : TurretDeployable if ce.Health > 0 && damage > 0 =>
if(ce.Shields > 0) { ce.History(data)
if(damage > ce.Shields) { if(ce.Shields > 0) {
ce.Health -= (damage - ce.Shields) if(damage > ce.Shields) {
ce.Shields = 0 ce.Health -= (damage - ce.Shields)
ce.Shields = 0
}
else {
ce.Shields -= damage
}
} }
else { 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 _ => case _ =>
obj.Definition.Initialize(obj, context) obj.Definition.Initialize(obj, context)
} }
obj.Zone = zone
sender ! Zone.Deployable.DeployableIsBuilt(obj, tool) sender ! Zone.Deployable.DeployableIsBuilt(obj, tool)
} }

View file

@ -1,7 +1,9 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.zones 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 net.psforever.objects.{Avatar, Player}
import scala.annotation.tailrec import scala.annotation.tailrec
@ -34,16 +36,22 @@ class ZonePopulationActor(zone : Zone, playerMap : TrieMap[Avatar, Option[Player
case Zone.Population.Spawn(avatar, player) => case Zone.Population.Spawn(avatar, player) =>
PopulationSpawn(avatar, player, playerMap) match { PopulationSpawn(avatar, player, playerMap) match {
case Some(tplayer) => case Some(tplayer) =>
tplayer.Zone = zone
if(tplayer ne player) { if(tplayer ne player) {
sender ! Zone.Population.PlayerAlreadySpawned(zone, player) sender ! Zone.Population.PlayerAlreadySpawned(zone, player)
} }
else {
player.Actor = context.actorOf(Props(classOf[PlayerControl], player), s"${player.Name}_${player.GUID.guid}")
}
case None => case None =>
sender ! Zone.Population.PlayerCanNotSpawn(zone, player) sender ! Zone.Population.PlayerCanNotSpawn(zone, player)
} }
case Zone.Population.Release(avatar) => case Zone.Population.Release(avatar) =>
PopulationRelease(avatar, playerMap) match { PopulationRelease(avatar, playerMap) match {
case Some(_) => ; case Some(tplayer) =>
tplayer.Actor ! akka.actor.PoisonPill
tplayer.Actor = ActorRef.noSender
case None => case None =>
sender ! Zone.Population.PlayerHasLeft(zone, 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!" * 15 - Displays "This facility's generator is under attack!"
* 16 - Displays "Generator has Overloaded! Evacuate Generator Room Immediately!" * 16 - Displays "Generator has Overloaded! Evacuate Generator Room Immediately!"
* 17 - Displays "This facility's generator is back on line" * 17 - Displays "This facility's generator is back on line"
* 19 - Cause mines to explode
* 20 - Hit flinch? (orig, 82->80) * 20 - Hit flinch? (orig, 82->80)
* 21 - Reset build cooldown from using an ACE * 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?) * 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> * 45 : Advanced Engineering (= Fortification Engineering + Assault Engineering) Must have Combat Engineering<br>
* `25 - Forget certifications (same order as 24)`<br> * `25 - Forget certifications (same order as 24)`<br>
* `26 - Certification reset timer (in seconds)` * `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> * `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> * `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> * ` - 0 is LFS`<br>
* ` - 1 is LFSM (Looking for Squad Members)`<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> * ` - 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> * `13 - Trunk permissions (same)`<br>
* `21 - Declare a player the vehicle's owner, by globally unique identifier`<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> * `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> * `68 - Vehicle shield health`<br>
* `79 - ???`<br> * `79 - ???`<br>
* `80 - Damage vehicle (unknown value)`<br> * `80 - Damage vehicle (unknown value)`<br>

View file

@ -327,7 +327,7 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
} }
else if(data.faction == PlanetSideEmpire.NEUTRAL) { else if(data.faction == PlanetSideEmpire.NEUTRAL) {
Attempt.successful( 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 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 scodec.codecs._
import shapeless.{::, HNil} 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 override def bitsize : Long = 17L
} }
object CommonFieldDataExtra { object CommonFieldDataExtra {
implicit val codec : Codec[CommonFieldDataExtra] = ( def codec(unk1 : Boolean) : Codec[CommonFieldDataExtra] = (
("unk1" | uint16L) :: ("unk1" | conditional(unk1, uint16L)) :: //not sure what flags this field
("unk2" | bool) ("unk2" | bool)
).as[CommonFieldDataExtra] ).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) * when set on a tool, that tool will be rendered nonfunctional instead (though it can still be equipped)
* @param v1 na * @param v1 na
* @param v2 na; * @param v2 na;
* optional data whose reading is triggered in unknown conditions; * optional data whose reading is triggered in unknown conditions
* flag a weapon as "jammered" * @param jammered flag as "jammered;"
* @param v3 na; * set on most game objects, that object will produce the characteristic jammered buzz
* for weapons, works like `alternate`
* @param v4 na; * @param v4 na;
* a field used by a second encoding format for this data * a field used by a second encoding format for this data
* @param v5 na; * @param v5 na;
@ -43,7 +42,7 @@ final case class CommonFieldData(faction : PlanetSideEmpire.Value,
alternate : Boolean, alternate : Boolean,
v1 : Boolean, v1 : Boolean,
v2 : Option[CommonFieldDataExtra], v2 : Option[CommonFieldDataExtra],
v3 : Boolean, jammered : Boolean,
v4 : Option[Boolean], v4 : Option[Boolean],
v5 : Option[Int], v5 : Option[Int],
guid : PlanetSideGUID guid : PlanetSideGUID
@ -64,7 +63,7 @@ final case class CommonFieldData(faction : PlanetSideEmpire.Value,
23L + extraSize + v4Size + v5Size 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] { object CommonFieldData extends Marshallable[CommonFieldData] {
@ -100,8 +99,8 @@ object CommonFieldData extends Marshallable[CommonFieldData] {
("bops" | bool) :: ("bops" | bool) ::
("alternate" | bool) :: ("alternate" | bool) ::
("v1" | bool) :: //the purpose of this bit changes depending on the previous bit ("v1" | bool) :: //the purpose of this bit changes depending on the previous bit
conditional(extra, "v2" | CommonFieldDataExtra.codec) :: conditional(extra, "v2" | CommonFieldDataExtra.codec(unk1 = false)) ::
("v3" | bool) :: ("jammered" | bool) ::
optional(bool, "v5" | uint16L) :: optional(bool, "v5" | uint16L) ::
("guid" | PlanetSideGUID.codec) ("guid" | PlanetSideGUID.codec)
).xmap[CommonFieldData] ( ).xmap[CommonFieldData] (
@ -122,8 +121,8 @@ object CommonFieldData extends Marshallable[CommonFieldData] {
("bops" | bool) :: ("bops" | bool) ::
("alternate" | bool) :: ("alternate" | bool) ::
("v1" | bool) :: //though the code path differs depending on the previous bit, this one gets read one way or another ("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) :: conditional(extra, "v2" | CommonFieldDataExtra.codec(unk1 = false)) ::
("v3" | bool) :: ("jammered" | bool) ::
optional(bool, "v5" | uint16L) :: optional(bool, "v5" | uint16L) ::
("v4" | bool) :: ("v4" | bool) ::
("guid" | PlanetSideGUID.codec) ("guid" | PlanetSideGUID.codec)

View file

@ -71,7 +71,7 @@ object OneMannedFieldTurretData extends Marshallable[OneMannedFieldTurretData] {
OneMannedFieldTurretData( OneMannedFieldTurretData(
CommonFieldDataWithPlacement( CommonFieldDataWithPlacement(
deploy.pos, 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, newHealth,
newInternals newInternals
@ -92,7 +92,7 @@ object OneMannedFieldTurretData extends Marshallable[OneMannedFieldTurretData] {
Attempt.successful( Attempt.successful(
CommonFieldDataWithPlacement( CommonFieldDataWithPlacement(
pos, 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 ) :: 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.ce.Deployable
import net.psforever.objects.equipment.Equipment import net.psforever.objects.equipment.Equipment
import net.psforever.objects.inventory.Container import net.psforever.objects.inventory.Container
import net.psforever.objects.vital.resolution.ResolutionCalculations
import net.psforever.objects.zones.Zone import net.psforever.objects.zones.Zone
import net.psforever.packet.PlanetSideGamePacket import net.psforever.packet.PlanetSideGamePacket
import net.psforever.packet.game.PlanetSideGUID 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 ChangeFireState_Stop(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID) extends Action
final case class ConcealPlayer(player_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 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 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 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 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.Player
import net.psforever.objects.ballistics.{Projectile, SourceEntry} import net.psforever.objects.ballistics.{Projectile, SourceEntry}
import net.psforever.objects.equipment.Equipment import net.psforever.objects.equipment.Equipment
import net.psforever.objects.vital.resolution.ResolutionCalculations
import net.psforever.packet.PlanetSideGamePacket import net.psforever.packet.PlanetSideGamePacket
import net.psforever.packet.game.objectcreate.ConstructorData import net.psforever.packet.game.objectcreate.ConstructorData
import net.psforever.packet.game.{ObjectCreateMessage, PlanetSideGUID} 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 ChangeFireState_Stop(weapon_guid : PlanetSideGUID) extends Response
final case class ConcealPlayer() extends Response final case class ConcealPlayer() extends Response
final case class EnvironmentalDamage(target : PlanetSideGUID, amount : Int) 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 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 DestroyDisplay(killer : SourceEntry, victim : SourceEntry, method : Int, unk : Int) extends Response
final case class DropItem(pkt : ObjectCreateMessage) extends Response final case class DropItem(pkt : ObjectCreateMessage) extends Response

View file

@ -64,6 +64,10 @@ class LocalService(zone : Zone) extends Actor {
LocalEvents.publish( LocalEvents.publish(
LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.DeployableMapIcon(behavior, deployInfo)) 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) => case LocalAction.DoorOpens(player_guid, _, door) =>
doorCloser ! DoorCloseActor.DoorIsOpen(door, zone) doorCloser ! DoorCloseActor.DoorIsOpen(door, zone)
LocalEvents.publish( LocalEvents.publish(
@ -282,9 +286,9 @@ class LocalService(zone : Zone) extends Actor {
} }
//synchronized damage calculations //synchronized damage calculations
case Vitality.DamageOn(target : Deployable, func) => case Vitality.DamageOn(target : Deployable, damage_func) =>
func(target) val cause = damage_func(target)
sender ! Vitality.DamageResolution(target) sender ! Vitality.DamageResolution(target, cause)
case msg => case msg =>
log.warn(s"Unhandled message $msg from $sender") 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 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 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 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 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 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 AlertDestroyDeployable(obj : PlanetSideGameObject with Deployable) extends Response
final case class DeployableMapIcon(action : DeploymentAction.Value, deployInfo : DeployableInfo) 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 DoorOpens(door_guid : PlanetSideGUID) extends Response
final case class DoorCloses(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 final case class EliminateDeployable(obj : PlanetSideGameObject with Deployable, object_guid : PlanetSideGUID, pos : Vector3) extends Response

View file

@ -2,16 +2,18 @@
package services.vehicle package services.vehicle
import akka.actor.{Actor, ActorRef, Props} 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.ballistics.VehicleSource
import net.psforever.objects.serverobject.pad.VehicleSpawnPad import net.psforever.objects.serverobject.pad.VehicleSpawnPad
import net.psforever.objects.serverobject.terminals.{MedicalTerminalDefinition, ProximityUnit} 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.vital.RepairFromTerm
import net.psforever.objects.zones.Zone import net.psforever.objects.zones.Zone
import net.psforever.packet.game.{ObjectCreateMessage, PlanetSideGUID} import net.psforever.packet.game.{ObjectCreateMessage, PlanetSideGUID}
import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent
import services.vehicle.support.{TurretUpgrader, VehicleRemover} import services.vehicle.support.{TurretUpgrader, VehicleRemover}
import net.psforever.types.DriveState import net.psforever.types.DriveState
import services.local.LocalServiceMessage
import services.{GenericEventBus, RemoverActor, Service} import services.{GenericEventBus, RemoverActor, Service}
import scala.concurrent.duration._ import scala.concurrent.duration._
@ -253,3 +255,36 @@ class VehicleService(zone : Zone) extends Actor {
.map(util => util().asInstanceOf[SpawnTube]) .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 vehicle = entry.obj.asInstanceOf[Vehicle]
val vehicleGUID = vehicle.GUID val vehicleGUID = vehicle.GUID
val zoneId = entry.zone.Id val zoneId = entry.zone.Id
vehicle.Actor ! Vehicle.PrepareForDeletion vehicle.Actor ! Vehicle.PrepareForDeletion()
//escape being someone else's cargo //escape being someone else's cargo
(vehicle.MountedIn match { (vehicle.MountedIn match {
case Some(carrierGUID) => case Some(carrierGUID) =>

View file

@ -29,7 +29,7 @@ class AegisShieldGeneratorDataTest extends Specification {
basic.data.alternate mustEqual false basic.data.alternate mustEqual false
basic.data.v1 mustEqual true basic.data.v1 mustEqual true
basic.data.v2.isDefined mustEqual false basic.data.v2.isDefined mustEqual false
basic.data.v3 mustEqual false basic.data.jammered mustEqual false
basic.data.v5.isDefined mustEqual false basic.data.v5.isDefined mustEqual false
basic.data.guid mustEqual PlanetSideGUID(2366) basic.data.guid mustEqual PlanetSideGUID(2366)

View file

@ -43,7 +43,7 @@ class CharacterDataTest extends Specification {
a.data.bops mustEqual false a.data.bops mustEqual false
a.data.v1 mustEqual false a.data.v1 mustEqual false
a.data.v2.isEmpty 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.v4.isEmpty mustEqual true
a.data.v5.isEmpty mustEqual true a.data.v5.isEmpty mustEqual true
a.exosuit mustEqual ExoSuitType.Reinforced a.exosuit mustEqual ExoSuitType.Reinforced
@ -162,7 +162,7 @@ class CharacterDataTest extends Specification {
a.data.bops mustEqual false a.data.bops mustEqual false
a.data.v1 mustEqual false a.data.v1 mustEqual false
a.data.v2.isEmpty 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.v4.isEmpty mustEqual true
a.data.v5.isEmpty mustEqual true a.data.v5.isEmpty mustEqual true
a.exosuit mustEqual ExoSuitType.Reinforced a.exosuit mustEqual ExoSuitType.Reinforced
@ -231,7 +231,7 @@ class CharacterDataTest extends Specification {
a.data.bops mustEqual false a.data.bops mustEqual false
a.data.v1 mustEqual false a.data.v1 mustEqual false
a.data.v2.isEmpty 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.v4.isEmpty mustEqual true
a.data.v5.isEmpty mustEqual true a.data.v5.isEmpty mustEqual true
a.exosuit mustEqual ExoSuitType.MAX a.exosuit mustEqual ExoSuitType.MAX

View file

@ -28,7 +28,7 @@ class OneMannedFieldTurretDataTest extends Specification {
deploy.alternate mustEqual false deploy.alternate mustEqual false
deploy.v1 mustEqual true deploy.v1 mustEqual true
deploy.v2.isEmpty mustEqual true deploy.v2.isEmpty mustEqual true
deploy.v3 mustEqual false deploy.jammered mustEqual false
deploy.v4.contains(false) mustEqual true deploy.v4.contains(false) mustEqual true
deploy.v5.isEmpty mustEqual true deploy.v5.isEmpty mustEqual true
deploy.guid mustEqual PlanetSideGUID(2502) deploy.guid mustEqual PlanetSideGUID(2502)

View file

@ -29,7 +29,7 @@ class RemoteProjectileDataTest extends Specification {
deploy.alternate mustEqual false deploy.alternate mustEqual false
deploy.v1 mustEqual true deploy.v1 mustEqual true
deploy.v2.isEmpty mustEqual true deploy.v2.isEmpty mustEqual true
deploy.v3 mustEqual false deploy.jammered mustEqual false
deploy.v4.isEmpty mustEqual true deploy.v4.isEmpty mustEqual true
deploy.v5.isEmpty mustEqual true deploy.v5.isEmpty mustEqual true
deploy.guid mustEqual PlanetSideGUID(0) deploy.guid mustEqual PlanetSideGUID(0)
@ -63,7 +63,7 @@ class RemoteProjectileDataTest extends Specification {
deploy.alternate mustEqual false deploy.alternate mustEqual false
deploy.v1 mustEqual true deploy.v1 mustEqual true
deploy.v2.isEmpty mustEqual true deploy.v2.isEmpty mustEqual true
deploy.v3 mustEqual false deploy.jammered mustEqual false
deploy.v4.isEmpty mustEqual true deploy.v4.isEmpty mustEqual true
deploy.v5.isEmpty mustEqual true deploy.v5.isEmpty mustEqual true
deploy.guid mustEqual PlanetSideGUID(0) deploy.guid mustEqual PlanetSideGUID(0)

View file

@ -30,7 +30,7 @@ class SmallTurretDataTest extends Specification {
deploy.alternate mustEqual true deploy.alternate mustEqual true
deploy.v1 mustEqual true deploy.v1 mustEqual true
deploy.v2.isEmpty mustEqual true deploy.v2.isEmpty mustEqual true
deploy.v3 mustEqual false deploy.jammered mustEqual false
deploy.v4.contains(false) mustEqual true deploy.v4.contains(false) mustEqual true
deploy.v5.isEmpty mustEqual true deploy.v5.isEmpty mustEqual true
deploy.guid mustEqual PlanetSideGUID(7742) deploy.guid mustEqual PlanetSideGUID(7742)
@ -61,7 +61,7 @@ class SmallTurretDataTest extends Specification {
deploy.alternate mustEqual false deploy.alternate mustEqual false
deploy.v1 mustEqual true deploy.v1 mustEqual true
deploy.v2.isEmpty mustEqual true deploy.v2.isEmpty mustEqual true
deploy.v3 mustEqual false deploy.jammered mustEqual false
deploy.v4.contains(true) mustEqual true deploy.v4.contains(true) mustEqual true
deploy.v5.isEmpty mustEqual true deploy.v5.isEmpty mustEqual true
deploy.guid mustEqual PlanetSideGUID(8208) deploy.guid mustEqual PlanetSideGUID(8208)

View file

@ -28,7 +28,7 @@ class TRAPDataTest extends Specification {
deploy.alternate mustEqual false deploy.alternate mustEqual false
deploy.v1 mustEqual true deploy.v1 mustEqual true
deploy.v2.isEmpty mustEqual true deploy.v2.isEmpty mustEqual true
deploy.v3 mustEqual false deploy.jammered mustEqual false
deploy.v4.contains(true) mustEqual true deploy.v4.contains(true) mustEqual true
deploy.v5.isEmpty mustEqual true deploy.v5.isEmpty mustEqual true
deploy.guid mustEqual PlanetSideGUID(4748) deploy.guid mustEqual PlanetSideGUID(4748)

View file

@ -71,7 +71,7 @@ class DetailedCharacterDataTest extends Specification {
a.data.bops mustEqual false a.data.bops mustEqual false
a.data.v1 mustEqual true a.data.v1 mustEqual true
a.data.v2.isEmpty 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.v4.isEmpty mustEqual true
a.data.v5.isEmpty mustEqual true a.data.v5.isEmpty mustEqual true
a.exosuit mustEqual ExoSuitType.Standard a.exosuit mustEqual ExoSuitType.Standard
@ -259,7 +259,7 @@ class DetailedCharacterDataTest extends Specification {
a.data.bops mustEqual false a.data.bops mustEqual false
a.data.v1 mustEqual false a.data.v1 mustEqual false
a.data.v2.isEmpty 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.v4.isEmpty mustEqual true
a.data.v5.isEmpty mustEqual true a.data.v5.isEmpty mustEqual true
a.exosuit mustEqual ExoSuitType.Standard a.exosuit mustEqual ExoSuitType.Standard
@ -444,7 +444,7 @@ class DetailedCharacterDataTest extends Specification {
a.data.bops mustEqual false a.data.bops mustEqual false
a.data.v1 mustEqual true a.data.v1 mustEqual true
a.data.v2.isEmpty 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.v4.isEmpty mustEqual true
a.data.v5.isEmpty mustEqual true a.data.v5.isEmpty mustEqual true
a.exosuit mustEqual ExoSuitType.MAX a.exosuit mustEqual ExoSuitType.MAX
@ -652,7 +652,7 @@ class DetailedCharacterDataTest extends Specification {
a.data.bops mustEqual false a.data.bops mustEqual false
a.data.v1 mustEqual true a.data.v1 mustEqual true
a.data.v2.isEmpty 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.v4.isEmpty mustEqual true
a.data.v5.isEmpty mustEqual true a.data.v5.isEmpty mustEqual true
a.exosuit mustEqual ExoSuitType.Agile a.exosuit mustEqual ExoSuitType.Agile
@ -1104,7 +1104,7 @@ class DetailedCharacterDataTest extends Specification {
cdata.data.alternate mustEqual false cdata.data.alternate mustEqual false
cdata.data.v1 mustEqual true cdata.data.v1 mustEqual true
cdata.data.v2.isEmpty 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.v4.isEmpty mustEqual true
cdata.data.v5.isEmpty mustEqual true cdata.data.v5.isEmpty mustEqual true
cdata.data.guid mustEqual PlanetSideGUID(0) cdata.data.guid mustEqual PlanetSideGUID(0)
@ -1160,7 +1160,7 @@ class DetailedCharacterDataTest extends Specification {
a.data.alternate mustEqual false a.data.alternate mustEqual false
a.data.v1 mustEqual false a.data.v1 mustEqual false
a.data.v2.isEmpty 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.v4.isEmpty mustEqual true
a.data.v5.isEmpty mustEqual true a.data.v5.isEmpty mustEqual true
a.exosuit mustEqual ExoSuitType.Standard a.exosuit mustEqual ExoSuitType.Standard
@ -1310,7 +1310,7 @@ class DetailedCharacterDataTest extends Specification {
a.data.alternate mustEqual false a.data.alternate mustEqual false
a.data.v1 mustEqual false a.data.v1 mustEqual false
a.data.v2.isEmpty 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.v4.isEmpty mustEqual true
a.data.v5.isEmpty mustEqual true a.data.v5.isEmpty mustEqual true
a.exosuit mustEqual ExoSuitType.Standard a.exosuit mustEqual ExoSuitType.Standard

View file

@ -31,7 +31,7 @@ class DetailedConstructionToolDataTest extends Specification {
cdata.alternate mustEqual false cdata.alternate mustEqual false
cdata.v1 mustEqual true cdata.v1 mustEqual true
cdata.v2.isEmpty mustEqual true cdata.v2.isEmpty mustEqual true
cdata.v3 mustEqual false cdata.jammered mustEqual false
cdata.v4.isEmpty mustEqual true cdata.v4.isEmpty mustEqual true
cdata.v5.isEmpty mustEqual true cdata.v5.isEmpty mustEqual true
cdata.guid mustEqual PlanetSideGUID(0) cdata.guid mustEqual PlanetSideGUID(0)
@ -71,7 +71,7 @@ class DetailedConstructionToolDataTest extends Specification {
cdata.alternate mustEqual false cdata.alternate mustEqual false
cdata.v1 mustEqual true cdata.v1 mustEqual true
cdata.v2.isEmpty mustEqual true cdata.v2.isEmpty mustEqual true
cdata.v3 mustEqual false cdata.jammered mustEqual false
cdata.v4.isEmpty mustEqual true cdata.v4.isEmpty mustEqual true
cdata.v5.isEmpty mustEqual true cdata.v5.isEmpty mustEqual true
cdata.guid mustEqual PlanetSideGUID(0) cdata.guid mustEqual PlanetSideGUID(0)
@ -111,7 +111,7 @@ class DetailedConstructionToolDataTest extends Specification {
cdata.alternate mustEqual false cdata.alternate mustEqual false
cdata.v1 mustEqual true cdata.v1 mustEqual true
cdata.v2.isEmpty mustEqual true cdata.v2.isEmpty mustEqual true
cdata.v3 mustEqual false cdata.jammered mustEqual false
cdata.v4.isEmpty mustEqual true cdata.v4.isEmpty mustEqual true
cdata.v5.contains(564) mustEqual true cdata.v5.contains(564) mustEqual true
cdata.guid mustEqual PlanetSideGUID(0) cdata.guid mustEqual PlanetSideGUID(0)
@ -139,7 +139,7 @@ class DetailedConstructionToolDataTest extends Specification {
cdata.alternate mustEqual false cdata.alternate mustEqual false
cdata.v1 mustEqual false cdata.v1 mustEqual false
cdata.v2.isEmpty mustEqual true cdata.v2.isEmpty mustEqual true
cdata.v3 mustEqual false cdata.jammered mustEqual false
cdata.v4.isEmpty mustEqual true cdata.v4.isEmpty mustEqual true
cdata.v5.isEmpty mustEqual true cdata.v5.isEmpty mustEqual true
cdata.guid mustEqual PlanetSideGUID(0) cdata.guid mustEqual PlanetSideGUID(0)

View file

@ -32,7 +32,7 @@ class MountedVehiclesTest extends Specification {
vdata.data.bops mustEqual false vdata.data.bops mustEqual false
vdata.data.alternate mustEqual false vdata.data.alternate mustEqual false
vdata.data.v1 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.v5.isEmpty mustEqual true
vdata.data.guid mustEqual PlanetSideGUID(3776) vdata.data.guid mustEqual PlanetSideGUID(3776)
vdata.health mustEqual 255 vdata.health mustEqual 255
@ -57,7 +57,7 @@ class MountedVehiclesTest extends Specification {
a.data.bops mustEqual false a.data.bops mustEqual false
a.data.v1 mustEqual false a.data.v1 mustEqual false
a.data.v2.isEmpty 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.v4.isEmpty mustEqual true
a.data.v5.isEmpty mustEqual true a.data.v5.isEmpty mustEqual true
a.exosuit mustEqual ExoSuitType.Agile a.exosuit mustEqual ExoSuitType.Agile

View file

@ -31,7 +31,7 @@ class NonstandardVehiclesTest extends Specification {
basic.data.alternate mustEqual false basic.data.alternate mustEqual false
basic.data.v1 mustEqual true basic.data.v1 mustEqual true
basic.data.v2.isDefined mustEqual false basic.data.v2.isDefined mustEqual false
basic.data.v3 mustEqual false basic.data.jammered mustEqual false
basic.data.v5.isDefined mustEqual false basic.data.v5.isDefined mustEqual false
basic.data.guid mustEqual PlanetSideGUID(0) basic.data.guid mustEqual PlanetSideGUID(0)

View file

@ -173,7 +173,7 @@ class NormalVehiclesTest extends Specification {
vdata.faction mustEqual PlanetSideEmpire.NC vdata.faction mustEqual PlanetSideEmpire.NC
vdata.alternate mustEqual false vdata.alternate mustEqual false
vdata.v1 mustEqual true vdata.v1 mustEqual true
vdata.v3 mustEqual false vdata.jammered mustEqual false
vdata.v5.isEmpty mustEqual true vdata.v5.isEmpty mustEqual true
vdata.guid mustEqual PlanetSideGUID(0) vdata.guid mustEqual PlanetSideGUID(0)

View file

@ -29,7 +29,7 @@ class UtilityVehiclesTest extends Specification {
ant.data.faction mustEqual PlanetSideEmpire.VS ant.data.faction mustEqual PlanetSideEmpire.VS
ant.data.alternate mustEqual false ant.data.alternate mustEqual false
ant.data.v1 mustEqual true ant.data.v1 mustEqual true
ant.data.v3 mustEqual false ant.data.jammered mustEqual false
ant.data.v5.isEmpty mustEqual true ant.data.v5.isEmpty mustEqual true
ant.data.guid mustEqual PlanetSideGUID(0) ant.data.guid mustEqual PlanetSideGUID(0)
ant.driveState mustEqual DriveState.Mobile ant.driveState mustEqual DriveState.Mobile
@ -59,7 +59,7 @@ class UtilityVehiclesTest extends Specification {
ams.data.faction mustEqual PlanetSideEmpire.VS ams.data.faction mustEqual PlanetSideEmpire.VS
ams.data.alternate mustEqual false ams.data.alternate mustEqual false
ams.data.v1 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.v5.isEmpty mustEqual true
ams.data.guid mustEqual PlanetSideGUID(2885) ams.data.guid mustEqual PlanetSideGUID(2885)
ams.driveState mustEqual DriveState.Deployed ams.driveState mustEqual DriveState.Deployed

View file

@ -324,7 +324,7 @@ class DamageModelTests extends Specification {
tplayer.Armor mustEqual 50 tplayer.Armor mustEqual 50
val resprojectile = ResolvedProjectile(ProjectileResolution.Hit, projectile, SourceEntry(tplayer), tplayer.DamageModel, Vector3.Zero) 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) func(tplayer)
tplayer.Health mustEqual 87 tplayer.Health mustEqual 87
@ -338,7 +338,7 @@ class DamageModelTests extends Specification {
tplayer.Armor mustEqual 50 tplayer.Armor mustEqual 50
val resprojectile = ResolvedProjectile(ProjectileResolution.Hit, projectile, SourceEntry(tplayer), tplayer.DamageModel, Vector3.Zero) 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) func(tplayer)
tplayer.Health mustEqual 98 tplayer.Health mustEqual 98
@ -352,7 +352,7 @@ class DamageModelTests extends Specification {
tplayer.Armor mustEqual 50 tplayer.Armor mustEqual 50
val resprojectile = ResolvedProjectile(ProjectileResolution.Hit, projectile, SourceEntry(tplayer), tplayer.DamageModel, Vector3.Zero) 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 tplayer.Armor = 0
func(tplayer) func(tplayer)
@ -365,7 +365,7 @@ class DamageModelTests extends Specification {
vehicle.Health mustEqual 650 vehicle.Health mustEqual 650
val resprojectile = ResolvedProjectile(ProjectileResolution.Hit, projectile, SourceEntry(vehicle), vehicle.DamageModel, Vector3.Zero) 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) func(vehicle)
vehicle.Health mustEqual 641 vehicle.Health mustEqual 641
@ -378,7 +378,7 @@ class DamageModelTests extends Specification {
vehicle.Shields mustEqual 10 vehicle.Shields mustEqual 10
val resprojectile = ResolvedProjectile(ProjectileResolution.Hit, projectile, SourceEntry(vehicle), vehicle.DamageModel, Vector3.Zero) 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) func(vehicle)
vehicle.Health mustEqual 650 vehicle.Health mustEqual 650
@ -392,7 +392,7 @@ class DamageModelTests extends Specification {
vehicle.Shields mustEqual 10 vehicle.Shields mustEqual 10
val resprojectile = ResolvedProjectile(ProjectileResolution.Hit, projectile, SourceEntry(vehicle), vehicle.DamageModel, Vector3.Zero) 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) func(vehicle)
vehicle.Health mustEqual 650 vehicle.Health mustEqual 650
@ -407,7 +407,7 @@ class DamageModelTests extends Specification {
vehicle.Health mustEqual 650 vehicle.Health mustEqual 650
val resprojectile = ResolvedProjectile(ProjectileResolution.Hit, projectile, SourceEntry(vehicle), vehicle.DamageModel, Vector3.Zero) 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) func(vehicle)
vehicle.Health mustEqual 641 vehicle.Health mustEqual 641

View file

@ -19,7 +19,7 @@ import scala.concurrent.duration._
class FacilityTurretTest extends Specification { class FacilityTurretTest extends Specification {
"FacilityTurretTest" should { "FacilityTurretTest" should {
"define" in { "define" in {
val obj = new TurretDefinition(480) val obj = new FacilityTurretDefinition(480)
obj.Weapons mustEqual mutable.HashMap.empty[TurretUpgrade.Value, ToolDefinition] obj.Weapons mustEqual mutable.HashMap.empty[TurretUpgrade.Value, ToolDefinition]
obj.ReserveAmmunition mustEqual false obj.ReserveAmmunition mustEqual false
obj.FactionLocked mustEqual true obj.FactionLocked mustEqual true
@ -152,7 +152,7 @@ class FacilityTurretControl3Test extends ActorTest {
class FacilityTurretControl4Test extends ActorTest { class FacilityTurretControl4Test extends ActorTest {
val player = Player(Avatar("", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) val player = Player(Avatar("", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
val objDef = new TurretDefinition(480) val objDef = new FacilityTurretDefinition(480)
objDef.FactionLocked = false objDef.FactionLocked = false
val obj = FacilityTurret(objDef) val obj = FacilityTurret(objDef)
obj.GUID = PlanetSideGUID(1) obj.GUID = PlanetSideGUID(1)

View file

@ -2,6 +2,7 @@
package objects package objects
import akka.actor.Props import akka.actor.Props
import akka.testkit.TestProbe
import base.ActorTest import base.ActorTest
import net.psforever.objects._ import net.psforever.objects._
import net.psforever.objects.ballistics.{PlayerSource, Projectile, ProjectileResolution, ResolvedProjectile} 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.serverobject.mount.Mountable
import net.psforever.objects.vehicles._ import net.psforever.objects.vehicles._
import net.psforever.objects.vital.{VehicleShieldCharge, Vitality} import net.psforever.objects.vital.{VehicleShieldCharge, Vitality}
import net.psforever.objects.zones.{Zone, ZoneMap}
import net.psforever.packet.game.PlanetSideGUID import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types._ import net.psforever.types._
import org.specs2.mutable._ import org.specs2.mutable._
import services.vehicle.{VehicleAction, VehicleServiceMessage}
import scala.concurrent.duration._ import scala.concurrent.duration._
@ -324,66 +327,82 @@ class VehicleControlStopMountingTest extends ActorTest {
val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy) val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
vehicle.GUID = PlanetSideGUID(3) vehicle.GUID = PlanetSideGUID(3)
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test") 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) vehicle.Actor.tell(Mountable.TryMount(player1, 0), probe.ref)
val reply = receiveOne(Duration.create(100, "ms")) val reply = probe.receiveOne(Duration.create(200, "ms"))
assert(reply.isInstanceOf[Mountable.MountMessages]) assert(reply.isInstanceOf[Mountable.MountMessages])
vehicle.Actor ! Vehicle.PrepareForDeletion vehicle.Actor.tell(Vehicle.PrepareForDeletion(), probe.ref)
vehicle.Actor ! Mountable.TryMount(player2, 1) vehicle.Actor.tell(Mountable.TryMount(player2, 1), probe.ref)
expectNoMsg(Duration.create(200, "ms")) probe.expectNoMsg(Duration.create(200, "ms")) //assertion failed: received unexpected message MountMessages(CanMount
} }
} }
} }
class VehicleControlRestartMountingTest extends ActorTest { 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 { "Vehicle Control" should {
"reactivate and resume handling mount messages" in { "reactivate and resume handling mount messages" in {
val player1 = Player(VehicleTest.avatar1) vehicle.Actor.tell(Mountable.TryMount(player1, 0), probe.ref)
player1.GUID = PlanetSideGUID(1) probe.receiveOne(Duration.create(200, "ms")) //discard
val player2 = Player(VehicleTest.avatar2) vehicle.Actor.tell(Vehicle.PrepareForDeletion(), probe.ref)
player2.GUID = PlanetSideGUID(2) vehicle.Actor.tell(Mountable.TryMount(player2, 1), probe.ref)
val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy) probe.expectNoMsg(Duration.create(200, "ms"))
vehicle.GUID = PlanetSideGUID(3)
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
vehicle.Actor ! Mountable.TryMount(player1, 0) vehicle.Actor.tell(Vehicle.Reactivate(), probe.ref)
receiveOne(Duration.create(100, "ms")) //discard vehicle.Actor.tell(Mountable.TryMount(player2, 1), probe.ref)
vehicle.Actor ! Vehicle.PrepareForDeletion val reply = probe.receiveOne(Duration.create(200, "ms"))
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"))
assert(reply.isInstanceOf[Mountable.MountMessages]) assert(reply.isInstanceOf[Mountable.MountMessages])
} }
} }
} }
class VehicleControlAlwaysDismountTest extends ActorTest { 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 { "Vehicle Control" should {
"always allow dismount messages" in { "always allow dismount messages" in {
val player1 = Player(VehicleTest.avatar1) vehicle.Actor.tell(Mountable.TryMount(player1, 0), probe.ref)
player1.GUID = PlanetSideGUID(1) probe.receiveOne(Duration.create(100, "ms")) //discard
val player2 = Player(VehicleTest.avatar2) vehicle.Actor.tell(Mountable.TryMount(player2, 1), probe.ref)
player2.GUID = PlanetSideGUID(2) probe.receiveOne(Duration.create(100, "ms")) //discard
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 ! Mountable.TryDismount(player2, 1) //player2 requests dismount vehicle.Actor.tell(Mountable.TryDismount(player2, 1), probe.ref) //player2 requests dismount
val reply1 = receiveOne(Duration.create(100, "ms")) val reply1 = probe.receiveOne(Duration.create(100, "ms"))
assert(reply1.isInstanceOf[Mountable.MountMessages]) assert(reply1.isInstanceOf[Mountable.MountMessages])
assert(reply1.asInstanceOf[Mountable.MountMessages].response.isInstanceOf[Mountable.CanDismount]) //player2 dismounts assert(reply1.asInstanceOf[Mountable.MountMessages].response.isInstanceOf[Mountable.CanDismount]) //player2 dismounts
vehicle.Actor ! Vehicle.PrepareForDeletion
vehicle.Actor ! Mountable.TryDismount(player1, 0) //player1 requests dismount vehicle.Actor.tell(Vehicle.PrepareForDeletion(), probe.ref)
val reply2 = receiveOne(Duration.create(100, "ms")) 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.isInstanceOf[Mountable.MountMessages])
assert(reply2.asInstanceOf[Mountable.MountMessages].response.isInstanceOf[Mountable.CanDismount]) //player1 dismounts assert(reply2.asInstanceOf[Mountable.MountMessages].response.isInstanceOf[Mountable.CanDismount]) //player1 dismounts
} }
@ -391,8 +410,9 @@ class VehicleControlAlwaysDismountTest extends ActorTest {
} }
class VehicleControlMountingBlockedExosuitTest extends ActorTest { class VehicleControlMountingBlockedExosuitTest extends ActorTest {
val probe = new TestProbe(system)
def checkCanNotMount() : Unit = { def checkCanNotMount() : Unit = {
val reply = receiveOne(Duration.create(100, "ms")) val reply = probe.receiveOne(Duration.create(100, "ms"))
reply match { reply match {
case msg : Mountable.MountMessages => case msg : Mountable.MountMessages =>
assert(msg.response.isInstanceOf[Mountable.CanNotMount]) assert(msg.response.isInstanceOf[Mountable.CanNotMount])
@ -402,7 +422,7 @@ class VehicleControlMountingBlockedExosuitTest extends ActorTest {
} }
def checkCanMount() : Unit = { def checkCanMount() : Unit = {
val reply = receiveOne(Duration.create(100, "ms")) val reply = probe.receiveOne(Duration.create(100, "ms"))
reply match { reply match {
case msg : Mountable.MountMessages => case msg : Mountable.MountMessages =>
assert(msg.response.isInstanceOf[Mountable.CanMount]) assert(msg.response.isInstanceOf[Mountable.CanMount])
@ -410,49 +430,49 @@ class VehicleControlMountingBlockedExosuitTest extends ActorTest {
assert(false) 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 { "Vehicle Control" should {
"block players from sitting if their exo-suit is not allowed by the seat" in { "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 //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() 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() 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() 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() 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() checkCanNotMount()
//allow //allow
vehicle.Actor ! Mountable.TryMount(player1, 1) vehicle.Actor.tell(Mountable.TryMount(player1, 1), probe.ref)
checkCanMount() checkCanMount()
vehicle.Actor ! Mountable.TryMount(player2, 9) vehicle.Actor.tell(Mountable.TryMount(player2, 9), probe.ref)
checkCanMount() checkCanMount()
vehicle.Actor ! Mountable.TryMount(player3, 0) vehicle.Actor.tell(Mountable.TryMount(player3, 0), probe.ref)
checkCanMount() checkCanMount()
} }
} }
} }
class VehicleControlMountingBlockedSeatPermissionTest extends ActorTest { class VehicleControlMountingBlockedSeatPermissionTest extends ActorTest {
val probe = new TestProbe(system)
def checkCanNotMount() : Unit = { def checkCanNotMount() : Unit = {
val reply = receiveOne(Duration.create(100, "ms")) val reply = probe.receiveOne(Duration.create(100, "ms"))
reply match { reply match {
case msg : Mountable.MountMessages => case msg : Mountable.MountMessages =>
assert(msg.response.isInstanceOf[Mountable.CanNotMount]) assert(msg.response.isInstanceOf[Mountable.CanNotMount])
@ -462,7 +482,7 @@ class VehicleControlMountingBlockedSeatPermissionTest extends ActorTest {
} }
def checkCanMount() : Unit = { def checkCanMount() : Unit = {
val reply = receiveOne(Duration.create(100, "ms")) val reply = probe.receiveOne(Duration.create(100, "ms"))
reply match { reply match {
case msg : Mountable.MountMessages => case msg : Mountable.MountMessages =>
assert(msg.response.isInstanceOf[Mountable.CanMount]) assert(msg.response.isInstanceOf[Mountable.CanMount])
@ -470,32 +490,33 @@ class VehicleControlMountingBlockedSeatPermissionTest extends ActorTest {
assert(false) 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 { "Vehicle Control" should {
//11 June 2018: Group is not supported yet so do not bother testing it //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 { "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.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() checkCanMount()
vehicle.PermissionGroup(2,0) //passenger group -> locked 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() checkCanNotMount()
} }
} }
} }
class VehicleControlMountingDriverSeatTest extends ActorTest { class VehicleControlMountingDriverSeatTest extends ActorTest {
val probe = new TestProbe(system)
def checkCanMount() : Unit = { def checkCanMount() : Unit = {
val reply = receiveOne(Duration.create(100, "ms")) val reply = probe.receiveOne(Duration.create(100, "ms"))
reply match { reply match {
case msg : Mountable.MountMessages => case msg : Mountable.MountMessages =>
assert(msg.response.isInstanceOf[Mountable.CanMount]) assert(msg.response.isInstanceOf[Mountable.CanMount])
@ -503,19 +524,18 @@ class VehicleControlMountingDriverSeatTest extends ActorTest {
assert(false) 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 { "Vehicle Control" should {
"allow players to sit in the driver seat, even if it is locked, if the vehicle is unowned" in { "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.PermissionGroup(0).contains(VehicleLockState.Locked)) //driver group -> locked
assert(vehicle.Seats(0).Occupant.isEmpty) assert(vehicle.Seats(0).Occupant.isEmpty)
assert(vehicle.Owner.isEmpty) assert(vehicle.Owner.isEmpty)
vehicle.Actor ! Mountable.TryMount(player1, 0) vehicle.Actor.tell(Mountable.TryMount(player1, 0), probe.ref)
checkCanMount() checkCanMount()
assert(vehicle.Seats(0).Occupant.nonEmpty) assert(vehicle.Seats(0).Occupant.nonEmpty)
} }
@ -523,8 +543,9 @@ class VehicleControlMountingDriverSeatTest extends ActorTest {
} }
class VehicleControlMountingOwnedLockedDriverSeatTest extends ActorTest { class VehicleControlMountingOwnedLockedDriverSeatTest extends ActorTest {
val probe = new TestProbe(system)
def checkCanNotMount() : Unit = { def checkCanNotMount() : Unit = {
val reply = receiveOne(Duration.create(100, "ms")) val reply = probe.receiveOne(Duration.create(100, "ms"))
reply match { reply match {
case msg : Mountable.MountMessages => case msg : Mountable.MountMessages =>
assert(msg.response.isInstanceOf[Mountable.CanNotMount]) assert(msg.response.isInstanceOf[Mountable.CanNotMount])
@ -534,7 +555,7 @@ class VehicleControlMountingOwnedLockedDriverSeatTest extends ActorTest {
} }
def checkCanMount() : Unit = { def checkCanMount() : Unit = {
val reply = receiveOne(Duration.create(100, "ms")) val reply = probe.receiveOne(Duration.create(100, "ms"))
reply match { reply match {
case msg : Mountable.MountMessages => case msg : Mountable.MountMessages =>
assert(msg.response.isInstanceOf[Mountable.CanMount]) assert(msg.response.isInstanceOf[Mountable.CanMount])
@ -542,30 +563,28 @@ class VehicleControlMountingOwnedLockedDriverSeatTest extends ActorTest {
assert(false) 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 { "Vehicle Control" should {
"block players that are not the current owner from sitting in the driver seat (locked)" in { "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.PermissionGroup(0).contains(VehicleLockState.Locked)) //driver group -> locked
assert(vehicle.Seats(0).Occupant.isEmpty) assert(vehicle.Seats(0).Occupant.isEmpty)
vehicle.Owner = player1.GUID vehicle.Owner = player1.GUID
vehicle.Actor ! Mountable.TryMount(player1, 0) vehicle.Actor.tell(Mountable.TryMount(player1, 0), probe.ref)
checkCanMount() checkCanMount()
assert(vehicle.Seats(0).Occupant.nonEmpty) assert(vehicle.Seats(0).Occupant.nonEmpty)
vehicle.Actor ! Mountable.TryDismount(player1, 0) vehicle.Actor.tell(Mountable.TryDismount(player1, 0), probe.ref)
receiveOne(Duration.create(100, "ms")) //discard probe.receiveOne(Duration.create(100, "ms")) //discard
assert(vehicle.Seats(0).Occupant.isEmpty) assert(vehicle.Seats(0).Occupant.isEmpty)
vehicle.Actor ! Mountable.TryMount(player2, 0) vehicle.Actor.tell(Mountable.TryMount(player2, 0), probe.ref)
checkCanNotMount() checkCanNotMount()
assert(vehicle.Seats(0).Occupant.isEmpty) assert(vehicle.Seats(0).Occupant.isEmpty)
} }
@ -573,8 +592,9 @@ class VehicleControlMountingOwnedLockedDriverSeatTest extends ActorTest {
} }
class VehicleControlMountingOwnedUnlockedDriverSeatTest extends ActorTest { class VehicleControlMountingOwnedUnlockedDriverSeatTest extends ActorTest {
val probe = new TestProbe(system)
def checkCanMount() : Unit = { def checkCanMount() : Unit = {
val reply = receiveOne(Duration.create(100, "ms")) val reply = probe.receiveOne(Duration.create(100, "ms"))
reply match { reply match {
case msg : Mountable.MountMessages => case msg : Mountable.MountMessages =>
assert(msg.response.isInstanceOf[Mountable.CanMount]) assert(msg.response.isInstanceOf[Mountable.CanMount])
@ -582,31 +602,29 @@ class VehicleControlMountingOwnedUnlockedDriverSeatTest extends ActorTest {
assert(false) 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 { "Vehicle Control" should {
"allow players that are not the current owner to sit in the driver seat (empire)" in { "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 vehicle.PermissionGroup(0,3) //passenger group -> empire
assert(vehicle.PermissionGroup(0).contains(VehicleLockState.Empire)) //driver group -> empire assert(vehicle.PermissionGroup(0).contains(VehicleLockState.Empire)) //driver group -> empire
assert(vehicle.Seats(0).Occupant.isEmpty) assert(vehicle.Seats(0).Occupant.isEmpty)
vehicle.Owner = player1.GUID //owner set vehicle.Owner = player1.GUID //owner set
vehicle.Actor ! Mountable.TryMount(player1, 0) vehicle.Actor.tell(Mountable.TryMount(player1, 0), probe.ref)
checkCanMount() checkCanMount()
assert(vehicle.Seats(0).Occupant.nonEmpty) assert(vehicle.Seats(0).Occupant.nonEmpty)
vehicle.Actor ! Mountable.TryDismount(player1, 0) vehicle.Actor.tell(Mountable.TryDismount(player1, 0), probe.ref)
receiveOne(Duration.create(100, "ms")) //discard probe.receiveOne(Duration.create(100, "ms")) //discard
assert(vehicle.Seats(0).Occupant.isEmpty) assert(vehicle.Seats(0).Occupant.isEmpty)
vehicle.Actor ! Mountable.TryMount(player2, 0) vehicle.Actor.tell(Mountable.TryMount(player2, 0), probe.ref)
checkCanMount() checkCanMount()
assert(vehicle.Seats(0).Occupant.nonEmpty) assert(vehicle.Seats(0).Occupant.nonEmpty)
} }
@ -614,26 +632,37 @@ class VehicleControlMountingOwnedUnlockedDriverSeatTest extends ActorTest {
} }
class VehicleControlShieldsChargingTest extends ActorTest { class VehicleControlShieldsChargingTest extends ActorTest {
val probe = new TestProbe(system)
val vehicle = Vehicle(GlobalDefinitions.fury) val vehicle = Vehicle(GlobalDefinitions.fury)
vehicle.GUID = PlanetSideGUID(10) vehicle.GUID = PlanetSideGUID(10)
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test") 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 { "charge vehicle shields" in {
assert(vehicle.Shields == 0) assert(vehicle.Shields == 0)
assert(!vehicle.History.exists({p => p.isInstanceOf[VehicleShieldCharge]})) assert(!vehicle.History.exists({p => p.isInstanceOf[VehicleShieldCharge]}))
vehicle.Actor ! Vehicle.ChargeShields(15)
val msg = receiveOne(500 milliseconds) vehicle.Actor ! Vehicle.ChargeShields(15)
assert(msg.isInstanceOf[Vehicle.UpdateShieldsCharge]) 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.Shields == 15)
assert(vehicle.History.exists({p => p.isInstanceOf[VehicleShieldCharge]})) assert(vehicle.History.exists({p => p.isInstanceOf[VehicleShieldCharge]}))
} }
} }
class VehicleControlShieldsNotChargingVehicleDeadTest extends ActorTest { class VehicleControlShieldsNotChargingVehicleDeadTest extends ActorTest {
val probe = new TestProbe(system)
val vehicle = Vehicle(GlobalDefinitions.fury) val vehicle = Vehicle(GlobalDefinitions.fury)
vehicle.GUID = PlanetSideGUID(10) vehicle.GUID = PlanetSideGUID(10)
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test") 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 { "not charge vehicle shields if the vehicle is destroyed" in {
assert(vehicle.Health > 0) assert(vehicle.Health > 0)
@ -641,18 +670,22 @@ class VehicleControlShieldsNotChargingVehicleDeadTest extends ActorTest {
assert(vehicle.Health == 0) assert(vehicle.Health == 0)
assert(vehicle.Shields == 0) assert(vehicle.Shields == 0)
assert(!vehicle.History.exists({p => p.isInstanceOf[VehicleShieldCharge]})) 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.Shields == 0)
assert(!vehicle.History.exists({p => p.isInstanceOf[VehicleShieldCharge]})) assert(!vehicle.History.exists({p => p.isInstanceOf[VehicleShieldCharge]}))
} }
} }
class VehicleControlShieldsNotChargingVehicleShieldsFullTest extends ActorTest { class VehicleControlShieldsNotChargingVehicleShieldsFullTest extends ActorTest {
val probe = new TestProbe(system)
val vehicle = Vehicle(GlobalDefinitions.fury) val vehicle = Vehicle(GlobalDefinitions.fury)
vehicle.GUID = PlanetSideGUID(10) vehicle.GUID = PlanetSideGUID(10)
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test") 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 { "not charge vehicle shields if the vehicle is destroyed" in {
assert(vehicle.Shields == 0) assert(vehicle.Shields == 0)
@ -661,54 +694,67 @@ class VehicleControlShieldsNotChargingVehicleShieldsFullTest extends ActorTest {
assert(!vehicle.History.exists({p => p.isInstanceOf[VehicleShieldCharge]})) assert(!vehicle.History.exists({p => p.isInstanceOf[VehicleShieldCharge]}))
vehicle.Actor ! Vehicle.ChargeShields(15) vehicle.Actor ! Vehicle.ChargeShields(15)
expectNoMsg(1 seconds) probe.expectNoMsg(1 seconds)
assert(!vehicle.History.exists({p => p.isInstanceOf[VehicleShieldCharge]})) assert(!vehicle.History.exists({p => p.isInstanceOf[VehicleShieldCharge]}))
} }
} }
class VehicleControlShieldsNotChargingTooEarlyTest extends ActorTest { class VehicleControlShieldsNotChargingTooEarlyTest extends ActorTest {
val probe = new TestProbe(system)
val vehicle = Vehicle(GlobalDefinitions.fury) val vehicle = Vehicle(GlobalDefinitions.fury)
vehicle.GUID = PlanetSideGUID(10) vehicle.GUID = PlanetSideGUID(10)
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test") 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 { "charge vehicle shields" in {
assert(vehicle.Shields == 0) assert(vehicle.Shields == 0)
vehicle.Actor ! Vehicle.ChargeShields(15)
val msg = receiveOne(200 milliseconds) vehicle.Actor ! Vehicle.ChargeShields(15)
assert(msg.isInstanceOf[Vehicle.UpdateShieldsCharge]) 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) 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) assert(vehicle.Shields == 15)
} }
} }
class VehicleControlShieldsNotChargingDamagedTest extends ActorTest { //TODO implement message protocol for zone startup completion
val vehicle = Vehicle(GlobalDefinitions.fury) //class VehicleControlShieldsNotChargingDamagedTest extends ActorTest {
vehicle.GUID = PlanetSideGUID(10) // val probe = new TestProbe(system)
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test") // val vehicle = Vehicle(GlobalDefinitions.fury)
// // vehicle.GUID = PlanetSideGUID(10)
val beamer_wep = Tool(GlobalDefinitions.beamer) // vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
val p_source = PlayerSource( Player(Avatar("TestTarget", PlanetSideEmpire.NC, CharacterGender.Female, 1, CharacterVoice.Mute)) ) // vehicle.Zone = new Zone("test", new ZoneMap("test"), 0) {
val projectile = Projectile(beamer_wep.Projectile, GlobalDefinitions.beamer, beamer_wep.FireMode, p_source, GlobalDefinitions.beamer.ObjectId, Vector3.Zero, Vector3.Zero) // VehicleEvents = probe.ref
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) // //
// val beamer_wep = Tool(GlobalDefinitions.beamer)
"not charge vehicle shields if recently damaged" in { // val p_source = PlayerSource( Player(Avatar("TestTarget", PlanetSideEmpire.NC, CharacterGender.Female, 1, CharacterVoice.Mute)) )
assert(vehicle.Shields == 0) // val projectile = Projectile(beamer_wep.Projectile, GlobalDefinitions.beamer, beamer_wep.FireMode, p_source, GlobalDefinitions.beamer.ObjectId, Vector3.Zero, Vector3.Zero)
vehicle.Actor ! Vitality.Damage({case v : Vehicle => v.History(obj)}) // 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)
val msg = receiveOne(200 milliseconds) //
assert(msg.isInstanceOf[Vitality.DamageResolution]) // "not charge vehicle shields if recently damaged" in {
assert(vehicle.Shields == 0) // assert(vehicle.Shields == 0)
vehicle.Actor ! Vehicle.ChargeShields(15) // vehicle.Actor.tell(Vitality.Damage({case v : Vehicle => v.History(obj); obj }), probe.ref)
//
expectNoMsg(200 milliseconds) // val msg = probe.receiveOne(200 milliseconds)
assert(vehicle.Shields == 0) // 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 { object VehicleTest {
import net.psforever.objects.Avatar import net.psforever.objects.Avatar

View file

@ -71,7 +71,9 @@ import services.support.SupportActor
import scala.collection.mutable import scala.collection.mutable
class WorldSessionActor extends Actor with MDCContextAware { class WorldSessionActor extends Actor
with MDCContextAware
with JammableBehavior {
import WorldSessionActor._ import WorldSessionActor._
private[this] val log = org.log4s.getLogger private[this] val log = org.log4s.getLogger
@ -151,6 +153,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
var timeDL : Long = 0 var timeDL : Long = 0
var timeSurge : Long = 0 var timeSurge : Long = 0
var skipStaminaRegenForTurns : Int = 0
lazy val unsignedIntMaxValue : Long = Int.MaxValue.toLong * 2L + 1L lazy val unsignedIntMaxValue : Long = Int.MaxValue.toLong * 2L + 1L
var serverTime : Long = 0 var serverTime : Long = 0
@ -174,12 +177,13 @@ class WorldSessionActor extends Actor with MDCContextAware {
import scala.language.implicitConversions import scala.language.implicitConversions
implicit def boolToInt(b : Boolean) : Int = if(b) 1 else 0 implicit def boolToInt(b : Boolean) : Int = if(b) 1 else 0
def JammableObject = player
override def postStop() : Unit = { override def postStop() : Unit = {
//TODO normally, player avatar persists a minute or so after disconnect; we are subject to the SessionReaper //TODO normally, player avatar persists a minute or so after disconnect; we are subject to the SessionReaper
clientKeepAlive.cancel clientKeepAlive.cancel
reviveTimer.cancel reviveTimer.cancel
respawnTimer.cancel respawnTimer.cancel
PlayerActionsToCancel()
chatService ! Service.Leave() chatService ! Service.Leave()
continent.AvatarEvents ! Service.Leave() continent.AvatarEvents ! Service.Leave()
continent.LocalEvents ! Service.Leave() continent.LocalEvents ! Service.Leave()
@ -187,6 +191,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
galaxyService ! Service.Leave() galaxyService ! Service.Leave()
LivePlayerList.Remove(sessionId) LivePlayerList.Remove(sessionId)
if(player != null && player.HasGUID) { if(player != null && player.HasGUID) {
PlayerActionsToCancel()
squadService ! Service.Leave(Some(player.CharId.toString)) squadService ! Service.Leave(Some(player.CharId.toString))
val player_guid = player.GUID val player_guid = player.GUID
//handle orphaned deployables //handle orphaned deployables
@ -316,7 +321,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
context.stop(self) context.stop(self)
} }
def Started : Receive = { def Started : Receive = jammableBehavior.orElse {
case ServiceManager.LookupResult("chat", endpoint) => case ServiceManager.LookupResult("chat", endpoint) =>
chatService = endpoint chatService = endpoint
log.info("ID: " + sessionId + " Got chat service " + 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) continent.Deployables ! Zone.Deployable.Dismiss(obj)
} }
case WorldSessionActor.FinalizeDeployable(obj : TurretDeployable, tool, index) => case WorldSessionActor.FinalizeDeployable(obj : SensorDeployable, tool, index) =>
//spitfires and deployable field turrets //motion alarm sensor and sensor disruptor
StartBundlingPackets()
DeployableBuildActivity(obj)
CommonDestroyConstructionItem(tool, index)
FindReplacementConstructionItem(tool, index)
StopBundlingPackets()
case WorldSessionActor.FinalizeDeployable(obj : ComplexDeployable, tool, index) =>
//deployable_shield_generator
StartBundlingPackets() StartBundlingPackets()
DeployableBuildActivity(obj) DeployableBuildActivity(obj)
continent.LocalEvents ! LocalServiceMessage(continent.Id, LocalAction.TriggerEffectInfo(player.GUID, "on", obj.GUID, true, 1000))
CommonDestroyConstructionItem(tool, index) CommonDestroyConstructionItem(tool, index)
FindReplacementConstructionItem(tool, index) FindReplacementConstructionItem(tool, index)
StopBundlingPackets() StopBundlingPackets()
@ -955,11 +953,10 @@ class WorldSessionActor extends Actor with MDCContextAware {
FindReplacementConstructionItem(tool, index) FindReplacementConstructionItem(tool, index)
StopBundlingPackets() StopBundlingPackets()
case WorldSessionActor.FinalizeDeployable(obj : SensorDeployable, tool, index) => case WorldSessionActor.FinalizeDeployable(obj : ComplexDeployable, tool, index) =>
//motion alarm sensor and sensor disruptor //spitfires and deployable field turrets and the deployable_shield_generator
StartBundlingPackets() StartBundlingPackets()
DeployableBuildActivity(obj) DeployableBuildActivity(obj)
continent.LocalEvents ! LocalServiceMessage(continent.Id, LocalAction.TriggerEffectInfo(player.GUID, "on", obj.GUID, true, 1000))
CommonDestroyConstructionItem(tool, index) CommonDestroyConstructionItem(tool, index)
FindReplacementConstructionItem(tool, index) FindReplacementConstructionItem(tool, index)
StopBundlingPackets() StopBundlingPackets()
@ -1011,7 +1008,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
StartBundlingPackets() StartBundlingPackets()
sendResponse(GenericObjectActionMessage(guid, 21)) //reset build cooldown sendResponse(GenericObjectActionMessage(guid, 21)) //reset build cooldown
sendResponse(ObjectDeployedMessage.Failure(definition.Name)) 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") log.warn(s"FinalizeDeployable: deployable will be cleaned up, but may not get unregistered properly")
TryDropConstructionTool(tool, index, obj.Position) TryDropConstructionTool(tool, index, obj.Position)
obj.Position = Vector3.Zero obj.Position = Vector3.Zero
@ -1100,10 +1097,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
case HackingProgress(progressType, tplayer, target, tool_guid, delta, completeAction, tickAction) => case HackingProgress(progressType, tplayer, target, tool_guid, delta, completeAction, tickAction) =>
HandleHackingProgress(progressType, tplayer, target, tool_guid, delta, completeAction, tickAction) HandleHackingProgress(progressType, tplayer, target, tool_guid, delta, completeAction, tickAction)
case Vitality.DamageResolution(target : Vehicle) => case Vitality.DamageResolution(target : TrapDeployable, _) =>
HandleVehicleDamageResolution(target)
case Vitality.DamageResolution(target : TrapDeployable) =>
//tank_traps //tank_traps
val guid = target.GUID val guid = target.GUID
val health = target.Health val health = target.Health
@ -1112,17 +1106,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
AnnounceDestroyDeployable(target, None) AnnounceDestroyDeployable(target, None)
} }
case Vitality.DamageResolution(target : SensorDeployable) => case Vitality.DamageResolution(target : TelepadDeployable, _) =>
//sensors //telepads
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
if(target.Health <= 0) { if(target.Health <= 0) {
//update if destroyed //update if destroyed
val guid = target.GUID val guid = target.GUID
@ -1130,27 +1115,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
AnnounceDestroyDeployable(target, Some(0 seconds)) AnnounceDestroyDeployable(target, Some(0 seconds))
} }
case Vitality.DamageResolution(target : TurretDeployable) => case Vitality.DamageResolution(target : PlanetSideGameObject, _) =>
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) =>
log.warn(s"Vital target ${target.Definition.Name} damage resolution not supported using this method") 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) => case ResponseToSelf(pkt) =>
log.info(s"Received a direct message: $pkt") log.info(s"Received a direct message: $pkt")
sendResponse(pkt) sendResponse(pkt)
@ -1246,22 +1213,19 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(GenericObjectActionMessage(guid, 9)) sendResponse(GenericObjectActionMessage(guid, 9))
case AvatarResponse.EnvironmentalDamage(target, amount) => 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 val originalHealth = player.Health
player.Health = player.Health - amount player.Health = originalHealth - amount
sendResponse(PlanetsideAttributeMessage(target, 0, player.Health)) sendResponse(PlanetsideAttributeMessage(target, 0, player.Health))
continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(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") damageLog.info(s"${player.Name}-infantry: BEFORE=$originalHealth/$armor/$capacitor, AFTER=${player.Health}/$armor/$capacitor, CHANGE=$amount/0/0")
if(amount != 0) { if(player.Health == 0 && player.isAlive) {
val playerGUID = player.GUID KillPlayer(player)
sendResponse(PlanetsideAttributeMessage(playerGUID, 0, player.Health)) }
continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(playerGUID, 0, player.Health)) else {
if(player.Health == 0 && player.isAlive) { //todo: History?
KillPlayer(player)
}
else {
//todo: History?
}
} }
} }
@ -1270,26 +1234,24 @@ class WorldSessionActor extends Actor with MDCContextAware {
val originalHealth = player.Health val originalHealth = player.Health
val originalArmor = player.Armor val originalArmor = player.Armor
val originalCapacitor = player.Capacitor.toInt val originalCapacitor = player.Capacitor.toInt
resolution_function(target) val cause = resolution_function(target)
val health = player.Health val health = player.Health
val armor = player.Armor val armor = player.Armor
val capacitor = player.Capacitor.toInt val capacitor = player.Capacitor.toInt
val damageToHealth = originalHealth - health val damageToHealth = originalHealth - health
val damageToArmor = originalArmor - armor val damageToArmor = originalArmor - armor
val damageToCapacitor = originalCapacitor - capacitor 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) { 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 val playerGUID = player.GUID
if(damageToHealth > 0) { if(damageToHealth > 0) {
sendResponse(PlanetsideAttributeMessage(playerGUID, 0, health)) sendResponse(PlanetsideAttributeMessage(playerGUID, 0, health))
continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(playerGUID, 0, health)) continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(playerGUID, 0, health))
} }
if(damageToArmor > 0) { if(damageToArmor > 0) {
sendResponse(PlanetsideAttributeMessage(playerGUID, 4, armor)) sendResponse(PlanetsideAttributeMessage(playerGUID, 4, armor))
continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(playerGUID, 4, armor)) continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(playerGUID, 4, armor))
} }
if(damageToCapacitor > 0) { if(damageToCapacitor > 0) {
sendResponse(PlanetsideAttributeMessage(playerGUID, 7, capacitor)) sendResponse(PlanetsideAttributeMessage(playerGUID, 7, capacitor))
} }
@ -1301,10 +1263,10 @@ class WorldSessionActor extends Actor with MDCContextAware {
KillPlayer(player) KillPlayer(player)
} }
else { else {
//first damage entry -> most recent damage source -> killing blow //damage cause -> recent damage source -> killing blow?
target.History.find(p => p.isInstanceOf[DamagingActivity]) match { cause match {
case Some(data : DamageFromProjectile) => case data : ResolvedProjectile =>
val owner = data.data.projectile.owner val owner = data.projectile.owner
owner match { owner match {
case pSource : PlayerSource => case pSource : PlayerSource =>
continent.LivePlayers.find(_.Name == pSource.Name) match { continent.LivePlayers.find(_.Name == pSource.Name) match {
@ -1316,11 +1278,14 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(DamageWithPositionMessage(damageToHealth + damageToArmor, vSource.Position)) sendResponse(DamageWithPositionMessage(damageToHealth + damageToArmor, vSource.Position))
case _ => ; case _ => ;
} }
continent.Activity ! Zone.HotSpot.Activity(owner, data.Target, target.Position) continent.Activity ! Zone.HotSpot.Activity(owner, data.target, target.Position)
case _ => ; case _ => ;
} }
} }
} }
if(target.isAlive && cause.projectile.profile.JammerProjectile) {
self ! JammableUnit.Jammered(cause)
}
} }
case AvatarResponse.Destroy(victim, killer, weapon, pos) => 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) => case AvatarResponse.ProjectileState(projectile_guid, shot_pos, shot_vel, shot_orient, seq, end, target_guid) =>
if(tplayer_guid != guid) { if(tplayer_guid != guid) {
sendResponse(ProjectileStateMessage(projectile_guid, shot_pos, shot_vel, shot_orient, seq, end, target_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) => case AvatarResponse.PutDownFDU(target) =>
if(tplayer_guid != guid) { if(tplayer_guid != guid) {
@ -1556,6 +1515,19 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(DeployableObjectsInfoMessage(behavior, deployInfo)) 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) => case LocalResponse.DoorOpens(door_guid) =>
if(tplayer_guid != guid) { if(tplayer_guid != guid) {
sendResponse(GenericObjectStateMsg(door_guid, 16)) 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) 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) => case LocalResponse.EliminateDeployable(obj : ComplexDeployable, guid, pos) =>
if(obj.Health == 0) { if(obj.Health == 0) {
DeconstructDeployable(obj, guid, pos) DeconstructDeployable(obj, guid, pos)
@ -1580,14 +1560,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
DeconstructDeployable(obj, guid, pos, obj.Orientation, 1) 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) => case LocalResponse.EliminateDeployable(obj : TelepadDeployable, guid, pos) =>
//if active, deactivate //if active, deactivate
if(obj.Active) { if(obj.Active) {
@ -2458,7 +2430,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
case VehicleResponse.ConcealPlayer(player_guid) => case VehicleResponse.ConcealPlayer(player_guid) =>
sendResponse(GenericObjectActionMessage(player_guid, 9)) sendResponse(GenericObjectActionMessage(player_guid, 9))
//sendResponse(PlanetsideAttributeMessage(player_guid, 29, 1))
case VehicleResponse.DismountVehicle(bailType, wasKickedByDriver) => case VehicleResponse.DismountVehicle(bailType, wasKickedByDriver) =>
if(tplayer_guid != guid) { if(tplayer_guid != guid) {
@ -2934,189 +2905,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
msgs 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 * na
* @param tplayer na * @param tplayer na
@ -3548,6 +3336,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
player.Slot(39).Equipment = SimpleItem(remote_electronics_kit) player.Slot(39).Equipment = SimpleItem(remote_electronics_kit)
player.Locker.Inventory += 0 -> SimpleItem(remote_electronics_kit) player.Locker.Inventory += 0 -> SimpleItem(remote_electronics_kit)
player.Inventory.Items.foreach { _.obj.Faction = faction } player.Inventory.Items.foreach { _.obj.Faction = faction }
player.Actor = self
//TODO end temp player character auto-loading //TODO end temp player character auto-loading
self ! ListAccountCharacters self ! ListAccountCharacters
import scala.concurrent.ExecutionContext.Implicits.global 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) => 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 isMoving = WorldEntity.isMoving(vel)
val isMovingPlus = isMoving || is_jumping || jump_thrust
//implants and stamina management start //implants and stamina management start
val implantsAreActive = avatar.Implants(0).Active || avatar.Implants(1).Active val implantsAreActive = avatar.Implants(0).Active || avatar.Implants(1).Active
val staminaBefore = player.Stamina val staminaBefore = player.Stamina
@ -3919,14 +3709,23 @@ class WorldSessionActor extends Actor with MDCContextAware {
} }
} }
} }
CapacitorTick(jump_thrust) if(skipStaminaRegenForTurns > 0) {
//if the player lost all stamina this turn (had stamina at the start), do not renew 1 stamina //do not renew stamina for a while
if(!isMoving && (if(player.Stamina > 0) player.Stamina < player.MaxStamina else !hadStaminaBefore)) { skipStaminaRegenForTurns -= 1
player.Stamina = player.Stamina + 1 player.Stamina > 0
true }
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 { else {
player.Stamina > 0 player.Stamina = player.Stamina + 1
true
} }
} }
else { else {
@ -3938,18 +3737,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(PlanetsideAttributeMessage(player.GUID, 2, player.Stamina)) sendResponse(PlanetsideAttributeMessage(player.GUID, 2, player.Stamina))
} }
if(implantsAreActive && !hasStaminaAfter) { //implants deactivated at 0 stamina if(implantsAreActive && !hasStaminaAfter) { //implants deactivated at 0 stamina
if(avatar.Implants(0).Active) { DeactivateImplants()
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
}
} }
//implants and stamina management finish //implants and stamina management finish
player.Position = pos player.Position = pos
@ -3959,8 +3747,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
player.Crouching = is_crouching player.Crouching = is_crouching
player.Jumping = is_jumping player.Jumping = is_jumping
player.Cloaked = player.ExoSuit == ExoSuitType.Infiltration && is_cloaking player.Cloaked = player.ExoSuit == ExoSuitType.Infiltration && is_cloaking
CapacitorTick(jump_thrust)
if(isMoving && usingMedicalTerminal.isDefined) { if(isMovingPlus && usingMedicalTerminal.isDefined) {
continent.GUID(usingMedicalTerminal) match { continent.GUID(usingMedicalTerminal) match {
case Some(term : Terminal with ProximityUnit) => case Some(term : Terminal with ProximityUnit) =>
StopUsingProximityUnit(term) StopUsingProximityUnit(term)
@ -3978,7 +3767,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
accessedContainer = None accessedContainer = None
} }
case Some(container) => //just in case case Some(container) => //just in case
if(isMoving) { if(isMovingPlus) {
val guid = player.GUID 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 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) { 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.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Start(playerGUID, item_guid))
continent.GUID(trigger.Companion) match { continent.GUID(trigger.Companion) match {
case Some(boomer : BoomerDeployable) => case Some(boomer : BoomerDeployable) =>
val boomerGUID = boomer.GUID
boomer.Exploded = true boomer.Exploded = true
sendResponse(TriggerEffectMessage(boomerGUID, "detonate_boomer")) continent.LocalEvents ! LocalServiceMessage(continent.Id, LocalAction.Detonate(boomer.GUID, boomer))
sendResponse(PlanetsideAttributeMessage(boomerGUID, 29, 1)) Deployables.AnnounceDestroyDeployable(boomer, Some(500 milliseconds))
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)))
case Some(_) | None => ; case Some(_) | None => ;
} }
FindEquipmentToDelete(item_guid, trigger) FindEquipmentToDelete(item_guid, trigger)
@ -4545,6 +4328,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
case msg @ AvatarJumpMessage(state) => case msg @ AvatarJumpMessage(state) =>
//log.info("AvatarJump: " + msg) //log.info("AvatarJump: " + msg)
player.Stamina = player.Stamina - 10 player.Stamina = player.Stamina - 10
skipStaminaRegenForTurns = 5
sendResponse(PlanetsideAttributeMessage(player.GUID, 2, player.Stamina)) sendResponse(PlanetsideAttributeMessage(player.GUID, 2, player.Stamina))
case msg @ ZipLineMessage(player_guid,origin_side,action,id,pos) => 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) => 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 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) // TODO: Not all incoming UseItemMessage's respond with another UseItemMessage (i.e. doors only send out GenericObjectStateMsg)
continent.GUID(object_guid) match { continent.GUID(object_guid) match {
@ -5495,6 +5279,15 @@ class WorldSessionActor extends Actor with MDCContextAware {
case _ => ; 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) => case msg @ WeaponFireMessage(seq_time, weapon_guid, projectile_guid, shot_origin, unk1, unk2, unk3, unk4, unk5, unk6, unk7) =>
log.info(s"WeaponFire: $msg") log.info(s"WeaponFire: $msg")
if(player.isShielded) { if(player.isShielded) {
@ -5609,6 +5402,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
case Some(projectile) => case Some(projectile) =>
projectile.Position = explosion_pos projectile.Position = explosion_pos
projectile.Velocity = projectile_vel projectile.Velocity = projectile_vel
//direct_victim_uid
continent.GUID(direct_victim_uid) match { continent.GUID(direct_victim_uid) match {
case Some(target : PlanetSideGameObject with FactionAffinity with Vitality) => case Some(target : PlanetSideGameObject with FactionAffinity with Vitality) =>
ResolveProjectileEntry(projectile, ProjectileResolution.Splash, target, target.Position) match { ResolveProjectileEntry(projectile, ProjectileResolution.Splash, target, target.Position) match {
@ -5618,6 +5412,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
} }
case _ => ; case _ => ;
} }
//other victims
targets.foreach(elem => { targets.foreach(elem => {
continent.GUID(elem.uid) match { continent.GUID(elem.uid) match {
case Some(target : PlanetSideGameObject with FactionAffinity with Vitality) => 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) val orient : Vector3 = Vector3(0f, 0f, sourceOrientZ)
continent.Ground ! Zone.Ground.DropItem(item2, pos, orient) continent.Ground ! Zone.Ground.DropItem(item2, pos, orient)
sendResponse(ObjectDetachMessage(destination_guid, item2_guid, pos, sourceOrientZ)) //ground sendResponse(ObjectDetachMessage(destination_guid, item2_guid, pos, sourceOrientZ)) //ground
val objDef = item2.Definition val objDef = item2.Definition
destination match { destination match {
case obj : Vehicle => case obj : Vehicle =>
continent.VehicleEvents ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.UnstowEquipment(player_guid, item2_guid)) 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. * This is not a complete list but, for the purpose of enforcement, some pointers will be documented here.
*/ */
def PlayerActionsToCancel() : Unit = { def PlayerActionsToCancel() : Unit = {
CancelJammeredSound(player)
CancelJammeredStatus(player)
progressBarUpdate.cancel progressBarUpdate.cancel
progressBarValue = None progressBarValue = None
lastTerminalOrderFulfillment = true lastTerminalOrderFulfillment = true
skipStaminaRegenForTurns = 0
accessedContainer match { accessedContainer match {
case Some(obj : Vehicle) => case Some(obj : Vehicle) =>
if(obj.AccessingTrunk.contains(player.GUID)) { if(obj.AccessingTrunk.contains(player.GUID)) {
@ -8284,8 +8082,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
*/ */
def FindProximityUnitTargetsInScope(terminal : Terminal with ProximityUnit) : Seq[PlanetSideGameObject] = { def FindProximityUnitTargetsInScope(terminal : Terminal with ProximityUnit) : Seq[PlanetSideGameObject] = {
terminal.Definition.asInstanceOf[ProximityDefinition].TargetValidation.keySet collect { terminal.Definition.asInstanceOf[ProximityDefinition].TargetValidation.keySet collect {
case ProximityTarget.Player => Some(player) case EffectTarget.Category.Player => Some(player)
case ProximityTarget.Vehicle | ProximityTarget.Aircraft => continent.GUID(player.VehicleSeated) case EffectTarget.Category.Vehicle | EffectTarget.Category.Aircraft => continent.GUID(player.VehicleSeated)
} collect { } collect {
case Some(a) => a case Some(a) => a
} toSeq } 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] = { def ResolveProjectileEntry(projectile_guid : PlanetSideGUID, resolution : ProjectileResolution.Value, target : PlanetSideGameObject with FactionAffinity with Vitality, pos : Vector3) : Option[ResolvedProjectile] = {
FindProjectileEntry(projectile_guid) match { FindProjectileEntry(projectile_guid) match {
case Some(projectile) => case Some(projectile) =>
val index = projectile_guid.guid - Projectile.BaseUID ResolveProjectileEntry(projectile, resolution, target, pos)
ResolveProjectileEntry(projectile, index, resolution, target, pos)
case None => case None =>
log.warn(s"ResolveProjectile: expected projectile, but ${projectile_guid.guid} not found") log.warn(s"ResolveProjectile: expected projectile, but ${projectile_guid.guid} not found")
None 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. * na
* 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.
* @param projectile the projectile object * @param projectile the projectile object
* @param resolution the resolution status to promote the projectile * @param resolution the resolution status to promote the projectile
* @return a copy of the projectile * @return a copy of the projectile
@ -8637,15 +8431,14 @@ class WorldSessionActor extends Actor with MDCContextAware {
case obj : Player => case obj : Player =>
//damage is synchronized on the target player's `WSA` (results distributed from there) //damage is synchronized on the target player's `WSA` (results distributed from there)
continent.AvatarEvents ! AvatarServiceMessage(obj.Name, AvatarAction.Damage(player.GUID, obj, func)) 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`) case obj : Vehicle => obj.Actor ! Vitality.Damage(func)
obj.Actor ! Vitality.Damage(func) case obj : FacilityTurret => obj.Actor ! Vitality.Damage(func)
case obj : Deployable => case obj : ComplexDeployable => obj.Actor ! Vitality.Damage(func)
case obj : SimpleDeployable =>
//damage is synchronized on `LSA` (results returned to and distributed from this `WSA`) //damage is synchronized on `LSA` (results returned to and distributed from this `WSA`)
continent.LocalEvents ! Vitality.DamageOn(obj, func) 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 _ => ; case _ => ;
} }
} }
@ -9803,7 +9596,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
case GlobalDefinitions.ams if vehicle.Faction == player.Faction => case GlobalDefinitions.ams if vehicle.Faction == player.Faction =>
log.info("BeforeUnload: cleaning up after a mobile spawn vehicle ...") log.info("BeforeUnload: cleaning up after a mobile spawn vehicle ...")
continent.VehicleEvents ! VehicleServiceMessage(continent.Id, VehicleAction.UpdateAmsSpawnPoint(continent)) continent.VehicleEvents ! VehicleServiceMessage(continent.Id, VehicleAction.UpdateAmsSpawnPoint(continent))
None
case GlobalDefinitions.router => case GlobalDefinitions.router =>
//this may repeat for multiple players on the same continent but that's okay(?) //this may repeat for multiple players on the same continent but that's okay(?)
log.info("BeforeUnload: cleaning up after a router ...") log.info("BeforeUnload: cleaning up after a router ...")
@ -10270,11 +10062,94 @@ class WorldSessionActor extends Actor with MDCContextAware {
taskResolver ! UnregisterProjectile(projectile) taskResolver ! UnregisterProjectile(projectile)
projectiles(local_index) match { projectiles(local_index) match {
case Some(obj) if !obj.isResolved => obj.Miss case Some(obj) if !obj.isResolved => obj.Miss
case None => ; case _ => ;
} }
projectilesToCleanUp(local_index) = false 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) = { def failWithError(error : String) = {
log.error(error) log.error(error)
sendResponse(ConnectionClose()) 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) protected final case class SquadUIElement(name : String, index : Int, zone : Int, health : Int, armor : Int, position : Vector3)
private final case class NtuCharging(tplayer: Player, private final case class NtuCharging(tplayer: Player, vehicle: Vehicle)
vehicle: Vehicle)
private final case class NtuDischarging(tplayer: Player, vehicle: Vehicle, silo_guid: PlanetSideGUID) private final case class NtuDischarging(tplayer: Player, vehicle: Vehicle, silo_guid: PlanetSideGUID)
private final case class FinalizeDeployable(obj : PlanetSideGameObject with Deployable, tool : ConstructionItem, index : Int) private final case class FinalizeDeployable(obj : PlanetSideGameObject with Deployable, tool : ConstructionItem, index : Int)

View file

@ -5,6 +5,7 @@ import akka.actor.Props
import akka.routing.RandomPool import akka.routing.RandomPool
import actor.base.ActorTest import actor.base.ActorTest
import net.psforever.objects._ import net.psforever.objects._
import net.psforever.objects.ballistics.ResolvedProjectile
import net.psforever.objects.guid.{GUIDTask, TaskResolver} import net.psforever.objects.guid.{GUIDTask, TaskResolver}
import net.psforever.objects.zones.{Zone, ZoneActor, ZoneMap} import net.psforever.objects.zones.{Zone, ZoneActor, ZoneMap}
import net.psforever.packet.game.objectcreate.{DroppedItemData, ObjectClass, ObjectCreateMessageParent, PlacementData} import net.psforever.packet.game.objectcreate.{DroppedItemData, ObjectClass, ObjectCreateMessageParent, PlacementData}
@ -370,8 +371,8 @@ class ChangeFireStateStopTest extends ActorTest {
} }
class DamageTest extends ActorTest { class DamageTest extends ActorTest {
val test_func_ref : (Any)=>Unit = { val test_func_ref : Any=>ResolvedProjectile = {
def test_func(o : Any) : Unit = { } def test_func(o : Any) : ResolvedProjectile = { null }
test_func test_func
} }
val player = Player(Avatar("TestCharacter", PlanetSideEmpire.VS, CharacterGender.Female, 1, CharacterVoice.Voice1)) val player = Player(Avatar("TestCharacter", PlanetSideEmpire.VS, CharacterGender.Female, 1, CharacterVoice.Voice1))